I'd like to get to know how I can set menu entries visible or hidden for certain user roles in XAML code? Eg I have "_MenuEntryForADMIN" which should only visible for user role "admin" and "_MenuEntryForAllUsers" should be visible to all users - role "user".
Here is my XAML code:
<Grid>
<DockPanel>
<MenuItem Header="_MenuEntryForADMIN">
<MenuItem Header="_Master1" Click="btnMaster1_Click">
<MenuItem.Icon>
<Image Source="database.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Master2" Click="btnMaster2_Click">
<MenuItem.Icon>
<Image Source="database.png" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_MenuEntryForAllUsers">
<MenuItem Header="_Normal1" Click="btnNormal1_Click">
<MenuItem.Icon>
<Image Source="database.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Normal2" Click="btnNormal2_Click">
<MenuItem.Icon>
<Image Source="database.png" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
</DockPanel>
</Grid>
The CS-Code contains the information whether a user has the role "admin" or "user" like this:
User userObject;
public MainWindow(User userObject)
{
InitializeComponent();
this.userObject = userObject; // this.userObject.Role is "admin" or "user"
}
In WPF, it is important to 'shape' your data into the correct structure before you try to display it. By this, I mean that you should filter the data, your MenuItems, in your view model or code behind before displaying them, rather than trying to filter them in the UI.
So rather than defining them all in XAML, create some custom data type to data bind to the various MenuItem properties that you are interested in. Then create a DataTemplate and bind a collection of these to the MenuItem.ItemsSource property. You can find out how to do that from my answer to the Dynamically hiding menu items question. In short, it looks something like this:
<DataTemplate x:Key="MenuItemTemplate" DataType="{x:Type MenuItem}">
<MenuItem Command="{Binding Command}" CommandParameter="{Binding CommandParameter}"
Header="{Binding Path=Header}" Visibility="{Binding Visible, Converter={
BooleanToVisibilityConverter}}" ItemsSource="{Binding ChildMenuItems}" />
</DataTemplate>
There are also many examples online of filtering data in collections, so I won't go over all of that here. You can find a short example in the “Hiding” part of List question here on Stack Overflow. Basically, you can use LinQ to select just the admin MenuItems something like this:
ChildMenuItems = new ObservableCollection<YourCustomDataType>(
FullCollection.Where(i = i.Role == "admin").ToList());
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 a MenuItem like below
<MenuItem Header="Edit">
<MenuItem Header="Copy Direct Link" Icon="{StaticResource CopyIcon}" Command="{Binding CopyImageCommand}" />
<MenuItem Header="Copy Image Data" Icon="{StaticResource CopyIcon}" Command="{Binding CopyImageDataCommand}" />
<MenuItem Header="Paste" Icon="{StaticResource PasteIcon}" Command="{Binding PasteImageCommand}" />
</MenuItem>
Notice the 1st 2 items use the same icon, I get something like below
I tried removing the 2nd item,
<MenuItem Header="Edit">
<MenuItem Header="Copy Direct Link" InputGestureText="Ctrl+C" Icon="{StaticResource CopyIcon}" Command="{Binding CopyImageCommand}" />
<!--<MenuItem Header="Copy Image Data" InputGestureText="Ctrl+Alt+C" Icon="{StaticResource CopyIcon}" Command="{Binding CopyImageDataCommand}" />-->
<MenuItem Header="Paste" InputGestureText="Ctrl+P" Icon="{StaticResource PasteIcon}" Command="{Binding PasteImageCommand}" />
</MenuItem>
then I got something like
How can I reuse Icons?
See this question
An Image can only have one parent so it will be moved from the first MenuItem to the second. You can add the x:Shared attribute like this
<Window.Resources>
<Image x:Key="CopyIcon" x:Shared="False" Source="..." />
</Window.Resources>
From msdn
x:Shared Attribute
When set to false, modifies WPF
resource-retrieval behavior so that
requests for the attributed resource
create a new instance for each request
instead of sharing the same instance
for all requests.
You're most likely declaring CopyIcon as Image type in your resource, something like this:
<Window.Resources>
<Image x:Key="CopyIcon" Source="yourcopyicon.ico"/>
</Window.Resources>
So, the root cause of the problem is, Image is a visual element, since it derives from FrameworkElement (which is a visual element), and a visual element cannot have more than one parent at the same time. That is why the first MenuItem is not showing the icon, since the second MenuItem reset the parent of CopyIcon, making itself parent of the CopyIcon.
Hope this explanation is helpful to you. Now follow what Meleak has said in his response. :-)
Try the following:
<MenuItem Header=“Paste“ >
<MenuItem.Icon><Image Height=“16“ Width=“16“ Source=“paste.jpg“ /></MenuItem.Icon>
</MenuItem>
Ok, so we have a recent file menu option. We databind the MenuItem entries using MVVM and supply the DisplayMemberPath. But WPF escapes the string so underscore is displayed as an underscore instead as the accesskey
<MenuItem x:Name="RecentScripts" DisplayMemberPath="Caption" Header="Recent _Files" cal:Message.Attach="OpenRecentScript($orignalsourcecontext)">
<MenuItem.Icon>
<Image Source="{StaticResource IconOpen}"/>
</MenuItem.Icon>
</MenuItem>
https://github.com/AndersMalmgren/FreePIE/blob/recet_files_shortcut/FreePIE.GUI/Views/Main/Menu/MainMenuView.xaml#L35
We also have custom theme, but disabling style for MennuItem does not help
https://github.com/AndersMalmgren/FreePIE/blob/recet_files_shortcut/FreePIE.GUI/Themes/ExpressionDark.xaml#L1921
Replace the DisplayMemberPath with ItemTemplate. Like explained here DisplayMemeberPath is
a template for a single property, shown in a TextBlock
As #XAMlMAX mentioned TextBlock doesn't support AccessText, while Label does.
<MenuItem x:Name="RecentScripts" Header="Recent _Files" cal:Message.Attach="OpenRecentScript($orignalsourcecontext)">
<MenuItem.Icon>
<Image Source="{StaticResource IconOpen}"/>
</MenuItem.Icon>
<MenuItem.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Caption}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
I built a menu bar using the menu control in WPF and it had been working but at some point the menu started showing up on the top left of my first monitor regardless of where in the screen I had the window. Even if I move the main window to the second monitor the menu still shows up in the first monitor.
Here is the code for the menu control:
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_New" Command="New" InputGestureText="Ctrl+N"/>
<MenuItem Header="_Open" Command="Open" InputGestureText="Ctrl+O" />
<MenuItem Header="_Close" Command="Close" InputGestureText="Ctrl+W" />
<Separator/>
<MenuItem Header="_Save" Command="Save" InputGestureText="Ctrl+S" />
<MenuItem Header="Save _As" Command="SaveAs" InputGestureText="Ctrl+Shift+S" />
<Separator/>
<MenuItem Header="E_xit" Command="{StaticResource CommandBinding_Exit}" InputGestureText="Ctrl+Q" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="_Add" Command="{StaticResource CommandBinding_Add}" InputGestureText="" />
<MenuItem Header="_Edit" Command="{StaticResource CommandBinding_Edit}" InputGestureText="" />
<MenuItem Header="_Delete" Command="Delete" InputGestureText="" />
<Separator/>
<MenuItem Header="Cut" Command="Cut" InputGestureText="Ctrl+X" />
<MenuItem Header="Copy" Command="Copy" InputGestureText="Ctrl+C" />
<MenuItem Header="Paste" Command="Paste" InputGestureText="Ctrl+V" />
</MenuItem>
<MenuItem Header="_View">
<MenuItem x:Name="miShowStatusBar" Header="Show Status Bar" IsCheckable="True" IsChecked="True" Click="miShowStatusBar_Click"/>
<MenuItem x:Name="miShowFullPath" Header="Show Full Path" IsCheckable="True" IsChecked="True" Click="miShowFullPath_Click"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About"/>
</MenuItem>
</Menu>
The menu is not referenced any where in the code behind so I can't figure out what might be causing this odd menu placement.
I think the source of the OP's problem is in the search box under the MenuBar. It may come from the Margin or Width settings of the search box.
I faced the same problem and found the sources. Here is the boilerplate code to reproduce the same error. The problem is a combination of 5 parts, as shown in the following XAML. Remove any one of them will solve the OP's issue.
You need to bind the XAML View to a ViewModel with public property Films. If the Films collection has any element in it, it will also cause the issue (Part 5).
<Grid x:Name="MainContent">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid >
<Menu>
<MenuItem Header="Film">
<MenuItem Header="New"/>
</MenuItem>
</Menu>
</Grid>
<Grid Grid.Row="1">
<Grid>
<!--Part 1: Remove HorizontalScrollBarVisibility="Auto"-->
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<!--Part 2: Remove ItemsSource="{Binding Films}"-->
<ItemsControl ItemsSource="{Binding Films}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<!--Part 3: Remove the setter tag-->
<Setter Property="Margin" Value="2" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<!--Part 4: Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=ActualWidth}-->
<ContentPresenter Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=ActualWidth}"/>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
</Grid>
This has nothing to do with WPF. It's a possible corruption of the Handedness setting or driver issues if you have a touch screen monitor, tablet PC or have a tablet attached with a bad driver (such as a Wacom drawing tablet).
Type this into the Run dialog: shell:::{80F3F1D5-FECA-45F3-BC32-752C152E456E}.
Once Tablet PC Settings come up, go to the Other tab and in the Handedness section, check the Left Handed option.
I bet that this has something to do with the Xaml-Designer of your Visual Studio instance. I encounter similar strange things because of leaving the Xaml-Designer open while debugging.
Try to kill the XDesProc.exe process and check if the problem still occurs.
I have a problem with command binding in WPF. I have the following xaml:
<ItemsControl ItemsSource="{Binding Entity}" Name="Lst">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="qwerty" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" >
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Send2" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As you can see Button and its ContextMenu have the similar command-bindings. But when i click button its command is firing and when i click context menu's item its command isn't firing. Where am i wrong? Thanks in advance!
I had a similar problem before and solved it by passing the datacontext through the tag property of the container as below. I have it working on a grid ContextMenu but dont see any reason why this wont work on a button. Let me know if you have any problem
<Button Content="qwerty" Tag="{Binding DataContext,ElementName=Lst}" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" >
<Button.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Send2" Command="{Binding SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
The ContextMenu being separate from the visual tree, you cannot bind with and element outside of it.
If you check your output window, you should have a message saying that it can't find the object "Lst"
A common and easy workaround would be to manually set the DataContext in code-behind (note: this is not breaking MVVM at all. You are just performing a pure UI operation of linking DataContexts together):
In your Xaml:
<Button.ContextMenu>
<ContextMenu Opened="OnContextMenuOpened">
<MenuItem Header="Send2" Command="{Binding ElementName=Lst, Path=DataContext.SaveCommand}" />
</ContextMenu>
</Button.ContextMenu>
In code-behind:
public void OnContextMenuOpened(object sender, RoutedEventArgs args)
{
(sender as ContextMenu).DataContext = Lst.DataContext;
}
You are therefore linking the DataContext every time the ContextMenu is opened (so if Lst's DataContext changes, your ContextMenu will as well)
Alternatively (cleaner if you are bound to use it a lot of times), get the BindingProxy from this article: http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ and it'll do the trick!