WPF - How can I create menu and submenus using binding - c#

I am trying to create a dynamic menu using binding. I my viewmodel I have a list of objects which contains an header and a command. However, it is not working. I think the problem is in the data template. See my code below:
<Menu Background="{x:Null}" Grid.Row="0" Grid.Column="1" Panel.ZIndex="2" Width="865" Height="85" HorizontalAlignment="Left" ItemsSource="{Binding Path=MenuItems}">
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="MenuItemViewModel" ItemsSource="{Binding Path=MenuItems}">
<MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" ItemsSource="{Binding Path=MenuItems}" Padding="10,12,10,0" Height="44.1" Margin="30,0,0,0" FontWeight="Bold">
<MenuItem.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</MenuItem.ItemsPanel>
</MenuItem>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" Padding="0,8,0,0" Height="38">
</MenuItem>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
The result only shows the first menu. The submenus are not shown but they are there since the menus which have children, the arrow is print after menu header.
Could anyone find something wrong on the binding? Or any suggestion?
Just for information, MenuItems is a list of MenuItemViewModel objects which has an header and a list of MenuItemViewModel objects (submenus) called MenuItems too.

For me, it worked with this simple template:
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
Here is the complete example:
MainWindow.xaml:
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication14"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<Grid>
</Grid>
</DockPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication14
{
public partial class MainWindow : Window
{
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
public MainWindow()
{
InitializeComponent();
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Alpha" },
new MenuItemViewModel { Header = "Beta",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Beta1" },
new MenuItemViewModel { Header = "Beta2",
MenuItems = new ObservableCollection<MenuItemViewModel>
{
new MenuItemViewModel { Header = "Beta1a" },
new MenuItemViewModel { Header = "Beta1b" },
new MenuItemViewModel { Header = "Beta1c" }
}
},
new MenuItemViewModel { Header = "Beta3" }
}
},
new MenuItemViewModel { Header = "Gamma" }
};
DataContext = this;
}
}
public class MenuItemViewModel
{
private readonly ICommand _command;
public MenuItemViewModel()
{
_command = new CommandViewModel(Execute);
}
public string Header { get; set; }
public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }
public ICommand Command
{
get
{
return _command;
}
}
private void Execute()
{
// (NOTE: In a view model, you normally should not use MessageBox.Show()).
MessageBox.Show("Clicked at " + Header);
}
}
public class CommandViewModel : ICommand
{
private readonly Action _action;
public CommandViewModel(Action action)
{
_action = action;
}
public void Execute(object o)
{
_action();
}
public bool CanExecute(object o)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
}
The resulting window looks like this:

