WPF MultiBinding Fails. Why?

12,923

Solution 1

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 the Output window:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=IsConnected; DataItem=null; target element is 'SolidColorBrush' (HashCode=17654054); target property is 'Color' (type 'Color')
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=IsLoggedIn; DataItem=null; target element is 'SolidColorBrush' (HashCode=17654054); target property is 'Color' (type 'Color')

So let's simplify the markup a bit and apply some diagnostics:

<GroupBox>
    <GroupBox.BorderBrush>
        <SolidColorBrush>
            <SolidColorBrush.Color>
                <Binding Path="GroupColor" PresentationTraceSources.TraceLevel="High"/>
            </SolidColorBrush.Color>
        </SolidColorBrush>
    </GroupBox.BorderBrush>
</GroupBox>

Applying the attached dependency property PresentationTraceSources.TraceLevel yields some more output:

System.Windows.Data Warning: 52 : Created BindingExpression (hash=17654054) for Binding (hash=44624228)
System.Windows.Data Warning: 54 :   Path: 'GroupColor'
System.Windows.Data Warning: 56 : BindingExpression (hash=17654054): Default mode resolved to OneWay
System.Windows.Data Warning: 57 : BindingExpression (hash=17654054): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 58 : BindingExpression (hash=17654054): Attach to System.Windows.Media.SolidColorBrush.Color (hash=52727599)
System.Windows.Data Warning: 60 : BindingExpression (hash=17654054): Use Framework mentor <null>
System.Windows.Data Warning: 63 : BindingExpression (hash=17654054): Resolving source 
System.Windows.Data Warning: 65 : BindingExpression (hash=17654054): Framework mentor not found
System.Windows.Data Warning: 61 : BindingExpression (hash=17654054): Resolve source deferred
System.Windows.Data Warning: 91 : BindingExpression (hash=17654054): Got InheritanceContextChanged event from SolidColorBrush (hash=52727599)
System.Windows.Data Warning: 63 : BindingExpression (hash=17654054): Resolving source 
System.Windows.Data Warning: 66 : BindingExpression (hash=17654054): Found data context element: GroupBox (hash=51393439) (OK)
System.Windows.Data Warning: 67 : BindingExpression (hash=17654054): DataContext is null
System.Windows.Data Warning: 91 : BindingExpression (hash=17654054): Got InheritanceContextChanged event from SolidColorBrush (hash=52727599)
System.Windows.Data Warning: 63 : BindingExpression (hash=17654054): Resolving source 
System.Windows.Data Warning: 65 : BindingExpression (hash=17654054): Framework mentor not found
System.Windows.Data Warning: 63 : BindingExpression (hash=17654054): Resolving source 
System.Windows.Data Warning: 65 : BindingExpression (hash=17654054): Framework mentor not found
System.Windows.Data Warning: 63 : BindingExpression (hash=17654054): Resolving source  (last chance)
System.Windows.Data Warning: 65 : BindingExpression (hash=17654054): Framework mentor not found
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=GroupColor; DataItem=null; target element is 'SolidColorBrush' (HashCode=52727599); target property is 'Color' (type 'Color')

We see that the binding doesn't find a DataContext and the binding fails. When I change the windows's constructor so that DataContext is set before initializing the content the binding works:

public Window1()
{
    DataContext = ...;
    InitializeComponent();
}

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):

<GroupBox BorderBrush="{DynamicResource resbrush}">
    <GroupBox.Resources>
        <SolidColorBrush x:Key="resbrush">
            <SolidColorBrush.Color>
                <MultiBinding Converter="{StaticResource ConnectionAndLoggedInToBorderBrush}">
                    <Binding Path="IsConnected"/>
                    <Binding Path="IsLoggedIn"/>
                </MultiBinding>
            </SolidColorBrush.Color>
        </SolidColorBrush>
    </GroupBox.Resources>
</GroupBox>

I would suggest though to drop the MultiBinding and to do some pre-processing in the DataContext if your UIManager class is some sort of MVVM ViewModel.

Solution 2

My theory. Color is struct (cannot be null), so SolidColorBrush.Color = null is wrong. WPF cannot create SolidColorBrush, and you get exception.

 <GroupBox.BorderBrush>
     <SolidColorBrush x:Name="Border">
         <SolidColorBrush.Color>
             <MultiBinding Converter="{StaticResource 
                           ConnectionAndLoggedInToBorderBrush}">
                 <Binding Path="IsConnected"/>
                 <Binding Path="IsLoggedIn"/>
             </MultiBinding>
         </SolidColorBrush.Color>
     </SolidColorBrush>
 </GroupBox.BorderBrush>

BorderBrush is object (can be null), so GroupBox.BorderBrush = null is OK.

 <GroupBox.BorderBrush>
      <MultiBinding Converter="{StaticResource 
                    ConnectionAndLoggedInToBorderBrush}">
           <Binding Path="IsConnected"/>
           <Binding Path="IsLoggedIn"/>
      </MultiBinding>
 </GroupBox.BorderBrush>

This SolidColorBrush is not an object but a FACTORY. It is instantiated only when needed, and at that point you already have attached DataContext.

 <GroupBox.Resources>
      <SolidColorBrush x:Key="resbrush">
           <SolidColorBrush.Color>
                <MultiBinding Converter="{StaticResource 
                              ConnectionAndLoggedInToBorderBrush}">
                     <Binding Path="IsConnected"/>
                     <Binding Path="IsLoggedIn"/>
                </MultiBinding>
           </SolidColorBrush.Color>
      </SolidColorBrush>
 </GroupBox.Resources>

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

Share:
12,923
Dabblernl
Author by

Dabblernl

MCTS in Windows Presentation Foundation since nov 18th 2009 Employed by Bridge Systems BV since may 2010 MCTS in Net 4.0 Data Access since sept 2012

Updated on July 09, 2022

Comments

  • Dabblernl
    Dabblernl almost 2 years

    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;
            }
    
  • Dabblernl
    Dabblernl over 14 years
    Kudos, setting the DataContext in the constructor in the code behind did the trick.