Почему я получаю сообщение о нарушении доступа при обращении к TValueListEditor, найденному с помощью FindControl?

#delphi #findcontrol

#delphi #findcontrol

Вопрос:

Я динамически создал TValueListEditor компонент VCL на TForm . Код находится во вложенной процедуре одного из методов основной формы. Я установил:

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

Это выглядит следующим образом:

 TMainForm.Method();
  

Method имеет вложенную процедуру, содержащую код, который создает компоненты, упомянутые выше.

Затем у меня есть вспомогательная функция:

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

В этом помощнике я использую этот код для загрузки XML-файла, а затем извлекаю его узлы и вставляю их в 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;
  

Проблема в том, что я получаю сообщение о нарушении доступа каждый раз, когда код переходит в строку:

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

Я пробовал различные типизации, значения, параметры .. ничего не помогает.

В чем моя ошибка?

Я использую Delphi XE.

Комментарии:

1. Опубликованный вами код (в частности, FindWindow строка) ищет TForm некоторый дополнительный текст. Почему вы предполагаете, что затем вы можете вызвать FindControl, используя это HWND и типизировать все, что он возвращает в TValueListEditor ?

Ответ №1:

Как прокомментировал Кен, ваша проблема в том, что вместо поиска редактора списка значений вы находите свою форму, а затем вводите ее в редактор списка значений, отсюда и AV.

Сначала вы передаете ‘TForm’ как ‘lpClassName’ в FindWindow . Предполагая, что ‘TForm’ — это имя класса вашей формы, он, конечно, найдет форму, а не дочернее окно в ней. Во-вторых, вы не можете использовать FindWindow для поиска дочернего окна, смотрите его документацию, он выполняет поиск окон верхнего уровня.

Если бы вы протестировали возврат FindControl , код, вызывающий AV, никогда бы не выполнялся:

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

Вы можете использовать FindWindowEx для поиска в дочерних окнах, если вы не знаете дескриптор вашей формы, найдите его первым, как вы уже делали:

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

или, еще лучше, протестируйте возврат FindWindowEx first, чтобы избежать передачи ‘0’ в FindControl :

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

Комментарии:

1. Sertac, пожалуйста, выделите FindWindEx в качестве функции ответа на этот вопрос.

2. @HX_ Ну, перед упоминанием есть разрыв строки, и он должен отображаться цветом ссылки. 🙂 Если серьезно, я думаю, это должно быть достаточно очевидно для любого, кто читает ответ..

Ответ №2:

Если ваша динамически создаваемая форма является частью того же приложения, вам не нужен весь шум из-за некорректности FindControl(FindWindow()) . Просто создайте свою форму, присвоив ей имя и назначив Application владельца:

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

Когда вы хотите получить новую ссылку на него:

 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 теперь я могу получить доступ к TValueListEditor напрямую:

 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;
  

Комментарии:

1. Кен, большое тебе спасибо за твой ответ, но в моем случае он недействителен, потому что у меня динамический изменяемый тип формы (MDI, SDI), и я не могу просто полагаться на Screen unit. 1, хотя за время, которое вы потратили на этот вопрос!

2. HX, это оправдание не имеет никакого смысла. Screen Объект содержит ссылки на все формы вашей программы, независимо от того, какого рода они являются. Но если вам не нравится поиск формы в Screen объекте, тогда сохраните динамически созданную форму в каком-нибудь другом контейнере, например, в списке. Если у вас есть только один экземпляр формы, вам даже не нужен контейнер. Просто сохраните его в простой TForm переменной, а затем обратитесь к нему напрямую, когда вам понадобится форма.

3. Роб, да, я мог бы, но я боюсь, что если бы я использовал Screen object для доступа к динамическим элементам, я бы рисковал столкнуться с некоторыми неизвестными проблемами. В любом случае спасибо за совет… Кроме того, это не оправдание, скорее чат, чтобы узнать другие варианты решения такой задачи для опытных профессионалов, таких как ты, Роб. 😉

4. HX, Роб прав. Используя Screen. Формы, как я показал вам выше, совершенно безопасны, независимо от того, является ли форма SDI или MDI, и вы можете протестировать конкретный тип вашей динамической формы, прежде чем что-либо делать (опять же, как я продемонстрировал). Я действительно думаю, что вы чрезмерно усложняете ситуацию, и если вы не опубликуете еще какой-нибудь код, показывающий, почему мы неправы, я буду продолжать так думать. 🙂

5. Кен, хе-хе, я не запрещаю тебе так думать 😉 В основном, я усложнял это, поскольку у меня есть некоторые переопределения элементов системного меню в различных WMS… поэтому я не хочу столкнуться с ситуацией, когда мне придется рефакторинговать или перепроектировать некоторые элементы только потому, что я не могу отследить какое-либо поведение. Кроме того, я стараюсь кодировать с учетом совместимости с WineHQ и, насколько я знаю, подложки экрана, в значительной степени функции D3D / GDI небезопасны в среде WineHQ .. jet … AFAIK, использующий FindWindow (Ex) / FindControl, в значительной степени зависит от не-компоновки и графической подсистемы…