Как сделать модальное окно delphi

Добавил пользователь Алексей Ф.
Обновлено: 17.09.2024

Мое приложение основано на модальных формах. Основная форма открывает одну форму с помощью ShowModal, эта форма открывает другую форму с помощью ShowModal, поэтому мы сложили модальные формы. Иногда возникает проблема, заключающаяся в том, что когда мы вызываем ShowModal в новой форме, он скрывается за предыдущими формами, а не отображается сверху. После нажатия alt + tab форма возвращается наверх, но это не хорошее решение. Вы сталкивались с этой проблемой и как справились с ней?

ИЗМЕНИТЬ

Я использую Delphi 7.

Вы не упомянули, какая версия Delphi .

Более новые версии Delphi добавили два новых свойства в TCustomForm: PopupMode и PopupParent. Установка PopupParent вашего модального диалога в форму, которая создает это диалоговое окно, гарантирует, что дочерняя форма остается поверх своего родительского. Обычно это решает проблему, которую вы описываете.

Я думаю, что эта пара свойств была добавлена ​​в Delphi 2006, но, возможно, это был 2005 год. Они определенно присутствуют в Delphi 2007 и более поздних версиях.

РЕДАКТИРОВАТЬ: После того, как вы увидели, что используете Delphi 7, у меня есть только одно предложение: в коде, отображающем вашу модальную форму, вы отключаете форму, создающую ее, и повторно включаете по возвращении. Это должно помешать получающему окну получать входные данные, что может помочь сохранить правильный Z-порядок.

Нечто подобное может работать (не проверено, поскольку я больше не использую D7):

Если Form2 создает модальное окно (как вы уже упоминали), просто повторите процесс - отключите Form2, создайте Form3 и покажите его модально, и снова включите Form2, когда оно вернется. Убедитесь, что вы используете try..finally, как я уже показал, чтобы в модальной форме что-то пошло не так, форма создания всегда была включена заново.

Извините за добавление отдельного ответа, но я провел немного больше исследований, и некоторые из них указывают, что мой предыдущий ответ (DisableProcessWindowsGhosting) не помогает. Поскольку я не всегда могу воспроизвести эту проблему, я не могу сказать наверняка.

Я нашел решение, которое кажется подходящим. Я ссылался на код в Delphi 2007 для метода CreateParams, и он очень близко соответствует (без всего остального кода, который обрабатывает PopupMode).

Я создал модуль, ниже которого подклассы TForm .

Затем я включаю этот модуль в модуль формы, а затем изменяю класс формы (в файле кода .pas) с class(TForm) в class(TModalForm)

Это работает для меня, похоже, близко к решению CodeGear.

Единственная проблема, которую я вижу, состоит в том, что это вызовет проблемы с функцией, которая позволяет пользователю свернуть, переместить или закрыть главное окно приложения, которое не отвечает . Но при этом вам не нужно покрывать каждый вызов кодом Self.Enabled := False .

Просто установите для свойства Visible формы, которую вы хотите открыть модальным, значение False . Затем вы можете открыть его с помощью .ShowModal(); , и он будет работать.


Загрузки всякие

Связь

Открывают из одной модальной формы другую.

И тут внезапно через пару секунд первая форма вылазит поверх второй формы. При любом клике вне 1-й формы возвращаюсь на 2-ю форму.

сколько существует Delphi, столько и присутствует этот глюк.

Согласно документации если установлен PopupMode = pmExplicit и PopupParent is nil, тогда неявно используется Application.MainForm как PopupParent.

Значит, у нас два окна с одним Popup-родителем.

Если установить PopupMode = pmAuto, в качестве PopupParent будет использоваться Screen.ActiveForm.

установка как PopupParent, так и PopupMode имеет мало смысла, потому что они связаны между собой PopupParent устанавливает pmExplicit, а pmAuto сбрасывает всплывающий родительский элемент на nil

Set Application.ModalPopupMode to pmAuto

