BindingProxy:绑定到索引属性

时间:2014-06-27 12:49:38

标签: c# wpf datagrid propertychanged

我有一个BindingProxy将DataGrid的DataGridColumns的Visibility属性绑定到Dictionary中的值(“ColumnsVisibility”)。我还有一个Context-Menu,可以隐藏/显示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>

初始加载有效,如果在InitializeComponent()之前字典“ColumnsVisibility”填充了Information,则应用我设置DictionaryEntry的值。

我的目标是检查Contextmenu中的复选框,并且列出现/消失。因为ContextMenu和Columns不是与DataGrid或其他所有相同的可视树的成员,所以我正在使用Proxy。 我的问题是,在ContextMenu中检查/取消选中CheckBox不会更改ColumnsVisibility [ElementName]的值。如果我在Checkbox中添加check / uncheck-Event,我可以在代码中使用它来更改它,但触发PropertyChanged-Event不会改变任何视觉效果。该列原样保留。

BindingProxy是否将事件转发到GUI,反之亦然?目前似乎没有。 任何人都有一个想法如何解决这个问题?

修改 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>

1 个答案:

答案 0 :(得分: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());
    }

您将能够在没有任何代码隐藏的情况下通知您的可视组件。

P.S。:我已经实现了INotifyProperyChanged,它通知了Item []索引属性的变化,但你可以尝试实现INotifyCollectionChanged接口 - 我只是不确定它如何与索引绑定一起使用。
P.P.S。:我没有看到你的评论,你发现this.PropertyChanged( , new ...问题。
P.P.P.S。:如果你有时间,那么将问题标题更改为&#34; BindingProxy:绑定到索引属性&#34;为了更好地反映问题,只留下上次编辑的代码(以避免重复) - 将其视为社区服务。