简体   繁体   English

BindingProxy:绑定到索引属性

[英]BindingProxy: binding to the indexed property

I have a BindingProxy to Bind the Visibility-Property of DataGridColumns of a DataGrid to a Value in a Dictionary ("ColumnsVisibility"). 我有一个BindingProxy将DataGrid的DataGridColumns的可视性-属性绑定到字典中的值(“ 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. 如果在InitializeComponent()之前用信息填充了字典“ ColumnsVisibility”,则将应用初始设置,即应用了我将DictionaryEntry设置为的值。

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. 因为ContextMenu和Columns不是与DataGrid或其他所有对象相同的可视树的成员,所以我使用的是Proxy。 My problem is, that the checking/unchecking of the CheckBox in the ContextMenu don't change the value of ColumnsVisibility[ElementName]. 我的问题是,在ContextMenu中选中/取消选中CheckBox不会更改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. 如果将check / uncheck-Event添加到Checkbox,则可以在代码中使用它来更改它,但是触发PropertyChanged-Event不会改变任何视觉效果。 The column stays as it is. 该列保持原样。

Is the BindingProxy forwarding Events to the GUI and vice versa? BindingProxy是否将事件转发到GUI,反之亦然? Currently it seems like it doesn't. 目前看来还没有。 Anybody has an Idea how to solve this problem? 任何人都有一个想法如何解决这个问题?

EDIT: The 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: The ColumnsVisibilty Property 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"));
        }
    }

Before InitializeComponent() this is done on loading: 在InitializeComponent()之前,这是在加载时完成的:

_ColumnsVisibility.Add("ElementName", false);

Edit3 OK, here is the full sourcecode: Interaction logic: 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: 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")); PropertyChanged( null , new PropertyChangedEventArgs("ColumnsVisibility"));

It should be: 它应该是:

PropertyChanged( this , new PropertyChangedEventArgs("ColumnsVisibility")); 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. 看来您无法通知WPF引擎您的词典已更改其项目。 And standard Dictionary does not do it on its own(it does not implement the ICollectionChanged or even INotifyPropertyChanged). 而且标准的Dictionary不能单独执行此操作(它不实现ICollectionChanged甚至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. 您无需任何代码即可通知视觉组件。

PS: 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. PS:我已经实现了INotifyProperyChanged,用于通知Item []索引属性中的更改,但是您可以尝试实现INotifyCollectionChanged接口-我只是不确定它如何与索引绑定一起工作。
PPS: I haven't seen your comment that you have found this.PropertyChanged( this , new ... issue. PPS:我没有看到您的评论,因为您找到了this.PropertyChanged( this , new ...问题。
PPPS: 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. PPPS:如果有时间,则将问题标题更改为“ BindingProxy:绑定到索引属性”以更好地反映问题,并仅保留上次编辑中的代码(以避免重复)-将其视为社区服务。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM