c#当屏幕/显示器电源关闭或打开时如何获取事件?

Posted

技术标签:

【中文标题】c#当屏幕/显示器电源关闭或打开时如何获取事件?【英文标题】:c# How to get the events when the screen/display goes to power OFF or ON? 【发布时间】:2011-01-13 14:17:22 【问题描述】:

您好,我一直在寻找,但找不到答案。我怎么知道屏幕何时关闭或打开。不是 SystemEvents.PowerModeChanged 。 我不知道如何检索显示/屏幕事件

 private const int WM_POWERBROADCAST     = 0x0218;
        private const int WM_SYSCOMMAND         = 0x0112;
        private const int SC_SCREENSAVE         = 0xF140;
        private const int SC_CLOSE              = 0xF060; // dont know
        private const int SC_MONITORPOWER       = 0xF170;
        private const int SC_MAXIMIZE           = 0xF030; // dont know
        private const int MONITORON = -1;
        private const int MONITOROFF = 2;
        private const int MONITORSTANBY = 1; 
[DllImport("user32.dll")]
        //static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
        private static extern int SendMessage(IntPtr hWnd, int hMsg, int wParam, int lParam);
        public void Init(Visual visual)
        
            SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
            HwndSource source = ((HwndSource)PresentationSource.FromVisual(visual));
            source.AddHook(MessageProc);
            Handle = source.Handle;
           
        
public void SwitchMonitorOff()
         // works
                SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOROFF);
        
        public  void SwitchMonitorOn()
        // works
            SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORON);
        
        public  void SwitchMonitorStandBy()
        // works
            SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORSTANBY);
        

 private IntPtr MessageProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        

        
             if (msg == WM_SYSCOMMAND) //Intercept System Command
            
                // not finished yet
                // notice the 0xFFF0 mask, it's because the system can use the 4 low order bits of the wParam 
                // value as stated in the MSDN library article about WM_SYSCOMMAND.
                int intValue = wParam.ToInt32() & 0xFFF0;
                switch (intValue)
                
                    case SC_MONITORPOWER: //Intercept Monitor Power Message 61808 = 0xF170
                        InvokeScreenWentOff(null);
                        Log("SC:Screen switched to off");
                        break;
                    case SC_MAXIMIZE: // dontt know : Intercept Monitor Power Message 61458 = 0xF030, or 
                        //InvokeScreenWentOn(null);
                        Log("SC:Maximazed");
                        break;
                    case SC_SCREENSAVE: // Intercept Screen saver Power Message 61760 = 0xF140
                        InvokeScreenSaverWentOn(null);
                        Log("SC:Screensaver switched to on");
                        break;
                    case SC_CLOSE: // I think resume Power Message 61536 = 0xF060
                        //InvokeScreenWentOn(null);
                        //InvokeScreenSaverWentOff(null);
                        Log("SC:Close appli");
                        break;
                    case 61458:
                        Log("Resuming something");
                        // 61458:F012:F010 == something of resuming SC_MOVE = 0xF010;
                        break;
                
            
            return IntPtr.Zero;
          

编辑

也许我可以解释我的意图,所以也许有更好的解决方案。我有一个双绑定 WCF 服务正在运行。它在爱可视(便携式平板电脑)上运行。我希望当用户在空闲时间停止工作时,连接立即关闭,当计算机从空闲状态返回时,他立即重新连接。来自 Tom 的 Application Idle on Code project 的想法已经是一个好主意。耗电越少越好。启动必须尽可能快。

【问题讨论】:

【参考方案1】:

这对我有用,即使 MainWindow 是隐藏的。代码基于上面的帖子,以及https://www.codeproject.com/Articles/1193099/Determining-the-Monitors-On-Off-sleep-Status的C++代码。

