Search code examples
c#wpftreeviewvirtualization

EditableTextBlock - TreeView - Virtualization


for the past couple of weeks I've been trying to get the EditableTextBlock (from codeproject) working on my TreeView.

The control has a property IsInEditMode which when set to true changes it to a TextBox.

The TreeView is virtualized and declared as follows:

<TreeView x:Name="treeEnvironment"
          Margin="0,29,0,0" BorderThickness="0"
          VirtualizingStackPanel.IsVirtualizing="True"
          VirtualizingStackPanel.VirtualizationMode="Recycling">
</TreeView>

The TreeView uses the ItemsSource property to get it's data and the value of this is always a single instance of a class (lets call it A). This class contains a list of instances of another type (lets call it B). And this last class contains a list of instances of yet another class (lets call it `C). This is how it looks like in the code:

class A
{
        public String Name;
        public ObservableCollection<B> Items;
}

class B
{
        public String Name;
        public ObservableCollection<C> Items;
}

class C
{
        public String Name;
        public bool IsRenaming;
}

For each of these three classes there is an HierarchicalDataTemplate defined in MainWindow.Resources as follows:

<DataTemplate DataType="{x:Type C}">
    <StackPanel Orientation="Horizontal">
        <StackPanel.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Rename" Click="C_Rename_Click" />
            </ContextMenu>
        </StackPanel.ContextMenu>

        <v:EditableTextBlock Text="{Binding Path=Name}" IsInEditMode="{Binding Path=IsRenaming, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    </StackPanel>
</DataTemplate>

<HierarchicalDataTemplate DataType="{x:Type B}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
    </StackPanel>
</HierarchicalDataTemplate>

<HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
    <StackPanel Orientation="Horizontal">
        <Image Source="icons/icon_A.png" Width="16" Height="16" />
        <TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
    </StackPanel>
</HierarchicalDataTemplate>

None of the DataTemplate have keys so that it is applied automatically.

The event that is triggered when the rename MenuItem of C's context menu is clicked, is defined as follows:

private void C_Rename_Click(object sender, RoutedEventArgs e)
{
    C instance = (sender as FrameworkElement).DataContext as C;

    if (instance != null) {
        instance.IsRenaming = true;
    } else {
        MessageBox.Show("DEBUG: C_Rename_Click(" + sender.ToString() + ", " + e.ToString() + ") : instance == null");
    }
}

The problem is that the EditableTextBlock does not turn into a TextBox when the IsRenaming property is set to true on the instance of C that was chosen to be renamed.

The EditableTextBlock works just fine when I place it as a normal control.

My guess is that it has to do with virtualization. Any help would be appreciated.

Thank you for your time, best regards,

100GPing100.


Solution

  • class A, B, C need to implement INotifyPropertyChanged for any changes made to them to be propagated to the UI. You can either implement it in each class individually or have a base class implement INPC and derive your classes from this base class.

    Something like:

    public class MyBaseViewModel : INotifyPropertyChanged {
      public event PropertyChangedEventHandler PropertyChanged;
    
      protected virtual void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
          handler(this, new PropertyChangedEventArgs(propertyName));
      }
    }
    
    public class A : MyBaseViewModel {
      private string _name;
      public string Name {
        get {
          return _name;
        }
        set {
          _name = value;
          OnPropertyChanged("Name");
        }
      }
    
      private ObservableCollection<B> _items;
      public ObservableCollection<B> Items {
        get {
          return _items;
        }
        set {
          _items = value;
          OnPropertyChanged("Items");
        }
      }
    }
    
    public class B : MyBaseViewModel {
      private string _name;
      public string Name {
        get {
          return _name;
        }
        set {
          _name = value;
          OnPropertyChanged("Name");
        }
      }
    
      private ObservableCollection<C> _items;
      public ObservableCollection<C> Items {
        get {
          return _items;
        }
        set {
          _items = value;
          OnPropertyChanged("Items");
        }
      }
    }
    
    public class C : MyBaseViewModel {
      private string _name;
      public string Name {
        get {
          return _name;
        }
        set {
          _name = value;
          OnPropertyChanged("Name");
        }
      }
    
      private bool _isRenaming;
      public bool IsRenaming {
        get {
          return _isRenaming;
        }
        set {
          _isRenaming = value;
          OnPropertyChanged("IsRenaming");
        }
      }
    }
    

    Now when you change IsRenaming in your code, you will see the update propagate to the UI and the TextBlock switch to a TextBox.

    Side-note

    Please have a look at MVVM. If you're not sure about it. Get to learn it slowly cos it helps UI development in WPF quite a bit.