将动态创建的菜单项从Button.ContextMenu绑定到ICommand

时间:2022-04-13 08:37:22

OK, a definite newbie here with WPF, and obviously need to keep learning more about MVVM, my code wasn't specifically designed that way, but I did designate one class to be the interface and controller for the GUI, whereas the model code resides in another set of classes. Have been scouring the web for examples, and questions similar to mine, of which there are plenty, but after three days of running through the maze I'm asking for help.

好的,这是WPF的一个新手,显然需要继续学习更多关于MVVM的知识,我的代码并不是专门设计的,但是我指定了一个类作为GUI的接口和控制器,而模型代码则驻留在另一组类中。我已经在网上搜索了很多例子和类似的问题,其中有很多,但是在经过三天的迷宫后,我请求帮助。

What I need is a simple dropdown menu, with items that can be dynamically updated (its an app that talks to a USB device, so however many are available should show up along with their device ID and serial number), and the currently selected item should show up on the Button (or whatever implementation of Dropdown menu I end up with). In this example, I just create a static list but that same list would be dynamically updated later on in the full app.

我需要的是一个简单的下拉菜单,可以动态更新的项目(它的一个应用,会谈到一个USB设备,所以不管有多少可用应该出现连同他们的设备ID和序列号),和当前选中的项应该出现在按钮(或其他实现我最终的下拉菜单)。在本例中,我只是创建了一个静态列表,但是相同的列表稍后将在整个应用程序中动态更新。

What I have so far looks like it is on the right track: I get the currently selected device id string to show up on the Button, and on pushing the Button, I get the list of all available devices (it doesn't bother me much that the currently selected device shows up redundantly in the list). However, I am not able to hook into any event when an item is selected, and thus can't update the item in the button, or do anything else for that matter.

我到目前为止看起来是正确的:我得到当前选中的设备id字符串显示在按钮上,按下按钮,我得到的所有可用设备列表(它不打扰我,当前选中的设备列表中出现多余地)。但是,当选择一个项目时,我不能连接到任何事件,因此不能更新按钮中的项目,也不能为此做任何其他事情。

My XAML below. Note that this was roughly hacked together, and there are some things in here that make no sense, like "IsActive" for the "IsChecked" property, that came from examples. The big problem is that as far as I can tell, none of the Setter properties in the ContextMenu.Resources seem to be doing anything at all...tried changing the fontsize to no avail. And the really big problem, of course, is that the "MyCommand" binding isn't working, that method never gets called.

我下面的XAML。注意,这是粗略地合并在一起的,这里有一些没有意义的东西,比如来自示例的“IsActive”属性。最大的问题是,据我所知,ContextMenu中没有任何Setter属性。资源似乎在做任何事情……试着改变字体大小,但没有效果。当然,真正的大问题是“MyCommand”绑定不起作用,这个方法永远不会被调用。

    <Label Content="Device Selected:" HorizontalAlignment="Left" Margin="25,22,0,0" VerticalAlignment="Top" Width="124" FontWeight="Bold" FontSize="14" Height="25"/>
    <Button x:Name="DeviceSelMenuButton" Content="{Binding DeviceID_and_SN, Mode=TwoWay}" HorizontalAlignment="Left" Height="28" Margin="25,52,0,0" VerticalAlignment="Top" Width="187" FontSize="14" Click="DeviceSelMenuButton_Click">
        <Button.ContextMenu>
            <ContextMenu ItemsSource="{Binding DeviceID_SN_Collection, Mode=TwoWay}">
                <ContextMenu.Resources>
                    <Style x:Key="SelectDeviceStyle" TargetType="MenuItem">
                        <Setter Property="Command" Value="{Binding MyCommand}"/>
                       <Setter Property="CommandTarget" Value="{Binding RelativeSource Self}"/> 
                        <Setter Property="IsChecked" Value="{Binding IsActive}"/>
                        <Setter Property="IsCheckable" Value="True"/>
                        <Setter Property="FontSize" Value="14"/>
                    </Style>
                </ContextMenu.Resources>
            </ContextMenu>
        </Button.ContextMenu>
    </Button>

And the code from MainWindow.xaml.cs:

以及来自mainwindow .xam .cs的代码:

public partial class MainWindow : Window
{
    CustomDeviceGUI _customDeviceGui = new CustomDeviceGUI();

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = _customDeviceGui;
    }

    private void DeviceSelMenuButton_Click(object sender, RoutedEventArgs e)
    {
        // " (sender as Button)" is PlacementTarget
        (sender as Button).ContextMenu.IsEnabled = true;
        (sender as Button).ContextMenu.PlacementTarget = (sender as Button);
        (sender as Button).ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
        (sender as Button).ContextMenu.IsOpen = true;
    }

    private void SomeMethod(object sender, DataTransferEventArgs e)
    {
        // TODO Somehow get the index of the selected menu item (collection index, 0-based)
        //     int selIndex = (sender as Button).ContextMenu.Items.IndexOf    ??         
        _customDeviceGui.UpdateDeviceID("RelayPro id updated");
    }

}

