views:

13

answers:

1

Hi, I have a WPF application in which I would like to decorate logically deleted items (held in a TreeView) by displaying them with the Strikethrough TextDecoration.

I can get the style trigger to successfully apply a Foreground color when fired, but when I try to set the TextDecorations it has no effect.

Here is some sample code that reproduces the problem. First the XAML:


 <Style TargetType="TreeViewItem">
  <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
  <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
  <EventSetter Event="TreeViewItem.MouseRightButtonDown" Handler="tvw_MouseRightButtonUp"/>
  <Style.Triggers>
   <DataTrigger Binding="{Binding IsDeleted}" Value="True">
    <!--<Setter Property="TextBlock.Foreground" Value="red" />-->
    <Setter Property="TextBlock.TextDecorations" Value="Underline" />
   </DataTrigger>
  </Style.Triggers>
 </Style>
</TreeView.Resources>

And here is the c#

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Globalization;

namespace StrikethroughTest {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            ObservableCollection<ViewModel> model = BuildModel();
            tvw.ItemsSource = model;
        }

        private ObservableCollection<ViewModel> BuildModel() {
            ObservableCollection<ViewModel> toplevel = new ObservableCollection<ViewModel>();
            ViewModel root = new ViewModel("Root");
            toplevel.Add(root);
            for (int i = 1; i < 5; ++i) {
                ViewModel child = new ViewModel("Child " + i);
                root.AddChild(child);
                for (int j = 1; j < 5; ++j) {
                    ViewModel leaf = new ViewModel("Leaf " + i + "," + j);
                    child.AddChild(leaf);
                }
            }
            return toplevel;
        }

        private void tvw_MouseRightButtonUp(object sender, MouseButtonEventArgs e) {
            ViewModel item = tvw.SelectedItem as ViewModel;
            if (item != null) {
                ShowMenu(item, tvw);
            }

        }

        private void ShowMenu(ViewModel item, FrameworkElement source) {
            ContextMenu menu = new ContextMenu();
            MenuItem mnuDelete = new MenuItem();
            mnuDelete.Header = "Delete";
            mnuDelete.Click += new RoutedEventHandler((src, e) => { item.IsDeleted = true; });
            menu.Items.Add(mnuDelete);
            source.ContextMenu = menu;
        }

    }

    public class ViewModel : ViewModelBase {

        private bool _expanded;
        private bool _selected;
        private bool _deleted;
        private ObservableCollection<ViewModel> _children;        

        public ViewModel(string caption) {
            this.Caption = caption;
            _children = new ObservableCollection<ViewModel>();
        }

        public void AddChild(ViewModel child) {
            _children.Add(child);
        }

        public bool IsExpanded {
            get { return _expanded; }
            set { SetProperty("IsExpanded", ref _expanded, value); }
        }

        public bool IsSelected {
            get { return _selected; }
            set { SetProperty("IsSelected", ref _selected, value); }
        }

        public bool IsDeleted {
            get { return _deleted; }
            set { SetProperty("IsDeleted", ref _deleted, value); }
        }

        public ObservableCollection<ViewModel> Children {
            get { return _children; }
        }


        public String Caption { get; set; }

    }

    public abstract class ViewModelBase : INotifyPropertyChanged {
        protected bool SetProperty<T>(string propertyName, ref T backingField, T value) {
            var changed = !EqualityComparer<T>.Default.Equals(backingField, value);
            if (changed) {
                backingField = value;
                RaisePropertyChanged(propertyName);
            }
            return changed;
        }

        protected void RaisePropertyChanged(string propertyName) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;

    }
}

When someone selects a tree node, right clicks and selects Delete, I want the caption to be striked out. In my real app, each TreeViewItem also has an icon, which I omitted for brevity, although it appears to make no difference.

Does anyone have any ideas why this isn't working?

A: 

You are setting the properties on a TreeViewItem. Setting TextBlock.Foreground works because that property is inheritable (see Property Value Inheritance), so the TextBlock will get the value from its parent TreeViewItem. TextBlock.TextDecorations is not inheritable, so you need to set it on the TextBlock itself instead of the TreeViewItem.

The easiest way to do that is probably by putting the trigger in the DataTemplate by doing something like this:

<TreeView.Resources>
    <Style TargetType="TreeViewItem">
        <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
        <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
        <EventSetter Event="TreeViewItem.MouseRightButtonDown" Handler="tvw_MouseRightButtonUp"/>
    </Style>
    <HierarchicalDataTemplate DataType="{x:Type vm:ViewModel}" ItemsSource="{Binding Children}">
        <TextBlock Name="TextBlock" Text="{Binding Caption}"/>
        <HierarchicalDataTemplate.Triggers>
            <DataTrigger Binding="{Binding IsDeleted}" Value="True">
                <Setter TargetName="TextBlock" Property="TextDecorations" Value="Underline" />
            </DataTrigger>
        </HierarchicalDataTemplate.Triggers>
    </HierarchicalDataTemplate>
</TreeView.Resources>
Quartermeister
Thanks, works perfectly! I guess I thought that because I'd prefixed my setter Property with 'TextBlock' that it would somehow magically apply it to all descendant children of the TreeViewItem that matched that type. I guess I've still got a ways to go in understanding the inheritance model. Thanks for the link, I'll go and check it out.
David