Док-версия Windows.Интеграция с плавающим окном и меню MainWindow - PullRequest
9 голосов
/ 20 июня 2011

В Visual Studio 2010 Dockable Windows работает, как и ожидалось, в любой ситуации.
Если «плавающий» документ активен и выбрано какое-то меню (например, «Правка» -> «Вставить»), то «плавающий» документ все еще имеет фокус, и команда будет выполнена для этого «плавающего» окна. Также обратите внимание, как это хорошо видно в пользовательском интерфейсе. MainWindow.xaml по-прежнему активен, а главное окно в Visual Studio неактивно, даже если выбрано Team-menu.

enter image description here

Я пытался добиться такого же поведения, используя множество сторонних стыковочных компонентов, но у них у всех одна и та же проблема: после выбора меню основное окно фокусируется, а мое плавающее окно больше не фокусируется. Кто-нибудь знает способ получить такое же поведение здесь, как в Visual Studio?

В настоящее время я использую Infragistics xamDockManager , и проблему можно воспроизвести с помощью следующего примера кода.

  • Щелкните правой кнопкой мыши «Заголовок 1» и выберите «Float»
  • Нажмите меню «Файл»
  • Обратите внимание, как 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>

Ответы [ 5 ]

15 голосов
/ 19 июля 2011

Команда Visual Studio обладает хорошей информацией об уроках, которые они извлекли при создании VS в WPF.Одна из проблем, с которыми они столкнулись, была связана с управлением фокусом.В результате в 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" звучит очень похоже на то, с чем вы сталкиваетесь.

EDIT

После дальнейшего исследования кажется, что Visual Studio может подключатьсяцикл сообщений Windows и возвращение определенных значений, чтобы заставить работать плавающие окна.

Я не программист на win32, но кажется, что когда пользователь щелкает меню в неактивном окне, Windows отправляет ему сообщение WM_MOUSEACTIVATE перед обработкой события мыши.Это позволяет главному окну определить, следует ли его активировать.

В моем неизмененном тестовом приложении WPF неактивное окно возвращает MA_ACTIVATE .Однако VS возвращает MA_NOACTIVATE .Документы указывают, что это говорит окнам НЕ активировать главное окно перед обработкой дальнейшего ввода.Я предполагаю, что 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. Вручную вызвать исходную команду против" другого окна ".

Надеждаэто работает для вас.Если нет, дайте мне знать.

5 голосов
/ 21 июля 2011

Я использовал отличный ответ от NathanAW и создал ResourceDictionary, содержащий Style для 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
    }
}
1 голос
/ 13 сентября 2012

Я знаю, что это старый пост, но Prism может сделать вашу жизнь намного проще. Используя RegionAdapter, созданный здесь:

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

Вы можете легко отслеживать, какое окно является активным, плавающим или нет, используя интерфейс IActiveAware. Команды Prism также учитывают это и могут выполнять команды только в активном представлении. В блоге есть пример приложения, с которым вы можете поиграть.

1 голос
/ 18 июля 2011

Просто из любопытства вы пытались связать MenuItem.CommandTarget с XamDockManager.ActivePane?

Глядя на документацию по XamDockManager, я также вижу свойство CurrentFlyoutPane, которое возвращает "Infragistics.Windows.DockManager.ContentPane, который в настоящее время находится в UnpinnedTabFlyout, или ноль, если всплывающее окно не отображается." I ' Я не уверен, какое свойство будет уместным в вашем сценарии, но стоит попробовать.

0 голосов
/ 20 июня 2011

Я не уверен, как заставить это работать, но я знаю, что у Infragistics есть отличный форум поддержки, поэтому, возможно, стоит задать и там вопрос.

http://forums.infragistics.com/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...