Raisepropertychanged имеет значение null и не уведомляет свойство после изменения контекста данных
У меня есть mainPage, на котором я вызываю свой пользовательский элемент управления. Идея в том, что у меня есть тексблок... и при нажатии кнопки я меняю текст, который находится выше, на имя Hello World.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="Hello, world!" Margin="20" FontSize="30" />
<local1:GenericControl></local1:GenericControl>
<Button x:Name="create" Click="clickBtn"/>
</Grid>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public TestViewModel viewModel { get; set; } = new TestViewModel();
public MainPage()
{
this.InitializeComponent();
this.DataContext = viewModel;
}
public void clickBtn(object sender, RoutedEventArgs e) {
var m = ((Button)sender).DataContext as TestViewModel;
m.CreateModel.HeaderText = "tripathi";
viewModel = m;
var s = new GenericControl {
DataContext = viewModel
};
}
}
и ниже пользователь контролирует то, что я создаю из кода, стоящего за
public GenericControl()
{
this.InitializeComponent();
//this.DataContext = this;
DataContextChanged += (s, e) =>
{
var createModel = this.DataContext as TestViewModel;
CreateControl(createModel.CreateModel);
};
}
private void CreateControl(Test t) {
var grid = new Grid();
grid.SetBinding(Grid.DataContextProperty, new Binding { Source = DataContext, Mode = BindingMode.TwoWay });
//grid.DataContext = this.DataContext;
var tb = new TextBlock();
tb.SetBinding(TextBlock.TextProperty, new Binding { Source = t.HeaderText, Mode = BindingMode.TwoWay
, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged});
grid.Children.Add(tb);
gridlayout.Children.Add(grid);
}
GenericControl.xaml -
<Grid Name="gridlayout">
</Grid>
TestViewModel -
private Test _createModel = new Test();
private string _textHeader;
public Test CreateModel
{
get { return _createModel; }
set { _createModel = value;
Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
RaisePropertyChanged("CreateModel");
});
}
}
public TestViewModel() {
CreateModel.HeaderText = "Ankit";
CreateModel = CreateModel;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
Модель -
private string _headerText;
public string HeaderText
{
get { return _headerText; }
set { _headerText = value;
Task.Factory.StartNew(() =>
{
Thread.Sleep(10000);
RaisePropertyChanged("HeaderText");
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string prop = "") {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
Итак, я получаю правильные значения при отладке, но они не отображаются в пользовательском интерфейсе.
1 ответ
Основная проблема здесь в том, что привязка не работает, заключается в том, что вы не должны устанавливать Source
свойство привязки здесь:
tb.SetBinding(
TextBlock.TextProperty,
new Binding {
Source = t.HeaderText,
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
По сути, это означает, что механизм привязки должен наблюдать за экземпляром строки, который находится в t.HeaderText
при создании привязки, которая, скорее всего, имеет значение null и не может измениться, поскольку string
не наблюдается.
Вместо этого вам нужно сделать следующее:
tb.SetBinding(
TextBlock.TextProperty,
new Binding {
Path = new PropertyPath("CreateModel.HeaderText"),
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
Это укажет механизму привязки установить текущий DataContext
когда TextBlock
добавляется в gridLayout
, который имеет тип TestViewModel
.
Вам также не нужна эта строка:
grid.SetBinding(
Grid.DataContextProperty,
new Binding {
Source = DataContext,
Mode = BindingMode.TwoWay
});
Поскольку это делается автоматически фреймворком.
Вы также должны быть осторожны с созданием пользовательского интерфейса в измененном DataContext, а не с его очисткой, где:
gridlayout.Children.Add(grid);
будет постоянно добавлять TextBlock
экземпляров в gridLayout
, не удаляя предыдущие.
И, наконец, не следует вызывать изменения событий таким образом:
set {
_createModel = value;
Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
RaisePropertyChanged("CreateModel");
});
}
поскольку вы поднимете его из потока, отличного от пользовательского интерфейса, и вызовет исключение. Обязательно используйте Dispatcher.RunAsync
вместо.