web-dev-qa-db-ja.com

WPFでの辞書による双方向データバインディング

WPFでDictionary<string, int>ListViewにバインドしたいのですが。 ValuesDictionaryがデータバインディングメカニズムを介して更新されるように、これを実行したいと思います。 Keysを変更したくないのは、Valuesだけです。また、Dictionaryに新しいマッピングを追加することも気にしません。既存のものを更新したいだけです。

辞書をItemsSourceListViewとして設定しても、これは達成されません。 ListViewはEnumeratorを使用してDictionaryのコンテンツにアクセスし、その列挙の要素は不変のKeyValuePairオブジェクトであるため、機能しません。

現在の調査では、Keysプロパティを使用しようとしています。これをItemsSourceListViewプロパティに割り当てます。これによりKeysを表示できますが、Values内のDictionaryにアクセスするためのWPFのデータバインディングメカニズムについて十分に理解していません。

私はこの質問を見つけました: XAMLのAccess codebehind変数 ですが、それでもギャップを埋めることができないようです。

このアプローチを機能させる方法を知っている人はいますか?誰かがより良いアプローチをしていますか?

最後の手段として、カスタムオブジェクトをビルドしてListに貼り付け、そこからDictionaryを再作成/更新できるように思えますが、これは、ビルドされたオブジェクトを回避する方法のようですデータバインディング機能を有効に活用するのではなく、.

25
Waylon Flinn

辞書であなたがやりたいことをできるようになるとは思いません。

  1. ディクショナリはINotifyPropertyChangedまたはINotifyCollectionChangedを実装していないため
  2. それはあなたが望むような双方向のバインディングを許可するつもりはないからです。

それが要件に正確に適合するかどうかはわかりませんが、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>
20
Ray

これは少しハックですが、私はそれを機能させました(私があなたが何を望んでいるか理解していると仮定します)。

最初にビューモデルクラスを作成しました。

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プロパティの値は、ItemsSourceListBoxにバインドされます。質問で述べたように、これはKeyValuePair<int, string>のインスタンスをListBoxの背後のデータとして使用することになります。 DisplayMemberPathKeyに設定して、キーの値がListBoxの各項目の表示値として使用されるようにします。

ご覧のとおり、ValueKeyValuePairTextBoxのデータとして使用することはできません。これは読み取り専用だからです。代わりに、TextBoxは、現在選択されているキーの値を取得および設定できるビューモデルのプロパティにバインドされます(これは、SelectedItemListBoxプロパティをビューモデルの別のプロパティにバインドすることによって更新されます)。 (KeyValuePairは構造体であるため)このプロパティをnullに設定できるようにして、選択がない場合にコードで検出できるようにしました。

私のテストアプリでは、これにより、ビューモデルのTextBoxに伝達されるDictionaryへの編集が発生するようです。

それは多かれ少なかれあなたが目指していることですか?少しすっきりしているように見えますが、どうすればいいのかわかりません。

6
Andy

上記から導き出したものを投稿するだけです。以下を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;
    }
}
1
Anthony Nichols

の代わりに

Dictionary<string, int>

、できますか

Dictionary<string, IntWrapperClass>?

IntWrapperClassにINotifyPropertyCHangedを実装させます。次に、リストビュー/リストボックスの双方向バインドを辞書内のアイテムに設定できます。

0
Adarsha