Получить дескриптор ранее активного окна, где мое приложение может быть приложением в трее или не в фокусе

Мне нужно получить дескриптор текущего/ранее активного окна на моем рабочем столе. В основном я работаю над приложением С# Windows Forms, которое может делать снимки экрана. Я использую GetForegroundWindow PInvoke, чтобы получить этот дескриптор, но если я вызову свой код из щелчка меню, активное окно, похоже, будет настроено на окно самого меню, поэтому созданное изображение представляет собой маленький черный прямоугольник.

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

Как отслеживать ранее активный дескриптор окна? Имейте в виду, что мое приложение работает как значок в трее, поэтому переопределение wndproc никогда не срабатывает. Я также пробовал формы NativeForm и MessageOnly, но они никогда не срабатывают, если приложение не имеет фокуса.

Дошли ли мы до глобальных хуков и внешних dll? Наверняка должен быть простой способ решить эту проблему? (любое да, мне нужно реализовать элементы меню, а также сочетания клавиш для конкретного сценария удобства использования.

Итак, как мне отслеживать текущее/предыдущее активное окно на моем рабочем столе из приложения С#, которое не имеет самого окна и не имеет фокуса?

Большое спасибо за любую помощь


person user2026382    schedule 16.08.2013    source источник
comment
Какое меню вы используете? ContextMenuStrip?   -  person King King    schedule 16.08.2013


Ответы (2)


Я нашел гораздо более простое решение, и хотя это немного хак, работает красиво, надежно и ничего не стоит. Что я делаю, так это то, что в моем меню нажмите событие, я показываю форму, а затем сразу же скрываю форму. Это приводит к перемещению фокуса с панели задач обратно на рабочий стол. Я показываю свою форму, затем, когда я ее закрываю, ранее выбранное окно восстанавливает свой фокус. Затем мой GetForegroundWindow получает дескриптор.

Мое приложение на самом деле имеет форму состояния, которая может обновлять пользователя во время длительных процессов, поэтому я могу показать его с сообщением «захват, пожалуйста, подождите» или что-то в этом роде. Или я могу просто высветить его на экране так быстро, что его вообще нельзя будет разглядеть. Затем я закрываю его, использую thread sleep(100) и продолжаю захват экрана. Работает как шарм

        if (_runMode == RunMode.Tray)
        {
            FormStatus f = new FormStatus();
            f.Show();
            f.Close();
            Thread.Sleep(100);
        }

        ScreenCapture.CaptureActiveWindow(filename);
person user2026382    schedule 16.08.2013

Предположим, вы используете ContextMenuStrip для своего меню в трее:

IntPtr lastHandle;
public IntPtr GetForegroundWin(){
   IntPtr hwnd = GetForegroundWindow();
   if(hwnd != contextMenuStrip1.Handle) lastHandle = hwnd; 
   return lastHandle;      
}
//Add a timer
Timer t = new Timer();
t.Interval = 1;
t.Tick += (s,e) => {
    GetForegroundWin();
};//Then you can get the foreground Handle by lastHandle
t.Start();//this timer will run as long as your application runs.

Хорошо, без использования таймера у нас все еще есть другой выбор, используя SetWinEventHook. Эта функция может помочь вам перехватить некоторые обратные вызовы, чтобы поймать некоторые события, включая событие active window change. Вот ссылка для вас, чтобы узнать больше: Обнаружение изменения активного окна использование C# без опроса

Вот код решения без использования таймера (опроса):

//Must add using System.Runtime.InteropServices;
public partial class Form1 : Form
{
    [DllImport("user32")]
    private static extern IntPtr SetWinEventHook(int minEvent, int maxEvent, IntPtr hModule, WinEventProcDelegate proc, int procId, int threadId, int flags);
    private delegate void WinEventProcDelegate(IntPtr hHook, int ev, IntPtr hwnd, int objectId, int childId, int eventThread, int eventTime);
    private void WinEventProc(IntPtr hHook, int ev, IntPtr hwnd, int objectId, int childId, int eventThread, int eventTime)
    {
        if(hwnd != contextMenuStrip1.Handle) lastHandle = hwnd;
    }
    public Form1()
    {
        InitializeComponent();            
        //EVENT_SYSTEM_FOREGROUND = 3
        //WINEVENT_OUTOFCONTEXT = 0
        SetWinEventHook(3, 3, IntPtr.Zero, WinEventProc, 0, 0, 0);                                                                      
    }
    IntPtr lastHandle;
}
//You can access the lastHandle to get the current active/foreground window. This doesn't require GetForegroundWindow()
person King King    schedule 16.08.2013
comment
Привет, KK‹ Я использую ContextMenuStrip для своего меню в трее. Я попробовал ваш код в его событии menu_click, но он все еще не работает. Можете ли вы связать свое решение вместе для меня? Как и где я могу использовать ваш метод GetForegroundWin()? - person user2026382; 17.08.2013
comment
@user2026382 user2026382 всякий раз, когда вам нужно получить Handle окна переднего плана, вы можете вызвать метод GetForegroundWin вместо метода GetForegroundWindow. Возврат Handle будет из окна переднего плана, за исключением окна ContextMenuStrip. - person King King; 17.08.2013
comment
Да, это то, что я делаю, но это не работает. Я только что попробовал это в событии открытия лоткового меню, и даже тогда оно не работало. Допустим, у меня открыт блокнот, например, я нажимаю на блокнот, и теперь это активное окно. Но как только я нажимаю на свое приложение в трее, блокнот теряет фокус, это больше не активное окно. Я использую ваш метод и передаю lasthandle в свой метод захвата экрана, но я все равно получаю небольшой задний прямоугольник в качестве изображения. - person user2026382; 17.08.2013
comment
@user2026382 user2026382 вам может понадобиться таймер для проверки окна переднего плана, см. мой обновленный код. - person King King; 17.08.2013
comment
О господи, я не хочу начинать использовать таймер, забивая мое приложение каждую миллисекунду или что-то еще, должно быть лучшее решение. Мне просто нужно знать, какое окно было последним активным, прежде чем я щелкнул значок в трее. - person user2026382; 17.08.2013
comment
@user2026382 user2026382 Я написал приложение такого типа (не моментальный снимок, но также должен был отслеживать последнее активное окно), и я также использовал таймер, он работал нормально. GetForegroundWindow() не требует много вычислений. Другого решения не будет, если вы будете придерживаться GetForegroundWindow. - person King King; 17.08.2013
comment
Ваше последнее предложение предполагает, что есть альтернативы GetForegroundWindow ?? У кого-нибудь еще есть идеи? - person user2026382; 17.08.2013
comment
@user2026382 user2026382 не совсем, я не знаю других вариантов, на самом деле мы должны получать уведомления, если активное/переднее окно изменяется (какое-то событие), но у нас нет такого события. Вот почему мы должны probe обновить последнее активное окно, используя GetForegroundWindow. Win32 не поддерживает event. Для вас есть еще один вариант - hook into system globally, однако это непросто сделать (даже с использованием какой-нибудь библиотеки, например EasyHook), а также дорого стоит из-за замедления производительности системы (немного или очень незначительно, но это влияет). - person King King; 17.08.2013
comment
@user2026382 user2026382 Я нашел для вас полезную ссылку, это будет тот выбор, который вам нужен. - person King King; 17.08.2013