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.
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.