2016-08-19 9 views
5

Я заметил что-то очень странное. Я сохраняю свойства верхней, левой, ширины и высоты формы при ее закрытии и используя эту информацию для восстановления последней позиции формы, когда она снова открывается, вызывая SetBounds с использованием ранее сохраненной информации. Это хорошо работает, но только в том случае, если свойство Position формы установлено в poDefault во время разработки. Если установлено что-то другое, например poDesigned, poScreenCenter или poMainFormCenter, SetBounds не восстанавливает предыдущую позицию и размер формы.Почему TForm.SetBounds работает правильно, когда TForm.Position установлен на poDefault во время разработки

Вот странная часть. Что, по-видимому, имеет значение, когда свойство Position задано во время разработки. Я могу изменить значение этого свойства во время выполнения до poDefault, и вызов SetBounds по-прежнему работает неправильно. Я пытался что-то вроде следующего

if Self.Position <> poDefault then 
    Self.Position := poDefault; 

как в обработчике события OnCreate формы, а так же, как от перегруженной конструкторы (и установить позицию для poDefault в конструкторе, и называется SetBounds в OnCreate обработчик событий). Во всех случаях изменение свойства позиции формы на poDefault во время выполнения не устраняет проблему, которую я наблюдал с помощью SetBounds. Единственная последовательная модель, которую я нашел, заключается в том, что SetBounds работает так, как это должно быть, только если свойство позиции формы было poDefault во время разработки.

Есть другие вещи, которые я заметил в отношении того, как SetBounds работает, когда свойство позиции формы не установлено в 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 установите позицию в poDefault, на Form3 задать положение для poScreenCenter, а form4 оставить позицию, установленную по умолчанию, poDefaultPosOnly. Только форма2 появится в 500, 500, с шириной 500 и высотой 500.

У кого-нибудь есть логическое объяснение этого результата?

+0

Попробуйте добавить 'процедура CreateParams (var Params: TCreateParams); переопределить; 'в _protected_ раздел вашего определения формы и написать ' inherited; if Self.Position <> poDefault then Self.Position: = poDefault; 'in _CreateParams_. – Miamy

+0

Miamy: Overridding CreateParams, и настройка Position to poDefault сработала !. Очень хорошо! Это не ответ (так как я просил объяснения), но это очень хорошее решение проблемы. И, поскольку у всех моих форм есть общий предок, я могу переопределить этот метод у предка, а затем можно бесплатно вызвать SetBounds в обработчике событий OnCreate (или переопределенном конструкторе). Спасибо за предложение. –

ответ

2

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, свойство - и переместите свое окно после этого. Попытки переместить окно, которое еще не существует, выглядят бесполезно для меня.

Либо PRESET вашей формы (в построении последовательности) явно игнорировать по умолчанию MS Windows и использовать предустановленные шнуры (через poDesigned значения), или пусть форму задать координаты для Windows, но переместить его с SetBoundsпосле он получил видимым через обработчик OnShow.

+0

Кажется, достаточно назвать 'HandleNeeded' перед' SetBounds'. –

+0

Во-первых, мой код более сложный, чем я подразумевал. При восстановлении позиции я принимаю во внимание изменения разрешения монитора и количества мониторов. Но это не имеет значения. Кажется, это правильный ответ (за исключением того, что SetBounds не работает в OnShow, но работает с OnActivate - хотя я стараюсь избегать использования обоих этих обработчиков событий). Однако, учитывая, что переопределение метода CreateParams (как предложено в комментарии Майами), позволяет изменить положение на poDefault, после чего SetBounds работает из OnCreate (или переопределенного конструктора), это объяснение имеет смысл. –

+0

@ OndrejKelle - да, наверное. Но почему вы используете грязные хаки, делая ** неожиданные ** сложные вызовы в конструкторе и потенциально запуская длинные цепочки обработчиков событий в полуприготовленном окне, когда вы можете просто переключить действие MOVE WINDOW на место, которое VCL искренне ожидает от него быть? –