当前位置: 代码网 > it编程>编程语言>Asp.net > dotnet 设置 X11 建立窗口之间的父子关系

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

2024年05月17日 Asp.net 我要评论
在 X11 里面有和 Win32 类似的窗口之间的关系机制,如 Owner-Owned 关系,以及 Parent-Child 关系。本文将告诉大家如何进行设置以及其行为 ...

在 x11 里面有和 win32 类似的窗口之间的关系机制,如 owner-owned 关系,以及 parent-child 关系。本文将告诉大家如何进行设置以及其行为

本文将大量使用到 new bing 提供的回答内容,感谢 new bing 人工智能提供的内容

owner-owned 关系

  • 在这种关系中,一个窗口可以被另一个窗口拥有(owner)。
  • 被拥有的窗口永远显示在拥有它的那个窗口的前面。
  • 当所有者窗口最小化时,它所拥有的窗口也会被隐藏。
  • 当所有者窗口被销毁时,它所拥有的窗口也会被销毁。
  • 当子窗口最小化时,不会影响到所有者窗口
  • 子窗口可以超过所有者窗口的范围

被拥有的窗口 = 子窗口

所有者窗口 = “在拥有它的那个窗口”

即与 wpf 的 childwindow.owner = mainwindow 的效果类似。以上的 childwindow 为子窗口,而 mainwindow 为 所有者窗口

核心 c# 代码如下

        // 我们使用xsettransientforhint函数将窗口a设置为窗口b的子窗口。这将确保窗口a始终在窗口b的上方
        xsettransientforhint(display, a, b);

通过关系的描述可以了解到,使用上面代码即可设置 a 窗口一定在 b 窗口上方

以上代码放在 githubgitee 上,可以使用如下命令行拉取代码

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 0331c5dd6057106df5cb179e45d34966a3eafd1b

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 0331c5dd6057106df5cb179e45d34966a3eafd1b

获取代码之后,进入 gececurbaiduhaldifokeejukolu 文件夹,即可获取到源代码

parent-child 关系

  • 在这种关系中,一个窗口是另一个窗口的父窗口。
  • 子窗口只能显示在父窗口的客户区内。
  • 当父窗口被隐藏时,它的所有子窗口也会被隐藏。
  • 当父窗口被销毁时,它所拥有的子窗口也会被销毁。

核心 c# 代码如下

        // 设置父子关系
        xreparentwindow(display, childwindowhandle, mainwindowhandle, 0, 0);
        xmapwindow(display, childwindowhandle);

需要记住在 xmapwindow 之前调用 xreparentwindow 方法,否则关系设置无效

以上代码放在 githubgitee 上,可以使用如下命令行拉取代码

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin bcfc938d44460c3f055957910ac1082525501c29

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin bcfc938d44460c3f055957910ac1082525501c29

获取代码之后,进入 dikalehebeekajaqunicobo 文件夹,即可获取到源代码

建立 parent-child 关系之后,如果子窗口没有调用 xselectinput 方法时,那所有在子窗口上的消息都能被所有者窗口收到,如果调用了 xselectinput 则子窗口收到子窗口的消息,即所有者窗口被子窗口遮挡的部分将不能收到消息,被子窗口遮挡的部分的触摸或鼠标消息会被子窗口接收

简单的测试代码逻辑如下

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);

var mainwindowhandle = handle;

        // 再创建另一个窗口设置 owner-owned 关系
        var childwindowhandle = xcreatesimplewindow(display, rootwindow, 0, 0, 300, 300, 5, white, black);

        xselectinput(display, childwindowhandle, mask);

        // 设置父子关系
        xreparentwindow(display, childwindowhandle, mainwindowhandle, 50,50);
        xmapwindow(display, childwindowhandle);

while (true)
{
    var xnextevent = xnextevent(display, out var @event);

    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}");
        }
    }
}

配置了以上代码,运行项目,可以看到鼠标在子窗口上时,只能收到子窗口的消息,如下图

以上代码有所忽略,全部的代码放在 githubgitee 上,可以使用如下命令行拉取代码

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 07fa8637c7c744935419e5a122b38718d8bc87e3

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 07fa8637c7c744935419e5a122b38718d8bc87e3

