3

有谁知道我如何确定 contextMenu 由于太靠近屏幕边缘而自动调整其位置?

我的场景是我有一个 contextMenu,它有 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);
}

这是实际应用程序的样子,因此您可以看到我需要知道调整圆角的问题。

在此处输入图像描述

4

2 回答 2

2

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

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

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

_secHelper是 type 的助手类PopupSecurityHelper,似乎只是一个内部助手......而且,这些都不会导致事件甚至公共属性被更改。

这是一篇 MSDN 文章,解释了通常如何确定弹出窗口的位置(“当弹出窗口遇到屏幕边缘时”描述了您的场景)。

但是,本文解释了如何使用CustomPopupPlacementCallback来覆盖这些行为。但是,这仍然使用 a PopupPrimaryAxis,它应该根据需要翻转菜单,并且会导致同样的问题。

我唯一能想到的另一件事是,您可以查看PlacementRectangle并可能轮询大小和位置,类似于UpdatePosition事情的方式......或者就像 UpdatePosition 一样检查弹出窗口本身。

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

更新

此外,您可以尝试对PointToScreenor进行混蛋PointFromScreen,但如果它有效,那将是非常复杂的代码......

于 2012-10-06T02:28:16.290 回答
2

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

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

<ContextMenu ContextMenu.Loaded="ContextMenu_Loaded">

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

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

注意 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;
}

最后结果:

在此处输入图像描述

于 2012-10-06T17:55:27.423 回答