2010-09-23 9 views
9

Estoy tratando de escribir una biblioteca dll en Delphi con una función que crea una instancia de un descendiente TFrame y lo devuelve. Pero cuando importé esta función en una aplicación, cada vez que la llamaba obtenía una excepción como "el control 'xxx' no tiene ventana principal". No estoy 100% seguro, pero la excepción apareció en el constructor de esa clase cuando se accedió a cualquiera de los controles de GUI.El control 'xxx' no tiene ventana principal

¿Podría decirme cuál es el motivo de ese comportamiento? ¿Debería usar los descendientes de TForm en su lugar o hay una mejor solución?

¡Gracias!

+0

¿Por qué usa archivos DLL y no paquetes? Los archivos DLL son una pesadilla para hacer las cosas bien. –

Respuesta

8

Sobre el error

Ese mensaje de error se eleva desde la unidad Controls.pas, a partir del método TWinControl.CreateWnd. Esencialmente, ese código se usa para crear el identificador de Ventana para su descendiente TWinControl (TFrame, TButton, TEdit ... si puede tener el foco del teclado es un descendiente de TWinControl), y en realidad es un mensaje de error muy sensible: No puede tener un Ventana sin WindowParent, y como estamos hablando del VCL aquí, tiene mucho sentido intentar obtener el manejador de la ventana principal desde TWinControl.Parent; Y eso no está asignado.

No es POR QUÉ el mensaje de error está apareciendo. Puede ver ese mensaje de error porque parte del código que está utilizando para configurar el marco requiere un control de Ventana para alguna operación. Podría ser cualquier cosa, como establecer el título de algún componente (que internamente requiere un manejador de ventana para algunos cálculos). Yo personalmente realmente odio cuando eso sucede.Cuando creo GUI desde el código trato de retrasar la asignación de Parent tanto como sea posible, en un intento de retrasar la creación de la ventana, por lo que me picaron muchas veces.

específica para su uso DLL, arreglo posible

voy a poner mi sombrero psico lector de la mente sobre. Como necesita devolver un FRAME desde su DLL, y no puede devolver el Frame porque ese es un objeto específico de Delphi y no tiene permitido devolver objetos específicos de Delph sobre los límites de DLL, supongo que está volviendo. un identificador de ventana, al igual que toda la buena de API, utilizando una definición de función como esta:

function GiveMeTheNiceFrame:HWND; 

el problema es que la rutina requiere la creación del identificador de ventana real, mediante una llamada a TWinControl.CreateWnd, ya su vez que la llamada requiere un identificador de ventana padre para configurar la llamada al Windows.CreateWindowEx, y la rutina no puede obtener un identificador de ventana principal, por lo que se produce un error.

intente reemplazar su función con algo allong las líneas de:

function GiveMeTheNiceFrame(OwnerWindow:HWND):HWND; 
begin 
    Result := TMyNiceFrame.CreateParanted(OwnerWindow).Handle; 
end; 

... es decir: utilizar el constructor CreateParented(AParentWindow:HWND), no es el típico Create(AOwner:TComponent) y aprobar un HWND propietario para su DLL.

+0

Gracias por la respuesta. Creo que debería ayudar; Lo intentaré más tarde. Por cierto, déjame hacerte una pregunta. ¿Por qué no se permite devolver un objeto específico de Delphi a través de las DLL? Voy a usar esta biblioteca solo con aplicaciones de Delphi. La función devuelve el tipo de TFrame, pero el objeto real creado es una instancia de una clase descendiente. ¿No está bien? ¡Gracias! –

+1

No está bien por razones de versión: usar el objeto devuelto requiere conocimiento de su diseño de memoria, y eso está sujeto a cambios (puede cambiar con la versión de Delphi).Funcionará cuando lo pruebe y funcionará si el Exe y el Dll están compilados con el mismo compilador y la misma configuración del compilador, pero si va a imponer tales restricciones, también podría usar paquetes. –

+0

Gracias, es genial saberlo. Hice lo que sugirió y sigo recibiendo la excepción :-(. La función exportada hace esto: Resultado: = TFrame1.CreateParented (ParentWindowHandle); Y ParentWindowHandle es un identificador de la ventana principal de una aplicación que llama al método (Self.Handle, cuando se llama desde la clase de la ventana principal). –

0

Parece que simplemente tiene que asignar el componente (un formulario o parte de un formulario, como un panel) que contiene el marco en theframe.parent.

No se puede hacer la GUI antes de asignarla. Los marcos son partes de formularios para su reutilización, y normalmente necesitan asignarles algunos padres.

Mueva el código GUI a onshow o un procedimiento que llame explícitamente, para que el código de llamada pueda asignar el elemento principal.

O haga que el elemento primario sea un parámetro en la función.

+0

Al leer su respuesta en segunda lectura (estoy un poco ciego a la palabra), parece que su respuesta falta un poco después de la "necesidad normal" ... –

+0

corregido. Escribir de prisa siempre es un problema para mí. Trabajando en todas las oraciones a la vez :) –

1

