Отмена выбора в выпадающем списке в WPF с MVVM

Многие объяснения уже присутствуют, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать рекомендациям, чтобы избежать NullPointerException вообще.

См. также: A хороший список лучших практик

Я бы добавил, очень важно, хорошо использовать модификатор final. Использование "окончательной" модификатор, когда это применимо в Java

Сводка:

  1. Используйте модификатор final для обеспечения хорошей инициализации.
  2. Избегайте возврата null в методы, например, при возврате пустых коллекций.
  3. Использовать аннотации @NotNull и @Nullable
  4. Быстрое завершение работы и использование утверждений, чтобы избежать распространения нулевых объектов через все приложение, когда они не должен быть пустым.
  5. Сначала используйте значения с известным объектом: if("knownObject".equals(unknownObject)
  6. Предпочитают valueOf() поверх toString ().
  7. Используйте null safe StringUtils StringUtils.isEmpty(null).

25
задан BoltClock 17 October 2011 в 22:02
поделиться

4 ответа

Чтобы достичь этого под MVVM ....

1] Имейте прикрепленное поведение, которое обрабатывает событие SelectionChanged ComboBox. Это событие вызывается с некоторыми аргументами события, имеющими флаг Handled. Но установка его в true бесполезна для SelectedValue привязки. Привязка обновляет источник независимо от того, было ли обработано событие.

2] Следовательно, мы настраиваем привязку ComboBox.SelectedValue на TwoWay и Explicit.

3] Только когда ваша проверка удовлетворена и в окне сообщений говорится Yes, когда мы выполняем BindingExpression.UpdateSource(). В противном случае мы просто вызываем BindingExpression.UpdateTarget(), чтобы вернуться к старому выбору.


В моем примере ниже у меня есть список KeyValuePair<int, int>, связанный с контекстом данных окна. ComboBox.SelectedValue связан с простым записываемым свойством MyKey из Window.

XAML ...

    <ComboBox ItemsSource="{Binding}"
              DisplayMemberPath="Value"
              SelectedValuePath="Key"
              SelectedValue="{Binding MyKey,
                                      ElementName=MyDGSampleWindow,
                                      Mode=TwoWay,
                                      UpdateSourceTrigger=Explicit}"
              local:MyAttachedBehavior.ConfirmationValueBinding="True">
    </ComboBox>

Где MyDGSampleWindow - это x: Имя Window.

Код позади ...

public partial class Window1 : Window
{
    private List<KeyValuePair<int, int>> list1;

    public int MyKey
    {
        get; set;
    }

    public Window1()
    {
        InitializeComponent();

        list1 = new List<KeyValuePair<int, int>>();
        var random = new Random();
        for (int i = 0; i < 50; i++)
        {
            list1.Add(new KeyValuePair<int, int>(i, random.Next(300)));
        }

        this.DataContext = list1;
    }
 }

И прикрепленное поведение

public static class MyAttachedBehavior
{
    public static readonly DependencyProperty
        ConfirmationValueBindingProperty
            = DependencyProperty.RegisterAttached(
                "ConfirmationValueBinding",
                typeof(bool),
                typeof(MyAttachedBehavior),
                new PropertyMetadata(
                    false,
                    OnConfirmationValueBindingChanged));

    public static bool GetConfirmationValueBinding
        (DependencyObject depObj)
    {
        return (bool) depObj.GetValue(
                        ConfirmationValueBindingProperty);
    }

    public static void SetConfirmationValueBinding
        (DependencyObject depObj,
        bool value)
    {
        depObj.SetValue(
            ConfirmationValueBindingProperty,
            value);
    }

    private static void OnConfirmationValueBindingChanged
        (DependencyObject depObj,
        DependencyPropertyChangedEventArgs e)
    {
        var comboBox = depObj as ComboBox;
        if (comboBox != null && (bool)e.NewValue)
        {
            comboBox.Tag = false;
            comboBox.SelectionChanged -= ComboBox_SelectionChanged;
            comboBox.SelectionChanged += ComboBox_SelectionChanged;
        }
    }

    private static void ComboBox_SelectionChanged(
        object sender, SelectionChangedEventArgs e)
    {
        var comboBox = sender as ComboBox;
        if (comboBox != null && !(bool)comboBox.Tag)
        {
            var bndExp
                = comboBox.GetBindingExpression(
                    Selector.SelectedValueProperty);

            var currentItem
                = (KeyValuePair<int, int>) comboBox.SelectedItem;

            if (currentItem.Key >= 1 && currentItem.Key <= 4
                && bndExp != null)
            {
                var dr
                    = MessageBox.Show(
                        "Want to select a Key of between 1 and 4?",
                        "Please Confirm.",
                        MessageBoxButton.YesNo,
                        MessageBoxImage.Warning);
                if (dr == MessageBoxResult.Yes)
                {
                    bndExp.UpdateSource();
                }
                else
                {
                    comboBox.Tag = true;
                    bndExp.UpdateTarget();
                    comboBox.Tag = false;
                }
            }
        }
    }
}