获取代码之后,进入 dikalehebeekajaqunicobo 文件夹,即可获取到源代码

设置 parent-child 关系之后,将限制子窗口只能在主窗口的客户区范围内,即子窗口不能超过主窗口范围,如下图所示

以上代码是在 xreparentwindow 方法里面设置了子窗口的坐标,让子窗口超过主窗口的范围,代码如下

        var mainwindowhandle = handle;

        // 再创建另一个窗口设置 owner-owned 关系
        var childwindowhandle = xcreatesimplewindow(display, rootwindow, 0, 0, 300, 300, 5, white, black);

        xselectinput(display, childwindowhandle, mask);

        // 设置父子关系
        xreparentwindow(display, childwindowhandle, mainwindowhandle, 300, 50);
        xmapwindow(display, childwindowhandle);

以上代码放在 githubgitee 上,可以使用如下命令行拉取代码

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin fbc6151abcbeba9b54028a849f06a8796db0adf7

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
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 设置的窗口关系时,子窗口将会挡住主窗口的渲染部分,即在子窗口范围内将看不到主窗口的绘制内容

其测试代码如下,先在主窗口和子窗口绘制内容

    if (@event.type == xeventname.expose)
    {
        if (@event.exposeevent.window == handle)
        {
            xdrawline(display, handle, gc, 2, 2, xdisplaywidth - 2, xdisplayheight - 2);
            xdrawline(display, handle, gc, 2, xdisplayheight - 2, xdisplaywidth - 2, 2);
        }
        else if (childwindowhandle != 0 && @event.exposeevent.window == childwindowhandle)
        {
            xdrawline(display, childwindowhandle, gc, 1, 1, xdisplaywidth - 2, 1);
            xdrawline(display, childwindowhandle, gc, 1, xdisplayheight - 2, xdisplaywidth - 2, xdisplayheight - 2);
            xdrawline(display, childwindowhandle, gc, 1, 1, 1, xdisplayheight - 2);
            xdrawline(display, childwindowhandle, gc, xdisplaywidth - 2, xdisplayheight - 2, xdisplaywidth - 2, xdisplayheight - 2);
        }
    }

接着使用 xmovewindow 设置子窗口坐标,此时可见子窗口所在地方将不可见主窗口绘制的内容

    while (true)
    {
        await task.delay(timespan.fromseconds(1));

        await invokeasync(() =>
        {
            xmovewindow(display, childwindowhandle, random.shared.next(200), random.shared.next(100));
        });
    }

全部的测试代码如下

// see https://aka.ms/new-console-template for more information

using 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<action>();
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)
    {
        await task.delay(timespan.fromseconds(1));

        await invokeasync(() =>
        {
            xmovewindow(display, childwindowhandle, random.shared.next(200), random.shared.next(100));
        });
    }
});

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)
    {
        if (@event.exposeevent.window == handle)
        {
            xdrawline(display, handle, gc, 2, 2, xdisplaywidth - 2, xdisplayheight - 2);
            xdrawline(display, handle, gc, 2, xdisplayheight - 2, xdisplaywidth - 2, 2);
        }
        else if (childwindowhandle != 0 && @event.exposeevent.window == childwindowhandle)
        {
            xdrawline(display, childwindowhandle, gc, 1, 1, xdisplaywidth - 2, 1);
            xdrawline(display, childwindowhandle, gc, 1, xdisplayheight - 2, xdisplaywidth - 2, xdisplayheight - 2);
            xdrawline(display, childwindowhandle, gc, 1, 1, 1, xdisplayheight - 2);
            xdrawline(display, childwindowhandle, gc, xdisplaywidth - 2, xdisplayheight - 2, xdisplaywidth - 2, xdisplayheight - 2);
        }
    }
    else if (@event.type == xeventname.clientmessage)
    {
        var clientmessageevent = @event.clientmessageevent;
        if (clientmessageevent.message_type == 0 && clientmessageevent.ptr1 == invokemessageid)
        {
            list<action> 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 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin bd9f8b2c8f3f42bea639677bf4ac69602b521fc0

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin bd9f8b2c8f3f42bea639677bf4ac69602b521fc0

获取代码之后,进入 dikalehebeekajaqunicobo 文件夹,即可获取到源代码

更多 x11 相关,请参阅 博客导航

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com