And the GUI code:

和GUI代码:

class CustomDeviceGUI : INotifyPropertyChanged
{
    // Declare the event 
    public event PropertyChangedEventHandler PropertyChanged = delegate { }; 
    private string _deviceDisplayString;
    private ICommand _updateMenu; 
    List<string> ControllerDeviceList = new List<string>();

    private System.Collections.ObjectModel.ObservableCollection<string> _DeviceID_SN_Collection = new System.Collections.ObjectModel.ObservableCollection<string>();

    // CTOR
    public CustomDeviceGUI()
    {
        ControllerDeviceList.Add("CustomDevice Device 1");
        ControllerDeviceList.Add("CustomDevice Device 2");
        ControllerDeviceList.Add("CustomDevice Device 3");
        ControllerDeviceList.Add("CustomDevice Device 6");
        UpdateDeviceID(ControllerDeviceList[0]);
    }

    #region CustomDeviceGUI Properties

    public System.Collections.ObjectModel.ObservableCollection<string> DeviceID_SN_Collection
    {
        get
        {
            _DeviceID_SN_Collection.Clear();
            foreach (string str in ControllerDeviceList)
            {
                _DeviceID_SN_Collection.Add(str);
            }
            return _DeviceID_SN_Collection;
        }
        private set 
        {
            _DeviceID_SN_Collection = value;
        }
    }

    public string DeviceID_and_SN
    {
        get
        {
            return _deviceDisplayString;
        }
        private set
        {
            _deviceDisplayString = value;
        }
    }

    public ICommand MyCommand
    {
        get
        {
            if (_updateMenu == null)
                _updateMenu = new MyGuiCommand();

            return _updateMenu;
        }

    }


    #endregion

    #region Public Methods


    public void UpdateDeviceID(string deviceID)
    {
        this._deviceDisplayString = deviceID;
        RaisePropertyChangeEvent("DeviceID_and_SN");
        RaisePropertyChangeEvent("DeviceID_SN_Collection");            
    }

    #endregion

    protected void RaisePropertyChangeEvent(string name)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;

        try
        {
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
        catch (Exception e)
        {
            //  ... TODO Remove this catchall or find specific exceptions
        }
    }


    public class MyGuiCommand : ICommand
    {
        public void Execute(object parameter)
        {
            //   Debug.WriteLine("Hello, world");
            int hmm = 3;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }
        public event EventHandler CanExecuteChanged // was ;
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }


} // class CustomDeviceGUI

2 个解决方案

#1


0  

All the changes I had to make were in XAML. Primarily it was a matter of using the ancestor to get the right data context. I also switched to ContextMenu.ItemContainer instead of ContextMenu.Resources.

我要做的所有改变都是在XAML中。主要是使用祖先获取正确的数据上下文。我还切换到ContextMenu。ItemContainer代替ContextMenu.Resources。

   <ContextMenu.ItemContainerStyle>
      <Style TargetType="MenuItem"> 
         <Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=DataContext.MyCommand}"/>
      </Style>
   </ContextMenu.ItemContainerStyle>

#2


-1  

Eventough I'm not sure I think that the:

虽然我不确定我认为:

<Setter Property="Command" Value="{Binding MyCommand}"/>

binding needs a RoutedUICommand object.

绑定需要一个RoutedUICommand对象。

EDIT: Another thing that i have noticed is that you don't set any command bindings before. Like this:

编辑:我注意到的另一件事是您以前没有设置任何命令绑定。是这样的:

<Window.CommandBindings>
    <CommandBinding Command="MyCommand" Executed="Execute" />
</Window.CommandBindings>

just an example you can set CommandBindings to many others controls.

您可以为许多其他控件设置CommandBindings。

#1


0  

All the changes I had to make were in XAML. Primarily it was a matter of using the ancestor to get the right data context. I also switched to ContextMenu.ItemContainer instead of ContextMenu.Resources.

我要做的所有改变都是在XAML中。主要是使用祖先获取正确的数据上下文。我还切换到ContextMenu。ItemContainer代替ContextMenu.Resources。

   <ContextMenu.ItemContainerStyle>
      <Style TargetType="MenuItem"> 
         <Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=DataContext.MyCommand}"/>
      </Style>
   </ContextMenu.ItemContainerStyle>

#2


-1  

Eventough I'm not sure I think that the:

虽然我不确定我认为:

<Setter Property="Command" Value="{Binding MyCommand}"/>

binding needs a RoutedUICommand object.

绑定需要一个RoutedUICommand对象。

EDIT: Another thing that i have noticed is that you don't set any command bindings before. Like this:

编辑:我注意到的另一件事是您以前没有设置任何命令绑定。是这样的:

<Window.CommandBindings>
    <CommandBinding Command="MyCommand" Executed="Execute" />
</Window.CommandBindings>

just an example you can set CommandBindings to many others controls.

您可以为许多其他控件设置CommandBindings。