在AvaloniaUI中,如何订阅标题栏的鼠标事件

huangapple go评论65阅读模式
英文:

In AvaloniaUI, how to subscribe to mouse events of the titlebar

问题

以下是翻译好的部分:

要重新创建我所做的,请创建一个新的AvaloniaUI项目。在MainWindow.cs中,在protected override void OnApplyTemplate(TemplateAppliedEventArgs e)方法中,通过调用this.FindDescendantOfType<TitleBar>();来获取TitleBar对象。然后,将任何事件添加到其PointerPressed事件中。然而,事件处理程序方法的断点从未被触发。

英文:

To recreate what I have done, create a new AvaloniaUI project. In the MainWindow.cs, get the TitleBar object by calling this.FindDescendantOfType<TitleBar>();in protected override void OnApplyTemplate(TemplateAppliedEventArgs e). Then add any event to its PointerPressed event.
However, the break point of the event handler method is never reached.

答案1

得分: 0

你不能那样做。如果你检查那个 TitleBar,你会发现它实际上是一个没有大小或功能的通用对象,只是存在于树中(不确定为什么他们不直接给你一个空对象,可能是一些内部可为空的问题)。

无论如何,你不能这样做的原因是Chrome是基于操作系统的,并由操作系统控制,实现是与操作系统特定的。替代方法是构建自己的标题栏并隐藏系统的,自己提供必要的功能(查看这个链接以获取想法 => 自定义标题栏模板)。

如果你不想这样做,而且你只在Windows应用程序上操作,你可以监视自己的消息队列(因为没有DLL注入,GUI线程不会移动到非托管线程),以检测“非客户端”操作,例如标题栏点击消息(WM_NCLBUTTONDOWN)并做出反应。例如:

MainWindow.axaml

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="Test01.MainWindow"
        Title="Test01" >
	<Grid>
        <Label Name="MainLabel"/>
    </Grid>
</Window>

MainWindow.axaml.cs

using Avalonia.Controls;
using System;
using System.Runtime.InteropServices;

namespace Test01;

public partial class MainWindow : Window
{
    private const int WM_NCLBUTTONDOWN = 0x00a1;
    private const int WH_GETMESSAGE = 3;

    private IntPtr theHook;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        theHook = MyInterop.SetWindowsHookEx(WH_GETMESSAGE, MsgHook, IntPtr.Zero, MyInterop.GetCurrentThreadId());
        // var err = Marshal.GetLastWin32Error();
    }

    private int MsgHook(int nCode, IntPtr wParam, IntPtr lParam)
    {
        // mandatory shortcut
        if (nCode < 0) return MyInterop.CallNextHookEx(theHook, nCode, wParam, lParam);
        // is it a NC click?
        var msg = (MyInterop.MsgStruct?)Marshal.PtrToStructure(lParam, typeof(MyInterop.MsgStruct));
        if (msg?.message == WM_NCLBUTTONDOWN)
        {
            // your action
            MainLabel.Content = "I am pressed!!!";
        }

        return MyInterop.CallNextHookEx(theHook, nCode, wParam, lParam);
    }

}

MyInterop.cs

namespace Test01;

internal static class MyInterop
{
    [StructLayout(LayoutKind.Sequential)]
    public class MsgStruct
    {
        public IntPtr hwnd;
        public uint message;
        // we dont care about the rest of MSG struct
    }


    internal delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SetWindowsHookEx", SetLastError = true)]
    internal static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    internal static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll")]
    internal static extern uint GetCurrentThreadId();
}

这只是一个可工作的示例,你可以将其重新打包成某种可观察的模式或事件启动器。

另外,这仅适用于Windows,在Linux和MacOS上不起作用。对于X11,也有一个消息队列,所以我认为可能存在替代方法。关于MacOS 完全不确定。

注意:由于Avalonia中的错误(Xaml Compiler error when code-behind class contains a DllImport method #10046),我不得不将所有的extern定义移到一个单独的类中。一旦他们修复了这个问题,MyInterop可以合并到主要的.cs文件中。

英文:

You cannot do it that way. If you inspect that TitleBar you will se its actually a generic object with no size or function, just existing in the tree (not sure why they don't just give you a null, probably some internal nullable issue).
Anyway, the reason you cannot is because chrome is OS based and controlled by OS and implementation is OS specific. Alternative is to build your own title bar and hide the system one, providing necessary features yourself (check this for ideas => Custom TitleBar Template).

If you do not want to do that and you are only doing Windows app, you can spy on your own message queue (safe since there is no DLL injection and GUI thread will not move across unmanaged threads) to detect "nonclient", e.g. titlebar click message (WM_NCLBUTTONDOWN) and react. For example:

MainWindow.axaml

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="Test01.MainWindow"
        Title="Test01" >
	<Grid>
        <Label Name="MainLabel"/>
    </Grid>
</Window>

MainWindow.axaml.cs

using Avalonia.Controls;
using System;
using System.Runtime.InteropServices;

namespace Test01;

public partial class MainWindow : Window
{
    private const int WM_NCLBUTTONDOWN = 0x00a1;
    private const int WH_GETMESSAGE = 3;

    private IntPtr theHook;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        theHook = MyInterop.SetWindowsHookEx(WH_GETMESSAGE, MsgHook, IntPtr.Zero, MyInterop.GetCurrentThreadId());
        // var err = Marshal.GetLastWin32Error();
    }

    private int MsgHook(int nCode, IntPtr wParam, IntPtr lParam)
    {
        // mandatory shortcut
        if (nCode < 0) return MyInterop.CallNextHookEx(theHook, nCode, wParam, lParam);
        // is it a NC click?
        var msg = (MyInterop.MsgStruct?)Marshal.PtrToStructure(lParam, typeof(MyInterop.MsgStruct));
        if (msg?.message == WM_NCLBUTTONDOWN)
        {
            // your action
            MainLabel.Content = "I am pressed!!!";
        }

        return MyInterop.CallNextHookEx(theHook, nCode, wParam, lParam);
    }

}

MyInterop.cs

namespace Test01;

internal static class MyInterop
{
    [StructLayout(LayoutKind.Sequential)]
    public class MsgStruct
    {
        public IntPtr hwnd;
        public uint message;
        // we dont care about the rest of MSG struct
    }


    internal delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SetWindowsHookEx", SetLastError = true)]
    internal static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    internal static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll")]
    internal static extern uint GetCurrentThreadId();
}

This is just a working example, you can repackage it into some observable pattern or event launcher.

Also, this is just for Windows, wont work on Linux and MacOS. For X11, there is also a message queue so I think alternative may be possible. Not at all sure about MacOS.

NOTE: I had to move all extern definitions to a separate class because of the bug in Avalonia (Xaml Compiler error when code-behind class contains a DllImport method #10046). Once they fix that MyInterop can be merged into main .cs

huangapple
  • 本文由 发表于 2023年7月18日 11:02:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76709276.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定