WPF ContextMenu放置调整事件

时间:2012-10-04 20:27:19

标签: wpf contextmenu placement

有没有人知道我如何确定contextMenu何时因为太靠近屏幕边缘而自动调整其位置?

我的情况是我有一个contextMenu有2个圆角和2个方角。当菜单打开时我绕着底部2 ...如果菜单向上打开然后我绕过顶部2.问题是我没有找到要绑定的事件或属性告诉我菜单何时获取它的方向自动改变了。

这是一些简化的示例代码。如果在窗口位于屏幕顶部时单击,则菜单将关闭。如果您将窗口移动到屏幕底部,则菜单将会上升。

<Window x:Class="menuRedirection.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="100" Width="200">
  <DockPanel Name="panel" ContextMenuOpening="DockPanel_ContextMenuOpening">
    <DockPanel.ContextMenu>
      <ContextMenu>
        <MenuItem Header="item"/>
        <MenuItem Header="item"/>
        <MenuItem Header="item"/>
        <MenuItem Header="item"/>
      </ContextMenu>
    </DockPanel.ContextMenu>
    <Rectangle DockPanel.Dock="Bottom" Name="menuTarget" Fill="Red" Height="10"/>
    <TextBlock DockPanel.Dock="Top" Text="right click for context menu"/>
  </DockPanel>
</Window>

private void DockPanel_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
  ContextMenuService.SetPlacement(panel, PlacementMode.Bottom);
  ContextMenuService.SetPlacementTarget(panel, menuTarget);
}

这是真正的应用程序的样子,所以你可以看到我需要知道调整圆角的问题。

enter image description here

2 个答案:

答案 0 :(得分:2)

据我所知,这是不可能的。

使用JustDecompile,我将此功能跟踪到UpdatePosition类中的Popup方法。最终位置似乎在这里设置:

    this._positionInfo.X = num4;
    this._positionInfo.Y = num5;
    this._secHelper.SetPopupPos(true, num4, num5, false, 0, 0);

_secHelperPopupSecurityHelper类型的帮助类,似乎只是一个内部帮助程序......而且,这些都不会导致事件或公共属性发生变化。

Here is an MSDN article explaining how popup positioning is determined in general(当弹出窗口遇到屏幕边缘时'描述您的场景)。

但是,this article解释了如何使用CustomPopupPlacementCallback稍微覆盖这些行为。但是,这仍然使用PopupPrimaryAxis,它应该根据需要翻转菜单,并将导致相同的问题。

我能想到的另一件事是你可以查看PlacementRectangle并且可能会调查大小和位置,类似于UpdatePosition的处理方式......或者只是查看弹出窗口本身就像UpdatePosition一样。

这是一种私人方法。因此,您尝试模仿的任何逻辑都可能在未来版本的框架中发生变化。

<强>更新

此外,您可以尝试对PointToScreenPointFromScreen进行混淆,但如果有效的话,这将是非常复杂的代码......

答案 1 :(得分:2)

我无法找到真正的WPF解决方案,但Justin的评论引导我尝试将菜单的位置与PlacementTarget的位置进行比较。

第一步是订阅contextMenu.Loaded事件(这在布局处理之后但在屏幕上完全可见之前触发)。

<ContextMenu ContextMenu.Loaded="ContextMenu_Loaded">

然后当它触发时我可以判断菜单是否在内部切换到我请求的placementMode的备用位置。如果它被反转,那么我继续相应地调整我的圆角。

注意:我最初使用了getWindowRect并将菜单Rect与目标的Rect进行了比较,但发现菜单Rect总是返回先前实例的位置。为了避免这个问题,我现在得到相关屏幕的workingArea并手动查看菜单是否合适。

注意2:确保菜单的模板导致倒置和常规显示的窗口高度相同。否则,您的计算可能会关闭,因为getWindowRect返回最后一个菜单的大小。

void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
  bool reversed = isMenuDirectionReversed(this.ContextMenu);

  //existing styles are read-only so we have to make a clone to change a property
  if (reversed)
  {//round the top corners if the menu is travelling upward
    Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
    newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(10, 10, 0, 0) });
    this.ContextMenu.Style = newStyle;
  }
  else
  { //since we may have overwritten the style in a previous evaluation, 
    //we also need to set the downward corners again    
    Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
    newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(0, 0, 10, 10) });
    this.ContextMenu.Style = newStyle;
  }
}

评估方法:

private bool isMenuDirectionReversed(ContextMenu menu)
{
  //get the window handles for the popup' placement target
  IntPtr targetHwnd = (HwndSource.FromVisual(menu.PlacementTarget) as HwndSource).Handle;

  //get the relevant screen
  winFormsScreen screen = winFormsScreen.FromHandle(targetHwnd);

  //get the actual point on screen (workingarea not taken into account)
  FrameworkElement targetCtrl = menu.PlacementTarget as FrameworkElement;
  Point targetLoc = targetCtrl.PointToScreen(new Point(0, 0));

  //compute the location for the bottom of the target control
  double targetBottom = targetLoc.Y + targetCtrl.ActualHeight;

  if (menu.Placement != PlacementMode.Bottom)
    throw new NotImplementedException("you need to implement your own logic for other modes");

  return screen.WorkingArea.Bottom < targetBottom + menu.ActualHeight;
}

最终结果:

enter image description here