Xamarin.Macでコンテキストメニューを表示する

Xamarin.Macでコンテキストメニューを表示するサンプルです。

コンテキストメニューの表示

MenuForEventメソッドをオーバーライドし、表示するコンテキストメニューを返します。

public override NSMenu MenuForEvent(NSEvent theEvent)
{
    var menu = new NSMenu();
    menu.AddItem(
        new NSMenuItem(
            "Menu Item",
            (sender, e) => Debug.WriteLine($"{((NSMenuItem)sender).Title} Clicked")));
    return menu;
}

Controlキー+左クリックと、右クリックのコンテキストメニューの動作の違い

Controlキー+左クリックのときと、右クリックのときでは、コンテキストメニューの動作に違いがあります。

サンプルアプリケーションでは、緑色のNSViewと青色のNSViewがあります。
青色のNSViewはコンテキストメニューを表示しますが、緑色のNSViewはコンテキストメニューを表示しません。

2つのNSViewが重なっている部分を右クリックしたとき、コンテキストメニューが表示されます。
Controlキー+左クリックでは、コンテキストメニューは表示されません。

右クリックの場合、上位のコントロールがコンテキストメニューを表示しなければ下位のコントロールのコンテキストメニューを表示します。
Controlキー+左クリックの場合は、下位のコントロールのコンテキストメニューは表示されません。

Xamarin.Macでウィンドウが閉じる時に処理を行うには

Xamarin.Macでウィンドウが閉じる時に処理を行う

Xamarin.Macではウィンドウが閉じる時、NSWindowのWillCloseイベントが発生します。

public partial class MainWindow : NSWindow
{
    public override void AwakeFromNib()
    {
        base.AwakeFromNib();
        this.WillClose += WindowClosed;
    }
    /// <summary>
    /// ウィンドウが閉じる時
    /// </summary>
    private void WindowClosed(object sender, EventArgs e)
    {
        // ウィンドウが閉じる時に行う処理をここに記述する
    }
}

メインウィンドウを閉じた時にアプリケーションを終了する

メインウィンドウを閉じた時にアプリケーションを終了するには、ウィンドウを閉じた時に「NSApplication.SharedApplication.Terminate()」を実行します。

public partial class MainWindow : NSWindow
{
    public override void AwakeFromNib()
    {
        base.AwakeFromNib();
        this.WillClose += WindowClosed;
    }
    /// <summary>
    /// ウィンドウが閉じる時
    /// </summary>
    private void WindowClosed(object sender, EventArgs e)
    {
        NSApplication.SharedApplication.Terminate(this);
    }
}

Xamarin.Macでマウスカーソルの位置を取得する

マウスカーソルの位置を取得するには、Event Monitorを使用します。

アプリケーションがアクティブなときはLocal Event Monitorを使用して、マウスカーソルの座標を取得します。

localEventMonitor = NSEvent.AddLocalMonitorForEventsMatchingMask(
    NSEventMask.MouseMoved,
    (theEvent) => /* イベント処理 */; )

アプリケーションがアクティブでないときはGlobal Event Monitorを使用して、マウスカーソルの座標を取得します。

globalEventMonitor = NSEvent.AddGlobalMonitorForEventsMatchingMask(
    NSEventMask.MouseMoved,
    (theEvent) => /* イベント処理 */; );

モニタリングを終了するときは、NSEvent.RemoveMonitor()を使用します。

NSEvent.RemoveMonitor(globalEventMonitor);
NSEvent.RemoveMonitor(localEventMonitor);

NSEventから画面上の座標を取得する

Global Monitorのときは、NSEventのLocationInWindowで画面上の座標を取得できます。

Local Monitorのときは、マウスカーソルがアプリケーションのウィンドウ上にあるときはウィンドウ上の座標、マウスカーソルがアプリケーションのウィンドウ外にあるときは画面上の座標を返します。

マウスカーソルがウィンドウ外にあるときはNSEventのWindowがnullになるようなので、この値を使って判定しました。

CGPoint p;
if (theEvent.Window != null)
{
    var rect = theEvent.Window.ConvertRectToScreen(new CGRect(theEvent.LocationInWindow, new CGSize(0, 0)));
    p = rect.Location;
}
else
{
    p = theEvent.LocationInWindow;
}

画面上の座標からウィンドウ上の座標に変換するには、ConvertScreenToBase()を使用します。

var localPoint = this.View.Window.ConvertScreenToBase(p);

ソースコード

ソースコードの一部を掲載します。

public partial class ViewController : NSViewController
{
    private NSObject globalEventMonitor;
    private NSObject localEventMonitor;

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        // Do any additional setup after loading the view.
        CheckBoxMonitoring.Activated += (sender, e) =>
        {
            if (CheckBoxMonitoring.State == NSCellStateValue.On)
                StartMonitoring();
            if (CheckBoxMonitoring.State == NSCellStateValue.Off)
                StopMonitoring();
        };
    }
    private void PrintMouseLocation(string monitor, CGPoint p)
    {
        LabelMonitor.StringValue = $"Monitor: {monitor}";
        LabelScreen.StringValue = $"Screen Pos: {p.X:F4} {p.Y:F4}";
        var localPoint = this.View.Window.ConvertScreenToBase(p);
        LabelWindow.StringValue = $"Window Pos: {localPoint.X:F4} {localPoint.Y:F4}";
    }
}

サンプルアプリケーション

こちらからソースコード一式をダウンロードできます。

UWPでコンテキストメニューを表示する

サンプルアプリケーション

ボタンを右クリックしたときにコンテキストメニューを表示するサンプルです。

ボタンを右クリックすると、コンテキストメニューを表示します。
コンテキストメニューからメニュー項目を選択すると、選択された項目を表示します。

XAML

<Button Content="このボタンを右クリックします。" RightTapped="Button_RightTapped">
    <FlyoutBase.AttachedFlyout>
        <MenuFlyout>
            <MenuFlyoutItem Click="MenuFlyoutItem_Click" Text="項目3" />
            <MenuFlyoutItem Click="MenuFlyoutItem_Click" Text="項目4" />
        </MenuFlyout>
    </FlyoutBase.AttachedFlyout>
</Button>

コントロールを右クリックしたときにコンテキストメニューを表示したいので、RightTappedイベントを設定します。

メニュー項目がクリックされたことを知るために、MenuFlyoutItemのClickイベントを設定します。

ソースコード

コンテキストメニューを表示する

コントロールの右クリックイベントで、コンテキストメニューを表示します。

private void Button_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    FlyoutBase.ShowAttachedFlyout((FrameworkElement)sender);
}

メニューが選択されたときのイベント

メニューがクリックされたとき、Clickイベントが呼ばれます。

private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
    TextBlock.Text = $"「{((MenuFlyoutItem)sender).Text}」が選択されました";
}

Button.Flyoutについて

ボタンコントロールには、Flyoutを表示する機能があります。

    <Button Content="このボタンを右クリックします。" RightTapped="Button_RightTapped">
        <Button.Flyout>
            <MenuFlyout>
                <MenuFlyoutItem Click="MenuFlyoutItem_Click" Text="項目1" />
                <MenuFlyoutItem Click="MenuFlyoutItem_Click" Text="項目2" />
            </MenuFlyout>
        </Button.Flyout>
    </Button>

このように設定すると、左クリックしたときにFlayoutが表示されます。