Если одна форма открывается как модальная. Из нее открывается другая форма как модальная с помощью ShowModal, у нас получаются stacked modal forms.

Предыдущие заметки по теме: номер раз, номер два.

Решение проблемы с иконкой в Aero

Проблема наблюдается при включённой теме Aero в Windows: если закрыть окно, то видно, как иконка приложения меняется на иконку по умолчанию.

Это заметно как раз в тот момент, когда окно начинает уменьшаться и плавно исчезает с экрана (верхняя часть картинки):

image

Проблему я решил таким образом:

Решение проблемы при сворачивании модального окна

Автоматическое закрытие окна по клавише Escape

Об этом я уже писал ранее. Единственное, для удобства, добавим свойство CloseByEscape, которое по умолчанию выставлено в True.

Автоматическое уничтожение окна при закрытии

Это очень полезно при использовании MDI-стиля приложения – MDIChild-окна, как правило, при закрытии надо сразу уничтожать (иначе, по умолчанию, они просто сворачиваются). Тоже бывает полезно и для не модальных диалогов. По-умолчанию свойство FreeOnClose установлено в False, но Вы можете установить его в True, заменив значение после слова default и добавив инициализацию переменной в методе InitializeNewForm.

Сразу замечу, что на самом деле это свойство работает не как FreeOnClose, а как ReleaseOnClose. Потому что. И на самом деле, это свойство не имеет значения, если у формы в обработчике OnClose переопределяется параметр Action.

Автоматическое уничтожение объектов, созданных в коде модуля формы

Об этом я писал ранее. Единственным отличием, от приведённого ранее кода будет то, что списков для уничтожения объектов будет два: один список будет уничтожать объекты при уничтожении формы (после обработчика OnDestroy), а другой – при её скрытии (после обработчика OnHide). Эти события являются зеркальными к событиям OnCreate и OnShow соответственно.

Итого

Имея базовую форму мы смогли (без дублирования кода):

  • обойти странности VCL (с иконкой и сворачиванием модального окна);
  • получили возможность скрывать форму по Escape и уничтожать её, изменяя лишь свойства через инспектор объектов;
  • мы научили форму автоматически уничтожать простые объекты (наследники от TObject).

Также я добавил тестовый проект, который наглядно демонстрирует новые возможности.

28 коммент.:

Мысли после прочтения.
1)Еще полезно, даже не в форму, а в файл проекта добавить функцию низкоуровневого перехвата клавиш, куда можно будет добавлять системные сочетания (альт+таб и.т.д)
2)Изредка встречалось предложение об эмуляции окна IDE (пользователями демо-версий компонентов), полезно, но вещь редкая.
3)Есть вещи, не слишком реализованные дефолтными методами - корректное сворачивание в трей с отрисовкой, или не реализованными в старых средах вообще - прогресс бар и превьюшка в Win7.
4)обычно имеется целый набор классов\функций навроде накопленного годами собственного api, было бы интересно иметь метод также вставлять автоматом ссылки на него.
Это не предложение, это просто мысли вслух, с уважением, dj vk.

Спасибо, интересные мысли.

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

Честно говоря, я не очень понял зачем понадобился флажок-свойство FreeOnClose.
Единственный вариант, к-рый мне приходит в голову - таким образом ты собираешься регулировать поведение деструктора.

Анонимный, насколько я помню, в той книге описан довольно простой способ - форма встраивается в любую другую только в Run-Time. У фрейм есть преимущество - их можно встраивать в Design-Time.

Именно поэтому я в своё время не стал это делать, определив для себя: всё что можно встраивать - надо делать в фрейме.

Хотя мысль интересная и не сложна в реализации - думаю, что добавлю это, спасибо за напоминание.

Сергей, FreeOnClose не регулирует поведение деструктора, деструктор вызывается автоматически, когда форме говорят Free (либо явно, либо неявно через Release).
Флаг FreeOnClose эквивалентен обработчику

procedure TForm1.Close(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;

и просто позволяет его не писать. Это бывает полезно, когда за временем жизни формы не следит создающий форму код. (Ну т.е. пользователь закрыл форму, и она уничтожилась - типичная ситуация для MDI)

Николай, ну я об этом же. Только другими словами.
В таком подходе естьодин маленький минус
создаем форму в ран-тайм
затем что-то делаем
и забываем написать
FreeOnClose := True;
и забываем написать деструктор, ну или вызвать FreeAndNil

Т. е. с моей точки зрения этот флажок лишний.
А лучше всего всегда пользоваться FreeAndNil.
А там уж VCL само разбереться.

Сергей, если мы забываем что-то уничтожить, то MemCheck, FastMM или EurekaLog эту утечку памяти покажут, на сегодня это не проблема.
Вообще, я приучил себя избавляться от генерируемых IDE переменных типа var Form1: TForm1 и все формы, в т.ч. и MainForm, я стараюсь создавать не в DPR-файле, а по мере необходимости.
Если использовать сценарий Create - ShowModal - Free, то флаг и не нужен.
Я этот флаг создавал для примерно такого сценария: FindOrCreateForm(TFormClass) - Show/BringToFront. Т.е. вызывающая сторона не заботится о времени жизни формы, её задача эту форму отобразить. При этом флаг я ставлю в True в RunTime.
. наверное Вы правы, в DesignTime этот флаг не очень-то и нужен, я просто хотел показать преимущества визарда.

Спасибо, попробую прикрутить к своим проектам первые 3 фичи. Хотелось бы понять, намеренно ли в модулях использующих BaseForms, таких как: BaseFormsTest/uMainForm - в .pas форма объявлена как TMainForm = class(TBaseForm), а в .dfm как object MainForm: TMainForm вместо *inherited* MainForm: TMainForm? Хотя в инспекторе объектов главной формы все равно видны свойства CloseByEscape и FreeOnClose, и IDE почему-то возвращает object вместо установленного вручную inherited в тексте формы (Alt-F12).

2All. У меня сейчас такой период, когда вдохновения что-то писать совсем нет (семья, работа, весна). Поэтому прошу прощения у всех за отсутствие активности.

IL, класс TBaseForm не имеет своего собственного DFM-файла. Отсюда следует, что визуальная часть TMainForm (т.е. та часть, которая редактируется в дизайнере) ни от чего не наследуется. Поэтому в DFM-файле главной формы подставляется слово object, как признак корневого ресурса формы (или фреймы, или датамодуля).

Если вы создадите наследник от TMainForm, то в DFM-файле наследника уже будет слово inherited, которое говорит о том, что при обработке DFM-файла наследника сначала надо обработать DFM-файл предка.

Когда вы руками прописываете слово inherited, среда видит, что у предка нет DFM-файла и исправляет это. Но если сделать наоборот, т.е. inherited заменить на object, можно заметить интересные побочные эффекты.

1) спасибо, учту (у меня такая проверка есть в момент создания формы и считывания её положения из реестра).
2) да, тот кусок кода на DelphiKingdom взят как раз из моей базовой формы. Я планировал его включить, но там есть один нюанс, который я реализовал грубо. я хочу попробовать найти более элегантное решение, а заодно проверить, как это отрабатывает в Delphi 2010 и старше.

IL, спасибо за предложение помощи. Я вот посмотрел, чем отличаются исходники VCL в Delphi 2010 от Delphi 7 - похоже, что в Delphi 2010 проблемы масштабирования уже решены. Я попробовал воспроизвести баг (достаточно в контролах выставить якори, отличными от дефолтовых) - в Delphi 7 баг воспроизводится, в Delphi 2010 - уже нет, всё масштабируется корректно.
Единственное, что не сделано в Delphi 2010 - это автомасштабирование фрейм в случае их ручного создания из RunTime - это я и добавлю в BaseForms.pas.
Если у Вас есть какой-нибудь демо пример с проблемами масштабирования - с удовольствием посмотрю.

