c# - 如何在 DataContext (INotifyPropertyChanged) 属性 (WPF - C# - XAML) 上设置点击事件?
问题描述
我想要完成的事情
我试图让我的嵌套菜单项更改显示的用户控件。用更多的技术术语来说,我试图:
- 获取附加到嵌套
MenuItem
(来自我的MyMenu.cs
文件 - 实现INotifyPropertyChanged
)的单击事件,以... - 使用
RoutedEventHandler
(也许来自MyMenu.cs
文件? - 实现UserControl
),以... - 调用
SwitchScreen
方法(从我的MainWindow.cs
文件 - 实现Window
)
我卡住的地方
我似乎找不到将点击事件添加到相应菜单项的方法。
我当前的逻辑还要求将原始发件人作为参数传递,以便我可以识别MySubview
要显示的正确内容。
XAML 处理程序
我已尝试按如下方式在 xaml 中添加单击事件,但它仅将处理程序添加到第一个菜单项级别(而不是嵌套菜单项元素)。
<MenuItem ItemsSource="{Binding Reports, Mode=OneWay}" Header="Reports">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MenuItem_Click"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
C#二传手
我已尝试添加此答案中建议的设置器,但我似乎无法从 to 创建点击MyMenu.cs
事件MyMenuUserControl.cs
。
Style style = new Style();
style.BasedOn = menuItem2.Style;
style.Setters.Add(new EventSetter( /* ??? */ ));
C# ICommand
我试过使用ICommand
,在这个答案中建议,但我似乎无法从 to 创建中继MyMenu.cs
命令MyMenuUserControl.cs
。
在其中一次尝试中,我可能做错了什么,但我现在已经过了玩耍的地步,准备认输。
笔记
实际结构
实际上,我的实际代码具有 n 嵌套的 foreach 循环来生成菜单,如果 foreach 可枚举(例如myObjects
)只有一个元素,我将删除一层嵌套。
移除一层嵌套也会将点击事件上移一层。
我的最终菜单可能如下所示:
我的菜单项:
- 项目 (menuItem1)
- 项目 (menuItem2)
- 项目(menuItem3)+点击事件
- 项目(menuItem3)+点击事件
- 项目(menuItem2)+点击事件(见A)
- 项目 (menuItem2)
- 项目(menuItem1)+点击事件(见B)
A : 只有一个 menuItem3 是嵌套的,所以我们将其移除(这是多余的)并将点击事件向上移动到 menuItem2。
B : menuItem2 嵌套只有一个,menuItem3 只有一个。两者都被删除,因为它们是多余的,我们将点击事件移动到 menuItem1。
这就是为什么我想维护MyMenu
类中菜单项的创建。
其他建议
我可能会完全错误地处理这件事,我愿意接受改变我处理方式的建议。
代码
MyMenu.cs
此类中的构造函数生成我的菜单项及其子菜单项。
这是我试图添加点击事件的地方。
class MyMenu : INotifyPropertyChanged
{
private List<MenuItem> menuItems = new List<MenuItem>();
public List<MenuItem> MenuItems
{
get { return menuItem; }
set
{
menuItem = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public List<Tuple<MyObject, MenuItem>> Map { get; private set; } = new List<Tuple<MyObject, MenuItem>>();
public MyMenu(List<MyObject> myObjects)
{
foreach(MyObject myObject in myObjects)
{
MenuItem menuItem1 = new MenuItem { Header = myObject.Name };
foreach(string s in myObject.Items)
{
MenuItem menuItem2 = new MenuItem { Header = s };
// Add click event to menuItem2 here
menuItem1.Items.Add(menuItem2);
Map.Add(new Tuple<MyObject, MenuItem>(myObject, menuItem2));
}
MenuItem.Add(menuItem1);
}
}
private void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MyMenuUserControl.xaml
最小代码示例 UserControl(使用默认 xmlns 属性)。
MyMenuUserControl.xaml.cs
只有构造函数InitializeComponent();
<UserControl>
<!-- xmlns default attributes in UserControl above removed for minimal code -->
<Menu>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem ItemsSource="{Binding MenuItems, Mode=OneWay}" Header="My menu items"/>
</Menu>
</UserControl>
MyDataContext.cs
最小的代码示例(PropertyChangedEventHandler
与OnPropertyChanged()
代码相同MyMenu.cs
)。构造函数只是设置Menu
和Subviews
属性。
类 MyDataContext : INotifyPropertyChanged { 私人 MyMenu 菜单;公共 MyMenu 菜单 { 获取 { 返回菜单;} 设置 { 菜单 = 值;OnPropertyChanged(); } }
private List<MySubview> mySubviews;
public List<MySubview> MySubviews
{
get { return mySubviews; }
set
{
mySubviews = value;
OnPropertyChanged();
}
}
// ... rest of code removed to maintain minimal code
}
MainWindow.xaml.cs
Subview
包含MyObject
类型的属性。
这使我可以使用MyMenu
'Map
属性来确定要为给定MenuItem
的单击显示哪个子视图。
是的,在地图上制作MainWindow
地图可能更容易,但是我的逻辑MyMenu
是一个最小的例子(有关更多信息,请参阅注释)。
公共部分类 MainWindow : Window { public MainWindow() { InitializeComponent();
// I get my data here
List<MyObject> myObjects = ...
List<MySubview> mySubviews = ...
DataContext = new MyDataContext(new MyMenu(myObjects), new MySubviews(mySubviews));
}
private void SwitchScreen(object sender, RoutedEventArgs e)
{
MyDataContext c = (MyDataContext)DataContext;
MyObject myObject = c.MyMenu.Map.Where(x => x.Item2.Equals(sender as MenuItem)).Select(x => x.Item1).First();
MySubview shownSubview = c.MySubviews.Where(x => x.MyObject.Equals(myObject)).First();
c.MySubviews.ForEach(x => x.Visibility = Visibility.Collapsed);
shownSubview.Visibility = Visibility.Visible;
}
}
解决方案
Wpf 旨在通过 MVVM 模式使用。您似乎正试图直接操纵视觉树,这可能是您的许多问题的来源,因为您似乎处于世界之间的一半。
是什么MyMenu.cs
?它看起来像一个视图模型,但它包含可视项(MenuItem)。VM 不应包含任何可视类。它们是视图的数据抽象。
看起来您MyMenuVM.cs
应该只公开您的List <MyObject>
,并且您的视图菜单应该绑定到它。MenuItem
已经有一个内置的ICommand
(在所有菜单都是为点击而制作的),所以你不需要添加自己的点击处理程序。相反,您绑定MenuItem.Command
到 VM 中的命令,并可能绑定CommandParameter
到MyObject
正在触发该命令的供应。
简而言之,我会阅读一些有关 MVVM 的信息,因为它会使您的代码更清晰、更易于理解,并有望防止此类问题。
推荐阅读
- firefox-developer-tools - 在 filrefox devtools 网络选项卡中保留过滤器
- progressive-web-apps - 如何将我的 PWA 发布到 App Store?
- python - 安装 scikit-learn 库时出错
- php - 函数 App\Http\Controllers\AdminController::updateSlider() 的参数太少,在第 54 行传入了 1 个.... 并且预期正好有 2 个 - laravel
- pandas - 根据 Pandas 数据框中的多行执行计算
- android - 片段布局为空白
- laravel-5 - 我如何在 laravel 5.7 的元标记中添加内容
- sql - SQL - 请求按用户查找平均贷款
- python - 使用 Python 从 Web 读取表格
- kubernetes - Kubernetes 水平 pod 自动扩缩器 - taget 副本计算