9

在 Visual Studio 2010 中,Dockable Windows 似乎在任何情况下都能正常工作。
如果“浮动”文档处于活动状态并且选择了某个菜单(例如编辑 -> 粘贴),则“浮动”文档仍然具有焦点,并且将针对该“浮动”窗口执行命令。另外,请注意这在 UI 中是如何清晰可见的。MainWindow.xaml 仍然处于活动状态,并且即使选择了 Team-menu,Visual Studio 中的主窗口也处于非活动状态。

在此处输入图像描述

我一直在尝试使用许多不同的 3rd-party 对接组件来获得相同的行为,但它们都有相同的问题:一旦我选择菜单,MainWindow 就会聚焦,而我的浮动窗口不再有焦点。有谁知道在这里获得与 Visual Studio 中相同的行为的方法?

目前我正在使用Infragistics xamDockManager并且可以使用以下示例代码重现该问题。

  • 右键单击“标题1”并选择“浮动”
  • 单击“文件”菜单
  • 注意 MainWindow 如何获得焦点。

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>
4

5 回答 5

15

Visual Studio 团队有一些关于他们在 WPF 中制作 VS 时学到的经验教训的好信息。他们遇到的问题之一与焦点管理有关。因此,WPF 4 有一些新功能可以提供帮助。

这是听起来像您的情况的问题的信息:

http://blogs.msdn.com/b/visualstudio/archive/2010/03/09/wpf-in-visual-studio-2010-part-3-focus-and-activation.aspx

他们对新的“HwndSource.DefaultAcquireHwndFocusInMenuMode”属性的讨论听起来与您遇到的非常相似。

编辑

经过进一步调查,看起来 Visual Studio 可能正在挂钩 Windows 消息循环并返回特定值以使浮动窗口工作。

我不是 win32 程序员,但似乎当用户在非活动窗口中单击菜单时,Windows会在处理鼠标按下事件之前向其发送WM_MOUSEACTIVATE消息。这让主窗口决定是否应该激活它。

在我未修改的 WPF 测试应用程序中,非活动窗口返回MA_ACTIVATE。但是,VS 返回MA_NOACTIVATE。文档表明这告诉 Windows 在处理进一步输入之前不要激活主窗口。我猜 Visual Studio 会钩住 Windows 消息循环并在用户单击菜单/工具栏时返回MA_NOACTIVATE 。

通过将此代码添加到顶级窗口,我能够在一个简单的两个窗口 WPF 应用程序中完成这项工作。

    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;
    }

在您的情况下,您可能需要添加更多逻辑来检查用户单击的内容并根据该逻辑决定是否拦截消息并返回 MA_NOACTIVATE。

编辑 2

我附上了一个示例 WPF 应用程序,它展示了如何使用一个简单的 WPF 应用程序来做到这一点。这应该与停靠工具包中的浮动窗口几乎相同,但我还没有测试过那个特定的场景。

该示例位于:http: //blog.alner.net/downloads/floatingWindowTest.zip

该示例有代码注释来解释它是如何工作的。要查看它的实际效果,请运行示例,单击“打开另一个窗口”按钮。这应该将焦点放在新窗口的文本框中。现在,单击主窗口的编辑菜单并使用“全选”等命令。这些应该在另一个窗口上运行,而不会将“主窗口”带到前台。

您还可以单击“退出”菜单项,以查看它是否仍可以根据需要将命令路由到主窗口。

关键点(激活/聚焦):

  1. 使用 HwndSource.DefaultAcquireHwndFocusInMenuMode 让菜单停止抓取焦点。
  2. 钩住消息循环并在用户单击菜单时返回“MA_NOACTIVATE”。
  3. 将事件处理程序添加到菜单的 PreviewGotKeyboardFocus 并将 e.Handled 设置为 true 以便菜单不会尝试获取焦点。

关键点(命令):

  1. 挂钩主窗口的“CommandManager.PreviewCanExecute”和“CommandManager.PreviewExecuted”事件。
  2. 在这些事件中,检测应用程序是否具有应该是事件目标的“其他窗口”。
  3. 针对“其他窗口”手动调用原始命令。

希望对你有效。如果没有,请告诉我。

于 2011-07-18T21:03:48.777 回答
5

我使用了NathanAW的出色答案并创建了一个ResourceDictionary包含Stylefor Window(应该由 使用MainWindow),包含解决此问题的关键部分。

更新:增加了对ToolBar以及的支持Menu

它包括专门针对 MainMenu 或 ToolBar 的命中测试,以确定是否应允许聚焦。

我为此使用 ResourceDictionary 的原因是为了可重用性,因为我们将在许多项目中使用它。此外,MainWindow 后面的代码可以保持干净。

MainWindow可以使用这种风格

<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
    }
}
于 2011-07-21T03:29:21.490 回答
1

只是出于好奇,您是否尝试过将 绑定MenuItem.CommandTargetXamDockManager.ActivePane

查看 XamDockManager 文档,我还看到一个CurrentFlyoutPane属性,它返回“Infragistics.Windows.DockManager.ContentPane 当前位于 UnpinnedTabFlyout 中,如果未显示浮出控件,则返回 null”。我不确定哪个属性适合您的场景,但值得一试。

于 2011-07-18T18:07:07.717 回答
1

我知道这是一篇旧帖子,但 Prism 可以让你的生活变得更轻松。使用此处创建的 RegionAdapter:

http://brianlagunas.com/2012/09/12/xamdockmanagera-prism-regionadapter/

通过使用 IActiveAware 界面,您可以轻松跟踪哪个窗口处于活动状态、浮动与否。Prisms 命令也考虑到这一点,并且只能在活动视图上执行命令。该博客文章有一个示例应用程序,您可以使用它。

于 2012-09-13T00:58:43.870 回答
0

我不确定如何进行这项工作,但我知道 Infragistics 有一个很棒的支持论坛,所以在那里问问题也可能是值得的。

http://forums.infragistics.com/

于 2011-06-20T11:03:05.213 回答