屏幕截图在移动到第二台显示器时不显示鼠标光标

Screenshot not show mouse cursor when is moved to second monitor

我最近一直在做很多工作(针对远程桌面系统)截取屏幕截图,只是在我尝试实现对多显示器的支持时偶然发现了一个问题。虽然可以截取屏幕截图,但我用来绘制光标的方法仅假定 1 个屏幕。如果我将指针定位在附加屏幕上(在截取该附加屏幕的屏幕截图时),则光标不会显示。我将指针移至主屏幕并显示(当然在错误的位置,因为它是错误的屏幕)。

我的代码完全在下面。

program Test;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Windows,
  vcl.Graphics,
  SysUtils;

function GetCursorInfo2: TCursorInfo;
var
  hWindow: HWND;
  pt: TPoint;
  dwThreadID, dwCurrentThreadID: DWORD;
begin
  Result.hCursor := 0;
  ZeroMemory(@Result, SizeOf(Result));
  if GetCursorPos(pt) then
  begin
    Result.ptScreenPos := pt;
    hWindow := WindowFromPoint(pt);
    if IsWindow(hWindow) then
    begin
      dwThreadID := GetWindowThreadProcessId(hWindow, nil);
      dwCurrentThreadID := GetCurrentThreadId;
      if (dwCurrentThreadID <> dwThreadID) then
      begin
        if AttachThreadInput(dwCurrentThreadID, dwThreadID, True) then
        begin
          Result.hCursor := GetCursor;
          AttachThreadInput(dwCurrentThreadID, dwThreadID, False);
        end;
      end
      else
        Result.hCursor := GetCursor;
    end;
  end;
end;

procedure TakeScreenshot(var Bmp: TBitmap; WndHdc: HDC; Width, Height, Left, Top: Integer);
const
  CAPTUREBLT = 000000;
var
  DesktopCanvas: TCanvas;
  MyCursor: TIcon;
  CursorInfo: TCursorInfo;
  IconInfo: TIconInfo;
  DC: HDC;
begin
  DC := GetDC(WndHdc);
  try
    if (DC = 0) then
      Exit;
    Bmp.Width := Width;
    Bmp.Height := Height;
    DesktopCanvas := TCanvas.Create;
    try
      DesktopCanvas.Handle := DC;
      BitBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, DesktopCanvas.Handle, Left, Top, SRCCOPY or CAPTUREBLT);
      MyCursor := TIcon.Create;
      try
        CursorInfo := GetCursorInfo2;
        if CursorInfo.hCursor <> 0 then
        begin
          MyCursor.Handle := CursorInfo.hCursor;
          GetIconInfo(CursorInfo.hCursor, IconInfo);
          Bmp.Canvas.Draw(CursorInfo.ptScreenPos.X - IconInfo.xHotspot, CursorInfo.ptScreenPos.Y - IconInfo.yHotspot, MyCursor);
        end;
      finally
        MyCursor.ReleaseHandle;
        MyCursor.Free;
      end;
    finally
      DesktopCanvas.Free;
    end;
  finally
    if (DC <> 0) then
      ReleaseDC(0, DC);
  end;
end;

function EnumDisplayMonitors(dc: HDC; rect: PRect; EnumProc: pointer; lData: Integer): Boolean; stdcall; external user32 name 'EnumDisplayMonitors';

type
  TMonInfo = record
    h: THandle;
    DC: HDC;
    R: TRect;
  end;

var
  MonList: array of TMonInfo;

function MonitorEnumProc(hMonitor: THandle; hdcMonitor: HDC; lprcMonitor: DWORD; dwData: Integer): Boolean; stdcall;
var
  I, Width, Height, Left, Top: Integer;
  Bmp: TBitmap;
begin
  I := High(MonList) + 1;
  SetLength(MonList, I + 1);
  MonList[I].h := hMonitor;
  MonList[I].DC := hdcMonitor;
  MonList[I].R := PRect(lprcMonitor)^;

  Left := PRect(lprcMonitor)^.Left;
  Top := PRect(lprcMonitor)^.Top;
  Width := PRect(lprcMonitor)^.Width;
  Height := PRect(lprcMonitor)^.Height;

  Bmp := TBitmap.Create;
  try
    TakeScreenshot(Bmp, hdcMonitor, Width, Height, Left, Top);
    Bmp.SaveToFile('C:\Screen' + IntToStr(I + 1) + '.bmp');
  finally
    Bmp.Free;
  end;

  Result := True;
end;

procedure Main;
var
  S: string;
  I: Integer;
begin
  Writeln('Number of monitors: ' + IntToStr(High(MonList) + 1) + #13#10);
  Writeln('-----------------');
  for I := 0 to High(MonList) do
    with MonList[I] do
    begin
      S := #13#10 + 'Handle: ' + IntToStr(h) + #13#10 + 'Dc: ' + IntToStr(DC) + #13#10 + 'Size: ' + IntToStr(R.Right) + 'x' + IntToStr(R.Bottom) + #13#10;
      Writeln(S);
      Writeln('-----------------');
    end;
end;

begin
  try
    EnumDisplayMonitors(0, nil, Addr(MonitorEnumProc), 0);
    Main;
    Writeln(#13#10 + 'Connected: ' + IntToStr(GetSystemMetrics(SM_CMONITORS)) + #13#10);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

问题是您从 GetCursorInfo2 获得的光标坐标相对于位图的坐标不正确。 首先判断光标点是否在lprcMonitor中,如果returns为真,则可以使用PtInRect, and then use DrawIcon将hcursor绘制到位图中。 这是从您的代码转换而来的 C++ 示例(因为我不熟悉 delphi):

#include <windows.h>
#include <iostream>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
using namespace std;
#pragma comment(lib, "Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT  num = 0;          // number of image encoders
    UINT  size = 0;         // size of the image encoder array in bytes

    ImageCodecInfo* pImageCodecInfo = NULL;

    GetImageEncodersSize(&num, &size);
    if (size == 0)
        return -1;  // Failure

    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL)
        return -1;  // Failure

    GetImageEncoders(num, size, pImageCodecInfo);

    for (UINT j = 0; j < num; ++j)
    {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;  // Success
        }
    }

    free(pImageCodecInfo);
    return -1;  // Failure
}

//HCURSOR GetCursorInfo2(POINT * pt)
//{
//    POINT p = { 0 };
//    HWND hWindow = NULL;
//    HCURSOR hCursor = NULL;
//    if (GetCursorPos(&p))
//    {
//        pt->x = p.x;
//        pt->y = p.y;
//        hWindow = WindowFromPoint(*pt);
//        if (IsWindow(hWindow))
//        {
//            DWORD dwThreadID = GetWindowThreadProcessId(hWindow, NULL);
//            DWORD dwCurrentThreadID = GetCurrentThreadId();
//            if (dwCurrentThreadID != dwThreadID)
//            {
//                if (AttachThreadInput(dwCurrentThreadID, dwThreadID, TRUE))
//                {
//                    hCursor = GetCursor();
//                    AttachThreadInput(dwCurrentThreadID, dwThreadID, FALSE);
//                }
//            }
//        }
//    }
//    return hCursor;
//}
void TakeScreenshot(HDC hdcbmp, HDC WndHdc, int Width, int Height, int Left, int Top)
{
    HDC hdc = GetDC(NULL);
    if (hdc == 0) exit(-1);
    BitBlt(hdcbmp, 0, 0, Width, Height, hdc, Left, Top, SRCCOPY | CAPTUREBLT);
    CURSORINFO cursorinfo = { 0 };
    cursorinfo.cbSize = sizeof(CURSORINFO);
    if (GetCursorInfo(&cursorinfo))
    {
        RECT rc = { Left ,Top,Left + Width ,Top + Height };

        if (PtInRect(&rc, cursorinfo.ptScreenPos))
        {
            DrawIcon(hdcbmp, cursorinfo.ptScreenPos.x - Left, cursorinfo.ptScreenPos.y - Top, cursorinfo.hCursor);
        }
    }
    /*ICONINFO IconInfo = { 0 };
    GetIconInfo(hCursor, &IconInfo);*/
}
BOOL CALLBACK Monitorenumproc(HMONITOR  hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    static int count = 0;
    int Left = lprcMonitor->left;
    int Top = lprcMonitor->top;
    int Width = lprcMonitor->right - lprcMonitor->left;
    int Height = lprcMonitor->bottom - lprcMonitor->top;

    HDC dev = GetDC(NULL);
    HDC CaptureDC = CreateCompatibleDC(dev);
   
    HBITMAP CaptureBitmap = CreateCompatibleBitmap(dev, Width, Height);
    HGDIOBJ old_obj = SelectObject(CaptureDC, CaptureBitmap);
    TakeScreenshot(CaptureDC, dev, Width, Height, Left, Top);
    Gdiplus::Bitmap bitmap(CaptureBitmap, NULL);
    
    CLSID pngClsid;
    GetEncoderClsid(L"image/bmp", &pngClsid);
    wstring BmpNameString = L"C:\screen";
    BmpNameString = BmpNameString + std::to_wstring(count) + L".bmp";
    count++;
    bitmap.Save(BmpNameString.c_str(), &pngClsid, NULL);

    SelectObject(CaptureDC, old_obj);
    DeleteDC(CaptureDC);
    ReleaseDC(NULL, dev);
    DeleteObject(CaptureBitmap);
    return TRUE;
}
int main(void)
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    EnumDisplayMonitors(0, NULL, Monitorenumproc, 0);

    GdiplusShutdown(gdiplusToken);
    return 0;
}

并注意函数中的这些行 TakeScreenshot:

CURSORINFO cursorinfo = { 0 };
cursorinfo.cbSize = sizeof(CURSORINFO);
if (GetCursorInfo(&cursorinfo))
{
    RECT rc = { Left ,Top,Left + Width ,Top + Height };

    if (PtInRect(&rc, cursorinfo.ptScreenPos))
    {
        DrawIcon(hdcbmp, cursorinfo.ptScreenPos.x - Left, cursorinfo.ptScreenPos.y - Top, cursorinfo.hCursor);
    }
}