从零搭建工业控制系统(十六):界面布局——Dock面板与多窗口 界面布局Dock面板与多窗口这是「从零搭建工业控制系统」系列第16篇。后端逻辑聊了十几篇终于说到前端了。工业系统的界面不像网页那样花哨但布局复杂度一点不低——十几个面板同屏显示还要可拖拽、可隐藏、可保存布局。本篇只讲布局自定义控件下篇再说。第一次布局的灾难最初版本的界面我用Grid硬切三行五列每个格子塞一个UserControl。看起来整齐但操作员投诉说面板太小看不清数据想拖大一点又拖不动。后来换成了DockPanel AvalonDockDockingManager面板可拖拽、可浮动、可钉住。操作员想看哪个面板就拖大哪个不想看的折叠起来。布局还能保存下次启动自动恢复。整体布局结构主窗口用Grid分三块顶部导航栏、中间DockingManager、底部状态栏GridGrid.RowDefinitionsRowDefinitionHeightAuto/!-- 导航栏 --RowDefinitionHeight*/!-- DockingManager --RowDefinitionHeightAuto/!-- 状态栏 --/Grid.RowDefinitions!-- 顶部导航Logo 用户信息 权限显示 --StackPanelGrid.Row0OrientationHorizontalTextBlockText控制系统FontWeightBold/TextBlockText{Binding PermissionService.UserDisplayText}//StackPanel!-- 中间可拖拽面板区域 --dock:DockingManagerGrid.Row1!-- 各种可停靠面板 --/dock:DockingManager!-- 底部状态栏连接状态 报警状态 --StatusBarGrid.Row2StatusBarItemContent{Binding DeviceStatusText}//StatusBar/GridDock面板布局DockingManager里放LayoutDocumentPane和LayoutAnchorablePane。Document是标签页式面板Anchorable是可停靠可隐藏的面板dock:DockingManagerdock:LayoutRootdock:LayoutPanelOrientationHorizontal!-- 左侧自动运行面板 --dock:LayoutAnchorablePaneDockWidth300dock:LayoutAnchorableTitle自动运行ContentIdAutoPanelviews:AutoRunView//dock:LayoutAnchorable/dock:LayoutAnchorablePane!-- 中间主操作区 --dock:LayoutDocumentPanedock:LayoutDocumentTitle设备控制ContentIdDevicePanelviews:DevicePanel//dock:LayoutDocument/dock:LayoutDocumentPane!-- 右侧日志面板 --dock:LayoutAnchorablePaneDockWidth400dock:LayoutAnchorableTitle日志ContentIdLogPanelviews:LogView//dock:LayoutAnchorable/dock:LayoutAnchorablePane/dock:LayoutPanel/dock:LayoutRoot/dock:DockingManager权限控制面板显隐面板的显隐也受权限控制。操作员不该看到的面板直接不创建dock:LayoutAnchorableTitle原始IO调试Visibility{Binding PermissionService.IsAdmin, Converter{StaticResource BoolToVisibilityConverter}}views:RawIOPanel//dock:LayoutAnchorable布局保存与恢复AvalonDock支持布局序列化。程序关闭时保存启动时恢复// 保存布局privatevoidSaveLayout(){varserializernewXmlLayoutSerializer(dockingManager);usingvarwriternewStreamWriter(layoutFilePath);serializer.Serialize(writer);}// 恢复布局privatevoidLoadLayout(){if(!File.Exists(layoutFilePath))return;varserializernewXmlLayoutSerializer(dockingManager);serializer.LayoutSerializationCallback(s,e){// 根据ContentId找回ViewModele.ContentFindContent(e.Model.ContentId);};usingvarreadernewStreamReader(layoutFilePath);serializer.Deserialize(reader);}踩过的坑反序列化时如果找不到对应的ContentId面板会消失。回调函数里要处理找不到的情况给个默认面板。ViewModel与View的映射因为代码混淆默认的ViewModel自动定位会失效。在App.xaml.cs里手动注册映射protectedoverridevoidConfigureViewModelLocator(){base.ConfigureViewModelLocator();ViewModelLocationProvider.RegisterMainWindow,MainWindowViewModel();ViewModelLocationProvider.RegisterViews.DevicePanel,ViewModels.DevicePanelViewModel();ViewModelLocationProvider.RegisterViews.Dock.LogView,ViewModels.Dock.LogViewModel();// ... 其他映射}不注册的话Release版混淆后View找不到对应的ViewModel界面全是空的。界面踩坑清单坑现象解决Grid硬切面板不能拖拽用DockingManager混淆后VM丢失Release版界面空白手动注册ViewModel映射布局恢复失败面板消失处理LayoutSerializationCallback权限面板可见操作员看到调试面板Visibility绑定权限属性本篇小结知识点关键做法Dock布局AvalonDock DockingManager面板类型Document(标签页) Anchorable(可停靠)权限控制Visibility绑定IsAdmin等属性布局序列化XmlLayoutSerializer保存/恢复VM映射ViewModelLocationProvider手动注册工业界面的核心不是好看是信息密度——一屏能看到多少关键数据能多快找到要操作的按钮。下期预告第17篇自定义控件设计布局讲完了下篇讲自定义控件——线圈开关、模拟仪表、DataTemplate选择器怎么封装可复用的工业UI组件。