Hay algunas cosas importantes para recordar:

  1. Al utilizar DLL, tanto el archivo DLL y el archivo EXE tienen cada uno una instancia de aplicación que están luchando por el control. Los controles en su DLL verán la instancia de la Aplicación que pertenece a la DLL; los Controles en su EXE verán la instancia de la Aplicación que pertenece al EXE. Esa lucha no está presente cuando se usan paquetes, ya que solo habrá una instancia de la Aplicación.
  2. Los marcos son controles, pero no son formularios.
  3. Al usar controles en una aplicación, no pueden existir visualmente sin un control principal (generalmente un formulario o un contenedor que tiene una jerarquía principal para un formulario).
  4. Algunos controles no pueden exponer su funcionalidad completa a menos que existan visualmente y tengan un elemento principal válido.

Intente reproducir su problema dentro del EXE; si no puede reproducirse, probablemente sea lo primero en la lista anterior.

--jeroen

0

yo encontramos este (CreateParams se llama como parte de CreateWnd):

procedure TCustomFrame.CreateParams(var Params: TCreateParams); 
begin 
    inherited; 
    if Parent = nil then 
    Params.WndParent := Application.Handle; 
end; 

Y Application.Handle = 0 por lo que siempre lanza el error más adelante en CreateWnd.
Después de leer este Delphi: How to call inherited inherited ancestor on a virtual method?

lo he resuelto reemplazando CreateParams en mi marco de perder la oportunidad de la versión tCustomFrame:

type 
    tCreateParamsMethod = procedure(var Params: TCreateParams) of object; 

type 
    tMyScrollingWinControl = class(TScrollingWinControl); 

procedure TDelphiFrame.CreateParams(var Params: TCreateParams); 
var 
    Proc: tCreateParamsMethod; 
begin 
    TMethod(Proc).Code := @TMyScrollingWinControl.CreateParams; 
    TMethod(Proc).Data := Self; 

    Proc(Params); 
end; 

Ahora se acaba de lanzar errores al intentar establecer el foco en subcontroles, que Creo que lo arreglaré interceptando WM_FOCUS, pero veremos cómo va desde aquí.

function CreateFrame(hwndParent: HWnd): HWnd; stdcall; 
var 
    frame: tFrame; 
begin 
    Result := 0; 
    try 
    frame := TDelphiFrame.CreateParented(hwndParent); 
    Result := frame.Handle; 
    except on e: Exception do 
    ShowMessage(e.Message); 
    end; 
end; 
0

Puede evitar este mensaje mediante la asignación nula al evento OnClose padres, a veces funciona:

SomeControl.Parent := nil;//Before free your TControl 
SomeControl.Free; 
+3

Esta es una programación accidental. – Ampere

+0

Kachwahed: No debe programar por conjeturas. Simplemente ensuciará tu código con basura innecesaria que generalmente actúa como un catalizador para errores sutiles e inesperados. Sí, puede ser complicado; pero necesita poner el esfuerzo en el análisis de causa raíz para que pueda entender el problema correctamente. En este caso particular, su respuesta es totalmente irrelevante para el problema de OP. –

+0

Y como segundo punto, no debería tratar de "evitar mensajes de error". Los mensajes de error no son un _problema_ ... Son simplemente un *** mensaje *** sobre un problema. Así que no solo dispares al messenger. ¡Descubre cuál es el _problema real_ y ** soluciona el problema * real *! –

0

Creo que esta es una solución muy fresco. Creo que no se intentó antes :) Estoy usando un Dummy Parent (que es un Formulario).

function MyFrame_Create(hApplication, hwndParent:THandle; X, Y, W, H:Integer):Pointer; stdcall; 
var Fr: TMyFrame; 
    F: TForm; 
    CurAppHandle: THandle; 
begin 
    CurAppHandle:=Application.Handle; 
    Application.Handle:=hApplication; 
    //--- 
    F:=TForm. Create(Application);//Create a dummy form 
    F.Position:=poDesigned; 
    F.Width:=0; F.Top:=0; F.Left:=-400; F.Top:=-400;//Hide Form 
    F.Visible:=True; 
    //--- 
    Fr:=TMyFrame.Create(Application); 
    Fr.Parent:=F;//Set Frame's parent 
    //Fr.ParentWindow:=hwndParent; 
    Windows.SetParent(Fr.Handle, hwndParent);//Set Frame's parent window 
    if CurAppHandle>0 then Application.Handle:=CurAppHandle; 
    //--- 
    Fr.Left:=X; 
    Fr.Top:=Y; 
    Fr.Width:=W; 
    Fr.Height:=H; 
    Result:=Fr; 
end;//MyFrame_Create 

procedure MyFrame_Destroy(_Fr:Pointer); stdcall; 
var Fr: TMyFrame; 
    F: TObject; 
begin 
Fr:=_Fr; 
F:=Fr.Parent; 
Fr.Parent:=Nil; 
if (F is TForm) then F.Free; 
//SetParent(Fr.Handle, 0); 
//Fr.ParentWindow:=0; 
Fr.Free; 
end;//MyFrame_Destroy 
Cuestiones relacionadas