如何从ContextMenu命令访问CommandTarget?

时间:2018-05-07 13:52:01

标签: c# wpf

我有一个ContextMenu,假设在其父TextBox上设置值。

enter image description here

文本框中没有名称(按要求),因此我将其设置为CommandTarget

    <TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
        <TextBox.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Set to 35"
                          Command="{Binding SetAmountCommand}"
                          CommandParameter="35"
                          CommandTarget="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}" />
                <MenuItem Header="Set to 50"
                          Command="{Binding SetAmountCommand}"
                          CommandParameter="50"
                          CommandTarget="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}" />
            </ContextMenu>
        </TextBox.ContextMenu>

如何从命令内部访问TextBox.Text

视图模型

public class MainVm : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public string TextBoxOne { get; set; } = "One";

    private ICommand _setAmountCommand;
    public ICommand SetAmountCommand
    {
        get
        {
            return _setAmountCommand ?? (_setAmountCommand = new CommandParameterHandler((o) =>
            {
                object param = o;
                double amount = (double)o;
                //MyParentTextBox.Text = amount; //What to put here ? (Cannot be TextBoxOne = amount, need to route from View)
            }, true));
        }
    }
}

Generic CommandParameterHandler

public class CommandParameterHandler : ICommand
{
    private Action<object> _action;
    private bool _canExecute;
    public CommandParameterHandler(Action<object> action, bool canExecute)
    {
        _action = action;
        _canExecute = canExecute;
    }
    public bool CanExecute(object parameter)
    {
        return _canExecute;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        _action(parameter);
    }
}

2 个答案:

答案 0 :(得分:1)

您只能将一个 CommandParameter传递给该命令。如果您想传递的是除实际值之外的内容,您可以创建一个带有多个值的自定义复合类型:

public class CompositeParameter : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return this;
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), 
        typeof(string), typeof(CompositeParameter));

    public string Value
    {
        get { return (string)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ControlProperty = DependencyProperty.Register(nameof(Control),
        typeof(FrameworkElement), typeof(CompositeParameter));
    public FrameworkElement Control
    {
        get { return (FrameworkElement)GetValue(ControlProperty); }
        set { SetValue(ControlProperty, value); }
    }
}

查看型号:

public ICommand SetAmountCommand
{
    get
    {
        return _setAmountCommand ?? (_setAmountCommand = new CommandParameterHandler((o) =>
        {
            CompositeParameter param = o as CompositeParameter;
            if (param != null)
            {
                double amount = Convert.ToDouble(param.Value);
                //...
                TextBox textBox = param.Control as TextBox;
                if (textBox != null)
                    textBox.Text = param.Value;
            }
        }, true));
    }
}

查看:

<TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
    <TextBox.ContextMenu>
        <ContextMenu>
            <ContextMenu.Resources>
                <local:CompositeParameter x:Key="paramA"
                                          Value="35" 
                                          Control="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
                <local:CompositeParameter x:Key="paramB"
                                          Value="50" 
                                          Control="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
            </ContextMenu.Resources>
            <MenuItem Header="Set to 35"
                      Command="{Binding SetAmountCommand}" 
                      CommandParameter="{StaticResource paramA}" />
            <MenuItem Header="Set to 50"
                      Command="{Binding SetAmountCommand}"
                      CommandParameter="{StaticResource paramB}" />
        </ContextMenu>
    </TextBox.ContextMenu>
</TextBox>

答案 1 :(得分:0)

在寻找答案2天后,我遇到了这个RoutedCommand教程。是的,您可以从CommandTarget访问Command,但必须是静态RoutedCommand。这种方法符合需求,因为SetAmountCommand共享MenuItem

XAML

<Window x:Class="WpfCommandTargetDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfCommandTargetDemo">
    <Window.CommandBindings>
        <CommandBinding CanExecute="SetAmountCommand_CanExecute"
                        Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
                        Executed="SetAmountCommand_Executed" />
    </Window.CommandBindings>
    <StackPanel>
        <TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
            <TextBox.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Set to 35"
                              Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
                              CommandParameter="35"
                              CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget}" />
                    <MenuItem Header="Set to 50"
                              Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
                              CommandParameter="50"
                              CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget}" />
                </ContextMenu>
            </TextBox.ContextMenu>
        </TextBox>
    </StackPanel>
</Window>

代码隐藏

public partial class MainWindow : Window
{
    private readonly MainVm _mainVm;

    public MainWindow()
    {
        InitializeComponent();
        _mainVm = new MainVm();
        DataContext = _mainVm;
    }

    void SetAmountCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    void SetAmountCommand_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        object param = e.Parameter; //CommandParameter
        TextBox textbox = e.OriginalSource as TextBox; //CommandTarget
        if (textbox != null)
        {
            textbox.Text = param.ToString();
        }
    }
}

RoutedCommand必须为static,因为它与XAML元素静态绑定。

public static class CustomRoutedCommand
{
    public static readonly RoutedCommand SetAmountCommand = new RoutedCommand();
}

为了完整性,我的ViewModel上没有CommandSetAmountCommand属性已被删除。

public class MainVm : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public string TextBoxOne { get; set; } = "One";
}