WPF菜单设计技巧

WPF应用程序中,菜单设计是一个重要的组成部分,它需要结合多种技术来实现。菜单通常包括静态部分、上下文敏感部分以及动态变化的部分,如最近打开的文件或当前打开的窗口。本文将展示如何针对不同情况设计菜单。

静态菜单结构

对于静态菜单,希望在XAML中声明整个结构。这样做的好处是设计和代码分离,同时获得最佳的开发工具支持。将每个菜单项绑定到视图模型中的ICommand属性。

<Menu DockPanel.Dock="Top"> <MenuItem Header="_File"> <MenuItem Header="_New" Command="{Binding FileNewCommand}"/> <MenuItem Header="_Open" Command="{Binding FileOpenCommand}"/> <MenuItem Header="_Save" Command="{Binding FileSaveCommand}"/> <MenuItem Header="_Close" Command="{Binding FileCloseCommand}"/> </MenuItem> </Menu>

可以使用Update Controls库中的MakeCommand方法来创建所有可绑定的ICommand属性。When子句将用于启用和禁用菜单项。

public ICommand FileSaveCommand { get { // 只有当文件打开时才能保存。 return MakeCommand.When(() => _dataModel.OpenFileName != null) .Do(() => _dataModel.LastAction = "Save"); } }

有些菜单项不是应用程序操作,而是窗口操作。这些可以在代码后台处理。

<Separator/> <MenuItem Header="E_xit" Click="Exit_Click"/> private void Exit_Click(object sender, RoutedEventArgs e) { Close(); }

上下文敏感菜单

上下文敏感菜单在特定条件下出现。WPF提供了DataTrigger机制来实现这一点。DataTrigger在数据属性等于特定值时设置控件属性。在这种情况下,希望当数据属性IsFileOpen为False时,将MenuItem的Visibility属性设置为Hidden。

<Window.Resources> <Style x:Key="VisibleWhenFileIsOpen" TargetType="MenuItem"> <Style.Triggers> <DataTrigger Binding="{Binding IsFileOpen}" Value="False"> <Setter Property="Visibility" Value="Hidden"/> </DataTrigger> </Style.Triggers> </Style> </Window.Resources>

将这种样式应用到任何对这种上下文敏感的菜单上。

<MenuItem Header="_Edit" Style="{StaticResource VisibleWhenFileIsOpen}"> <MenuItem Header="Cu_t"/> <MenuItem Header="_Copy"/> <MenuItem Header="_Paste"/> </MenuItem>

DataTriggers会自动重置。当IsFileOpen数据属性不再是False时,Visibility控制属性将恢复为默认的Visible。不需要为此规则创建另一个触发器。

动态菜单

对于最近打开的文件或当前打开的窗口,希望每个菜单项代表一个数据对象。希望将菜单绑定到一个列表。

如果直接绑定到原始数据对象,将很难在视图中获得所需的确切行为。XAML是声明式的,当数据已经处于正确的格式时,使用起来最简单。这就是视图模型的用武之地。

public class RecentFileViewModel { private int _index; private string _fileName; private IFileHandler _fileHandler; public RecentFileViewModel(int index, string fileName, IFileHandler fileHandler) { _index = index; _fileName = fileName; _fileHandler = fileHandler; } public string FileName { get { return string.Format("{0} - {1}", _index + 1, _fileName); } } public ICommand Open { get { return MakeCommand.Do(() => _fileHandler.Open(_fileName)); } } }

最近文件视图模型以适合菜单项的格式呈现文件名。它甚至添加了下划线,将基于1的索引转换为热键。

视图模型还提供了打开文件的命令。它实际上并不执行操作;它委托给文件处理程序并提供上下文。

根据最近打开的文件列表提供这些视图模型的列表。

public IEnumerable<RecentFileViewModel> RecentFiles { get { // 为每个最近打开的文件创建RecentFileViewModel。 // 视图模型服务于菜单项。 return _dataModel.RecentFiles.Select((fileName, index) => new RecentFileViewModel(index, fileName, this)); } }

请注意,这种模式不适用于ObservableCollection。一旦在ObservableCollection上调用.Select(),它就不再是可观察的。这种模式只适用于Update Controls。

现在需要将MenuItems绑定到这个集合。第一反应是将父MenuItem的ItemTemplate设置为包含子MenuItem的DataTemplate。问题是DataTemplate控制的是子项的内容,而不是子项本身。因此,需要设置ItemContainerStyle,而不是ItemTemplate。

<MenuItem Header="_Recent Files" ItemsSource="{Binding RecentFiles}"> <MenuItem.ItemContainerStyle> <Style> <Setter Property="MenuItem.Header" Value="{Binding FileName}"/> <Setter Property="MenuItem.Command" Value="{Binding Open}"/> </Style> </MenuItem.ItemContainerStyle> </MenuItem>
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485