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; }
}
}
Related
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>
In a WPF project, my viewmodel has some general properties that I bind to a style. I then would like to use that style in a DataTemplate where I bind a collection from my viewmodel.
The databound style works outside the DataTemplate as expected, but does not apply inside. When debugging I can see that it is looking for the general properties inside the collection objects, so my question is, how do I inside a DataTemplate get a hold of properties from the viewmodel. I imagine I have to use a RelativeSource binding, but I have not been able to get it working.
This quick app should show what I am trying to do:
MainWindow.xaml
<Window x:Class="StyleTest.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:StyleTest"
mc:Ignorable="d"
Title="Test"
SizeToContent="WidthAndHeight">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Header">
<Setter Property="FontSize" Value="{Binding FontSize}" />
<Setter Property="Foreground" Value="{Binding Foreground}" />
</Style>
<DataTemplate x:Key="UserTemplate">
<StackPanel>
<TextBlock Style="{StaticResource Header}" Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Margin="20">
<StackPanel>
<ItemsControl Name="Itemscontrol" ItemsSource="{Binding Users}" ItemTemplate="{StaticResource UserTemplate}" />
<TextBlock Style="{StaticResource Header}">Style this.</TextBlock>
</StackPanel>
</Grid>
</Window>
MainWindow.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace StyleTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Model m = new Model {
FontSize = 28,
Foreground = new SolidColorBrush(Colors.Orange),
Users = new List<User>() };
m.Users.Add(new User() { Name = "Mambo No. 1" });
m.Users.Add(new User() { Name = "Right Hand Rob" });
m.Users.Add(new User() { Name = "Perry Junior" });
this.DataContext = m;
}
}
public class Model
{
private int fontSize;
public int FontSize { get => fontSize; set => fontSize = value; }
private SolidColorBrush foreground;
public SolidColorBrush Foreground { get => foreground; set => foreground = value; }
private List<User> users;
public List<User> Users { get => users; set => users = value; }
}
public class User
{
public string Name { get; set; }
}
}
I think you want something like this:
<Style TargetType="TextBlock" x:Key="Header">
<Setter Property="FontSize" Value="{Binding Path=DataContext.FontSize, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
<Setter Property="Foreground" Value="{Binding Path=DataContext.Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Style>
I have a custom ComboBox that have each item (Favorites and not favorites) is a Label + Button, then the last item have only a button to load all elements. Now I want to add a header as the first item, that says "Favorites".
Right now I have:
<ComboBox
x:Name="ComboBoxBtn"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="0,0,0,-1"
Width="300"
ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding Path=Selected, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Name="PART_GRID">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Width="250" Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}" />
<Button Name="PART_BUTTON"
Grid.Column="1"
Content="+"
Command="{Binding AddCommandButton, ElementName=root}"
CommandParameter="{Binding}"
Visibility="{Binding Path=., Converter={StaticResource elementToVisibilityConverter}}"/>
<Button Content="Carregar Todos" Margin="5,5"
Command="{Binding LoadAllCommandButton, ElementName=root}"
CommandParameter="{Binding ElementName=root, Path=FavoriteType}"
Visibility="{Binding Path=.,Converter={StaticResource elementToVisibilityForAddConverter}}"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Favorite}"
Value="True">
<Setter TargetName="PART_GRID"
Property="Background"
Value="#FFE6E6FA" />
<Setter TargetName="PART_BUTTON"
Property="Content"
Value="-" />
<Setter TargetName="PART_BUTTON"
Property="Command"
Value="{Binding RemoveCommandButton, ElementName=root}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I preferred a different aproach, which i think is easier and more clean:
i created an empty interface IDrawable.
all classes i need to put inside the combobox should inherit from IDrawable
i created these:
MyLabel:
public class MyLabel : IDrawable
{
public string text { get; set; }
public MyLabel()
{
this.text = "MYTEXT";
}
}
MyButton:
public class MyButton : IDrawable
{
public string text { get; set; }
public MyButton()
{
this.text = "MYNBUTTON";
}
}
MyLabelButton:
public class MyLabelButton : IDrawable
{
public string labelText { get; set; }
public string buttonText { get; set; }
public MyLabelButton()
{
labelText = "labelText";
buttonText = "buttonText";
}
}
than here is the xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfApplication1">
<Window.Resources>
<DataTemplate DataType="{x:Type me:MyButton}">
<Button Content="{Binding text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabel}">
<TextBlock Text="{Binding text}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type me:MyLabelButton}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding labelText}"/>
<Button Content="{Binding buttonText}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Name="MyGrid">
<ComboBox Name="MyCombo" ItemsSource="{Binding list}" SelectedItem="{Binding sel}" PreviewMouseLeftButtonUp="ComboBox_PreviewMouseLeftButtonUp"/>
</Grid>
</Window>
and codebehind:
public partial class MainWindow : Window
{
private IDrawable clicked;
public ObservableCollection<IDrawable> list { get; set; }
public IDrawable sel { get; set; }
public MainWindow()
{
InitializeComponent();
list = new ObservableCollection<IDrawable>();
list.Add(new MyLabel());
list.Add(new MyLabelButton());
list.Add(new MyButton());
this.DataContext = this;
}
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition(MyGrid);
clicked = null;
VisualTreeHelper.HitTest(
MyGrid,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
private HitTestResultBehavior ResultCallback(HitTestResult result)
{
DependencyObject parentObject = VisualTreeHelper.GetParent(result.VisualHit);
if (parentObject == null)
return HitTestResultBehavior.Continue;
var v = parentObject as Button;
if (v == null)
return HitTestResultBehavior.Continue;
if (v.DataContext != null && v.DataContext is IDrawable)
{
clicked = (IDrawable)v.DataContext;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}
}
the result is
as you can see I have standard combobox and custom elements. which i think it's better. in codebehind you can handle everything, like the impossibility of select the first label, call the command associated with the button, if the button is pressed, and so on.
In ComboBox_PreviewMouseLeftButtonUp i handled the click on the selected item, in case you want to do something particular if the selected button is pressed, and not show the dropdown menu.
this example is pretty barebones you need to customize it a little more and use MVVM everywhere.
at the moment you can push the button in the dropDown menu, you probabily want to disable the click if that buttont isn't selected.
EDIT
ComboBox_PreviewMouseLeftButtonUp should be like this:
private void ComboBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((ComboBox)sender);
clicked = null;
VisualTreeHelper.HitTest(
(ComboBox)sender,
null,
new HitTestResultCallback(ResultCallback),
new PointHitTestParameters(pt));
if (clicked != null)
{
((ComboBox)sender).IsDropDownOpen = false;
//do something
}
}
(replaced Mygrid with (ComboBox)sender
As you can see in the image above, I have three columns in listbox.
First Column is FirstName, Second Column is LastName and Third Column is Age.
When I hit Enter on the Age Column a new Person is added to the list. Which eventually reflects the changes in ListBox.
Problem:
When I Press Enter on Age Column I get a new Person added as expected. But the focus does not go to the next ListItem. No matter how many times I press Enter I never get focus to the items added Programmatically.
Sample:
I have created a sample project that reproduces the issue:
Download Sample Project
I have a ListBox as follows:
<ListBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}">
<ListBox.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
<Setter Property="Focusable" Value="False" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="CurrentItemGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" Tag="IgnoreEnterKeyTraversal">
<TextBox.InputBindings>
<KeyBinding Command="{Binding DataContext.DeleteUnwantedOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Return" />
<KeyBinding Command="{Binding DataContext.DeleteUnwantedOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Tab" />
</TextBox.InputBindings>
</TextBox>
<TextBox Grid.Column="1" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" Margin="5,0"/>
<TextBox Grid.Column="2" Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" Margin="5,0" Tag="IgnoreEnterKeyTraversal">
<TextBox.InputBindings>
<KeyBinding Command="{Binding DataContext.AddNewOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Return" />
<KeyBinding Command="{Binding DataContext.AddNewOrderItemTransactionCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Gesture="Tab" />
</TextBox.InputBindings>
</TextBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
IEventAggregator eventAggregator;
public MainWindowViewModel(IEventAggregator _eventAggregator)
{
People = new ObservableCollection<Person>();
People.Add(new Person());
eventAggregator = _eventAggregator;
DeleteUnwantedOrderItemTransactionCommand = new RelayCommand(DeleteUnwantedOrderItemTransaction);
AddNewOrderItemTransactionCommand = new RelayCommand(AddNewOrderItemTransaction);
}
public RelayCommand DeleteUnwantedOrderItemTransactionCommand { get; set; }
public RelayCommand AddNewOrderItemTransactionCommand { get; set; }
private ObservableCollection<Person> _People;
public ObservableCollection<Person> People
{
get
{
return _People;
}
set
{
if (_People != value)
{
_People = value;
OnPropertyChanged("People");
}
}
}
private Person _SelectedPerson;
public Person SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
if (_SelectedPerson != value)
{
_SelectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
}
protected void DeleteUnwantedOrderItemTransaction(object obj)
{
if (SelectedPerson.FirstName == "")
{
People.Remove(SelectedPerson);
}
if (People.Count == 0)
{
People.Add(new Person());
}
eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Publish(true);
}
protected void AddNewOrderItemTransaction(object obj)
{
if (SelectedPerson == People.Last())
People.Add(new Person());
eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Publish(true);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
In Code-Behind:
public partial class MainWindow : Window
{
IEventAggregator _eventAggregator = new EventAggregator();
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel(_eventAggregator);
_eventAggregator.GetEvent<ChangeFocusToNextUIElementEvent>().Subscribe(MoveToNextUIElement);
}
void MoveToNextUIElement(bool obj)
{
// Gets the element with keyboard focus.
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
if (elementWithFocus != null)
{
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
The moment you fire your event you have added your element to your VM, but it might not have been bound and created by the View (ListBox) yet. Dispatching the focus request with a low priority might help. Also check if you can access the element by pressing TAB, so you know the traversal works.
elementWithFocus.Dispatcher.Invoke(() =>
elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)),
DispatcherPriority.Input); // !
Virtualization of the ListBox Elements could also be an issue. You have to bring the added item into view.
cheeers.
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; }
}