1

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.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
HX_unbanned
  • 583
  • 1
  • 15
  • 51
  • 2
    The code you posted (specifically the `FindWindow` line) looks for `TForm` + some additional text. Why would you assume you can then call FindControl using that `HWND` and typecast whatever it returns to a `TValueListEditor`? – Ken White Jun 13 '11 at 20:44

2 Answers2

1

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
    ...
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Sertac, please highlight FindWindEx as answering function for this question. – HX_unbanned Jun 14 '11 at 14:50
  • @HX_ Well, there's a line break before it is mentioned, and it should show with the link color. :) Seriously though, I think it should be obvious enough to anyone reading the answer.. – Sertac Akyuz Jun 14 '11 at 15:20
1

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;
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • Ken, THANK you very much for your answer, but it is not valid in my case because I have dynamicalle changable form type ( MDI, SDI ) and I cannot simply rely on Screen unit. +1 though for for time you spared for this question! – HX_unbanned Jun 14 '11 at 04:40
  • 2
    HX, that excuse doesn't make any sense. The `Screen` object holds references to *all* your program's forms, no matter what kind of forms they are. But if you don't like searching for the form in the `Screen` object, then store your dynamically created form in some other container, like a list. If you only have one instance of the form, you don't even need a container. Just store it in a simple `TForm` variable, and then refer to it directly when you want the form. – Rob Kennedy Jun 14 '11 at 04:51
  • Rob, yes, I could, but I am afraid that if I would use Screen object for access dynamic elements I would risk with some unknown issues. Thanks for the tip anyway ... Besides, it is not meant as excuse, more like chat to know other options for such task for experienced and professionals like you, Rob. ;) – HX_unbanned Jun 14 '11 at 07:02
  • HX, Rob's right. Using Screen.Forms as I showed you above is perfectly safe no matter if the form is SDI or MDI, and you can test for the specific type of your dynamic form before doing anything (again, as I demonstrated). I really think you're over-complicating things, and unless you post some more code showing why we're wrong I'll continue to think that way. :) – Ken White Jun 14 '11 at 10:58
  • Ken, hehe, I do not forbid you to think that way ;) Mainlyu I was complicating this as I have some overrides of System Menu elements in various WMs ... so I do not want to experience situation where I have to refactor or re-design some elements just because I am not able to track some behaivor. Also, I try to code with WIneHQ compatibility in mind and as much I know - Screen underlays pretty much D3D / GDI functions are not safe in WIneHQ environment .. jet ... AFAIK using FindWindow(Ex) / FindControl relys pretty much on non-Compositioning and graphical subsystem ... – HX_unbanned Jun 14 '11 at 14:41
  • @HX: No, `Screen` has nothing to do with D3D/GDI. It's information on monitors (multiple, if there are any), screen resolution, and so forth. It has nothing to do with drawing, it exists (in the Forms unit, like `Application`) whether you use it or not, and works fine on Wine in Ubuntu under VirtualBox. – Ken White Jun 14 '11 at 18:25