翼度科技»论坛 编程开发 .net 查看内容

dotnet 设置 X11 建立窗口之间的父子关系

2

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
在 X11 里面有和 Win32 类似的窗口之间的关系机制,如 Owner-Owned 关系,以及 Parent-Child 关系。本文将告诉大家如何进行设置以及其行为
本文将大量使用到 new bing 提供的回答内容,感谢 new bing 人工智能提供的内容
Owner-Owned 关系


  • 在这种关系中,一个窗口可以被另一个窗口拥有(owner)。
  • 被拥有的窗口永远显示在拥有它的那个窗口的前面。
  • 当所有者窗口最小化时,它所拥有的窗口也会被隐藏。
  • 当所有者窗口被销毁时,它所拥有的窗口也会被销毁。
  • 当子窗口最小化时,不会影响到所有者窗口
  • 子窗口可以超过所有者窗口的范围
被拥有的窗口 = 子窗口
所有者窗口 = “在拥有它的那个窗口”
即与 WPF 的 ChildWindow.Owner = MainWindow 的效果类似。以上的 ChildWindow 为子窗口,而 MainWindow 为 所有者窗口
核心 C# 代码如下
  1.         // 我们使用XSetTransientForHint函数将窗口a设置为窗口b的子窗口。这将确保窗口a始终在窗口b的上方
  2.         XSetTransientForHint(Display, a, b);
复制代码
通过关系的描述可以了解到,使用上面代码即可设置 a 窗口一定在 b 窗口上方
以上代码放在 githubgitee 上,可以使用如下命令行拉取代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin 0331c5dd6057106df5cb179e45d34966a3eafd1b
复制代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin 0331c5dd6057106df5cb179e45d34966a3eafd1b
复制代码
获取代码之后,进入 GececurbaiduhaldiFokeejukolu 文件夹,即可获取到源代码
Parent-Child 关系


  • 在这种关系中,一个窗口是另一个窗口的父窗口。
  • 子窗口只能显示在父窗口的客户区内。
  • 当父窗口被隐藏时,它的所有子窗口也会被隐藏。
  • 当父窗口被销毁时,它所拥有的子窗口也会被销毁。
核心 C# 代码如下
  1.         // 设置父子关系
  2.         XReparentWindow(display, childWindowHandle, mainWindowHandle, 0, 0);
  3.         XMapWindow(display, childWindowHandle);
复制代码
需要记住在 XMapWindow 之前调用 XReparentWindow 方法,否则关系设置无效
以上代码放在 githubgitee 上,可以使用如下命令行拉取代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin bcfc938d44460c3f055957910ac1082525501c29
复制代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin bcfc938d44460c3f055957910ac1082525501c29
复制代码
获取代码之后,进入 DikalehebeekaJaqunicobo 文件夹,即可获取到源代码
建立 Parent-Child 关系之后,如果子窗口没有调用 XSelectInput 方法时,那所有在子窗口上的消息都能被所有者窗口收到,如果调用了 XSelectInput 则子窗口收到子窗口的消息,即所有者窗口被子窗口遮挡的部分将不能收到消息,被子窗口遮挡的部分的触摸或鼠标消息会被子窗口接收
简单的测试代码逻辑如下
  1. var xDisplayWidth = XDisplayWidth(display, screen) / 2;
  2. var xDisplayHeight = XDisplayHeight(display, screen) / 2;
  3. var handle = XCreateWindow(display, rootWindow, 0, 0, xDisplayWidth, xDisplayHeight, 5,
  4.     32,
  5.     (int) CreateWindowArgs.InputOutput,
  6.     visual,
  7.     (nuint) valueMask, ref xSetWindowAttributes);
  8. XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask |
  9.                          XEventMask.PointerMotionHintMask;
  10. var mask = new IntPtr(0xffffff ^ (int) ignoredMask);
  11. XSelectInput(display, handle, mask);
  12. var mainWindowHandle = handle;
  13.         // 再创建另一个窗口设置 Owner-Owned 关系
  14.         var childWindowHandle = XCreateSimpleWindow(display, rootWindow, 0, 0, 300, 300, 5, white, black);
  15.         XSelectInput(display, childWindowHandle, mask);
  16.         // 设置父子关系
  17.         XReparentWindow(display, childWindowHandle, mainWindowHandle, 50,50);
  18.         XMapWindow(display, childWindowHandle);
  19. while (true)
  20. {
  21.     var xNextEvent = XNextEvent(display, out var @event);
  22.     if(@event.type == XEventName.MotionNotify)
  23.     {
  24.         if (@event.MotionEvent.window == handle)
  25.         {
  26.             Console.WriteLine($"Window1 {DateTime.Now:HH:mm:ss}");
  27.         }
  28.         else
  29.         {
  30.             Console.WriteLine($"Window2 {DateTime.Now:HH:mm:ss}");
  31.         }
  32.     }
  33. }
