温故知新,遇见WPF/WinForms,自动记忆和还原WinFroms窗体大小和位置及状态
前言
有时候我们可能需要将用户上一次弹窗的大小和位置自动记忆和还原,让用户有一种宾至如归的感觉。
窗体起始位置控制
对WinFroms窗体而言,默认起始位置是在左上角的。但是可以通过System.Windows.Forms.Form.StartPosition
来指定,存在如下几个选项:
属性值 | 含义 |
---|---|
Manual |
手动指定位置 |
CenterScreen |
使其屏幕居中 |
WindowsDefaultLocation |
默认位置,尺寸在窗体中设置 |
WindowsDefaultBounds |
默认位置、默认尺寸 |
CenterParent |
在父窗体中居中 |
指定窗体位置的本质
虽然窗体默认有Left
、Top
、Right
、Bottom
四个属性,但是实际只有前两个是可以设置的,后面两个是只读的。
public int Left
{
get
{
return x;
}
set
{
SetBounds(value, y, width, height, BoundsSpecified.X);
}
}
我们常说的Location
值实际上就是指的Left
和Top
的值。
Location = new Point(100, 200);
Left = 100;
Top = 200;
指定窗体的宽度和高度
完成了对窗体位置的指定之后,接下来就是设置窗体的宽度和高度了。
Width = 600;
Height = 300;
如果有需要,还可以设置MaximumSize
和MinimumSize
。
获取不含任务栏的桌面工作区
var WorkingArea = Screen.PrimaryScreen?.WorkingArea ?? SystemInformation.WorkingArea;
var width = WorkingArea.Width;
var height = WorkingArea.Height;
置顶窗体状态
可以通过System.Windows.Forms.Form.WindowState
来指定,存在如下几个选项:
属性值 | 含义 |
---|---|
Normal |
正常大小 |
Minimized |
最小化 |
Maximized |
最大化 |
窗体置顶
TopMost = true;
实现思路
存储窗体信息
public class WindowInfo
{
public FormWindowState WindowState;
public Point Location = new Point(int.MinValue, int.MinValue);
public Size ClientSize;
public Size Size;
public SizeF DPI = new SizeF(96F, 96F);
}
这里也准备一个本地配置文件来保存和读取。
const string WindowInfoConfigFile = "windowinfo.json";
在关闭时记住位置
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
MemoryWindowInfo();
}
private void MemoryWindowInfo()
{
var bounds = WindowState == FormWindowState.Normal ? DesktopBounds : CurrentBounds;
var clientSize = WindowState == FormWindowState.Normal ? ClientSize : CurrentClientSize;
var windowInfo = new WindowInfo();
windowInfo.Location = bounds.Location;
windowInfo.Size = bounds.Size;
windowInfo.ClientSize = clientSize;
windowInfo.DPI = GetDpi();
windowInfo.WindowState = WindowState;
File.WriteAllText(WindowInfoConfigFile, JsonConvert.SerializeObject(windowInfo));
}
这里引入了CurrentBounds
和CurrentClientSize
上下文变量,原因是,当窗体处于最小化时,这时候获取的位置和大小就是有问题的,这里我们需要使用这个上下文变量来托管下正常模式下的值。
Rectangle CurrentBounds;
Size CurrentClientSize;
protected override void OnLocationChanged(EventArgs e)
{
base.OnLocationChanged(e);
if (WindowState == FormWindowState.Normal)
{
CurrentBounds.Location = this.Location;
CurrentClientSize = this.ClientSize;
}
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (WindowState == FormWindowState.Normal)
{
CurrentBounds.Size = this.Size;
CurrentClientSize = this.ClientSize;
}
}
在加载时还原位置和大小
public XXXXXXXXForm()
{
InitializeComponent();
RestoreWindowInfo();
}
private void RestoreWindowInfo()
{
WindowInfo windowInfo;
if (File.Exists(WindowInfoConfigFile))
{
windowInfo = JsonConvert.DeserializeObject<WindowInfo>(File.ReadAllText(WindowInfoConfigFile));
}
else
{
windowInfo = new WindowInfo();
}
var memoryDPI = windowInfo.DPI;
if (memoryDPI.Width == 0F)
memoryDPI.Width = 96F;
if (memoryDPI.Height == 0F)
memoryDPI.Height = 96F;
var currentDPI = GetDpi();
var factor = new SizeF(currentDPI.Width / memoryDPI.Width, currentDPI.Height / memoryDPI.Height);
var loc = ScaleUtils.Scale(windowInfo.Location, factor);
var size = ScaleUtils.Scale(windowInfo.Size, factor);
var clientSize = ScaleUtils.Scale(windowInfo.ClientSize, factor);
if (loc.X > int.MinValue && loc.Y > int.MinValue)
{
StartPosition = FormStartPosition.Manual;
}
else
{
loc = this.DesktopLocation;
}
if (!clientSize.IsEmpty)
size = SizeFromClientSize(clientSize);
if (size.IsEmpty)
size = this.Size;
var bounds = new Rectangle(loc, size);
var workArea = Screen.GetWorkingArea(bounds);
var intersectRect = Rectangle.Intersect(workArea, bounds);
var minWinSize = SystemInformation.MinimumWindowSize;
if (intersectRect.Width <= minWinSize.Width || intersectRect.Height < minWinSize.Height)
{
// 如果保存的位置不在工作区内,由默认放到主屏显示
var mainWorkArea = Screen.PrimaryScreen?.WorkingArea ?? SystemInformation.WorkingArea; // Screen.PrimaryScreen 有可能为空
bounds.Location = mainWorkArea.Location;
}
this.DesktopBounds = bounds;
this.WindowState = windowInfo.WindowState;
}
关键的帮助类
获取当前DPI
private SizeF GetDpi()
{
HandleRef hDC = new HandleRef(null, Win32NativeMethods.GetDC(default(HandleRef)));
int deviceCaps = Win32NativeMethods.GetDeviceCaps(hDC, 88);
int deviceCaps2 = Win32NativeMethods.GetDeviceCaps(hDC, 90);
Size result = new Size(deviceCaps, deviceCaps2);
Win32NativeMethods.ReleaseDC(default(HandleRef), hDC);
return result;
}
根据缩放值计算
public static class ScaleUtils
{
public static Point Scale(Point point, SizeF factor)
{
return new Point((int)Math.Round((float)point.X * factor.Width, MidpointRounding.AwayFromZero), (int)Math.Round((float)point.Y * factor.Height, MidpointRounding.AwayFromZero));
}
public static Size Scale(Size size, SizeF factor)
{
return new Size((int)Math.Round((float)size.Width * factor.Width, MidpointRounding.AwayFromZero), (int)Math.Round((float)size.Height * factor.Height, MidpointRounding.AwayFromZero));
}
}
获取系统信息的API
public static class Win32NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetDC(HandleRef hWnd);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
public static extern int GetDeviceCaps(HandleRef hDC, int nIndex);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int ReleaseDC(HandleRef hWnd, HandleRef hDC);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2019-09-15 使用NATAPP(基于ngrok)搭建内外网穿透代理、便利微信调试、局域网外网代理