如果您的工作涉及生成PDF报告,发票等,则您可能已经考虑过使用Python自动化。Python有一些很不错的第三方库用于处理PDF文件,使您可以从脚本中读取和写入PDF。同样,您也可以将这些库作为简单GUI工具的基础,从而为您提供一种在桌面上操作自动填充或编辑PDF报告的简便方法。

在本教程中,我们将使用两个库来创建自定义PDF报告填充器。数据将使用Qt表单收集:只需编辑字段,按“生成”按钮即可在文件夹中获取填写的表单。我们将在这里使用的两个库是:

  • reportlab,可让您使用文本和图片类原件创建PDF

  • pdfrw,一个用于从现有PDF读取和提取页面的库

尽管我们可以使用reportlab来绘制整个PDF,但是使用外部工具设计模板然后在其上叠加动态内容会更容易。我们可以使用pdfrw来读取模板PDF,提取页面,然后可以使用reportlab在该页面上进行绘制。这样一来,我们就可以将自定义信息(来自我们的应用程序)直接覆盖到现有的PDF模板上,并以新名称保存。

在此示例中,我们通过手动输入字段,但是您可以修改应用程序以从外部CSV文件读取PDF数据并从中生成多个PDF。

PDF 模板

为了进行测试,我使用Google Docs创建了一个自定义的TPS报告模板,并将页面下载为PDF。该页面包含许多要填写的字段。在本教程中,我们将编写一个PyQt表单,用户可以填写该表单,然后将数据写到正确位置的PDF上。

模板为A4格式。将其与脚本保存在同一文件夹中。

如果您想使用其他模板,请随时使用。只需记住,编写表单时需要调整表单字段的位置。

布置表单视图

Qt包含一个QFormLayout布局,该布局简化了生成简单表单布局的过程。它的工作方式类似于网格,但是您可以将元素的行添加在一起,并将字符串自动转换为QLabel对象。我们的框架应用程序,包括与模板表单匹配的完整布局,如下所示。

from PyQt5.QtWidgets import QPushButton, QLineEdit, QApplication, QFormLayout, QWidget, QTextEdit, QSpinBoxclass Window(QWidget):def __init__(self):super().__init__()self.name = QLineEdit()self.program_type = QLineEdit()self.product_code = QLineEdit()self.customer = QLineEdit()self.vendor = QLineEdit()self.n_errors = QSpinBox()self.n_errors.setRange(0, 1000)self.comments = QTextEdit()self.generate_btn = QPushButton("Generate PDF")layout = QFormLayout()layout.addRow("Name", self.name)layout.addRow("Program Type", self.program_type)layout.addRow("Product Code", self.product_code)layout.addRow("Customer", self.customer)layout.addRow("Vendor", self.vendor)layout.addRow("No. of Errors", self.n_errors)layout.addRow("Comments", self.comments)layout.addRow(self.generate_btn)self.setLayout(layout)app = QApplication([])
w = Window()
w.show()
app.exec()

在编写用于替换/自动化纸质表格的工具时,尝试模仿纸质表格的布局通常是个好主意,这样就很熟悉了。

上面的代码运行后在窗口中提供以下布局。您已经可以在字段中输入内容,但是按下按钮尚无任何作用 —— 我们尚未编写代码来生成PDF或将其连接到按钮。

生成 PDF 文本

为了将基本模板生成PDF,我们将结合reportlabPdfReader两个库。流程如下:

  • 使用PdfReader读入template.pdf文件,并仅提取第一页。

  • 创建一个reportlabCanvas对象

  • 使用pdfrw.toreportlab.makerl生成画布对象,然后使用canvas.doForm()将其添加到Canvas中。

  • 在画布上绘制自定义位

  • 将PDF保存到文件

代码如下所示,不需要Qt,您可以保存到文件并按原样运行。运行后,生成的PDF将作为result.pdf保存在同一文件夹中。

from reportlab.pdfgen.canvas import Canvas
from pdfrw import PdfReader
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerloutfile = "result.pdf"template = PdfReader("template.pdf", decompress=False).pages[0]
template_obj = pagexobj(template)canvas = Canvas(outfile)xobj_name = makerl(canvas, template_obj)
canvas.doForm(xobj_name)ystart = 443# Prepared by
canvas.drawString(170, ystart, "My name here")canvas.save()

由于生成PDF的过程正在进行IO操作,因此可能会花费一些时间(例如,如果我们从网络驱动器中加载文件)。因此,最好在单独的线程中进行处理。接下来,我们将定义这个自定义线程运行器。

