2011-06-20 56 views
9

在Visual Studio 2010中,可停靠窗口在任何情况下都可以像预期的那样工作。
如果“浮动”文档处于活动状态并且选择了某个菜单(例如编辑 - >粘贴),则“浮动”文档仍具有焦点并且将针对该“浮动”窗口执行该命令。另外,请注意这在UI中如何清晰可见。即使选择了团队菜单,MainWindow.xaml仍处于活动状态,并且Visual Studio中的主窗口处于非活动状态。可停靠的窗户。浮动窗口和MainWindow菜单集成

enter image description here

我一直在尝试使用不同的第三方对接组件很多以获得相同的行为,但他们都有着同样的问题:一旦我选择菜单,在主窗口的重点是,我的浮动窗口不再有焦点了。有没有人知道在Visual Studio中获取相同行为的方法?

目前我使用的是Infragistics xamDockManager,可以通过下面的示例代码复制问题。

  • 右键点击“标题1”,然后选择“浮动”
  • 单击“文件”菜单
  • 通知主窗口如何获得焦点。

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

回答

15

视觉工作室团队有一些很好的信息,他们在WPF中制作VS时学到的经验教训。他们遇到的其中一个问题与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不要在处理进一步输入之前激活主窗口。我猜测,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。

EDIT 2

我已经附上sample WPF application,显示了如何用一个简单的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. 针对“其他窗口”手动调用原始命令。

希望它适合你。如果没有,请告诉我。

+0

感谢您的链接,我实际上已经阅读了三次。虽然不能真正使用这些属性来工作,但是,他们看起来好像是为了解决像这样的问题。有什么想法如何工作?:) –

+0

Meleak,我添加了一些信息,我发现通过使用Spy ++来观看VS发送的Windows消息。通过将此代码添加到主窗口,我能够使一个简单的测试应用程序的行为与VS类似。主窗口有一个菜单控件,并且还打开了一个子窗口。通过上面的代码,点击菜单时,子窗口不会松动焦点。 – NathanAW

+0

我已经创建了一个显示这个工作的示例。有几个比上面提到的更少的细节。这些在示例的代码注释中进行了描述。 – NathanAW

0

林不知道如何使这项工作,但我不知道的Infragistics有一个很大的支持论坛,以便那里也许值得提问。

http://forums.infragistics.com/

+0

感谢您的回答,是的,我已经在infragistics论坛上问过同样的问题:) –

1

只是出于好奇,你试图绑定MenuItem.CommandTargetXamDockManager.ActivePane

望着XamDockManager文档,我也看到了CurrentFlyoutPane属性返回“的Infragistics.Windows.DockManager.ContentPane目前内UnpinnedTabFlyout或空,如果没有显示弹出窗口。”我不确定哪个属性适合您的情况,但值得一试。

+0

我尝试过ActivePane,它会在菜单打开后立即更改..我没有尝试过CurrentFlyoutPane,我会尝试一下。即使它起作用,仍然会留下这样的问题,即当用户使用菜单时,另一个文档变得焦点时,用户会感到困惑。 –

5

我使用了大的答案从NathanAW创造了ResourceDictionary含有Window一个Style(应由MainWindow使用),包含在关键件来解决这个问题。

更新:ToolBar增加支持以及Menu

它包括命中测试专门为MainMenu的栏或工具栏,以决定是否重点应该被允许。

我之所以使用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 
    } 
} 
+0

非常好,非常干净!我喜欢你如何将它设置为风格并简化代码。感谢您发布这回! – NathanAW

+0

嘿,我发布了另一个答案,如果你和其他人看着它,会很酷,显然你必须在调用Window.Show()之前设置HwndSource属性..我能够在没有过滤消息的情况下使它工作,但是我仍然必须处理GotKeyboardFocus并将其设置为处理 – Alan

+0

这是一个很好的开始。我将这些代码作为附加属性和全局样式的Menu和ToolBar元素与附加属性一起使用。不幸的是,它不是支持键盘的应用程序的完整解决方案。例如,按下ALT + F转到文件菜单将无法从浮动窗口中工作。当主窗口处于活动状态时,ALT + F不会聚焦(由于e.Handled = true),因此无法使用箭头键导航主菜单。我正在为我的应用程序开发解决方案。如果它足够通用,我会发布细节。 VS 2010以某种方式处理这个问题。 –

1

I K现在这是一个旧帖子,但棱镜可以让你的生活变得更容易。使用此创建的RegionAdapter:

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

您可以轻松地跟踪哪个窗口是活动的,浮动与否,使用IActiveAware接口。棱镜命令也考虑到了这一点,并且只能在活动视图上执行命令。博客文章中有一个示例应用程序,您可以随时使用。

+1

我在使用IActiveAware的Prism应用程序中遇到了同样的问题,它可能实际上取决于DockSiteRegionAdapter,Region,Behavior的实现方式。我没有看到他们处理这些事件,虽然... – Alan

+0

我写了关于采取上述方法,并简化了Prism CompositeCommands和IActiveAware DelegateCommands它。 http://brianlagunas.com/wpf-xamdockmanager-floating-panes-and-xamribbon-menu-integration/ –