Search code examples
c#wpf.net-core

Why the following code changing the tree view node icon in WPF not working?


I had created a TreeView with my custom class as follows in WPF:

Class definition:

    public class FileTreeItem
    {
        public FileTreeItem() // For root item
        {
            Icon = new WriteableBitmap(1, 1, 72, 72, PixelFormats.Rgb24, null);
            ItemText = "";
            Items = new ObservableCollection<FileTreeItem>();
            ParentNode = this;
        }
        public FileTreeItem(FileTreeItem parentNode) // For other items.
        {
            Icon = new WriteableBitmap(1, 1, 72, 72, PixelFormats.Rgb24, null);
            ItemText = "";
            Items = new ObservableCollection<FileTreeItem>();
            ParentNode = parentNode;
        }
        public ObservableCollection<FileTreeItem> Items { get; set; }
        public ImageSource Icon { get; set; }
        public string ItemText { get; set; }
        
        public NodeType NodeType
        {
            get => _nodeType;
            set
            {
                _nodeType = value;
                Icon = _nodeType switch
                {
                    NodeType.File => SharedRes.FileIcon, // See below for SharedRes definition
                    NodeType.Folder => SharedRes.FolderIcon,
                    _ => Icon
                };
            }
        }

        public override string ToString()
        {
            return ItemText;
        }
        public bool IsExpanded { get; set; }
        public bool IsSelected { get; set; }
        public FileTreeItem ParentNode { get; }
        private NodeType _nodeType;
    }

And here's the SharedRes class definition, it's basically a class that loads the resources icons in "Resource.resx" and convert it to a bitmap source:

public static class SharedRes
{
    public static BitmapSource FolderIcon
    {
        get { return _folderIcon ??= GetBitmapFromIcon("Shell_Folder"); }
    }

    public static BitmapSource FileIcon
    {
        get { return _fileIcon ??= GetBitmapFromIcon("Shell_File"); }
    }

    private static BitmapSource GetBitmapFromIcon(string IconName)
    {
        Uri folder_path = new Uri($"pack://application:,,,/{IconName}.ico");
        using var ms = new MemoryStream(Resources.ResourceManager.GetObject(IconName) as byte[] ?? Array.Empty<byte>());
        IconBitmapDecoder decoder = new IconBitmapDecoder(ms, BitmapCreateOptions.PreservePixelFormat,
            BitmapCacheOption.None);
        return decoder.Frames[0].Clone();
    }

    private static BitmapSource? _folderIcon;
    private static BitmapSource? _fileIcon;
}

Here's the tree view template, you can see the Image is binding to Icon property in the FileTreeItem class:

            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:FileTreeItem}" ItemsSource="{Binding Items, Mode=TwoWay}">
                    <WrapPanel HorizontalAlignment="Center">
                        <Image MaxWidth="16" MaxHeight="16" Width="16" Height="16" Source="{Binding Icon}" Stretch="Fill"></Image>
                        <TextBlock Text="{Binding ItemText, Mode=TwoWay}" HorizontalAlignment="Center"/>
                    </WrapPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>

Now, if I do this thing in the main window code, with a node already selected:

    this_node.NodeType = this_node.NodeType == NodeType.File ? NodeType.Folder : NodeType.File; // It went wrong here.

The binded Icon property changed, but the image that's showing in the screen does not.

I can confirm that every time the Icon property changed, it was assigned with different value every time I try to change the NodeType property. I've also tried to use other ways to set the Icon property directly (even by directly forcing the value when running), but none of them works.

Please note that this is not about "How to use INotifyProperty" interface.


Solution

  • You should implement INotifyPropertyChanged and raise the PropertyChanged event each time the Node property is set to a new value:

    public class FileTreeItem : INotifyPropertyChanged
    {
        ...
    
        private ImageSource _icon;
        public ImageSource Icon
        {
            get { return _icon; }
            set { _icon = value; NotifyPropertyChanged(); }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }