WPF-菜单和Tab控件
因为菜单和Tab控件一起用,所以就拿出来一块写
Tab控件
定义一个名为ViewItem类,定义Tab的属性
1 /// <summary>
2 /// tab item
3 /// </summary>
4 public class ViewItem
5 {
6 /// <summary>
7 /// 标题
8 /// </summary>
9 public string Header
10 {
11 get;
12 set;
13 }
14
15 /// <summary>
16 /// 主键名称
17 /// </summary>
18 public string KeyName
19 {
20 get;
21 set;
22 }
23
24 /// <summary>
25 /// the show Control
26 /// </summary>
27 public object ViewControl
28 {
29 get;
30 set;
31 }
32 }
TabControl用的是Dev的控件,所以先要引用三个Dev的DLL,分别是DevExpress.Xpf.Core,DevExpress,Xpf.Docking,DevExpress.Xpf.Layout.Core。
新建一个UserControl,命名为MenuControl,在主窗体的Window中需要添加一下引用
1 xmlns:dxd="http://schemas.devexpress.com/winfx/2008/xaml/docking"2 xmlns:c="clr-namespace:RemindWin"
3 xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
4 Name="MainView_View"
其中,c引用的是MenuControl所在的文件夹,因为要用到MenuControl,所以需要引用MenuControl所在的文件夹。并为窗体命名为“MainView_View”(后面用到)。
定义一个DockPanel,在其的顶端设置菜单栏,内容放TabControl,也可以在下面放FootBar,这里省略,主窗体代码如下:
1 <Grid x:Name="MainView">
2 <DockPanel LastChildFill="True">
4 <c:MenuControl DockPanel.Dock="Top" HorizontalAlignment="Left"/>
5 <dxd:DockLayoutManager x:Name="dockManager" UseLayoutRounding="True" AllowCustomization="False">
6 <dxd:LayoutGroup Orientation="Vertical" >
7 <dxd:DocumentGroup Name="documentContainer"
8 SelectedTabIndex="{Binding SelectedTabIndex,Mode=TwoWay}"
9 ItemsSource="{Binding ItemList}"
10 DestroyOnClosingChildren="False"
11 Background="Azure"
12 ClosePageButtonShowMode="InActiveTabPageAndTabControlHeader">
13 <dxd:DocumentGroup.ItemStyle>
14 <Style TargetType="dxd:DocumentPanel">
15 <Setter Property="CloseCommand" Value="{Binding DataContext.CloseCommand,ElementName=MainView}" />
16 <Setter Property="Caption" Value="{Binding Header}"></Setter>
17 <Setter Property="AllowFloat" Value="False"></Setter>
18 <Setter Property="AllowMaximize" Value="False"></Setter>
19 </Style>
20 </dxd:DocumentGroup.ItemStyle>
21 <dxd:DocumentGroup.ItemContentTemplate>
22 <DataTemplate>
23 <ContentControl Content="{Binding ViewControl}"></ContentControl>
24 </DataTemplate>
25 </dxd:DocumentGroup.ItemContentTemplate>
26 </dxd:DocumentGroup>
27 </dxd:LayoutGroup>
28 </dxd:DockLayoutManager>
29 </DockPanel>
30 </Grid>
binding了Model中的ItemList,这是Tab集合,SelectedTabIndex是所选Tab的索引,为Int型,每个Tab有一个Close事件,Binding后台代码中的CloseCommand事件,属性Caption,banding了ViewItem中的Header,每个Tab的内容ContentControl绑定了ViewItem中的ViewControl,其中需要注意Grid的Name与<Setter>里面的元素一致。
添加主窗体的ViewModel,命名MainViewModel,定义ItemList和SelectedTabIndex(引用了SimpleMvvmToolkit)


