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 события в графический интерфейс и наоборот? В настоящее время кажется, что это не так. У кого-нибудь есть идея, как решить эту проблему?

EDIT: 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 Хорошо, вот полный исходный код: Логика взаимодействия:

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>

person Hunv    schedule 27.06.2014    source источник
comment
Можете ли вы показать модель данных, привязанную к BindingProxy?   -  person Eugene Podskal    schedule 27.06.2014
comment
Привет, Евгений, я добавил это в исходный пост   -  person Hunv    schedule 27.06.2014
comment
Я имел в виду DataSource вашего окна: local:BindingProxy x:Key=proxy Data={Binding}   -  person Eugene Podskal    schedule 27.06.2014
comment
А вы пробовали создать checkBox в визуальном дереве (привязав его через BindingProxy)? Это просто для того, чтобы увидеть, связана ли проблема с ContextMenu или нет.   -  person Eugene Podskal    schedule 27.06.2014
comment
Привет, специального источника данных нет. Он привязывается к родителю. Таким образом, я могу получить доступ ко всему с помощью Data.whatever (в данном случае Data.ColumnsVisibility). Я добавил ColumnsVisibility в исходный пост. Если я изменю значение, т.е. с помощью кнопки и вызову PropertyChanged, сетка не отреагирует на это.   -  person Hunv    schedule 27.06.2014
comment
Что такое DataSource родителя? Что такое объект данных, который содержит свойство ColumnsVisibility? Можете ли вы показать его определение? Я имею в виду что-то вроде ColumnVisibilityCollection ColumnsVisibility { get; частный набор;} в классе MainViewModel.   -  person Eugene Podskal    schedule 27.06.2014
comment
Хорошо, чтобы все было проще: см. Edit3 - я добавил полный код.   -  person Hunv    schedule 27.06.2014


Ответы (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 о том, что ваш словарь изменил свои элементы. И стандартный Dictionary не делает этого сам по себе (он не реализует 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());
    }

вы сможете уведомлять свои визуальные компоненты без кода программной части.

P.S.: Я реализовал INotifyProperyChanged, который уведомляет об изменениях в индексированном свойстве Item[], но вы можете попробовать реализовать интерфейс INotifyCollectionChanged — я просто не уверен, как он будет работать с индексированными привязками.
P.P.S.: Я не видел вашего прокомментируйте, что вы нашли this.PropertyChanged(эту, new ... проблему.
P.P.P.S.: Если у вас есть время, измените заголовок вопроса на "BindingProxy: привязка к индексируемому свойству", чтобы лучше отразить проблему, и оставьте только код из последнего редактирования (чтобы избежать дублирования) - думайте об этом как об общественной службе.

person Eugene Podskal    schedule 27.06.2014
comment
Это тоже не работает. Я также пробовал Values, Values[], Keys, Keys[], Item[MachineName], Values[Machinename], Keys[Machinename], Value, Value[], Value[Machinename]. Я все еще думаю, что вся связь (в обе стороны) не работает. Значение установлено, потому что я обрабатываю его с помощью события checked/unchecked. Если он работает так, как я ожидаю, он должен быть установлен привязкой. - person Hunv; 27.06.2014
comment
Я проверил его с логическим значением вместо словаря. Это тоже не работает, но логическое значение устанавливается CheckBox! Не работает только смена видимости столбца - person Hunv; 27.06.2014
comment
Вы имеете в виду простое свойство Bool? - person Eugene Podskal; 27.06.2014
comment
Я отредактировал свой пост ранее, потому что он был не совсем правильным. Да, я имею в виду простое свойство bool =› public bool Testbool{get;set;} - person Hunv; 27.06.2014
comment
Как в моем последнем редактировании? Давайте также попробуем изменить BindingMode, как я показал. - person Eugene Podskal; 27.06.2014
comment
Да, как и ваша редакция. Я узнал, что вызывался BoolToVis-Converter. Значит, что-то там происходит. С OneWay-Option для Колонки это работает :). В Check/Uncheck-Command мне просто нужно вызвать PropertyChanged. И НАКОНЕЦ: Таким образом, это работает и со словарем! Правильный вызов PropertyChanged выглядит следующим образом: PropertyChanged(this, new PropertyChangedEventArgs(ColumnsVisibility)); - person Hunv; 27.06.2014