Изменение видовой модели
Я упростил приложение, чтобы показать мою проблему
Когда я нажимаю кнопку, она меняется 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;
}
Это работает для меня.