You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ubuntu环境下DearpyGUI2.0.0/Python3.12拖拽功能故障排查

Ubuntu下DearpyGUI 2.0.0 + Python3.12拖拽功能失效问题解决

问题概述

在Ubuntu系统中使用DearpyGUI 2.0.0与Python3.12实现拖拽功能时,已尝试关联父组件及子组件,但功能无法正常工作。拖拽图片或文件时,d.pending_events()输出始终为0。

运行输出

(env_312) (base) xam
a@xam-58:~/Desktop/Python/310py$ python test1.py
✅ Found
Parent window: 0x800df0
🔎 Dumping window tree:
↳ 0x800df0 name: Drag-and-Drop-Example
↳ 0x800df1 name: None
↳ 0x2a00009 name: Drag-and-Drop-Example
Child: 0x2a00009, name: Drag-and-Drop-Example

原测试代码

import dearpygui.dearpygui as dpg 
import threading 
import time
from Xlib import X, display, Xatom


#PARENT  
def _func_find_window_by_name(name, d):
    root = d.screen().root

    def search(w):
        for child in w.query_tree().children:
            try:
                win_name = child.get_wm_name()
                if win_name and name in win_name:
                    return child
                result = search(child)
                if result:
                    return result
            except:
                pass
        return None

    return search(root)


#CHILDREN 
def find_real_content_window(parent):
    for child in parent.query_tree().children:
        name = child.get_wm_name()
        if name:
            print(f"Child: {hex(child.id)}, name: {name}")
        if name and "Drag-and-Drop-Example" in name:
            return child
    return parent  # fallback


def _func_setup_xdnd(win, d):
    XdndAware = d.intern_atom("XdndAware")
    win.change_property(XdndAware, Xatom.ATOM, 32, [5])
    d.flush()

    # Verify
    prop = win.get_full_property(XdndAware, Xatom.ATOM)
    if prop:
        print(f"✅ XdndAware property set correctly: {prop.value}")
        print(f"✅ Using child content window: {hex(win.id)}")
    else:
        print("❌ Failed to set XdndAware property.")


def _func_xdnd_loop(win, d):
    XdndEnter = d.intern_atom("XdndEnter")
    XdndPosition = d.intern_atom("XdndPosition")
    XdndDrop = d.intern_atom("XdndDrop")
    XdndSelection = d.intern_atom("XdndSelection")
    UTF8_STRING = d.intern_atom("UTF8_STRING")

    print(f"👂 Listening for XDND events on: {hex(win.id)}")

    while True:
        print(d.pending_events())
        if d.pending_events():
            print("🔄 Processing pending events...")
    # #         e = d.next_event()
    # #         if e.type == X.ClientMessage:
    # #             if e.client_type == XdndEnter:
    # #                 print("📥 Drag entered")
    # #             elif e.client_type == XdndPosition:
    # #                 print("📍 Drag position updated")
    # #             elif e.client_type == XdndDrop:
    # #                 print("📦 Drop detected!")
    # #                 # Ask for the selection contents
    # #                 win.convert_selection(XdndSelection, UTF8_STRING, X.CurrentTime)
    # #         elif e.type == X.SelectionNotify:
    # #             prop = win.get_full_property(e.property, 0)
    # #             if prop:
    # #                 uris = prop.value.decode("utf-8").strip().splitlines()
    # #                 for uri in uris:
    # #                     if uri.startswith("file://"):
    # #                         path = uri[7:]  # strip file://
    # #                         print(f"📄 Dropped file: {path}")
    # #                         # TODO: update DPG UI here if you want
        else:
            time.sleep(0.01)


def dump_tree(w, level=0):
    indent = "  " * level
    name = w.get_wm_name()
    print(f"{indent}↳ {hex(w.id)}  name: {name}")
    for child in w.query_tree().children:
        dump_tree(child, level+1)

dpg.create_context()
dpg.create_viewport(title='Drag-and-Drop-Example', width=400, height=300)


with dpg.window(label="main-window", tag="main-window", no_close=True):
    dpg.add_text("🗂 Drag a file from Nautilus here!")


dpg.setup_dearpygui()
dpg.show_viewport()

time.sleep(2)
# dpg.set_primary_window("main-window", True)

d = display.Display()
win = _func_find_window_by_name("Drag-and-Drop-Example", d)
if not win:
    raise Exception("Could not find DPG window")
print(f"✅ Found Parent window: {hex(win.id)}")

print("🔎 Dumping window tree:")
dump_tree(win)

win = find_real_content_window(win)

_func_setup_xdnd(win, d)

threading.Thread(target=_func_xdnd_loop, args=(win, d), daemon=True).start()

while dpg.is_dearpygui_running():
    dpg.render_dearpygui_frame()


dpg.destroy_context() 

问题分析与修复方案

核心问题

  1. 未订阅XDND事件:仅设置XdndAware属性不足以接收事件,需明确向窗口订阅ClientMessage等事件掩码。
  2. XDND协议交互不完整:未按照协议规范回复拖拽源的XdndEnter、XdndPosition事件,导致拖拽源认为窗口不支持放置。
  3. 事件处理逻辑被注释:即使有事件触发,也无法解析拖拽内容。

