Reputation:
I'm trying to create a WPF custom control called "DataTextBox". Everything works fine except for the context menu of this control. Indeed, I would like to add an item in the DataTextBox's Context Menu. To do this, I have added a MenuItem in my DataTextBox style defined in generic.xaml:
<Style TargetType="{x:Type controls:DataTextBox}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{DynamicResource Components_TextBoxCut}" Command="ApplicationCommands.Cut" />
<MenuItem Header="{DynamicResource Components_TextBoxCopy}" Command="ApplicationCommands.Copy" />
<MenuItem Header="{DynamicResource Components_TextBoxPaste}" Command="ApplicationCommands.Paste" />
<MenuItem x:Name="menuItemInsertChecksum" Header="{DynamicResource Components_DataTextBoxInsertChecksum}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:DataTextBox}}, Path=CalculateChecksumCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
...
</Setter.Value>
</Setter>
</Style>
I have also added a command in the DataTextBox code-behind
public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
public ICommand CalculateChecksumCommand { get; private set; }
This command is initialized in the DataTextBox constructor :
public DataTextBox() : base()
{
CalculateChecksumCommand = new RelayCommand(() => CalculateChecksum(), () => CanCalculateChecksum());
}
The issue I have is that the Command binding of my last MenuItem does not work because the "CalculateChecksumCommand" is not found. This means that the "CalculateChecksum()" method is never called.
I would appreciate any help on that subject. Thank you.
EDIT : The Dependency Property declaration should be :
public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
public ICommand CalculateChecksumCommand
{
get { return (ICommand)GetValue(CalculateChecksumCommandProperty); }
private set { SetValue(CalculateChecksumCommandProperty, value); }
}
Upvotes: 2
Views: 7594
Reputation: 892
Have you tried to implement a CustomRoutedCommand?
This works for my CustomControl:
public static RoutedCommand CustomCommand = new RoutedCommand();
CommandBinding CustomCommandBinding = new CommandBinding(CustomCommand, ExecutedCustomCommand, CanExecuteCustomCommand);
this.CommandBindings.Add(CustomCommandBinding);
customControl.Command = CustomCommand;
KeyGesture kg = new KeyGesture(Key.F, ModifierKeys.Control);
InputBinding ib = new InputBinding(CustomCommand, kg);
this.InputBindings.Add(ib);
private void ExecutedCustomCommand(object sender, ExecutedRoutedEventArgs e)
{
//what to do;
MessageBox.Show("Custom Command Executed");
}
private void CanExecuteCustomCommand(object sender, CanExecuteRoutedEventArgs e)
{
Control target = e.Source as Control;
if (target != null)
{
e.CanExecute = true;
}
else
{
e.CanExecute = false;
}
}
another interesting example
Upvotes: 0
Reputation:
With comments from Aybe and nkoniishvt, I think that I'm able to answer my own question.
Objective: create a command in a CustomControl (not UserControl) and use it in the xaml part of this CustomControl (As nkoniishvt said, commands are typically used in ViewModel and not in UI components. However, I have not found any similar solutions.)
CustomControl code-behind:
using GalaSoft.MvvmLight.Command;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public class CustomControl1 : TextBox
{
public CustomControl1()
: base()
{
MyCommand = new RelayCommand(() => Execute(), () => CanExecute());
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public static DependencyProperty MyCommandProperty = DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(CustomControl1));
public ICommand MyCommand
{
get { return (ICommand)GetValue(MyCommandProperty); }
private set { SetValue(MyCommandProperty, value); }
}
private void Execute()
{
//Do stuff
}
private bool CanExecute()
{
return true;
}
}
}
CustomControl appearance defined in Themes/Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Cut" Command="ApplicationCommands.Cut" />
<MenuItem Header="Copy" Command="ApplicationCommands.Copy" />
<MenuItem Header="Past" Command="ApplicationCommands.Paste" />
<MenuItem Header="Execute MyCommand in CustomControl1"
Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.TemplatedParent.MyCommand}" />
<!--In this case, PlacementTarget is "txtBox"
This is why we have to find the templated parent of the PlacementTarget because MyCommand is defined in the CustomControl1 code-behind-->
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Grid>
<!--Some UI elements-->
<TextBox Name="txtBox" ContextMenu="{TemplateBinding ContextMenu}" />
<!--Others UI elements-->
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Example of use of this CustomControl in MainWindow.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:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:CustomControl1 />
</Grid>
</Window>
Do not forget to add resources in App.xaml:
<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
By running the application, we can see that MyCommand is properly bound. This means that the Execute() method is called when the user click on the fourth MenuItem in the ContextMenu.
If you see areas for improvement, thank you to let me know. Hoping it will help someone.
Upvotes: 1
Reputation: 16652
The window that hosts a control and defines a style for it which binds one menu item of its context menu to a command of it :
XAML
<Window x:Class="WpfApplication2.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:wpfApplication2="clr-namespace:WpfApplication2"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<Style TargetType="wpfApplication2:UserControl1" x:Shared="False">
<Style.Setters>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.(wpfApplication2:UserControl1.MyCommand)}" Header="Hello" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</Grid.Resources>
<wpfApplication2:UserControl1 />
</Grid>
</Window>
Code
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class DelegateCommand : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute) : this(execute, s => true)
{
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
}
}
Control :
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
private DelegateCommand _myCommand;
public UserControl1()
{
InitializeComponent();
MyCommand = new DelegateCommand(Execute);
}
public DelegateCommand MyCommand
{
get { return _myCommand; }
set
{
_myCommand = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void Execute(object o)
{
MessageBox.Show("Hello");
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Upvotes: 1