2

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); }
    }
8
  • You aren't using your DP here: you just created it and created a property. CalculateChecksumCommand won't notify any change to the interface. Please look at how a DP should be declared: msdn (Note that this is probably not the issue) Commented Mar 23, 2015 at 19:57
  • Use a DP when you need to use a property from outside your control: this is not the case here. Use Commands & Binding to communicate with the ViewModel layer, avoid it to communicate with the View layer. A binding with a RelativeSource shouldn't be a problem but don't ever use a binding depending on the DataContext in your custom control. The problem with your code is that it expose CalculateChecksumCommand to the outside world when it shouldn't (you should expose only what can be used by the outside world, which isn't the case for your property) Commented Mar 23, 2015 at 20:08
  • @nkoniishvt, Ok, I understand. I have edited my first post to declare the DP the right way. Commented Mar 23, 2015 at 20:33
  • @nkoniishvt, In fact, I wanted to use a command so that my MenuItem is disabled automatically when it has to be. This is probably not the best way to do it. How should I implement this behavior ? Should, I use the click event of the MenuItem? If so, how? (I tried many times without success) Commented Mar 23, 2015 at 20:35
  • this is the right thing to do except that your component should expose a ICommand DP and let the consumer set it. A custom control should be as "generic" as possible, here your control will work in only one context: calculate a check sum it seems. I'm not the most experienced WPF dev but I think what you should do is expose an ICommand DP and set your menuItemInsertChecksum's Command DP in the callback function. (Whenever your exposed ICommand get set, set the MenuItem's Command) Commented Mar 23, 2015 at 20:43

3 Answers 3

1

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));
        }
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Great! Thank you very much for this detailed answer. I almost found the solution. My case is slightly different since my command is defined in my CustomControl code-behind. This is why I wanted to set my MenuItem DataContext like that : DataContext="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget}".This should have worked because the PlacementTarget should have been my CustomControl...but it's not. In fact it seems that the PlacementTarget is a child of my CustomControl and not the CustomControl itself. Is there a way to solve this issue?
Sorry, I forgot to change the command. I have no more binding error but it still don't work. I think I will not use ContextMenu for this feature...I spent too much time on that without success. Thank you anyway for the time you spent. Also thank you for the link !
I finally found the solution by chance ... Maybe it Will help someone so ... Since the PlacementTarget of my ContextMenu is a child inside my CustomControl template, I had to set my command like that: Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.TemplatedParent.CalculateChecksumCommand}" (see TemplatedParent). There is probably a better way to do but this one works for me.
Good work, in the future you should post your entire code so no one has to guess and can help you the better. Also I think you should get familiar with MVVM since this pattern is very useful for WPF.
,You are right, my problem was not clearly explained. I have posted an answer that contains a simplified version of my fixed code. Concerning the MVVM pattern, I'm currently trying to learn it. This is why I don't know the good practices.
1

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.

Comments

0

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

2 Comments

This solution seems really interesting! I will try to implement it. Thank you.
You are welcome. Take a look to the "example" link too. Also you need to declare the static RoutedComand right under the class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.