WPF ContextMenu位置调整事件

yrwegjxp  于 2022-11-18  发布在  其他
关注(0)|答案(2)|浏览(328)

有没有人知道我如何确定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);
}

下面是真实的的应用程序看起来像这样,你可以看到我的问题,需要知道调整我的圆角。

toiithl6

toiithl61#

据我所知,这是不可能的。
使用JustDecompile,我跟踪这个功能到Popup类中的UpdatePosition方法。

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

_secHelperPopupSecurityHelper类型的helper类,似乎只是一个内部helper...并且,这些都不会导致事件甚至公共属性被更改。
Here is an MSDN article explaining how popup positioning is determined in general("当弹出窗口遇到屏幕边缘时“描述了您的场景)。
然而,this article解释了如何使用CustomPopupPlacementCallback来覆盖这些行为。然而,这仍然使用了PopupPrimaryAxis,它应该在必要时翻转菜单,并将导致同样的问题。
我能想到的唯一的另一件事是,你可以查看PlacementRectangle,也许轮询的大小和位置类似于UpdatePosition如何做的事情...或者只是检查弹出本身,就像UpdatePosition一样。
但是这是一个私有方法,所以,任何你试图模仿的逻辑都可能在框架的未来版本中改变。

更新

此外,您可能会尝试bastardizing PointToScreenPointFromScreen,但这将是非常复杂的代码,如果它的工作...

vsmadaxz

vsmadaxz2#

我无法找到一个真正的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;
}

最终结果:

相关问题