在《PyQt5中使用QWebChannel和內(nèi)嵌網(wǎng)頁(yè)進(jìn)行js交互》一文中,我記錄了如何使用QWebchannel與內(nèi)嵌網(wǎng)頁(yè)進(jìn)行js交互,其根本目標(biāo)在于使用Qt5調(diào)起打印機(jī)服務(wù)氮块。在這篇文章中我將介紹一下具體使用Qprinter打印超市熱敏小票的過程疆瑰。
>>>原文地址
參考內(nèi)容:
Qt5官方文檔
《pyqt5的 QPrinter 使用模板》 by 一心獅
本文包含以下內(nèi)容:
1.使用html進(jìn)行熱敏打印的方法
2.分析存在的問題
3.提出另一種打印方法來解決問題
使用html進(jìn)行熱敏打印
python端代碼
# -*- coding:utf-8 -*-
# webprint.py
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QObject, pyqtSlot, QUrl, QSizeF
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtPrintSupport import QPrinter, QPrinterInfo
from PyQt5.QtGui import QTextDocument
import sys
class Printer:
def __init__(self):
self.p = QPrinterInfo.defaultPrinter() # 獲取默認(rèn)打印機(jī)
self.print_device = QPrinter(self.p) # 指定打印所使用的裝置
def print(self, content):
# 設(shè)置打印內(nèi)容的寬度,否則打印內(nèi)容會(huì)變形
self.print_device.setPageSizeMM(QSizeF(110, 250))
d = QTextDocument() # 使用QTextDcument對(duì)html進(jìn)行解析
d.setDocumentMargin(0) # 將打印的邊距設(shè)為0
# 設(shè)置全局生效的默認(rèn)樣式
d.setDefaultStyleSheet('''
* {padding:0;margin: 0;}
h1 {font-size: 20px;}
h3 {font-size: 16px;}
.left {float: left;}
.right {float:right;}
.clearfix {clear: both;}
ul {list-style: none;}
.print_container {width: 250px;}
.section2 label {display: block;}
.section3 label {display: block;}
.section4 .total label {display: block;}
.section4 {border-bottom: 1px solid #DADADA;}
.section5 label {display: block;}
''')
d.setHtml(content) # 注入html內(nèi)容
d.print(self.print_device) # 調(diào)用打印機(jī)進(jìn)行打印
class Print(QObject):
def __init__(self):
super().__init__()
self.printer = Printer()
@pyqtSlot(str, result=str)
def print(self, content):
self.printer.print(content)
return
if __name__ == '__main__':
app = QApplication(sys.argv)
browser = QWebEngineView()
browser.setWindowTitle('使用PyQt5打印熱敏小票')
browser.resize(900, 600)
channel = QWebChannel()
printer = Print()
channel.registerObject('printer', printer)
browser.page().setWebChannel(channel)
url_string = "file:///python/print/webprint.html" # 內(nèi)置的網(wǎng)頁(yè)地址
browser.load(QUrl(url_string))
browser.show()
sys.exit(app.exec_())
網(wǎng)頁(yè)端代碼
<!-- webprint.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用PyQt5打印熱敏小票</title>
</head>
<style type="text/css">
* {padding:0;margin: 0;}
h1 {font-size: 20px;}
h3 {font-size: 16px;}
.left {float: left;}
.right {float:right;}
.clearfix {clear: both;}
ul {list-style: none;}
.print_container {width: 250px;}
.section2 label {display: block;}
.section3 label {display: block;}
.section4 .total label {display: block;}
.section4 {border-bottom: 1px solid #DADADA;}
.section5 label {display: block;}
</style>
<body>
<div id="capture">
<div class="print_container">
<h3>便利店</h3>
<span>***************************************</span>
<div class="section3">
<label>訂單號(hào):700001001201811161631123558</label>
<label>下單時(shí)間:2018-10-16 16:31:14</label>
<label>收銀員:王小明</label>
</div>
<span>***************************************</span>
<div class="section4">
<div style="border-bottom: 1px solid #DADADA;">
<table style="width: 100%;">
<thead>
<tr>
<td width="60%">品名</td>
<td width="20%">數(shù)量</td>
<td width="20%">金額</td>
</tr>
</thead>
<tbody>
<tr>
<td>今麥郎</td>
<td>1</td>
<td>100.00</td>
</tr>
</tbody>
</table>
</div>
<div class="total">
<label class="left">合 計(jì)</label>
<label class="right">100.00</label>
<div class="clearfix"></div>
<label class="left">收款金額</label>
<label class="right">100</label>
<div class="clearfix"></div>
<label class="left">找零金額</label>
<label class="right">0.00</label>
<div class="clearfix"></div>
</div>
<div style="text-align: right;">
<label>顧客已付款</label>
</div>
<span>***************************************</span>
</div>
<div class="section5">
<label>電話:</label>
</div>
<span>***************************************</span>
<div class="section5">
<label>歡迎光臨貌夕,謝謝惠顧炸枣!</label>
<label>便利店</label>
</div>
</div>
</div>
<div>
<button onclick="do_print()">進(jìn)行html打印</button>
</div>
<script src="qwebchannel.js" type="text/javascript"></script>
<script>
window.onload = function() {
new QWebChannel(qt.webChannelTransport, function (channel) {
window.printer = channel.objects.printer;
});
}
function do_print() {
if (printer !== null) {
var html = document.querySelector('#capture').innerHTML;
printer.print(html);
}
}
</script>
</body>
</html>
關(guān)于使用qwebchannel進(jìn)行js交互的內(nèi)容這里不再贅述虏等,請(qǐng)查閱本文開頭提到的文章弄唧。
問題分析
運(yùn)行上述代碼,我們可以成功地調(diào)起打印機(jī)服務(wù)霍衫。但是打印出來的內(nèi)容卻慘不忍睹候引,熱敏小票的左邊和頂部空出一大片空白,以至于打印出來的票據(jù)內(nèi)容丟失了大半敦跌!
為什么會(huì)這樣呢澄干?在代碼中我們已經(jīng)對(duì)QTextDocument進(jìn)行了setDocumentMargin設(shè)置,打印時(shí)卻依然有巨大的邊距峰髓。
一開始我以為是margin設(shè)置無效傻寂,后來查看了pyqt5的源碼以及在Google上搜索息尺,才得知QTextDocument強(qiáng)制左邊和頂部留白携兵。事實(shí)上默認(rèn)的margin已經(jīng)是0了。這樣一來使用QTextDocument進(jìn)行打印的計(jì)劃宣告破產(chǎn)搂誉,我不得不苦苦思索徐紧,在互聯(lián)網(wǎng)上胡搜一通,看看是否有人遇到相同的問題炭懊。
值得一提的是并级,熱心的(:p)簡(jiǎn)書網(wǎng)友一心獅,他向我提供了一種思路:
先在項(xiàng)目?jī)?nèi)侮腹,放置一個(gè)已經(jīng)排版好的excel文件嘲碧。然后用win32com,對(duì)這個(gè) execl文件父阻,進(jìn)行操作愈涩,如賦值,如打印
這確實(shí)是一個(gè)不錯(cuò)的方法加矛,遺憾的是對(duì)我來說不太適用履婉。超市熱敏小票的內(nèi)容不是固定長(zhǎng)度的,而且我打算用pyinstaller將所有代碼封裝成一個(gè)獨(dú)立的可執(zhí)行程序(exe)斟览,放置一個(gè)excel文件也不太方便毁腿。
后來在Stackoverflow我偶然的看見了一個(gè)同樣的問題,具體鏈接我忘了保存苛茂。下面只有一個(gè)回答已烤,答者粗略地提到一個(gè)解決方法——Qt5打印圖片不會(huì)留邊距,可以從這個(gè)角度著手妓羊,把要打印的內(nèi)容轉(zhuǎn)為圖片再打印草戈。這條曲線救國(guó)的思路真是太棒了!
使用圖片進(jìn)行熱敏打印
想要使用圖片打印侍瑟,首先就要把文字內(nèi)容轉(zhuǎn)成圖片才行唐片。幸好這世上已經(jīng)有人提供了簡(jiǎn)單方便的html轉(zhuǎn)圖片方案丙猬,而且是在網(wǎng)頁(yè)端進(jìn)行的!這個(gè)方案就是使用起來方便簡(jiǎn)單的html2canvas:
The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The screenshot is based on the DOM and as such may not be 100% accurate to the real representation as it does not make an actual screenshot, but builds the screenshot based on the information available on the page.
簡(jiǎn)而言之就是支持對(duì)html頁(yè)面的部分進(jìn)行“截屏”操作费韭。
使用方法極其簡(jiǎn)單:
html2canvas(document.querySelector("#capture")).then(canvas => {
document.body.appendChild(canvas)
});
轉(zhuǎn)好了圖片茧球,我們只需在python端對(duì)圖片數(shù)據(jù)流使用QPainter連接打印機(jī)進(jìn)行打印即可。
python端代碼
# -*- coding:utf-8 -*-
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QObject, pyqtSlot, QUrl
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtPrintSupport import QPrinter, QPrinterInfo
from PyQt5.QtGui import QPainter, QImage
import sys, base64
class Printer:
def __init__(self):
self.p = QPrinterInfo.defaultPrinter()
self.print_device = QPrinter(self.p)
def print_(self, data_url):
image_content = base64.b64decode(data_url) # 數(shù)據(jù)流base64解碼
image = QImage()
image.loadFromData(image_content) # 使用QImage構(gòu)造圖片
painter = QPainter(self.print_device) # 使用打印機(jī)作為繪制設(shè)備
painter.drawImage(0, 0, image) # 進(jìn)行繪制(即調(diào)起打印服務(wù))
painter.end() # 打印結(jié)束
class Print(QObject):
def __init__(self):
super().__init__()
self.printer = Printer()
@pyqtSlot(str, result=str)
def print_(self, data_url):
# 去除頭部標(biāo)識(shí)
self.printer.print_(data_url.replace('data:image/png;base64,', ''))
return
if __name__ == '__main__':
app = QApplication(sys.argv)
browser = QWebEngineView()
browser.setWindowTitle('使用PyQt5打印熱敏小票')
browser.resize(900, 600)
channel = QWebChannel()
printer = Print()
channel.registerObject('printer', printer)
browser.page().setWebChannel(channel)
url_string = "file:///python/print/webprint.html" # 內(nèi)置的網(wǎng)頁(yè)地址
browser.load(QUrl(url_string))
browser.show()
sys.exit(app.exec_())
網(wǎng)頁(yè)端代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用PyQt5打印熱敏小票</title>
</head>
<style type="text/css">
* {padding:0;margin: 0;}
h1 {font-size: 20px;}
h3 {font-size: 16px;}
.left {float: left;}
.right {float:right;}
.clearfix {clear: both;}
ul {list-style: none;}
.print_container {width: 250px;}
.section2 label {display: block;}
.section3 label {display: block;}
.section4 .total label {display: block;}
.section4 {border-bottom: 1px solid #DADADA;}
.section5 label {display: block;}
</style>
<body>
<div id="capture">
<div class="print_container">
<h3>便利店</h3>
<span>***************************************</span>
<div class="section3">
<label>訂單號(hào):700001001201811161631123558</label>
<label>下單時(shí)間:2018-10-16 16:31:14</label>
<label>收銀員:王小明</label>
</div>
<span>***************************************</span>
<div class="section4">
<div style="border-bottom: 1px solid #DADADA;">
<table style="width: 100%;">
<thead>
<tr>
<td width="60%">品名</td>
<td width="20%">數(shù)量</td>
<td width="20%">金額</td>
</tr>
</thead>
<tbody>
<tr>
<td>今麥郎</td>
<td>1</td>
<td>100.00</td>
</tr>
</tbody>
</table>
</div>
<div class="total">
<label class="left">合 計(jì)</label>
<label class="right">100.00</label>
<div class="clearfix"></div>
<label class="left">收款金額</label>
<label class="right">100</label>
<div class="clearfix"></div>
<label class="left">找零金額</label>
<label class="right">0.00</label>
<div class="clearfix"></div>
</div>
<div style="text-align: right;">
<label>顧客已付款</label>
</div>
<span>***************************************</span>
</div>
<div class="section5">
<label>電話:</label>
</div>
<span>***************************************</span>
<div class="section5">
<label>歡迎光臨星持,謝謝惠顧抢埋!</label>
<label>便利店</label>
</div>
</div>
</div>
<div>
<button onclick="do_print_()">進(jìn)行圖像打印</button>
</div>
<script src="qwebchannel.js" type="text/javascript"></script>
<script src="html2canvas.min.js" type="text/javascript"></script>
<script>
window.onload = function() {
new QWebChannel(qt.webChannelTransport, function (channel) {
window.printer = channel.objects.printer;
});
}
function do_print_() {
if (printer !== null) {
html2canvas(document.querySelector("#capture")).then(canvas => {
var data_url = canvas.toDataURL();
printer.print_(data_url);
});
}
}
</script>
</body>
</html>
運(yùn)行代碼,我們欣喜地發(fā)現(xiàn)督暂,熱敏小票的排版終于正常了揪垄!
事實(shí)上,無論是圖片打印逻翁,或者excel打印饥努,都是同樣的曲線救國(guó)思路。在得知第一種方法的情況下八回,我卻沒能想到第二種方法酷愧,看來我的聯(lián)想能力還有待鍛煉。
以上缠诅,就是我的解決歷程溶浴。
附:完整代碼(包含兩種打印方式)
python端代碼
# -*- coding:utf-8 -*-
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QObject, pyqtSlot, QUrl, QSizeF
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtPrintSupport import QPrinter, QPrinterInfo
from PyQt5.QtGui import QTextDocument, QPainter, QImage
import sys, base64
class Printer:
def __init__(self):
self.p = QPrinterInfo.defaultPrinter()
self.print_device = QPrinter(self.p)
def print(self, content):
self.print_device.setPageSizeMM(QSizeF(110, 250))
d = QTextDocument()
d.setDocumentMargin(0)
d.setDefaultStyleSheet('''
* {padding:0;margin: 0;}
h1 {font-size: 20px;}
h3 {font-size: 16px;}
.left {float: left;}
.right {float:right;}
.clearfix {clear: both;}
ul {list-style: none;}
.print_container {width: 250px;}
.section2 label {display: block;}
.section3 label {display: block;}
.section4 .total label {display: block;}
.section4 {border-bottom: 1px solid #DADADA;}
.section5 label {display: block;}
''')
d.setHtml(content)
d.print(self.print_device)
def print_(self, data_url):
image_content = base64.b64decode(data_url) # 數(shù)據(jù)流base64解碼
image = QImage()
image.loadFromData(image_content) # 使用QImage構(gòu)造圖片
painter = QPainter(self.print_device) # 使用打印機(jī)作為繪制設(shè)備
painter.drawImage(0, 0, image) # 進(jìn)行繪制(即調(diào)起打印服務(wù))
painter.end() # 打印結(jié)束
class Print(QObject):
def __init__(self):
super().__init__()
self.printer = Printer()
@pyqtSlot(str, result=str)
def print(self, content):
self.printer.print(content)
return
@pyqtSlot(str, result=str)
def print_(self, data_url):
# 去除頭部標(biāo)識(shí)
self.printer.print_(data_url.replace('data:image/png;base64,', ''))
return
if __name__ == '__main__':
app = QApplication(sys.argv)
browser = QWebEngineView()
browser.setWindowTitle('使用PyQt5打印熱敏小票')
browser.resize(900, 600)
channel = QWebChannel()
printer = Print()
channel.registerObject('printer', printer)
browser.page().setWebChannel(channel)
url_string = "file:///python/print/webprint.html" # 內(nèi)置的網(wǎng)頁(yè)地址
browser.load(QUrl(url_string))
browser.show()
sys.exit(app.exec_())
網(wǎng)頁(yè)端代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用PyQt5打印熱敏小票</title>
</head>
<style type="text/css">
* {padding:0;margin: 0;}
h1 {font-size: 20px;}
h3 {font-size: 16px;}
.left {float: left;}
.right {float:right;}
.clearfix {clear: both;}
ul {list-style: none;}
.print_container {width: 250px;}
.section2 label {display: block;}
.section3 label {display: block;}
.section4 .total label {display: block;}
.section4 {border-bottom: 1px solid #DADADA;}
.section5 label {display: block;}
</style>
<body>
<div id="capture">
<div class="print_container">
<h3>便利店</h3>
<span>***************************************</span>
<div class="section3">
<label>訂單號(hào):700001001201811161631123558</label>
<label>下單時(shí)間:2018-10-16 16:31:14</label>
<label>收銀員:王小明</label>
</div>
<span>***************************************</span>
<div class="section4">
<div style="border-bottom: 1px solid #DADADA;">
<table style="width: 100%;">
<thead>
<tr>
<td width="60%">品名</td>
<td width="20%">數(shù)量</td>
<td width="20%">金額</td>
</tr>
</thead>
<tbody>
<tr>
<td>今麥郎</td>
<td>1</td>
<td>100.00</td>
</tr>
</tbody>
</table>
</div>
<div class="total">
<label class="left">合 計(jì)</label>
<label class="right">100.00</label>
<div class="clearfix"></div>
<label class="left">收款金額</label>
<label class="right">100</label>
<div class="clearfix"></div>
<label class="left">找零金額</label>
<label class="right">0.00</label>
<div class="clearfix"></div>
</div>
<div style="text-align: right;">
<label>顧客已付款</label>
</div>
<span>***************************************</span>
</div>
<div class="section5">
<label>電話:</label>
</div>
<span>***************************************</span>
<div class="section5">
<label>歡迎光臨,謝謝惠顧管引!</label>
<label>便利店</label>
</div>
</div>
</div>
<div>
<button onclick="do_print()">進(jìn)行html打印</button>
<button onclick="do_print_()">進(jìn)行圖像打印</button>
</div>
<script src="qwebchannel.js" type="text/javascript"></script>
<script src="html2canvas.min.js" type="text/javascript"></script>
<script>
window.onload = function() {
new QWebChannel(qt.webChannelTransport, function (channel) {
window.printer = channel.objects.printer;
});
}
function do_print() {
if (printer !== null) {
var html = document.querySelector('#capture').innerHTML;
printer.print(html);
}
}
function do_print_() {
if (printer !== null) {
html2canvas(document.querySelector("#capture")).then(canvas => {
var data_url = canvas.toDataURL();
printer.print_(data_url);
});
}
}
</script>
</body>
</html>