wxWidgets TaskBarIcon как цель удаления - PullRequest
0 голосов
/ 09 марта 2020

Как я могу определить иконку в трее как цель перетаскивания, используя wxWidgets? SetDropTarget недоступен ни в TaskBarIcon, ни в классе Icon. Я хотел бы иметь что-то вроде:

class TextDropTarget(wx.TextDropTarget):

    def __init__(self, obj):
        wx.TextDropTarget.__init__(self)
        self.obj = obj

    def OnDropText(self, x, y, data):
        self.obj.action(data)


class TaskBarIcon(wx.adv.TaskBarIcon):

    def __init__(self):
        super().__init__()
        self.SetIcon(wx.Icon(wx.Bitmap(TRAY_ICON)), TRAY_TOOLTIP)
        self.SetDropTarget(TextDropTarget(self))

    def action(self, data):
        # Do something

1 Ответ

0 голосов
/ 10 марта 2020

После некоторых исследований я могу ответить на вопрос сам:

На Ма c это возможно, потому что это общая функция в этой ОС. На Windows и Linux это возможно через уродливый обходной путь. Мне интересно, почему это не поддерживается в этих ОС, потому что действительно есть некоторые варианты использования этой функции. Вот обходной путь (который также упоминается здесь ) для Windows:

  1. Получите область системного трея:

def FindSysPagerWindow():
    hWnd = win32gui.FindWindowEx(win32gui.GetDesktopWindow(), 0, "Shell_TrayWnd", None)
    if hWnd:
        hWnd = win32gui.FindWindowEx(hWnd, None, "TrayNotifyWnd", None)
        if hWnd:
            hWnd = win32gui.FindWindowEx(hWnd, None, "SysPager", None)
    return hWnd


class TaskBarIcon(wx.adv.TaskBarIcon):

    def __init__(self, r):
        super().__init__()
        hSysPager = FindSysPagerWindow()
        # Get rectangle of system area
        self.region = win32gui.GetWindowRect(hSysPager)
        self.frm = None
        self.source = None
Создайте поток, который обнаруживает события начала перетаскивания и создает прозрачную рамку над системным треем:
    def callback(self, hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime):
        length = user32.GetWindowTextLengthW(hwnd)
        title = ctypes.create_unicode_buffer(length + 1)
        user32.GetWindowTextW(hwnd, title, length + 1)
        if self.frm is None and (title.value == "Drag"):
            self.source = GetProcessFilename(GetProcessId(dwEventThread, hwnd))
            self.frm = SystemTrayFrame(self.region, self.onDrop)

    def DragDetectThread(self):
        ole32.CoInitialize(0)
        WinEventProc = WinEventProcType(self.callback)
        user32.SetWinEventHook.restype = ctypes.wintypes.HANDLE
        hookId = user32.SetWinEventHook(win32con.EVENT_OBJECT_SHOW, win32con.EVENT_OBJECT_SHOW,
                                        0, WinEventProc, 0, 0, win32con.WINEVENT_OUTOFCONTEXT)
        msg = ctypes.wintypes.MSG()
        while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) != 0:
            user32.TranslateMessageW(msg)
            user32.DispatchMessageW(msg)
        user32.UnhookWinEvent(hookId)
        ole32.CoUninitialize()

Здесь все еще остается нерешенной проблема, связанная с тем, что определение правильного источника перетаскивания не работает надежный. Если мышь перемещается слишком быстро в начале перетаскивания, то обнаруженный источник может быть неправильным. Но это только проблема, если эта информация важна.

Создайте прослушиватель событий кнопки мыши, используя pynput, для обнаружения события нажатия левой кнопки мыши, которое интерпретируется как событие окончания перетаскивания. Слушатель, а также метод onDrop уничтожают прозрачную рамку:
from pynput.mouse import Listener, Button

...

        self.listener = Listener(on_click=self.onMouseButtonEvent)
        self.listener.start()

    def onMouseButtonEvent(self, x, y, button, pressed):
        if self.frm is not None and (button == Button.left) and not pressed:
            self.frm.Destroy()
            self.frm = None

    def onDrop(self, x, y, data):
        # Do something with the dropped data
        if self.frm is not None:
            self.frm.Destroy()
            self.frm = None
Класс для прозрачной рамки выглядит примерно так:
class SystemTrayFrame(wx.Frame):

    def __init__(self, r, cbDrop):
        super().__init__(None, wx.ID_ANY, "TransparentFrame", pos=(r[0], r[1]), size=(r[2] - r[0], r[3] - r[1]),
                         style=wx.STAY_ON_TOP)
        dropTarget = DropTarget(cbDrop)
        self.SetDropTarget(dropTarget)
        self.SetTransparent(0)
        self.Show()

Вплоть до этого все нормально, если все в порядке, что вся область панели задач является целью перетаскивания для вашего приложения. , Но если вы хотите ограничить область перетаскивания значком на панели задач, теперь вам нужен уродливый обходной путь:

a) Замените свой модный значок на панели задач на значок уникального цвета с формой, которую вы можете легко определить.

b) Сделайте снимок экрана области системного трея, замените иконку в системном трее обратно на иконку вашего причудливого приложения, затем найдите положение уникально окрашенного значка:

    im = ImageGrab.grab(bbox=self.region)
    # Search for icon position and size (because of optional scaling by OS)

Поиск Операция может быть немного более сложной, если в ОС включено масштабирование.

c) Используйте этот результат для позиционирования прозрачной рамки.

Надеюсь, это поможет другим людям, которые сталкиваются с та же проблема.

...