BindingProxy: привязка к индексируемому свойству
У меня есть BindingProxy для привязки свойства видимости DataGridColumns DataGrid к значению в словаре ("ColumnsVisibility"). У меня также есть контекстное меню, которое должно позволить скрывать / показывать столбцы таблицы.
<DataGrid Name="dgMachines"
ItemsSource="{Binding HVMachineList,
UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False"
>
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}"/>
<ContextMenu x:Key="DataGridColumnHeaderContextMenu">
<MenuItem Header="Names">
<CheckBox Content="Name" IsChecked="{Binding Data.ColumnsVisibility[ElementName], Source={StaticResource proxy}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</MenuItem>
</ContextMenu>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding ElementName}" Visibility="{Binding Data.ColumnsVisibility[ElementName], UpdateSourceTrigger=PropertyChanged, Source={StaticResource proxy}, Converter={StaticResource BoolToVisibilityConv}, Mode=TwoWay}" />
</DataGrid.Columns>
</DataGrid>
Первоначальная загрузка работает, если словарь "ColumnsVisibility" заполнен информацией перед InitializeComponent(), то применяется значение, которое я установил для DictionaryEntry.
Моя цель - установить флажок в контекстном меню, и столбец появляется / исчезает. Поскольку ContextMenu и столбцы не являются членами одного и того же визуального дерева, как DataGrid или все остальное, я использую прокси. Моя проблема в том, что проверка / снятие флажка CheckBox в ContextMenu не меняет значение ColumnsVisibility[ElementName]. Если я добавлю check/uncheck-Event в Checkbox, я могу изменить его, используя его в коде, но запуск PropertyChanged-Event ничего не меняет. Столбец остается как есть.
BindingProxy передает события в графический интерфейс и наоборот? В настоящее время кажется, что это не так. У кого-нибудь есть идея, как решить эту проблему?
РЕДАКТИРОВАТЬ: BindingProxy:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Edit2: свойство ColumnsVisibilty
private Dictionary<string, bool> _ColumnsVisibility = new Dictionary<string, bool>();
public Dictionary<string, bool> ColumnsVisibility
{
get{return(_ColumnsVisibility);}
set
{
_ColumnsVisibility = value;
if (PropertyChanged != null)
PropertyChanged(null, new PropertyChangedEventArgs("ColumnsVisibility"));
}
}
Перед InitializeComponent () это делается при загрузке:
_ColumnsVisibility.Add("ElementName", false);
Edit3 OK, вот полный исходный код: Логика взаимодействия:
using System.Collections.Generic;
using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace HyperV
{
/// <summary>
/// Interaction logic for HyperVControl.xaml
/// </summary>
public partial class HyperVControl : UserControl, INotifyPropertyChanged
{
public HyperVControl()
{
#region Set default visibility for Columns
_ColumnsVisibility.Add("ElementName", false);
//(...)
#endregion
InitializeComponent();
}
#region Control triggered
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
}
/// <summary>
/// Is Triggered by Checkboxes, that are in the contextmenu of the DataGrid-Header to show/hide columns
/// </summary>
/// <param name="sender">The Checkbox, that send this command</param>
/// <param name="e"></param>
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
//This sets the value in ColumnsVisibility to be sure. The value is NOT set by binding (but should...)
ColumnsVisibility[((CheckBox)sender).Tag.ToString()] = (bool)((CheckBox)sender).IsChecked;
//Nothing of this works
if (PropertyChanged != null)
{
PropertyChanged(null, new PropertyChangedEventArgs("ColumnsVisibility"));
PropertyChanged(null, new PropertyChangedEventArgs("ColumnsVisibility[Machinename]"));
PropertyChanged(null, new PropertyChangedEventArgs("Data.ColumnsVisibility"));
PropertyChanged(null, new PropertyChangedEventArgs("Data.ColumnsVisibility[Machinename]"));
}
}
#endregion
#region Properties (private and publics)
private ObservableCollection<HyperVMachine> _HVMachineList;
private Dictionary<string, bool> _ColumnsVisibility = new Dictionary<string, bool>();
/// <summary>
/// Contains all loaded information about the virtual Clients
/// </summary>
public ObservableCollection<HyperVMachine> HVMachineList
{
get { return _HVMachineList; }
set
{
_HVMachineList = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("HVMachineList"));
}
}
/// <summary>
/// To set
/// </summary>
public Dictionary<string, bool> ColumnsVisibility
{
get{return(_ColumnsVisibility);}
set
{
_ColumnsVisibility = value;
if (PropertyChanged != null)
PropertyChanged(null, new PropertyChangedEventArgs("ColumnsVisibility"));
}
}
#endregion
#region Events
//To Update Content on the Form
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
//Binding Proxy
#region Freezable for Context-Menu-Data-Transmition
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
#endregion
}
XAML:
<UserControl xmlns:Controls="clr-namespace:HyperV.Controls"
x:Class="HyperV.HyperVControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:HyperV"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="900"
Loaded="UserControl_Loaded"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Language/language.xaml"/>
<ResourceDictionary Source="Language/language.de-DE.xaml"/>
</ResourceDictionary.MergedDictionaries>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConv"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<DataGrid Name="dgMachines"
ItemsSource="{Binding HVMachineList, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False"
>
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}"/>
<ContextMenu x:Key="DataGridColumnHeaderContextMenu">
<MenuItem Header="{StaticResource MenHeadGeneral}">
<CheckBox Tag="ElementName" Content="{StaticResource MenMachinename}" IsChecked="{Binding Data.ColumnsVisibility[ElementName], Source={StaticResource proxy}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked"/>
<!-- ... -->
</MenuItem>
<!-- ... -->
</ContextMenu>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContextMenu" Value="{StaticResource DataGridColumnHeaderContextMenu}" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="{StaticResource MenMachinename}" Binding="{Binding ElementName}" Visibility="{Binding Data.ColumnsVisibility[ElementName], UpdateSourceTrigger=PropertyChanged, Source={StaticResource proxy}, Converter={StaticResource BoolToVisibilityConv}, Mode=TwoWay}" />
<!-- ... -->
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
1 ответ
РЕДАКТИРОВАТЬ:
Это ошибка наверняка:
PropertyChanged(
ноль, new PropertyChangedEventArgs("ColumnsVisibility"));
Так должно быть:
PropertyChanged(
этот, new PropertyChangedEventArgs("ColumnsVisibility"));
Я слепо скопировал это в мое первое редактирование из вашего кода. Ну, иногда ты просто не видишь вещи на глазах
В будущем я рекомендую вам использовать какую-то функцию в некотором базовом классе, например
public class NotifyPropertyChangeableBase: INotifyPropertyChanged // Usually I name it somewhat like 'ViewModelBase' in my projects, but your actual class is the control, so it is not the most appropriate name
{
protected void OnPropertyChanged(String propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Это не решило проблему, но это как минимум одна проблема вниз
РЕДАКТИРОВАТЬ С (Я НАДЕЮСЬ) ЗАКЛЮЧИТЕЛЬНОЕ РЕШЕНИЕ:
Похоже, вы не можете уведомить механизм WPF о том, что ваш словарь изменил свои элементы. И стандартный словарь не делает это сам по себе (он не реализует ICollectionChanged или даже INotifyPropertyChanged).
Вот почему вы должны использовать свой собственный словарь, или, точнее, класс словаря-обертки:
public class DictionaryNotificationWrapper<TKey, TValue> : IDictionary<TKey, TValue>, INotifyPropertyChanged
{
#region Fields
private IDictionary<TKey, TValue> innerDictionary;
#endregion
#region Constructors
public DictionaryNotificationWrapper(IDictionary<TKey, TValue> innerDictionary)
{
if (innerDictionary == null)
throw new ArgumentNullException("innerDictionary", "The inner dictionary is null");
this.innerDictionary = innerDictionary;
}
#endregion
#region IDictionary implementation
public TValue this[TKey key]
{
get
{
return this.innerDictionary[key];
}
set
{
this.innerDictionary[key] = value;
this.OnPropertyChanged("Item[]");
this.OnPropertyChanged("Count");
}
}
#endregion
#region not implemented IDictionary members - you are free to finish the work
public void Add(TKey key, TValue value)
{
throw new NotImplementedException();
}
public bool ContainsKey(TKey key)
{
throw new NotImplementedException();
}
public ICollection<TKey> Keys
{
get { throw new NotImplementedException(); }
}
public bool Remove(TKey key)
{
throw new NotImplementedException();
}
public bool TryGetValue(TKey key, out TValue value)
{
throw new NotImplementedException();
}
public ICollection<TValue> Values
{
get { throw new NotImplementedException(); }
}
public void Add(KeyValuePair<TKey, TValue> item)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public int Count
{
get { throw new NotImplementedException(); }
}
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
throw new NotImplementedException();
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
throw new NotImplementedException();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(String propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
#endregion
}
С таким классом:
/// <summary>
/// Interaction logic for HyperVControl.xaml
/// </summary>
public partial class HyperVControl : UserControl, INotifyPropertyChanged
{
#region Constructors
public HyperVControl()
{
// Form initialization
InitializeComponent();
// Initialize columns visibility collection
IDictionary<String, Boolean> innerColumnsVisibilityDictionary = new Dictionary<String, Boolean>();
innerColumnsVisibilityDictionary.Add("ElementName", true);
// Wrap the visibility dictionary
this.ColumnsVisibility = new DictionaryNotificationWrapper<String, Boolean>(innerColumnsVisibilityDictionary);
// Initialize grid's datasource
this.HVMachineList = new ObservableCollection<HyperVMachine>();
this.HVMachineList.Add(new HyperVMachine());
this.HVMachineList.Add(new HyperVMachine());
this.HVMachineList.Add(new HyperVMachine());
}
вы сможете уведомлять ваши визуальные компоненты без какого-либо кода.
PS: я реализовал INotifyProperyChanged, который уведомляет об изменениях в индексированном свойстве Item[], но вы можете попробовать реализовать интерфейс INotifyCollectionChanged - я просто не уверен, как он будет работать с индексированными привязками.
PPS: я не видел ваш комментарий, который вы нашли this.PropertyChanged(
этот, new ...
вопрос.
PPPS: если у вас есть время, измените заголовок вопроса на "BindingProxy: привязка к индексируемому свойству", чтобы лучше отразить проблему и оставить только код из последнего редактирования (чтобы избежать дублирования) - думайте о нем как о службе сообщества.