I have a list of strings that holds the names of menu items:
List<string> namesStorage;
How do I bind this list as collection in XAML?
Right now I have wrote this is part, which already works:
<StackPanel Orientation="Vertical">
<MenuItem Header="MenuItem1" Command="{Binding Command1}" />
<MenuItem Header="MenuItem2" Command="{Binding Command1}" />
</StackPanel>
This items must have one command (different arguments will be set in the view model).
But I think this is not a good way to set every MenuItem in XAML - It is ok now for two items but I wonder how to make a universal decision for any number of this menu items. I have created a list that holds names but how to bind it (and keep a commands)?
After setting the correct DataContext, this should work:
<ItemsControl ItemsSource="{Binding namesStorage}" x:Name="ItemsControlName">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding}" Command="{Binding ElementName=ItemsControlName, Path=DataContext.Command1}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit: Removed Parent-Stackpanel as suggested in the comments
Related
There is a WPF MVVM app. On the main view I have a list of elements, which are defined with ListView.ItemTemplate, in that I want to have a context menu with Delete action.
The Command for that is separated from the view and is kept in ViewModel DreamListingViewModel.
The problem is that on clicking on Delete I can't get it to execute the command on ViewModelk as context there is that of the item, not the items container.
I can make it work somehow by moving the context menu definition outside of the list view elements, but then when I open the context menu, it flickers, as if it's being called "20" times (which what I think does happen, as many times as I have elements in collection), anyways, I need a clean solution for that and I am very bad with XAML.
Here is how my View looks:
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type viewmodels:DreamListingViewModel}}}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
It's the main window and initialized in a generic host in App.cs:
public partial class App : Application
{
private readonly IHost _host;
public App()
{
...
_host = Host.CreateDefaultBuilder().ConfigureServices(services =>
{
...
services.AddTransient<DreamListingViewModel>();
services.AddSingleton((s) => new DreamListingView()
{
DataContext = s.GetRequiredService<DreamListingViewModel>()
});
...
}).Build();
The Command and CommandParameter values are what I've been experimenting with, but it doesn't work
Here is how my ViewModel looks:
internal class DreamListingViewModel : ViewModelBase
{
public ICommand DeleteSelectedDream{ get; }
...
Finally, when the command is fired, I need to pass the current element on which the menu has been shown.
So, here is what I want:
User clicks on a list item with mouse right button - OK
Sees a menu with Delete entry - OK
On Delete click, Command DeleteSelectedDream is fired with current dream (item in the list) as a parameter - ERR
Your example is somewhat lacking necessary information, but I'll try to help.
First you need to verify that you are actually bound to your view model. Are you using Prism or just standard WPF ? In the constructor of your code-behind of your view, set up the DataContext to an instance of your VM.
InitializeComponent();
this.DataContext = new DreamListingViewModel();
Now, you bind to a relative source via Mode 'FindAncestor' and the AncestorType is set to the type of a view model. That usually won't work, as the view model is not naturally a part of the visual tree of your WPF view. Maybe your ItemTemplate somehow wires it up. In a large WPF app of mine I use Telerik UI for WPF and a similar approach to you, however, I set up the DataContext of the Context menu to a RelativeSource set to Self combined with Path set to PlacementTarget.DataContext.
You do not have to use all the XAML in my example, just observe how I do it. Exchange 'RadContextMenu' with 'ContextMenu', Ignore the Norwegian words - here and only use what you need :
<telerik:RadContextMenu x:Key="CanceledOperationsViewContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="{Binding PatientName}" IsEnabled="False" Style="{StaticResource ContextMenuHeading}" />
<MenuItem Header="Gå til aktuell SomeAcme-liste" IsEnabled="{Binding IsValid}" Command="{Binding NavigateToListCommand}" />
<MenuItem Header="Åpne protokoll..." Command="{Binding CommonFirstProtocolCommand, Mode=OneWay}" CommandParameter="{Binding}" />
<MenuItem Header="Åpne Opr.spl.rapport...." Command="{Binding CommonFirstNurseReportCommand, Mode=OneWay}" CommandParameter="{Binding}" />
</telerik:RadContextMenu>
In your example it will be :
<ContextMenu x:Key="SomeContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="Delete" />
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListViewItem}}}"
/>
</telerik:RadContextMenu>
Now I here consider you are using the class ListViewItem
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.listviewitem?view=netframework-4.8
It might be that you need to specify DataContext.DeleteSelectedDream here to be sure you bind up to the DataContext where your implementation of ICommand is.
Accidentally found this answer, that's basically what I needed, just added to it a CommandParameter to send the item and it works like magic!
<ListView Name="lvDreams" ItemsSource="{Binding Dreams}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem
Header="Delete"
Command="{Binding DataContext.DeleteSelectedDream, Source={x:Reference lvDreams}}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I find the following the simplest; perhaps it's because I do not understand WPF, but it's "simple" to remember, and it works with my MVVM pattern.
<ListBox ItemsSource="{Binding MyViewModelItemsCollection, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" >
<TextBlock Text="{Binding Path=Name, Converter={StaticResource FullPathToFileName}, Mode=OneWay}" Grid.Column="0">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem
Command="{Binding Path=DataContext.MyViewModelAction, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Header="{Binding Name, Converter={StaticResource resourceFormat}, ConverterParameter={x:Static res:Resources.CONTEXT_MENU_BLOCK_APPLICATION}}">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</grid>
</DataTemplate>
</ListBox.ItemTemplate
</ListBox>
The MyViewModelXXXXXXX named items are in the view model that is mapped to the data context of the control.
I have the following XAML:
<Grid x:Name="main_grid">
<ItemsControl ItemsSource="{Binding MyThings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.Things, ElementName=main_grid}" SelectedItem="{Binding .}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
MyThings and Things are both ObservableCollection<Thing>.
If Things contains three objects, and I have added one of them to MyThings, when the view/XAML loads the ComboBox will initially display that item. However, when I change the selection in the ComboBox, the item within MyThings is not replace with the newly selected item as I would expect (if it were bound to a single object, rather than an object in a collection, it would be replaced).
Why does this not work? And is there a way to make it work?
My TreeView contains objects of type ServerItem, which in turn contains objects of type DatabaseItem. Xaml looks like this:
<TreeView Grid.Column="0" HorizontalAlignment="Stretch" DockPanel.Dock="Left" ItemsSource="{Binding Path=ServerItems, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding ConnectServer}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Databases}">
<TextBlock Text="{Binding}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Now - binding to ConnectServer works ok, but I have another command - ConnectDatabase, and I want it to be executed when the user double clicks TreeViewItem contained by ServerItem's TreeViewItem. In other words - is it possible to bind commands in WPF based on the object type (so that first level items would have other command bindings than second level, third level (and so on) items)?
You could use a ItemTemplateSelector.
By using this technique in combination with multiple Hierarchical DataTemplates you should meet your goal.
I'm interested in creating an app that displays some buttons and changes a viewport according to the selected button. The viewport in my app is a ContentControl and I thought of changing its content whenever a button is clicked. However, I believe there's a better approach, by perhaps injecting the ViewModels of each of the Views I want to present to the ContentControl and styling them using DataTemplates (Since I want to avoid having a grid with many controls and just setting their Visibility property whenever I want to show a particular view). Which of the approaches seems better to you? Do you have a different approach for this?
The view should be something similar to this:
Thanks!
Usually have a ViewModel behind the window which contains:
ObservableCollection<IViewModel> AvailableViewModels
IViewModel SelectedViewModel
ICommand SetCurrentViewModelCommand
I display the AvailableViewModels using an ItemsControl, which has its ItemTemplate set to a Button. The Button.Command is bound to the SetCurrentViewModelCommand, and it passes the current data item from the AvailableViewModels collection in through the CommandParameter
To display the content area, I use a ContentControl with ContentControl.Content bound to SelectedViewModel, and DataTemplates get used to tell WPF how to render each ViewModel.
The end result is my XAML looks something like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding AvailableViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.SetCurrentViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding SelectedViewModel}" />
</DockPanel>
You can view an example of the full code used for such a setup on my blog
I am using an ObjectDataProvider and a DataTemplate to populate a MenuItem inside my Menu bar. (WPF, C#/XAML) See snipet below.
Result: The top menu item appears, when i click on it, the wrapping menu item (the one with the bound header text) appears along with the little arrow indicating the presence of children but hovering or clicking the arrow does not show the children, they cannot be accessed.
Expected result: The children are visible and behave properly.
Snippet:
<ObjectDataProvider x:Key="Brokers" ObjectInstance="{x:Static brokers:BrokerManager.Instance}" MethodName="GetBrokers" IsAsynchronous="True" />
<DataTemplate x:Key="BrokerMenuItem" DataType="IBroker">
<MenuItem Header="{Binding Path=Name}">
<MenuItem Header="Connect" />
<MenuItem Header="Disconnect" />
</MenuItem>
</DataTemplate>
<MenuItem Header="Brokers" ItemsSource="{Binding Source={StaticResource Brokers}}" ItemTemplate="{DynamicResource BrokerMenuItem}"/>
arsenmrkt: I have exactly the same problem, if I populate a MenuItem using a DataTemplate I cant seem to add children to any of those generated items. I don't understand your answer though, how should I use the ContentPresenter to get around this problem?
EDIT:
Actually, my problem was'nt exactly the same, since I'm trying to bind a collection of collections to a menu. I think I've gotten it to work though using the HierarchicalDataTemplate:
<Menu>
<MenuItem Header="{Binding Name}" ItemsSource="{Binding MenuOptions}">
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Categories}">
<MenuItem Header="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
Does this help you NicholasF?
After searching for over a week, i finally found how to make this work properly. It turns out DataTemplates don't work too great for dynamic menus. The proper way to do this is to use the ItemContainerStyle property of MenuItem. (Or is that ItemStyleContainer?)
Simply create a style to override the header and set it to whatever you need. I them overrode the ItemsSource to include my children. However be careful here, as the children will inherit the style and each have the same children and generate a recursive menu. You'll need to override the ItemsSource of your children and set it to an empty x:Array or the likes.
There are several blogs out there describing how to use ItemContainerStyle, check them out.
ItemSource property of menuitem control is used for giving childes for that item, try to use <ContentPresenter /> with that datatemplate.