ventanas ventana una otra navegacion modal entre emergentes dentro abrir c# .net wpf winapi windows-7

c# - otra - ¿Cómo puedo hacer que una ventana de WPF se mueva arrastrando el marco de la ventana extendida?



ventana modal c# (4)

En aplicaciones como Windows Explorer e Internet Explorer, uno puede tomar las áreas de marco extendidas debajo de la barra de título y arrastrar las ventanas.

Para las aplicaciones de WinForms, los formularios y controles son lo más parecido posible a las API Win32 nativas; uno simplemente anularía el manejador WndProc() en su forma, procesaría el mensaje de la ventana WM_NCHITTEST y WM_NCHITTEST al sistema para que WM_NCHITTEST hacer clic en el área del marco era realmente un clic en la barra de título al devolver HTCAPTION . Lo he hecho en mis propias aplicaciones WinForms con un efecto encantador.

En WPF, también puedo implementar un método WndProc() similar y engancharlo al controlador de mi ventana WPF mientras extiendo el marco de la ventana al área del cliente, así:

// In MainWindow // For use with window frame extensions private IntPtr hwnd; private HwndSource hsource; private void Window_SourceInitialized(object sender, EventArgs e) { try { if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero) { throw new InvalidOperationException("Could not get window handle for the main window."); } hsource = HwndSource.FromHwnd(hwnd); hsource.AddHook(WndProc); AdjustWindowFrame(); } catch (InvalidOperationException) { FallbackPaint(); } } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { case DwmApiInterop.WM_NCHITTEST: handled = true; return new IntPtr(DwmApiInterop.HTCAPTION); default: return IntPtr.Zero; } }

El problema es que, como estoy configurando a ciegas handled = true y devolviendo HTCAPTION , al hacer clic en cualquier lugar excepto el icono de ventana o los botones de control ocasiona que se arrastre la ventana. Es decir, todo lo resaltado en rojo a continuación provoca arrastre. Esto incluso incluye los controladores de cambio de tamaño a los lados de la ventana (el área no cliente). Mis controles WPF, a saber, los cuadros de texto y el control de pestañas, también dejan de recibir clics como resultado:

Lo que quiero es solo

  1. la barra de título, y
  2. las regiones del área de cliente ...
  3. ... que no están ocupados por mis controles

ser arrastrable Es decir, solo quiero que estas regiones rojas sean arrastrables (área de cliente + barra de título):

¿Cómo WndProc() mi método WndProc() y el resto del código XAML / código subyacente de mi ventana para determinar qué áreas deberían devolver HTCAPTION y cuáles no? Estoy pensando en utilizar el sistema Point s para verificar la ubicación del clic en las ubicaciones de mis controles, pero no estoy seguro de cómo hacerlo en WPF Land.

EDITAR [4/24]: una forma simple de hacerlo es tener un control invisible, o incluso la ventana misma, responder a MouseLeftButtonDown invocando DragMove() en la ventana (ver la respuesta de Ross ). El problema es que, por alguna razón, DragMove() no funciona si la ventana está maximizada, por lo que no funciona bien con Windows 7 Aero Snap. Ya que voy a la integración de Windows 7, no es una solución aceptable en mi caso.


Código de muestra

Gracias a un correo electrónico que recibí esta mañana, me pidieron que creara una aplicación de ejemplo que demostrara esta funcionalidad. He hecho eso ahora; puedes encontrarlo en GitHub (o en el CodePlex ahora archivado ). Solo clone el repositorio o descargue y extraiga un archivo, luego ábralo en Visual Studio y compile y ejecútelo.

La aplicación completa en su totalidad es licenciada por MIT, pero probablemente lo desmantelará y pondrá partes de su código en su lugar en lugar de usar el código de la aplicación en su totalidad, aunque tampoco la licencia le impida hacer eso. Además, aunque sé que el diseño de la ventana principal de la aplicación no es ni de lejos similar a los wireframes anteriores, la idea es la misma que se plantea en la pregunta.

Espero que esto ayude a alguien!

Solución paso-a-paso

Finalmente lo resolví. ¡Gracias a Jeffrey L Whitledge por señalarme en la dirección correcta! Su respuesta fue aceptada porque si no fuera por ella no habría logrado encontrar una solución. EDIT [9/8]: esta respuesta ahora se acepta ya que es más completa; Le daré a Jeffrey una gran recompensa por su ayuda.

Por el bien de la posteridad, así es como lo hice (citando la respuesta de Jeffrey donde sea relevante a medida que avanzo):

Obtenga la ubicación del clic del mouse (¿del wParam, quizás lParam?), Y úselo para crear un Point (posiblemente con algún tipo de transformación de coordenadas).

Esta información se puede obtener del lParam del mensaje WM_NCHITTEST . La coordenada x del cursor es su palabra de orden inferior y la coordenada y del cursor es su palabra de orden superior, como lo WM_NCHITTEST .

Dado que las coordenadas son relativas a toda la pantalla, necesito llamar a Visual.PointFromScreen() en mi ventana para convertir las coordenadas en relación con el espacio de la ventana.

Luego llame al método estático VisualTreeHelper.HitTest(Visual,Point) pasándolo y al Point que acaba de VisualTreeHelper.HitTest(Visual,Point) . El valor de retorno indicará el control con el Z-Order más alto.

