WPF - treeviewitems 属性的更改未反映在 UI 问题中
WPF - Changes in treeviewitems' properties not reflected in UI problem
我有一个使用 HierarchicalDataTemplates 构建的树视图,我希望能够将 JSON 文件添加到 SegmentInfo 节点 - 如果我这样做,数据会被添加,但更改不会反映在 UI(评论仍然以红色显示 "no data")。
我已将树视图项目列表设为 ObservableCollection,将其移动到继承 INotifyPropertyChanged 的 "ViewModel" class,我似乎设置正确,我已将 DataContext 设置为我的 Window 中的 ViewModel 对象。
在 xaml 中,我将绑定和模式设置为 TwoWay。仍然没有任何帮助
XAML:
<Window.Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter" FalseValue="no data" TrueValue="has data" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" MinHeight="384.8"/>
<RowDefinition Height="35.2"/>
</Grid.RowDefinitions>
<TreeView Name="trvTypeInfos" Margin="5" Grid.Row="0" ItemsSource="{Binding Path=TypeInfoList, Mode=TwoWay}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="ListBoxItem.PreviewMouseUp"
Handler="ListBoxItem_PreviewMouseUp"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:TypeInfo}" ItemsSource="{Binding SegmentInfos, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding SegmentInfos.Count}" Foreground="Blue"/>
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:SegmentInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=HasData, Mode=TwoWay, Converter={StaticResource BoolToStringConverter}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="no data">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<Trigger Property="Text" Value="has data">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="80" Height="20" Content="OK" Margin="5,0, 5, 5" IsDefault="True" Click="OK_Click"/>
<Button Width="80" Height="20" Content="Cancel" Margin="5,0, 5, 5" Click="Cancel_Click" />
</StackPanel>
</Grid>
Window class:
public SegmentDataUpdaterDialog(SegmentDataUpdater segmentDataUpdater, List<TypeInfo> typeInfoList)
{
ViewModel = new ViewModel(typeInfoList);
DataContext = ViewModel;
SegmentDataUpdater = segmentDataUpdater;
InitializeComponent();
}
private void ListBoxItem_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
SegmentInfo segInfo = item.Header as SegmentInfo;
if (segInfo != null)
{
MessageBox.Show(segInfo.JsonContents);
var filePath = AskForFile();
bool success = SegmentDataUpdater.TryStoreJson(segInfo, filePath, out string json);
if (success)
{
segInfo.JsonContents = json;
segInfo.HasData = true;
}
}
}
ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TypeInfo> _typeInfoList;
public ObservableCollection<TypeInfo> TypeInfoList
{
get { return _typeInfoList; }
set
{
if (_typeInfoList==null || !value.All(_typeInfoList.Contains))
{
_typeInfoList = value;
OnPropertyChanged(nameof(TypeInfoList));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(List<TypeInfo> typeInfos)
{
TypeInfoList = new ObservableCollection<TypeInfo>(typeInfos);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
类型信息class:
public class TypeInfo
{
public string Name { get; set; }
public ObservableCollection<SegmentInfo> SegmentInfos { get; set; }
public int ElementId { get; set; }
public TypeInfo()
{
SegmentInfos = new ObservableCollection<SegmentInfo>();
}
}
段信息class:
public class SegmentInfo
{
public string Name { get; set; }
public bool HasData { get; set; }
public string JsonContents { get; set; }
public int ElementId { get; set; }
}
转换器 classes:
public class BoolToValueConverter<T> : IValueConverter
{
public T FalseValue { get; set; }
public T TrueValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
public class BoolToStringConverter : BoolToValueConverter<String> { }
我希望在成功将 json 文件添加到 SegmentInfo 后,UI 将使用 "has data" 注释更新节点。
现在我可以检查数据是否真的添加到 SegmentInfo 但 UI 没有反映出来。
您的 HasData
属性 不会更新 UI,因为您没有更新它的机制 (INotifyPropertyChanged)。 SegmentInfo
需要实施 INotifyPropertyChanged。
如果您计划将 属性 绑定到 UI,则需要单独发出 PropertyChanged 通知。所以在你的 SegmentInfo
class 上; Name
、HasData
和 JsonContent
应该各自在其 setter 中引发一个 OnPropertyChanged
事件。
想的好办法; XAML (Text="{Binding Name}"
) 中直接绑定的任何内容在更改时都应引发事件。如果您绑定任何属性,如:(Text="{Binding MyThing.Name}"
),您将不会在 MyThing.Name
更改时获得更新。你需要打破 属性 并直接通知它。
我有一个使用 HierarchicalDataTemplates 构建的树视图,我希望能够将 JSON 文件添加到 SegmentInfo 节点 - 如果我这样做,数据会被添加,但更改不会反映在 UI(评论仍然以红色显示 "no data")。
我已将树视图项目列表设为 ObservableCollection,将其移动到继承 INotifyPropertyChanged 的 "ViewModel" class,我似乎设置正确,我已将 DataContext 设置为我的 Window 中的 ViewModel 对象。 在 xaml 中,我将绑定和模式设置为 TwoWay。仍然没有任何帮助
XAML:
<Window.Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter" FalseValue="no data" TrueValue="has data" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" MinHeight="384.8"/>
<RowDefinition Height="35.2"/>
</Grid.RowDefinitions>
<TreeView Name="trvTypeInfos" Margin="5" Grid.Row="0" ItemsSource="{Binding Path=TypeInfoList, Mode=TwoWay}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="ListBoxItem.PreviewMouseUp"
Handler="ListBoxItem_PreviewMouseUp"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:TypeInfo}" ItemsSource="{Binding SegmentInfos, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding SegmentInfos.Count}" Foreground="Blue"/>
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:SegmentInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=HasData, Mode=TwoWay, Converter={StaticResource BoolToStringConverter}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="no data">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<Trigger Property="Text" Value="has data">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="80" Height="20" Content="OK" Margin="5,0, 5, 5" IsDefault="True" Click="OK_Click"/>
<Button Width="80" Height="20" Content="Cancel" Margin="5,0, 5, 5" Click="Cancel_Click" />
</StackPanel>
</Grid>
Window class:
public SegmentDataUpdaterDialog(SegmentDataUpdater segmentDataUpdater, List<TypeInfo> typeInfoList)
{
ViewModel = new ViewModel(typeInfoList);
DataContext = ViewModel;
SegmentDataUpdater = segmentDataUpdater;
InitializeComponent();
}
private void ListBoxItem_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
SegmentInfo segInfo = item.Header as SegmentInfo;
if (segInfo != null)
{
MessageBox.Show(segInfo.JsonContents);
var filePath = AskForFile();
bool success = SegmentDataUpdater.TryStoreJson(segInfo, filePath, out string json);
if (success)
{
segInfo.JsonContents = json;
segInfo.HasData = true;
}
}
}
ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TypeInfo> _typeInfoList;
public ObservableCollection<TypeInfo> TypeInfoList
{
get { return _typeInfoList; }
set
{
if (_typeInfoList==null || !value.All(_typeInfoList.Contains))
{
_typeInfoList = value;
OnPropertyChanged(nameof(TypeInfoList));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(List<TypeInfo> typeInfos)
{
TypeInfoList = new ObservableCollection<TypeInfo>(typeInfos);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
类型信息class:
public class TypeInfo
{
public string Name { get; set; }
public ObservableCollection<SegmentInfo> SegmentInfos { get; set; }
public int ElementId { get; set; }
public TypeInfo()
{
SegmentInfos = new ObservableCollection<SegmentInfo>();
}
}
段信息class:
public class SegmentInfo
{
public string Name { get; set; }
public bool HasData { get; set; }
public string JsonContents { get; set; }
public int ElementId { get; set; }
}
转换器 classes:
public class BoolToValueConverter<T> : IValueConverter
{
public T FalseValue { get; set; }
public T TrueValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
public class BoolToStringConverter : BoolToValueConverter<String> { }
我希望在成功将 json 文件添加到 SegmentInfo 后,UI 将使用 "has data" 注释更新节点。 现在我可以检查数据是否真的添加到 SegmentInfo 但 UI 没有反映出来。
您的 HasData
属性 不会更新 UI,因为您没有更新它的机制 (INotifyPropertyChanged)。 SegmentInfo
需要实施 INotifyPropertyChanged。
如果您计划将 属性 绑定到 UI,则需要单独发出 PropertyChanged 通知。所以在你的 SegmentInfo
class 上; Name
、HasData
和 JsonContent
应该各自在其 setter 中引发一个 OnPropertyChanged
事件。
想的好办法; XAML (Text="{Binding Name}"
) 中直接绑定的任何内容在更改时都应引发事件。如果您绑定任何属性,如:(Text="{Binding MyThing.Name}"
),您将不会在 MyThing.Name
更改时获得更新。你需要打破 属性 并直接通知它。