本课学习目标
- 认识 MenuStrip、ToolStrip 和 StatusStrip。
- 建立 File、Edit、Format、Help 等主菜单。
- 使用 ToolStripMenuItem 建立菜单命令。
- 设置菜单快捷键 ShortcutKeys。
- 使用分隔线整理菜单。
- 建立 ToolStripButton 常用操作。
- 让菜单和工具栏重用同一组方法。
- 使用 ToolStripStatusLabel 显示状态。
- 在 TextChanged 事件中更新字数。
- 使用项目资源为工具栏按钮提供图标。
- 完成专业版迷你文本编辑器。
20.1 三种常见界面组件
MenuStrip、ToolStrip 和 StatusStrip 各自负责不同任务,组合起来可以让 Windows Forms 应用更接近专业桌面软件。
菜单栏
按类别组织完整功能,例如 File、Edit、Format、Help。
工具栏
为打开、保存、剪切等常用功能提供快速按钮。
状态栏
显示当前文件、字符数、状态消息等持续信息。
20.2 建立 MenuStrip
从 Toolbox 拖入:
MenuStrip
将名称设为:
menuMain
然后建立主菜单:
File
Edit
Format
Help
每一个项目实际上都是 ToolStripMenuItem。
20.3 建立子菜单项目
File 菜单可以包含:
New
Open...
Save...
----------------
Exit
建议 Name:
| 菜单文字 | Name |
|---|---|
| New | mnuFileNew |
| Open... | mnuFileOpen |
| Save... | mnuFileSave |
| Exit | mnuFileExit |
20.4 使用菜单分隔线
菜单中的横线是 ToolStripSeparator,用来把相关命令分组。
New
Open
Save
------------
Exit
20.5 设置 ShortcutKeys
桌面应用常用快捷键:
Ctrl+N建立新文件Ctrl+O打开文件Ctrl+S保存文件Ctrl+X剪切Ctrl+C复制Ctrl+V粘贴在 Properties Window 中选择菜单项目,例如 mnuFileSave,设置:
ShortcutKeys = Ctrl + S
设置之后,即使用户不打开菜单,也可以直接按 Ctrl+S 触发菜单命令。
20.6 Edit 菜单
TextBox 本身已经提供:
txtEditor.Cut()
txtEditor.Copy()
txtEditor.Paste()
txtEditor.SelectAll()
因此菜单事件可以非常简单:
Private Sub mnuEditCut_Click(
sender As Object,
e As EventArgs) Handles mnuEditCut.Click
txtEditor.Cut()
End Sub
复制:
Private Sub mnuEditCopy_Click(
sender As Object,
e As EventArgs) Handles mnuEditCopy.Click
txtEditor.Copy()
End Sub
粘贴:
Private Sub mnuEditPaste_Click(
sender As Object,
e As EventArgs) Handles mnuEditPaste.Click
txtEditor.Paste()
End Sub
20.7 建立 ToolStrip
从 Toolbox 加入:
ToolStrip
名称:
toolMain
然后加入 ToolStripButton:
| 功能 | Name | 建议 Text |
|---|---|---|
| 新建 | tsbNew | New |
| 打开 | tsbOpen | Open |
| 保存 | tsbSave | Save |
| 剪切 | tsbCut | Cut |
| 复制 | tsbCopy | Copy |
| 粘贴 | tsbPaste | Paste |
20.8 工具栏图标
ToolStripButton 可以设置 Image。固定图标适合加入项目 Resources:
tsbOpen.Image = My.Resources.OpenIcon
tsbSave.Image = My.Resources.SaveIcon
也可以直接在 Designer 中为 ToolStripButton 的 Image 属性选择资源。
20.9 菜单和工具栏不要复制相同代码
如果菜单 Open 和工具栏 Open 都要打开文件,不应该复制两份完整逻辑。
先建立方法:
Private Sub OpenTextFile()
dlgOpen.Filter =
"文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"
If dlgOpen.ShowDialog() =
DialogResult.OK Then
txtEditor.Text =
File.ReadAllText(
dlgOpen.FileName)
currentFilePath =
dlgOpen.FileName
UpdateStatus(
"已打开文件。")
End If
End Sub
菜单调用:
Private Sub mnuFileOpen_Click(
sender As Object,
e As EventArgs) Handles mnuFileOpen.Click
OpenTextFile()
End Sub
工具栏也调用:
Private Sub tsbOpen_Click(
sender As Object,
e As EventArgs) Handles tsbOpen.Click
OpenTextFile()
End Sub
20.10 建立 StatusStrip
从 Toolbox 加入:
StatusStrip
名称:
statusMain
再加入两个 ToolStripStatusLabel:
lblStatus
lblCharCount
例如:
lblStatus.Text = "Ready"
lblCharCount.Text = "Characters: 0"
20.11 自定义 UpdateStatus 方法
Private Sub UpdateStatus(
message As String)
lblStatus.Text =
message
End Sub
以后各个功能都可以调用:
UpdateStatus("文件保存成功。")
20.12 自动更新字符数量
TextBox 的 TextChanged 事件会在文字改变时触发:
Private Sub txtEditor_TextChanged(
sender As Object,
e As EventArgs) Handles txtEditor.TextChanged
lblCharCount.Text =
$"Characters: {txtEditor.Text.Length}"
End Sub
这样用户输入、粘贴、打开文件或清除内容时,状态栏都会自动更新。
20.13 让状态栏项目分开排列
如果希望左边显示状态、右边显示字数,可以把第一个 ToolStripStatusLabel 的:
Spring = True
并将:
TextAlign = MiddleLeft
这样第一个 Label 会自动占用剩余空间,把第二个 Label 推到右侧。
20.14 实作:Mini Text Editor Pro 2026
建立新的 Windows Forms 项目:
MiniTextEditorPro2026
这个项目延续 Lesson 19,但将按钮式界面升级为菜单栏、工具栏和状态栏结构。
支持菜单、工具栏、状态栏、打开、保存、字体、颜色和快捷键。
Form1 主要组件:
| 组件 | Name | 用途 |
|---|---|---|
| MenuStrip | menuMain | File / Edit / Format / Help |
| ToolStrip | toolMain | 常用快捷按钮 |
| TextBox | txtEditor | Multiline = True;Dock = Fill |
| StatusStrip | statusMain | 状态和字数 |
| ToolStripStatusLabel | lblStatus | 当前状态;Spring = True |
| ToolStripStatusLabel | lblCharCount | 字符数量 |
| OpenFileDialog | dlgOpen | 打开文件 |
| SaveFileDialog | dlgSave | 保存文件 |
| FontDialog | dlgFont | 选择字体 |
| ColorDialog | dlgColor | 选择文字颜色 |
Form1.vb 顶部
Imports System.IO
Public Class Form1
Private currentFilePath As String = ""
新建文件方法
Private Sub NewDocument()
If txtEditor.Text.Length > 0 Then
Dim result As DialogResult =
MessageBox.Show(
"建立新文件会清除当前内容。是否继续?",
"新建文件",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question)
If result <> DialogResult.Yes Then
Exit Sub
End If
End If
txtEditor.Clear()
currentFilePath = ""
Me.Text =
"Mini Text Editor Pro 2026 - Untitled"
UpdateStatus(
"已建立新文件。")
End Sub
打开文件方法
Private Sub OpenTextFile()
dlgOpen.Filter =
"文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"
dlgOpen.Title =
"打开文本文件"
If dlgOpen.ShowDialog() <>
DialogResult.OK Then
Exit Sub
End If
txtEditor.Text =
File.ReadAllText(
dlgOpen.FileName)
currentFilePath =
dlgOpen.FileName
Me.Text =
"Mini Text Editor Pro 2026 - " &
Path.GetFileName(
currentFilePath)
UpdateStatus(
"文件打开成功。")
End Sub
保存文件方法
Private Sub SaveTextFile()
If String.IsNullOrWhiteSpace(
currentFilePath) Then
dlgSave.Filter =
"文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"
dlgSave.DefaultExt =
"txt"
dlgSave.FileName =
"note.txt"
If dlgSave.ShowDialog() <>
DialogResult.OK Then
Exit Sub
End If
currentFilePath =
dlgSave.FileName
End If
File.WriteAllText(
currentFilePath,
txtEditor.Text)
Me.Text =
"Mini Text Editor Pro 2026 - " &
Path.GetFileName(
currentFilePath)
UpdateStatus(
"文件保存成功。")
End Sub
菜单事件
Private Sub mnuFileNew_Click(
sender As Object,
e As EventArgs) Handles mnuFileNew.Click
NewDocument()
End Sub
Private Sub mnuFileOpen_Click(
sender As Object,
e As EventArgs) Handles mnuFileOpen.Click
OpenTextFile()
End Sub
Private Sub mnuFileSave_Click(
sender As Object,
e As EventArgs) Handles mnuFileSave.Click
SaveTextFile()
End Sub
工具栏事件重用相同方法
Private Sub tsbNew_Click(
sender As Object,
e As EventArgs) Handles tsbNew.Click
NewDocument()
End Sub
Private Sub tsbOpen_Click(
sender As Object,
e As EventArgs) Handles tsbOpen.Click
OpenTextFile()
End Sub
Private Sub tsbSave_Click(
sender As Object,
e As EventArgs) Handles tsbSave.Click
SaveTextFile()
End Sub
剪切、复制、粘贴
Private Sub mnuEditCut_Click(
sender As Object,
e As EventArgs) Handles mnuEditCut.Click
txtEditor.Cut()
End Sub
Private Sub mnuEditCopy_Click(
sender As Object,
e As EventArgs) Handles mnuEditCopy.Click
txtEditor.Copy()
End Sub
Private Sub mnuEditPaste_Click(
sender As Object,
e As EventArgs) Handles mnuEditPaste.Click
txtEditor.Paste()
End Sub
ToolStrip 的 Cut、Copy、Paste 按钮也可以直接调用对应 TextBox 方法,或者进一步拆成共用 Sub。
字体菜单
Private Sub mnuFormatFont_Click(
sender As Object,
e As EventArgs) Handles mnuFormatFont.Click
dlgFont.Font =
txtEditor.Font
If dlgFont.ShowDialog() =
DialogResult.OK Then
txtEditor.Font =
dlgFont.Font
UpdateStatus(
"字体已经更新。")
End If
End Sub
文字颜色菜单
Private Sub mnuFormatColor_Click(
sender As Object,
e As EventArgs) Handles mnuFormatColor.Click
dlgColor.Color =
txtEditor.ForeColor
If dlgColor.ShowDialog() =
DialogResult.OK Then
txtEditor.ForeColor =
dlgColor.Color
UpdateStatus(
"文字颜色已经更新。")
End If
End Sub
状态更新方法
Private Sub UpdateStatus(
message As String)
lblStatus.Text =
message
End Sub
字符数自动更新
Private Sub txtEditor_TextChanged(
sender As Object,
e As EventArgs) Handles txtEditor.TextChanged
lblCharCount.Text =
$"Characters: {txtEditor.Text.Length}"
End Sub
退出菜单
Private Sub mnuFileExit_Click(
sender As Object,
e As EventArgs) Handles mnuFileExit.Click
Dim result As DialogResult =
MessageBox.Show(
"确定要退出程序吗?",
"退出",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question)
If result =
DialogResult.Yes Then
Me.Close()
End If
End Sub
20.15 程序结构为什么更好?
用户可以按 File、Edit、Format 找到所有命令。
最常用功能不需要层层打开菜单。
OpenTextFile 和 SaveTextFile 不属于某一个按钮专用。
告诉用户文件是否保存,以及当前字符数。
熟练用户可以直接 Ctrl+S、Ctrl+O 等操作。
20.16 初学者常见错误
菜单和工具栏复制整段代码
相同命令应该调用同一个自定义方法,避免维护两份逻辑。
菜单项目 Name 太模糊
不要全部叫 ToolStripMenuItem1。建议 mnuFileOpen、mnuEditCopy 等。
快捷键冲突
同一个窗体不要让多个命令使用同一组快捷键。
状态栏永远显示 Ready
重要操作完成后应调用 UpdateStatus,让用户知道程序发生了什么。
20.17 小练习
- 加入 Edit → Select All,并设置 Ctrl+A。
- 为 ToolStrip 的 New、Open、Save 加入 Resources 图标。
- 加入状态栏 Label 显示当前文件名称。
- 加入 Format → Background Color,改变 txtEditor.BackColor。
- 在 Help 菜单加入 About,使用 MessageBox.Show 显示程序名称和版本。
- 把 Cut、Copy、Paste 也拆成共用 Sub,让菜单和工具栏完全重用。
- 加入 ToolStripSeparator,把文件操作与编辑操作分开。
本课复习
- MenuStrip、ToolStrip 和 StatusStrip 分别有什么用途?
- 菜单中的单个项目通常是什么类型?
- ToolStripSeparator 的作用是什么?
- 怎样为菜单设置 Ctrl+S?
- 为什么菜单 Open 与工具栏 Open 不应该复制两份打开文件代码?
- ToolStripButton 的 Image 适合从哪里取得?
- ToolStripStatusLabel 可以显示哪些类型的状态?
- TextChanged 为什么适合用来更新字符数?
- StatusLabel 的 Spring = True 有什么效果?
- 为什么专业桌面应用通常同时提供菜单、工具栏和快捷键?