界面设计

您需要高效学习,找工作? 点击这里 白月黑羽实战班

点击这里,边看视频讲解,边学习以下内容

开发图形界面应用的时候, 重点之一,当然就是 界面设计

界面布局 (Layout)

从前面一章的例子中我们发现,界面开发,好像很简单:就是先了解各个控件的作用,然后拖拽摆放你需要的控件。

但我们发现如果窗体大小变化,控件没有相应的缩放,就会显得很不美观。

这里就需要我们学习界面设计的重点:界面布局 (Layout)


在 WinForms 中,实现控件随窗口缩放而自动调整的响应式布局,主要依赖 DockAnchor 属性,以及 FlowLayoutPanelTableLayoutPanel 这两个布局容器。

Anchor 属性

Anchor 属性定义了控件的边缘父容器边缘相对距离

设定了 Anchor 属性后,控件会在父容器缩放时相对于设计时的情况保持该控件和父容器指定边缘的距离不变

Anchor 属性 可以设置为多个边界的组合,比如 Top, LeftTop, Left, RightTop, Left, Right, Bottom 等等

如下图所示:

中间的这个All 是 Anchor 属性设置为 Top, Left, Right, Bottom,表示控件在父容器缩放时,始终保持与四个边的距离不变。


前面的例子程序:

输入框在左,按钮在右, 输入框应该为:Top, Bottom, Left, Right 按钮应该为:Right

输入框在左,按钮在右上, 输入框应该为:Top, Bottom, Left, Right 按钮应该为:Right,Up

输入框在上面,按钮在下面, 输入框应该为:Top, Bottom, Left, Right 按钮应该为:Bottom


Anchor属性经常和 MinimumSizeMaximumSize 属性一起使用,以确保控件在缩放时保持合适的大小。

比如:前面的例子程序,

输入框在上面,按钮在下面, 窗体缩小太多,可能会导致输入框消失,可以指定 输入框 MinimumSize 400,200

指定窗体 MinimumSize 500, 470

Dock 属性

Dock(停靠): Dock 属性让控件完全填充父容器的某一个边缘(上、下、左、右)或剩余的全部空间 (Fill)。

一旦新设定 Dock 属性,Anchor 属性将被忽略。

Dock 属性可以设置为以下值:


比如,Dock 属性值设置为 Bottom,就意味着:

- 控件停靠在父容器的底部,
- 扩展个控件宽度到父容器的宽度。

这时候我们没法手工(拖拽)设置它的宽度,margin等都没有效果,因为它的宽度已经由dock策略决定了。

但是我们却可以设置控件的高度,因为 `Dock` 值为 `Bottom` 或者 `top` 时,高度并不受策略限制。

前面的例子程序:

输入框在上,按钮在下,

输入框为: Dock Fill
按钮为: Dock Bottom, 调整 按钮的高度为 35px

当多个控件停靠在同一父容器中时,它们会按照 Z-order (Z顺序) 依次排列。

Z-order 可以通过上下文菜单的 Bring to FrontSend to Back 来调整。也可以通过 Document Outline 窗口来调整控件的次序。


Dock 对于创建主次分明的布局(如导航栏、状态栏和主内容区)非常有用。通常会使用 Panel 控件作为容器来组合使用 Dock

容器控件

容器(container) 控件, 是 WinForms 中用于组织和管理控件布局的特殊控件。

Visual StudioToolbox 中,容器控件放在 Container 类别中。

常用的布局容器有:

Panel

Panel 是最基本的容器控件,可以用来组织其他控件。它可以通过 Dock 属性停靠在窗体的任意边缘,或者使用 Anchor 属性来控制其大小和位置。

TableLayoutPanel

TableLayoutPanel 非常常用,它将区域划分为 行和列,形成一个 网格,控件可以放置在任意单元格中。

一般我做复杂界面的顶级区域设置,都会使用 TableLayoutPanel, 比如典型的网站布局,如下

就是 3行2列的网格布局,左侧是导航栏,右侧是内容区,顶部是标题栏,底部是页脚。


TableLayoutPanel 特点如下:


SplitContainer

SplitContainer 允许用户通过拖动分隔条来调整两个子控件的大小。

可以设置水平或垂直分割,适用于需要动态调整布局的场景。

比如上面网页布局的左侧导航栏和右侧内容区,可以使用 SplitContainer 来实现。


可以设置 Orientation 属性来选择水平或垂直分割。

Layout -> Splitter Width 可以设置 分割线宽度

要设置分割线的颜色比较麻烦一些,不能直接设置。可以设置整个 SplitContainer的背景色为你需要的分割线颜色, 然后设置每个子Panel 的 背景色为不同的颜色(比如白色)。


如果你分隔的超过2个分区,可以嵌套使用 SplitContainer


Dock 属性设置为 Fill的控件不能在设计器中拖动到 SplitContainer 中,可以在Document Outline 窗口中拖动。


TabControl

TabControl 用于创建选项卡式界面。它允许在同一窗口中组织多个页面,每个页面可以包含不同的控件和布局。


FlowLayoutPanel

FlowLayoutPanel 会将其中的控件按照指定的方向(从左到右、从右到左、从上到下或从下到上)依次排列。当一行或一列空间不足时,它会自动换行/换列。

适合用来展示一组内容, 比如 商品列表、图片库 等内容。

FlowLayoutPanel 的主要作用是作为布局容器,它会自动排列你添加到其中的子控件(比如 Button, Label, PictureBox 等):


通常要添加的控件都是动态的,比如从数据库中获取的商品列表,或者从文件中读取的图片列表。

这些数据源往往是一个 List<T>DataTable 或其他集合类型

要实现根据数据源动态创建控件并添加到 FlowLayoutPanel 中,你需要手动编写代码来完成这个过程。基本步骤如下:

  1. 遍历你的数据源(例如 List<T>DataTable 或其他集合)。
  2. 在循环中,为每一个数据项创建一个或多个新的控件实例
  3. 将当前数据项的信息赋值给新创建控件的属性(如 Text, Image, Tag 等)。
  4. 将新创建的控件添加到 FlowLayoutPanelControls 集合中。

假设你有一个字符串列表,想为每个字符串创建一个按钮并显示在 FlowLayoutPanel 中。

// 你的数据源
List<string> categories = new List<string> { "Electronics", "Books", "Clothing", "Home & Kitchen", "Sports" };

// 获取 FlowLayoutPanel 控件的引用 (假设在设计器中已命名为 flowLayoutPanel1)
// this.flowLayoutPanel1

// 清空面板中现有的所有控件,以防重复添加
this.flowLayoutPanel1.Controls.Clear();

// 遍历数据源
foreach (string categoryName in categories)
{
    // 1. 创建一个新的 Button 控件
    Button btn = new Button();

    // 2. 设置按钮的属性
    btn.Text = categoryName;
    btn.Tag = categoryName; // 可以用 Tag 属性存储相关数据
    btn.AutoSize = true;   // 让按钮大小自适应内容
    btn.Margin = new Padding(5); // 设置一些外边距,让布局更好看

    // (可选) 为按钮添加点击事件
    btn.Click += (sender, e) => {
        Button clickedButton = sender as Button;
        MessageBox.Show($"You clicked on: {clickedButton.Tag}");
    };

    // 3. 将创建的按钮添加到 FlowLayoutPanel 中
    this.flowLayoutPanel1.Controls.Add(btn);
}

如果每个数据项需要显示的布局比较复杂(例如,一张图片 + 一个标题 + 一段描述),最佳实践是:

  1. 创建一个自定义 UserControl,在其中设计好单个数据项的布局(比如包含一个 PictureBox 和两个 Label)。
  2. 为这个 UserControl 创建一个公共方法或属性,用于接收数据并更新其内部的控件。
  3. 在主窗体的循环中,创建这个 UserControl 的实例,调用方法或属性填充数据,然后将其添加到 FlowLayoutPanel 中。

这种方式能让你的代码结构更清晰、更易于维护。

控件样式

WinForms 没有像 Web CSS 那样的样式表(Stylesheet)系统。在 WinForms 中,控件的外观和感觉(Look and Feel)是通过直接设置其属性来实现的。这可以通过两种主要方式完成:

  1. 设计时 (Design-Time): 在 Visual Studio 的 属性 (Properties) 窗口中直观地设置。这是最常用、最便捷的方式。
  2. 运行时 (Run-Time): 在代码中动态地修改属性。这为实现动态样式、响应用户交互等提供了灵活性。

核心样式属性

以下是一些最常用的,用于控制控件外观和布局的属性:

