TComboBox 'Control has no parent window' in the destructor

I am using Delphi XE2. I am creating a custom TComboBox so that I can easily add key / string pairs and handle cleanup in the component destructor.

All if not (csDesigning in ComponentState) code if not (csDesigning in ComponentState) omitted for brevity.

 interface type TKeyRec = class(TObject) Key: string; Value: string; end; TMyComboBox = class(TComboBox) public destructor Destroy; override; procedure AddItemPair(const Key, Value: string); end; implementation destructor TMyComboBox.Destroy; var i: Integer; begin for i := 0 to Self.Items.Count - 1 do Self.Items.Objects[i].Free; Self.Clear; inherited; end; procedure TMyComboBox.AddItemPair(const Key, Value: string); var rec: TKeyRec; begin rec := TKeyRec.Create; rec.Key := Key; rec.Value := Value; Self.Items.AddObject(Value, rec); end; 

When the application closes, the destructor is called, but the Items.Count property Items.Count not available because the TComboBox must have a parent control to access this property. By the time the destructor is called, it no longer has a parent control.

I saw this problem before and had to store objects in a separate TList and release them separately. But this only works because the order I added to the TList was always the same as the lines added to the combo box. When the user selected a row, I could use a list index to find the correlating object in the TList . If the combo box is sorted, then the indices will not match, so I cannot always use this solution.

Has anyone else seen this? How did you think about the problem? It would be very nice to be able to free objects in the component destructor!

+4
source share
3 answers

After reading the link from Sertac ( David Heffernan's comment and NGLN's answer ), I believe that the solution that stores objects in a managed list, and not in a graphical interface, is the best. To this end, I created a combo box with TCustomComboBox . This allows me to promote all properties except Sorted to published . This allows you to save the internal FList synchronously with the rows in the Items list property. I just make sure they are sorted the way I want before adding them ...

The following shows what I did. I just included the essential code (less range checking) for brevity, but included some conditional logic that allows the use of the combo box without objects.

FList correctly destroyed in the destructor , freeing all objects without any exceptions at runtime, and the list of objects is managed inside the component itself, instead of being managed elsewhere, which makes it very portable. It works when a control is added to the form at design time or when it is created at run time. Hope this is helpful to someone else!

 interface type TMyComboBox = class(TCustomComboBox) private FList: TList; FUsesObjects: Boolean; function GetKey: string; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure AddItemPair(const Key, Value: string); procedure ClearAllItems; procedure DeleteItem(const Index: Integer); property Key: string read GetKey; published // all published properties (except Sorted) from TComboBox end; implementation type TKeyRec = class(TObject) Key: string; Value: string; end; function TMyComboBox.GetKey: string; begin if not FUsesObjects then raise Exception.Create('Objects are not used.'); Result := TKeyRec(FList.Items[ItemIndex]).Key; end; constructor TMyComboBox.Create(AOwner: TComponent); begin inherited; if not (csDesigning in ComponentState) then begin FUsesObjects := False; FList := TList.Create; end; end; destructor TMyComboBox.Destroy; begin if not (csDesigning in ComponentState) then begin ClearAllItems; FreeAndNil(FList); end; inherited; end; procedure TMyComboBox.AddItemPair(const Key, Value: string); var rec: TKeyRec; begin FUsesObjects := True; rec := TKeyRec.Create; rec.Key := Key; rec.Value := Value; FList.Add(rec); Items.Add(Value); end; procedure TMyComboBox.ClearAllItems; var i: Integer; begin if not (csDesigning in ComponentState) then begin if FUsesObjects then begin for i := 0 to FList.Count - 1 do TKeyRec(FList.Items[i]).Free; FList.Clear; end; if not (csDestroying in ComponentState) then Clear; // can't clear if the component is being destroyed or there is an exception, 'no parent window' end; end; procedure TMyComboBox.DeleteItem(const Index: Integer); begin if FUsesObjects then begin TKeyRec(FList.Items[Index]).Free; FList.Delete(Index); end; Items.Delete(Index); end; end. 
+1
source

You can override the GetItemsClass function:

 function GetItemsClass: TCustomComboBoxStringsClass; override; 

It is called by Combo to create elements (by default, TComboBoxStrings possible). You can then create your own descendant of TComboBoxStrings , for example TComboBoxStringObjects , where you can free the object associated with the element (when the element is deleted).

+2
source

There is a way to avoid having to rewrite a component to use a different list to save objects. The solution is to use the WM_DESTROY message along with the ComponentState property. When the component is about to be destroyed, its state will change to csDestroying , so the next time it receives the WM_DESTROY message, it will not be part of the window repair process. We successfully use this method in our component library.

 TMyCombo = class(TCombobox) ... procedure WMDestroy(var message: TMessage); message WM_DESTROY; ... procedure TMyCombo.WMDestroy(var message: TMessage); var i: integer; begin if (csDestroying in ComponentState) then for i:=0 to Items.Count - 1 do Items.Objects[i].Free; inherited; end; 
0
source

Source: https://habr.com/ru/post/1498679/


All Articles