Please go here for updates to this post:
Binding Visibility to a bool value in WPF
I was putting code in my ViewModel that returns Visibility but I didn’t really like that very much. If the UI is created with something other than WPF, that is really not going to work. Since I intend to do cross compile my code in Mono, which doesn’t have WPF but uses either Forms or GTK#, I have already encountered this issue. What I really want to use is bool.
The solution is IValueConverter. If you just want the code and don’t want to read this post, just scroll to the bottom and grab the
IValueConverter is part of PresentationFramework (in PresentationFramework.dll) so it isn’t available in Mono, but that is OK because you don’t instantiate it in the ViewModel, you use it in the View so it will only be used when the GUI is part of WPF. So if you are separating your View into a separate DLL, this would be included in the View DLL, that way when you compile everything else, with say a different GUI that uses GTK#, you won’t get a compiler error because PresentationFramework doesn’t exist in Mono.
BooleanToVisibilityConverter
Well, there is an object already created for you called BooleanToVisibilityConverter, but it is limited. True is converted Visibility.Visible. False is converted to Visibility.Collapsed.
Here we see a problem. Visibility has three possible values but a Boolean only has two.
Boolean |
Visibility |
True |
Visible |
False |
Collapsed |
|
Hidden |
This will cover many of the scenarios, but not all.
Here is how it would be used.
For this example, I have this Person class.
public class Person
{
public Person() { }
public String FirstName { get; set; }
public String LastName { get; set; }
public String Age { get; set; }
}
Here is a simple View for this object. It has a Grid that has a Label and TextBox for each property in the Person object. It also has a CheckBox. The CheckBox gives us a easy bool value, IsChecked. This works similar to a bool property in a ViewModel.
<Window x:Class="TestBooleanToVisibilityConverter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestBooleanToVisibilityConverter"
Title="MainWindow"
SizeToContent="WidthAndHeight"
>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Name="PersonViewGrid">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="First Name:" Grid.Column="0" Grid.Row="0" />
<TextBox Grid.Column="1" Grid.Row="0" Name="firstNameTextBox"
Text="{Binding Path=FirstName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" MinWidth="175" />
<Label Content="Last Name:" Grid.Column="0" Grid.Row="1" />
<TextBox Grid.Column="1" Grid.Row="1" Name="lastNameTextBox"
Text="{Binding Path=LastName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" MinWidth="175" />
<Label Content="Age:" Grid.Column="0" Grid.Row="2"
Visibility="{Binding IsChecked, ElementName=ShowAgeCheckBox, Converter={StaticResource BoolToVisConverter}}"/>
<TextBox Grid.Column="1" Grid.Row="2" Name="ageTextBox"
Text="{Binding Path=Age, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" MinWidth="175"
Visibility="{Binding IsChecked, ElementName=ShowAgeCheckBox, Converter={StaticResource BoolToVisConverter}}"/>
</Grid>
<Grid Grid.Row="1">
<CheckBox Name="ShowAgeCheckBox" Content="Show Age" />
</Grid>
</Grid>
</Window>
I am not using MVVM for this example, but instead there is a just a single object created in the code behind for demo purposes.
using System;
using System.Windows;
namespace TestBooleanToVisibilityConverter
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent(); Person p = new Person() { FirstName = "Michael", LastName = "Michaels", Age = "33" };
PersonViewGrid.DataContext = p;
}
}
}
Ok, now build the project and you will see that the Label and TextBox for Age are hidden until you check the box.
Writing your own Bool to Visibility Converter
Sometimes you may need to write you own Converter. For example, in the above project, it is annoying how the CheckBox moves up and down in position because Visibility.Collapsed is used instead of Visibility.Hidden. You may want to use Visibility.Hidden instead.
You can write your own Converter that returns Visibility.Hidden instead of Visibility.Collapsed.
BooleanToVisibleOrHidden.cs
using System;
using System.Windows.Data;
using System.Windows;
namespace TestBooleanToVisibilityConverter
{
class BoolToVisibleOrHidden : IValueConverter
{
#region Constructors
/// <summary>
/// The default constructor
/// </summary>
public BoolToVisibleOrHidden() { }
#endregion
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool bValue = (bool)value;
if (bValue)
return Visibility.Visible;
else
return Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible)
return true;
else
return false;
}
#endregion
}
}
Here we do the conversion ourselves and now we have a different conversion table.
Boolean |
Visibility |
True |
Visible |
|
Collapsed |
False |
Hidden |
Now replace the Converter object in your XAML. We only change Line 15.
<local:BoolToVisibleOrHidden x:Key="BoolToVisConverter"/>
This works, but it could be improved. This still leaves us having to choose between two objects.
Creating a Converter that supports a choice of Hidden or Collapsed.
Here we will provide a property that determines if we should collapse or not.
Add a property called Collapse and return the appropriate Visibility based on that property. Here is the new object. As you see, the code changes to add this feature is really just an empty bool property and an if statement that used the bool property.
using System;
using System.Windows.Data;
using System.Windows;
namespace TestBooleanToVisibilityConverter
{
class BoolToVisibleOrHidden : IValueConverter
{
#region Constructors
/// <summary>
/// The default constructor
/// </summary>
public BoolToVisibleOrHidden() { }
#endregion
#region Properties
public bool Collapse { get; set; }
#endregion
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool bValue = (bool)value;
if (bValue)
{
return Visibility.Visible;
}
else
{
if (Collapse)
return Visibility.Collapsed;
else
return Visibility.Hidden;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible)
return true;
else
return false;
}
#endregion
}
}
Now in your XAML you have the option to do nothing, which uses the bool default value false, or to set the Collapse property to true as shown below.
<local:BoolToVisibleOrHidden x:Key="BoolToVisConverter" Collapse="True"/>
We now support either feature with the following table. We probably at this point would rename the object to BooleanToVisibilityConverter, but Microsoft already took that object name so I will leave it named as is.
Boolean |
Visibility |
True |
Visible |
False – Collapse=True |
Collapsed |
False – Collapse=False |
Hidden |
We are starting to get a little more usability from one object.
Adding the Reverse feature so False is Visible and True is Collapsed or Hidden
Lets say we want to change the CheckBox so that instead of saying “Show Age” it says “Hide Age”.
<CheckBox Name="ShowAgeCheckBox" Content="Hide Age" />
Now we have to reverse the mapping. If Reverse=”True” we want the mapping to be like this:
Boolean |
Visibility |
False |
Visible |
True – Collapse=True |
Collapsed |
True – Collapse=False |
Hidden |
This is also quite simple. We add another bool property called Reverse. Then key of that in another if statement.
using System;
using System.Windows.Data;
using System.Windows;
namespace TestBooleanToVisibilityConverter
{
class BoolToVisibleOrHidden : IValueConverter
{
#region Constructors
/// <summary>
/// The default constructor
/// </summary>
public BoolToVisibleOrHidden() { }
#endregion
#region Properties
public bool Collapse { get; set; }
public bool Reverse { get; set; }
#endregion
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool bValue = (bool)value;
if (bValue != Reverse)
{
return Visibility.Visible;
}
else
{
if (Collapse)
return Visibility.Collapsed;
else
return Visibility.Hidden;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible)
return !Reverse;
else
return Reverse;
}
#endregion
}
}
Now you can reverse this very easily in the XAML.
<local:BoolToVisibleOrHidden x:Key="BoolToVisConverter" Collapse="True" Reverse="True" />
And now you have a much more full featured converter.
Additional Thoughts
I have to wonder why the developers didn’t do this originally with the BooleanToVisibilityConverter object. It is so simple. This is a perfect example of where Microsoft would benefit from Open Sourcing some of their code. A dozen people would have contributed this change by now if they had and all Microsoft would have to do is look at the submitted code, approve, and check it in.