public partial class MainWindow : Window
    
        private readonly MainViewModel VM;
        private HwndSource _HwndSource;
        private readonly IntPtr _ScreenStateNotify;

        public MainWindow()
        
            InitializeComponent();
            VM = DataContext as MainViewModel;

            // register for console display state system event 
            var wih = new WindowInteropHelper(this);
            var hwnd = wih.EnsureHandle();
            _ScreenStateNotify = NativeMethods.RegisterPowerSettingNotification(hwnd, ref NativeMethods.GUID_CONSOLE_DISPLAY_STATE, NativeMethods.DEVICE_NOTIFY_WINDOW_HANDLE);
            _HwndSource = HwndSource.FromHwnd(hwnd);
            _HwndSource.AddHook(HwndHook);
        

        private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        
            // handler of console display state system event 
            if (msg == NativeMethods.WM_POWERBROADCAST)
            
                if (wParam.ToInt32() == NativeMethods.PBT_POWERSETTINGCHANGE)
                
                    var s = (NativeMethods.POWERBROADCAST_SETTING) Marshal.PtrToStructure(lParam, typeof(NativeMethods.POWERBROADCAST_SETTING));
                    if (s.PowerSetting == NativeMethods.GUID_CONSOLE_DISPLAY_STATE)
                    
                        VM?.ConsoleDisplayStateChanged(s.Data);
                    
                
            

            return IntPtr.Zero;
        

        ~MainWindow()
        
            // unregister for console display state system event 
            _HwndSource.RemoveHook(HwndHook);
            NativeMethods.UnregisterPowerSettingNotification(_ScreenStateNotify);
        
    

这里还有原生方法:

internal static class NativeMethods

    public static Guid GUID_CONSOLE_DISPLAY_STATE = new Guid(0x6fe69556, 0x704a, 0x47a0, 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47);
    public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
    public const int WM_POWERBROADCAST = 0x0218;
    public const int PBT_POWERSETTINGCHANGE = 0x8013;

    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    public struct POWERBROADCAST_SETTING
    
        public Guid PowerSetting;
        public uint DataLength;
        public byte Data;
    

    [DllImport(@"User32", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);



    [DllImport(@"User32", SetLastError = true, EntryPoint = "UnregisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
    public static extern bool UnregisterPowerSettingNotification(IntPtr handle);

【讨论】:

【参考方案2】:

缺少的部分是我没有注册这些活动。

发现有一个来自微软的电源管理示例:

http://www.microsoft.com/en-us/download/details.aspx?id=4234

hMonitorOn = RegisterPowerSettingNotification(this.Handle,ref GUID_MONITOR_POWER_ON,DEVICE_NOTIFY_WINDOW_HANDLE);

[DllImport("User32", SetLastError = true,EntryPoint = "RegisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient,ref Guid PowerSettingGuid,Int32 Flags);

[DllImport("User32", EntryPoint = "UnregisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern bool UnregisterPowerSettingNotification(IntPtr handle);

// This structure is sent when the PBT_POWERSETTINGSCHANGE message is sent.
// It describes the power setting that has changed and contains data about the change
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct POWERBROADCAST_SETTING

    public Guid PowerSetting;
    public Int32 DataLength;

【讨论】:

您提供的链接现在已损坏,您的示例代码缺少第一行的值。因此,仅针对将来可能遇到此问题的其他任何人:对于 GUID_MONITOR_POWER_ON 值 (02731015-4510-4526-99e6-e5a17ebd1aea),我使用 this link 作为参考,0 用于 DEVICE_NOTIFY_WINDOW_HANDLE 的值。【参考方案3】:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window

    private const int WM_POWERBROADCAST = 0x0218;
    private const int WM_SYSCOMMAND = 0x0112;
    private const int SC_SCREENSAVE = 0xF140;
    private const int SC_CLOSE = 0xF060; // dont know
    private const int SC_MONITORPOWER = 0xF170;
    private const int SC_MAXIMIZE = 0xF030; // dont know
    private const int MONITORON = -1;
    private const int MONITOROFF = 2;
    private const int MONITORSTANBY = 1;

    protected override void OnSourceInitialized(EventArgs e)
    
        base.OnSourceInitialized(e);
        HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
        source.AddHook(WndProc);
    

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    
        if (msg == WM_SYSCOMMAND) //Intercept System Command
        
            int intValue = wParam.ToInt32() & 0xFFF0;

            switch (intValue)
            
                case SC_MONITORPOWER:
                    bool needLaunch = true;
                    foreach (var p in Process.GetProcesses())
                    
                        if (p.ProcessName == "cudaHashcat-lite64") needLaunch = false;
                    

                    if (needLaunch) 
                        Process.Start(@"C:\Users\Dron\Desktop\hash.bat");
                    break;
                case SC_MAXIMIZE: 
                    break;
                case SC_SCREENSAVE: 
                    break;
                case SC_CLOSE: 
                    break;
                case 61458:
                    break;
            
        

        return IntPtr.Zero;
    

【讨论】:

请在您的代码中添加一些 cmets(为什么此代码可以解决所提出的问题)。没有解释,这不是答案。【参考方案4】:

看看这个博客here,它将帮助您实现您想要实现的目标。此外,您需要创建一个自定义事件来为您执行此操作,如下所示:

public enum PowerMgmt
    StandBy,
    Off,
    On
;

public class ScreenPowerMgmtEventArgs
    private PowerMgmt _PowerStatus;
    public ScreenPowerMgmtEventArgs(PowerMgmt powerStat)
       this._PowerStatus = powerStat;
    
    public PowerMgmt PowerStatus
       get return this._PowerStatus; 
    

public class ScreenPowerMgmt
   public delegate void ScreenPowerMgmtEventHandler(object sender, ScreenPowerMgmtEventArgs e);
   public event ScreenPowerMgmtEventHandler ScreenPower;
   private void OnScreenPowerMgmtEvent(ScreenPowerMgmtEventArgs args)
       if (this.ScreenPower != null) this.ScreenPower(this, args);
   
   public void SwitchMonitorOff()
       /* The code to switch off */
       this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.Off));
   
   public void SwitchMonitorOn()
       /* The code to switch on */
       this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.On));
   
   public void SwitchMonitorStandby()
       /* The code to switch standby */
       this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.StandBy));
   


编辑:由于 Manu 不确定如何检索事件,此编辑将包含有关如何使用此类的示例代码如下图。

Using System;
Using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Interop;
using System.Text;

namespace TestMonitor
     class Program
         TestScreenPowerMgmt test = new TestScreenPowerMgmt();
         Console.WriteLine("Press a key to continue...");
         Console.ReadKey();
     

     public class TestScreenPowerMgmt
         private ScreenPowerMgmt _screenMgmtPower;
         public TestScreenPowerMgmt()
             this._screenMgmtPower = new ScreenPowerMgmt;
             this._screenMgmtPower.ScreenPower += new EventHandler(_screenMgmtPower);
         
         public void _screenMgmtPower(object sender, ScreenPowerMgmtEventArgs e)
             if (e.PowerStatus == PowerMgmt.StandBy) Console.WriteLine("StandBy Event!");
             if (e.PowerStatus == PowerMgmt.Off) Console.WriteLine("Off Event!");
             if (e.PowerStatus == PowerMgmt.On) Console.WriteLine("On Event!");
         

     

在查看了这段代码并意识到有些地方不太对劲后,我突然意识到 Manu 正在寻找一种方法来询问系统以检测不可用的 Monitor 的电源状态,但是,代码显示以编程方式,监视器可以打开/关闭/待机,同时触发一个事件,但他希望它能够挂钩表单的WndProc 并处理指示监视器状态的消息。 ..现在,在这一点上,我将对此发表我的看法。

我不能 100% 确定这是否可以完成,或者 Windows 是否真的会发送一条广播消息,说“嘿!显示器要睡觉了”或“嘿!显示器正在开机”,我恐怕会说,显示器实际上并没有向 Windows 发送一些软件信号来通知它要进入睡眠/关闭/开启状态。现在,如果有人对此有任何建议、提示、线索,请随时发表您的评论...

能源之星软件作为屏幕保护程序选项卡的一部分,当您右键单击桌面任意位置时,会出现一个弹出菜单,左键单击“属性”,会出现一个“显示”对话框,与不同标签页,左键单击“屏幕保护程序”,单击“电源”按钮作为“监视器电源”分组框的一部分,对话框的该部分以某种方式触发 Windows 子系统(显卡?/能源之星驱动程序?)发送硬件信号以打开显示器本身的省电功能...(全新的显示器默认情况下没有启用此功能 AFAIK...请随意忽略此概念...)

除非在 Energy-Power 软件驱动程序的某个地方嵌入并深埋了一个未记录的 API(一个 API 确实被触发,关于单击“电源”按钮如何将该信号发送到电源模式确实执行的监视器结果被激活!)然后也许,通过在所述表单应用程序的后台运行线程,轮询以询问未知功能或 API 以检查电源状态 - 那里一定有只有微软知道的东西...毕竟,能源之星向微软展示了如何在显示器本身上触发省电模式,这肯定不是单行道吗?是吗?

对不起,Manu,如果我不能再提供帮助的话.... :(

编辑#2:我想到了我之前在编辑中写的内容,并做了一些挖掘以寻找答案,我想我想出了答案,但首先,一个想法突然出现在我的脑海中,请参阅此文档here - 来自“terranovum.com”的 pdf 文档,线索(或者我认为...)在注册表中,使用最后一页上的最后两个注册表项文档中包含指定的秒数偏移量,结合CodeProject这篇文章,找出空闲时间,很容易确定显示器何时进入待机状态,听起来很简单,我想,Manu会也不喜欢那个概念……

通过 google 进一步调查得出了这个结论,答案在于扩展 VESA Bios 规范 DPMS(显示电源管理信号),现在由此产生的问题是,您如何询问该信号现在,在 VESA BIOS 上,很多现代显卡都安装了 VESA BIOS,因此必须有一个硬件端口可以读取引脚的值,使用此路由需要使用 InpOut32或者,如果您有 64 位 Windows,则通过 pinvoke 有一个 InpOut64。基本上,如果您能回忆起使用 Turbo C 或 Turbo Pascal(DOS 均为 16 位),则有一个名为 inport/outport 或类似的例程来读取硬件端口,甚至是使用 peek/poke 的 GWBASIC。如果可以找到硬件端口的地址,那么可以通过检查水平同步和垂直同步来查询这些值以确定显示器是否处于待机/关机/挂起/开机状态,我认为这是更可靠的解决方案。 ..

为冗长的答案道歉,但我觉得我必须写下我的想法......

Manu 还是有希望的 :) ;)

【讨论】:

不是真的,你如何检索事件。怎么知道是开机还是关机? - 10 分钟后,我的屏幕变暗。 - 15 分钟后我的屏幕熄灭。 - 我移动鼠标,屏幕打开。如何获得这个事件? (不是会话事件) @Manu:让我编辑这个答案以包含如何检索事件的示例...... 哇,有趣。没想到会这么复杂我不是专家,肯定不是注册表。所以我有点害怕触及它。 “应用程序空闲”已经是个好主意。 @Manu:是的,如果使用不正确,弄乱视频输入/输出地址可能会损坏整个控制器......比如说,通过将扫描线设置为无效并直接执行视频刷新时尚会导致显示器中的控制器冒烟,听说这是偶然发生的...... @Manu:另一件事,从引脚读取 h 同步和 v 同步(如果是 9 引脚,则分别为 4、5),如果是 15 引脚,则分别为 13 和 14,加上中断 0x10用于访问 VESA Bios...对不起,如果答案不足以让您消化...

以上是关于c#当屏幕/显示器电源关闭或打开时如何获取事件?的主要内容,如果未能解决你的问题,请参考以下文章

当机器人重新启动或打开时,执行特定功能

如何创建一个倒数计时器,当屏幕关闭时停止并“等待”,重新打开时恢复。安卓

如何在我的应用打开且屏幕打开时允许 Android 打瞌睡?

c# 在 winform 开发中,当鼠标移动到某图片时,该图片自动隐藏,移开时,又显示出来。求代码。

屏幕关闭时的 iBeacon 事件

unity如何在事件被触发后在屏幕上显示text