'WPF TreeView in ControlTemplate - Handling Item Expanded
I am creating a Custom Control and in the control template I have a TreeView class in it.
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyControl}">
<Border>
<TreeView ItemsSource="{Binding TreeDataItems, RelativeSource={RelativeSource TemplatedParent}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TreeViewItem.Expanded">
<i:InvokeCommandAction Command="{Binding TreeItemExpandedCommand, RelativeSource={RelativeSource TemplatedParent}}"
CommandParameter="{Binding}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
In the code file for the custom control I have this command:
private ICommand _TreeItemExpandedCommand;
public ICommand TreeItemExpandedCommand { get { if (_TreeItemExpandedCommand == null) _TreeItemExpandedCommand = new RelayCommand(p => TreeItemExpandedExecuted(p)); return _TreeItemExpandedCommand; } }
private void TreeItemExpandedExecuted(object args) { }
I have also tried
<TreeView TreeViewItem.Expanded="TreeViewItem_Expanded">
...
</TreeView/>
but neither gets fired.
How can I handle the TreeView's Expanded event INSIDE my custom control's code file?
Thanks
Solution 1:[1]
I am not pro Wpf programmer, but i manage to figure out the solution for your problem. The problem with your code, is that the event is not exactly belongs to TreeView itself, but rather the elements inside it (Items property).
So this wont work:
...
<i:EventTrigger EventName="TreeViewItem.Expanded">
...
On the other hand, this would work:
...
<i:EventTrigger EventName="SelectedItemChanged">
...
because this event belongs to TreeView
Basicly, what you need to do is subscribe for the Expanded event of all the TreeViewItems in the TreeView. You can do this from code behind.
Here is how i did it:
public class MyCustomControl : Control
{
public TreeView MyTreeView { get; set; }
public static readonly DependencyProperty TreeViewItemListProperty = DependencyProperty.Register("TreeViewItemList",
typeof(List<StorageItem>),
typeof(MyCustomControl),
new UIPropertyMetadata(null));
public List<StorageItem> TreeViewItemList
{
get { return (List<StorageItem>)GetValue(TreeViewItemListProperty); }
set { SetValue(TreeViewItemListProperty, value);}
}
public static readonly RoutedEvent MyEventRoutedEvent = EventManager.RegisterRoutedEvent("MyEvent",
RoutingStrategy.Bubble,
typeof(EventHandler<RoutedEventArgs>),
typeof(MyCustomControl));
public event RoutedEventHandler MyEvent
{
add { this.AddHandler(MyEventRoutedEvent, value); }
remove { this.RemoveHandler(MyEventRoutedEvent, value); }
}
public override void OnApplyTemplate()
{
MyTreeView = Template.FindName("MyTreeView", this) as TreeView;
MyTreeView.Items.CurrentChanged += Items_CurrentChanged;
base.OnApplyTemplate();
}
private void Items_CurrentChanged(object sender, EventArgs e)
{
SubscribeAllTreeViewItems(MyTreeView);
}
private void SubscribeAllTreeViewItems(ItemsControl treeViewItem)
{
foreach (object item in treeViewItem.Items)
{
TreeViewItem treeItem = treeViewItem.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (treeItem != null)
{
SubscribeAllTreeViewItems(treeItem);
treeItem.Expanded += TreeViewItem_Expanded;
}
}
}
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
RoutedEventArgs args = new RoutedEventArgs(MyEventRoutedEvent, this);
this.RaiseEvent(args);
}
static MyCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
}
}
First we need to acquire our TreeView Control and subscribe the event if it gets items:
public override void OnApplyTemplate()
{
MyTreeView = Template.FindName("MyTreeView", this) as TreeView;
MyTreeView.Items.CurrentChanged += Items_CurrentChanged;
base.OnApplyTemplate();
}
Then, if this is fired, we know that our TreeView has Items in it, so we can cycle through its elements and subscribe for the event Expanded:
private void Items_CurrentChanged(object sender, EventArgs e)
{
SubscribeAllTreeViewItems(MyTreeView);
}
private void SubscribeAllTreeViewItems(ItemsControl treeViewItem)
{
foreach (object item in treeViewItem.Items)
{
TreeViewItem treeItem = treeViewItem.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (treeItem != null)
{
SubscribeAllTreeViewItems(treeItem);
treeItem.Expanded += TreeViewItem_Expanded;
}
}
}
After that, we are good to go! I also added code for to be able to subscribe for this event from outside the custom control:
public static readonly RoutedEvent MyEventRoutedEvent = EventManager.RegisterRoutedEvent("MyEvent",
RoutingStrategy.Bubble,
typeof(EventHandler<RoutedEventArgs>),
typeof(MyCustomControl));
public event RoutedEventHandler MyEvent
{
add { this.AddHandler(MyEventRoutedEvent, value); }
remove { this.RemoveHandler(MyEventRoutedEvent, value); }
}
....
....
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
RoutedEventArgs args = new RoutedEventArgs(MyEventRoutedEvent, this);
this.RaiseEvent(args);
}
So this way, you can subscribe it from outside like that:
<local:MyCustomControl TreeViewItemList="{Binding Path=Items}"
MyEvent="MyCustomControl_MyEvent"/>
Finally, here it is the .xaml
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<TreeView ItemsSource="{TemplateBinding TreeViewItemList}"
x:Name="MyTreeView">
<TreeView.Resources>
<!--Here we specify how to display a FolderItem-->
<HierarchicalDataTemplate DataType="{x:Type localviewmodels:FolderItem}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"/>
</HierarchicalDataTemplate>
<!--Here we specify how to display a FileItem-->
<DataTemplate DataType="{x:Type localviewmodels:FileItem}">
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UPDATE
if you want to set TreeView items inside the custom Control, you should subscribe the MyTreeView.Loaded event as well to catch the first time when it gets values.
i hope it helps! If anything is not clear, feel free to ask, i am ready to help!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |