Изменение модели просмотра

Я упростил приложение, чтобы показать свою проблему

Когда я нажимаю кнопку, она изменяет Text свойство ViewModel, а TextBlock.Text обновляется.

MainPage.xaml

<StackPanel>
  <Button  Click="ButtonBase_OnClick">Button to change text</Button>
  <TextBlock Text="{x:Bind ViewModel.Text, Mode=OneWay}"></TextBlock>
</StackPanel>

MainPage.xaml.cs

 public MainPage()
    {
        ViewModel = new ViewModel();
        this.InitializeComponent();
    }
 private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        ViewModel.Text = "x:Bind works";
    }

Класс ViewModel имеет одно строковое свойство (текст) и реализует интерфейс INotifyPropertyChange.


Проблема начинается, когда ViewModel не установлен в ctor (т.е. viewModel имеет значение null и изменяется во время выполнения):

   public MainPage()
    {
        //ViewModel = new ViewModel();//this line has been removed
        this.InitializeComponent();
    }
 private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        ViewModel = new ViewModel();//this line has been added
        ViewModel.Text = "x:Bind does not work";
    }

Завершенная привязка не работает (текст не изменен), и я не мог понять, почему это так... Мне нужно изменить viewModel с нуля (vm имеет значение null, потому что он ожидает некоторых данных в настоящее приложение)


person Alamakanambra    schedule 07.07.2016    source источник
comment
Конечно, потому что нет уведомления о том, что ViewModel изменился.   -  person Sir Rufo    schedule 08.07.2016
comment
Вы имеете в виду свойство ViewModel? Как я могу уведомить об изменении - я не хочу реализовывать INotifyPropertychange в MainPage   -  person Alamakanambra    schedule 08.07.2016
comment
Без волшебства вы должны были уведомить страницу об изменении содержимого ViewModel. Неважно, как вы это сделаете, но вы должны   -  person Sir Rufo    schedule 08.07.2016
comment
Возможно, вам следует использовать свойство DataContext — уже есть встроенное событие DataContextChanged, и система привязки знает об этом.   -  person Sir Rufo    schedule 08.07.2016
comment
Без реализации INotifyPropertyChange вам придется сделать гораздо больше, чем просто установить свойство в poco, если вы хотите, чтобы элемент был успешно привязан. Если вы новичок в xaml и привязках, я хотел бы изучить, как работает MVVM, и вы исправите проблему. msdn.microsoft.com/en-gb/library/hh848246.aspx   -  person Oli    schedule 08.07.2016
comment
@Oli Хорошо, чем .. Мне просто показалось неправильным реализовывать INotifyPropertyChenged в классе Page ...   -  person Alamakanambra    schedule 08.07.2016


Ответы (2)


Привязки {x:Bind} (часто называемые скомпилированными привязками) используют сгенерированный код для достижения своих преимуществ. Во время загрузки XAML {x:Bind} преобразуется в то, что можно представить как объект привязки, и этот объект получает значение из свойства в источнике данных. Этот сгенерированный код можно найти в папке obj с такими именами, как (для C#) <view name>.g.cs.

Для вашего кода сгенерированный код будет выглядеть следующим образом:

// Update methods for each path node used in binding steps.
private void Update_(global::UWP.BlankPage3 obj, int phase)
{
    if (obj != null)
    {
        if ((phase & (NOT_PHASED | DATA_CHANGED | (1 << 0))) != 0)
        {
            this.Update_ViewModel(obj.ViewModel, phase);
        }
    }
}
private void Update_ViewModel(global::UWP.ViewModel obj, int phase)
{
    this.bindingsTracking.UpdateChildListeners_ViewModel(obj);
    if (obj != null)
    {
        if ((phase & (NOT_PHASED | DATA_CHANGED | (1 << 0))) != 0)
        {
            this.Update_ViewModel_Text(obj.Text, phase);
        }
    }
}

...

private global::UWP.ViewModel cache_ViewModel = null;
public void UpdateChildListeners_ViewModel(global::UWP.ViewModel obj)
{
    if (obj != cache_ViewModel)
    {
        if (cache_ViewModel != null)
        {
            ((global::System.ComponentModel.INotifyPropertyChanged)cache_ViewModel).PropertyChanged -= PropertyChanged_ViewModel;
            cache_ViewModel = null;
        }
        if (obj != null)
        {
            cache_ViewModel = obj;
            ((global::System.ComponentModel.INotifyPropertyChanged)obj).PropertyChanged += PropertyChanged_ViewModel;
        }
    }
}

Здесь я просто копирую какой-то метод, связанный с вашей проблемой. Из этого метода вы можете обнаружить, что перед обновлением слушателей TextBlock или PropertyChanged он проверяет, является ли ViewModel null. Если это null, ничего не будет сделано. Таким образом, чтобы {x:Bind} работал, мы должны инициализировать ViewModel перед загрузкой страницы. Именно по этой причине {x:Bind} не работает, когда вы инициализируете ViewModel в событии Button.Click.

Чтобы решить эту проблему, вы можете реализовать интерфейс INotifyPropertyChanged для ViewModel, как сказал Филип, чтобы сгенерированный код можно было уведомить об изменении ViewModelnull на new ViewModel()) и обновить пользовательский интерфейс.

Но я думаю, что вы можете просто инициализировать ViewModel в конструкторе. Когда вы инициализируете ViewModel, вы можете сначала установить ожидаемые свойства null, например:

public MainPage()
{
    ViewModel = new ViewModel() { Text = null };
    this.InitializeComponent();
}

А затем обновите эти свойства, когда ваша дата будет готова. Таким образом, вы не сможете реализовать интерфейс INotifyPropertyChanged на своей странице.

Помимо этого, есть еще один более дешевый способ: вы можете вызвать метод this.Bindings.Update();, чтобы принудительно обновить привязки после инициализации ViewModel следующим образом:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    ViewModel = new ViewModel();
    ViewModel.Text = "x:Bind does not work";
    this.Bindings.Update();
}
person Jay Zuo    schedule 08.07.2016
comment
Я знаю, что это старо, но большое спасибо. Я не знал об этом.Bindings.Update(). - person mbl; 16.04.2019

Вы реализовали INotifyPropertyChanged на странице вот так

public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    private ViewModel viewModel;

    public ViewModel ViewModel
    {
        get { return viewModel; }
        set 
        {
            viewModel = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ViewModel)));
        }
    }

    public MainPage()
    {
        ViewModel = new ViewModel { };
        this.InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        ViewModel = new ViewModel {  };//this line has been added
        ViewModel.Text = "x:Bind does not work";
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Это работает для меня.

person Filip Cordas    schedule 07.07.2016