屏幕截图在移动到第二台显示器时不显示鼠标光标
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);
}
}
我最近一直在做很多工作(针对远程桌面系统)截取屏幕截图,只是在我尝试实现对多显示器的支持时偶然发现了一个问题。虽然可以截取屏幕截图,但我用来绘制光标的方法仅假定 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);
}
}