Нет изображения в буфере обмена после PrintScreen

Я использую другой захват экрана, чтобы сравнить их. Один из них должен использовать клавишу «printscreen» и буфер обмена.

Я отправляю нажатие клавиши с помощью keybd_event:

[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() всегда имеет значение false. Я пытался подождать некоторое время после отправки ключей, но это тоже не работает. Я пропустил какую-то настройку или есть фундаментальная ошибка?

PS: я могу вставить правильное изображение в краску или гимп после запуска программы.


person floAr    schedule 20.09.2013    source источник


Ответы (4)


это консольная программа

Это самая важная деталь, вы должны указать ее в своем вопросе. Буфер обмена — это системный объект, базовый API которого основан на COM. Что делает его чувствительным к состоянию квартиры потока, использующего API. Класс .NET Clipboard немного ошибается, он действительно должен генерировать исключение, если состояние потока неверно. И это неправильно в случае приложения в консольном режиме, его основной поток по умолчанию — MTA, и вам нужен STA, чтобы использовать API.

Исправление простое, вы можете просто поместить атрибут в метод Main(), чтобы запросить STA:

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

Технически поток STA также должен перекачивать цикл сообщений, как это делает приложение Winforms или WPF. Но вам это сойдет с рук, если вы будете вызывать методы только из основного потока.

person Hans Passant    schedule 20.09.2013
comment
Извините за поздний комментарий, в выходные у меня не было доступа к машине разработчика. Атрибут [STAThread] объявляется для основного метода. Я думаю, проблема может заключаться в том, что захват изображения выполняется в потоке ThreadPool-worker. - person floAr; 23.09.2013
comment
Состояние квартиры MTA - person floAr; 23.09.2013

Пробовали ли вы вместо этого использовать класс 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();
}

Источник примера кода

person Mayazcherquoi    schedule 20.09.2013
comment
Та же проблема. Clipboard.GetImage() не приводит ни к какому объекту, но я могу вставить нужное изображение в краску. - person floAr; 20.09.2013

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, это работает


re: все работает в рабочем потоке в пуле потоков. Я не могу найти способ POST PrintScreen() для «Main-SynchronizationContext», потому что это консольная программа.

вы можете попробовать это:

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();
}
person kwingho    schedule 20.09.2013
comment
на самом деле это ошибка при вводе вопроса. У разработчика mychiene нет доступа к сети, поэтому все это было скопировано вручную. Спасибо, что указали на это, но коды событий верны, я отредактировал вопрос. - person floAr; 20.09.2013
comment
Я тестирую его в проекте winform, private void button1_Click(object sender, EventArgs e){PrintScreen();this.pictureBox1.Image = Clipboard.GetImage();}, после того как я нажму кнопку1, prictureBox1 покажет снимок экрана. Проблема может быть в операционной системе или другом коде - person kwingho; 20.09.2013
comment
Это консольная программа, работающая под Win8. В настоящее время я изучаю потоки, так как все это выполняется в рабочем потоке в пуле потоков. - person floAr; 20.09.2013

Я знаю, что это старый вопрос, но я подумал, что поделюсь своими выводами, поскольку он связан с этим. Проблема, которую я видел, заключалась в том, что описанные выше методы работали, пока у меня была точка останова в коде. Без точки останова события сработают, но они сработают только после выхода из вызова метода, в котором они находились.

Это означает что-то вроде

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

не будет работать, потому что буфер обмена не будет заполнен, пока он не покинет этот метод. Обойти это — старый трюк VB с использованием DoEvents(). Это позволит нашему приложению обрабатывать все сообщения Windows в очереди. Так что исправленный код должен работать.

    InputController.PrintScreen();
    Application.DoEvents();
    var img=Clipboard.GetImage();
person CodeMode    schedule 28.07.2014