修复后的代码

import dearpygui.dearpygui as dpg 
import threading 
import time
from Xlib import X, display, Xatom


def _func_find_window_by_name(name, d):
    root = d.screen().root

    def search(w):
        for child in w.query_tree().children:
            try:
                win_name = child.get_wm_name()
                if win_name and name in win_name:
                    return child
                result = search(child)
                if result:
                    return result
            except:
                pass
        return None

    return search(root)


def _func_setup_xdnd(win, d):
    # 设置XdndAware属性,声明支持XDND协议版本5
    XdndAware = d.intern_atom("XdndAware")
    win.change_property(XdndAware, Xatom.ATOM, 32, [5])
    
    # 订阅必要的事件掩码,确保能接收XDND相关事件
    win.select_input(X.ClientMessageMask | X.SubstructureNotifyMask)
    
    d.flush()

    # 验证属性设置
    prop = win.get_full_property(XdndAware, Xatom.ATOM)
    if prop:
        print(f"✅ XdndAware property set correctly: {prop.value}")
        print(f"✅ Listening on window: {hex(win.id)}")
    else:
        print("❌ Failed to set XdndAware property.")


def _func_xdnd_loop(win, d):
    XdndEnter = d.intern_atom("XdndEnter")
    XdndPosition = d.intern_atom("XdndPosition")
    XdndDrop = d.intern_atom("XdndDrop")
    XdndSelection = d.intern_atom("XdndSelection")
    XdndFinished = d.intern_atom("XdndFinished")
    UTF8_STRING = d.intern_atom("UTF8_STRING")

    print(f"👂 Listening for XDND events on: {hex(win.id)}")

    while dpg.is_dearpygui_running():
        if d.pending_events():
            e = d.next_event()
            if e.type == X.ClientMessage:
                if e.client_type == XdndEnter:
                    print("📥 Drag entered")
                    # 回复拖拽源,确认接收进入事件
                    reply = d.intern_atom("XdndPosition")
                    win.send_event(
                        e.data[0][1],
                        X.ClientMessageEvent(
                            type=X.ClientMessage,
                            window=e.data[0][1],
                            client_type=reply,
                            data=(32, [win.id, X.CurrentTime, 0, 0, 0])
                        )
                    )
                elif e.client_type == XdndPosition:
                    print("📍 Drag position updated")
                    # 回复拖拽源,允许放置
                    reply = d.intern_atom("XdndStatus")
                    win.send_event(
                        e.data[0][1],
                        X.ClientMessageEvent(
                            type=X.ClientMessage,
                            window=e.data[0][1],
                            client_type=reply,
                            data=(32, [win.id, 1, 0, 0, 0])
                        )
                    )
                elif e.client_type == XdndDrop:
                    print("📦 Drop detected!")
                    # 请求获取拖拽内容
                    win.convert_selection(XdndSelection, UTF8_STRING, X.CurrentTime)
                elif e.client_type == XdndFinished:
                    print("🔚 Drag session finished")
            elif e.type == X.SelectionNotify:
                if e.property != X.None:
                    prop = win.get_full_property(e.property, UTF8_STRING)
                    if prop:
                        uris = prop.value.decode("utf-8").strip().splitlines()
                        for uri in uris:
                            if uri.startswith("file://"):
                                path = uri[7:]
                                print(f"📄 Dropped file: {path}")
                                # 更新DPG UI显示拖拽结果
                                dpg.set_value("drop-result", f"已拖拽文件:{path}")
                        # 删除临时属性
                        win.delete_property(e.property)
        time.sleep(0.001)


dpg.create_context()
dpg.create_viewport(title='Drag-and-Drop-Example', width=400, height=300)

with dpg.window(label="main-window", tag="main-window", no_close=True):
    dpg.add_text("🗂 Drag a file from Nautilus here!")
    dpg.add_text("", tag="drop-result")  # 用于显示拖拽结果

dpg.setup_dearpygui()
dpg.show_viewport()

# 等待视口完全初始化
time.sleep(1)

d = display.Display()
win = _func_find_window_by_name("Drag-and-Drop-Example", d)
if not win:
    raise Exception("Could not find DPG window")
print(f"✅ Found window: {hex(win.id)}")

_func_setup_xdnd(win, d)

# 启动XDND监听线程
threading.Thread(target=_func_xdnd_loop, args=(win, d), daemon=True).start()

while dpg.is_dearpygui_running():
    dpg.render_dearpygui_frame()

dpg.destroy_context() 

关键修复点

  • 添加事件订阅:调用win.select_input()订阅X.ClientMessageMask,确保窗口能接收XDND相关客户端消息。
  • 完善协议交互:按照XDND规范添加XdndEnter、XdndPosition的回复逻辑,让拖拽源确认窗口支持放置。
  • 恢复事件处理:取消注释事件解析代码,实现拖拽文件路径的提取,并更新DearpyGUI界面显示结果。
  • 优化线程循环:将线程循环条件改为dpg.is_dearpygui_running(),确保线程随程序退出而终止。

内容的提问来源于stack exchange,提问作者kalopseeia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 18:05:56