I have this Markup:
<GroupBox BorderThickness="2">
<GroupBox.BorderBrush>
<SolidColorBrush x:Name="Border">
<SolidColorBrush.Color>
<MultiBinding Converter="{StaticResource ConnectionAndLoggedInToBorderBrush}">
<Binding Path="IsConnected"/>
<Binding Path="IsLoggedIn"/>
</MultiBinding>
</SolidColorBrush.Color>
</SolidColorBrush>
</GroupBox.BorderBrush>
In the code behind I have this line in the window_loaded method:
DataContext = uiManager;
uiManager is of type UIManager that has two public properties named IsConnected and IsLoggedIn.
This code fails at at startup because the values array in the Converter that is called by the Multibinding is not filled with booleans but have a value of DependencyProperty.UnsetValue.
When I use the markup below (and change the returntype of the converter) it does work.
<GroupBox BorderThickness="2">
<GroupBox.BorderBrush>
<MultiBinding Converter="{StaticResource ConnectionAndLoggedInToBorderBrush}">
<Binding Path="IsConnected"/>
<Binding Path="IsLoggedIn"/>
</MultiBinding>
</GroupBox.BorderBrush>
It seems that the binding set through the DataContext in the code behind fails in the first example, but works in the second one. Why?
For completeness below the UIManager class:
public class UIManager:IUIManager
{
#region Implementation of IUIManager
private const string IsLoggedInProperty = "IsLoggedIn";
private bool loggedIn;
private readonly object loggedInLock = new object();
public bool IsLoggedIn
{
get
{
lock (loggedInLock)
{
return loggedIn;
}
}
set
{
lock (loggedInLock)
{
if(value==loggedIn)return;
loggedIn = value;
OnPropertyChanged(IsLoggedInProperty);
}
}
}
private void OnPropertyChanged(string property)
{
if(PropertyChanged!=null)PropertyChanged(this,new PropertyChangedEventArgs(property));
}
private const string IsConnectedProperty = "IsConnected";
private bool isConnected;
private object isConnectedLock = new object();
public bool IsConnected
{
get
{
lock (isConnectedLock)
{
return isConnected;
}
}
set
{
lock (isConnectedLock)
{
if(value==isConnected)return;
isConnected = value;
OnPropertyChanged(IsConnectedProperty);
}
}
}
#endregion
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
EDIT: The conversion method for the failing XAML (it fails on the conversion to bool of values[0]:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var is_connected = (bool) values[0];
var is_loggedin = (bool) values[1];
return is_loggedin
? is_connected
? Colors.YellowGreen
: Colors.Red
: Colors.Gray;
}
for the working XAML:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var is_connected = (bool) values[0];
var is_loggedin = (bool) values[1];
return is_loggedin
? is_connected
? Brushes.YellowGreen
: Brushes.Red
: Brushes.Gray;
}
It's for reasons like this that you might want to consider learning MVVM. This pattern helps you to abstract the model and bindings so that you don't have to rely so heavily on DPs - you can simply bind to a notifiable property in a view-model instead.
There are several excellent articles on MVVM, so I'd suggest you start by reading the works of Karl Shifflett, Josh Smith, Marlon Grech and Sacha Barber.
My theory. Color is struct (cannot be null), so SolidColorBrush.Color = null is wrong. WPF cannot create SolidColorBrush, and you get exception.
BorderBrush is object (can be null), so GroupBox.BorderBrush = null is OK.
This SolidColorBrush is not an object but a FACTORY. It is instantiated only when needed, and at that point you already have attached DataContext.
Just my 2 cents.
Read my article, btw, could be useful if you need some weird Bindings or Animations with weird Converters. http://www.codeproject.com/KB/WPF/BindingHub.aspx
The problem has nothing to do with a
MultiBinding
or your converter.DependencyProperty.UnsetValue
means that the binding got no value. And indeed if you run in debug mode you can see binding errors in theOutput
window:So let's simplify the markup a bit and apply some diagnostics:
Applying the attached dependency property
PresentationTraceSources.TraceLevel
yields some more output:We see that the binding doesn't find a
DataContext
and the binding fails. When I change the windows's constructor so thatDataContext
is set before initializing the content the binding works:Which is weird since for bindings in other places this doesn't matter. Not sure why it doesn't work there so I can only offer workarounds. What works for example is creating the brush as a resource with the bindings (that resource could also be local to the
GroupBox
):I would suggest though to drop the
MultiBinding
and to do some pre-processing in theDataContext
if yourUIManager
class is some sort ofMVVM
ViewModel
.