样式概念WinForms 属性说明
外观
文本颜色ForeColor设置控件的前景色,通常是文本、边框等元素的颜色。
背景色BackColor设置控件的背景色。
背景图BackgroundImage设置控件的背景图片。
背景图布局BackgroundImageLayout控制背景图的平铺、拉伸、居中等方式。
字体Font一个复合属性,可以设置字体名称、大小、样式(粗体、斜体、下划线等)。
边框样式BorderStyle某些控件(如 Panel, TextBox, Label)有此属性,可设为 None, FixedSingle, Fixed3D
平面样式FlatStyle按钮类控件特有,用于创建更现代的外观,可选值包括 Flat, Popup, Standard, System
布局
外边距Margin控件 外部 的空间,定义其与相邻元素的最小距离。
内边距Padding控件 内部 的空间,定义其边框与内容(如文本)之间的距离。
大小Size一个复合属性,包含 WidthHeight,定义控件的尺寸。
定位Location一个复合属性,包含 XY,定义控件左上角相对于其父容器的位置。
停靠Dock将控件停靠到其父容器的某条边(上/下/左/右)或填充整个剩余空间。
锚定Anchor使控件在父容器大小改变时,能与其父容器的边框保持固定的相对距离。

Margin 与 Padding

MarginPadding 是布局中至关重要的两个属性,它们共同决定了控件周围的空白区域。

下图清晰地展示了 Margin 和 Padding 的区别:

在 Visual Studio 设计器中,当你拖动控件时,会自动显示对齐线(snaplines),这些线就是基于 Margin 属性来帮助你快速对齐控件,保持一致的间距。


在代码中设置样式

几乎所有在属性窗口中能设置的样式,都可以在代码中进行修改。这通常在窗体的构造函数、Load 事件处理函数或响应其他事件时完成。

// 示例:在 Form_Load 事件中初始化按钮样式
private void MyForm_Load(object sender, EventArgs e)
{
    // 设置按钮的样式
    myButton.Text = "Click Me";
    myButton.BackColor = Color.CornflowerBlue;
    myButton.ForeColor = Color.White;
    myButton.Font = new Font("Microsoft YaHei UI", 10, FontStyle.Bold);
    myButton.Size = new Size(150, 40);
    
    // 设置边距和内边距 (需要 System.Windows.Forms.Padding 对象)
    myButton.Margin = new Padding(10); // 四周外边距均为10
    myButton.Padding = new Padding(5);  // 四周内边距均为5

    // 设置面板的边框
    myPanel.BorderStyle = BorderStyle.FixedSingle;
    myPanel.BackColor = Color.WhiteSmoke;
}

模拟 CSS 选择器和伪状态

虽然 WinForms 没有原生的 CSS 引擎,但我们可以通过编写代码来模拟其核心概念,实现更高级和可维护的样式管理。

模拟选择器

模拟伪状态

伪状态(如 :hover, :active)描述了控件因用户交互而进入的特殊状态。这需要通过订阅控件的事件来模拟。


样式继承

在 WinForms 中,一些被称为“环境属性”(Ambient Properties)的样式(如 Font, BackColor, ForeColor)可以从父容器继承。

例如,如果你没有为 Button 明确设置 Font,它会自动使用其父容器(如 FormPanel)的 Font 属性。如果父容器也没有设置,它会继续向上寻找,直到找到一个设置了该属性的祖先,或者使用默认值。

这非常有用,你只需设置顶层容器(如窗体 Form)的 FontBackColor,就可以为整个窗体上的大部分控件提供一个统一的基础样式。

注意:一旦你为子控件明确设置了某个环境属性(例如,在属性窗口中修改了按钮的字体),这个显式设置的值将 覆盖 从父容器继承的值。


菜单、工具栏和状态栏

在经典的桌面应用中,菜单栏、工具栏和状态栏是构成窗体框架的核心元素。WinForms 提供了一套功能强大且统一的“条带”控件来实现它们,主要包括 MenuStrip, ToolStrip, StatusStrip, 和 ContextMenuStrip


MenuStrip (主菜单栏)

MenuStrip 用于创建应用程序的顶级主菜单,如“文件”、“编辑”、“视图”等。它是用户发现应用程序所有功能的主要入口。

核心组件: MenuStrip 本身是容器,其内容由 ToolStripMenuItem (菜单项) 和 ToolStripSeparator (分隔线) 构成。

设计时操作:

  1. 从工具箱拖动一个 MenuStrip 到窗体上,它会自动停靠在顶部。
  2. 在设计器中出现的 "在此处键入" 的地方输入菜单项文本。使用 & 符号可以创建访问键(快捷键),例如 文件(&F),用户按 Alt+F 即可打开该菜单。
  3. 可以继续添加子菜单项,形成层级菜单。

常用 ToolStripMenuItem 属性:

代码示例:

// “文件” -> “退出” 菜单项的点击事件
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
    // 关闭应用程序
    Application.Exit();
}

// “视图” -> “工具栏” 菜单项的点击事件
private void toolbarToolStripMenuItem_Click(object sender, EventArgs e)
{
    // sender 就是被点击的菜单项
    ToolStripMenuItem menuItem = sender as ToolStripMenuItem;

    // 切换工具栏的可见性
    mainToolStrip.Visible = menuItem.Checked;
}

ToolStrip (工具栏)

ToolStrip 用于创建包含常用功能快捷按钮的工具栏,提供对常用操作的快速访问。

设计时操作:

  1. 从工具箱拖动一个 ToolStrip 到窗体上。
  2. 使用控件右上角的小箭头或在设计器上直接点击,可以添加不同类型的项。

常用 ToolStrip:

常用属性:

ToolStripMenuStrip 可以通过 ToolStripManager 进行合并,这是实现MDI(多文档界面)等复杂应用的关键功能。


StatusStrip (状态栏)

StatusStrip 通常停靠在窗体底部,用于显示应用程序的当前状态、提示信息或任务进度。

常用 StatusStrip:

在代码中更新状态:

// 在代码中更新状态栏文本
statusLabel.Text = "Ready";

// 更新进度条
progressBar.Minimum = 0;
progressBar.Maximum = 100;
progressBar.Value = 50;

ContextMenuStrip (上下文菜单)

ContextMenuStrip 用于创建右键菜单。它在设计时不可见,而是存在于窗体下方的组件栏中。

设计时操作:

  1. 从工具箱拖动一个 ContextMenuStrip 到窗体上。
  2. 在组件栏中选中它,然后在窗体上就会出现一个菜单编辑器,可以像编辑 MenuStrip 一样添加菜单项。
  3. 选中需要关联此右键菜单的控件(如一个 TextBoxListView),然后在属性窗口中找到 ContextMenuStrip 属性,从下拉列表中选择刚刚创建的 ContextMenuStrip 即可。

动态显示:

有时右键菜单的内容需要根据点击的位置动态改变。可以在控件的 MouseDown 事件中判断是否为右键点击,然后动态构建或显示菜单。

private void myListView_MouseUp(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Right)
    {
        // 检查鼠标是否在某个列表项上
        ListViewItem item = myListView.GetItemAt(e.X, e.Y);
        if (item != null)
        {
            // 选中该项
            item.Selected = true;
            // 可以在这里根据 item 的内容修改 contextMenuStrip 的菜单项
            // ...
            // 在鼠标位置显示右键菜单
            itemContextMenuStrip.Show(myListView, e.Location);
        }
    }
}

ToolStripContainer (专业停靠容器)

当你的应用需要多个工具栏,或者希望用户能像 Visual Studio 一样自定义工具栏布局时,就需要使用 ToolStripContainer

它是一个专业的布局控件,预先在窗体内划分好了五个区域:

  1. 顶部停靠区 (TopToolStripPanel)
  2. 底部停靠区 (BottomToolStripPanel)
  3. 左侧停靠区 (LeftToolStripPanel)
  4. 右侧停靠区 (RightToolStripPanel)
  5. 中央内容区 (ContentPanel)

核心功能:

使用流程:

  1. 先拖一个 ToolStripContainer 到窗体上,并将其 Dock 属性设置为 Fill
  2. MenuStripToolStrip 拖到 ToolStripContainer 的上、下、左、右四个专用停靠区里。
  3. 将应用程序的核心内容控件(如 Panel, TabControl 等)放置在中央的 ContentPanel 里。

实用技巧

一个练习

请用前面学到的知识,完成下面的 HTTP API 接口测试工具的 界面设计。

您需要高效学习,找工作? 点击这里 白月黑羽实战班