6

コンボボックスをViewModelのコレクションにバインドする標準のwpf/mvvmアプリケーションを使用しています。

ドロップダウンからアイテムの選択を解除できるようにする必要があります。つまり、ユーザーは何かを選択でき、後でその選択を解除する(何も選択しない)ことを決定できる必要があります。問題は、バインドされたコレクションに空の要素がないことです

私の最初の考えは、コレクションに新しいアイテムを挿入することでした。その結果、コレクションの上に空のアイテムが表示されます。

ただし、これはハックであり、ビューモデルでそのコレクションを使用するすべてのコードに影響します。

たとえば誰かが書くことになった場合

_myCollection.Frist(o => o.Name == "foo") 

これにより、null参照例外がスローされます。

考えられる回避策は次のとおりです。

_myCollection.Where(o => o != null).First(o => o.Name == "foo");

これは機能しますが、そのコレクションを将来使用しても中断が発生しないことを保証する方法はありません。

ユーザーが選択を解除できるように空のアイテムを追加できるようにするための良いパターン/ソリューションは何ですか。(私はCollectionView構造も知っていますが、それは非常に単純なものにはやり過ぎのようです)

アップデート

@hbarckの提案を採用し、CompositeCollectionを実装しました(概念実証)

    public CompositeCollection MyObjects {
        get {
            var col = new CompositeCollection();

            var cc1 = new CollectionContainer();
            cc1.Collection = _actualCollection;

            var cc2 = new CollectionContainer();
            cc2.Collection = new List<MyObject>() { null }; // PROBLEM

            col.Add(cc2);
            col.Add(cc1);
            return col;
        }
    }

このコードは、既存のバインディング(SelectedItemを含む)で機能します。これはすばらしいことです。

これに関する1つの問題は、アイテムが完全にnullの場合、SelectedItemセッターが選択時に呼び出されないことです。

その1行をこれに変更すると:

            cc2.Collection = new List<MyObject>() { new MyObject() }; // PROBLEM

セッターが呼び出されますが、選択したアイテムはnullではなく基本的な初期化クラスになります。セッターにコードを追加してチェック/リセットすることはできますが、それは良くありません。

4

5 に答える 5

3

最も簡単な方法は、CompositeCollection を使用することだと思います。空のアイテム (null またはプレースホルダー オブジェクトなど、必要に応じて) のみを含む別のコレクションにコレクションを追加し、CompositeCollection を ComboBox の ItemsSource にします。これはおそらく意図されたものです。

アップデート:

これは、最初に考えたよりも複雑であることが判明しましたが、実際には、次の解決策を思い付きました。

<Window x:Class="ComboBoxFallbackValue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:TestWpfDataBinding"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:w="clr-namespace:System.Windows;assembly=WindowsBase"
Title="ComboBoxFallbackValue" Height="300" Width="300">
<Window.Resources>
    <t:TestCollection x:Key="test"/>
    <CompositeCollection x:Key="MyItemsSource">
        <x:Static Member="t:TestCollection.NullItem"/>
        <CollectionContainer Collection="{Binding Source={StaticResource test}}"/>
    </CompositeCollection>
    <t:TestModel x:Key="model"/>
    <t:NullItemConverter x:Key="nullItemConverter"/>
</Window.Resources>
<StackPanel>
    <ComboBox x:Name="cbox" ItemsSource="{Binding Source={StaticResource MyItemsSource}}" IsEditable="true" IsReadOnly="True" Text="Select an Option" SelectedItem="{Binding Source={StaticResource model}, Path=TestItem, Converter={StaticResource nullItemConverter}, ConverterParameter={x:Static t:TestCollection.NullItem}}"/>
    <TextBlock Text="{Binding Source={StaticResource model}, Path=TestItem, TargetNullValue='Testitem is null'}"/>
</StackPanel>

基本的には、アイテムとして使用するクラスのシングルトン NullInstance を宣言し、VM プロパティの設定時にこのインスタンスを null に変換する Converter を使用するパターンです。コンバーターは、次のように普遍的に記述できます (これは VB です。気にしないでください)。

Public Class NullItemConverter
Implements IValueConverter

Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
    If value Is Nothing Then
        Return parameter
    Else
        Return value
    End If
End Function

Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
    If value Is parameter Then
        Return Nothing
    Else
        Return value
    End If
End Function

クラス終了

コンバーターを再利用できるため、これをすべて XAML で設定できます。コードで行う必要がある唯一のことは、シングルトンの NullItem を提供することです。

于 2012-09-28T17:23:17.213 に答える
2

個人的には、バインド先のコレクションにあるオブジェクトの「空の」バージョンを追加する傾向があります。たとえば、文字列のリストにバインドしている場合は、viewmodel でコレクションの先頭に空の文字列を挿入します。モデルにデータ コレクションがある場合は、ビューモデル内の別のコレクションでラップします。

モデル:

public class Foo
{
    public List<string> MyList { get; set;}
}

モデルを見る:

public class FooVM
{
    private readonly Foo _fooModel ;

    private readonly ObservableCollection<string> _col;
    public ObservableCollection<string> Col // Binds to the combobox as ItemsSource
    {
        get { return _col; }
    }

    public string SelectedString { get; set; } // Binds to the view

    public FooVM(Foo model)
    {
        _fooModel = model;
        _col= new ObservableCollection<string>(_fooModel.MyList);
        _col.Insert(0, string.Empty);
    }
}
于 2012-09-28T18:53:27.413 に答える
1

ComboBoxを拡張して、選択解除を有効にすることもできます。SelectedItemユーザーがをnullに設定できるようにする1つ以上のフックを追加します(たとえば、エスケープキーを押す) 。

using System.Windows.Input;

public class NullableComboBox : ComboBox
{
    public NullableComboBox()
        : base()
    {
        this.KeyUp += new KeyEventHandler(NullableComboBox_KeyUp);

        var menuItem = new MenuItem();
        menuItem.Header = "Remove selection";
        menuItem.Command = new DelegateCommand(() => { this.SelectedItem = null; });
        this.ContextMenu = new ContextMenu();
        this.ContextMenu.Items.Add(menuItem);
    }

    void NullableComboBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Escape || e.Key == Key.Delete) 
        {
            this.SelectedItem = null;
        }
    }
}

編集FlorianGIのコメントに気づいたばかりですが、コンテキストメニューは追加するもう1つの優れた選択解除フックかもしれません。

于 2012-09-28T17:05:22.563 に答える
0

1つのオプションは、最初の「空の」要素が必要なコンシューマー向けに特別に公開するアダプターコレクションを作成することです。IList(ObservableCollectionと同じパフォーマンスが必要な場合)とINotifyCollectionChangedを実装するラッパークラスを作成する必要があります。ラップされたコレクションでINotifyCollectionChangedをリッスンしてから、インデックスを1つ上にシフトしてイベントを再ブロードキャストする必要があります。関連するすべてのリストメソッドも、インデックスを1つシフトする必要があります。

public sealed class FirstEmptyAdapter<T> : IList<T>, IList, INotifyCollectionChanged
{
    public FirstEmptyCollection(ObservableCollection<T> wrapped)
    {
    }

    //Lots of adapter code goes here...
}

IListメソッドを避けたい場合は、最低限、INotifyCollectionChangedとを実装する必要がありIEnumerable<T>ます。

于 2012-09-28T17:04:07.457 に答える
0

簡単な方法の 1 つは、ComboBox を再テンプレート化して、項目が選択されたときにボックスの右側に小さな X が表示されるようにすることです。クリックすると、選択した項目がクリアされます。

これには、ViewModel をこれ以上複雑にしないという利点があります。

于 2012-09-29T02:20:20.433 に答える