that is very easy,you can use this code for your nested menu
ViewModel: TopMenuViewModel.cs
public partial class TopMenuViewModel
{
public TopMenuViewModel()
{
TopMenuItems = new ObservableCollection<MenuItem>
{
new MenuItem
{
Title = "File",
PageName =typeof(OfficeListView).FullName,
ChildMenuItems= {
new MenuItem
{
Title = "New"
},
new MenuItem
{
Title = "Open"
},
new MenuItem
{
Title = "Save"
}
}
},
new MenuItem
{
Title = "Edit"
},
new MenuItem
{
Title = "Search"
}
};
}
View: TopMenuView.xaml
<Menu IsMainMenu="True" ItemsSource="{Binding TopMenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Title}"/>
<Setter Property="ItemsSource" Value="{Binding Path=ChildMenuItems}"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>

If like me you are keen to keep UI constructs in the XAML code, after some difficulty, I have worked a nice way of binding custom-typed collections to create menu items.
In XAML:
<Menu DockPanel.Dock="Top">
<MenuItem Header="SomeHeaderName" ItemsSource="{Binding Path=MyCollection}">
<MenuItem.ItemsContainerStyle>
<Setter Property="Header" Value="{Binding Path=SomeRelevantTextProperty}"/>
<EventSetter Event="Click" Handler="SomeMenuItemClickEventHandler"/>
</MenuItem.ItemsContainerStyle>
</MenuItem>
</Menu>
In code-behind:
ObservableCollection<MyClass> MyCollection;
private void SomeMenuItemClickEventHandler(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
MyClass myClass = menuItem.DataContext as MyClass;
// do something useful!
}
public class MyClass
{
public string SomeRelevantTextProperty { get; }
}

Related

TreeViewItem with TextBox in WPF: How selected TreeViewItem containing a TextBox when clicking on TextBox

I have a TreeView, displaying information using TextBox, with the ability to edit.
But the TextBox stops the MouseDown event and the selection of the TreeViewItem does not occur when the TextBox is clicked.
How to solve this problem by leaving the ability to edit the text.
Smallest possible example: In this code, the SelectedItemChanged event is not fired because no item is selected.
<Window x:Class="TreeViewItemSelected.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<TextBlock x:Name="text" FontSize="20" HorizontalAlignment="Center" />
<TreeView x:Name="tree" FontSize="20" ItemsSource="{Binding Items}" SelectedItemChanged="tree_SelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBox Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace TreeViewItemSelected
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new VM();
}
private void tree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue is testitem item) text.Text = item?.Name;
}
}
public class VM
{
public VM()
{
testitem item = new testitem("Aaaaaaa");
item.Items.Add(new testitem("Aaa 1"));
item.Items.Add(new testitem("Aaa 2"));
item.Items.Add(new testitem("Aaa 3"));
Items.Add(item);
item = new testitem("Bbbbbbb");
item.Items.Add(new testitem("Bbb 1"));
item.Items.Add(new testitem("Bbb 2"));
item.Items.Add(new testitem("Bdd 3"));
Items.Add(item);
Items.Add(new testitem("Ccccccc"));
Items.Add(new testitem("Ddddddd"));
Items.Add(new testitem("Eeeeeee"));
}
public ObservableCollection<testitem> Items { get; set; } = new ObservableCollection<testitem>();
}
public class testitem
{
public string Name { get; set; }
public testitem(string name) => Name = name;
public ObservableCollection<testitem> Items { get; set; } = new ObservableCollection<testitem> {};
}
}
I tried to use this, but it didn't work as I expected. Perhaps I am using it incorrectly.
AddHandler(UIElement.MouseDownEvent, new MouseButtonEventHandler(StackPanel_MouseDown), true);
or
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
Thanks in advance!
A straightforward way is to connect IsKeyboardFocused property of TextBox with IsSelected property of TreeViewItem. It can be done easily by Microsoft.Xaml.Behaviors.Wpf.
<TextBox Text="{Binding Name}">
<i:Interaction.Triggers>
<i:DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type TextBox}}, Path=IsKeyboardFocused}" Value="True">
<i:ChangePropertyAction TargetObject="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}"
PropertyName="IsSelected" Value="True"/>
</i:DataTrigger>
</i:Interaction.Triggers>
</TextBox>

How to access to controls inside a GroupItem?

