打印屏幕后剪贴板中没有图像

本文关键字:图像 剪贴板 屏幕 打印 | 更新日期: 2023-09-27 18:32:04

我正在实现不同的屏幕采集卡来比较它们。其中一个应该使用"打印屏幕"键和剪贴板。

我发送带有keybd_事件的击键:

[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern void keybd_event(byte vVK, byte bScan, int dwFlags,int dwExtraInfo);
public const int KEYEVENTF_EXTENDEDKEY=0x0001; //key down
public const int KEYEVENTF_KEYUP=0x0002; //key up
public const int VK_SNAPSHOT=0x2C; //VirtualKey code for print key
public static void PrintScreen(){
keybd_event(VK_SNAPSHOT,0,KEYEVENTF_EXTENDEDKEY,0);
keybd_event(VK_SNAPSHOT,0,KEYEVENTF_KEYUP,0);
}

在我的IEnumerable中,我调用此方法,然后尝试抓取图像:

...
InputController.PrintScreen();
var img=Clipboard.GetImage();
...

返回的图像始终为 null,并且 Clipboards.ContainsImage() 始终为假。我尝试在发送密钥后等待片刻,但它也不起作用。我是否缺少某种设置,或者是否存在根本错误?

PS:运行程序后,我可以将正确的图像粘贴到油漆或gimp中。

打印屏幕后剪贴板中没有图像

这是一个控制台程序

这是最相关的细节,你应该把它放在你的问题中。 剪贴板是一个系统对象,其基础 API 基于 COM。 这使得它对使用 API 的线程的单元状态敏感。 .NET 剪贴板类对此进行了一些摸索,如果线程的状态错误,它应该真正引发异常。 在控制台模式应用程序的情况下是错误的,默认情况下其主线程是 MTA,您需要 STA 才能使用 API。

修复很简单,您只需在 Main() 方法上放置一个属性来请求 STA:

    [STAThread]
    static void Main(string[] args) {
        // etc...
    }

从技术上讲,STA 线程还应该泵送消息循环,就像 Winforms 或 WPF 应用一样。 但是,只要您只从主线程进行方法调用,您就可以侥幸逃脱。

您是否尝试过使用 SendKeys 类?

public static Image TakeScreenSnapshot(bool activeWindowOnly)
{
    // PrtSc = Print Screen Key
    string keys = "{PrtSc}";
    if (activeWindowOnly)
        keys = "%" + keys; // % = Alt
    SendKeys.SendWait(keys);
    return Clipboard.GetImage();
}

代码源示例

public const int KEYEVENTF_EXTENDEDKEY=0x0001; //key down
public const int KEYEVENTF_KEYUP=0x0002; //key up

但您正在使用:

keybd_event(VK_SNAPSHOT,0,KEYEVENT_EXTENDEDKEY,0);
keybd_event(VK_SNAPSHOT,0,KEYEVENT_KEYUP,0);

使用KEYEVENTF_EXTENDEDKEY并KEYEVENTF_KEYUP工作


回复:整个事情在线程池的工作线程中运行我找不到将 PrintScreen() 发布到"主同步上下文"的方法,因为它是一个控制台程序

你可以试试这个:

class Program
{        
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern void keybd_event(byte vVK, byte bScan, int dwFlags, int dwExtraInfo);
    public const int KEYEVENTF_EXTENDEDKEY = 0x0001; //key down
    public const int KEYEVENTF_KEYUP = 0x0002; //key up
    public const int VK_SNAPSHOT = 0x2C; //VirtualKey code for print key
    public static void PrintScreen()
    {
        keybd_event(VK_SNAPSHOT, 0, KEYEVENTF_EXTENDEDKEY, 0);
        keybd_event(VK_SNAPSHOT, 0, KEYEVENTF_KEYUP, 0);
    }
    public static void test(Action<Image> action)
    {
        PrintScreen();
        var image = Clipboard.GetImage();
        action.BeginInvoke(image, ar => action.EndInvoke(ar), null);
    }
    [STAThread]
    static void Main(string[] args)
    {
        var processAction = new Action<Image>(img =>
        {
            if (img == null)
                Console.WriteLine("none");
            else
                Console.WriteLine(img.PixelFormat);
        });
        test(processAction);
        System.Console.ReadLine();
}
我知道

这是一个较老的问题,但我想我会分享我的发现,因为它与此有关。 我看到的问题是,只要我在代码中有断点,上面发布的方法就可以工作。 如果没有断点,事件将触发,但只有在退出它所在的方法调用后才会触发。

这意味着类似

    InputController.PrintScreen();
    var img=Clipboard.GetImage();

不起作用,因为剪贴板在离开此方法之前不会填充。 解决这个问题的解决方法是使用 DoEvents() 的旧 VB 技巧。 这将允许我们的应用程序处理队列中的所有窗口消息。 因此,修订后的代码应该有效。

    InputController.PrintScreen();
    Application.DoEvents();
    var img=Clipboard.GetImage();