WPF ComboBoxSelectedItem-以前の値に変更

2010年04月06日に質問されました。  ·  閲覧回数 15.8k回  ·  ソース

Rob Buhler picture
2010年04月06日

SelectedItemがViewModelにバインドされているComboBoxがあります。

<ComboBox SelectedItem="{Binding SelItem, Mode=TwoWay}" ItemsSource="{Binding MyItems}">

ユーザーがViewComboBoxで新しいアイテムを選択したときに、プロンプトを表示して、変更を加えることを確認したいと思います。

ビューモデルのSetItemプロパティセッターで、選択を確認するためのダイアログを表示します。 彼らが「はい」と言うとき、それはうまくいきます。

私の問題は、ユーザーが「いいえ」をクリックしたときに、ComboBoxを以前の値に戻すために誰を取得するかがわからないことです。 ViewModelのプロパティには正しい古い値がありますが、ViewではComboBoxに新しく選択された値が表示されます。

ユーザーがアイテムを選択し、続行することを確認し、選択しない場合は、ComboBoxを前のアイテムに戻します。

どうすればこれを達成できますか? ありがとう!

回答

Kent Boogaart picture
2010年04月06日
26

ユーザーが「いいえ」と言うと、WPFは値が変更されたことを認識しません。 WPFに関する限り、値はユーザーが選択したものです。

プロパティ変更通知を発行してみてください。

public object SelItem
{
    get { ... }
    set
    {
        if (!CancelChange())
        {
            this.selItem = value;
        }

        OnPropertyChanged("SelItem");
    }
}

問題は、変更通知が選択イベントの同じコンテキスト内で発生することです。 したがって、WPFは、プロパティが変更されたことを既に認識しているため、それを無視します-ユーザーが選択したアイテムに!

あなたがする必要があるのは、別のメッセージで通知イベントを発生させることです:

public object SelItem
{
    get { ... }
    set
    {
        if (CancelChange())
        {
            Dispatcher.BeginInvoke((ThreadStart)delegate
            {
                OnPropertyChanged("SelItem");
            });
            return;
        }

        this.selItem = value;
        OnPropertyChanged("SelItem");
    }
}

WPFは、選択変更イベントの処理が完了したにこのメッセージ

VMは明らかに現在のDispatcherアクセスする必要があります。 これを行う方法についていくつかの指針が必要な場合は、基本VMクラスに関する私のブログ投稿を参照し

NathanAW picture
2010年04月26日
14

この質問と回答をありがとう。 Dispatcher.BeginInvokeは私を助け、私の最終的な解決策の一部でしたが、上記の解決策は私のWPF4アプリでは完全には機能しませんでした。

理由を理解するために、小さなサンプルをまとめました。 基になるメンバー変数の値を実際に一時的に変更するコードを追加する必要がありました。これにより、WPFがゲッターを再クエリしたときに、値が変更されたことがわかります。 それ以外の場合、UIはキャンセルを適切に反映せず、BeginInvoke()呼び出しは何もしませんでした。

これが私のブログ投稿で、サンプルが機能していない実装と機能している実装を示しています。

私のセッターは次のようになりました:

    private Person _CurrentPersonCancellable;
    public Person CurrentPersonCancellable
    {
        get
        {
            Debug.WriteLine("Getting CurrentPersonCancellable.");
            return _CurrentPersonCancellable;
        }
        set
        {
            // Store the current value so that we can 
            // change it back if needed.
            var origValue = _CurrentPersonCancellable;

            // If the value hasn't changed, don't do anything.
            if (value == _CurrentPersonCancellable)
                return;

            // Note that we actually change the value for now.
            // This is necessary because WPF seems to query the 
            //  value after the change. The combo box
            // likes to know that the value did change.
            _CurrentPersonCancellable = value;

            if (
                MessageBox.Show(
                    "Allow change of selected item?", 
                    "Continue", 
                    MessageBoxButton.YesNo
                ) != MessageBoxResult.Yes
            )
            {
                Debug.WriteLine("Selection Cancelled.");

                // change the value back, but do so after the 
                // UI has finished it's current context operation.
                Application.Current.Dispatcher.BeginInvoke(
                        new Action(() =>
                        {
                            Debug.WriteLine(
                                "Dispatcher BeginInvoke " + 
                                "Setting CurrentPersonCancellable."
                            );

                            // Do this against the underlying value so 
                            //  that we don't invoke the cancellation question again.
                            _CurrentPersonCancellable = origValue;
                            OnPropertyChanged("CurrentPersonCancellable");
                        }),
                        DispatcherPriority.ContextIdle,
                        null
                    );

                // Exit early. 
                return;
            }

            // Normal path. Selection applied. 
            // Raise PropertyChanged on the field.
            Debug.WriteLine("Selection applied.");
            OnPropertyChanged("CurrentPersonCancellable");
        }
    }
Sorin Comanescu picture
2010年04月06日
1

それを行う別の方法(コメントも読んでください):

http://amazedsaint.blogspot.com/2008/06/wpf-combo-box-cancelling-selection.html

リンクから:グローバル変数なしでイベントハンドラーを再帰的に呼び出す問題の別の解決策は、プログラムによる選択の変更前にハンドラーの割り当てをキャンセルし、その後で再割り当てすることです。

元:

cmb.SelectionChanged -= ComboBox_SelectionChanged;
cmb.SelectedValue = oldSel.Key;
cmb.SelectionChanged += ComboBox_SelectionChanged;
No Ordinary Love picture
2014年10月03日
1

私のやり方は、変更を通過させ、ディスパッチャーでBeginInvokedであるラムダで検証を実行することです。

    public ObservableCollection<string> Items { get; set; }
    private string _selectedItem;
    private string _oldSelectedItem;
    public string SelectedItem
    {
        get { return _selectedItem; }
        set {
            _oldSelectedItem = _selectedItem;
            _selectedItem = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
            }
            Dispatcher.BeginInvoke(new Action(Validate));                
        }
    }

    private void Validate()
    {            
        if (SelectedItem == "Item 5")
        {
            if (MessageBox.Show("Keep 5?", "Title", MessageBoxButton.YesNo) == MessageBoxResult.No)
            {
                SelectedItem = _oldSelectedItem;
            }
        }
    }

またはViewModelで:

   Synchronization.Current.Post(new SendOrPostCallback(Validate), null);