9

Visual Studio 2010では、ドッキング可能なWindowsはあらゆる状況で期待どおりに機能するようです。
「フローティング」ドキュメントがアクティブで、いくつかのメニューが選択されている場合(たとえば、[編集]-> [貼り付け])、[フローティング]ドキュメントにはフォーカスがあり、コマンドはその[フローティング]ウィンドウに対して実行されます。また、これがUIにはっきりと表示されていることに注目してください。チームメニューが選択されていても、MainWindow.xamlはアクティブであり、VisualStudioのメインウィンドウは非アクティブです。

ここに画像の説明を入力してください

さまざまなサードパーティのドッキングコンポーネントを使用して同じ動作を実現しようとしていますが、すべて同じ問題があります。メニューを選択すると、メインウィンドウがフォーカスされ、フローティングウィンドウにフォーカスがなくなります。ここでVisualStudioと同じ動作をする方法を知っている人はいますか?

現在、 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

ビジュアル スタジオ チームは、WPF で VS を作成する際に学んだ教訓に関する有益な情報を提供しています。彼らが遭遇した問題の 1 つは、Focus の管理に関するものでした。その結果、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 がウィンドウのメッセージ ループをフックし、フローティング ウィンドウを機能させるために特定の値を返す可能性があるようです。

私は win32 プログラマーではありませんが、ユーザーが非アクティブなウィンドウでメニューをクリックすると、ウィンドウはマウス ダウン イベントを処理する前にWM_MOUSEACTIVATEメッセージを送信するようです。これにより、メイン ウィンドウは、アクティブにする必要があるかどうかを判断できます。

私の変更されていない WPF テスト アプリでは、非アクティブ ウィンドウはMA_ACTIVATEを返します。ただし、VS はMA_NOACTIVATEを返します。ドキュメントは、これにより、さらに入力を処理する前にメインウィンドウをアクティブにしないようにウィンドウに指示することを示しています。ユーザーがメニュー/ツールバーをクリックすると、ビジュアルスタジオがWindowsメッセージループをフックし、MA_NOACTIVATEを返すと思います。

このコードを最上位ウィンドウに追加することで、シンプルな 2 ウィンドウ 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このスタイルを使用できます

<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.CommandTargetましたXamDockManager.ActivePaneか?

XamDockManager ドキュメントを見ると、「現在 UnpinnedTabFlyout 内にある Infragistics.Windows.DockManager.ContentPane、またはフライアウトが表示されていない場合は null」CurrentFlyoutPaneを返すプロパティも表示されます。どのプロパティがあなたのシナリオに適しているかわかりませんが、試してみる価値はあります。

于 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

これを機能させる方法についてはよくわかりませんが、インフラジスティックスには優れたサポート フォーラムがあることはわかっているので、そこでも質問する価値があるかもしれません。

http://forums.infragistics.com/

于 2011-06-20T11:03:05.213 に答える