Изменить свойство в методе PropertyChanged, не обновляя представление

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

ViewModel

public class VM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    private string selected;
    public string Selected
    {
        get { return selected; }
        set
        {
            if (selected != value)
            {
                selected = value;
                OnPropertyChanged("Selected");
            }
        }
    }

    private ObservableCollection<string> collection;
    public ObservableCollection<string> Collection
    {
        get { return collection; }
        set
        {
            collection = value;
            OnPropertyChanged("Collection");
        }
    }

    public VM()
    {
        this.Collection = new ObservableCollection<string>(new string[] { "A", "B", "C" });
        this.Selected = "A";
        this.PropertyChanged += VM_PropertyChanged;
    }

    void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.Selected = "C";
    }
}

Вид

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <Grid>
      <ComboBox ItemsSource="{Binding Collection}" SelectedValue="{Binding Selected}"/>
    </Grid>
    <Label Content="{Binding Selected, UpdateSourceTrigger=PropertyChanged}"/>
 </StackPanel>
</Window>

Итак, в этом примере, независимо от того, что я выбираю, он должен отображать «C» как в поле со списком, так и в метке, но «C» отображается только на метке, это означает, что ViewModel обновляется, но не представление.

Кажется, проблема здесь в том, чтобы попытаться изменить свойство из метода PropertyChanged.

введите здесь описание изображения

Что может быть не так?


person The One    schedule 25.04.2017    source источник
comment
Была ли это просто ошибка копирования/вставки, когда ваша модель представления использует свойство Collection, тогда как ваше представление привязывается к Coleccion?   -  person wablab    schedule 25.04.2017
comment
Почему вы добавили UpdateSourceTrigger=PropertyChanged к привязкам свойств, которые не могут обновлять источник? Почему вы ожидаете, что ваш ComboBox назначит новую ObservableCollection для Collection?   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 25.04.2017
comment
@wablab Да, исходный код на испанском, спасибо   -  person The One    schedule 25.04.2017
comment
Я знаю, ты прав, я так привыкла так писать, что это стало плохой привычкой.   -  person The One    schedule 25.04.2017
comment
Не уверен, какова ваша конечная цель, но, возможно, вы могли бы реализовать и привязать к INotifyPropertyChanging.PropertyChanging и, возможно, установить для этого Selected в обработчике событий. Я не пробовал, поэтому не знаю, поможет или нет. И на самом деле вам может даже не понадобиться привязываться к нему; вы могли бы справиться со всем этим в самой модели представления. (Но если вы собираетесь это сделать, то может иметь смысл просто изменить значение Selected в самом установщике.)   -  person wablab    schedule 25.04.2017
comment
Спасибо @wablab, я не знал об INotifyPropertyChanging, я следовал примеру в этой link, изменение корректно отменяется, я могу установить для свойства Selected новое значение, но пользовательский интерфейс не обновляется   -  person The One    schedule 25.04.2017


Ответы (2)


Вот как я, скорее всего, это сделаю, но вызов BeginInvoke(), который творит чудеса, может быть так же легко вызван из вашего обработчика PropertyChanged.

То, что он делает, по сути, ставит в очередь действие, которое должно произойти после того, как вся работа с набором свойств будет полностью завершена. Флаг DispatcherPriority.ApplicationIdle является ключевым моментом.

Как вы уже поняли, обработчику PropertyChanged и установщику свойств бесполезно поднимать PropertyChanged, пока ComboBox все еще находится в процессе изменения своего выбора. Этот код позволяет всему этому закончиться, а затем немедленно меняет Selected на что-то другое. В этот момент ComboBox будет занято тем, что заметит ваше событие PropertyChanged и обновит свой собственный выбор.

private string selected;
public string Selected
{
    get { return selected; }
    set
    {
        if (selected != value)
        {
            //  Don't let them select "B". 
            if (value == "B")
            {
                Dispatcher.CurrentDispatcher.
                    BeginInvoke(new Action(() => this.Selected = "C"),
                                DispatcherPriority.ApplicationIdle);
                return;
            }
            selected = value;
            OnPropertyChanged("Selected");
        }
    }
}
person 15ee8f99-57ff-4f92-890c-b56153    schedule 25.04.2017

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

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

Это сказало

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

Есть множество способов заставить код работать так, как вы хотите. Вероятно, проще всего просто отложить обновление исходного свойства до завершения обработки пользовательского ввода. Вы можете сделать это, используя метод Dispatcher.InvokeAsync():

void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    Dispatcher.CurrentDispatcher.InvokeAsync(() => this.Selected = "C");
}

Я не большой поклонник вышеизложенного из-за того, что он берет то, что в идеале должно быть объектом, не зависящим от представления, модель представления, и вводит знания о вашем конкретном API представления, то есть использование объекта Dispatcher. Тем не менее, вы можете использовать другие механизмы, которые похожи и не зависят от объекта Dispatcher (например, с использованием асинхронного таймера).

В Stack Overflow есть множество других примеров того, как решить эту проблему. Например, вы можете посмотреть этот ответ для вдохновения. Я не думаю, что он будет делать именно то, что вы хотите, «прямо из коробки», но подход с прикрепленным свойством может быть чем-то, что вы сочтете более подходящим, путем перемещения логики из модели представления в код представления и, таким образом, место, где она более уместно использовать Dispatcher. (И, возможно, если вы собираетесь сделать что-то подобное, логика, вероятно, принадлежит представлению в любом случае, это достаточно странно, но я не вижу убедительной причины, по которой это должно быть присуще модели представления.)

Другой подход можно увидеть в вопросе Принудительно заставить текстовое поле WPF больше не работать в .NET 4.0. т.е. вручную заставить состояние представления обновляться постфактум.

person Peter Duniho    schedule 25.04.2017
comment
Просто чтобы объяснить немного больше, я показывал всплывающее диалоговое окно, запрашивающее у пользователя информацию только после выбора определенного элемента из поля со списком только при определенных условиях, если пользователь отменяет диалоговое окно, тогда выбранный элемент должен вернуться к предыдущему. Вы отвечаете отлично, и я переделаю логику, чтобы не уходить с правильного пути дизайна. - person The One; 25.04.2017
comment
@ Туко: понятно. Да, по крайней мере, если вы предоставляете пользователю некоторое уведомление о том, что настройка отменяется, это позволит избежать большой путаницы пользователей. - person Peter Duniho; 26.04.2017