WinForm 小坑合集
2022 年 1 月 19 日 · 2 分钟读完
目录
几篇 WinForm 的短笔记,合成一篇方便查。
窗体的几个基本属性
图标
Icon 属性只支持 .ico 文件:

更换前后:

这只是窗体左上角和任务栏的图标。exe 文件本身的图标在 项目属性 → 应用程序 → 图标和清单 里设置。
边框样式 FormBorderStyle

| 值 | 含义 | 样式 |
|---|---|---|
| Fixed3D | 固定的三维边框 | ![]() |
| FixedDialog | 固定的对话框粗边框 | ![]() |
| FixedSingle | 固定的单线边框 | ![]() |
| FixedToolWindow | 不可调大小的工具窗口 | ![]() |
| None | 无边框 | ![]() |
| Sizable | 可调大小(默认) | ![]() |
| SizableToolWindow | 可调大小的工具窗口 | ![]() |
显示位置 StartPosition

常用的是 CenterScreen(屏幕居中)和 CenterParent(父窗体居中,对 ShowDialog 弹出的窗体有效)。设置成 Manual 时,由 Location 决定位置。
ComboBox 清空后的 SelectedIndex
Items 被清空后再设置选中项,只能设 SelectedIndex = -1,表示什么都不选;设成 0 会抛越界异常。
反过来也有个坑:DropDownStyle 是 DropDown(可输入)时,SelectedIndex = -1 有时清不掉文本框里的字,需要再设一次 comboBox1.Text = "";。
窗体隐藏时不刷新
工业项目里数据一直在变,变化时推送给界面更新。但切到别的页面、或者窗体被隐藏时,没必要继续刷新它,否则白白占用界面线程,影响正在显示的页面。
几种判断可见的方法:
bool v = form.Visible; // 窗体本身是否可见
Form f = this.FindForm(); // 控件内部:找到所在的窗体
bool v2 = f != null && f.Visible;
bool v3 = this.Parent.Visible; // 控件放在 Panel、GroupBox、TabPage 里时,看父容器
注意:Visible 只要自己和所有父级都可见才是 true。TabControl 里没被选中的 TabPage,它的 Visible 是 false,可以直接用来判断「这一页现在有没有被看着」。
更省事的做法是在更新入口统一判断:
void OnDataChanged(Data d)
{
if (!this.Visible) { _dirty = true; return; } // 先记一下,不刷新
Render(d);
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
if (Visible && _dirty) { Render(_latest); _dirty = false; } // 重新可见时补一次
}
无边框窗体最大化遮住任务栏
FormBorderStyle = None 的窗体最大化时会盖住整个屏幕,包括任务栏。设置最大化时的范围为屏幕的工作区(不含任务栏):
this.MaximizedBounds = Screen.PrimaryScreen.WorkingArea;
// 或者限制最大尺寸
this.MaximumSize = Screen.PrimaryScreen.WorkingArea.Size;
多显示器时,用 Screen.FromControl(this).WorkingArea 取窗体所在的那块屏幕。工控机上需要真正全屏、盖住任务栏的,反而正好利用这个特性。
只允许运行一个实例
很多上位机软件不允许多开(会抢串口、抢相机)。原来的做法是按进程名查找:
static Process GetRunningInstance()
{
Process current = Process.GetCurrentProcess();
foreach (Process item in Process.GetProcessesByName(current.ProcessName))
{
if (item.Id != current.Id) return item;
}
return null;
}
[DllImport("User32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow);
[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
static void Main()
{
Process running = GetRunningInstance();
if (running != null)
{
ShowWindowAsync(running.MainWindowHandle, 3); // 3 = SW_SHOWMAXIMIZED
SetForegroundWindow(running.MainWindowHandle);
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmLoading());
}
按进程名判断有漏洞:exe 改个名字就能多开,同名的别的程序也会被误判。更可靠的是用命名互斥体:
static void Main()
{
using (var mutex = new Mutex(true, @"Global\DP_MyApp_SingleInstance", out bool createdNew))
{
if (!createdNew)
{
MessageBox.Show("程序已经在运行。");
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmMain());
}
}
Global\ 前缀让互斥体在所有用户会话间生效(远程桌面多用户时也只能开一个)。想同时把已有窗口调到前台,可以把上面的 ShowWindowAsync 和互斥体结合起来。
ShowWindowAsync 的第二个参数常用值(微软文档):
| 值 | 名称 | 效果 |
|---|---|---|
| 0 | SW_HIDE | 隐藏窗口 |
| 1 | SW_SHOWNORMAL | 激活并显示,最小化或最大化时还原 |
| 2 | SW_SHOWMINIMIZED | 激活并最小化 |
| 3 | SW_SHOWMAXIMIZED | 激活并最大化 |
| 5 | SW_SHOW | 激活,按当前大小和位置显示 |
| 6 | SW_MINIMIZE | 最小化,激活下一个顶层窗口 |
| 9 | SW_RESTORE | 激活并还原,还原最小化的窗口时用这个 |
把已经打开的程序调到前台,一般用 9(SW_RESTORE)比 3 更合适,不会改变用户原来的窗口大小。
原文首发于 blog.csdn.net






