Почему TForm.SetBounds работает правильно только тогда, когда TForm.Position установлено в poDefault во время разработки
Я заметил кое-что очень странное. Я сохраняю свойства top, left, width и height формы, когда она закрывается, и использую эту информацию для восстановления последней позиции формы, когда она снова открывается, путем вызова SetBounds с использованием ранее сохраненной информации. Это работает хорошо, но только если свойство Position формы установлено в poDefault во время разработки. Если установлено другое значение, например poDesigned, poScreenCenter или poMainFormCenter, SetBounds не восстанавливает предыдущую позицию и размер формы.
Вот странная часть. То, что имеет значение, это то, что свойство Position установлено во время разработки. Я могу изменить значение этого свойства во время выполнения на poDefault, и вызов SetBounds по-прежнему не работает правильно. Я пробовал что-то вроде следующего
if Self.Position <> poDefault then
Self.Position := poDefault;
как в обработчике события OnCreate формы, так и из переопределенного конструктора (и установили Position в poDefault в конструкторе и вызвали SetBounds в обработчике события OnCreate). Во всех случаях изменение свойства Position формы на poDefault во время выполнения не решает проблему, которую я наблюдал с SetBounds. Единственный непротиворечивый шаблон, который я обнаружил, состоит в том, что SetBounds работает так, как и должно, только если свойство Position формы было poDefault во время разработки.
Есть и другие вещи, которые я заметил относительно того, как SetBounds работает, когда свойство Position формы не установлено в poDefault во время разработки. Например, форма, свойство Position которой установлено в poScreenCenter во время разработки, не обязательно будет отображаться по центру экрана при вызове SetBounds. Однако он не отображается в верхнем левом месте, определенном SetBounds, и не учитывает ширину и высоту, указанные в вызове SetBounds. Позвольте мне повторить, однако, что я устанавливаю свойство Position формы в poDefault перед вызовом SetBounds. Я даже вставил вызов Application.ProcessMessages между двумя операциями, но это не решает проблему.
Я тщательно протестировал это на Delphi 10.1 Berlin, работающем под Windows 10. Я также протестировал его на Delphi XE6 на Windows 7. Те же результаты.
Если у вас есть сомнения, создайте приложение VCL с четырьмя формами. В первой форме поместите три кнопки и добавьте что-то вроде следующего OnClick для каждой кнопки:
with TForm2.Create(nil) do
try
ShowModal;
finally
Release;
end;
где конструктор создает TForm2, затем TForm3 и TForm4.
В OnCreate форм со 2 по 4 добавьте следующий код:
if Self.Position <> poDefault then
Self.Position := poDefault;
Self.SetBounds(500,500,500,500);
В form2 установите Position для poDefault, в form3 установите Position для poScreenCenter, а в form4 оставьте Position для значения по умолчанию poDefaultPosOnly. Только form2 появится в 500, 500, с шириной 500 и высотой 500.
У кого-нибудь есть логическое объяснение этого результата?
1 ответ
poDefault
"друзья" означают "позвольте Microsoft Windows позиционировать окно этой формы, когда форма создаст и покажет его".
Вы только что создали объект Delphi - но мне интересно, если он также создал / показал объект Windows (HWND
обрабатывать и все соответствующие Windows внутренние структуры). Особенно с тематическими приложениями, а не с теми, которые используют стандартный pre-XP внешний вид - они имеют тенденцию ReCreateHWND
при показе, потому что предварительная загрузка этих модных тем Windows является относительно дорогой операцией и должна выполняться только при необходимости.
Я думаю, что ваши границы по умолчанию (каждое значение свойства, установленное в конструкторе, может считаться ненастроенным значением по умолчанию, которое будет настроено позже после создания объекта) правильно игнорируются, когда вы (или TApplication
- это мало что меняет для темы) наконец сделаю FormXXX.Show
,
Это происходит во время последовательности "сделай мне окно и покажи его", когда твоя форма смотрит на ее свойства и сообщает MS Windows что-то вроде "теперь я хочу создать твой внутренний HWND-объект и расположить его по умолчанию с координатами / размером на твое усмотрение",
И это абсолютно правильное поведение - иначе КОГДА и КАК могли TForm
применить Position
имущество??? Просто не имеет смысла спрашивать у Windows координаты окна, которого еще нет на экране и, возможно, никогда не будет. Windows предлагает координаты / размеры по умолчанию для той самой секунды, которую она запрашивает, смотря, сколько других окон там и где они расположены (и видео драйверы AMD/NVidia также могут применить к ним свою коррекцию).
Было бы бессмысленно приобретать значения по умолчанию сейчас и применять их через два часа, когда, вероятно, все будет иначе - разное количество других окон и их различное положение, другой набор мониторов, подключенных и с другим разрешением и т. Д.
Просто подумайте о ноутбуке типа "замена рабочего стола". Он был установлен на столе, подключенном к большому стационарному внешнему монитору. Затем - давайте представим - я запустил ваше приложение, и оно создало объект tform Delphi, и в конструкторе он запросил положение в MS Windows - и Windows по праву предложила положение на этом очень большом мониторе. Но через час я отключил ноутбук и ушел с ним. Теперь через час я скажу вашей заявке показать форму - и что она будет делать? отобразить его с координатами, принадлежащими этому отсоединенному внешнему дисплею? За пределами области просмотра внутреннего дисплея ноутбука, который у меня есть только на данный момент? Должна ли эта форма отображаться в теперь "невидимой" позиции только потому, что когда я запустил приложение, тогда это место еще было там видно??? Думаю, способ запутать пользователей без выгоды.
Таким образом, единственное правильное поведение - запрашивать в Windows координаты по умолчанию в эту самую секунду, КОГДА форма переходит от скрытой к видимой, а не на секунду раньше.
А это значит, что если вы хотите переместить свою форму - вы должны сделать это после того, как она была показана. Поместите свой Self.SetBounds(500,500,500,500);
в OnShow
обработчик события. Поэтому позвольте MS Windows материализовать вашу форму в положение по умолчанию, как того требует poDefault
в Position
свойство - и переместите ваше окно после этого. Попытки переместить окно, которое еще не существует, выглядят для меня совершенно бесполезными.
Либо ПРЕДУСТАНОВИТЕ свою форму (при построении последовательности), чтобы явно игнорировать значения по умолчанию MS Windows и использовать предварительно установленные шнуры (через poDesigned
значение), или пусть форма спрашивает координаты Windows, но переместите его с SetBounds
после того, как это стало видимым через OnShow
обработчик.