_wpf 点击menui">
赞
踩
问题一
当为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中进行额外处理,这里不再赘述。
如有不当之处,欢迎指出。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。