Как выполнить откат выбранного SelectedValue поля со списком с помощью WPF MVVM - PullRequest
4 голосов
/ 04 мая 2011

У меня что-то вроде этого появится у пользователя для получения подтверждения изменений. Если он нажимает «нет», я устанавливаю значение selectedValue в модели представления на предыдущее выделение. Но это не отображается правильно в поле зрения. Пожалуйста, помогите.

Ответы [ 8 ]

6 голосов
/ 23 июня 2011

Если пользователь нажимает нет, и вы пытаетесь отменить значение, а затем вызываете OnPropertyChanged, WPF проглотит событие, так как оно уже отвечает на это событие. Один из способов обойти это - использовать диспетчер и вызвать событие вне контекста текущего события.

System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { OnPropertyChanged("ComSelectedValue"); }), null);
5 голосов
/ 03 февраля 2016

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

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

В большинстве случаев это работает для меня.Просто запустите NotifyPropertyChanged без присвоения значения для отката.

4 голосов
/ 27 августа 2012

WPF, кажется, проверяет, что связанное свойство изменилось перед обновлением пользовательского интерфейса. Так что простой вызов NotifyPropertyChanged () / OnPropertyChanged () не поможет.

Проблема в том, что, поскольку свойство не изменилось, WPF не считает необходимым обновлять поле со списком.

вот невероятно хакерский способ работы с ним в моих моделях ViewMile;

private int _someProperty = -1;
public int SomeProperty
{
    if (_someProperty != -1 && doYesNo() == Yes)
    {
       _someProperty = value;
    }
    else if (_someProperty != -1)
    {
       int oldValue = _someProperty;
       _someProperty = -1; // Set to a different/invalid value so WPF sees a change
       Dispatcher.BeginInvoke(new Action(() => { SomeProperty = oldValue; }));
    }
    else 
    {
       _someProperty = value;
    }
    NotifyPropertyChanged("SomeProperty");
}

Не красиво, но работает надежно.

0 голосов
/ 23 февраля 2015

Я понимаю, что это старый пост, но, похоже, никто не сделал это правильно.Я использовал System.Interactivity.Triggers и Prism для обработки события SelectionChanged и ручного запуска SelectedItem.Это предотвратит нежелательные изменения выбранного элемента как в пользовательском интерфейсе, так и в модели представления.

Мое представление:

<Window x:Class="Lind.WPFTextBlockTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:Lind.WPFTextBlockTest"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
    Title="MainWindow" Height="649" Width="397">
<Window.DataContext>
    <vm:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
    <ComboBox ItemsSource="{Binding Data}" SelectedItem="{Binding SelectedData, Mode=OneWay}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <prism:InvokeCommandAction Command="{Binding TryChangeSelectedData}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ComboBox>
</StackPanel>

Моя модель представления (BindeableBase и DelegateCommand fromПризма 5):

public class MainWindowViewModel : BindableBase
{
    public ObservableCollection<string> Data { get; private set; }
    private string selectedData;
    public string SelectedData
    {
        get { return selectedData; }
        set
        {
            SetProperty(ref selectedData, value);
        }
    }
    public DelegateCommand<SelectionChangedEventArgs> TryChangeSelectedData { get; private set; }
    public MainWindowViewModel()
    {
        Data = new ObservableCollection<string>() { "Foo", "Bar", "Dick", "Head" };
        SelectedData = Data.First();
        TryChangeSelectedData = new DelegateCommand<SelectionChangedEventArgs>(args =>
        {
            var newValue = args.AddedItems.Cast<string>().FirstOrDefault();
            if (newValue == "Dick")
                this.OnPropertyChanged(() => this.SelectedData);
            else
                SelectedData = newValue;
        });
    }
}
0 голосов
/ 05 октября 2014

