У меня есть древовидное представление, построенное с помощью HierarchicalDataTemplates, я хочу иметь возможность добавлять файлы JSON в узлы SegmentInfo - если я это сделаю, данные будут добавлены, но изменение не будет отражено в пользовательском интерфейсе (тем не менее комментарий говорит "нет данных" красным ).
Я составил список элементов древовидного представления как ObservableCollection, переместил его в класс ViewModel, который наследует INotifyPropertyChanged, кажется, я правильно его настроил, я установил DataContext для объекта 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>
Класс окна:
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:
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));
}
}
Класс TypeInfo:
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>();
}
}
Класс СегментИнфо:
public class SegmentInfo
{
public string Name { get; set; }
public bool HasData { get; set; }
public string JsonContents { get; set; }
public int ElementId { get; set; }
}
Преобразователи классов:
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 пользовательский интерфейс обновит узел с комментарием «имеет данные». Теперь я могу проверить, что данные действительно добавляются в SegmentInfo, но пользовательский интерфейс этого не отражает.
@Sinatr А свойство DataContext по-прежнему должно быть объектом ViewModel?
Другая возможность, если вы хотите обновить представления, — установить DataContext на null, а затем обратно. Тогда вам не нужно менять TypeInfo.
Обновление DataContext @Sinatr работает нормально, но не кажется очень элегантным, не так ли? Реализация INotifyPropertyChanged на TypeInfo не работает, или я не правильно делаю...





Ваше свойство HasData не обновляет пользовательский интерфейс, так как у вас нет механизма для его обновления (INotifyPropertyChanged). SegmentInfo необходимо реализовать INotifyPropertyChanged.
Если вы планируете иметь привязку свойства к пользовательскому интерфейсу, для него должно быть отправлено отдельное уведомление PropertyChanged Notification. Итак, в вашем SegmentInfo классе; Name, HasData и JsonContent должны вызывать событие OnPropertyChanged в своем сеттере.
Хороший способ думать об этом; все, что напрямую связано с XAML (Text = "{Binding Name}"), должно вызывать событие при изменении. Если вы привяжете какие-либо свойства, такие как: (Text = "{Binding MyThing.Name}"), вы не получите обновление при изменении MyThing.Name. Вам нужно вырваться из собственности и уведомить об этом напрямую.
Я также не уверен на 100%, нужно ли вам использовать выход конвертера в ваших триггерах has data и no data. А нельзя просто использовать True и False?
Да, с DataTrigger. Он также мог бы использовать <local:BoolToStringConverter x:Key = "BoolToRedGreenConverter" FalseValue = "Red" TrueValue = "Green" />, а затем <TextBlock Foreground = "{Binding HasData, Converter = {StaticResource BoolToRedGreenConverter}}" ... /> — я думаю, это правильно преобразует строки в кисти. Если нет, он может сделать BoolToBrushConverter или BoolToObject конвертер и инициализировать FalseValue = "{x:Static Brushes.Red}".
TypeInfoдолжен реализоватьINotifyPropertyChanged, если вы хотите позже присвоить новое значение свойствуSegmentInfos.