Delphi - Създаване на контроли преди стартиране на формуляр Create?

Е, моят проблем е следният:

Имам приложение Delphi 5, което по същество пренасям към Delphi 2010 (заменям старите компоненти с най-новите им версии, коригирам неизбежните проблеми с низовете Ansi/Unicode и т.н.) и се натъкнах на някакъв проблем.

При създаване на една от нашите форми се случва нарушение на достъпа. След като го прегледах, стигнах до заключението, че причината за това е, че един от сетерите, извикан в Create, се опитва да промени свойство на обект във формуляра, който все още не е създаден.

Намалих го малко, но кодът изглежда основно така:

В декларация на формуляр:

property EnGrpSndOption:boolean read fEnGrpSndOption write SetGrpSndOption;

Във формуляра Създаване:

EnGrpSndOption := false;

В изпълнение:

procedure Myform.SetGrpSndOption(const Value: boolean);
begin
  fEnGrpSndOption := Value;
  btGrpSnd.Visible := Value;
end;

Като хвърлих ShowMessage(BooltoStr(Assigned(btGrpSend), true)) точно преди btGrpSnd.Visible := Value, потвърдих, че проблемът е, че btGrpSnd все още не е създаден.

btGrpSend е LMDButton, но съм почти сигурен, че не е съвсем подходящ, тъй като дори още не е създаден.

Въпреки че осъзнавам, че вероятно трябва да присвоя стойност само след като потвърдя, че контролата е присвоена, това просто ще доведе до това, че стойността, зададена в create, не е зададена на действителната контрола.

Така че това, което искам да направя, е да намеря начин да се уверя, че всички контроли във формуляра са създадени ПРЕДИ моят Create да бъде стартиран.

Всяка помощ при извършването на това или информация относно това как Delphi създава формуляри ще бъде оценена. Работеше в Delphi 5, така че предполагам, че причината за това трябва да бъде спомената някъде сред списъците с промени между версиите. Delphi 2010 все пак е доста по-нов от Delphi 5.


person Michael Stahre    schedule 14.12.2009    source източник
comment
Забравих да спомена това: Получавам въпросното нарушение на достъпа, когато поставям компонента във формуляра си по време на проектиране. Вероятно се случва и по време на изпълнение, но не мога да го потвърдя, тъй като не мога да го стартирам.   -  person Michael Stahre    schedule 14.12.2009
comment
Добре дошъл в Stack Overflow, Майкъл. :)   -  person Mason Wheeler    schedule 14.12.2009
comment
Благодаря ви ^^ Разгледах сайта за други проблеми, свързани с delphi, които и аз съм имал, но обикновено установявах, че повечето от тях вече са били задавани и отговорено някъде;)   -  person Michael Stahre    schedule 14.12.2009
comment
Мисля, че открихме проблема. Вижте редакцията на моя отговор.   -  person Mason Wheeler    schedule 14.12.2009
comment
Видях го и отговорих с още два коментара на този отговор. Наистина трябва да спра да мисля за още нещо, което да кажа веднага след натискането на бутона „Добавяне на коментар“.   -  person Michael Stahre    schedule 14.12.2009
comment
Честно казано не съм съвсем сигурен как това в крайна сметка работи, но го промених, за да наследи от TFrame, заменяйки всички предишни употреби на ClientHeight и ClientWidth в кода (тъй като ги няма по очевидни причини) съответно с Height и Width... Току що го реших. Constructor Create overridden (started with an inherited; call)... Наистина не знам защо това не работеше преди. Може би защото не използвах TFrame досега? Изглежда, че не бяха несъответствия между DFM и типа обект... RAD Studio свърши добра работа, като се оплакваше от тях (и го поправи почти автоматично)   -  person Michael Stahre    schedule 15.12.2009
comment
Това е частта, в която се появява любопитството - Слизайки възможно най-ниско, как работи създаването на контроли в Delphi? Какво прави в какъв ред и т.н.? Възможно ли е да се намери кодът, който управлява създаването от DFM (оригиналния Create, предполагам?), или това е твърдо включено в компилатора?   -  person Michael Stahre    schedule 15.12.2009


Отговори (5)


Както спомена Тобиас (но се застъпва против), можете да промените реда на създаване (точно във формуляра при промяна на реда на създаване).

Но можете също така в метода за настройка да проверите дали формулярът се създава (csCreating във form.componentstate). И ако е така, трябва сами да съхраните тази стойност на свойството и да я управлявате в AfterConstruction.

person BennyBechDk    schedule 14.12.2009
comment
Ще разгледам AfterConstruction. Но ако има такова нещо, не е ли почти винаги по-безопасно да използвате AfterConstruction вместо Create, когато пишете компонент, който има контроли върху него? - person Michael Stahre; 14.12.2009

От вашия коментар, че получавате AV, когато го поставяте по време на проектиране, това означава, че има проблем със самото управление и то не е правилно пренесено напред. За да го възпроизведете по време на изпълнение при контролирани обстоятелства, трябва да напишете малка програма като тази:

Направете ново VCL приложение с един формуляр. Поставете TButton върху формуляра. На OnClick на бутона направете нещо подобно:

var
   newButton: TLMDButton;
begin
   newButton := TLMDButton.Create(self);
   newButton.Parent := self;
   //assign any other properties you'd like here
end;

Поставете точка на прекъсване на конструктора и проследете в него, докато откриете какво причинява нарушението на достъпа.

РЕДАКТИРАНЕ: Добре, след като разгледах коментарите, мисля, че открихме вашия проблем!

Подконтролите на формуляр се инициализират чрез четене на DFM файла. Когато променихте контролата си на TCustomForm, предоставихте ли нов DFM, за да го дефинирате? Ако не, трябва да замените конструктора на формуляра и да създадете контролите и да дефинирате техните свойства ръчно. Няма "магия", която да го инициализира вместо вас.

person Mason Wheeler    schedule 14.12.2009
comment
Направих това току-що и това ме доведе до същия ред код, който бях намерил, като разгледах съобщението за нарушение на достъпа, дадено ми по време на проектиране. Вероятно е същото нещо, което описвам в първоначалния си въпрос, тъй като този ред от код е този, където се случва нарушението на достъпа. Така че това означава, че се случва както по време на проектиране, така и по време на изпълнение по същия начин. Някаква идея защо контролите на компонента, който поставям, не са създадени преди създаването на компонента да бъде извикано, когато очевидно е било обратно в Delphi 5? - person Michael Stahre; 14.12.2009
comment
Отново забравих да кажа: Така или иначе, това е много удобен трик, който определено ще запомня. Изненадан съм, че не се бях сетил вече. Благодаря :) - person Michael Stahre; 14.12.2009
comment
Трябва да се създаде. Ако поставите отметка, всички други обекти във формуляра вече ще бъдат създадени дотогава. Може би нещо се обърка при десериализиране на компонента? Трудно е да се каже без повече информация. Между другото това бутон ли е директно във вашия формуляр или е подкомпонент на по-голям компонент, който сте поставили във вашия формуляр? - person Mason Wheeler; 14.12.2009
comment
Става така: TRadioPanel е VCL формуляр, в който има различни компоненти (този LMDButton е един от тях.) В моето основно приложение поставяме множество компоненти на TRadioPanel в основния формуляр и ги правим видими, ако потребителят натисне съответния бутони. Това би го направило подкомпонент според мен. - person Michael Stahre; 14.12.2009
comment
TRadioPanel е VCL формуляр, който се поставя върху други форми? Имате предвид, че е рамка? - person Mason Wheeler; 14.12.2009
comment
Е, първоначално беше TVisualComponent, извлечен от CDK инструментариума. Тъй като това вече не е налично (и така или иначе не е необходимо), го промених на TCustomForm. Опитах TForm, TWinControl, TFrame и други подобни. TVisualComponent беше базиран на TWinControl, така че бихте си помислили, че трябва да работи... Но уви, няма разлика. - person Michael Stahre; 14.12.2009
comment
Вероятно няма нищо общо със създаването на формуляр и всичко, което е свързано с инициализацията на контрола във вашата персонализирана контрола. Не форма. контрол. Контрол с подконтрол. Вероятно трябва да преминете през вашия персонализиран контрол ред по ред. И би било страхотно, ако можете да публикувате работеща извадка, демонстрираща този проблем. - person Warren P; 14.12.2009
comment
Дали разликата е нещо повече от семантика? Предполагам, че е правилно да го наричам контрола. И в двата случая подконтролите на контрола не се ли инициализират автоматично по същия начин, както контролите на формуляр? Предполагам, че това може да е частта, в която нещата са променени след Delphi 5. Ще се погрижа да го репликирам. - person Michael Stahre; 14.12.2009
comment
ах Не бях намерил никаква информация в DFM, отнасяща се до неща като това, така че просто предположих, че Delphi преглежда обектите, записани в DFM, тези, дефинирани в .pas файла, и разбра подробности за създаването от там. ...Така че остава въпросът - как да направя нов DFM, който съдържа тези данни? Единственият начин, който някога съм открил („някога“ може да е малко преувеличена дума, тъй като имам само три или повече месеца опит в Delphi) за създаване на DFM файлове е File-›New-›Form (или VCL Application) - person Michael Stahre; 14.12.2009
comment
Заслужава да се отбележи - ИМА DFM, който, когато се разглежда като текст в RAD Studio, съдържа записи за въпросните бутони. Прав ли съм да разбера, че DFM съдържа дефинициите, за които се позовавате във вашите данни за редактиране, които не се показват, когато използвате изглед като формуляр? Отварянето на файла в Notepad2 разкрива много данни, които не са обикновен текст. Част от това скрито ли е напълно от потребителя в RAD Studio? - person Michael Stahre; 14.12.2009

Вашият Create винаги се извиква първи, преди конструктора на предшественик. Просто така работят конструкторите. Трябва да можете да извикате наследения конструктор, преди да направите останалата част от вашата инициализация:

constructor MyForm.Create(Owner: TComponent);
begin
  inherited;
  EnGrpSndOption := False;
end;

Има обаче по-добър начин да посочите какво се опитвате да направите. Вашият клас зарежда свойства от DFM ресурс. Когато приключи, ще извика виртуален метод с име Loaded. Обикновено се използва за уведомяване на всички деца, че всичко е готово, така че ако някое от тях има препратки към други деца във формуляра, те знаят, че е безопасно да използват тези препратки в този момент. Можете да го замените и във формуляра.

procedure MyForm.Loaded;
begin
  inherited;
  EnGrpSndOption := False;
end;

Това обаче обикновено не би трябвало да има голяма разлика във вашия случай. Loaded се извиква от конструктора веднага след като формулярът приключи да се зарежда от DFM ресурса. Този ресурс казва на формуляра всички контроли, които трябва да създаде за себе си. Ако вашият бутон не се създава, вероятно не е посочен правилно в DFM. Възможно е контролите да бъдат изброени в DFM, които нямат съответстващи полета в класа. От друга страна, ако има публикувано поле, което няма съответен запис в DFM, IDE трябва да ви предупреди за това и да предложи премахване на декларацията всеки път, когато я изведете в дизайнера на формуляри. Прегледайте своя DFM като текст и потвърдете, че наистина има запис за контрола с име btGrpSnd.

person Rob Kennedy    schedule 14.12.2009
comment
Е, за съжаление има такива записи там. Освен ако DFM не съдържа данни, напълно скрити от кодера, които също трябва да са правилни, това не изглежда да е проблемът. Надяваме се, че нов DFM може да го реши... Благодаря ви за описанието на Loaded. Определено исках да знам дали съществува такава процедура. - person Michael Stahre; 14.12.2009

Това достатъчно добро ли е, за да тръгнете:

if Assigned(btGrpSnd) and btGrpSnd.HandleAllocated then btGrpSnd.Visible := ...

person Warren P    schedule 14.12.2009
comment
Е, проблемът е, че това ще остави стойностите непроменени, премахвайки смисъла от задаването им в Create на първо място. Някой вече предложи да се използва AfterConstruction за присвояване на стойностите, които не могат да бъдат присвоени по време на Create, но все още не съм отделил време за това, защото всъщност не искам да обикалям около проблема. Освен това съм доста любопитен как аспектът за автоматично създаване на delphi обработва контроли върху формуляри. Не успях да намеря нищо конкретно по въпроса. Така че бих могъл да го заобиколя по начин, не по-различен от това, което предлагате, но не бих предпочел. Благодаря все пак. - person Michael Stahre; 14.12.2009

Виждам 2 възможности: проверете дали btGrpSnd е нула, преди да присвоите стойност на свойството Visible. Ако е нула, бихте могли или:

  • не е зададен свойството
  • създайте btGrpSnd

Не бих се забърквал с реда на създаване. По-сложно е и може да се повреди с по-нататъшни промени.


от вашия коментар: можете да проверите дали сте в режим на проектиране или в режим на изпълнение. Проверете дали сте във времето за проектиране, преди да зададете видимостта.

if not (csDesigning in Componentstate) then
begin
  btGrpSnd:=Value;
end;

Отговор на вашия коментар:

отиде за това:

procedure Myform.SetGrpSndOption(const Value: boolean); 
begin 
  fEnGrpSndOption := Value; 
  if btGrpSnd<>nil then btGrpSnd.Visible := Value; 
end; 

и една допълнителна настройка на свойство btGrpSnd. Ако е зададена стойност ‹> нула, задайте и видимостта, защитена в fEnGrpSndOption.

Ако няма нужда да задавате btGrpSnd извън Myform, създайте init-процедура, която създава всичко. напр.:

constructor Myform.Create(...)
begin
  init;
end;

procedure init
begin
  btGrpSnd:=TButton.Create;
  ...
end;

procedure Myform.SetGrpSndOption(const Value: boolean); 
begin 
 fEnGrpSndOption := Value; 
 if btGrpSnd<>nil then init;
 btGrpSnd.Visible := Value; 
end; 

Това все още е по-добре, отколкото в зависимост от някои променени init-code-hack, които може да се повредят в бъдеще.

person Tobias Langner    schedule 14.12.2009
comment
Трябва обаче тези неща да бъдат направени по време на проектиране. Например, свойството, споменато в моя въпрос, трябва да е възможно да се зададе по време на проектиране. - person Michael Stahre; 14.12.2009