В поведении я использую свойство ComboBox.Tag для временного хранения пропущенного флага перепроверка, когда мы возвращаемся к старому выбранному значению.

Дайте мне знать, если это поможет.

17
ответ дан WPF-it 17 October 2011 в 22:02
поделиться

Очень простое решение для .NET 4.5.1 +:

<ComboBox SelectedItem="{Binding SelectedItem, Delay=10}" ItemsSource="{Binding Items}"  />

Это работает для меня в большинстве случаев. Вы можете откатить выделение в выпадающем списке, просто запустите NotifyPropertyChanged без присвоения значения.

18
ответ дан Dvor_nik 17 October 2011 в 22:02
поделиться

Я предпочитаю пример кода «сплинтора», а не «AngelWPF». Их подходы довольно похожи, хотя. Я реализовал прикрепленное поведение CancellableSelectionBehavior, и оно работает так, как рекламируется. Возможно, это было просто, что код в примере сплинтора было легче подключить к моему приложению. Код в прикрепленном поведении AngelWPF содержал ссылки на тип KeyValuePair, который потребовал бы большего изменения кода.

В моем приложении был ComboBox, в котором элементы, отображаемые в DataGrid, основаны на элементе, выбранном в ComboBox. Если пользователь внес изменения в DataGrid, а затем выбрал новый элемент в ComboBox, я бы предложил пользователю сохранить изменения с помощью кнопок Да | Нет | Отмена в качестве параметров. Если они нажали «Отмена», я хотел игнорировать их новый выбор в ComboBox и сохранить старый выбор. Это сработало как победитель!

Для тех, кто пугает, когда видит ссылки на Blend и System.Windows.Interactivity, вам не нужно устанавливать Microsoft Expression Blend. Вы можете скачать Blend SDK для .NET 4 (или Silverlight).

Blend SDK для .NET 4

Blend SDK для Silverlight 4

О да, в моем XAML я действительно использую это как мое объявление пространства имен для Blend в этом примере:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
1
ответ дан Andy 17 October 2011 в 22:02
поделиться

Я сделал это аналогично тому, что сплинтор выше.

Ваше представление:

<ComboBox  
ItemsSource="{Binding CompetitorBrands}" 
DisplayMemberPath="Value" 
SelectedValuePath="Key" 
SelectedValue="{Binding Path=CompMfgBrandID, 
Mode=TwoWay,
UpdateSourceTrigger=Explicit}" //to indicate that you will call UpdateSource() manually to get the property "CompMfgBrandID" udpated 
SelectionChanged="ComboBox_SelectionChanged"  //To fire the event from the code behind the view
Text="{Binding CompMFGText}"/>

Ниже приведен код для обработчика событий «ComboBox_SelectionChanged» из файла кода за представлением. Например, если вы просматриваете myview.xaml, имя файла кода для этого обработчика событий должно быть myview.xaml.cs

private int previousSelection = 0; //Give it a default selection value

private bool promptUser true; //to be replaced with your own property which will indicates whether you want to show the messagebox or not.

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBox comboBox = (ComboBox) sender;
            BindingExpression be = comboBox.GetBindingExpression(ComboBox.SelectedValueProperty);

            if (comboBox.SelectedValue != null && comboBox.SelectedIndex != previousSelection)
            {
                if (promptUser) //if you want to show the messagebox..
                {
                    string msg = "Click Yes to leave previous selection, click No to stay with your selection.";
                    if (MessageBox.Show(msg, "Confirm", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) //User want to go with the newest selection
                    {

                        be.UpdateSource(); //Update the property,so your ViewModel will continue to do something
                        previousSelection = (int)comboBox.SelectedIndex;  
                    }
                    else //User have clicked No to cancel the selection
                    {
                        comboBox.SelectedIndex = previousSelection; //roll back the combobox's selection to previous one
                    }
                }
                else //if don't want to show the messagebox, then you just have to update the property as normal.
                {
                    be.UpdateSource();
                    previousSelection = (int)comboBox.SelectedIndex;
                }
            }
        }
0
ответ дан Yongquan 17 October 2011 в 22:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: