见上图。这是 Visual Studio 选项表单的屏幕截图。
左侧本质上是一个 TreeView。右侧是更改程序选项的各种控件。当 TreeView 中的节点被选中时,右侧会发生变化,显示不同的选项。
你如何编写这样的程序?右侧是否只有 50 个重叠的面板,节点的选择只是改变了哪个面板是可见的?如果是这种情况,您将如何管理这种情况?这将是一个混乱的设计师。
不,您不会制作 50 个重叠的面板。只需创建几个用户控件,例如,链接节点标签上的类型。您可以使用激活器来创建控件。创建 1 个树视图和 1 个面板:(伪代码)
// create nodes:
TreeNode item = new TreeNode();
item.Tag = typeof(UserControl1);
TreeView.Nodes.Add( item );
// field currentControl
UserControl _currentControl;
// on selection:
TreeViewItem item = (TreeViewItem)sender;
if(_currentControl != null)
{
_currentControl.Controls.Remove(_currentControl);
_currentControl.Dispose();
}
// if no type is bound to the node, just leave the panel empty
if (item.Tag == null)
return;
_currentControl = (UserControl)Activator.Create((Type)item.Tag);
Panel1.Controls.Add(_currentControl);
下一个问题是,“我想在控件中调用保存方法或 RequestClose 方法”。为此,您应该在控件上实现一个接口,并且当您切换节点时,只需尝试将 _currentusercontrol 转换为 IRequestClose 接口并调用例如 bool RequestClose(); 方法。
// on selection:
TreeViewItem item = (TreeViewItem)sender;
if(_currentControl != null)
{
// if the _currentControl supports the IRequestClose interface:
if(_currentControl is IRequestClose)
// cast the _currentControl to IRequestCode and call the RequestClose method.
if(!((IRequestClose)_currentControl).RequestClose())
// now the usercontrol decides whether the control is closed/disposed or not.
return;
_currentControl.Controls.Remove(_currentControl);
_currentControl.Dispose();
}
if (item.Tag == null)
return;
_currentControl = (UserControl)Activator.Create(item.Tag);
Panel1.Controls.Add(_currentControl);
但这将是下一步。
对我来说,常见的设计是左侧的经典树视图和右侧的“内容区域”。当用户在树视图中选择某些内容时,您会在内容区域中加载相关视图。在实现这些内容的方法有很多不同之后,例如,自动化会根据一个对象列表生成树视图,该对象包含要进行安装的视图类型,并创建一个通用的实例化器,何时选择项目以创建相关视图,不管怎样,背景还是一样的。要恢复,树视图,只需根据所选项目在内容区域中创建视图。(我在工作中见过几个这样的屏幕,大多数时候都是这样)
在检查了几个选项后,我的方法是继承 TabControl 组件,使控件的页面可以用作分页面板,并添加功能以使选项卡在运行时不显示。然后,通过创建一个依赖于 TabPages 的名为 Pages 的属性,我可以以语义正确的方式引用每个页面,从而能够将每个页面作为 Pages 集合的一部分进行管理,并且还可以通过文档资源管理器进行分层管理。
该代码还隐藏了与常规 TabControl 相关的设计时属性,但这与分页面板无关。如果有人感兴趣,下面是代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
namespace MyCustomControls
{
public class PagedPanel : TabControl
{
//------------------------------------------------------------------------------------------------
public PagedPanel()
{
base.Multiline = true;
base.Appearance = TabAppearance.Buttons;
base.ItemSize = new Size(0, 1);
base.SizeMode = TabSizeMode.Fixed;
base.TabStop = false;
}
//------------------------------------------------------------------------------------------------
protected override void WndProc(ref Message m)
{
// Hide tabs by trapping the TCM_ADJUSTRECT message
if (m.Msg == 0x1328 && !DesignMode) m.Result = (IntPtr)1;
else base.WndProc(ref m);
}
//------------------------------------------------------------------------------------------------
protected override void OnKeyDown(KeyEventArgs ke)
{
// Block Ctrl+Tab and Ctrl+Shift+Tab hotkeys
if (ke.Control && ke.KeyCode == Keys.Tab)
return;
base.OnKeyDown(ke);
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(true)]
public new bool Multiline
{
get { return base.Multiline; }
set { base.Multiline = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(TabAppearance.Buttons)]
public new TabAppearance Appearance
{
get { return base.Appearance; }
set { base.Appearance = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(typeof(Size), "0, 1")]
public new Size ItemSize
{
get { return base.ItemSize; }
set { base.ItemSize = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(TabSizeMode.Fixed)]
public new TabSizeMode SizeMode
{
get { return base.SizeMode; }
set { base.SizeMode = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
, DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new TabPageCollection TabPages
{
get { return base.TabPages; }
}
//------------------------------------------------------------------------------------------------
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[DefaultValue(false)]
public new bool TabStop
{
get { return base.TabStop; }
set { base.TabStop = value; Invalidate(); }
}
//------------------------------------------------------------------------------------------------
public TabPageCollection Pages
{
get { return base.TabPages; }
}
//------------------------------------------------------------------------------------------------
}
}
树视图将处理通过键或索引调用每个选项卡,这是一项相对微不足道的任务。为此,我使用前缀“tvn”命名树中的节点,然后将 PagedPanel 中的页面命名为相同但前缀为“pg”。因此,在树视图的 AfterSelect 事件中,我需要的只是当前节点的名称,并且我知道要显示哪个页面。