WPF - изменения в свойствах элементов дерева, не отраженных в проблеме с пользовательским интерфейсом

У меня есть древовидное представление, построенное с помощью 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, но пользовательский интерфейс этого не отражает.

TypeInfo должен реализовать INotifyPropertyChanged, если вы хотите позже присвоить новое значение свойству SegmentInfos.
Sinatr 23.05.2019 15:18

@Sinatr А свойство DataContext по-прежнему должно быть объектом ViewModel?

adam.k 23.05.2019 15:22

Другая возможность, если вы хотите обновить представления, — установить DataContext на null, а затем обратно. Тогда вам не нужно менять TypeInfo.

Sinatr 23.05.2019 15:26

Обновление DataContext @Sinatr работает нормально, но не кажется очень элегантным, не так ли? Реализация INotifyPropertyChanged на TypeInfo не работает, или я не правильно делаю...

adam.k 23.05.2019 15:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
430
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваше свойство 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?

Ginger Ninja 23.05.2019 16:32

Да, с DataTrigger. Он также мог бы использовать <local:BoolToStringConverter x:Key = "BoolToRedGreenConverter" FalseValue = "Red" TrueValue = "Green" />, а затем <TextBlock Foreground = "{Binding HasData, Converter = {StaticResource BoolToRedGreenConverter}}" ... /> — я думаю, это правильно преобразует строки в кисти. Если нет, он может сделать BoolToBrushConverter или BoolToObject конвертер и инициализировать FalseValue = "{x:Static Brushes.Red}".

15ee8f99-57ff-4f92-890c-b56153 23.05.2019 16:42

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