Tuve que pasar el control de Grid nivel superior en lugar de this como visual para probar el punto. Asimismo, tuve que comprobar si el resultado era nulo en lugar de verificar si era la ventana. Si es nulo, el cursor no golpeó ninguno de los controles secundarios de la cuadrícula; en otras palabras, toca la región de marco de la ventana desocupada. De todos modos, la clave era usar el método VisualTreeHelper.HitTest() .

Ahora, una vez dicho esto, hay dos advertencias que pueden aplicarse a usted si sigue mis pasos:

  1. Si no cubre toda la ventana, y en su lugar solo extiende parcialmente el marco de la ventana, debe colocar un control sobre el rectángulo que no está relleno por el marco de la ventana como relleno del área del cliente.

    En mi caso, el área de contenido de mi control de tabulación se ajusta perfectamente a esa área rectangular, como se muestra en los diagramas. En su aplicación, puede que necesite colocar una forma de Rectangle o un control de Panel y pintar el color apropiado. De esta forma el control será golpeado.

    Este problema sobre los rellenos de área del cliente lleva al siguiente:

  2. Si su cuadrícula u otro control de nivel superior tiene una textura de fondo o un degradado sobre el marco de la ventana extendida, toda el área de la cuadrícula responderá al golpe, incluso en cualquier región completamente transparente del fondo (consulte Prueba de Impacto en la Capa Visual ). En ese caso, querrás ignorar los hits contra la grilla en sí, y solo prestar atención a los controles dentro de ella.

Por lo tanto:

// In MainWindow private bool IsOnExtendedFrame(int lParam) { int x = lParam << 16 >> 16, y = lParam >> 16; var point = PointFromScreen(new Point(x, y)); // In XAML: <Grid x:Name="windowGrid">...</Grid> var result = VisualTreeHelper.HitTest(windowGrid, point); if (result != null) { // A control was hit - it may be the grid if it has a background // texture or gradient over the extended window frame return result.VisualHit == windowGrid; } // Nothing was hit - assume that this area is covered by frame extensions anyway return true; }

La ventana ahora se puede mover haciendo clic y arrastrando solo las áreas desocupadas de la ventana.

Pero eso no es todo. Recuerde en la primera ilustración que el área no cliente que comprende los bordes de la ventana también se vio afectada por HTCAPTION por lo que la ventana ya no se podía redimensionar.

Para solucionar esto, tuve que comprobar si el cursor estaba tocando el área del cliente o el área no cliente. Para comprobar esto, necesitaba usar la función DefWindowProc() y ver si devolvía HTCLIENT :

// In my managed DWM API wrapper class, DwmApiInterop public static bool IsOnClientArea(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam) { if (uMsg == WM_NCHITTEST) { if (DefWindowProc(hWnd, uMsg, wParam, lParam).ToInt32() == HTCLIENT) { return true; } } return false; } // In NativeMethods [DllImport("user32.dll")] private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);

Finalmente, aquí está mi método de procedimiento de ventana final:

// In MainWindow private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { case DwmApiInterop.WM_NCHITTEST: if (DwmApiInterop.IsOnClientArea(hwnd, msg, wParam, lParam) && IsOnExtendedFrame(lParam.ToInt32())) { handled = true; return new IntPtr(DwmApiInterop.HTCAPTION); } return IntPtr.Zero; default: return IntPtr.Zero; } }


Aquí hay algo que podrías probar:

Obtenga la ubicación del clic del mouse (¿del wParam, quizás lParam?), Y úselo para crear un Point (posiblemente con algún tipo de transformación de coordenadas).

Luego llame al método estático VisualTreeHelper.HitTest(Visual,Point) pasándolo y al Point que acaba de VisualTreeHelper.HitTest(Visual,Point) . El valor de retorno indicará el control con el Z-Order más alto. Si esa es su ventana, entonces haga su vudú HTCAPTION . Si es otro control, entonces ... no.

¡Buena suerte!


Buscando hacer lo mismo (hacer que mi Aero se pueda arrastrar en mi aplicación WPF), me encontré con esta publicación a través de Google. Leí su respuesta, pero decidí seguir buscando para ver si había algo más simple.

Encontré una solución mucho menos intensiva de código.

Simplemente crea un elemento transparente detrás de tus controles y dale un controlador de evento de botón izquierdo del mouse que llama al método DragMove() la ventana.

Aquí está la sección de mi XAML que aparece sobre mi cristal Aero extendido:

<Grid DockPanel.Dock="Top"> <Border MouseLeftButtonDown="Border_MouseLeftButtonDown" Background="Transparent" /> <Grid><!-- My controls are in here --></Grid> </Grid>

Y el código subyacente (esto está dentro de una clase Window , y así DragMove() está disponible para llamar directamente):

private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { DragMove(); }

¡Y eso es! Para su solución, debería agregar más de uno de estos para lograr su área no rectangular arrastrable.


manera simple es crear stackpanel o todo lo que quieras para tu barra de título XAML

<StackPanel Name="titleBar" Background="Gray" MouseLeftButtonDown="titleBar_MouseLeftButtonDown" Grid.ColumnSpan="2"></StackPanel>

código

private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { DragMove(); }