I have a nested context menu like this:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Thing Count" ItemsSource="{Binding ThingsProvider}">
NB the Thing Count parent MenuItem with children from ThingsProvider.
ThingsProvider provides a List of ThingViewModel which contains properties Thing and IsChecked. I want to be able to control IsChecked from my main view model and from the user via the MenuItem but I have hit a problem; If I use an ItemContainerStyle like this.
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Thing, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Path=IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</MenuItem.ItemContainerStyle>
then (predictably I suppose as it's a style) it will observe the ViewModel's IsChecked but will not set it.
If I use an ItemTemplate like so
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Thing, Mode=OneWay}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
I can't get to the MenuItem's IsChecked property as I've only got the TextBlock. If I put a MenuItem in the ItemTemplate then I end up with nested MenuItems in my UI and it looks bad.
There must be a way to do this but I'm stumped at the moment, can anyone help?
Cheers
Rich
Related
AFAIK bubbling means that if an event is triggered on the children, the same event will be triggered on the parents.
I have this piece of code:
<ListView Name="lvFiles" SelectedItem="{Binding SelectedTabFilePath, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=SelectedItem.Files,ElementName=trvFiles, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Name}"
Command="{Binding DataContext.OpenFileFromTreeView,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding DataContext,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
Background="Transparent"
BorderBrush="Transparent"
>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The problem is that if I click on the button, the property SelectedTabFilePath from SelectedItem will not be filled with data (because I didn't click on the item of the ListView).
So, my question is:
Is there any way to trigger the Click event of the button and that of the ListView too?
Or if not, what would be the way to set the SelectedTabFilePath from the SelectedItem attribute when I click on that button, too?
The Click event is indeed bubbling, which means it will be routed up the elements of visual tree until the root element is reached. However, the Button itself handles the event, because its Command property was bound. That means its parent elements, including ListViewItem (the item container) will ignore the event, therfore not select the item.
There is a workaround using the fact that the Button receives keyboard focus upon clicking. You add an item container style with a trigger that sets the IsSelected property of the ListViewItem once the keyboard focus is within, denoted by the IsKeyboardFocusWithin property.
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<!-- ...your other markup. -->
</ListView>
how to handle WPF listbox selectionchanged event using MVVM
I think you could use interaction to trigger the same command(OpenFileFromTreeView) when the listbox selection changes.
Instead of:
AncestorType={x:Type ListView}
What if you try:
AncestorType={x:Type ListViewItem}
I have a MVVM project with a View and a ViewModel in its DataContext.
In this project I have a class ComboBoxCustom which inherits from ComboBox. I define some additional functionality in my ComboBoxCustom class.
To this ComboBoxCustom class I assign a control template to define its appearance.
The (simplified) style defining the (simplified) control template looks like:
<Style TargetType="{x:Type lib:ComboBoxCustom}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type lib:ComboBoxCustom}">
<StackPanel>
<TextBlock Text="{TemplateBinding TextPropertyInComboBoxCustom}"/>
<ComboBox DataContext="{TemplateBinding DataContext}"
ItemsSource="{TemplateBinding ItemsSource}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
SelectedValuePath="{TemplateBinding SelectedValuePath}"
SelectedValue="{TemplateBinding SelectedValue}"
SelectedItem="{TemplateBinding SelectedItem}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Which resides in a ResourceDictionary. The real control template has some additional features which are left out since they are not relevant for the question.
I use this ComboBoxCustom control in my View using:
<lib:ComboBoxCustom ItemsSource="{Binding MyObservableCollectionOfMyObjects}"
TextPropertyInComboBoxCustom="MyText"
DisplayMemberPath="MyDescription"
SelectedValuePath="MyValue"
SelectedItem="{Binding SelectedMyObject, Mode=TwoWay}"/>
The view is ok, and all items get loaded in the ComboBox which I can select.
The problem is that when I select a different item in the ComboBox, the property SelectedMyObject in my ViewModel does not get updated and consequently its setter is not called. Therefore, the (correct) information about the selected object is not available in my ViewModel.
When I use <ComboBox .../> (without the TextPropertyInComboBoxCustom property) instead of <lib:ComboBoxCustom .../> everything works just fine but then I don't have the additional functionality defined in ComboBoxMessage which I need.
Can anyone tell me what is wrong and how to fix this issue so I can use ComboBoxMessage in my view? Preferably without breaking the MVVM pattern.
Thank you!
Thanx to ASh's comment and information in this post.
The problem is that the TemplateBinding is one way. Therefore, all information from the ViewModel can get into the controls in the template. But not the other way around.
The solution is to specify a normal binding as:
SelectedItem ="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Whichs does about the same as a TemplateBinding but is two way.
The control template has become:
<Style TargetType="{x:Type lib:ComboBoxCustom}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type lib:ComboBoxCustom}">
<StackPanel>
<TextBlock Text="{TemplateBinding TextPropertyInComboBoxCustom}"/>
<ComboBox DataContext="{TemplateBinding DataContext}"
ItemsSource="{TemplateBinding ItemsSource}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
SelectedValuePath="{TemplateBinding SelectedValuePath}"
SelectedValue="{TemplateBinding SelectedValue}"
SelectedItem ="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am not sure about the SelectedValue property though. With the template like this, it works both when I use the SelectedValue property or the SelectedItem property.
The Mode=TwoWay option can be omitted in the view since the default binding mode for SelectedItem is already two way. The view line becomes:
<lib:ComboBoxCustom ItemsSource="{Binding MyObservableCollectionOfMyObjects}"
TextPropertyInComboBoxCustom="MyText"
DisplayMemberPath="MyDescription"
SelectedValuePath="MyValue"
SelectedItem="{Binding SelectedMyObject}"/>
Bind SelectedValue to the property in ViewModel
SelectedValue="{Binding SelectedMyObject, Mode=TwoWay}"
in your lib:ComboBoxCustom
I have a databound hierarchical menu in WPF. All items are displayed, but the commands only fire for the leafs of the menu, not the items that have children. I'm guessing the command is overriden by expanding the child menu...
How do I get the command to execute even for the menu items with children?
What I have now is
<UserControl ...>
<WrapPanel>
<Menu>
<Menu.Resources>
<Style x:Key="MenuItemStyle" TargetType="MenuItem" d:DataContext="{d:DesignInstance local:TreeItem}">
<Setter Property="Command" Value="{Binding DataContext.AddColumnCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</Menu.Resources>
<MenuItem Header="Add ▼" ItemsSource="{Binding AvailableFields}">
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding NestedItems}" ItemContainerStyle="{StaticResource MenuItemStyle}">
<ContentPresenter Content="{Binding Annotation}"/>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
</WrapPanel>
</UserControl>
I found a question with a similar name, but the situation is different and it doesn't have a good answer anyway.
All items are displayed, but the commands only fire for the leafs of the menu, not the items that have children.
Yes, this is the expected behaviour since clicking on a MenuItem with children is supposed to expand the submenu of child items. It doesn't execute a command.
If you want to expand the child items and execute the command you could handle the PreviewMouseLeftButtonDown event of the MenuItem:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.AddColumnCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseDown" />
</Style>
-
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && mi.Command != null && mi.HasItems)
mi.Command.Execute(mi.CommandParameter);
}
Note that handling the event in the code-behind of the view doesn't really break the MVVM pattern here since you are just invoking the command of the view model from the code-behind instead of invoking it from the XAML markup of the same view. But if you don't like this approach you could use an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf
I have a problem with the RibbonMenuButton. Currently I have :
<RibbonMenuButton Label="Meeting" Width="Auto" ToolTipDescription="Display requests on the agenda for the meeting selected" ToolTipTitle="Meeting"
LargeImageSource="pack://application:,,,/Resources/meeting.png"
ItemsSource="{Binding MeetingsAvailable}">
<RibbonMenuButton.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</RibbonMenuButton.ItemTemplate>
</RibbonMenuButton>
My MeetingsAvailable is actually a Dictionary<int, string>. This code is working, the RibbonMenuButtonis well displaying each Value of the dictionnary.
Now I'm trying to get back the Key of the MenuItem which has been clicked. My idea was to use a ICommand in my ViewModel and to bind an event to this command. But I don't really know how to get the event corresponding to clicking an Item in the RibbonMenuButton
Do someone have already did that ?
Thank you in advance.
You can data bind an ICommand to a RibbonMenuButton using the ItemContainerStyle property, like this:
<RibbonMenuButton Label="Meeting" ItemsSource="{Binding MeetingsAvailable}" ... >
<RibbonMenuButton.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</RibbonMenuButton.ItemTemplate>
<RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.NameOfCommand,
RelativeSource={RelativeSource AncestorType={x:Type Views:View}}}" />
<Setter Property="CommandParameter" Value="{Binding Key}" />
</Style>
</RibbonMenuButton.ItemContainerStyle>
</RibbonMenuButton>
You have to create a command in your VM to which you can bind. Then you have to do a binding of the key of your dictionary to the command parameter so you can use it inside your commandfunction. Maybe you have to create an additional Button inside your DataTemplate.
I am creating an MVVM Wpf client application. I want create menu in the main View for the application that his a menu item called "Window" on it. That menu item will dynamically update itself with a submenu of menuitems who are made up of the list of active windows running in the application. I created a ViewManager whom each View registers itself with to compile a list of active windows.
I am trying to do this in XAML but getting an error when I click on "Window"
<MenuItem Header="Window">
<ItemsControl ItemsSource="{Binding ViewMgr.Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd ,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</MenuItem>
How do I create a dynamically updated list of menuitems on my menu in XAML using a MVVM style of data bindings and commands?
You are adding a new ItemsControl as a single child of the menu item, instead of adding each view as one child of the menu item itself. You probably get the error because the styles TargetType doesn't match. MenuItem inherits from ItemsControl itself and exposes a property ItemsSource. Try the following:
<MenuItem ItemsSource="{Binding ViewMgr.Views}" DisplayMemberPath="Title">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>