在单独的线程中运行生成器

由于每个生成器都是一个孤立的工作,因此使用Qt的QRunner框架来处理该流程是很有意义的,这也使以后为每个作业添加可自定义的模板变得很简单。我们在使用多线程教程中可以看到相同的方法,在该方法中,我们使用QRunner的子类来保存我们的自定义运行代码,并在单独的QObject子类上实现特定于运行器的信号。

from PyQt5.QtWidgets import QPushButton, QLineEdit, QApplication, QFormLayout, QWidget, QTextEdit, QMessageBox, QSpinBox
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlotfrom reportlab.pdfgen.canvas import Canvasfrom pdfrw import PdfReader
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerlclass WorkerSignals(QObject):"""Defines the signals available from a running worker thread."""error = pyqtSignal(str)file_saved_as = pyqtSignal(str)class Generator(QRunnable):"""Worker threadInherits from QRunnable to handle worker thread setup, signalsand wrap-up.:param data: The data to add to the PDF for generating."""def __init__(self, data):super().__init__()self.data = dataself.signals = WorkerSignals()@pyqtSlot()def run(self):try:outfile = "result.pdf"template = PdfReader("template.pdf", decompress=False).pages[0]template_obj = pagexobj(template)canvas = Canvas(outfile)xobj_name = makerl(canvas, template_obj)canvas.doForm(xobj_name)ystart = 443# Prepared bycanvas.drawString(170, ystart, self.data['name'])canvas.save()except Exception as e:self.signals.error.emit(str(e))returnself.signals.file_saved_as.emit(outfile)

我们在这里定义了两个信号:

  • file_saved_as,它发出已保存的PDF文件的文件名(成功时)

  • error,它以调试字符串的形式发出错误信号

我们需要一个QThreadPool来添加运行我们的自定义运行器。我们可以将它添加到__init__块的MainWindow中。

class Window(QWidget):def __init__(self):super().__init__()self.threadpool = QThreadPool()

现在我们已经定义了生成器QRunner,我们只需要实现generate方法来创建运行器,将表单字段中的数据传递给运行器,并开始运行生成器。

def generate(self):self.generate_btn.setDisabled(True)data = {'name': self.name.text(),'program_type': self.program_type.text(),'product_code': self.product_code.text(),'customer': self.customer.text(),'vendor': self.vendor.text(),'n_errors': str(self.n_errors.value()),'comments': self.comments.toPlainText()}g = Generator(data)g.signals.file_saved_as.connect(self.generated)g.signals.error.connect(print)  # Print errors to console.self.threadpool.start(g)def generated(self, outfile):pass28

在此代码中,我们首先禁用了generate_btn,目的是使用户在生成过程中无法多次按下按钮。然后,我们从控件中构造数据字典,使用.text()方法从QLineEdit控件中获取文本,.value()QSpinBox中获取值,以及.toPlainText()获得QTextEdit的纯文本表示。因为我们要放置文本格式,所以我们将数值转换为字符串。

为了实际生成PDF,我们创建了刚刚定义的Generator运行器的实例,并传入了数据字典。我们将file_saved_as信号连接到生成的方法(在底部定义,但尚未执行任何操作),并将错误信号连接到标准Python打印功能:这会自动将任何错误打印到控制台。

最后,我们使用Generator实例,并将其传递到线程池的.start()方法以使其排队运行(它应立即启动)。然后,我们可以将此方法挂接到主窗口__init__中的按钮上,例如:

self.generate_btn.pressed.connect(self.generate)

如果立即运行该应用程序,则按下按钮将触发PDF的生成,并且结果将作为result.pdf保存在启动该应用程序的同一文件夹中。到目前为止,我们只在页面上放置了一个文本块,因此让我们完成生成器的工作,以将所有字段写在正确的位置。

完成生成器

接下来,我们需要完成模板上的文本放置。这里的技巧是弄清模板的每行间距(取决于字体大小等),然后计算相对于第一行的位置。y坐标增加了页面的高度(所以0,0在左下角),因此在之前的代码中,我们为顶行定义ystart,然后为每行减去28。

ystart = 443# Prepared by
canvas.drawString(170, ystart, self.data['name'])# Date: Todays date
today = datetime.today()
canvas.drawString(410, ystart, today.strftime('%F'))# Device/Program Type
canvas.drawString(230, ystart-28, self.data['program_type'])# Product code
canvas.drawString(175, ystart-(2*28), self.data['product_code'])# Customer
canvas.drawString(315, ystart-(2*28), self.data['customer'])# Vendor
canvas.drawString(145, ystart-(3*28), self.data['vendor'])ystart = 250# Program Language
canvas.drawString(210, ystart, "Python")canvas.drawString(430, ystart, self.data['n_errors'])

包装

对于大多数的表单字段,我们都可以按原样输出文本,因为没有换行符。如果输入的文本太长,则会溢出 —— 但是如果我们希望可以通过设置字符的最大长度来限制字段本身,例如

field.setMaxLength(25)

对于注释字段,事情有些棘手。该字段可以更长,并且需要将行包装在模板中的多行上。该字段还接受换行符(通过按Enter键),这些换行符会在写入PDF时出现问题。

如您在上面的屏幕截图中所见,换行符在文本中显示为黑色正方形。好的方面是,仅删除换行符将使换行更加容易:我们可以将每行换行为指定数量的字符。

由于字符的宽度是可变的,因此这并不是完美的选择,但这无关紧要。如果我们换行以最宽的字符(W)填充,则任何实际行都将适合。

Python带有内置的textwrap库,一旦我们删除了换行符,我们就可以使用该库包装文本。

import textwrap
comments = comments.replace('\n', ' ')
lines = textwrap.wrap(comments, width=80)

但是我们需要考虑第一行较短,这可以通过以下方法实现:首先将其包装为较短的长度,重新加入其余部分,然后重新包装,例如:

import textwrap
comments = comments.replace('\n', ' ')
lines = textwrap.wrap(comments, width=65) # 45
first_line = lines[0]
remainder = ' '.join(lines[1:])lines = textwrap.wrap(remainder, 75) # 55
lines = lines[:4]  # max lines, not including the first.

换行线(45和55)上的注释标记显示了将Ws线插入空间所需的换行长度。这是最短的线,但不现实。使用的值应适用于大多数普通文本。

为了正确执行此操作,我们应该计算文档字体中每个文本长度的实际大小,并使用该大小告知包装器。

准备好行之后,可以遍历列表并每次减小y位置,将它们打印到 PDF 上。模板文档中各行之间的间距为28。

comments = self.data['comments'].replace('\n', ' ')
if comments:lines = textwrap.wrap(comments, width=65) # 45first_line = lines[0]remainder = ' '.join(lines[1:])lines = textwrap.wrap(remainder, 75) # 55lines = lines[:4]  # max lines, not including the first.canvas.drawString(155, 223, first_line)for n, l in enumerate(lines, 1):canvas.drawString(80, 223 - (n*28), l)

这给出了一些带有乱数假文文本的结果。

自动显示结果

创建文件后,运行程序会在信号中返回创建文件的文件名(当前始终相同)。最好自动将生成的PDF呈现给用户,这样他们就可以检查运行是否正常。在Windows上,我们可以使用os.startfile以该类型的默认启动器打开文件 —— 在这种情况下,使用默认的PDF查看器打开PDF。

由于这在其他平台上不可用,因此我们捕获了错误,而是显示了QMessageBox

def generated(self, outfile):self.generate_btn.setDisabled(False)try:os.startfile(outfile)except Exception:# If startfile not available, show dialog.QMessageBox.information(self, "Finished", "PDF has been generated")

完整代码

PyQt5 的完整代码如下所示。

from PyQt5.QtWidgets import QPushButton, QLineEdit, QApplication, QFormLayout, QWidget, QTextEdit, QMessageBox, QSpinBox
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlotfrom reportlab.pdfgen.canvas import Canvasimport osimport textwrap
from datetime import datetimefrom pdfrw import PdfReader
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerlclass WorkerSignals(QObject):"""Defines the signals available from a running worker thread."""error = pyqtSignal(str)file_saved_as = pyqtSignal(str)class Generator(QRunnable):"""Worker threadInherits from QRunnable to handle worker thread setup, signalsand wrap-up.:param data: The data to add to the PDF for generating."""def __init__(self, data):super().__init__()self.data = dataself.signals = WorkerSignals()@pyqtSlot()def run(self):try:outfile = "result.pdf"template = PdfReader("template.pdf", decompress=False).pages[0]template_obj = pagexobj(template)canvas = Canvas(outfile)xobj_name = makerl(canvas, template_obj)canvas.doForm(xobj_name)ystart = 443# Prepared bycanvas.drawString(170, ystart, self.data['name'])# Date: Todays datetoday = datetime.today()canvas.drawString(410, ystart, today.strftime('%F'))# Device/Program Typecanvas.drawString(230, ystart-28, self.data['program_type'])# Product codecanvas.drawString(175, ystart-(2*28), self.data['product_code'])# Customercanvas.drawString(315, ystart-(2*28), self.data['customer'])# Vendorcanvas.drawString(145, ystart-(3*28), self.data['vendor'])ystart = 250# Program Languagecanvas.drawString(210, ystart, "Python")canvas.drawString(430, ystart, self.data['n_errors'])comments = self.data['comments'].replace('\n', ' ')if comments:lines = textwrap.wrap(comments, width=65) # 45first_line = lines[0]remainder = ' '.join(lines[1:])lines = textwrap.wrap(remainder, 75) # 55lines = lines[:4]  # max lines, not including the first.canvas.drawString(155, 223, first_line)for n, l in enumerate(lines, 1):canvas.drawString(80, 223 - (n*28), l)canvas.save()except Exception as e:self.signals.error.emit(str(e))returnself.signals.file_saved_as.emit(outfile)class Window(QWidget):def __init__(self):super().__init__()self.threadpool = QThreadPool()self.name = QLineEdit()self.program_type = QLineEdit()self.product_code = QLineEdit()self.customer = QLineEdit()self.vendor = QLineEdit()self.n_errors = QSpinBox()self.n_errors.setRange(0, 1000)self.comments = QTextEdit()self.generate_btn = QPushButton("Generate PDF")self.generate_btn.pressed.connect(self.generate)layout = QFormLayout()layout.addRow("Name", self.name)layout.addRow("Program Type", self.program_type)layout.addRow("Product Code", self.product_code)layout.addRow("Customer", self.customer)layout.addRow("Vendor", self.vendor)layout.addRow("No. of Errors", self.n_errors)layout.addRow("Comments", self.comments)layout.addRow(self.generate_btn)self.setLayout(layout)def generate(self):self.generate_btn.setDisabled(True)data = {'name': self.name.text(),'program_type': self.program_type.text(),'product_code': self.product_code.text(),'customer': self.customer.text(),'vendor': self.vendor.text(),'n_errors': str(self.n_errors.value()),'comments': self.comments.toPlainText()}g = Generator(data)g.signals.file_saved_as.connect(self.generated)g.signals.error.connect(print)  # Print errors to console.self.threadpool.start(g)def generated(self, outfile):self.generate_btn.setDisabled(False)try:os.startfile(outfile)except Exception:# If startfile not available, show dialog.QMessageBox.information(self, "Finished", "PDF has been generated")app = QApplication([])
w = Window()
w.show()
app.exec_()

从CSV文件生成

在上面的示例中,您需要输入数据以手动填写。如果您没有大量的PDF生成,这很好,但是如果您有一个完整的CSV文件,可以生成报告的数据,那么就没那么有趣了。在下面的示例中,我们没有向用户显示表单字段列表,而是要求提供可从中生成PDF的源CSV文件 —— 文件中的每一行都使用文件中的数据生成单独的PDF文件。

