双向数据绑定与WPF中的字典

时间:2009-04-28 22:41:10

标签: c# wpf data-binding xaml dictionary

我想将Dictionary<string, int>绑定到WPF中的ListView。我想以这样的方式执行此操作:Values中的Dictionary通过数据绑定机制得到更新。我不想只更改Keys Values。我也不关心为Dictionary添加新的映射。我只是想更新现有的。

将词典设置为ItemsSource的{​​{1}}无法实现此目的。它不起作用,因为ListView使用枚举器来访问ListView的内容,并且该枚举的元素是不可变的Dictionary个对象。

我目前的调查尝试使用KeyValuePair属性。我将其分配给Keys的{​​{1}}属性。这允许我显示ItemsSource但我不太了解WPF的数据绑定机制来访问ListView中的Keys

我发现了这个问题:Access codebehind variable in XAML但似乎仍无法弥合差距。

你们中的任何人都知道如何使这种方法有效吗? 有没有人有更好的方法?

看起来,作为最后的手段,我可​​以构建一个自定义对象并将其粘贴在Values中,我可以从中重新创建/更新Dictionary,但这似乎是一种规避构建的方法-in数据绑定功能而不是有效地利用它。

5 个答案:

答案 0 :(得分:19)

我认为你不能用字典做你想做的事。

  1. 因为Dictionary没有实现INotifyPropertyChanged或INotifyCollectionChanged
  2. 因为它不会像你想要的那样允许双向绑定。
  3. 我不确定它是否完全符合您的要求,但我会使用ObservableCollection和自定义类。

    我正在使用DataTemplate,以显示如何以两种方式对值进行绑定。

    当然在这个实现中没有使用Key,所以你可以使用ObservableCollection<int>

    自定义类

    public class MyCustomClass
    {
        public string Key { get; set; }
        public int Value { get; set; }
    }
    

    设置ItemsSource

    ObservableCollection<MyCustomClass> dict = new ObservableCollection<MyCustomClass>();
    dict.Add(new MyCustomClass{Key = "test", Value = 1});
    dict.Add(new MyCustomClass{ Key = "test2", Value = 2 });
    listView.ItemsSource = dict;
    

    XAML

    <Window.Resources>
        <DataTemplate x:Key="ValueTemplate">
            <TextBox Text="{Binding Value}" />
        </DataTemplate>
    </Window.Resources>
    <ListView Name="listView">
        <ListView.View>
            <GridView>
                <GridViewColumn CellTemplate="{StaticResource ValueTemplate}"/>
            </GridView>
        </ListView.View>
    </ListView>
    

答案 1 :(得分:6)

这有点hacky,但我让它工作(假设我明白你想要的东西)。

我首先创建了一个视图模型类:

class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        this.data.Add(1, "One");
        this.data.Add(2, "Two");
        this.data.Add(3, "Three");
    }

    Dictionary<int, string> data = new Dictionary<int, string>();
    public IDictionary<int, string> Data
    {
        get { return this.data; }
    }

    private KeyValuePair<int, string>? selectedKey = null;
    public KeyValuePair<int, string>? SelectedKey
    {
        get { return this.selectedKey; }
        set
        {
            this.selectedKey = value;
            this.OnPropertyChanged("SelectedKey");
            this.OnPropertyChanged("SelectedValue");
        }
    }

    public string SelectedValue
    {
        get
        {
            if(null == this.SelectedKey)
            {
                return string.Empty;
            }

            return this.data[this.SelectedKey.Value.Key];
        }
        set
        {
            this.data[this.SelectedKey.Value.Key] = value;
            this.OnPropertyChanged("SelectedValue");
        }
    }

    public event PropertyChangedEventHandler  PropertyChanged;
    private void OnPropertyChanged(string propName)
    {
        var eh = this.PropertyChanged;
        if(null != eh)
        {
            eh(this, new PropertyChangedEventArgs(propName));
        }
    }
}

然后在XAML中:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox x:Name="ItemsListBox" Grid.Row="0"
            ItemsSource="{Binding Path=Data}"
            DisplayMemberPath="Key"
            SelectedItem="{Binding Path=SelectedKey}">
        </ListBox>
        <TextBox Grid.Row="1"
            Text="{Binding Path=SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</Window>

Data属性的值绑定到ItemsSource的{​​{1}}。正如您在问题中所述,这导致使用ListBox的实例作为KeyValuePair<int, string>背后的数据。我将ListBox设置为DisplayMemberPath,以便将键的值用作Key中每个项目的显示值。

正如您所发现的那样,您不能仅使用ListBox的{​​{1}}作为Value的数据,因为这是只读的。相反,KeyValuePair绑定到视图模型上的属性,该属性可以获取和设置当前所选键的值(通过将TextBox的{​​{1}}属性绑定到更新来更新视图模型上的另一个属性)。我必须使这个属性可以为空(因为TextBox是一个结构),所以代码可以检测到什么时候没有选择。

在我的测试应用中,这似乎会导致对SelectedItem的修改传播到视图模型中的ListBox

这或多或少是你想要的吗?它似乎应该更清洁,但我不确定是否有任何方法可以这样做。

答案 2 :(得分:1)

只需发布我从上面得到的内容。您可以将以下内容放在ObservableCollection<ObservableKvp<MyKeyObject, MyValueObject>>

将密钥设为只读,因为它们的密钥可能不应该被更改。这需要Prism工作 - 或者您可以实现自己的INotifyPropertyChanged版本

public class ObservableKvp<K, V> : BindableBase
{
    public K Key { get; }

    private V _value;
    public V Value
    {
        get { return _value; }
        set { SetProperty(ref _value, value); }
    }

    public ObservableKvp(K key, V value)
    {
        Key = key;
        Value = value;
    }
}

答案 3 :(得分:0)

而不是

Dictionary<string, int>

,你能有一个

吗?
Dictionary<string, IntWrapperClass>?

让IntWrapperClass实现INotifyPropertyCHanged。然后,您可以将Listview / Listbox双向绑定到字典中的项目。

答案 4 :(得分:0)

我设法在我的项目中使用了由dr.wpf here实现的可观察词典,并使用了不是字符串而是对象[]作为值。 在xaml中,我得到了类似的东西:

<ListView ItemsSource="{Binding myObservableDictionary}" >
                            <ListView.View>
                                <GridView>
                                    <GridViewColumn DisplayMemberBinding="{Binding Key}"/>
                                    <GridViewColumn>
                                        <GridViewColumn.CellTemplate>
                                            <DataTemplate>
                                                <TextBox Text="{Binding Value[0], Mode=TwoWay}" AllowDrop="True" Drop="TextBox_Drop"></TextBox>
                                            </DataTemplate>
                                        </GridViewColumn.CellTemplate>
                                    </GridViewColumn>
                                </GridView>
                            </ListView.View>
                        </ListView>