HX_unbanned
HX_unbanned

Reputation: 597

Why do I get an access violation when I access a TValueListEditor found with FindControl?

I have dynamically created TValueListEditor VCL component on a TForm. The code is located in nested procedure of one of the main form's methods. I have set:

ValueListEditor.KeyOptions := [keyEdit, keyAdd, keyUnique];

It looks like this:

TMainForm.Method();

Method has a nested procedure that contains code that creates the components mentioned above.

Then, I have helper function:

function GetMenuListData(XMLNode: TXMLNode; const XNMLDoc: string = '') : string;

In this helper I use this code to load an XML file and then retrieve its nodes and insert them into ValueListEditor.

XMLDoc := TXMLDocument.Create(Self);
XMLDoc.ParseOptions := [poPreserveWhiteSpace];
try
  XMLDoc.LoadFromFile(XNMLDoc);
  try
    Control := FindControl(FindWindow('TForm',PChar('(' + ExtractFileExt(Form1.Edit1.Text) + ')')));
    if Control <> nil then
    begin
      TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1] := XMLDoc.DocumentElement.NodeName;
      if XMLDoc.DocumentElement.ChildNodes.First.AttributeNodes.Count > 0 then
        TValuelistEditor(Control).Values[TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1]] := String(XMLDoc.DocumentElement.Attributes['id'])
      else
        TValuelistEditor(Control).Values[TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1]] := '<Empty>';
    end else begin
      MessageBeep(0);
      FlashWindow(Application.Handle, True);
      ShowMessagePos('...');
    end;
  finally
    XMLDoc.Active := False; Result := 'Forced ' + Form1.RAWInputBtn.Caption + ' in ' + DateTimeToStr(Now);
  end;
except
  on E : EXMLDocError do
  begin
    Result := 'Forced ' + Form1.RAWInputBtn.Caption + ' in ' + DateTimeToStr(Now);
  end;
end;

The problem is that I get access violations every time code goes into the line:

TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1] := XMLDoc.DocumentElement.NodeName;

I have tried various typecasts, values, parameters .. nothing does the trick.

What is my mistake?

I'm using Delphi XE.

Upvotes: 1

Views: 1217

Answers (2)

Sertac Akyuz
Sertac Akyuz

Reputation: 54812

As Ken commented your problem is, instead of finding the value list editor, you are finding your form and then typecasting it to a value list editor, hence the AV.

First, you're passing 'TForm' as 'lpClassName' to FindWindow. Assuming 'TForm' is the class name of your form, it will of course find the form - not a child window on it. Second, you cannot use FindWindow to find a child window, see its documentation, it searches top-level windows.

If you had tested the return of FindControl, the code raising the AV would never run:

  if (Control <> nil) and (Control is TValueListEditor) then


You can use FindWindowEx to search in child windows, if you don't know the handle of your form find it first as you've done already:

FormHandle := FindWindow('TForm',PChar('(' + ExtractFileExt(Form1.Edit1.Text) + ')'));
if FormHandle <> 0 then
begin
  Control := FindControl(FindWindowEx(FormHandle, 0, 'TValueListEditor', nil));

or better yet, test the return of FindWindowEx first to avoid passing '0' to FindControl:

ValueListEditorHandle := FindWindowEx(FormHandle, 0, 'TValueListEditor', nil);
if Win32Check(ValueListEditorHandle <> 0) then
begin
  Control := FindControl(ValueListEditorHandle);
  if Assigned(Control) then
  begin
    ...

Upvotes: 1

Ken White
Ken White

Reputation: 125728

If your dynamically created form is part of the same application, you don't need all the noise of the incorrect FindControl(FindWindow()). Just create your form, giving it a name, and making Application the owner:

MyForm := TMyForm.Create(Application);
MyForm.Name := 'MyDynamicForm';

When you want to get a new reference to it:

var
  TheForm: TMyForm;
  i: Integer;
begin
  TheForm := nil;
  for i := 0 to Screen.FormCount - 1 do
    if Screen.Forms[i] is TMyForm then
      // Could also use Screen.Forms[i].Caption
      if Screen.Forms[i].Name = 'MyDynamicForm' then
        TheForm := TMyForm(Screen.Forms[i]);

  if Assigned(TheForm) then
    TheForm.MethodThatLoadsXML(XMLFileName); // or whatever
end;

TheForm.MethodThatLoadsXML can now access the TValueListEditor directly:

procedure TMyForm.MethodThatLoadsXML(const XMLFileName: string);
begin
  // Load xml as before, using XMLFileName
  with TValueListEditor.Create(Self) do
  begin
    Options := [Whatever];
    Parent := Self;
    Left := SomeNumber;
    Top := SomeNumber;
    // Create items for value list from XML and other stuff
  end;
end;

Upvotes: 1

Related Questions