Reputation: 7878
I have a DataTemplate that defines the context menu:
<DataTemplate>
<TextBlock>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding SendToRecycleBin}" Header="Delete">
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
I want to add another menu item to the context menu that is only shown if the user held down Shift while opening the Context Menu, using only XAML (maybe create a new attached property App.PowerUserOnly?):
<MenuItem Command="{Binding Delete}" Header="Permanently Delete"
local:App.PowerUserOnly="true">
Can this be done in XAML only (if so, how?), or do you have to use code behind?
Edit: The Windows shell also shows advanced options when Shift was held down while opening the context menu. I'm trying to emulate that behavior. For instance, one of the advanced options for an application is to run it as a different user.
I simplified my code to help test out people's suggestions. The project is created in VS2010 with the default WPF Application, named ShiftContextMenu. The App.xaml and App.xaml.cs files are unmodified.
MainWindow.xaml:
<Window x:Class="ShiftContextMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="DummyItemTemplate">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding SendToRecycleBin}" Header="Delete" />
<MenuItem Command="{Binding Delete}" Header="Permanently Delete" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</Window.Resources>
<TreeView Name="tvMain" ItemTemplate="{StaticResource DummyItemTemplate}" ItemsSource="{Binding DummyItems}" />
</Window>
MainWindow.xaml.cs:
using System.Collections.Generic;
using System.Windows;
using System.Collections.ObjectModel;
namespace ShiftContextMenu
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DummyItem[] dummyItems = new DummyItem[] {
new DummyItem("First"),
new DummyItem("Second"),
new DummyItem("Third")
};
DummyItems = new ReadOnlyCollection<DummyItem>(new List<DummyItem>(dummyItems));
this.DataContext = this;
InitializeComponent();
}
public ReadOnlyCollection<DummyItem> DummyItems { get; protected set; }
}
}
ViewModelBase.cs:
using System.ComponentModel;
namespace ShiftContextMenu
{
public class ViewModelBase : INotifyPropertyChanged
{
protected PropertyChangedEventHandler _propertyChangedEvent;
protected void SendPropertyChanged(string propertyName)
{
if (_propertyChangedEvent != null)
{
_propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged
{
add
{
_propertyChangedEvent += value;
}
remove
{
_propertyChangedEvent -= value;
}
}
}
}
DummyItem.cs:
using System;
using System.Windows.Input;
using System.Windows;
namespace ShiftContextMenu
{
public class DummyItem : ViewModelBase
{
public string Name { get; protected set; }
public DummyItem(string name)
{
Name = name;
_sendToRecycleBinCommand = new SendToRecycleBinCommand();
_deleteCommand = new DeleteCommand();
}
protected SendToRecycleBinCommand _sendToRecycleBinCommand;
protected DeleteCommand _deleteCommand;
public ICommand SendToRecycleBin { get { return _sendToRecycleBinCommand; } }
public ICommand Delete { get { return _deleteCommand; } }
protected class SendToRecycleBinCommand : ICommand
{
public void Execute(object parameter)
{
MessageBox.Show("Send To Recycle Bin");
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged { add { } remove { } }
}
protected class DeleteCommand : ICommand
{
public void Execute(object parameter)
{
MessageBox.Show("Permanently Delete");
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged { add { } remove { } }
}
}
}
Upvotes: 0
Views: 2871
Reputation: 7878
kmatyaszek's answer worked, but I didn't like having to modify my ViewModel. So I ended up creating the attached dependency property that I proposed in the question:
public static readonly DependencyProperty PowerUserOnlyProperty =
DependencyProperty.RegisterAttached(
"PowerUserOnly",
typeof(bool),
typeof(App),
new UIPropertyMetadata(false, new PropertyChangedCallback(PUOChanged)));
public static bool GetPowerUserOnly(MenuItem obj)
{
return (bool)obj.GetValue(PowerUserOnlyProperty);
}
public static void SetPowerUserOnly(MenuItem obj, bool value)
{
obj.SetValue(PowerUserOnlyProperty, value);
}
public static void PUOChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem == null) return;
bool value = (bool)e.NewValue;
if (!value) return;
new PowerUserOnlyHelper(menuItem);
}
public class PowerUserOnlyHelper
{
public MenuItem Item { get; protected set; }
public PowerUserOnlyHelper(MenuItem menuItem)
{
Item = menuItem;
ContextMenu parent = VisualUpwardSearch<ContextMenu>(menuItem);
if (parent != null)
{
parent.Opened += new RoutedEventHandler(OnContextMenuOpened);
}
}
protected void OnContextMenuOpened(object sender, RoutedEventArgs e)
{
Visibility v;
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
v = Visibility.Visible;
}
else v = Visibility.Collapsed;
Item.Visibility = v;
}
}
public static T VisualUpwardSearch<T>(DependencyObject source)
where T : DependencyObject
{
DependencyObject returnVal = source;
DependencyObject tempReturnVal;
while (returnVal != null && !(returnVal is T))
{
tempReturnVal = null;
if (returnVal is Visual || returnVal is Visual3D)
{
tempReturnVal = VisualTreeHelper.GetParent(returnVal);
}
if (tempReturnVal == null)
{
returnVal = LogicalTreeHelper.GetParent(returnVal);
}
else
{
returnVal = tempReturnVal;
}
}
return returnVal as T;
}
Upvotes: 2
Reputation: 19296
Windows shell behaviour solution:
In this solution I'm using two assemblies:
Add following namespace to window:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
DataTemplate should look like this:
<DataTemplate x:Key="DummyItemTemplate">
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Opened">
<i:InvokeCommandAction Command="{Binding ShowMoreOptions}" />
</i:EventTrigger>
<i:EventTrigger EventName="Closed">
<i:InvokeCommandAction Command="{Binding HideMoreOptions}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem Command="{Binding SendToRecycleBin}" Header="Delete" />
<MenuItem Command="{Binding Delete}" Header="Permanently Delete">
<MenuItem.Style>
<Style TargetType="MenuItem">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisibleDelete}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
And in DummyItem
class you must add two commands and one property:
private bool _isVisibleDelete;
public bool IsVisibleDelete
{
get { return _isVisibleDelete; }
set { _isVisibleDelete = value; SendPropertyChanged("IsVisibleDelete"); }
}
public ICommand ShowMoreOptions { get; private set; }
private void OnShowMoreOptions()
{
if (System.Windows.Forms.Control.ModifierKeys == System.Windows.Forms.Keys.Shift)
IsVisibleDelete = true;
}
public ICommand HideMoreOptions { get; private set; }
private void OnHideMoreOptions()
{
IsVisibleDelete = false;
}
In my example I'm using DelegateCommand
from Microsoft.Practices.Prism
assembly.
So in ctor DummyItem
I have:
ShowMoreOptions = new DelegateCommand(this.OnShowMoreOptions);
HideMoreOptions = new DelegateCommand(this.OnHideMoreOptions);
Second solution allow you change this menu dynamically:
You can try something like that:
XAML file:
<TextBlock>
<TextBlock.ContextMenu>
<ContextMenu>
<ContextMenu.InputBindings>
<KeyBinding Modifiers="Shift" Key="Shift" Command="{Binding ShowMoreOptions}" />
</ContextMenu.InputBindings>
<MenuItem Command="{Binding SendToRecycleBin}" Header="Delete" />
<MenuItem Command="{Binding Delete}" Header="Permanently Delete">
<MenuItem.Style>
<Style TargetType="MenuItem">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisibleDelete}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
And in your ViewModel class you should add property and command to change MenyItem
visibility property:
private bool _isVisibleDelete;
public bool IsVisibleDelete
{
get { return _isVisibleDelete; }
set { _isVisibleDelete = value; RaisePropertyChanged(() => IsVisibleDelete); }
}
public ICommand ShowMoreOptions { get; private set; }
private void OnShowMoreOptions()
{
IsVisibleDelete = !IsVisibleDelete;
}
Upvotes: 2