Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   API de Windows (https://www.clubdelphi.com/foros/forumdisplay.php?f=7)
-   -   Hacer una ventana MDI siempre delante (always on top) (https://www.clubdelphi.com/foros/showthread.php?t=16605)

zerelho 03-12-2004 19:15:46

Hacer una ventana MDI siempre delante (always on top)
 
Hola a todos, os cuento el problema que tengo:

Estoy haciendo una aplicación MDI y necesito que las ventanas hijas tengan la posibilidad de estar siempre delante (la típica opción de "Always on Top").

Para ésto lo que estoy haciendo es cambiar el FormStyle de las ventanas hijas de "fsMDIChild" a "fsStayOnTop" y viceversa, lo que pasa es que al hacer los cambios no me coloca la ficha en el mismo lugar por lo que almaceno la posición actual y despues la recupero (de forma bastante chapucera) pero el problema es que se ven los cambios de posición y me salen unos "pantallazos" bastante feos.

Corregi el problema a medias minimizando y restaurando la ventana, pero aun así no queda muy bien.

¿Alguien sabe otra forma mas correcta y eficiente de hacer esto si que se note el cambio de FormStyle?

Este es el codigo del metodo donde realizo el cambio:

Código:

         
Izq:=Self.Left;
Arriba:=Self.Top;
{**}
// esto lo quité del libro de "La Cara Oculta de Delphi":
// Anula la actualización de la ventana en el monitor, para evitar el parpadeo
LockWindowUpdate(Self.Handle);
try
        WindowState:=wsMinimized;
        {**}
        if SiempreVisible.Checked then
                // Checkbox activado (de MDIChild a StayOnTop)
                Begin
                FormStyle:=fsStayOnTop;
                Self.Left:=(Izq+2);
                Self.Top:=Arriba+(FormPrincipal.Height-FormPrincipal.ClientHeight+23);
                End
        else
                // Checkbox desactivado (de StayOnTop a MDIChild)
                Begin
                FormStyle:=fsMDIChild;
                Self.Left:=(Izq-2);
                Self.Top:=Arriba-(FormPrincipal.Height-FormPrincipal.ClientHeight+23);
                End;
        {**}
finally
        WindowState:=wsNormal;
        LockWindowUpdate(0);
        end;

No me pregunteis de donde viene el Top:=...+23 y el Left:=...+2 pero me posiciona la ventana el mismo lugar exacto... si alguien sabe por qué este valor? :confused:

Aunque sea otra duda diferente, que tipo de botón tengo que utilizar para que si hago click en él me quede como presionado y volviendo a hacer click vuelve a su posición habitual?

La idea sería cambiar el checkbox que utilizo por un botón de este tipo, para ponerle el típico icono con la agujita pinchada.

roman 05-12-2004 09:25:33

¡Uf! Esta estuvo difícil :p

Vamos a ver primero porque no funciona tal como está.

Cuando el estilo de un formulario es fsMDIChild, su posición (Left, Top) está dada en relación al área cliente del formulario principal (el área no ocupada por los bordes, la barra de menú, barras de herramientas, etc), mientras que con los otros estilos la posición está dada en relación a la pantalla.

Entonces, si un formulario MDIChild está, por decir algo, en la posición (0, 0), es decir pegada a los bordes izquierdo y superior del formulario principal, al cambiarle el estilo, sus coordenadas siguen siendo (0, 0) pero ahora con relación a la pantalla lo que ocasiona que el formulario se mueva hasta la esquina superior izquierda de la pantalla, fuera del área cliente del formulario principal, razón por la cual hay que reposicionarla.

Ahora bien, LockWindowUpdate inhibe el redibujado de la ventana cuyo identificador se pasa como parámetro, en este caso el formulario principal, pero dado que el formulario hijo se movió fuera del área cliente, nada está impidiendo su redibujado y de ahí el 'pantallazo'.

La primera opción que viene a la cabeza entonces es aplicar el bloqueo a toda la pantalla:

LockWindowUpdate(GetDesktopHandle);

Pero esto, además de ser una solución chapucera, tiende a provocar un parpadeo en los iconos del escritorio.

Pues bueno, luego de zambullirme un rato en la VCL encontré lo que parece ser una solución aceptable: impedir nosotros mismos que se muestre la ventana durante el proceso de cambio de estilo (que en el fondo implica la destrucción y recreación de la ventana) y mostrarla una vez que la hayamos movido y redimensionado (esto último porque al cambiar al estilo fsMDIChild el formulario también cambia de tamaño).

Suponiendo que TChildForm es el formulario MDIChild, pon su declaración así:

Código Delphi [-]
TChildForm = class(TForm)
private
  ChangingStyle: Boolean;
  procedure CMShowingChanged(var Msg: TMessage); message CM_SHOWINGCHANGED;

public
  procedure ChangeStyle(NewStyle: FormStyle);
end;

El mensaje CM_SHOWINGCHANGED lo manda la VCL cuando ha cambiado la visibilidad de la ventana y es quien se encarga de llamar a la API de Window para mostrar el formulario. La variable ChangingStyle la usaremos entonces como bandera para impedir que se muestre la ventana cuando estamos cambiando el estilo.

La implementación queda así:

Código Delphi [-]
procedure TChildForm.CMShowingChanged(var Msg: TMessage);
begin
  if not ChangingStyle then
    inherited;
end;

procedure TChildForm.ChangeStyle(NewStyle: TFormStyle);
var
  Base: TPoint;
  Size: TPoint;

begin
  Base := Point(Left, Top);
  Size := Point(Width, Height);

  if NewStyle = fsMdiChild
    then Windows.ScreenToClient(Application.MainForm.ClientHandle, Base)
    else Windows.ClientToScreen(Application.MainForm.ClientHandle, Base);

  ChangingStyle := true;
  FormStyle := NewStyle;
  ChangingStyle := false;

  SetBounds(Base.X, Base.Y, Size.X, Size.Y);
  BringToFront;
  ShowWindow(Handle, SW_SHOW);
end;

Base y Size nos sirven para guardar la posición y tamaño de la ventana antes de hacer el cambio de estilo y usamos las funciones ClientToScreen y ScreenToClient para la reposición de la ventana en lugar de hacer chapucerías sumando y restando cosas raras.

Y ya con esto, en el evento OnClick de tu CheckBox pondrías

Código Delphi [-]
if (Sender as TCheckBox).Checked
  then ChangeStyle(fsMDiChild)
  else ChangeStyle(fsStayOnTop);

Hasta donde lo he probado funciona bien pero faltará que lo pruebes tú a ver si te sirve.

// Saludos

zerelho 06-12-2004 02:54:11

Perfecto!!!
 
dpm!!! :o Muchas gracias roman, Todo perfecto a la primera, un 10... la verdad es que me quitas de un apuro... aparte todo perfectamente claro y explicado.

Ya estuviera intentando cacharrear con el tema de las funciones ClientToScreen y ScreenToClient pero no daba hecho y lo de los mensajes a la VCL me sobrepasa completamente. :(

Ahora solo me queda lo del botoncillo "pulsado" pero bueno eso ya son temas menores... podría utilizar el componente JvSwitch de las JEDI pero me gustaría mas un botón pulsado, como podría hacerlo? existe ya un componente específico? (No se si puedo preguntar esto aquí o tengo que abrir otro post).

Otra vez muchas gracias y hasta otra.

roman 06-12-2004 03:38:50

Lo del botón es sencillo: usa un SpeedButton (paleta Additional) con su propiedad GroupIndx = 1 y su propiedad AllowAllUp = true;

Lo del típico icono con la agujita pinchada será muy típico pero no sé a qué te refieres.

En cuanto a que los de los mensajes de la VCL te sobrepasa en realidad no es tan difícil. Es cuestión de armarse de paciencia y trazar la aplicación a través de la VCL. Para ello, en Project|Options|Compiler seleccionas la opción "Use Debug DCUs".

Lo que yo hice fue poner un punto de ruptura en la línea

FormStyle := fsStayOnTop

y a partir de ahí comencé a trazar el programa con F7. De lo primero que te das cuenta es de que el cambio de estilo provoca una reconstrucción de la ventana, es decir, primero debe destruirse y volverse a crear, así que en un momento dado durante el trazado desaparece la ventana.

Entonces busco si pasa por algún método que pueda redefinir para ahí poner Visible := false de manera que no se muestre en la nueva posición al momento de reconstruirse y la muestre yo mismo hasta que la cambie de lugar.

Desafortunadamente no hay tal método, mejor dicho sí lo hay pero ocurre antes de que cambie la propiedad FormStyle de manera que al poner Visible := true se genera el consabido error de que no se puede esconder una ventana MDIChild.

Entonces sigo trazando hasta ver aparecer de nuevo a la ventana y así me doy cuenta que ocurre justo después de la línea (en el código de la VCL):

Perform(CM_SHOWINGCHANGED, 0, 0);

Entonces es cuando hago el manejador de CM_SHOWINGCHANGED y veo que el inherited no hace gran cosa sino llamar a la función ShowWindow (o SetWindowPos, no recuerdo bien) de la API de Windows así que nada más había que deshabilitarla y ahora sí llamarla nosotros mismos cuando estuvieramos listos.

Te comento esto para que te des una idea de por donde buscar este tipo de cosas en un futuro.

// Saludos

zerelho 08-12-2004 19:21:04

Gracias otra vez roman por las respuestas...
Lo de la agujita pinchada pues no es tan "típico" como pensaba, estoy buscando algun programilla donde la utilice (para coger prestado el icono :p ) y no lo doy encontrado y eso que estoy seguro de ver mogollon de aplicaciones que la utilizan para activar la opción de always on top.
Cuando lo encuentre pongo aqui unas capturas para que sepais de que hablo. Ta'luego.


La franja horaria es GMT +2. Ahora son las 12:05:31.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi