【问题标题】:Cannot open read-only Microsoft Word files with win32com.client无法使用 win32com.client 打开只读 Microsoft Word 文件
【发布时间】:2019-05-19 17:22:33
【问题描述】:

我有数千个 docx 文件,我需要通过 Python 提取其中的某些元素。我在我的 Python 脚本中使用 win32com.client 来完成这项任务。

import win32com.client
doc = win32com.client.GetObject(some_path_to_a_docx_file)

这适用于 99% 的文件;但是,对于一些文件,Microsoft Word 对话框会启动:“作者希望您以只读方式打开此文件,除非您需要进行更改。以只读方式打开?”。脚本此时停止等待用户输入。

在对话框上按是继续根据需要运行脚本;但是,这是低于标准的,因为我需要它完全自动化,而不会弹出任何对话框。有没有办法通过 win32com Python 或通过 MS Word 永久禁用 MS Word 提示? (注意:这里不能选择导入 docx 作为 win32com 的替代品。)

【问题讨论】:

    标签: python ms-word win32com


    【解决方案1】:

    Office 应用程序是最终用户应用程序,并未作为开发工具进行优化。这意味着它们在等待用户输入时可能会出现挂起,如问题中所述。没有简单、干净的方法可以解决这个问题,这就是为什么建议将 Open XML 文件格式用于消除对话框存在问题的需求...

    如果必须使用自动化,那么我知道有两种可能性。详细信息不是python,但这确实记录了基本方法。

    1. 如果代码无法继续,请使用 Timer 函数和 SendKeys 自动关闭对话框。这有点像彩票,因为不可能知道哪个对话框被关闭了。通常,发送“Escape”键。曾几何时,有一组针对各种编程语言的知识库文章,但 Microsoft 站点上不再提供这些文章。我找到了一个存档C-Bit,并正在复制演示经典 VB6 原理的相关示例内容:

    本节中的步骤演示 Microsoft Word 的自动化 打印文档。自动化客户端调用 PrintOut 方法 Word 文档对象。如果配置了用户的默认打印机 打印到 FILE 端口,然后调用 PrintOut 会产生一个对话框 框提示用户输入文件名。要确定是否 PrintOut 方法导致出现此对话框,Visual Basic 自动化客户端使用 Timer 控件来检测空闲时间 调用 PrintOut 方法。在调用 PrintOut 之前,定时器是 启用并设置为在五秒内触发。当 PrintOut 完成时, 定时器被禁用。因此,如果 PrintOut 方法在 五秒钟,计时器事件永远不会发生,也不会采取进一步的行动 采取。文档被打印出来,代码继续执行 打印输出方法。但是,如果 Timer 事件发生在 五秒间隔,假设PrintOut方法没有 完成,并且延迟是由等待的对话框引起的 用户输入。当 Timer 事件发生时,自动化客户端给出 焦点到 Word 并使用 SendKeys 关闭对话框。

    注意 出于演示目的,此示例使用 PrintOut 方法 以这样一种方式,它会故意显示一个对话框,当它 打印到设置为 FILE 端口的打印机。请注意,打印输出 方法有两个参数,OutputfileName 和 PrintToFile,您可以 提供以避免此对话框。

    此外,当使用这种“计时器”方法时,您可以自定义 等待时间大于或小于五秒,以及 自定义您发送到对话框的击键。

    此演示包含两个 Visual Basic 项目:

    1. 提供用于检测延迟的 Timer 类的 ActiveX EXE。为 Timer 类使用 ActiveX EXE 的原因是在单独的进程中运行 Timer 代码,因此在单独的线程中运行。 这使得 Timer 类可以在 暂停自动呼叫。

    2. 使用 Word 自动化并调用 PrintOut 方法来打印文档的标准 EXE。它使用 ActiveX EXE 来检测延迟 调用 PrintOut 方法时。创建 ActiveX EXE 项目

    3. 启动 Visual Basic 并创建一个 ActiveX EXE 项目。 Class1 是默认创建的。
    4. 在项目菜单上,单击以选择属性,然后将项目名称更改为 MyTimer。
    5. 将以下代码复制并粘贴到 Class1 模块中:Option Explicit
     Public Event Timer() Private oForm1 As Form1
    
     Private Sub Class_Initialize()
         Set oForm1 = New Form1
         oForm1.Timer1.Enabled = False 
    End Sub
    
     Private Sub Class_Terminate()
         Me.Enabled = False
         Unload oForm1
         Set oForm1 = Nothing 
    End Sub
    
    Public Property Get Enabled() As Boolean
         Enabled = oForm1.Timer1.Enabled 
    End Property
    
    Public Property Let Enabled(ByVal vNewValue As Boolean)
         oForm1.Timer1.Enabled = vNewValue
         If vNewValue = True Then
             Set oForm1.oClass1 = Me
         Else
             Set oForm1.oClass1 = Nothing
         End If 
    End Property
    
     Public Property Get Interval() As Integer
         Interval = oForm1.Timer1.Interval 
    End Property
    
     Public Property Let Interval(ByVal vNewValue As Integer)
         oForm1.Timer1.Interval = vNewValue End Property
    
     Friend Sub TimerEvent()
         RaiseEvent Timer 
    End Sub                 
    
    1. 在“项目”菜单上,选择“添加表单”以将新表单添加到项目中。
    2. 将 Timer 控件添加到窗体。
    3. 将以下代码复制并粘贴到 Form1:Option Explicit 的代码模块中
     Public oClass1 As Class1
    
     Private Sub Timer1_Timer()
         oClass1.TimerEvent 
    End Sub
    
    1. 将此项目保存在名为 Server 的新子文件夹中。
    2. 在“文件”菜单上,选择“生成 MyTimer.Exe”以构建和注册组件。创建自动化客户端
    1. 使用 Windows API 来识别和消除可能的问题对话框。我在MSDN forum 上找到了一些代码,我在这里复制。归属于用户名yet

    这是一个在 C# 中通过 pinvoke 使用 Win32 API 的示例。我是 能够处理已知的 Word 窗口,如 Word->File->Options 对话框 窗口通过 FindWindow 和 SendMessage 或 PostMessage。请离开 通过样本,看看它是否适合你。自从你 知道你想关闭哪些对话框,请使用 spy++ 找到窗口标题和窗口类并在此示例中使用它。

    对于您的场景,可能不需要 SendKeys。

    希望这会有所帮助。

    using System;
     using System.Collections.Generic;
     using System.ComponentModel;
     using System.Data;
     using System.Drawing;
     using System.Linq;
     using System.Text;
     using System.Windows.Forms;
    
    using System.Runtime.InteropServices;
    
    namespace SendKeys
     {
    
        public partial class Form1 : Form
         {
             // For Windows Mobile, replace user32.dll with coredll.dll
    
             [DllImport("user32.dll", SetLastError = true)]
             static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    
            // Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
    
            [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
             static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
    
            [DllImport("user32.dll")]
             public static extern bool SetForegroundWindow(IntPtr hWnd);
    
    
             [return: MarshalAs(UnmanagedType.Bool)]
             [DllImport("user32.dll", SetLastError = true)]
             static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
             static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    
            static uint WM_CLOSE = 0x10;
    
            public Form1()
             {
                 InitializeComponent();
             }
    
            private void button1_Click(object sender, EventArgs e)
             {
    
                 // the caption and the className is for the Word -> File -> Options window
                 // the caption and the className are got by using spy++ application and focussing on the window we are researching.
                 string caption = "Word Options";
                 string className = "NUIDialog";
                 IntPtr hWnd= (IntPtr)(0);
    
                 // Win 32 API being called through PInvoke
                 hWnd = FindWindow(className, caption);
    
                /*bool retVal = false;
                 if ((int)hWnd != 0)
                 {
                    // Win 32 API being called through PInvoke 
                   retVal = SetForegroundWindow(hWnd);
                 }*/
    
    
    
                if ((int)hWnd != 0)
                 {
                     CloseWindow2(hWnd);
                     //CloseWindow(hWnd); // either sendMessage or PostMessage can be used.
                 }
             }
    
    
    
            static bool CloseWindow(IntPtr hWnd)
             {
                 // Win 32 API being called through PInvoke
                 SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
                 return true;
             }
    
            static bool CloseWindow2(IntPtr hWnd)
             {
                 // Win 32 API being called through PInvoke
                 PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
                 return true;
    
             }
    
        }
     }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多