复制代码
配置了以上代码,运行项目,可以看到鼠标在子窗口上时,只能收到子窗口的消息,如下图

以上代码有所忽略,全部的代码放在 githubgitee 上,可以使用如下命令行拉取代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin 07fa8637c7c744935419e5a122b38718d8bc87e3
复制代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin 07fa8637c7c744935419e5a122b38718d8bc87e3
复制代码
获取代码之后,进入 DikalehebeekaJaqunicobo 文件夹,即可获取到源代码
设置 Parent-Child 关系之后,将限制子窗口只能在主窗口的客户区范围内,即子窗口不能超过主窗口范围,如下图所示

以上代码是在 XReparentWindow 方法里面设置了子窗口的坐标,让子窗口超过主窗口的范围,代码如下
  1.         var mainWindowHandle = handle;
  2.         // 再创建另一个窗口设置 Owner-Owned 关系
  3.         var childWindowHandle = XCreateSimpleWindow(display, rootWindow, 0, 0, 300, 300, 5, white, black);
  4.         XSelectInput(display, childWindowHandle, mask);
  5.         // 设置父子关系
  6.         XReparentWindow(display, childWindowHandle, mainWindowHandle, 300, 50);
  7.         XMapWindow(display, childWindowHandle);
复制代码
以上代码放在 githubgitee 上,可以使用如下命令行拉取代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin fbc6151abcbeba9b54028a849f06a8796db0adf7
复制代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin fbc6151abcbeba9b54028a849f06a8796db0adf7
复制代码
获取代码之后,进入 DikalehebeekaJaqunicobo 文件夹,即可获取到源代码
以下是 new bing 给出的 XReparentWindow 函数的更多信息
XReparentWindow 函数的作用是将一个窗口重新设置其父窗口。具体来说,如果指定的窗口已经被映射到屏幕上,XReparentWindow 会自动执行 UnmapWindow 请求,将其从当前层次结构中移除,并将其插入到指定父窗口的子级中。这个窗口会在兄弟窗口中的堆叠顺序中置于顶部。¹²
如果原始窗口已经被映射,XReparentWindow 还会导致 X 服务器生成一个 ReparentNotify 事件。在此事件中,override_redirect 成员被设置为窗口的相应属性。通常情况下,窗口管理器客户端应该忽略此窗口,如果此成员设置为 True。最后,如果原始窗口已经被映射,X 服务器会自动对其执行 MapWindow 请求。对于原先被遮挡的窗口,X 服务器会执行正常的曝光处理。但是,由于最终的 MapWindow 请求会立即遮挡初始 UnmapWindow 请求的某些区域,因此 X 服务器可能不会为这些区域生成 Expose 事件。¹
以下情况会导致 BadMatch 错误:

  • 新的父窗口不在与旧的父窗口相同的屏幕上。
  • 新的父窗口是指定窗口本身或指定窗口的下级。
  • 新的父窗口是 InputOnly 类型,而窗口不是。
  • 指定窗口具有 ParentRelative 背景,而新的父窗口与指定窗口的深度不同。
总之,XReparentWindow 允许您在 X 窗口系统中重新组织窗口的层次结构。
使用 XReparentWindow 设置的窗口关系时,子窗口将会挡住主窗口的渲染部分,即在子窗口范围内将看不到主窗口的绘制内容
其测试代码如下,先在主窗口和子窗口绘制内容
  1.     if (@event.type == XEventName.Expose)
  2.     {
  3.         if (@event.ExposeEvent.window == handle)
  4.         {
  5.             XDrawLine(display, handle, gc, 2, 2, xDisplayWidth - 2, xDisplayHeight - 2);
  6.             XDrawLine(display, handle, gc, 2, xDisplayHeight - 2, xDisplayWidth - 2, 2);
  7.         }
  8.         else if (childWindowHandle != 0 && @event.ExposeEvent.window == childWindowHandle)
  9.         {
  10.             XDrawLine(display, childWindowHandle, gc, 1, 1, xDisplayWidth - 2, 1);
  11.             XDrawLine(display, childWindowHandle, gc, 1, xDisplayHeight - 2, xDisplayWidth - 2, xDisplayHeight - 2);
  12.             XDrawLine(display, childWindowHandle, gc, 1, 1, 1, xDisplayHeight - 2);
  13.             XDrawLine(display, childWindowHandle, gc, xDisplayWidth - 2, xDisplayHeight - 2, xDisplayWidth - 2, xDisplayHeight - 2);
  14.         }
  15.     }
