Предотвратить контроль WebBrowser от кражи фокуса?

Есть ли способ не дать элементу управления WebBrowser заставить его родительскую форму вывести себя на передний план?

Если вы используете метод InvokeScript для вызова функции JavaScript, которая вызывает focus() для iframe в главном родительском документе, это приведет к тому, что окно переместится прямо вперед (или, по крайней мере, заставит значок панели задач мигать). Есть ли способ предотвратить это?

Обновить:

Я нашел временный ответ на мою проблему.

Когда происходит событие Deactive родительской формы WebBrowser, я удаляю WebBrowser из его контейнера и повторно добавляю его, когда его старая родительская форма снова активируется.

Это отчасти хакерски, но это работает. Я открыт для любых лучших предложений, хотя.

2 ответа

Решение

РЕДАКТИРОВАТЬ: полный вопрос переписан, я неправильно понял оригинальный вопрос

Давайте обобщим проблему: элемент управления или компонент, над которым у вас нет контроля, может вызвать FlashWindow (функция Win32 API), чтобы привлечь внимание пользователя. Вы этого не хотите.

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

FlashWindow

Microsoft не объясняет так много слов, что FlashWindow делает. К сожалению, он не отправляет конкретное сообщение (скажем, WM_FLASH или аналогичный), который бы облегчил захват и аннулирование этого поведения. Вместо, FlashWindow делает три вещи:

  1. Устанавливает системный таймер для интервалов мигания
  2. Отправляет WM_NCACTIVATE сообщение для первой вспышки
  3. Отправляет WM_NCACTIVATE сообщение об истечении времени таймера (при получении WM_SYSTIMER)

В зависимости от того, как компонент вызывает FlashWindow, это может быть неопределенным, пока не произойдет другой тайм-аут, пока у него не будет фокуса или только один раз. Каждое сообщение WM_NCACTIVATE активирует или деактивирует NC-зону (строка заголовка, кнопка на панели задач). Это не меняет фокус ввода.

Вызов

Любое решение для предотвращения перепрошивки немного вовлечено. Основными проблемами являются:

  1. WM_SYSTIMER событие отправляется асинхронно с PostMessage и не получает WndProc метод формы (обрабатывает только синхронные сообщения)
  2. WM_NCACTIVATE Сообщения также используются, когда пользователь нажимает на строку заголовка или кнопку панели задач, чтобы установить фокус ввода, просто отмена этих сообщений будет иметь нежелательные побочные эффекты
  3. FlashWindow всегда будет мигать хотя бы один раз, независимо от WM_SYSTIMER стрельба или нет.

WM_SYSTIMER сообщение недокументировано Имеет значение 0x0118 и используется внутри Windows для определения времени таких вещей, как мигание каретки, задержка открытия меню и т. д. Здесь оно используется для промежутка времени между вспышками.

Решение

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

protected override void WndProc(ref Message m)
{
    bool messageHandled = false;
    if (m.Msg == WM_NCACTIVATE)
    {
        // add logic here to determine user action, losing focus etc and set 
        // messageHandled and m.Result only when user action is not the cause 
        // of triggering WM_NCACTIVATE
        m.Result = IntPtr.Zero;
        messageHandled = true;
    }

    if(!messageHandled)
        base.WndProc(ref m);
}

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

Следующий код дает вам больше контроля. Вы можете использовать его, чтобы реагировать на саму вспышку. Обычно главное окно не получает WM_SYSTIMER события так часто, но вам придется экспериментировать, должны ли вы делать исключения. Кажется, что для FlashWindow, wParam всегда установлен на 0xFFF8, но поэкспериментируйте с этим, так как это нигде не задокументировано.

public class MyMessageFilter : IMessageFilter
{
    // an application can have many windows, only filter for one window at the time
    IntPtr FilteredHwnd = IntPtr.Zero;

    public MyMessageFilter(IntPtr hwnd)
    {
        this.FilteredHwnd = hwnd;
    }

    public bool PreFilterMessage(ref Message m)
    {
        if (this.FilteredHwnd == m.HWnd && m.Msg == WM_SYSTIMER)
            return true;     // stop handling the message further
        else
            return false;    // all other msgs: handle them
    }
}

Чтобы активировать этот фильтр сообщений, просто добавьте следующую строку в событие загрузки формы:

Application.AddMessageFilter(new MyMessageFilter(this.Handle));

Следующие константы должны быть размещены на уровне класса. Они используются в обоих разделах кода выше:

public const UInt32 WM_SYSTIMER = 0x0118;
public const UInt32 WM_NCACTIVATE = 0x86;

Заключение

Хотя сама проблема разрешима, это далеко не просто. С вышеупомянутыми ручками вы должны пройти довольно далеко. Используйте фильтр, чтобы предотвратить мигание, но тогда все еще происходит первая "вспышка". Использовать WinProc переопределите, чтобы предотвратить первый, но добавьте некоторую логику, чтобы приложение не работало слишком странно (то есть: всегда неактивная строка заголовка или всегда активная). У вас уже есть код, который вы можете объединить с этим, чтобы установить некоторые логические флаги.

Реализация IProtectFocus::AllowFocusChange в объекте

Реализуйте IServiceProvider на том же объекте, который реализует IOleClientSite, затем ответьте на IServiceProvider::QueryService для SID_SProtectFocus с объектом, который предоставляет IProtectFocus

Это новый интерфейс в IE7, поэтому старым версиям не повезло.

Другие вопросы по тегам