P.S.: обновил исходники - добавил базовый датамодуль и прописал default значения для свойств OldCreateOrder, Color, ParentFont, Left, Top -- чтобы в dfm файле не сохранялась всякая ерунда.

не, AutoSize это не масштабирование. Масштабирование это именно пропорциональное изменение размеров формы и контролов, расположенных на ней, в зависимости от PixelsPerInch, установленном в Design-Time (и сохранённом в DFM) и от текущего значения PixelsPerInch.
Т.е. если Screen.PixelsPerInch <> DFM.PixelsPerInch - нужно делать масштабирование.

Так вот, это масштабирование делает VCL, причём в Delphi 7 это масштабирование работает довольно криво: оно не учитывает констрейнты, а Anchors контролов учитываются "лишний раз", из-за этого контролы могут уехать за пределы формы. Чтобы это воспроизвести, достаточно на форму кинуть кнопку (куда-нибудь в середину формы) и установить Anchors = [akRight, akBottom].

В Delphi 2010 масштабирование уже корректно обрабатывает Anchors и масштабирует констрейнты, и даже поля (Margins), поэтому тут вроде бы и фиксить нечего.
Но есть одно но: при сохраненнии фреймы в DFM значение PixelsPerInch не пишется. Поэтому если фрейму создать и встроить в форму в RunTime (а-ля TMyFrame.Create(Self).Parent := Self), то фрейма не промасштабируется. Вот это и надо фиксить.

Ух ты! Я и не знал, что Delphi 2010+ уже действительно масштабирует контролы. Все же мне кажется, что старые формы из D7 с растровым шрифтом MS Sans Serif даже откомпилированные в D2010+ при dpi>96 будут смотреться весьма криво. Новые формы имеют Tahoma по-умолчанию.

Я тоже не знал, но предполагал, что там это уже пофикшено (ну не могли разработчики пройти мимо этого бага). Кстати я вот решил включить в BaseForms поддержку Delphi7, всё-таки есть у меня ещё несколько проектов, которые портировать на Delphi2010 не имеет смысла, да и у сообщества это пока ещё популярная среда. Да и на память код по масштабированию сохраню :)

> старые формы из D7 с растровым шрифтом MS Sans Serif .. будут смотреться весьма криво
Угу. Поэтому я сделал так (пока об этом не писал), что BaseForm.ParentFont = True по умолчанию, и в DFM шрифт не сохраняется, а используется Application.DefaultFont (или DefFontData в старых Delphi). Это позволяет централизовано устанавливать шрифт по-умолчанию. Правда при этом надо себя приучить не трогать свойство Font у контролов и изменять размер/начертание в RunTime.

Код масштабирования в D7 будет очень полезен. Его-то я и готов погонять под madexcept на разных унаследованных из D7 формах. Правда, хотелось бы все-таки это делать в XE, а не D7 :)
Почему все-таки не стоит трогать шрифт контролов в дизайнере, например, включить Bold, поменять размер или цвет, если использовать тот же базовый шрифт Tahoma, что и в BaseForms?

IL
Дело в том, что если свойство ParentFont у формы выставлено в True, то подставляется дефолтовый шрифт приложения (а не шрифт, объявленный в родительском классе).
В Delphi 2010 - это Application.DefaultFont. В Delphi 7 - Graphics.DefFontData.

Вообще термин Parent в VCL относится не к наследованию, а к расположению WinControl'ов относительно друг друга. А для формы Parent'а как бы и нету.

(прошу прощения, что долго не отвечал)

На вопрос "Почему все-таки не стоит трогать шрифт контролов в дизайнере" у меня такой ответ: если Вы в своём приложении хотите дать возможность пользователю менять шрифт, и при этом хотите обойтись малой кровью - то достаточно изменить Application.DefaultFont перед созданием главной формы. Если при этом все видоизменения шрифта делаются в коде, то больше ничего делать и не надо.

Если же шрифт задаётся в дизайнере, то тогда придётся делать больше телодвижений.