I'm using a CollectionView to group my item as following:
<CollectionViewSource Source="{Binding Competitions}" x:Key="GroupedData">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Item.Country" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
the full XAML structure is this:
<ComboBox x:Name="CompetitionCombo"
ItemsSource="{Binding Source={StaticResource GroupedData}}"
ItemTemplate="{StaticResource CombinedTemplate}">
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource containerStyle}"
HeaderTemplate="{StaticResource GroupHeader}">
</GroupStyle>
</ComboBox.GroupStyle>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}" />
</DataTemplate>
<DataTemplate x:Key="GroupHeader">
<DockPanel>
<Image Source="{Binding Name.ISO,
Converter={StaticResource CountryIdToFlagImageSourceConverter}}"
Stretch="None" Width="23" Height="18" RenderOptions.BitmapScalingMode="HighQuality" />
<TextBlock Text="{Binding Name.Name}" Margin="10,0,0,0" Foreground="Black" FontWeight="Bold"/>
<CheckBox Margin="5,0,0,0" HorizontalAlignment="Right" IsChecked="True" x:Name="PART_DisplayLeague"
Unchecked="CheckBoxCountry_Unchecked" Checked="CheckBoxCountry_Checked" />
</DockPanel>
</DataTemplate>
<Style TargetType="{x:Type GroupItem}" x:Key="containerStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="False" x:Name="ComboExpander"
Header="{TemplateBinding Content}"
HeaderTemplate="{StaticResource GroupHeader}" >
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
as you can see in the GroupHeader I've a CheckBox, I'm trying to access to this CheckBox in the following way:
//current value of league.Country = "England";
//Iterate the available groups
foreach(CollectionViewGroup gp in CompetitionCombo.Items.Groups)
{
if(league.Country.Name == (gp.Name as Country).Name)
{
//Get the container
GroupItem container = CompetitionCombo.ItemContainerGenerator.ContainerFromItem(gp) as GroupItem;
//Get the control
var control = container.Template.FindName("PART_DisplayLeague", container);
control.IsChecked = true;
}
}
I get correctly the groups, but the control variable is even Null, seems that the code cannot find PART_DisplayLeague: the CheckBox associated to the Country in the header of the container.
Note:
the league is a simple implementation of the following:
public class League
{
public string Name { get; set; }
public Country Country { get; set; }
}
public class Country
{
public string Name { get; set; }
public string ISO { get; set; }
}
what am I doing wrong?
UPDATE
Forgot to add the collection implementation, in particular Competition:
public List<CheckedListItem<League>> Competitions = new List<CheckedListItem<League>>();
for add element to the collection:
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo" }
});
is possible find an implementation of CheckedListItem here
as you can see Item is part of CheckedListItem.
UPDATE #2
Practice example of the situation:
Suppose that Competitions contains the following Items:
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "Italy"}}
});
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo2", Country = new Country { Name = "Italy"}}
});
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "England"}}
});
the UI Structure will this:
[] Italy <- this is the GroupHeader sort by xaml
[] foo <- Items of the container
[] foo2
[] England
[] foo3
now what I'm asking for: When I check foo2 and foo the Italy CheckBox need to be Checked, also when I uncheck an Item contained in the Italy container, then, Italy must be unchecked.
This mean two thing:
if all items in the container are checked, then also the header checkbox need to be checked.
if almost one item in the container isn't checked, then the checkbox in the container must be unchecked 'cause this mean that not all the items associated to this country are checked.
My problem's that I can't find the CheckBox associated to the Country 'cause is generated by the Xaml, that's why I can't use a Binding property as suggested.
post the code that someone can just copy paste and reproduce your problem, then people can not only solve your problem but also suggest a better solution.
in the meantime, you can find your checkbox in the following manner:
var control = FindVisualChildren<CheckBox>(container);
Here you can find the method FindVisualChildren.
Edit: a full working example
public class League
{
public string Name { get; set; }
public Country Country { get; set; }
}
public class Country
{
public string Name { get; set; }
public string ISO { get; set; }
}
public class CheckedListItem<T>
{
private bool isChecked;
private T item;
public CheckedListItem() { }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
}
}
}
public partial class MainWindow : Window
{
public ObservableCollection<CheckedListItem<League>> Competitions { get; set; }
public MainWindow()
{
InitializeComponent();
Competitions = new ObservableCollection<CheckedListItem<League>> { (new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "Italy" } }
}),
(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo2", Country = new Country { Name = "Italy" } }
}),
(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "England" } }
}) };
this.DataContext = this;
}
public void CheckBoxCountry_Checked(object sender, EventArgs args)
{
foreach (CollectionViewGroup gp in CompetitionCombo.Items.Groups)
{
//Get the container
GroupItem container = CompetitionCombo.ItemContainerGenerator.ContainerFromItem(gp) as GroupItem;
//Get the control
var control = FindVisualChildren<CheckBox>(container);
}
}
public IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
yield return (T)child;
foreach (T childOfChild in FindVisualChildren<T>(child))
yield return childOfChild;
}
}
}
}
.xaml
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<CollectionViewSource Source="{Binding Competitions}" x:Key="GroupedData">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Item.Country" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="NormalItemTemplate">
<DockPanel>
<TextBlock Text="sdf" Margin="10,0,0,0" Foreground="Black" FontWeight="Bold"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}"
/>
</DataTemplate>
<DataTemplate x:Key="GroupHeader">
<DockPanel>
<TextBlock Text="{Binding Name.Name}" Margin="10,0,0,0" Foreground="Black" FontWeight="Bold"/>
<CheckBox Margin="5,0,0,0" HorizontalAlignment="Right" IsChecked="True" x:Name="PART_DisplayLeague"
Checked="CheckBoxCountry_Checked" />
</DockPanel>
</DataTemplate>
<Style TargetType="{x:Type GroupItem}" x:Key="containerStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="False" x:Name="ComboExpander"
Header="{TemplateBinding Content}"
HeaderTemplate="{StaticResource GroupHeader}" >
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ComboBox x:Name="CompetitionCombo"
ItemsSource="{Binding Source={StaticResource GroupedData}}"
ItemTemplate="{StaticResource CombinedTemplate}">
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource containerStyle}"
HeaderTemplate="{StaticResource GroupHeader}">
</GroupStyle>
</ComboBox.GroupStyle>
</ComboBox>
</Grid>
</Window>
I added NormalItemTemplate because you didn't provide the template.
When you check the checkbox in the UI, you will call the method CheckBoxCountry_Checked in mainwindow.cs and that method will find the checkbox inside all 3 comboboxes.

WPF MenuItem Binding Issue

I faced an issue regarding MenuItem Binding, I need to bind a nested object to a MenuItem.
public class QuestionType
{
public string Name { get; set; }
public ICollection<QuestionType> Types { get; set; }
}
public class ViewModel
{
public ICollection<QuestionType> QuestionTypes { get; set; }
public ViewModel()
{
QuestionTypes = new List<QuestionType>()
{
new QuestionType() { Name="Completion" },
new QuestionType() { Name="Easy" },
new QuestionType() { Name="MoreType", Types = new List<QuestionType>()
{
new QuestionType() { Name="SingleChoice" },
new QuestionType() { Name="MultiChoice" },
new QuestionType() { Name="Blend" },
} },
};
}
}
public partial class CustomMenu : UserControl
{
public CustomMenu()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
In XAML Code:
I binding them to MenuItem like this way:
<MenuItem ItemsSource="{Binding QuestionTypes}">
<MenuItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:QuestionType}"
ItemsSource="{Binding Types}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:QuestionType}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</MenuItem.Resources>
</MenuItem>
When I run my project, an exception occurred.
Additional information: Set property 'System.Windows.ResourceDictionary.DeferrableContent' threw an exception.
In addition:
I need to add click event for Menu Item like
<MenuItem.Resources>
...
</MenuItem.Resources>
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Menu.Click" Handler="MenuItem_Click"/>
</Style>
</MenuItem.ItemContainerStyle>
It didn't work, I don't know how to solve this issue.
Try this:
<MenuItem Header="Question Type" ItemsSource="{Binding QuestionTypes}" Click="MenuItem_Click">
<MenuItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:QuestionType}" ItemsSource="{Binding Types}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</MenuItem.Resources>
</MenuItem>

How to write EventToCommand for TreeviewItem in Prism 5.0

I have Treeview which has also child nodes and is populated dynamically. The datacontext of the view is binded to view model.I want to get the selecteditem of the treeview when i double click any level of the treeview. I want to write EventToCommand for doubleclick event. I don't want to write any code in code behind.
The structure of the treeview is as follows. I have written InvokeCommandAction to pass doubleclick event to viewmodel using delegatecommand. This is working. But i am not getting the selecteditem when i double click on the child nodes. Always get root name.
<TreeView Name="treeView" Background="Transparent" ItemsSource="{Binding TreeList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<prism:InvokeCommandAction Command="{Binding TreeNodeDoubleClickevent}" CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}">
</prism:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected,Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type UserControl}},Path=DataContext.IsExpanded, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type provider:dataprovider}" ItemsSource="{Binding providerList}">
<TextBlock Text="{Binding Name}" Background="{Binding Property.Background}" Tag="{Binding Parent}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
View Model code:
public class VM_TreeView : IVM_TreeView
{
public DelegateCommand<CustomXML> TreeNodeDoubleClickevent { get; set; }
[ImportingConstructor]
public VM_TreeView(IRegionManager regionManager)
: base(regionManager)
{
RegionManager = regionManager;
TreeNodeDoubleClickevent = new DelegateCommand<CustomXML>(OnExecute_TreeNodeDouble_click);
}
public void OnExecute_TreeNodeDouble_click(CustomXML obj)
{
}
}
Please help to achieve this.
It is working fine for me. Refer below code.
<Window x:Class="TreeView_Learning.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeView_Learning"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
Title="MainWindow" Height="650" Width="525">
<StackPanel>
<TreeView x:Name="tree" Width="500" Height="200" ItemsSource="{Binding NodeList}"
VerticalAlignment="Top" HorizontalAlignment="Center" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<prism:InvokeCommandAction Command="{Binding TreeNodeDoubleClickevent}" CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeView}}}">
</prism:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding NodeName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
class ViewModel
{
private ObservableCollection<Node> myVar;
public ObservableCollection<Node> NodeList
{
get { return myVar; }
set { myVar = value; }
}
public DelegateCommand<Node> TreeNodeDoubleClickevent { get; set; }
public ViewModel()
{
NodeList = new ObservableCollection<Node>();
Node nd = new Node();
nd.NodeName = "Item 1.1";
Node nd1 = new Node();
nd1.NodeName = "Item 1.2";
Node nd2 = new Node();
nd2.NodeName = "Item 1";
nd2.Children.Add(nd);
nd2.Children.Add(nd1);
NodeList.Add(nd2);
TreeNodeDoubleClickevent = new DelegateCommand<Node>(MouseDoubleClick);
}
private void MouseDoubleClick(Node obj)
{
MessageBox.Show(obj.NodeName);
}
}
class Node
{
private string nodeName;
public string NodeName
{
get { return nodeName; }
set { nodeName = value; }
}
private ObservableCollection<Node> myVar = new ObservableCollection<Node>();
public ObservableCollection<Node> Children
{
get { return myVar; }
set { myVar = value; }
}
}

Custom Treeview User Control MVVM Double Click Bubbling Event WPF

I'm trying to make a custom TreeView and make it a user control. When I wrap the user control in another window, I tried to get the TreeView item double click event in the main window.
<Window xmlns:avalondock="http://avalondock.codeplex.com" x:Class="WellsVisualizationWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:well="clr-namespace:VisualizationWPF.ViewModel.ViewUserControl"
Title="e-IFD" Height="408" Width="558" WindowState="Maximized"
>
<Grid MinWidth="100" **TreeViewItem.MouseLeftButtonClick=<EventHandler>**> <-- Trying to override but failed :p
<local:CustomTreeView />
</Grid>
I tried to get the bubbling mouse double click from CustomTreeView item and intercept the event in the grid wrapper outside the usercontrol. I tried to add TreeViewItem. TreeViewItem.MouseLeftButtonDown="Grid_MouseLeftButtonDown and failed. Any ideas to solve my problem ?
Here is my code of custom user control for treeview
<UserControl x:Class="WellsVisualizationWPF.ViewModel.ViewUserControl.WellsTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VisualizationWPF.ViewModel"
>
<Grid MinWidth="100">
<Grid.RowDefinitions>
<RowDefinition MaxHeight="500" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="1">
<TextBlock TextWrapping="Wrap" FontSize="12">
Text1
</TextBlock>
<Button Height="24" Content="Add New" Name="btn_add" Click="btn_add_Click" />
</StackPanel>
<ScrollViewer>
<DockPanel>
<TreeView Grid.Row="0" ItemsSource="{Binding Data}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:MainViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ParamsViewModel}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</DockPanel>
</ScrollViewer>
</Grid>
You don't need to publish double-click event outside from the user control at all.
You need to add some InputBinding (MouseBinding in this particular case) into InputBindings collection of the TreeView.SelectedItem.
The problem is that you can't do that in normal, obvious way - set InputBindings via TreeView.ItemContainerStyle, because InputBindings collection is read-only. Sad, but true.
Good news is that you can use attached property to accomplish that.
The sample:
View models.
a) this is what will be displayed as items in tree view:
public class Node : ViewModelBase
{
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
OnPropertyChanged("Text");
}
}
}
private String text;
public ObservableCollection<Node> Nodes { get; set; }
}
b) this is "main" view model:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
this.selectedNodeDoubleClickedCommand = new RelayCommand<Node>(node =>
{
Debug.WriteLine(String.Format("{0} clicked!", node.Text));
});
}
public ObservableCollection<Node> Nodes { get; set; }
public RelayCommand<Node> SelectedNodeDoubleClickedCommand
{
get { return selectedNodeDoubleClickedCommand; }
}
private readonly RelayCommand<Node> selectedNodeDoubleClickedCommand;
}
User control code-behind. Basic idea - we're adding one attached property to set input binding though it in XAML, and another one - to allow external world bind command, when input binding fires:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public ICommand SelectedItemDoubleClickedCommand
{
get { return (ICommand)GetValue(SelectedItemDoubleClickedCommandProperty); }
set { SetValue(SelectedItemDoubleClickedCommandProperty, value); }
}
public static readonly DependencyProperty SelectedItemDoubleClickedCommandProperty = DependencyProperty.Register(
"SelectedItemDoubleClickedCommand", typeof(ICommand),
typeof(UserControl1),
new UIPropertyMetadata(null));
public static ICommand GetSelectedItemDoubleClickedCommandAttached(DependencyObject obj)
{
return (ICommand)obj.GetValue(SelectedItemDoubleClickedCommandAttachedProperty);
}
public static void SetSelectedItemDoubleClickedCommandAttached(DependencyObject obj, ICommand value)
{
obj.SetValue(SelectedItemDoubleClickedCommandAttachedProperty, value);
}
public static readonly DependencyProperty SelectedItemDoubleClickedCommandAttachedProperty = DependencyProperty.RegisterAttached(
"SelectedItemDoubleClickedCommandAttached",
typeof(ICommand), typeof(UserControl1),
new UIPropertyMetadata(null, SelectedItemDoubleClickedCommandAttachedChanged));
private static void SelectedItemDoubleClickedCommandAttachedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var item = d as TreeViewItem;
if (item != null)
{
if (e.NewValue != null)
{
var binding = new MouseBinding((ICommand)e.NewValue, new MouseGesture(MouseAction.LeftDoubleClick));
BindingOperations.SetBinding(binding, InputBinding.CommandParameterProperty, new Binding("SelectedItem")
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(TreeView), 1)
});
item.InputBindings.Add(binding);
}
}
}
}
User control XAML:
<Grid>
<TreeView ItemsSource="{Binding Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}" DataType="{x:Type local:Node}">
<TextBlock Text="{Binding Text}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="local:UserControl1.SelectedItemDoubleClickedCommandAttached"
Value="{Binding SelectedItemDoubleClickedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
Main window XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 SelectedItemDoubleClickedCommand="{Binding SelectedNodeDoubleClickedCommand}"/>
</Grid>
</Window>
Main window code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel
{
Nodes = new ObservableCollection<Node>
{
new Node
{
Text = "Parent 1",
Nodes = new ObservableCollection<Node>
{
new Node { Text = "Child 1.1"},
new Node { Text = "Child 1.2"},
}
},
new Node
{
Text = "Parent 2",
Nodes = new ObservableCollection<Node>
{
new Node { Text = "Child 2.1"},
new Node { Text = "Child 2.2"},
}
},
}
};
}
}

Categories