WPF - Changes in treeviewitems' properties not

2019-08-23 10:25发布

I have a tree view built with HierarchicalDataTemplates, I want to be able to add JSON files to SegmentInfo nodes - if I do so, the data is added but the change is not reflected in UI (still the comment says "no data" in red).

I've made the list the tree view items as ObservableCollection, moved it to a "ViewModel" class that inherits INotifyPropertyChanged, I seem to set it up properly, I've set DataContext to the ViewModel object in my Window. In xaml I've set the bindings and mode as TwoWay. Still nothing helped

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));
    }
}

TypeInfo 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>();
    }
}

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; }
}

Converter 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> { }

I expect that after successful adding json file to the SegmentInfo the UI will update the node with "has data" comment. Now I can check that the data is really added to the SegmentInfo but UI doesn't reflect that.

1条回答
走好不送
2楼-- · 2019-08-23 10:46

Your HasData property does not update the UI, as you have no mechanism to update it (INotifyPropertyChanged). SegmentInfo needs to implement INotifyPropertyChanged.

If you plan to have a property Bind to the UI, it needs to have an PropertyChanged Notification go out for it individually. So on your SegmentInfo class; Name, HasData, and JsonContent should each raise an OnPropertyChanged event in their setter.

A good way to think of it; anything that is directly bound in XAML (Text="{Binding Name}") should raise an event when changed. If you bind any properties like: (Text="{Binding MyThing.Name}") you will not get an update when MyThing.Name changes. You need to break out the property and Notify on it directly.

查看更多
登录 后发表回答