【问题标题】:BindingProxy: binding to the indexed propertyBindingProxy:绑定到索引属性
【发布时间】:2014-08-18 14:09:14
【问题描述】:

我有一个 BindingProxy 来将 DataGrid 的 DataGridColumns 的可见性属性绑定到字典中的值(“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>

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

我的目标是选中上下文菜单中的复选框,然后列出现/消失。因为 ContextMenu 和 Columns 与 DataGrid 或其他所有内容都不是同一可视树的成员,所以我使用的是代理。 我的问题是,在 ContextMenu 中选中/取消选中 CheckBox 不会更改 ColumnsVisibility [ElementName] 的值。如果我向复选框添加一个选中/取消选中事件,我可以通过在代码中使用它来更改它,但触发 PropertyChanged-Event 不会改变任何视觉效果。该列保持原样。

BindingProxy 是否将事件转发到 GUI,反之亦然?目前好像没有。 有人知道如何解决这个问题吗?

编辑: 绑定代理:

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));
}

编辑2: 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);

编辑3 好的,这是完整的源代码: 交互逻辑:

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>

【问题讨论】:

  • 能否展示绑定到BindingProxy的数据模型?
  • 嗨,尤金,我把它添加到原始帖子中
  • 我的意思是你窗口的数据源:local:BindingProxy x:Key="proxy" Data="{Binding}"
  • 您是否尝试过在可视化树中创建复选框(通过 BindingProxy 绑定)?只是看看这个问题是否与ContextMenu有关。
  • 您好,没有特殊的数据源。它绑定到父级。所以我可以通过 Data.whatever 访问所有内容(在本例中为 Data.ColumnsVisibility)。我在原始帖子中添加了 ColumnsVisibility。如果我更改值,即使用按钮并触发 PropertyChanged,则网格不会对此做出反应

标签: c# wpf datagrid propertychanged


【解决方案1】:

编辑:

这肯定是个错误:

PropertyChanged(null, 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(this, new ... issue。
PPPS:如果您有时间,请将问题标题更改为“BindingProxy:绑定到索引属性”以更好地反映问题并仅保留上次编辑的代码(以避免重复) - 将其视为社区服务。

【讨论】:

  • 这也不行。我还尝试了 Values、Values[]、Keys、Keys[]、Item[MachineName]、Values[Machinename]、Keys[Machinename]、Value、Value[]、Value[Machinename]。我仍然认为整个沟通(两种方式)都不起作用。设置值是因为我使用选中/未选中事件来处理它。如果它按我的预期工作,它应该由绑定设置。
  • 我用布尔值而不是字典对其进行了测试。这也不起作用,但布尔值由 CheckBox 设置!只有更改列的可见性不起作用
  • 你的意思是一个简单的 Bool 属性?
  • 我之前编辑过我的帖子,因为它不完全正确。是的,我的意思是一个简单的 bool 属性 => public bool Testbool{get;set;}
  • 和我上次的编辑一样?让我们也尝试像我展示的那样更改 BindingMode。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-03
  • 2013-01-16
  • 1970-01-01
  • 1970-01-01
  • 2018-08-20
相关资源
最近更新 更多