Для всех
Я провёл много экспериментов с масштабированием форм в новых версиях Delphi - всё-таки проблемы масштабирования остались. VCL Form and Frame Scale Fix

Ничего страшного, что не отвечали, у меня было время причесать формы. Насчет DefaultFont и ParentFont формы теперь понятно. Не думаю, что мне когда-либо понадобится дать пользователю шрифт бизнес-приложения, так как размер кнопок, к сожалению, не промасштабировать. И смысла в этом не вижу. Стандартных вариантов тоже не много: Tahoma,8 или Segoe,9 (Виста+).
Спасибо, пора попробовать ваш Scale Fix в действии :)

IL, за ссылки и исследования - спасибо.
Старые Delphi по-умолчанию используют тот шрифт, который зашит в исходниках VCL - см. Graphics.pas, DefFontData и InitDefFontData.
В новых Delphi хитрее - они смотрят реестр, параметр MS Shell Dlg 2 (в ветке HKLM), который равен Tahoma.
Есть ещё такой момент: при разработке новой формы, её шрифт сохраняется в DFM (т.к. ParentFont = False), поэтому на практике получается так, что шрифт всё-равно определяется разработчиком.

Для исходников под 2010 Delphi пришлось немного изменить путь к файлу inc

вместо

Под XE4 с трудом, но собралась.
Несколько мыслей:

1) Необходимо добавить Win64
2) Нельзя использовать SetWindowPos(Self.Handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW); в TBaseForm.DoClose(var Action: TCloseAction);
Это влечет за собой несолько негативных момнетов:

- Если форма была создана, но не показана, и из нее вызвыно Close то форма мигает на экране. Иногда в моем проекте она вообще остается на экране, любой клик по ней - AV
- Более серъезный баг - при вызове SetWindowPos в OnClose вызывается OnActivate.

> 1) Необходимо добавить Win64
Возможно. Для Вас ведь это не проблема?

> 2) Нельзя использовать SetWindowPos
Простите, я Вас не понял. SetWindowPos я явно не дёргаю. Вероятно, дёргаете Вы. Без описания причин и кода, ничего не могу сказать.
В DoClose - вообще ничего не делается, кроме переопределения Action (и то, при условии, что установлено свойство FreeOnClose)

> форма была создана, но не показана, и из нее вызвыно Close
Зачем Вы так делаете?

> Более серъезный баг
Это проявляется с использованием BaseFroms? Попробуйте отключить (закомментировать) вызов RestoreFormsPositions в TBaseForm.WMActivate. Хотя я не уверен, что дело именно в этом.

Прошу прощения, у меня каким то чудом смержились 2 BaseForms.pas файла :)

Насчет закрытия непоказанной формы - я сопровождаю написанный код. Пока код и логика не разделены, приходится терпеть.
Проблема с OnActivate именно из за SetWindowPos в DoClose (в моем BaseForms).

Так вот на модальном окне у меня расположены две кнопки btnLog с ModalResult = mrOk и
btnExit c ModalResult = mrCancel

property ModalResult: TModalResult;

Setting the TButton component’s ModalResult property is an easy way to make clicking the button close a modal form. When a button is clicked, the ModalResult property of its parent form is set to the same value as the button’s ModalResult property.

For example, if a dialog box has OK and Cancel buttons, their ModalResult properties could be set at design time to mrOk and mrCancel, respectively. At runtime, clicking the OK button then changes the dialog’s ModalResult property to mrOk, and clicking the Cancel button changes the dialog’s ModalResult property to mrCancel. Unless further processing is required, no OnClick event handlers are required for the buttons.

Posted via ActualForum NNTP Server 1.4

Self это аналог this в С++ но зачем он здесь - получается владелец - сам экземпляр класса который создается Поясните смысл этого .

Posted via ActualForum NNTP Server 1.4

Так вот на модальном окне у меня расположены две кнопки btnLog с ModalResult = mrOk и
btnExit c ModalResult = mrCancel

Читайте также: