1. Имеется родительское окно, оно открывается при запуске программы. Назовем его WinROD.
2. Далее из меню WinROD открываются еще два окошка Win_1 и Win_2.
3. В Win_1 кнопка, при нажатии котрой в Win_2 должен обновиться SQL запрос (F_Win_2.Query.Refresh).
4. Пробовал конструкции:
4.1. if Assigned(F_Win_2) то рефреш
4.2. если F_Win_2.Visible то рефреш
Обе они работают, если Win_2 открыто, в противном случае - ПК выдает ошибку.
В чем косяк – не понятно… Может кто подскажет?
В принципе нужно организовать проверку на существование Win_2, вот и все. Но как это сделать??
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
18-05-2010 10:25 | Комментарий к предыдущим ответам
Поскольку собираюсь дать сюда ссылку в другом вопросе, считаю необходимым обратить особое внимание на то, что на момент уничтожения контролируемой формы необходимо обеспечить существование переменной, на которую сохраняется ссылка. В противном случае будет портиться случайная память.
Поэтому:
1. Переменная не должна быть полем другой MDIChild-формы (контролирующей), т.к. эта форма может быть уничтожена раньше контролируемой. Либо при своём уничтожении контролирующая форма сначала должна уничтожать контролируемую.
2. Переменная не должна быть элементом динамического массива, т.к. при SetLength динамические массивы перераспределяются.
Т.е. это может быть либо глобальная переменная, либо элемент глобального статического массива, либо поле главной формы (отдельная переменная или элемент статического массива).
type
TChild1Form = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FPVarForNil: PPointer;
public
constructor Create(var AVarForNil: TChild1Form; AOwner: TComponent); reintroduce;
destructor Destroy; override;
end;
Если в своём классе в конструкторе не требуется выполнять дополнительных действий, то его и не объявляют. Тогда при создании класса будет вызываться наследуемый конструктор. Для формы это будет TCustomForm.Create:
constructor Create(AOwner: TComponent); override;
Если нужен свой конструктор, то обычно перекрывают наследуемый виртуальный, в своём выполняют нужные действия и вызывают наследуемый, т.е. если бы нам не нужно было передавать в свой конструктор дополнительных параметров, то логичнее всего его было объявить так-же, как в TCustomForm.
Поскольку нам нужно передать дополнительный параметр, то мы объявляем свой конструктор с другим набором параметров.
Теперь имеем ситуацию, когда наш конструктор имеет то-же имя, что и наследуемый, но другой набор параметров. Поэтому он не может перекрывать наследуемый виртуальный конструктор, т.е. мы не можем написать ему "override". Но поскольку имя совпадает, то наш конструктор скрывает наследуемый, т.е. его нельзя будет вызвать снаружи (только из наследников, указывая ключевое слово inherited).
Компилятор отслеживает такие ситуации и считает их подозрительными, поэтому выдаёт по этому поводу предупреждение. Чтобы избавиться от предупреждения, мы объявляем свой конструктор с ключевым словом reinroduce, давая компилятору понять, что данная ситуация - не следствие ошибки, а именно так всё и было задумано, поэтому ему не следует беспокоиться по этому поводу и выдавать нам предупреждение.
В принципе, свой конструктор может иметь любое имя, и мы могли бы объявить его, например, так:
Однако в этом случае оставалась бы возможность создать нашу форму через вызов наследуемого конструктора, который не учитывает нашу специфику.
В прародителе всех классов TObject конструктор, деструктор и метод Free объявлены так:
procedure TObject.Free;
begin
if Self <> nil then
Destroy;
end;
Что такое Self...
Метод вообще, а в данном конкретном случае, Free, можно рассматривать как отдельную процедуру:
procedure Free(Self: TObject);
begin
if Self <> nil then
Destroy;
end;
При этом вызов Obj.Free можно рассматривать как Free(Obj)
Никакого другого волшебного смысла переменная Self не несёт. Внутри метода никакими другими способами, иначе как через переменную Self, нельзя узнать "свой объект". Вся работа со своим объектом внутри метода идёт только через Self. Другое дело, что в большинстве случаев его явно не указывают, если это не приводит к неоднозначности, но он всегда подразумевется. Это так называемый "неявный Self". Т.е. процедура Free могла быть записана так:
procedure TObject.Free;
begin
if Self <> nil then
Self.Destroy;
end;
При вызове Obj.Free внутрь Free, а затем и Destroy передаётся копия указателя Obj, т.е. внутри каждого метода существует своя переменная Self, которой присваивается значение, хранящееся в переменной Obj. По этому внутри метода мы можем что угодно делать с переменной Self (хотя это - изврат), но не можем изменить ту переменную, значение которой было присвоено Self при вызове метода, а также не имеем никакой информации о том, где она находтся.
Пара слов о передаче параметров в процедуры... Параметры могут передаваться двумя способами:
1. По значению. При этом внутри процедуры параметр можно рассматривать как отдельную переменную, изменение которой никак не влияет на внешнюю.
2. По ссылке. В этом случае переменная внутри процедуры - это та-же самая переменная, что и снаружи, и изменение параметра внутри процедуры, соответственно, приводит к изменению внешней переменной. Такой способ накладывает некоторые дополнительные ограничения, в частноси, по ссылке нельзя передавать свойства. Ссылочная природа такого параметра реализуется за кадром и работа с ним ничем не отличается от работы с параметром, переданным по значению.
procedure SomeProc(A: Integer); // По значению
procedure SomeProc(var A: Integer); // По ссылке
Для того, чтобы иметь возможность добраться до переменной, в которой хранится ссылка на нашу форму, мы в явном виде передаём её конструктору как параметр, передаваемый по ссылке. При этом "ссылочность" нужна не для изменения исходной переменной, а для того, чтобы получить её адрес через оператор @. Для хранения указателя, ссылающегося на переменную, в которой хранится ссылка на форму, мы заводим приватное поле FPVarForNil: PPointer;
Тип PPointer объявлен в модуле System: PPointer = ^Pointer;
Т.е. это указатель на указатель. Нетипизированный указатель совместим по присваиванию и сравнению с указателями любого типа без необходимости его приведения к какому-либо типу.
При уничтожении формы в деструкторе мы выполняем строчку:
FPVarForNil^ := nil;
Т.е. переменной, на которую ссылается переменная FPVarForNil, присваиваем значение nil. Т.е. обнуляем ту самую переменную, которую мы указывали конструктору в качестве первого параметра.
В конструкторе и деструкторе вызываем соответственно наследуемые конструктор и деструктор.
Когда объявление текущего метода совпадает с объявлением наследуемого, можно указывать только слово inherited
Если есть параметры, то они передаются соответственно в наследуемый метод. В нашем случае объявления конструкторов не совпадают, поэтому имя и параметр указываем явно, а деструктор - совпадает, поэтому ограничивается только inherited (хотя, можно было бы написать и inherited Destroy).
Про Action := caFree; уже писал. Упомяну только, что нельзя уничтожать объект внутри его собственных обработчиков. При возникновении такой необходимости применяют метод Release. Его вызов приводит не к немедленному уничтодению объекта, а к отложенному - после выхода из всех обработчиков. Присвоение значения caFree параметру Action, переданному по ссылке, является косвенным способом вызова метода Release.
Загляните ещё в »вопрос КС №72638«
Ещё по поводу деструктора...
Поскольку деструктор является виртуальным, начиная с TObject, то для вызова Free можно использовать переменную типа любого предка объекта - деструктор всё равно будет вызван правильный - реального типа объекта, а не типа переменной.
По поводу TMainForm.SpeedButton1Click...
Формы с FormStyle=fsMDIChild отображаются сразу после создания, поэтому для них не следует вызывать Show.
Автосоздаваемые формы создаются в файле проекта (.dpr) строчками вроде такой:
Application.CreateForm(TMainForm, MainForm);
Такой способ создания формы не может обеспечить вызов деструктора, объявление которого не совпадает с наследуемым.
В своём коде создавать формы следует только путём вызова конструктора и никогда не применять Application.CreateForm.
P.S. На самом деле, если предполагается создавать одновременно не более одной такой формы, то можно было ограничиться обнулением одной совершенно конкретной глобальной переменной в деструкторе и даже не писать своего конструктора:
>>> 1. Имеется родительское окно, оно открывается при запуске программы. Назовем его WinROD.
Я бы назвал его MainForm.
>>> Каким образом явно обнулить уаказатель?
Например, сохранить в поле самой формы указатель на переменную, которую следует обнулять при своём уничтожении и в деструкторе обнулять её. Указывать эту переменную логично в конструкторе.
Форму я уничтожаю именно через Free
// --------- закрыть окно при нажатии на крестик
procedure TF_Vodila.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
Вообще-то, не через Free, а через Release.
Строчка Action:=caFree ничего не уничтожает. Она всего лишь присваивает значение локальной переменной в TCustomForm.Close, которое проверяется после выхода из обработчика и в случае необходимости вызывается Release.
type
TMainForm = class(TForm)
SpeedButton1: TSpeedButton;
procedure SpeedButton1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm; // FormStyle=fsMDIForm
implementation
uses Child1_Form;
{$R *.dfm}
procedure TMainForm.SpeedButton1Click(Sender: TObject);
begin
if Assigned(Child1Form) then
ShowMessage('Форма уже существует')
else
Child1Form := TChild1Form.Create(Child1Form, Application);
end;
type
TChild1Form = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FPVarForNil: PPointer;
public
constructor Create(var AVarForNil: TChild1Form; AOwner: TComponent); reintroduce;
destructor Destroy; override;
end;
var
Child1Form: TChild1Form; // FormStyle=fsMDIChild, Неавтосоздаваемая!!!
implementation
{$R *.dfm}
{ TChild1Form }
constructor TChild1Form.Create(var AVarForNil: TChild1Form; AOwner: TComponent);
begin
inherited Create(AOwner);
FPVarForNil := @AVarForNil; // Параметр объявлен с var
end;
destructor TChild1Form.Destroy;
begin
FPVarForNil^ := nil;
inherited;
end;
procedure TChild1Form.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
В этом примере я использовал глобальную переменную, но это, естественно, не обязательно. Переменная может быть, например, полем (но не свойством) класса. Только нужно быть очень осторожным, чтобы не получилось так, что в форме останется ссылка на поле уже уничтоженного экземпляра какого-либо класса.
">>> В принципе нужно организовать проверку на существование Win_2, вот и все. Но как это сделать??
И что же вы делаете?
// Проверить, что форма создана:
if Assigned(F_WindowD2) then
Вы здесь проверяете не то, что форма создана, а то, что указатель, который вы собираетесь использовать для доступа к форме, не равен nil... А он не равен nil сразу же после присвоения ему ссылки, которую вернул конструктор после создания формы, и до тех пор, пока вы его явно не обнулите. Если вы уничтожили форму через Free, то он сам не обнулится... "
Внимание вопрос :))) :
Каким образом явно обнулить уаказатель?
Форму я уничтожаю именно через Free
// --------- закрыть окно при нажатии на крестик
procedure TF_Vodila.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
Это - не решение... Это - верёвочки и завязочки с бантиками...
Годится только для того, чтобы один раз показать, что, мол, работает...
Причины проблемы не выяснены. Соответственно, решение не основывается на правильном понимании ситуации.
Вы совершенно правильно поставили вопрос:
>>> В принципе нужно организовать проверку на существование Win_2, вот и все. Но как это сделать??
И что же вы делаете?
// Проверить, что форма создана:
if Assigned(F_WindowD2) then
Вы здесь проверяете не то, что форма создана, а то, что указатель, который вы собираетесь использовать для доступа к форме, не равен nil... А он не равен nil сразу же после присвоения ему ссылки, которую вернул конструктор после создания формы, и до тех пор, пока вы его явно не обнулите. Если вы уничтожили форму через Free, то он сам не обнулится... Обязательно прочитайте »вопрос КС №57952«
// Проверить, что форма открыта и показывается:
if F_WindowD2.Showing then
F_WindowD2.ZROQuery_VOnWork.Refresh;
Вот она, завязочка с бантиками... Поскольку ваша проверка через Assigned срабатывает только на то, что форма ещё ни разу не была создана, а после создания и уничтожения уже выдаёт ложную информацию, то всю нагрузку на себя берёт проверка Showing уже, возможно, уничтоженного объекта... То, что эта проверка пока выдаёт False для уничтоженной формы, а не True или не вызывает исключения - случайность, на которую ни в коем случае нельзя полагаться.
Запросу совершенно безразлично, показывается в данный момент форма, или нет. Главное - чтобы она существовала. Так что решение здесь такое: при уничтожении формы обнулять переменную, а потом просто проверять её на равенство nil хоть через <>nil, хоть через Assigned...
Это - решение крупным планом. Могут быть тонкости при обнулении переменной.
// Проверить, что форма создана:
if Assigned(F_WindowD2) then
// Проверить, что форма открыта и показывается:
if F_WindowD2.Showing then
F_WindowD2.ZROQuery_VOnWork.Refresh;
В принципе нужно организовать проверку на существование Win_2, вот и все. Но как это сделать??
А как создаётся окно? Какая ошибка?
Если Win_2 не создано, а переменная, содержащая указатель на него является
атрибутом класса (например, формы TWinROD), проверьте: F_Win_2 <> nil.
Если нет, то обнуляйте переменную во время загрузки программы и после
уничтожения окна.
Можете попробовать функцию IsWindow(Win_2.Handle).
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.