from PyQt5.QtWidgets import QPushButton, QLineEdit, QApplication, QFormLayout, QWidget, QTextEdit, QMessageBox, QSpinBox, QFileDialog
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlotfrom reportlab.pdfgen.canvas import Canvasimport os, csvimport textwrap
from datetime import datetimefrom pdfrw import PdfReader
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerlclass WorkerSignals(QObject):"""Defines the signals available from a running worker thread."""error = pyqtSignal(str)finished = pyqtSignal()class Generator(QRunnable):"""Worker threadInherits from QRunnable to handle worker thread setup, signalsand wrap-up.:param data: The data to add to the PDF for generating."""def __init__(self, data):super().__init__()self.data = dataself.signals = WorkerSignals()@pyqtSlot()def run(self):try:filename, _ = os.path.splitext(self.data['sourcefile'])folder = os.path.dirname(self.data['sourcefile'])template = PdfReader("template.pdf", decompress=False).pages[0]template_obj = pagexobj(template)with open(self.data['sourcefile'], 'r', newline='') as f:reader = csv.DictReader(f)for n, row in enumerate(reader, 1):fn = f'{filename}-{n}.pdf'outfile = os.path.join(folder, fn)canvas = Canvas(outfile)xobj_name = makerl(canvas, template_obj)canvas.doForm(xobj_name)ystart = 443# Prepared bycanvas.drawString(170, ystart, row.get('name', ''))# Date: Todays datetoday = datetime.today()canvas.drawString(410, ystart, today.strftime('%F'))# Device/Program Typecanvas.drawString(230, ystart-28, row.get('program_type', ''))# Product codecanvas.drawString(175, ystart-(2*28), row.get('product_code', ''))# Customercanvas.drawString(315, ystart-(2*28), row.get('customer', ''))# Vendorcanvas.drawString(145, ystart-(3*28), row.get('vendor', ''))ystart = 250# Program Languagecanvas.drawString(210, ystart, "Python")canvas.drawString(430, ystart, row.get('n_errors', ''))comments = row.get('comments', '').replace('\n', ' ')if comments:lines = textwrap.wrap(comments, width=65) # 45first_line = lines[0]remainder = ' '.join(lines[1:])lines = textwrap.wrap(remainder, 75) # 55lines = lines[:4]  # max lines, not including the first.canvas.drawString(155, 223, first_line)for n, l in enumerate(lines, 1):canvas.drawString(80, 223 - (n*28), l)canvas.save()except Exception as e:self.signals.error.emit(str(e))returnself.signals.finished.emit()class Window(QWidget):def __init__(self):super().__init__()self.threadpool = QThreadPool()self.sourcefile = QLineEdit()self.sourcefile.setDisabled(True)  # must use the file finder to select a valid file.self.file_select = QPushButton("Select CSV...")self.file_select.pressed.connect(self.choose_csv_file)self.generate_btn = QPushButton("Generate PDF")self.generate_btn.pressed.connect(self.generate)layout = QFormLayout()layout.addRow(self.sourcefile, self.file_select)layout.addRow(self.generate_btn)self.setLayout(layout)def choose_csv_file(self):filename, _ = QFileDialog.getOpenFileName(self, "Select a file", filter="CSV files (*.csv)")if filename:self.sourcefile.setText(filename)def generate(self):if not self.sourcefile.text():return  # If the field is empty, ignore.self.generate_btn.setDisabled(True)data = {'sourcefile': self.sourcefile.text(),}g = Generator(data)g.signals.finished.connect(self.generated)g.signals.error.connect(print)  # Print errors to console.self.threadpool.start(g)def generated(self):self.generate_btn.setDisabled(False)QMessageBox.information(self, "Finished", "PDFs have been generated")app = QApplication([])
w = Window()
w.show()
app.exec()

您可以使用template.pdf和此示例CSV文件运行此应用,以生成一些TPS报告。

注意事项:

  • 现在我们生成了多个文件,完成后打开它们并没有多大意义。取而代之的是,我们始终只显示一次“完成”消息。信号file_saved_as已重命名为finished,并且由于不再使用文件名str,我们将其删除。

  • 用于获取文件名的QLineEdit已禁用,因此无法直接进行编辑:设置源CSV文件的唯一方法是直接选择文件,确保已在其中。

  • 我们基于导入文件名和当前行号自动生成输出文件名。文件名取自输入CSV:CSV文件名为tps.csv,文件名为tps-1.pdftps-2.pdf等。文件被写到源CSV所在的文件夹中。

  • 由于某些行/文件可能会漏掉必填字段,因此我们在行字典上使用.get()并使用默认的空字符串。

可能的改进

如果您想改进此代码,可以尝试以下方法

  • 使模板和输出文件位置可配置 —— 使用Qt文件对话框

  • 从文件和模板(JSON)一起加载字段位置,因此您可以将同一表单用于多个模板

  • 使字段可配置-这非常棘手,但是您可以为特定类型(strdatetimeint等)分配特定的小部件

更多阅读

5 分钟快速上手 pytest 测试框架

5分钟掌握 Python 随机爬山算法

5分钟快速掌握 Adam 优化算法

特别推荐

点击下方阅读原文加入社区会员

