modal - ventana emergente c# wpf
Windows acoplable. Ventana flotante e integración del menú MainWindow (5)
En Visual Studio 2010, Windows acoplable parece funcionar como se espera en cada situación.
Si un documento "Flotante" está activo y se ha seleccionado un menú (por ejemplo, Editar -> Pegar), el documento "Flotante" todavía tiene Enfoque y el comando se ejecutará contra esa ventana "Flotante". Además, observe cómo esto es claramente visible en la interfaz de usuario. MainWindow.xaml aún está activo y la ventana principal en Visual Studio está inactiva aunque se haya seleccionado el menú del equipo.
He intentado obtener el mismo comportamiento utilizando muchos componentes de acoplamiento de terceros diferentes, pero todos tienen el mismo problema: una vez que selecciono el menú, la ventana principal se enfoca y mi ventana flotante ya no tiene foco. ¿Alguien sabe de una manera de obtener el mismo comportamiento aquí que en Visual Studio?
En este momento estoy usando Infragistics xamDockManager y el problema puede ser reproducido con el siguiente código de muestra.
- Haga clic derecho en "Encabezado 1" y seleccione "Float"
- Haga clic en el menú "Archivo"
- Observe cómo MainWindow recibe el foco.
xmlns: igDock = "http://infragistics.com/DockManager"
<DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New"/>
</MenuItem>
</Menu>
<Grid>
<igDock:XamDockManager x:Name="dockManager" Theme="Aero">
<igDock:DocumentContentHost>
<igDock:SplitPane>
<igDock:TabGroupPane>
<igDock:ContentPane Header="Header 1">
<TextBox Text="Some Text"/>
</igDock:ContentPane>
<igDock:ContentPane Header="Header 2">
<TextBox Text="Some Other Text"/>
</igDock:ContentPane>
</igDock:TabGroupPane>
</igDock:SplitPane>
</igDock:DocumentContentHost>
</igDock:XamDockManager>
</Grid>
</DockPanel>
No estoy seguro de cómo hacer que esto funcione, pero sí sé que los Infragistics tienen un gran foro de apoyo, por lo que también puede valer la pena preguntar allí.
Solo por curiosidad, ¿ha intentado enlazar el MenuItem.CommandTarget
con el XamDockManager.ActivePane
?
Al observar la documentación de XamDockManager, también veo una propiedad CurrentFlyoutPane
que devuelve "Infragistics.Windows.DockManager.ContentPane actualmente dentro de UnpinnedTabFlyout o null si el menú desplegable no se muestra". No estoy seguro de qué propiedad sería apropiada en su escenario, pero vale la pena intentarlo.
El equipo del estudio visual tiene buena información sobre las lecciones que aprendieron al crear VS en WPF. Uno de los problemas con los que se encontraron estaba relacionado con la administración de Focus. Como resultado, WPF 4 tiene algunas características nuevas para ayudar.
Aquí está la información sobre el problema que se parece a su situación:
Su discusión de la nueva propiedad "HwndSource.DefaultAcquireHwndFocusInMenuMode" suena muy similar a lo que se está ejecutando.
EDITAR
Después de más investigaciones, parece que Visual Studio podría estar enganchando el bucle de mensajes de Windows y devolviendo valores específicos para hacer que las ventanas flotantes funcionen.
No soy un programador de win32, pero parece que cuando un usuario hace clic en un menú en una ventana inactiva, Windows le envía el mensaje WM_MOUSEACTIVATE antes de procesar el evento del mouse hacia abajo. Esto permite que la ventana principal determine si debe activarse.
En mi aplicación de prueba WPF no modificada, la ventana inactiva devuelve MA_ACTIVATE . Sin embargo, VS devuelve MA_NOACTIVATE . Los documentos indican que esto le indica a las ventanas que NO activen la ventana principal antes de manejar entradas adicionales. Supongo que Visual Studio engancha el bucle de mensajes de Windows y devuelve MA_NOACTIVATE cuando el usuario hace clic en los menús / barras de herramientas.
Pude hacer que esto funcionara en una aplicación WPF simple de dos ventanas al agregar este código a la ventana de nivel superior.
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var hook = new HwndSourceHook(this.FilterMessage);
var source2 = HwndSource.FromVisual(this) as HwndSource;
source2.AddHook(hook);
}
private IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_MOUSEACTIVATE = 0x0021;
const int MA_NOACTIVATE = 3;
switch (msg)
{
case WM_MOUSEACTIVATE:
handled = true;
return new IntPtr(MA_NOACTIVATE);
}
return IntPtr.Zero;
}
En su caso, probablemente necesite agregar más lógica que verifique en qué hizo clic el usuario y decidir en función de si interceptar el mensaje y devolver MA_NOACTIVATE.
EDIT 2
He adjuntado una aplicación WPF de muestra que muestra cómo hacerlo con una aplicación WPF simple. Esto debería funcionar de manera similar con las ventanas flotantes de un conjunto de herramientas de acoplamiento, pero no he probado ese escenario específico.
La muestra está disponible en: http://blog.alner.net/downloads/floatingWindowTest.zip
La muestra tiene comentarios de código para explicar cómo funciona. Para verlo en acción, ejecute la muestra, haga clic en el botón "abrir otra ventana". Esto debería poner el foco en el cuadro de texto de la nueva ventana. Ahora, haga clic en el menú de edición de la ventana principal y use los comandos como "seleccionar todo". Estos deberían operar en la otra ventana sin poner la "ventana principal" en primer plano.
También puede hacer clic en el elemento de menú "salir" para ver que aún puede enrutar comandos a la ventana principal si es necesario.
Puntos clave (Activación / Enfoque):
- Use HwndSource.DefaultAcquireHwndFocusInMenuMode para que los menús funcionen y dejen de capturar el foco.
- Enganche el bucle de mensaje y devuelva "MA_NOACTIVATE" cuando el usuario haga clic en el menú.
- Agregue un controlador de eventos al PreviewGotKeyboardFocus del menú y configure e.Handled en true para que el menú no intente captar el foco.
Puntos clave (Comandos):
- Enganche los eventos "CommandManager.PreviewCanExecute" y "CommandManager.PreviewExecuted" de la ventana principal.
- En estos eventos, detectar si la aplicación tiene una "otra ventana" que se supone que es el objetivo de los eventos.
- Invocar manualmente el comando original contra la "otra ventana".
Espero que funcione para ti. Si no, házmelo saber.
Sé que esta es una publicación anterior, pero Prism podría hacerte la vida mucho más fácil. Usando el RegionAdapter creado aquí:
http://brianlagunas.com/2012/09/12/xamdockmanagera-prism-regionadapter/
Puede rastrear fácilmente qué ventana está activa, flotante o no, utilizando la interfaz IActiveAware. Los comandos Prismas también toman esto en consideración y pueden ejecutar comandos solo en la vista activa. La publicación del blog tiene una aplicación de muestra con la que puedes jugar.
Utilicé la gran respuesta de NathanAW y creé un ResourceDictionary
contenía un Style
para Window
(que debería ser utilizado por MainWindow
), que contenía las piezas clave para resolver este problema.
Actualización: Se agregó soporte para ToolBar
y también Menu
Incluye pruebas de detección específicas para MainMenu o ToolBar para decidir si se debe permitir el enfoque.
La razón por la que he usado un ResourceDictionary para esto es por reutilización ya que lo usaremos en muchos proyectos. Además, el código detrás de MainWindow puede mantenerse limpio.
MainWindow
puede usar este estilo con
<Window...>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NoFocusMenuWindowDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Window.Style>
<StaticResource ResourceKey="NoFocusMenuWindow"/>
</Window.Style>
<!--...-->
</Window>
NoFocusMenuWindowDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MainWindowVS2010Mode.NoFocusMenuWindowDictionary">
<Style x:Key="NoFocusMenuWindow" TargetType="Window">
<EventSetter Event="Loaded" Handler="MainWindow_Loaded"/>
</Style>
<Style TargetType="Menu">
<EventSetter Event="PreviewGotKeyboardFocus"
Handler="Menu_PreviewGotKeyboardFocus"/>
</Style>
<Style TargetType="ToolBar">
<EventSetter Event="PreviewGotKeyboardFocus"
Handler="ToolBar_PreviewGotKeyboardFocus"/>
</Style>
</ResourceDictionary>
NoFocusMenuWindowDictionary.xaml.cs
namespace MainWindowVS2010Mode
{
public partial class NoFocusMenuWindowDictionary
{
#region Declaration
private static Window _mainWindow;
private static bool _mainMenuOrToolBarClicked;
#endregion // Declaration
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_mainWindow = sender as Window;
HwndSource.DefaultAcquireHwndFocusInMenuMode = true;
Keyboard.DefaultRestoreFocusMode = RestoreFocusMode.None;
HwndSource hwndSource = HwndSource.FromVisual(_mainWindow) as HwndSource;
hwndSource.AddHook(FilterMessage);
}
private static IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_MOUSEACTIVATE = 0x0021;
const int MA_NOACTIVATE = 3;
switch (msg)
{
case WM_MOUSEACTIVATE:
if (ClickedMainMenuOrToolBarItem())
{
handled = true;
return new IntPtr(MA_NOACTIVATE);
}
break;
}
return IntPtr.Zero;
}
#region Hit Testing
private static bool ClickedMainMenuOrToolBarItem()
{
_mainMenuOrToolBarClicked = false;
Point clickedPoint = Mouse.GetPosition(_mainWindow);
VisualTreeHelper.HitTest(_mainWindow,
null,
new HitTestResultCallback(HitTestCallback),
new PointHitTestParameters(clickedPoint));
return _mainMenuOrToolBarClicked;
}
private static HitTestResultBehavior HitTestCallback(HitTestResult result)
{
DependencyObject visualHit = result.VisualHit;
Menu parentMenu = GetVisualParent<Menu>(visualHit);
if (parentMenu != null && parentMenu.IsMainMenu == true)
{
_mainMenuOrToolBarClicked = true;
return HitTestResultBehavior.Stop;
}
ToolBar parentToolBar = GetVisualParent<ToolBar>(visualHit);
if (parentToolBar != null)
{
_mainMenuOrToolBarClicked = true;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
}
public static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
#endregion // Hit Testing
#region Menu
private void Menu_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
Menu menu = sender as Menu;
if (menu.IsMainMenu == true)
{
e.Handled = true;
}
}
#endregion // Menu
#region ToolBar
private void ToolBar_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
#endregion // ToolBar
}
}