如何将附件从Outlook拖放到WxPython应用程序

2024-05-16 04:35:02 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在运行以下程序:

  1. Python 3.7.9 64位
  2. wxpython 4.1.1 msw(凤凰城)wxWidgets 3.1.5

我正在尝试编写一个可以接收从Outlook拖动的附件的应用程序。这件事似乎真的没有得到充分的记录,但经过大量的研究和痛苦,我得到的是:

import struct
import wx

class MainFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.drop_target = MyDropTarget()

        self.SetSize((800, 600))
        self.SetDropTarget(self.drop_target)

class MyDropTarget(wx.DropTarget):
    def __init__(self):
        wx.DropTarget.__init__(self)

        self.fileContentsDataFormat = wx.DataFormat("FileContents")
        self.fileGroupDataFormat = wx.DataFormat("FileGroupDescriptor")
        self.fileGroupWDataFormat = wx.DataFormat("FileGroupDescriptorW")

        self.composite = wx.DataObjectComposite()
        self.fileContentsDropData = wx.CustomDataObject(format=self.fileContentsDataFormat)
        self.fileGroupDropData = wx.CustomDataObject(format=self.fileGroupDataFormat)
        self.fileGroupWDropData = wx.CustomDataObject(format=self.fileGroupWDataFormat)

        self.composite.Add(self.fileContentsDropData, preferred=True)
        self.composite.Add(self.fileGroupDropData)
        self.composite.Add(self.fileGroupWDropData)

        self.SetDataObject(self.composite)

    def OnDrop(self, x, y):
        return True

    def OnData(self, x, y, result):
        self.GetData()

        format = self.composite.GetReceivedFormat()
        data_object = self.composite.GetObject(format, wx.DataObject.Get)

        if format in [self.fileGroupDataFormat, self.fileGroupWDataFormat]:
            # See:
            #   https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-filedescriptora
            filenames = []
            data = data_object.GetData()
            count = struct.unpack("i", data[:4])
            fmt = "i16s8s8si8s8s8sii260s"
            for unpacked in struct.iter_unpack(fmt, data[4:]):
                filename = ""
                for b in unpacked[10]:
                    if b:
                        filename += chr(b)
                    else:
                        break
                filenames.append(filename)
                print(filenames)
        return result

app = wx.App(redirect=False)
frame = MainFrame(None)
frame.Show()
app.MainLoop()

因此,现在我的应用程序接受拖拽的Outlook附件,我可以解析它们的名称,但如何获取实际的文件内容呢?我似乎从未收到任何使用“文件内容”格式的数据对象

在我的旅行中,我发现:

这让我发疯了,每次我觉得我正在接近一个解决方案,它都在逃避我


Tags: inselfaddformatdatainitdefstruct
1条回答
网友
1楼 · 发布于 2024-05-16 04:35:02

不,使用普通wxPython不可能实现这一点。问题在于wx:s数据对象的概念不同于WIN32:s。在WX中,DataObject有一个它支持的所有格式的列表。假设每种格式对应于一条数据。在WIN32中,DataObject在请求数据时采用结构,该结构除了采用格式外,还采用索引。从Outlook拖放文件要求您提供索引以迭代拖动的文件及其内容,而无法向WX提供此索引

因此,我必须编写自己的拖放功能。此实现是特定于Windows的。另外,由于每个窗口只能调用一次RegisterDragDrop,这意味着此代码与WX:s拖放不兼容:

import struct

import pythoncom
import winerror
import win32con
import win32com
import win32api
import win32clipboard
import win32com.server.policy
from win32com.shell import shell, shellcon

import wx

# See:
#   http://timgolden.me.uk/pywin32-docs/PyFORMATETC.html
fmt_filegroupdescriptor = win32clipboard.RegisterClipboardFormat("FileGroupDescriptorW")
fmt_filegroupdescriptorw = win32clipboard.RegisterClipboardFormat("FileGroupDescriptorW")
fmt_filecontents = win32clipboard.RegisterClipboardFormat("FileContents")

fmts = [
    fmt_filegroupdescriptorw,
    fmt_filegroupdescriptor,
]

class MainFrame(wx.Frame):

    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.SetSize((800, 600))

        self.hwnd = self.GetHandle()
        self.drop_target = DropTarget(self.hwnd)

        wx.CallAfter(self.After)

    def After(self):
        pass

# For info on setting up COM objects in Python, see:
#   https://mail.python.org/pipermail/python-win32/2008-April/007410.html
#
#   http://www.catch22.net/tuts/win32/drag-and-drop-introduction
#   https://docs.microsoft.com/en-us/windows/win32/shell/datascenarios#copying-the-contents-of-a-dropped-file-into-an-application
#
# For clipboard format names under WIN32, see:
#   https://www.codeproject.com/Reference/1091137/Windows-Clipboard-Formats
#
# Dragging and dropping from Outlook is a "Shell Clipboard" DataObject. The formats
# and instructions on how to query are here:
#   https://docs.microsoft.com/en-us/windows/win32/shell/clipboard
class DropTarget(win32com.server.policy.DesignatedWrapPolicy):
    _reg_clsid_ = '{495E9ABE-5337-4AD5-8948-DF3B17D97FBC}'
    _reg_progid_ = "Test.DropTarget"
    _reg_desc_ = "Test for DropTarget"
    _public_methods_ = ["DragEnter", "DragLeave", "DragOver", "Drop"]
    _com_interfaces_ = [pythoncom.IID_IDropTarget]
    
    def __init__(self, hwnd):
        self._wrap_(self)
        self.hWnd = hwnd

        pythoncom.RegisterDragDrop(
            hwnd,
            pythoncom.WrapObject(
                self,
                pythoncom.IID_IDropTarget,
                pythoncom.IID_IDropTarget
            )
        )

    def DragEnter(self, data_object, key_state, point, effect):
        # print(data_object, key_state, point, effect)
        return shellcon.DROPEFFECT_COPY

    def DragOver(self, key_state, point, effect):
        # print(key_state, point, effect)
        return shellcon.DROPEFFECT_COPY

    def DragLeave(self):
        pass

    def Drop(self, data_object, key_state, point, effect):
        print(data_object)

        self.EnumFormats(data_object)
        print("")

        fmts = [
            (win32con.CF_HDROP,        self.OnDropFileNames),
            (fmt_filegroupdescriptorw, self.OnDropFileGroupDescriptor),
            (fmt_filegroupdescriptor,  self.OnDropFileGroupDescriptor),
        ]

        for fmt, callback in fmts:
            try:
                formatetc = (fmt, None, 1, -1, pythoncom.TYMED_HGLOBAL)
                ret = data_object.QueryGetData(formatetc)
                if not ret:
                    callback(data_object, fmt)
                    break
            except Exception as e:
                pass

        return effect

    def EnumFormats(self, data_object):
        for enum in data_object.EnumFormatEtc(pythoncom.DATADIR_GET):
            try:
                fmt = enum[0]
                name = win32clipboard.GetClipboardFormatName(fmt)
                print("GET", name, enum)
            except Exception as e:
                print(e, enum)

    def OnDropFileNames(self, data_object, fmt):
        formatetc = (win32con.CF_HDROP, None, 1, -1, pythoncom.TYMED_HGLOBAL)
        stgmedium = data_object.GetData(formatetc)

        data = stgmedium.data

        dropfiles_fmt = "I2lii"
        dropfiles_fmt_size = struct.calcsize(dropfiles_fmt)
        (offset, px, py, area_flag, is_unicode) = struct.unpack(dropfiles_fmt, data[0:dropfiles_fmt_size])

        charsize = 2 if is_unicode else 1

        data = data[dropfiles_fmt_size:]
        index = 0
        while True:
            data = data[index:]
            index, string = self.UnpackString(data, charsize)
            print(f"string: {string}")
            if not string:
                break

    def UnpackString(self, data, charsize):
        i = 0
        while True:
            if any(data[i*charsize:i*charsize + charsize]):
                i += 1
            else:
                break

        text = ""
        if i:
            if charsize == 1:
                text = data[:i*charsize].decode("ascii")
            elif charsize == 2:
                text = data[:i*charsize].decode("utf-16")

        return (i+1)*charsize, text

    def OnDropFileGroupDescriptor(self, data_object, fmt):
        filenames = self.UnpackGroupFileDescriptor(data_object, fmt)
        for index, filename in enumerate(filenames):
            # See:
            #   http://timgolden.me.uk/pywin32-docs/PyIStream.html
            formatetc_contents = (fmt_filecontents,  None, 1, index, pythoncom.TYMED_ISTREAM)
            stgmedium_stream = data_object.GetData(formatetc_contents)
            stream = stgmedium_stream.data

            stat = stream.Stat()
            data_size = stat[2]
            data = stream.Read(data_size)

            print(index, filename, len(data))

    def UnpackGroupFileDescriptor(self, data_object, fmt):
        formatetc = (fmt, None, 1, -1, pythoncom.TYMED_HGLOBAL)
        stgmedium = data_object.GetData(formatetc)
        data = stgmedium.data
        filenames = []
        count = struct.unpack("i", data[:4])
        if fmt == fmt_filegroupdescriptorw:
            charsize = 2 
            struct_fmt = "i16s8s8si8s8s8sii520s"
        else:
            charsize = 1
            struct_fmt = "i16s8s8si8s8s8sii260s"

        for unpacked in struct.iter_unpack(struct_fmt, data[4:]):
            filename = self.UnpackString(unpacked[10], charsize)
            filenames.append(filename)

        return filenames

if __name__ == "__main__":

    pythoncom.OleInitialize()

    app = wx.App(redirect=False)
    frame = MainFrame(None)
    frame.Show()
    app.MainLoop()

相关问题 更多 >