在实际打印文档之前预览PDF文件并选择打印机

时间:2017-03-14 09:18:35

标签: python macos printing tkinter printers

我目前正在使用Python3 / tkinter开发一个主要在Mac上运行的应用程序,需要打印PDF文件。

过去,我已经设法通过使用bash脚本中的一些命令自动将PDF文件发送到打印机,所以我想我最终可能会使用这种方法作为最后的手段。

但是,我从未找到指定一台打印机的方法,它总是将它直接发送到当前选定的打印机(通常是最后使用的打印机)。问题是所有这些计算机都连接到多台打印机,而某些打印机不适合A4文档(例如,标签打印机)。

有没有人知道是否有办法从命令行启动预览应用程序,或者是否允许我们在实际打印之前预览和选择打印机的任何外部模块?

1 个答案:

答案 0 :(得分:1)

这是我刚刚创建的一个程序,可让您选择打印机并打印。

import tkinter as tk
from tkinter.filedialog import askopenfilename
import subprocess
from pprint import pprint
import platform
import sys

def which(program):
    # http://stackoverflow.com/a/377028/3924118
    import os
    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None


class FilePrinterDialog(tk.Toplevel):

    def __init__(self, root, *args, **kwargs):
        tk.Toplevel.__init__(self, root, *args, **kwargs)
        self.root = root

        self.body = tk.Frame(self, bg="lightblue")
        self.body.pack(expand=True, fill="both")

        self.title_frame = tk.Frame(self.body, pady=5)
        self.title_frame.pack(fill="both", pady=(15, 5))

        self.title = tk.Label(self.title_frame,  text="Let's print!")
        self.title.pack(fill="x")

        # Current selected printer of your system
        self.system_default_destination = self._find_system_default_destination()

        # Finds printer names
        self.printers_names = self._find_printers_names()

        self.selected_file = None  # To hold the selected file's name
        self.data_bytes = None  # Bytes read from the selected file to print
        self.selected_printer = None  # Hols name of selected printer

        # Display them
        self.printers_frame = tk.Frame(self.body, bg="lightblue", padx=10, pady=10)
        self.printers_frame.pack(expand=True, fill="both")
        self._display_printers()

        self.bottom_frame = tk.Frame(self.body, pady=5)
        self.bottom_frame.pack(fill="both", pady=(5, 16))
        self.open_file_chooser = tk.Button(self.bottom_frame,
                                           text="Open file chooser",
                                           command=self._select_file)
        self.open_file_chooser.pack(side="left", padx=10)

        self.print_file = tk.Button(self.bottom_frame,
                                           text="Print",
                                           command=self._print_selected_file)
        self.print_file.pack(side="right", padx=10)


        self._make_modal()

    def _read_file(self):
        # NOT USED!
        if not self.selected_file:
            raise ValueError("No file chosen")
        with open(self.selected_file, "rb") as in_file: # opening for [r]eading as [b]inary
            return in_file.read() # if you only wanted to read 512 bytes, do .read(512)

    def _print_selected_file(self):
        if not self.selected_file:
            print("No file selected yet!")
        else:
            subprocess.call(["lpr", self.selected_file])

    def _select_file(self):
        self.selected_file = askopenfilename(title = "Choose file to print")
        print(self.selected_file)

    def _on_listbox_selection(self, event):
        self.selected_printer = self._find_current_selected_printer()

        # Sets the printer on your system
        subprocess.call(["lpoptions", "-d", self.selected_printer])
        print("Selected printer:", self.selected_printer)

    def _find_current_selected_printer(self):
        curselection = self.listbox.curselection()
        if len(curselection) > 0:
            return self.listbox.get(curselection[0])   
        else:
            return None

    def _display_printers(self):
        self.scrollbar = tk.Scrollbar(self.printers_frame)
        self.scrollbar.pack(side="right", fill="y")

        self.listbox = tk.Listbox(self.printers_frame,
                                  yscrollcommand=self.scrollbar.set,
                                  selectbackground="yellow",
                                  selectmode="single",
                                  height=6)

        for printer_name in self.printers_names:
            self.listbox.insert("end", printer_name)

        # Keep track of selected listbox
        self.listbox.bind("<<ListboxSelect>>", self._on_listbox_selection)

        # Sets first listbox as selected
        self.listbox.select_set(0) # Sets focus
        self.listbox.event_generate("<<ListboxSelect>>")

        self.listbox.pack(side="left", fill="both", expand=True)
        self.scrollbar.config(command=self.listbox.yview)

    def _find_system_default_destination(self):
        return subprocess.getoutput("lpstat -d").split(": ")[1]

    def _find_printers_names(self):
        # Command to obtain printer names based on: https://superuser.com/a/1016825/317323
        return subprocess.getoutput("lpstat -a | awk '{print $1}'").split("\n")

    def _make_modal(self):
        # Makes the window modal
        self.transient(self.root)
        self.grab_set()
        self.wait_window(self)


if __name__ == "__main__":
    if not which("lpoptions") or not which("lpr") or not which("awk") or not which("lpstat"):        
        sys.stderr.write("Requirements: lopotions, lpr, lpstat and awk not satisfied")
    else:
        root = tk.Tk()
        opener = tk.Button(root, text="Open printer chooser", command=lambda: FilePrinterDialog(root))
        opener.pack()
        root.mainloop()

只有拥有我在程序中指定的必需依赖项时,此程序才有效。你应该拥有那些,因为我也在Mac OS X(Sierra)上。

我仅使用.py文件进行了测试。遗憾的是,所记录的文档格式并不是真正符合要求(因为我并不是如何使用lpr打印文件的专家),但您可以查看{{lpr的选项。 1}}并看看你能用它做些什么。

另外,我没有创建所选文件的任何预览,但您可以使用PIL(至少对于图像)实现此目的...

注意:请注意,不要在print按钮上单击100次,否则队列中将有100个文件要打印!你可能想以某种方式解决这个问题。