(Un TPrinterDialog cambia su posición centrada en OnShow con SetWindowPos y MoveWindow pero no un TOpenDialog)
Las clases de la VCL que se ocupan de gestionar los cuadros de díalogo, TCommonDialog y derivadas, tienen la "manía" de centrar la ventana en el monitor, aunque en Delphi 4 y 5 se tiene en consideración la existencia de varios monitores, por lo que el cuadro de diálogo se centra en el monitor donde se encuentra la ventana principal de la aplicación.
El caso es que los cuadros de diálogo de impresión, color, etc., es decir, los que no son TOpenDialog o TSaveDialog, tienen unas dimensiones fijas. Por ello, en la VCL, se centra la ventana durante el proceso del mensaje de inicialización del cuadro de diálogo (WM_INITDIALOG). Puesto que la visualización del cuadro de diálogo es posterior, momento en que se produce el evento OnShow, podemos modificar la posición de la ventana en ese momento sin problemas. La propiedad Handle de TPrintDialog, TColorDialog, etc., contiene el manejador de la ventana, es decir, del cuadro de diálogo.
Los cuadros de diálogo que tienen el estilo "explorador", que son las TOpenDialog y TSaveDialog, no tienen unas dimensiones fijas. Estas ventanas, tras producirse el mensaje WM_INITDIALOG, sufren un proceso de preparación por parte de Windows, terminado el cual se genera un mensaje de notificación con el código CDN_INITDONE. La VCL aprovecha este mensaje, cuando ya se conocen las dimensiones del cuadro de diálogo, para centrarlo en la pantalla. Lo peor, no obstante, es que esto se produce después de haber generado el evento OnShow, por lo que hagas lo que hagas en dicho evento, la ventana a continuación se vuelve a centrar. Para colmo, la propiedad Handle de estos cuadros de diálogo no contiene el manejador de la ventana propiamente dicha, sino de una ventana hija que hay en el interior del cuadro de diálogo. Esto se debe a que los cuadros de diálogo de guardar y abrir, a diferencia de los demás, son redimensionables.
Bueno, después del rollo, que es para intentar explicar lo que sucede, vienen las posibles soluciones. Como digo hay varias: usar directamente la función GetOpenFilename() o GetSaveFilename(), que es, a fin de cuentas, lo que hacen los dos componentes. Otra opción es derivar un nuevo componente a partir de aquellos, sustituyendo el método de proceso de mensajes para evitar el centrado de la ventana. Por último, podemos sustituir el mencionado método de proceso de mensajes de los componentes TOpenDialog o TSaveDialog en ejecución. Esta última solución es la más rápida, aunque quizá el resultado no sea el más óptimo.
Para que sirva como ejemplo, la sustitución de la función de proceso de mensajes de un TOpenDialog se efectuaría con el código siguiente, asumiendo que tenemos un formulario con un TOpenDialog y un botón.
var
PrevWndFunc: Pointer; // Variable para conservar la dirección anterior
// Esta será la nueva función de proceso de mensajes
function DialogWndProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam:
LPARAM): UINT; Stdcall;
var
Area: TRect;
begin
// Llamamos a la anterior función de proceso de mensajes, para que
// el cuadro de diálogo siga funcionando correctamente
Result := CallWindowProc(PrevWndFunc, hWnd, uMsg, wParam, lParam);
// El código de notificación CDN_SELCHANGE se recibe siempre en el momento
// de mostrar el cuadro de diálogo, por lo que podemos aprovecharlo para
// reposicionar la ventana
if (uMsg = WM_NOTIFY) and ((POFNotify(lParam)^.hdr.code) = CDN_SELCHANGE)
then
begin
GetWindowRect(hWnd, Area); // Obtenemos las dimensiones del cuadro de diálogo
// y desplazamos la ventana en que está contenido dicho cuadro hasta
// la posición que nos interese
MoveWindow(GetWindowLong(hWnd, GWL_HWNDPARENT), -610, 260,
Area.right-Area.left, Area.bottom-Area.top, true);
// Puesto que ya hemos hecho nuestro trabajo, volvemos a poner el
// anterior proceso de mensajes
SetWindowLong(Form1.OpenDialog1.Handle, GWL_WNDPROC, Integer(PrevWndFunc));
end;
end;
// Al mostrarse el cuadro de diálogo
procedure TForm1.OpenDialog1Show(Sender: TObject);
begin
// Sustituimos la función de proceso de mensajes del cuadro de diálogo
// por un procedimiento propio, conservando la dirección del anterior
PrevWndFunc := Pointer(SetWindowLong(OpenDialog1.Handle, GWL_WNDPROC,
Integer(@DialogWndProc)));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
OpenDialog1.Execute;
end;
Aquí la clave está en saber para qué sirven las funciones SetWindowLong()/GetWindowLong() y CallWindowProc(). Con SetWindowLong() sustituimos el procedimiento de proceso de mensajes original por el nuestro, con lo que conseguimos que todos los mensajes del cuadro de diálogo lleguen a nuestra función. En dicha función usamos un mensaje conocido, para obtener las dimensiones actuales de la ventana y situarla en su nueva posición (mi escritorio tiene coordenadas negativas). Hay que fijarse que al llamar a MoveWindow() no se facilita como manejador de ventana el del cuadro de diálogo, sino el de la ventana en que éste se haya incluido, que obtenemos con GetWindowLong().
|