使用Delphi在Windows 7中检测用户锁定/解锁屏幕

9
如何在Windows 7中检测用户锁定/解锁屏幕?
我找到了这个问题,其中有一个C#的答案,但我想在Delphi 2009中使用它。我猜应该有一些Windows消息(例如这些)可以完成这项工作。这是我尝试过的代码,但它没有起作用:
const
  NOTIFY_FOR_ALL_SESSIONS = 1;
  {$EXTERNALSYM NOTIFY_FOR_ALL_SESSIONS}
  NOTIFY_FOR_THIS_SESSION = 0;
  {$EXTERNALSYM NOTIFY_FOR_THIS_SESSION}

type

TfrmAlisson = class(TForm)
  lbl2: TLabel;
  procedure FormCreate(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
public
  FLockedCount: Integer;
  procedure WndProc(var Message: TMessage); override;
  function WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): bool; stdcall;
  function WTSUnRegisterSessionNotification(hWND: HWND): bool; stdcall;
end;

implementation

uses
  // my impl uses here

procedure TfrmAlisson.FormCreate(Sender: TObject);
begin
  if (WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION)) then
    ShowMessage('Nice')
  else
  begin
    lastError := GetLastError;
    ShowMessage(SysErrorMessage(lastError));
  end;
end;

procedure TfrmAlisson.FormDestroy(Sender: TObject);
begin
  WTSUnRegisterSessionNotification(Handle);
end;

procedure TfrmAlisson.WndProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_WTSSESSION_CHANGE:
      begin
        if Message.wParam = WTS_SESSION_LOCK then
        begin
          Inc(FLockedCount);
        end;
        if Message.wParam = WTS_SESSION_UNLOCK then
        begin
          lbl2.Caption := 'Session was locked ' +
          IntToStr(FLockedCount) + ' times.';
        end;
      end;
  end;
  inherited;
end;

function TfrmAlisson.WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): bool;
  external 'wtsapi32.dll' Name 'WTSRegisterSessionNotification';

function TfrmAlisson.WTSUnRegisterSessionNotification(hWND: HWND): bool;
  external 'wtsapi32.dll' Name 'WTSUnRegisterSessionNotification';

当执行FormCreate时,WTSRegisterSessionNotification返回false,并且最后的操作系统错误返回无效参数

1
调用WTSRegisterSessionNotification函数,你将会收到WM_WTSSESSION_CHANGE消息。当wParamWTS_SESSION_LOCKWTS_SESSION_UNLOCK时,处理相应情况。 - RbMm
@RbMm 谢谢,我会阅读并告诉你我是否成功实现了。 - Alisson Reinaldo Silva
@RbMm:那应该发布为答案。 - Remy Lebeau
@RemyLebeau - 真的是重复的 - 例如 https://dev59.com/MlTTa4cB1Zd3GeqPw-kr#5198312 - RbMm
1
这段代码没有做到这一点,是吗?在重新创建期间有一个窗口,你没有在听。因此,使用VCL窗口是错误的。 - David Heffernan
显示剩余5条评论
1个回答

12

你的代码不起作用是因为你没有正确实现它。

你没有正确声明WTSRegisterSessionNotification()WTSUnRegisterSessionNotification()

此外,你没有考虑到VCL在Form对象生命周期内动态重建窗口的可能性。因此,即使WTSRegisterSessionNotification()成功了,你也可能会失去注册并且没有意识到它。

尝试这个:

interface

uses
  ...;

type
  TfrmAlisson = class(TForm)
    lbl2: TLabel;
  protected
    procedure CreateWnd; override;
    procedure DestroyWindowHandle; override;
    procedure WndProc(var Message: TMessage); override;
  public
    LockedCount: Integer;
  end;

implementation

const
  NOTIFY_FOR_THIS_SESSION = $0;
  NOTIFY_FOR_ALL_SESSIONS = $1;

function WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSRegisterSessionNotification';
function WTSUnRegisterSessionNotification(hWnd: HWND): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSUnRegisterSessionNotification';

procedure TfrmAlisson.CreateWnd;
begin
  inherited;
  if not WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION) then
    RaiseLastOSError;
end;

procedure TfrmAlisson.DestroyWindowHandle;
begin
  WTSUnRegisterSessionNotification(Handle);
  inherited;
end;

procedure TfrmAlisson.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_WTSSESSION_CHANGE then
  begin
    case Message.wParam of
      WTS_SESSION_LOCK: begin
        Inc(LockedCount);
      end;
      WTS_SESSION_UNLOCK: begin
        lbl2.Caption := Format('Session was locked %d times.', [LockedCount]);
      end;
    end;
  end;
  inherited;
end;

end.

话虽如此,考虑编写代码时不要依赖 VCL 的窗口重建行为。您可以为监视会话更改分配一个专用窗口:

interface

uses
  ...;

type
  TfrmAlisson = class(TForm)
    lbl2: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    SessionWnd: HWND;
    procedure SessionWndProc(var Message: TMessage);
  public
    LockedCount: Integer;
  end;

implementation

const
  NOTIFY_FOR_THIS_SESSION = $0;
  NOTIFY_FOR_ALL_SESSIONS = $1;

function WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSRegisterSessionNotification';
function WTSUnRegisterSessionNotification(hWnd: HWND): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSUnRegisterSessionNotification';

procedure TfrmAlisson.FormCreate(Sender: TObject);
begin
  SessionWnd := AllocateHWnd(SessionWndProc);
  if not WTSRegisterSessionNotification(SessionWnd, NOTIFY_FOR_THIS_SESSION) then
    RaiseLastOSError;
end;

procedure TfrmAlisson.FormDestroy(Sender: TObject);
begin
  if SessionWnd <> 0 then
  begin
    WTSUnRegisterSessionNotification(SessionWnd);
    DeallocateHWnd(SessionWnd);
  end;
end;

procedure TfrmAlisson.SessionWndProc(var Message: TMessage);
begin
  if Message.Msg = WM_WTSSESSION_CHANGE then
  begin
    case Message.wParam of
      WTS_SESSION_LOCK: begin
        Inc(LockedCount);
      end;
      WTS_SESSION_UNLOCK: begin
        lbl2.Caption := Format('Session was locked %d times.', [LockedCount]);
      end;
    end;
  end;

  Message.Result := DefWindowProc(SessionWnd, Message.Msg, Message.WParam, Message.LParam);
end;

end.

我收到了“未声明的标识符:WTSRegisterSessionNotification”。我已经将“Windows”添加到接口使用中。我忘记提到我正在使用Windows 10编译,尽管应用程序将由Windows 7用户运行。 - Alisson Reinaldo Silva
我重新声明了它,遵循这个示例,并且它与您的示例一起工作。我注意到的唯一一件事是,在我调试并关闭应用程序时没有调用DestroyWnd(也许这是在进程从调试器分离后调用的)。 - Alisson Reinaldo Silva
1
@Alisson:我的错,WTS函数是在XE2中添加到Windows单元的。至于DestroyWnd(),它只在窗口被销毁而表单仍然存在时才会被调用。VCL在表单销毁期间绕过了DestroyWnd。我已经更新了我的答案来解决这些问题。 - Remy Lebeau
它运行得很好,非常感谢。我按建议更改了我的代码以分配专用窗口。 - Alisson Reinaldo Silva
1
就此而言,在CreateWndDestroyWindowHandle中,使用WindowHandle而不是Handle是惯用语。 - David Heffernan
@DavidHeffernan:在这些情况下,它们是相同的东西。它们都返回FHandle。唯一的区别是,如果为零,则Handle会分配FHandle,而WindowHande则不会。在这些情况下,FHandle永远不为零。我很少看到有人在这些情况下使用WindowHandle。两者都可以使用。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接