PDF 报告生成器:用 reportlab 和 pdfrw 生成自定义 PDF 报告相关推荐

  1. java jsp生成pdf_如何使用jsp、servlet输出iText生成的pdf

    使用itext生成pdf 如果每次都在服务端生成一个PDF文件给用户,不仅麻烦,而且浪费服务器资源,最好的方法就是以二进制流的形式输送到客户端. 1)JSP输出: response.setConten ...

  2. 自动生成课堂分析报告写后感想

    学习目标: 1.国内外达到的水平.存在的主要问题 2.根据和意义 怎么找: 1.找不到相关论文 扩大范围 上谷歌学术,百度学术,搜索自动生成课堂分析报告,这类很少,那就自动生成报告,如果还是没有就自动 ...

  3. 比较工具导出html比较结果,使用Beyond Compare如何生成文件比较报告

    当我们在使用Beyond Compare软件比较文件夹或者文件时,可以巧用软件强大的过滤功能,找出感兴趣的差异,合并变化,然后同步文件,并生成报告.其中,生成文件比较报告可以将文件差异部分直观的展示出 ...

  4. 两个PDF比较标出差异_轻松搞定PDF格式转换

    PDF是我们最常用的文件类型,它是由Adobe公司发明的文件格式,是 Portable Document Format的缩写,意为"便携文档格式".由于PDF的格式稳定,无论是在安 ...

  5. 用一个程序生成另一个程序_还有另一个报告生成器?

    用一个程序生成另一个程序 如果您具有业务应用程序开发的经验,那么很可能会遇到要求该应用程序具有灵活的报告机制的需求. 我工作的公司主要专注于开发业务解决方案,而报告是必不可少的,实际上,它必须包含我们 ...

  6. 还有另一个报告生成器?

    如果您具有业务应用程序开发的经验,那么很可能会遇到要求该应用程序具有灵活的报告机制的需求. 我工作的公司主要专注于开发业务解决方案,而报告是必不可少的,的确,它必须包含我们开发的所有企业系统的方面. ...

  7. Big Faceless Java Pdf报表生成器

    Report Generator 建立在 PDF 库之上,可将 XML 转换为 PDF,是生成复杂.多页报表的绝佳方式.现在,您可使用 JSP.ASP 或类似技术来创建动态 PDF 报表,与 HTML ...

  8. Big Faceless Java Pdf报表生成器控件介绍

    Report Generator 建立在 PDF 库之上,可将 XML 转换为 PDF,是生成复杂.多页报表的绝佳方式.现在,您可使用 JSP.ASP 或类似技术来创建动态 PDF 报表控件,与 HT ...

  9. python处理pdf 层_Python处理PDF及生成多层PDF实例代码

    Python提供了众多的PDF支持库,本文是在Python3环境下,试用了两个库来完成PDF的生成的功能.PyPDF对于读取PDF支持较好,但是没找到生成多层PDF的方法.Reportlab看起来更成 ...

  10. 【2.5万字】详解 Python-docx 自动生成word图文报告

    目录 推荐:[python自动化办公--python操作Excel.Word.PDF集合大全](https://blog.csdn.net/weixin_41261833/article/detail ...

最新文章

  1. java 锁降级 知乎_HotSpot VM重量级锁降级机制的实现原理
  2. 前驱、后驱和四驱,究竟哪个好?
  3. 大闹天竺里的机器人_在《大闹天竺》中哪一位演员是你喜欢的
  4. 哪些集合不能使用迭代器_Rust能力养成之(6):集合体与迭代器
  5. python发送微信消息_python 发送QQ或者微信消息
  6. windows执行命令来运行loadrunner录制好的脚本(收藏)
  7. SVN替换图标后依然显示老图标的BUG
  8. 基于python实现的电影推荐系统
  9. Linux系统如何安装PDF编辑器,在Ubuntu中编辑PDF文件的5种方法
  10. FLTK Fl_File_Chooser的使用
  11. 易语言html截图,易语言如何指定区域截图;易语言怎么才能全屏截图
  12. 【视频】离职创业感悟
  13. layui define 的使用
  14. 超实用,一口气学会 Centos/Docker/Nginx/Node/Jenkins 等基础操作
  15. Python检查文件内容是否有变动
  16. 云主机服务比价与预测系统开发心得--第一周(1)--git技术
  17. 微信商城后台服务器在那儿里,第3讲 微信商城云服务器后台创建
  18. 杭州计算机职称考试培训,杭州全国职称英语等级考试强化培训班(综合类)
  19. SVN客户端安装及使用说明
  20. 定位及元素的显示和隐藏

热门文章

  1. 红蜘蛛多媒体网络教室v7.2版一款网络教学的软件
  2. 基于51单片机的双机通信系统设计protues仿真
  3. 《王道计算机考研》:数据链路层
  4. 网络协议TCP/IP、IPX/SPX、NETBEUI简介
  5. 【QT】QT网络编程简介
  6. css样式基础库,WeUI基础样式库
  7. 内嵌网页 UniWebView 3 的使用
  8. 数字集成电路设计之加法器
  9. c++rs法计算hurst指数_计算机组成原理与接口技术
  10. 移动通信原理、技术与系统——概述