前言
有時候我們可能需要將用戶上一次彈窗的大小和位置自動記憶和還原,讓用戶有一種賓至如歸的感覺。
窗體起始位置控制
對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);
}