I have a BindingProxy to Bind the Visibility-Property of DataGridColumns of a DataGrid to a Value in a Dictionary ("ColumnsVisibility"). I Also have a Context-Menu, that should make it possible to hide/show the columns of the 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="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>
The initial loading works, if the Dictionary "ColumnsVisibility" is filled with Information before InitializeComponent(), the value I set the DictionaryEntry to, is applied.
My target is to check the checkbox in the Contextmenu and the Column appears/disappears. Because the ContextMenu and the Columns are not Member of the same visual tree as the DataGrid or everything else, I'm using the Proxy. My problem is, that the checking/unchecking of the CheckBox in the ContextMenu don't change the value of ColumnsVisibility[ElementName]. If I add a check/uncheck-Event to the Checkbox, I can change it by using it in the code, but triggering a PropertyChanged-Event don't change anything visual. The column stays as it is.
Is the BindingProxy forwarding Events to the GUI and vice versa? Currently it seems like it doesn't. Anybody has an Idea how to solve this problem?
EDIT: The 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: The ColumnsVisibilty Property
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"));
}
}
Before InitializeComponent() this is done on loading:
_ColumnsVisibility.Add("ElementName", false);
Edit3 OK, here is the full sourcecode: Interaction logic:
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>
EDIT:
It is an error for sure:
PropertyChanged(
null, new PropertyChangedEventArgs("ColumnsVisibility"));
It should be:
PropertyChanged(
this, new PropertyChangedEventArgs("ColumnsVisibility"));
I have blindly copied it into my first edit from your code. Well, sometimes you just don't see the things before your eyes
For future I recommend you to use some sort of a function in some base class like
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;
}
It did not solve the issue, but it is at least one problem down
EDIT WITH(I HOPE) THE FINAL SOLUTION:
It looks that you cannot notify the WPF engine that your dictionary has changed its items. And standard Dictionary does not do it on its own(it does not implement the ICollectionChanged or even INotifyPropertyChanged).
That's why you have to use your own dictionary, or to be more precise wrapper dictionary class:
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
}
With such a class:
/// <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());
}
you will be able to notify your visual components without any code-behind.
P.S.: I have implemented INotifyProperyChanged that notifies about the changes in Item[] indexed property, but you could try to implement INotifyCollectionChanged interface - I am just unsure how it will work with indexed bindings.
P.P.S.: I haven't seen your comment that you have found this.PropertyChanged(
this, new ...
issue.
P.P.P.S.: If you have time then change the question title to "BindingProxy: binding to the indexed property" to better reflect the problem and leave only the code from the last edit(to avoid duplication) - think of it as the community service.