3

画面の端に近すぎるためにcontextMenuの配置が自動的に調整されるタイミングを判断する方法を知っている人はいますか?

私のシナリオは、2つの丸い角と2つの正方形の角を持つcontextMenuがあるというものです。メニューが開いたら、下の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はタイプのヘルパークラスでありPopupSecurityHelper、単なる内部ヘルパーのようです...そして、これらのいずれも、イベントや公共のプロパティが変更されることはありません。

これは、ポップアップの配置が一般的にどのように決定されるかを説明するMSDNの記事です(「ポップアップが画面の端に遭遇したとき」はシナリオを説明しています)。

ただし、この記事でCustomPopupPlacementCallbackは、を使用してこれらの動作をいくらかオーバーライドする方法について説明します。ただし、これでも、が使用PopupPrimaryAxisされ、必要に応じてメニューが反転するため、同じ問題が発生します。

私が考えることができる他の唯一のことは、あなたが物事を行うPlacementRectangleのと同じようにサイズと場所を調べてポーリングすることができるというUpdatePositionことです...またはUpdatePositionと同じようにポップアップ自体をチェックするだけです。

ただし、これはプライベートメソッドです。したがって、模倣しようとするロジックは、フレームワークの将来のバージョンで変更される可能性があります。

アップデート

PointToScreenまた、ろくでなしやを試すこともできますがPointFromScreen、それが機能した場合、それは非常に複雑なコードになります...

于 2012-10-06T02:28:16.290 に答える
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;
}

最終結果:

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

于 2012-10-06T17:55:27.423 に答える