Welcome back all.
Let’s continue our exploration of WPF.
Today we will unravel the purpose of Fallback and TargetNull values.
Introduction
In order to unravel this mystery, we can take help from our friend C#. If you have studied design patterns in C#, then you might be familiar with the
Null Object Design pattern.
Let me brief you a bit.
We basically assign some default value to object type, so whenever a null value is expected we can simply replace it with the assigned default value.
That way we can always have something to show rather than showing null values to the user.
WPF has this behaviour integrated so we can deal with null values in a much simpler way.
Let's understand this through an example.
Say we have a college admission form which has basic fields, such as
- Name: Textbox
- Date of birth: DateTime
- Age: Textbox
- Gender: RadioButtons for male & female
- Nationality: checkbox for specifying if nationality is Indian
- Register button
- Textblock for a message on successful admission.
Here is a glance at the UI: Specifying datatype of each field.
Mainwindow.xaml
- <Window x:Class="LearnWPF.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:LearnWPF.ViewModel"
- mc:Ignorable="d"
- Title="MainWindow" Height="200" Width="400">
- <Window.Resources>
- <local:MainWindowViewModel x:Key="VM" />
- <Style x:Key="SubmitButtonStyle"
- BasedOn="{StaticResource {x:Type Button}}"
- TargetType="Button">
- <Setter Property="Height" Value="25"/>
- <Setter Property="Width" Value="150"/>
- <Setter Property="BorderThickness" Value="2"/>
- <Setter Property="BorderThickness" Value="2"/>
- <Setter Property="HorizontalAlignment" Value="Center"/>
- <Setter Property="VerticalAlignment" Value="Center"/>
-
- <Setter Property="Content" Value="Trigger Not Applied"/>
- <Setter Property="BorderBrush" Value="Black"/>
- <Setter Property="Background" Value="Gray"/>
- <Setter Property="Foreground" Value="Wheat"/>
- <Style.Triggers>
- <MultiTrigger>
- <MultiTrigger.Conditions>
- <Condition Property="IsMouseOver" Value="True"/>
- <Condition Property="IsKeyboardFocused" Value="True"/>
- </MultiTrigger.Conditions>
- <MultiTrigger.Setters>
- <Setter Property="Content" Value="Trigger Applied"/>
- <Setter Property="BorderBrush" Value="White"/>
- <Setter Property="Background" Value="SkyBlue"/>
- <Setter Property="Foreground" Value="Black"/>
- </MultiTrigger.Setters>
- </MultiTrigger>
- </Style.Triggers>
- </Style>
-
- </Window.Resources>
- <Grid DataContext="{Binding Source={StaticResource VM}}">
- <Grid.RowDefinitions>
- <RowDefinition/>
- <RowDefinition/>
- </Grid.RowDefinitions>
-
- <StackPanel x:Name="MainGrid"
- Orientation="Horizontal"
- HorizontalAlignment="Center">
- <Button x:Name="ButtonStyleMe"
- Style="{StaticResource SubmitButtonStyle}"
- Command="{Binding StyledButtonCliked}"/>
- <Button x:Name="ButtonCancel"
- Content="Cancel"
- Height="25"
- Margin="10 0 0 0"
- Width="150"
- Command="{Binding CancelButtonCliked}"/>
- </StackPanel>
- <TextBlock Text="{Binding FocusText}"
- HorizontalAlignment="Center"
- Grid.Row="1"/>
- </Grid>
- </Window>
MainWindowViewModel.cs
- using System;
- using Prism.Commands;
- using Prism.Mvvm;
-
- namespace LearnWPF.ViewModel
- {
- public class MainWindowViewModel : BindableBase
- {
- #region Properties
- private bool _isButtonClicked;
- public bool IsButtonClicked
- {
- get { return _isButtonClicked; }
- set { SetProperty(ref _isButtonClicked, value); }
- }
-
- private string _userName;
- public string UserName
- {
- get { return _userName; }
- set { SetProperty(ref _userName, value); }
- }
-
- private DateTime _dob;
- public DateTime DOB
- {
- get { return _dob; }
- set
- {
- SetProperty(ref _dob , value);
- }
- }
-
- private string _age;
- public string Age
- {
- get { return _age; }
- set
- {
- SetProperty(ref _age , value);
- }
- }
-
- private bool _isNationalityIndian;
- public bool IsNationalityIndian
- {
- get { return _isNationalityIndian; }
- set { SetProperty(ref _isNationalityIndian, value); }
- }
-
- private bool _isMale = false;
- public bool IsMale
- {
- get { return _isMale; }
- set
- {
- SetProperty(ref _isMale, value);
- }
- }
-
- private bool _isFeMale = false;
- public bool IsFemale
- {
- get { return _isFeMale; }
- set
- {
- SetProperty(ref _isFeMale, value);
- }
- }
- public DelegateCommand<object> RegisterButtonClicked { get; set; }
- #endregion
-
- #region Constructor
- public MainWindowViewModel()
- {
- RegisterButtonClicked = new DelegateCommand<object>(RegisterUser);
- }
-
- #endregion
-
- #region Methods
- private void RegisterUser(object obj)
- {
- IsButtonClicked = true;
- }
- #endregion
-
- }
- }
If I run this application, all the controls will be filled with a default value of their respective dataTypes. As follows:
What is the problem here?
- Here, what if I don't want the user to see this useless information? Instead I want to show information, which really makes sense here.
- Such as having a sensible date rather than 1/1/0001 or
- No one with age 0 is going to the college or
- And most of the users will be Indians, so I want (Indian)Nationality to be checked by default.
Solution - TargetNullValue
- This is where we use TargetNullValue: TargetNullValue is set to thecontrol's bound property when the value of the bound property also known as source property is null for some reason.
- In layman's terms, when the target is null, this property will be assigned.
- Let's dig in to our code,
- For UserName - as string UserName is null, it will display "Dow Jones" instead of showing empty text block. Note: string is already null so TargetNullvalue will be triggered.
- Text="{Binding UserName,TargetNullValue='Dow Jones'}"
- For Date of Birth, rather than showing date's default value, we are going to display today's date. Note: we can't set DateTime to null so have to make nullable DateTime.
- SelectedDate="{Binding DOB, TargetNullValue={x:Static sys:DateTime.Now}}"
- Minimum age criteria for admission is 18, so let's set TargetNullValue to 18. Note: we can't set int to null so have to make nullable int.
- Text="{Binding Age, TargetNullValue=18}"
- For Nationality: bool's default value is false, but we are setting TargetNullValue to true, as it is one of our requirements. Note: we can't set the bool to null so have to make nullable bool.
- IsChecked="{Binding IsNationalityIndian, TargetNullValue=True}"
Finally, let's find out how it turned out to be.
Perfect! TargetNullValues have been successfully applied when source property was null for all 4 kinds of datatypes.
New Problem!!
- That is cool, we can now handle a situation when source value is null and can make sense out of data with TargetNullValues. But what if there is a typo in bound property or our application fails to bind property for any reason. In such a case, we will have no data to display.
The Solution: FallBackValue
- If your application is unable to find a bound property i.e. it fails to bind property then WPF will consider FallBackValue rather than displaying nothing.
- In our application let's say we change the bound properties name and let's add FallBackValues to those controls.
- Random_UserName instead of UserName
- Text="{Binding Random_UserName,FallbackValue='Dow Jones'}"
- Random_DOB instead of DOB
- SelectedDate="{Binding Random_DOB, FallbackValue={x:Static sys:DateTime.Now}}"
- Random_Age instead of Age
- Text="{Binding Random_Age, FallbackValue=18}"
- Random_IsNationalityIndian instead of IsNationalityIndian
- IsChecked="{Binding Random_IsNationalityIndian, FallbackValue=True}"
Let's check if it takes fallback values successfully!
Perfect... It works well.
Tip
We should always use both TargetNullValue & FallBackValue together, to have better control on error-prone code.
After following this tip your final code will look something like this,
MainWindow.xaml
- <Window x:Class="LearnWPF.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:LearnWPF.ViewModel"
- xmlns:sys="clr-namespace:System;assembly=mscorlib"
- mc:Ignorable="d"
- Title="MainWindow" Height="250" Width="400">
- <Window.Resources>
- <local:MainWindowViewModel x:Key="VM" />
- <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
- </Window.Resources>
-
- <Grid DataContext="{Binding Source={StaticResource VM}}"
- HorizontalAlignment="Center">
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="5*"/>
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"/>
- <ColumnDefinition Width="Auto"/>
- </Grid.ColumnDefinitions>
- <Label x:Name="LabelUserName"
- Content="User Name:"
- Margin="0 10 0 0"/>
- <Label x:Name="LabelDOB"
- Content="DOB:"
- Grid.Row="1"/>
- <Label x:Name="LabelAGE"
- Content="Age:"
- Grid.Row="2"/>
- <Label x:Name="LabelGender"
- Content="Gender:"
- Grid.Row="3"/>
- <Label x:Name="LabelNationality"
- Content="Nationality:"
- Grid.Row="4"/>
- <TextBox x:Name="TextBoxUserName"
- Text="{Binding UserName, FallbackValue='Rikam', TargetNullValue='Palkar'}"
- Height="20"
- Width="150"
- Margin="0 10 0 0"
- Grid.Column="1"/>
- <DatePicker x:Name="DatePickerDOB"
- SelectedDate="{Binding DOB, FallbackValue={x:Static sys:DateTime.Now}, TargetNullValue={x:Static sys:DateTime.Now}}"
- DisplayDateStart="1/1/1990"
- Width="150"
- Grid.Column="1"
- Grid.Row="1"/>
- <TextBox x:Name="TextBoxAge"
- Text="{Binding Age, FallbackValue=18, TargetNullValue=18}"
- Height="20"
- Width="150"
- Grid.Column="1"
- Grid.Row="2"/>
-
- <RadioButton x:Name="RadioButtonGenderMale"
- IsChecked="{Binding IsMale, FallbackValue=False, TargetNullValue=False}"
- Content="Male"
- Margin="-110 8 0 0"
- GroupName="Gender"
- HorizontalAlignment="Center"
- Grid.Row="3"
- Grid.Column="1"/>
- <RadioButton x:Name="RadioButtonGenderFeMale"
- IsChecked="{Binding IsFemale, FallbackValue=False, TargetNullValue=False}"
- Content="Female"
- Margin="30 8 0 0"
- GroupName="Gender"
- HorizontalAlignment="Center"
- Grid.Row="3"
- Grid.Column="1"/>
- <CheckBox x:Name="CheckBoxIsNationalityIndian"
- IsChecked="{Binding IsNationalityIndian, FallbackValue=True, TargetNullValue=True}"
- Margin="-50 8 0 0"
- Content="Are you Indian?"
- HorizontalAlignment="Center"
- VerticalAlignment="Top"
- Grid.Column="1"
- Grid.Row="4"/>
- <Button x:Name="ButtonLogin"
- Height="20"
- Width="100"
- Content="Register"
- HorizontalAlignment="Center"
- Margin="20 10 0 0"
- Command="{Binding RegisterButtonClicked}"
- Grid.Row="5"
- Grid.ColumnSpan="2"/>
-
- <TextBlock x:Name="TextBlockMessage"
- Visibility="{Binding IsButtonClicked, Converter={StaticResource BooleanToVisibilityConverter}}"
- Text="{Binding UserName, StringFormat='User: {0} is successfully registered!'}"
- HorizontalAlignment="Center"
- Margin="20 8 0 0"
- Grid.Row="6"
- Grid.ColumnSpan="2"/>
- </Grid>
- </Window>
Conclusion
- We learned whatTargetNull & FallBack values are, and how & when to use them.
- What the difference between them is.
- How to use them with different types of datatypes.
- I believe the given example was understandable as we have tried to cover the most. Please leave any suggestions with respect to FallbackValue and TargetNullValue.
- Want to be a friend? Connect with me @
Thank you all, I wish you all the very best...
As always, Happy Coding...