WinFrom窗口始终置顶于桌面

WinFrom窗口始终置顶于桌面 – WhiteNight's Site

听起来像是比较奇怪的需求。

前情提要

举个栗子

举个例子,你突然想记录自己的每一局,甚至每一回合的实际游戏时长。然而每次记录前你要么在屏幕外面掐个秒表,要么到点了切窗口切到计时器手动停止。这么做很累,效率也很低,而且测量还不一定准确。

然后为了追求完美和精确的你接触了WinForm和dotnet,你在想:“为什么不自己写一个计时器呢?”。只要在游戏过程中它能永远保持在屏幕的顶层,不管打开什么窗口、什么视频,这个计时器都不会被任何窗口给覆盖住。这样你在游戏中时刻都能盯着这个计时器。等到点了直接截个图不就能获取到时间戳了吗?

写者注

不包括Windows左下角的开始菜单。这东西的不知道是优先级还是layer啥的是真高啊,什么窗口拖过去都会被遮挡。

方案一

TopMost

第一种方法很简单粗暴,直接在InitializeComponent的时候,把TopMost设置为true,完事。

private void InitializeComponent()
 {
           ...
           this.TopMost = true;
           ...
}

你debug之后,发现一切都很正常。你把debug出来的exe发到其他电脑上,再启动你的winform程序,会发现它已经可以置顶显示在桌面上了,并且不会被其他窗口——至少不会被大部分窗口遮挡。

However,你可能会发现当你点击任务栏的其他窗口的时候,你的计时器程序就会被“藏到后面”。用更好理解的方式来说,就是你的计时器程序“等级不够”。假设最高层是-1。你刚打开计时器的时候,由于聚焦的是计时器的窗口,此时计时器的等级是0。但当你聚焦其他窗口的时候,计时器的等级可能就变成了1。

而且你发现当你启动某些应用,比如VLC播放器的时候,这个计时器也有可能会被遮挡。这很明显不符合我们“计时器窗口始终置顶于桌面的需求”。

方案二

user32.dll

方案一很简单,接下来看个stack overflow上的解决方案。

如果你发现TopMost在某些情况下不生效,或者是有时候窗口仍然会被其他东西遮挡,又或者是单独上方案二也不能保证置顶;那可以方案一和二一起上。我自己试过了,目前还没发现会被非系统窗口遮挡的情况。

方案二简单来说,是通过通过委托去监听Windows系统前端页面的变化,如果当前窗口没有置顶,通过调用回调函数去调Windows操作系统的API,来实现窗口的置顶。user32.dll的话主要是提供管理窗口和对话框等用户界面元素的函数。

那怎么通过.NET去调用本地平台的函数?以下是gpt4的解释:

P/Invoke(Platform Invocation Services)是.NET框架中的一种技术,它允许托管代码(如C#或VB.NET编写的代码)调用本地平台上的非托管函数,这些函数通常是通过C/C++在DLL中实现的。通过P/Invoke,.NET程序可以调用Windows操作系统的API函数,或者其他任何提供了C风格导出函数的本地库。
P/Invoke的工作原理是在托管代码中声明外部函数,并提供必要的信息以便运行时能够找到并调用这些函数。这包括函数名、所在的DLL、调用约定以及参数类型等。.NET运行时负责将托管类型映射到相应的本地类型,并处理所有必要的内存管理和数据封送(Marshalling)。

说了很长一大段,但是用起来的话就两行代码。假设你要调用系统本身的SetWindowsPos函数,那么直接在你要调用的函数上面假设一行[DllImport]就行:

[DllImport("user32.dll")]
static extern bool SetWindowPos(......);

写了段示例用的代码,如下。

 partial class Form1
 {
     ...
     [DllImport("user32.dll")]
     static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
     [DllImport("user32.dll")]
     static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
     delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
     static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
     const uint SWP_NOMOVE = 0x0002;
     const uint SWP_NOSIZE = 0x0001;
     const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
     const uint WINEVENT_OUTOFCONTEXT = 0x0000;
     void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
     {
         if (!this.TopMost)
         {
             SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
         }
     }
     ...
 }

  public Form1()
  {
      InitializeComponent();
      SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

      _winEventDelegate = new WinEventDelegate(WinEventProc);
      SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, _winEventDelegate, 0, 0, WINEVENT_OUTOFCONTEXT);
  }

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值