WPF解决ContextMenu菜单的DataContext无法绑定,并实现按钮左键弹出菜单

1 篇文章 0 订阅

问题一

当为ContextMenu指定DataContext时,因为ContextMenu属于弹出层,在VisualTree中与所在的设计视图已经分离,所以无法将DataContext绑定到任何父级元素,即使整个视图已指定了DataContext,也无法设置MenuItem的Command,也就是说,以下几种方式都是无效的,均不能触发菜单项的Command

<UserControl x:Class="....."
             x:Name="TestView">
    <Grid>

        <Button Content="测试菜单-继承DataContext">
            <Button.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="菜单项1" Command="{Binding Test1}"/>
                    <MenuItem Header="菜单项2" Command="{Binding Test2}"/>
                </ContextMenu>
            </Button.ContextMenu>
        </Button>

        <Button Content="测试菜单-指定DataContext" 
                DataContext="{Binding ElementName=TestView}">
            <Button.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="菜单项1" Command="{Binding Test1}"/>
                    <MenuItem Header="菜单项2" Command="{Binding Test2}"/>
                </ContextMenu>
            </Button.ContextMenu>
        </Button>

        <Button Content="测试菜单-指定菜单项的DataContext">
            <Button.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="菜单项1" Command="{Binding Test1, ElementName=TestView}"/>
                    <MenuItem Header="菜单项2" Command="{Binding Test2, ElementName=TestView}"/>
                </ContextMenu>
            </Button.ContextMenu>
        </Button>

        <Button Content="测试菜单-绑定动态DataContext" 
                DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}">
            <Button.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="菜单项1" Command="{Binding Test1}"/>
                    <MenuItem Header="菜单项2" Command="{Binding Test2}"/>
                </ContextMenu>
            </Button.ContextMenu>
        </Button>

    </Grid>
</UserControl>

  问题1可以参考博客园的一个帖子,使用BindingProxy实现代理绑定上下文:https://www.cnblogs.com/muran/p/6702444.html,这里介绍的是从Button本身优化的方式,更容易理解,也较为直接

问题二

通常将ContextMenu赋值给UIElement对象时,都是作为右键菜单,但某些应用场景希望能通过点击鼠标左键弹出菜单。

问题二也可以在按钮的Click事件上进行处理,主动使它弹出菜单,但缺点是每个按钮都要写同样的处理逻辑,违反了单一职责原则和迪米特法则,简单讲就是麻烦和不易维护。

 

解决方案

重写Button,其他有意义的扩展也可以加入到同一个重写的类中,比如 Button.Key,Button.IsChecked属性等

后端代码

namespace MenuDemo.Controls
{

    public class AppButton : Button
    {

        //注册Menu依赖属性
        public ContextMenu Menu
        {
            get { return (ContextMenu)GetValue(MenuProperty); }
            set { SetValue(MenuProperty, value); }
        }
        public static readonly DependencyProperty MenuProperty =
            DependencyProperty.Register("Menu", typeof(ContextMenu), typeof(AppButton), new PropertyMetadata(null, Menu_PropertyChanged));

        //处理Menu属性变更
        private static void Menu_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as SmartButton;
            var menu = e.NewValue as ContextMenu;
            if (menu != null && menu.DataContext == null && menu.GetBindingExpression(DataContextProperty) == null)
            {
                //当未使用其他方式指定ContextMenu的DataContext时,使其与Button的DataContext一致
                menu.DataContext = button.DataContext;
            }
        }

        //重写点击时执行的方法
        protected override void OnClick()
        {
            base.OnClick();
            if (this.Menu != null)
            {
                //弹出菜单
                this.Menu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
                this.Menu.PlacementTarget = this;
                this.Menu.IsOpen = true;
            }
        }

    }

}

前端代码

<UserControl x:Class="......"
             xmlns:controls="MenuDemo.Controls">
    <Grid>
        <controls:AppButton Content="开始" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}">
            <controls:AppButton.Menu>
                <ContextMenu>
                    <MenuItem Header="测试1" Command="{Binding Test1}"/>
                    <MenuItem Header="测试2" Command="{Binding Test2}"/>
                </ContextMenu>
            </controls:AppButton.Menu>
        </controls:AppButton>
    </Grid>
</UserControl>

这样通过前端赋值Menu时处理其DataContext的方式,使ContextMenu与Button的上下文保持一致,既解决了无法绑定上下文的问题,又可以实现左键点击弹出。

如果UserControl的代码中设置了 DataContext = this,可以省去为AppButton绑定DataContext的过程,但需要在Menu_PropertyChanged中进行额外处理,这里不再赘述。

如有不当之处,欢迎指出。

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
实现点击 WPF 界面上的“添加”按钮弹出添加页面并使用绑定的方式,可以采用以下步骤: 1. 创建一个新的 ViewModel(比如 AddViewModel.cs),用于管理添加页面的数据和行为。 ``` public class AddViewModel : INotifyPropertyChanged { private string _name; private int _age; public string Name { get { return _name; } set { if (_name != value) { _name = value; OnPropertyChanged("Name"); } } } public int Age { get { return _age; } set { if (_age != value) { _age = value; OnPropertyChanged("Age"); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } ``` 2. 在 AddPage.xaml 中,将页面的 DataContext 属性绑定到 AddViewModel 对象。 ``` <Window x:Class="WpfApp1.AddPage" ... xmlns:local="clr-namespace:WpfApp1"> <Window.DataContext> <local:AddViewModel /> </Window.DataContext> ... </Window> ``` 3. 在 AddPage.xaml 中,使用绑定方式将页面的控件与 AddViewModel 的属性进行绑定。 ``` <TextBox Text="{Binding Name}" /> <TextBox Text="{Binding Age}" /> ``` 4. 在主界面的“添加”按钮的 Click 事件中,创建一个新的 AddPage 对象,并设置该页面的 Owner 属性为主界面的窗口。 ``` private void addButton_Click(object sender, RoutedEventArgs e) { AddPage addPage = new AddPage(); addPage.Owner = this; addPage.ShowDialog(); } ``` 5. 在 AddPage.xaml 中,添加一个“保存”按钮,并在 Click 事件中编写保存数据的代码。 ``` private void saveButton_Click(object sender, RoutedEventArgs e) { // 保存数据的代码 // 关闭 AddPage 页面 this.Close(); } ``` 这样,当用户点击主界面上的“添加”按钮时,就会弹出 AddPage 页面,用户可以在该页面上输入相关数据,并点击“保存”按钮保存数据。保存成功后,AddPage 页面会自动关闭,回到主界面。同时,使用绑定的方式可以更方便地管理页面的数据和行为,提高代码的可维护性和可扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为轮子而生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值