Как управлять окном переднего плана между процессами?

У меня есть два проекта, которые работают как отдельные процессы, но принадлежат одному и тому же приложению:

  • Мастер (содержит TMasterMainForm и TMasterModalForm)
  • Раб (содержит TSlaveForm)

Типичный способ использования этого приложения выглядит следующим образом:

  1. Мастер запускается и показывает файл TMasterMainForm.
  2. Пользователь может запустить ведомое устройство, нажав кнопку в TMasterMainForm.
  3. Master запускает процесс Slave.
  4. Ведомый показывает TSlaveForm.
  5. Мастер отправляет TForm.Handle из TMasterMainForm подчиненному. (через IPC = WM_COPYDATA)

Шаг 5 сделан для того, чтобы при закрытии Ведомого он мог установить окно переднего плана обратно на TMasterMainForm. Это сделано для улучшения пользовательского опыта.

Это работало нормально, пока мы не представили TMasterModalForm.

TMasterModalForm можно запустить с помощью другой кнопки в TMasterMainForm. Это отдельное окно, но оно отображается как модальное и имеет TMasterMainForm в качестве явного родителя всплывающего окна.

Теперь, когда TSlaveForm закрывается, подчиненное приложение вызывает SetForegroundWindow на дескрипторе TMasterMainForm, но это больше не правильно, так как поверх него находится модальная форма (TMasterModalForm).

Итак, вопрос:

Как мне установить окно переднего плана в этой нетривиальной ситуации?

PS: Это упрощенное описание, реальное приложение также делает это окно переднего плана наоборот.


person Jens Mühlenhoff    schedule 22.02.2012    source источник
comment
@Sertac Конечно, Application.BringToFront было бы более естественным?   -  person David Heffernan    schedule 22.02.2012
comment
@ Дэвид - Нет, это должно вызываться из «раба». Йенс изменил бы только одну строку своего кода.   -  person Sertac Akyuz    schedule 22.02.2012


Ответы (2)


Вы не можете установить окно переднего плана в основную основную форму, потому что оно отключено. Он отключен, потому что вы показываете модальную форму, владельцем которой является эта основная форма.

Очевидное решение состоит в том, чтобы установить окно переднего плана в качестве модальной формы, а не основной формы. Поскольку ваше подчиненное приложение, по-видимому, не может легко узнать, какое окно активно, вам следует переработать свой IPC, чтобы подчиненное приложение могло запрашивать главное приложение, какое окно активно, а затем сделать это окно переднего плана.

Более элегантным решением было бы позволить главному приложению вызывать SetForegroundWindow. На самом деле было бы проще просто вызвать Application.BringToFront из главного процесса. Конечно, ведомый процесс все равно должен отправить сообщение ведущему, чтобы вызвать это. Последняя часть головоломки связана с ограничениями на кражу фокуса, но вы можете сделать это с помощью AllowSetForegroundWindow. Вам нужно, чтобы ваш подчиненный процесс вызывал это, передавая идентификатор главного процесса.

person David Heffernan    schedule 22.02.2012
comment
Тогда я выберу более элегантное решение :). - person Jens Mühlenhoff; 22.02.2012
comment
К сожалению, вызов AllowSetForegroundWindow не работает. Приложение Master не выводится на передний план, вместо этого просто мигает значок на панели задач. - person Jens Mühlenhoff; 22.02.2012
comment
Я совершенно уверен, что это работает. Я не могу диагностировать, что не так, хотя. - person David Heffernan; 22.02.2012
comment
Когда ты звонишь AllowSetForegroundWindow? Вы делаете это непосредственно перед отправкой сообщения главной программе? Вы знаете, что должны делать это каждый раз, когда отправляете это сообщение. - person David Heffernan; 22.02.2012
comment
Я делаю это каждый раз и непосредственно перед отправкой сообщения. AllowSetForegroundWindow возвращает False, а GetLastError возвращает ERROR_INVALID_PARAMETER, я дважды проверил идентификатор процесса. - person Jens Mühlenhoff; 22.02.2012
comment
@JensMühlenhoff отлично работает для меня. PID должен быть неправильным. Это единственный параметр! - person David Heffernan; 22.02.2012
comment
Я превратил это в новый вопрос: stackoverflow.com/questions/9399023 / - person Jens Mühlenhoff; 22.02.2012

Чтобы решить вашу проблему, я бы не стал вызывать SetForegroundWindow из подчиненного приложения, а вместо этого отправил бы пользовательское сообщение активации в главное приложение. В этом сообщении вы можете передать нужный дескриптор в WParam или LParam. Главное приложение само может определить, может ли оно активировать этот дескриптор или вместо этого нужно активировать модальную форму. Вы возлагаете ответственность за активацию нужного окна на само приложение.

PS: вам не нужно WM_COPYDATA для отправки дескриптора. Вы можете просто передать в WParam или LParam любое сообщение, которое вы составили. Это касается дескриптора, отправляемого обратно, как я объяснял выше, а также дескриптора, который отправляется подчиненному в первую очередь.

person GolezTrol    schedule 22.02.2012
comment
Основное приложение не может вызывать SetForegroundWindow, если оно не владеет текущим активным окном, см. раздел «Примечания» здесь: msdn.microsoft.com/en-us/library/windows/desktop/ - person Jens Mühlenhoff; 22.02.2012
comment
Хорошо, Дэвид дал недостающую часть головоломки, AllowSetForegroundWindow, я должен был заметить это раньше... - person Jens Mühlenhoff; 22.02.2012