Сбой мультипривязки WPF.Почему?
-
22-07-2019 - |
Вопрос
У меня есть эта разметка:
<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>
В коде у меня есть эта строка в методе window_loaded:
DataContext = uiManager;
uiManager имеет тип UIManager, который имеет два общедоступных свойства с именами IsConnected и IsLoggedIn.
Этот код завершается сбоем при запуске, поскольку массив значений в преобразователе, вызываемом мультипривязкой, не заполнен логическими значениями, а имеет значение DependencyProperty.UnsetValue.
Когда я использую разметку ниже (и меняю возвращаемый тип конвертера), это работает.
<GroupBox BorderThickness="2">
<GroupBox.BorderBrush>
<MultiBinding Converter="{StaticResource ConnectionAndLoggedInToBorderBrush}">
<Binding Path="IsConnected"/>
<Binding Path="IsLoggedIn"/>
</MultiBinding>
</GroupBox.BorderBrush>
Кажется, что привязка, установленная через DataContext в коде, не работает в первом примере, но работает во втором.Почему?
Для полноты ниже класса UIManager:
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
}
РЕДАКТИРОВАТЬ:Метод преобразования для неудачного XAML (он не работает при преобразовании в bool значений[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;
}
для рабочего 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;
}
Решение
Проблема не имеет ничего общего с MultiBinding
или ваш конвертер. DependencyProperty.UnsetValue
означает, что привязка не получила значения.И действительно, если вы запустите в режиме отладки, вы можете увидеть ошибки привязки в Output
окно:
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')
Итак, давайте немного упростим разметку и применим некоторую диагностику:
<GroupBox>
<GroupBox.BorderBrush>
<SolidColorBrush>
<SolidColorBrush.Color>
<Binding Path="GroupColor" PresentationTraceSources.TraceLevel="High"/>
</SolidColorBrush.Color>
</SolidColorBrush>
</GroupBox.BorderBrush>
</GroupBox>
Применение прикрепленного свойства зависимости PresentationTraceSources.TraceLevel
дает еще несколько результатов:
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')
Мы видим, что привязка не находит DataContext
и привязка не удалась.Когда я меняю конструктор Windows так, чтобы DataContext
устанавливается перед инициализацией содержимого, привязка работает:
public Window1()
{
DataContext = ...;
InitializeComponent();
}
Что странно, поскольку для привязок в других местах это не имеет значения.Не знаю, почему там это не работает, поэтому могу только предложить обходные пути.Например, работает создание кисти как ресурса с привязками (этот ресурс также может быть локальным для 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>
Я бы предложил, однако, отказаться от MultiBinding
и выполнить некоторую предварительную обработку в DataContext
если ваш UIManager
класс какой-то MVVM
ViewModel
.
Другие советы
Моя теория.Цвет имеет структуру (не может быть нулевым), поэтому SolidColorBrush.Color = null является неправильным.WPF не может создать SolidColorBrush, и вы получаете исключение.
<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 — это объект (может иметь значение null), поэтому GroupBox.BorderBrush = null — это нормально.
<GroupBox.BorderBrush>
<MultiBinding Converter="{StaticResource
ConnectionAndLoggedInToBorderBrush}">
<Binding Path="IsConnected"/>
<Binding Path="IsLoggedIn"/>
</MultiBinding>
</GroupBox.BorderBrush>
Эта SolidColorBrush — не объект, а ФАБРИКА.Его экземпляр создается только при необходимости, и на этом этапе вы уже подключили 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>
Просто мои 2 цента.
Прочтите мою статью, кстати, она может быть полезна, если вам нужны странные привязки или анимации со странными конвертерами. http://www.codeproject.com/KB/WPF/BindingHub.aspx
Именно по таким причинам вы можете рассмотреть возможность изучения MVVM.Этот шаблон помогает вам абстрагировать модель и привязки, чтобы вам не приходилось так сильно полагаться на DP - вместо этого вы можете просто привязаться к уведомляемому свойству в модели представления.
О MVVM есть несколько отличных статей, поэтому я бы посоветовал вам начать с чтения работ Карла Шиффлетта, Джоша Смита, Марлона Греча и Саши Барбера.