Вот общий поток, который я использую:

  1. Я просто позволил изменению пройти через ViewModel и отследить все, что было передано ранее.(Если ваша бизнес-логика требует, чтобы выбранный элемент не находился в недопустимом состоянии, я предлагаю переместить его в сторону Модели).Этот подход также удобен для ListBox, которые визуализируются с использованием Radio Buttons, поскольку как можно скорее завершает работу установщика SelectedItem, что не помешает подсветке переключателей при появлении окна сообщения.
  2. Я немедленно вызываю событие OnPropertyChangedнезависимо от передаваемого значения.
  3. Я помещаю любую логику отмены в обработчик и вызываю ее, используя SynchronizationContext.Post () (BTW: SynchronizationContext.Post также работает для приложений Магазина Windows. Так что если у вас естьобщий код ViewModel, этот подход все еще будет работать).

    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public List<string> Items { get; set; }
    
        private string _selectedItem;
        private string _previouslySelectedItem;
        public string SelectedItem
        {
            get
            {
                return _selectedItem;
            }
            set
            {
                _previouslySelectedItem = _selectedItem;
                _selectedItem = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
                }
                SynchronizationContext.Current.Post(selectionChanged, null);
            }
        }
    
        private void selectionChanged(object state)
        {
            if (SelectedItem != Items[0])
            {
                MessageBox.Show("Cannot select that");
                SelectedItem = Items[0];
            }
        }
    
        public ViewModel()
        {
            Items = new List<string>();
            for (int i = 0; i < 10; ++i)
            {
                Items.Add(string.Format("Item {0}", i));
            }
        }
    }
    
0 голосов
/ 16 апреля 2014

Что если вы попытаетесь вызвать событие измененного свойства асинхронно?Это похоже на примеры из Shaun и NebulaSleuth.

  public int SomeProperty
  {
     get { return m_someProperty; }
     set
     {
        if (value == m_someProperty)
           return;

        if (doYesNo() == No)
        {
           // Don't update m_someProperty but let the UI know it needs to retrieve the value again asynchronously.
           Application.Current.Dispatcher.BeginInvoke((Action) (() => NotifyOfPropertyChange("SomeProperty")));
        }
        else
        {
           m_someProperty = value;
           NotifyOfPropertyChange("SomeProperty");
        }
     }
  }
0 голосов
/ 04 мая 2011

В большинстве приложений WPF вы связываете модель представления с пользовательским интерфейсом с помощью режима TwoWay, а затем настраиваете его на запуск.

Однако это идет вразрез с типичным пользовательским интерфейсом, где при редактировании чего-либо ивы не сохраняете, вы не видите, что редактирование отражается во всем приложении, даже если вы не сохраняете свои изменения в базе данных.

Механизм, доступный в WPF, является свойством UpdateSourceTrigger привязки.,С помощью этого свойства вы можете контролировать, когда пользовательский интерфейс обновляет ViewModel, с которой он связан.Это позволяет вам обновлять только когда пользователь сохраняет то, что он редактирует или что-то подобное.

Пример привязки XAML с UpdateSourceTrigger, установленным в Explicit:

"{Binding Path=Privado, UpdateSourceTrigger=Explicit, Mode=TwoWay}"

И когда вы хотитедействительно сохранить в ViewModel, которую вы называете:

UpdateSource();
0 голосов
/ 04 мая 2011

Допущения:
- Вы отображаете диалоговое окно (с сообщением и кнопками OKCancel), когда пользователь выбирает какое-либо значение из ComboBox.
- Если пользователь нажимает OK, все в порядке.:)
- Если пользователь нажимает Отмена, вы говорите vmPropSelectedValue = previousValue.

Это не сработает.Зачем?

Точного ответа нет, но я полагаю, что когда вы показываете диалоговое окно, система только что изменила выбранное значение, а только что уведомил источник (через инфраструктуру привязки) об измененном значении.Если в этот момент (когда источник имеет контроль), вы теперь измените значение свойства ViewModel из своего кода виртуальной машины, что, как вы ожидаете, вызовет OnPropertyChanged из INotifyPropertyChanged, который, как вы ожидаете, попросит WPF обновить цель с вашим запрошенным значением.Однако WPF еще не завершил цикл - он все еще ждет, пока Источник вернет ему элемент управления.Поэтому он просто отклоняет ваш запрос (в противном случае он будет идти в бесконечном цикле).

Если это сбивает с толку, попробуйте следующее:
Цикл начинается:
1. Пользователь изменяет значение в пользовательском интерфейсе.WPF меняет цель.
2. привязка инфраструктуры запрашивает источник об обновлении самого себя.
3. Источник обновляет себя (свойство VM).
4. Источник возвращает управление обратно связыванию ниже.
Завершение цикла.

Эксперты: Не удалось найти какую-либо документацию по этому вопросу.Выше моя вера, как все работает.Пожалуйста, исправьте, если неверно.


Краткий ответ:
AFAIK, это невозможно сделать только с помощью чистого кода виртуальной машины.Вам нужно будет добавить код с выделенным кодом.
Вот один из способов: http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...