2012-09-28 41 views
5

標準のwpf/mvvmアプリケーションを使用しています。ここでコンボボックスをViewModelのコレクションにバインドします。MVVMのComboBoxItemの選択解除

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

私の最初の考えは、単にコレクションの上に空のアイテムを持つ新しいアイテムをコレクションに挿入することでした。

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

誰かが、これはnull参照の例外がスローされます

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

を書くことだった場合。

可能な回避策は次のとおりです。

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

これは動作しますが、そのコレクションの任意の将来の使用を確保する方法は任意のブレークを起こさないことはありません。

空のアイテムを追加できるようにするための良いパターン/解決策は、ユーザーが選択を解除できるようにすることです。 (私もCollectionView構造を認識していますが、それはそう単純なもののためにやり過ぎのように思える)

更新

(コンセプトの迅速な証拠)

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; 
     } 
    } 
を@hbarck提案して行き、CompositeCollectionを実装

このコードは、既存のバインディング(SelectedItemを含む)でうまく動作します。

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

私はこれまで1行ことを変更した場合:

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

はセッターが呼ばれ、今は私の選択した項目ではなくヌルだけの基本的な初期化クラスです..私はにセッターにいくつかのコードを追加することができますチェック/リセット、それは良くありません。

+2

あなたのviewmodelsをしていないことの利点は、任意のより複雑持っています。 –

+0

ドロップダウンを右クリックしますか?それは直感的ではありません。 –

+0

私はそれで生きることができます。^^コンボボックスはあなたが右クリックすれば開いてもいないので、その人には不愉快です。 –

答えて

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を使用します。

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でこのすべてを設定することができます。コンバータは、この(それはVBだ、私はあなたが気にしないことを望む)のように、普遍的に書き込むことができます;コードで実行されることが残っている唯一のことは、シングルトンNullItemを提供することです。

+0

私はこれが好きです。私の唯一の懸念は、SelectedItem Binding ..を使用していることです。これは、元のコレクションにある同じアイテム(クローンされていないアイテム)でなければなりません。 –

+0

「選択されていない」アイテムをnullにすると、これは問題にはならないと思います。私が知る限り、アイテムはクローンされません。そのため、SelectedItemはコレクションメンバーまたはnullのいずれかになります。 – hbarck

+1

はCompositeCollectionを実装しましたが、面白いWPFの動作に出くわしました..(私の質問を更新しました) –

0

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

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

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

最低限はINotifyCollectionChangedとIEnumerable<T>を実装することです。

1

また、選択を解除できるようにComboBoxを拡張することもできます。ユーザがSelectedItemをヌルに設定できる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; 
     } 
    } 
} 

編集だけでコンテキストメニューを追加するための別の良い選択解除フックであるかもしれない、フロリアンGIさんのコメントに気づきました。

+0

DelegateCommandはどこから入手できますか?あなたはComboBoxではなくAXMLの中でどのようにこれを使いますか? – theAlse

+0

@theAlse(例:https://code.google.com/p/moltenmonkey/source/browse/Source/mmonkey/Commands/DelegateCommand.cs?「Command」を使用するspec = svneb38ee7524ec4ec9cf2520ddee47ad303396e389&r = eb38ee7524ec4ec9cf2520ddee47ad303396e389 – McGarnagle

+0

、どこで入手できますか? – theAlse

2

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

MODEL:

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

一つの簡単なアプローチは、アイテムがある場合には、小さなXを選択するの右側に表示されるようにコンボボックスをテンプレートし直すことですボックス。クリックすると、選択した項目がクリアされます。

これはあなたはユーザーコンテキストメニューにメニューアイテムをクリックすることでアイテムを選択解除せていただく場合があります

+0

おそらくAdornerを使用しますか? – McGarnagle

関連する問題