Изменение видовой модели

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

Когда я нажимаю кнопку, она меняется 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 имеет одно строковое свойство (Text) и реализован интерфейс 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 является нулем, потому что он ждет некоторых данных в реальном приложении)

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 как сказал Филипп, чтобы сгенерированный код мог быть уведомлен, когда ViewModel изменилось (с null в 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();
}

Вы реализовали 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;
}

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

Другие вопросы по тегам