1 #region Properties
2
3 //
4 private ObservableCollection<ViewItem> _itemList = new ObservableCollection<ViewItem>();
5 /// <summary>
6 /// Tab页集合
7 /// </summary>
8 public ObservableCollection<ViewItem> ItemList
9 {
10 get
11 {
12 return _itemList;
13 }
14 set
15 {
16 _itemList = value;
17 }
18 }
19
20 //所选tab索引
21 private int _selectedTabIndex = 0;
22 /// <summary>
23 /// 所选tab索引
24 /// </summary>
25 public int SelectedTabIndex
26 {
27 get
28 {
29 return _selectedTabIndex;
30 }
31 set
32 {
33 _selectedTabIndex = value;
34 NotifyPropertyChanged(x => x.SelectedTabIndex);
35 }
36 }
37
38 #endregion
View Code
定义添加Tab的方法,定义了两个,一个ViewModel是为类型的方法,一个ViewModel以参数形式的方法,第二种主要是针对一些需要传参的ViewModel,这里有一个规则,如果一个需要添加一个已有的Tab页,会自动跳到该Tab页,而不是出现两个一样的Tab页,这里主要依靠KeyName做判断(也可添加两个一样的Tab,代码需简单修改)
1 /// <summary>
2 /// 添加Tab页
3 /// </summary>
4 /// <typeparam name="TView">View</typeparam>
5 /// <typeparam name="KMode">ViewModel</typeparam>
6 /// <param name="header">对应的标题</param>
7 /// <param name="systemFunction">View的类型</param>
8 public void OpenNewItem<TView, KMode>(string header, string systemFunction)
9 where TView : System.Windows.Controls.UserControl, new()
10 where KMode : new()
11 {
12 string keyName = systemFunction.ToString();
13 var q = ItemList.Where(x => x.KeyName == keyName).FirstOrDefault();
14 int index = 0;
15 if (q == null)
16 {
17 KMode vModel = new KMode();
18 TView view = new TView();
19 ViewItem item = new ViewItem();
20 item.Header = header;
21 item.KeyName = keyName;
22 item.ViewControl = view;
23 ItemList.Add(item);
24 view.DataContext = vModel;
25 index = ItemList.Count() - 1;
26 }
27 else
28 {
29 index = ItemList.IndexOf(q);
30 }
31 SelectedTabIndex = index;
32 }
33
34 /// <summary>
35 /// 添加Tab页
36 /// </summary>
37 /// <typeparam name="TView">View</typeparam>
38 /// <param name="header">对应的标题</param>
39 /// <param name="systemFunction">View的类型</param>
40 /// <param name="_model">可带参数的ViewModel</param>
41 public void OpenNewItemPara<TView>(string header, string systemFunction,object _model)
42 where TView : System.Windows.Controls.UserControl, new()
43 {
44 string keyName = systemFunction.ToString();
45 var q = ItemList.Where(x => x.KeyName == keyName).FirstOrDefault();
46 int index = 0;
47 if (q == null)
48 {
49 TView view = new TView();
50 ViewItem item = new ViewItem();
51 item.Header = header;
52 item.KeyName = keyName;
53 item.ViewControl = view;
54 ItemList.Add(item);
55 view.DataContext = _model;
56 index = ItemList.Count() - 1;
57 }
58 else
59 {
60 index = ItemList.IndexOf(q);
61 }
62 SelectedTabIndex = index;
63 }
定义关闭Tab的事件和方法


1 /// <summary>
2 /// 关闭Tab页
3 /// </summary>
4 public DelegateCommand CloseCommand
5 {
6 get
7 {
8 return new DelegateCommand(CloseTab);
9 }
10 }
11
12 private void CloseTab()
13 {
14 if (SelectedTabIndex >= 0)
15 {
16 var q = ItemList[SelectedTabIndex];
17 ItemList.RemoveAt(SelectedTabIndex);
18 ((System.Windows.Controls.UserControl)q.ViewControl).DataContext = null;
19 q.ViewControl = null;
20 q = null;
21 GC.Collect();
22 GC.WaitForPendingFinalizers();
23 GC.Collect();
24 }
25 }
View Code
别忘了,在MainWindow.xaml.cs的构造中添加这句话
1 this.DataContext = new MainViewModel();
MenuControl菜单栏
定义MenuItem


1 /// <summary>
2 /// 主菜单项。可添加更多属性实现菜单的个性配置,如字体、图片、响应等
3 /// </summary>
4 public class MenuItem : ModelBase<MenuItem>
5 {
6 public MenuItem(string item)
7 {
8 Header = item;
9 Childrens = new List<MenuItem>();
10 }
11
12 public MenuItem(string header, string keyName)
13 {
14 Header = header;
15 KeyName = keyName;
16 }
17
18 /// <summary>
19 /// 菜单名称,做为菜单主键
20 /// </summary>
21 public string KeyName
22 {
23 get;
24 set;
25 }
26
27 /// <summary>
28 /// 菜单名
29 /// </summary>
30 public string Header { get; set; }
31
32 /// <summary>
33 /// 图片
34 /// </summary>
35 public BitmapImage Icon { get; set; }
36
37 /// <summary>
38 /// 是否可用
39 /// </summary>
40 private bool m_IsEnabled = true;
41 /// <summary>
42 /// 菜单可用性。判断权限后设置此属性实现菜单的可用性控制
43 /// </summary>
44 new public bool IsEnabled
45 {
46 get
47 {
48 return m_IsEnabled;
49 }
50 set
51 {
52 m_IsEnabled = value;
53 NotifyPropertyChanged(x => x.IsEnabled);
54 }
55 }
56
57 /// <summary>
58 /// 子菜单
59 /// </summary>
60 public List<MenuItem> Childrens { get; set; }
61
62 /// <summary>
63 /// 事件
64 /// </summary>
65 public DelegateCommand<string> SelectedCommand { get; set; }
66
67
68 }
View Code
在MenuControl中定义各种格式和属性


1 <UserControl.Resources>
2 <HierarchicalDataTemplate x:Key="ItemTemplate"
3 ItemsSource="{Binding Childrens}">
4 <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
5 <TextBlock Text="{Binding Header}"
6 IsEnabled="{Binding IsEnabled}" VerticalAlignment="Center"/>
7 </StackPanel>
8 </HierarchicalDataTemplate>
9 <ControlTemplate x:Key="MenuSeparatorTemplate">
10 <Separator />
11 </ControlTemplate>
12 </UserControl.Resources>
13
14 <Menu DockPanel.Dock="Top"
15 VerticalAlignment="Top"
16 ItemsSource="{Binding MenuList}"
17 ItemTemplate="{StaticResource ItemTemplate}">
18
19 <!-- 菜单背景色-->
20 <Menu.Background>
21 <LinearGradientBrush StartPoint="0,0" EndPoint="0,1.5">
22 <GradientStop Color="#f2f2f2" Offset="0.5"/>
23 <GradientStop Color="#F8F9FB" Offset="01"/>
24 </LinearGradientBrush>
25 </Menu.Background>
26 <Menu.ItemContainerStyle>
27 <Style TargetType="MenuItem">
28 <Setter Property="IsEnabled"
29 Value="{Binding IsEnabled}" />
30 <Setter Property="Command"
31 Value="{Binding SelectedCommand}" />
32 <Setter Property="CommandParameter"
33 Value="{Binding Header}" />
34 <Setter Property="FontSize" Value="14"/>
35
36 <Style.Triggers>
37 <DataTrigger Binding="{Binding }"
38 Value="{x:Null}">
39 <Setter Property="Template"
40 Value="{StaticResource MenuSeparatorTemplate}" />
41 </DataTrigger>
42 </Style.Triggers>
43
44 </Style>
45 </Menu.ItemContainerStyle>
46 </Menu>
View Code
定义菜单的ViewModel,名为MenuControlModel,定义属性
1 #region Properties
2
3 private List<MenuItem> m_MainMenus = new List<MenuItem>();
4 public List<MenuItem> MenuList
5 {
6 get
7 {
8 return m_MainMenus;
9 }
10 set
11 {
12 m_MainMenus = value;
13 NotifyPropertyChanged(x => x.MenuList);
14 }
15 }
16
17 #endregion
定义递归设置所有菜单的命令
1 /// <summary>
2 /// 递归设置所有菜单的命令
3 /// </summary>
4 /// <param name="Menus"></param>
5 void SetMenuCommand(List<MenuItem> Menus)
6 {
7 foreach (var item in Menus)
8 {
9 item.SelectedCommand = new DelegateCommand<string>(OpenMenu);
10 if (item.Childrens.Count > 0) SetMenuCommand(item.Childrens);
11 }
12 }
定义点击菜单Item时执行的方法,其中第一个判断是找到主窗体,再进行操作,如果菜单的Name中包含“测试”两个字符,则使用第一个添加Tab的方法,否则,用第二种。Control_Test和TestModel分别是UserControl和其ViewModel。
1 /// <summary>
2 /// 打开方法
3 /// </summary>
4 /// <param name="MenuName"></param>
5 private void OpenMenu(string MenuName)
6 {
7 foreach (System.Windows.Window win in System.Windows.Application.Current.Windows)
8 {
9 if (win.Name == "MainView_View")
10 {
11 if (MenuName.Contains("测试"))
12 {
13 (win.DataContext as MainViewModel).OpenNewItem<UserControls.Control_Test, UserControls.TestModel>(MenuName, MenuName);
14 }
15 else
16 {
17 UserControls.TestModelSec m = new UserControls.TestModelSec(MenuName);
18 (win.DataContext as MainViewModel).OpenNewItemPara<UserControls.Control_Test>(MenuName, MenuName, m);
19 }
20 break;
21 }
22 }
23 }
初始化菜单,在“主菜单”中有四个子菜单,其中“操作”的子菜单中还有子菜单
1 private void LoadMenuData()
2 {
3 MenuItem mainMenu = new MenuItem("主菜单");
4 MenuItem projectMenu = new MenuItem("项目");
5 MenuItem toolMenu = new MenuItem("工具");
6 MenuItem otherMenu = new MenuItem("其他");
7 MenuItem helpMenu = new MenuItem("帮助");
8
9 mainMenu.Childrens.Add(new MenuItem("打开"));
10 mainMenu.Childrens.Add(new MenuItem("新建"));
11
12 MenuItem operateMenu = new MenuItem("操作");
13
14
15 MenuItem saveMenu = new MenuItem("保存");
16 MenuItem deleteMenu = new MenuItem("删除");
17 MenuItem readMenu = new MenuItem("读取");
18 operateMenu.Childrens.Add(saveMenu);
19 operateMenu.Childrens.Add(deleteMenu);
20 operateMenu.Childrens.Add(readMenu);
21
22 mainMenu.Childrens.Add(operateMenu);
23
24 mainMenu.Childrens.Add(new MenuItem("测试三"));
25
26 helpMenu.Childrens.Add(new MenuItem("测试一"));
27 helpMenu.Childrens.Add(new MenuItem("测试二"));
28 helpMenu.Childrens.Add(new MenuItem("测试三"));
29
30 MenuList.Add(mainMenu);
31 MenuList.Add(projectMenu);
32 MenuList.Add(toolMenu);
33 MenuList.Add(otherMenu);
34 MenuList.Add(helpMenu);
35
36 SetMenuCommand(MenuList);
37 }
在构造中,执行初始化菜单的方法
1 public MenuControlModel()
2 {
3 LoadMenuData();
4 }
结束
Tab每项的内容必须为UserControl,UserControl无需与Model进行banding。测试的UserControl和其Model没有写,只是一个简单的例子~