复制代码
接着使用 XMoveWindow 设置子窗口坐标,此时可见子窗口所在地方将不可见主窗口绘制的内容
  1.     while (true)
  2.     {
  3.         await Task.Delay(TimeSpan.FromSeconds(1));
  4.         await InvokeAsync(() =>
  5.         {
  6.             XMoveWindow(display, childWindowHandle, Random.Shared.Next(200), Random.Shared.Next(100));
  7.         });
  8.     }
复制代码
全部的测试代码如下
  1. // See https://aka.ms/new-console-template for more informationusing CPF.Linux;using System;using System.Diagnostics;using System.Runtime;using static CPF.Linux.XLib;XInitThreads();var display = XOpenDisplay(IntPtr.Zero);var screen = XDefaultScreen(display);var rootWindow = XDefaultRootWindow(display);XMatchVisualInfo(display, screen, 32, 4, out var info);var visual = info.visual;var valueMask =        //SetWindowValuemask.BackPixmap        0        | SetWindowValuemask.BackPixel        | SetWindowValuemask.BorderPixel        | SetWindowValuemask.BitGravity        | SetWindowValuemask.WinGravity        | SetWindowValuemask.BackingStore        | SetWindowValuemask.ColorMap    //| SetWindowValuemask.OverrideRedirect    ;var xSetWindowAttributes = new XSetWindowAttributes{    backing_store = 1,    bit_gravity = Gravity.NorthWestGravity,    win_gravity = Gravity.NorthWestGravity,    //override_redirect = true, // 设置窗口的override_redirect属性为True,以避免窗口管理器的干预    colormap = XCreateColormap(display, rootWindow, visual, 0),    border_pixel = 0,    background_pixel = 0,};var xDisplayWidth = XDisplayWidth(display, screen) / 2;var xDisplayHeight = XDisplayHeight(display, screen) / 2;var handle = XCreateWindow(display, rootWindow, 0, 0, xDisplayWidth, xDisplayHeight, 5,    32,    (int) CreateWindowArgs.InputOutput,    visual,    (nuint) valueMask, ref xSetWindowAttributes);XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask |                         XEventMask.PointerMotionHintMask;var mask = new IntPtr(0xffffff ^ (int) ignoredMask);XSelectInput(display, handle, mask);XMapWindow(display, handle);XFlush(display);var white = XWhitePixel(display, screen);var black = XBlackPixel(display, screen);var gc = XCreateGC(display, handle, 0, 0);XSetForeground(display, gc, white);XSync(display, false);var invokeList = new List();var invokeMessageId = new IntPtr(123123123);async Task InvokeAsync(Action action){    var taskCompletionSource = new TaskCompletionSource();    lock (invokeList)    {        invokeList.Add(() =>        {            action();            taskCompletionSource.SetResult();        });    }    // 在 Avalonia 里面,是通过循环读取的方式,通过 XPending 判断是否有消息    // 如果没有消息就进入自旋判断是否有业务消息和判断是否有 XPending 消息    // 核心使用 epoll_wait 进行等待    // 整个逻辑比较复杂    // 这里简单处理,只通过发送 ClientMessage 的方式,告诉消息循环需要处理业务逻辑    // 发送 ClientMessage 是一个合理的方式,根据官方文档说明,可以看到这是没有明确定义的    // https://www.x.org/releases/X11R7.5/doc/man/man3/XClientMessageEvent.3.html    // https://tronche.com/gui/x/xlib/events/client-communication/client-message.html    // The X server places no interpretation on the values in the window, message_type, or data members.    // 在 cpf 里面,和 Avalonia 实现差不多,也是在判断 XPending 是否有消息,没消息则判断是否有业务逻辑    // 最后再进入等待逻辑。似乎 CPF 这样的方式会导致 CPU 占用略微提升    var @event = new XEvent    {        ClientMessageEvent =        {            type = XEventName.ClientMessage,            send_event = true,            window = handle,            message_type = 0,            format = 32,            ptr1 = invokeMessageId,            ptr2 = 0,            ptr3 = 0,            ptr4 = 0,        }    };    XSendEvent(display, handle, false, 0, ref @event);    XFlush(display);    await taskCompletionSource.Task;}IntPtr childWindowHandle = 0;_ = Task.Run(async () =>{    await InvokeAsync(() =>    {        var mainWindowHandle = handle;        // 再创建另一个窗口设置 Owner-Owned 关系        // 创建无边框窗口        valueMask =            //SetWindowValuemask.BackPixmap            0            | SetWindowValuemask.BackPixel            | SetWindowValuemask.BorderPixel            | SetWindowValuemask.BitGravity            | SetWindowValuemask.WinGravity            | SetWindowValuemask.BackingStore            | SetWindowValuemask.ColorMap            | SetWindowValuemask.OverrideRedirect // [dotnet C# X11 开发笔记](https://blog.lindexi.com/post/dotnet-C-X11-%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0.html )            ;        xSetWindowAttributes = new XSetWindowAttributes        {            backing_store = 1,            bit_gravity = Gravity.NorthWestGravity,            win_gravity = Gravity.NorthWestGravity,            override_redirect = true,            colormap = XCreateColormap(display, rootWindow, visual, 0),            border_pixel = 0,            background_pixel = 0,        };        childWindowHandle = XCreateWindow(display, rootWindow, 0, 0, xDisplayWidth, xDisplayHeight, 5,            32,            (int) CreateWindowArgs.InputOutput,            visual,            (nuint) valueMask, ref xSetWindowAttributes);        XSelectInput(display, childWindowHandle, mask);        // 设置父子关系        XReparentWindow(display, childWindowHandle, mainWindowHandle, 300, 50);        XMapWindow(display, childWindowHandle);    });    while (true)
  2.     {
  3.         await Task.Delay(TimeSpan.FromSeconds(1));
  4.         await InvokeAsync(() =>
  5.         {
  6.             XMoveWindow(display, childWindowHandle, Random.Shared.Next(200), Random.Shared.Next(100));
  7.         });
  8.     }});Thread.CurrentThread.Name = "主线程";while (true){    var xNextEvent = XNextEvent(display, out var @event);    if (xNextEvent != 0)    {        Console.WriteLine($"xNextEvent {xNextEvent}");        break;    }    if (@event.type == XEventName.Expose)
  9.     {
  10.         if (@event.ExposeEvent.window == handle)
  11.         {
  12.             XDrawLine(display, handle, gc, 2, 2, xDisplayWidth - 2, xDisplayHeight - 2);
  13.             XDrawLine(display, handle, gc, 2, xDisplayHeight - 2, xDisplayWidth - 2, 2);
  14.         }
  15.         else if (childWindowHandle != 0 && @event.ExposeEvent.window == childWindowHandle)
  16.         {
  17.             XDrawLine(display, childWindowHandle, gc, 1, 1, xDisplayWidth - 2, 1);
  18.             XDrawLine(display, childWindowHandle, gc, 1, xDisplayHeight - 2, xDisplayWidth - 2, xDisplayHeight - 2);
  19.             XDrawLine(display, childWindowHandle, gc, 1, 1, 1, xDisplayHeight - 2);
  20.             XDrawLine(display, childWindowHandle, gc, xDisplayWidth - 2, xDisplayHeight - 2, xDisplayWidth - 2, xDisplayHeight - 2);
  21.         }
  22.     }    else if (@event.type == XEventName.ClientMessage)    {        var clientMessageEvent = @event.ClientMessageEvent;        if (clientMessageEvent.message_type == 0 && clientMessageEvent.ptr1 == invokeMessageId)        {            List tempList;            lock (invokeList)            {                tempList = invokeList.ToList();                invokeList.Clear();            }            foreach (var action in tempList)            {                action();            }        }    }    else if (@event.type == XEventName.MotionNotify)    {        if (@event.MotionEvent.window == handle)        {            Console.WriteLine($"Window1 {DateTime.Now:HH:mm:ss}");        }        else        {            Console.WriteLine($"Window2 {DateTime.Now:HH:mm:ss}");        }    }}Console.WriteLine("Hello, World!");
复制代码
运行代码之后的效果如下图

如上图,应用是透明窗口,可以看到背后的图片应用显示的内容。上述图片是使用 WPF 基础绘图 创建和加工图片 绘制的图片。可以看到无论是主窗口还是子窗口都能透过去。但是子窗口将会遮挡主窗口的绘制,即让子窗口直接显示窗口之后的部分内容,但不会与主窗口合成,即主窗口被子窗口挡住的部分就没有进行渲染
以上代码放在 githubgitee 上,可以使用如下命令行拉取代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin bd9f8b2c8f3f42bea639677bf4ac69602b521fc0
复制代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin bd9f8b2c8f3f42bea639677bf4ac69602b521fc0
复制代码
获取代码之后,进入 DikalehebeekaJaqunicobo 文件夹,即可获取到源代码
更多 X11 相关,请参阅 博客导航

来源:https://www.cnblogs.com/lindexi/p/18197088
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具