uTools-Manuals/docs/PyQt5/自定义组件.html
2019-05-07 01:01:38 +08:00

191 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<h1 id="自定义控件">自定义控件</h1>
<p>PyQt5有丰富的组件但是肯定满足不了所有开发者的所有需求PyQt5只提供了基本的组件像按钮文本滑块等。如果你还需要其他的模块应该尝试自己去自定义一些。</p>
<p>自定义组件使用绘画工具创建,有两个基本方式:根据已有的创建或改进;通过自己绘图创建。</p>
<h2 id="burning-widget">Burning widget</h2>
<p>这个组件我们会在NeroK3B或者其他CD/DVD烧录软件中见到。</p>
<div class="sourceCode" id="cb1"><pre><code class="language-python"><a class="sourceLine" id="cb1-1" data-line-number="1"><span class="co">#!/usr/bin/python3</span></a>
<a class="sourceLine" id="cb1-2" data-line-number="2"><span class="co"># -*- coding: utf-8 -*-</span></a>
<a class="sourceLine" id="cb1-3" data-line-number="3"></a>
<a class="sourceLine" id="cb1-4" data-line-number="4"><span class="co">&quot;&quot;&quot;</span></a>
<a class="sourceLine" id="cb1-5" data-line-number="5"><span class="co">ZetCode PyQt5 tutorial </span></a>
<a class="sourceLine" id="cb1-6" data-line-number="6"></a>
<a class="sourceLine" id="cb1-7" data-line-number="7"><span class="co">In this example, we create a custom widget.</span></a>
<a class="sourceLine" id="cb1-8" data-line-number="8"></a>
<a class="sourceLine" id="cb1-9" data-line-number="9"><span class="co">Author: Jan Bodnar</span></a>
<a class="sourceLine" id="cb1-10" data-line-number="10"><span class="co">Website: zetcode.com </span></a>
<a class="sourceLine" id="cb1-11" data-line-number="11"><span class="co">Last edited: August 2017</span></a>
<a class="sourceLine" id="cb1-12" data-line-number="12"><span class="co">&quot;&quot;&quot;</span></a>
<a class="sourceLine" id="cb1-13" data-line-number="13"></a>
<a class="sourceLine" id="cb1-14" data-line-number="14"><span class="im">from</span> PyQt5.QtWidgets <span class="im">import</span> (QWidget, QSlider, QApplication, </a>
<a class="sourceLine" id="cb1-15" data-line-number="15"> QHBoxLayout, QVBoxLayout)</a>
<a class="sourceLine" id="cb1-16" data-line-number="16"><span class="im">from</span> PyQt5.QtCore <span class="im">import</span> QObject, Qt, pyqtSignal</a>
<a class="sourceLine" id="cb1-17" data-line-number="17"><span class="im">from</span> PyQt5.QtGui <span class="im">import</span> QPainter, QFont, QColor, QPen</a>
<a class="sourceLine" id="cb1-18" data-line-number="18"><span class="im">import</span> sys</a>
<a class="sourceLine" id="cb1-19" data-line-number="19"></a>
<a class="sourceLine" id="cb1-20" data-line-number="20"><span class="kw">class</span> Communicate(QObject):</a>
<a class="sourceLine" id="cb1-21" data-line-number="21"> </a>
<a class="sourceLine" id="cb1-22" data-line-number="22"> updateBW <span class="op">=</span> pyqtSignal(<span class="bu">int</span>)</a>
<a class="sourceLine" id="cb1-23" data-line-number="23"></a>
<a class="sourceLine" id="cb1-24" data-line-number="24"></a>
<a class="sourceLine" id="cb1-25" data-line-number="25"><span class="kw">class</span> BurningWidget(QWidget):</a>
<a class="sourceLine" id="cb1-26" data-line-number="26"> </a>
<a class="sourceLine" id="cb1-27" data-line-number="27"> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>): </a>
<a class="sourceLine" id="cb1-28" data-line-number="28"> <span class="bu">super</span>().<span class="fu">__init__</span>()</a>
<a class="sourceLine" id="cb1-29" data-line-number="29"> </a>
<a class="sourceLine" id="cb1-30" data-line-number="30"> <span class="va">self</span>.initUI()</a>
<a class="sourceLine" id="cb1-31" data-line-number="31"> </a>
<a class="sourceLine" id="cb1-32" data-line-number="32"> </a>
<a class="sourceLine" id="cb1-33" data-line-number="33"> <span class="kw">def</span> initUI(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-34" data-line-number="34"> </a>
<a class="sourceLine" id="cb1-35" data-line-number="35"> <span class="va">self</span>.setMinimumSize(<span class="dv">1</span>, <span class="dv">30</span>)</a>
<a class="sourceLine" id="cb1-36" data-line-number="36"> <span class="va">self</span>.value <span class="op">=</span> <span class="dv">75</span></a>
<a class="sourceLine" id="cb1-37" data-line-number="37"> <span class="va">self</span>.num <span class="op">=</span> [<span class="dv">75</span>, <span class="dv">150</span>, <span class="dv">225</span>, <span class="dv">300</span>, <span class="dv">375</span>, <span class="dv">450</span>, <span class="dv">525</span>, <span class="dv">600</span>, <span class="dv">675</span>]</a>
<a class="sourceLine" id="cb1-38" data-line-number="38"></a>
<a class="sourceLine" id="cb1-39" data-line-number="39"></a>
<a class="sourceLine" id="cb1-40" data-line-number="40"> <span class="kw">def</span> setValue(<span class="va">self</span>, value):</a>
<a class="sourceLine" id="cb1-41" data-line-number="41"></a>
<a class="sourceLine" id="cb1-42" data-line-number="42"> <span class="va">self</span>.value <span class="op">=</span> value</a>
<a class="sourceLine" id="cb1-43" data-line-number="43"></a>
<a class="sourceLine" id="cb1-44" data-line-number="44"></a>
<a class="sourceLine" id="cb1-45" data-line-number="45"> <span class="kw">def</span> paintEvent(<span class="va">self</span>, e):</a>
<a class="sourceLine" id="cb1-46" data-line-number="46"> </a>
<a class="sourceLine" id="cb1-47" data-line-number="47"> qp <span class="op">=</span> QPainter()</a>
<a class="sourceLine" id="cb1-48" data-line-number="48"> qp.begin(<span class="va">self</span>)</a>
<a class="sourceLine" id="cb1-49" data-line-number="49"> <span class="va">self</span>.drawWidget(qp)</a>
<a class="sourceLine" id="cb1-50" data-line-number="50"> qp.end()</a>
<a class="sourceLine" id="cb1-51" data-line-number="51"> </a>
<a class="sourceLine" id="cb1-52" data-line-number="52"> </a>
<a class="sourceLine" id="cb1-53" data-line-number="53"> <span class="kw">def</span> drawWidget(<span class="va">self</span>, qp):</a>
<a class="sourceLine" id="cb1-54" data-line-number="54"> </a>
<a class="sourceLine" id="cb1-55" data-line-number="55"> MAX_CAPACITY <span class="op">=</span> <span class="dv">700</span></a>
<a class="sourceLine" id="cb1-56" data-line-number="56"> OVER_CAPACITY <span class="op">=</span> <span class="dv">750</span></a>
<a class="sourceLine" id="cb1-57" data-line-number="57"> </a>
<a class="sourceLine" id="cb1-58" data-line-number="58"> font <span class="op">=</span> QFont(<span class="st">&#39;Serif&#39;</span>, <span class="dv">7</span>, QFont.Light)</a>
<a class="sourceLine" id="cb1-59" data-line-number="59"> qp.setFont(font)</a>
<a class="sourceLine" id="cb1-60" data-line-number="60"></a>
<a class="sourceLine" id="cb1-61" data-line-number="61"> size <span class="op">=</span> <span class="va">self</span>.size()</a>
<a class="sourceLine" id="cb1-62" data-line-number="62"> w <span class="op">=</span> size.width()</a>
<a class="sourceLine" id="cb1-63" data-line-number="63"> h <span class="op">=</span> size.height()</a>
<a class="sourceLine" id="cb1-64" data-line-number="64"></a>
<a class="sourceLine" id="cb1-65" data-line-number="65"> step <span class="op">=</span> <span class="bu">int</span>(<span class="bu">round</span>(w <span class="op">/</span> <span class="dv">10</span>))</a>
<a class="sourceLine" id="cb1-66" data-line-number="66"></a>
<a class="sourceLine" id="cb1-67" data-line-number="67"></a>
<a class="sourceLine" id="cb1-68" data-line-number="68"> till <span class="op">=</span> <span class="bu">int</span>(((w <span class="op">/</span> OVER_CAPACITY) <span class="op">*</span> <span class="va">self</span>.value))</a>
<a class="sourceLine" id="cb1-69" data-line-number="69"> full <span class="op">=</span> <span class="bu">int</span>(((w <span class="op">/</span> OVER_CAPACITY) <span class="op">*</span> MAX_CAPACITY))</a>
<a class="sourceLine" id="cb1-70" data-line-number="70"></a>
<a class="sourceLine" id="cb1-71" data-line-number="71"> <span class="cf">if</span> <span class="va">self</span>.value <span class="op">&gt;=</span> MAX_CAPACITY:</a>
<a class="sourceLine" id="cb1-72" data-line-number="72"> </a>
<a class="sourceLine" id="cb1-73" data-line-number="73"> qp.setPen(QColor(<span class="dv">255</span>, <span class="dv">255</span>, <span class="dv">255</span>))</a>
<a class="sourceLine" id="cb1-74" data-line-number="74"> qp.setBrush(QColor(<span class="dv">255</span>, <span class="dv">255</span>, <span class="dv">184</span>))</a>
<a class="sourceLine" id="cb1-75" data-line-number="75"> qp.drawRect(<span class="dv">0</span>, <span class="dv">0</span>, full, h)</a>
<a class="sourceLine" id="cb1-76" data-line-number="76"> qp.setPen(QColor(<span class="dv">255</span>, <span class="dv">175</span>, <span class="dv">175</span>))</a>
<a class="sourceLine" id="cb1-77" data-line-number="77"> qp.setBrush(QColor(<span class="dv">255</span>, <span class="dv">175</span>, <span class="dv">175</span>))</a>
<a class="sourceLine" id="cb1-78" data-line-number="78"> qp.drawRect(full, <span class="dv">0</span>, till<span class="op">-</span>full, h)</a>
<a class="sourceLine" id="cb1-79" data-line-number="79"> </a>
<a class="sourceLine" id="cb1-80" data-line-number="80"> <span class="cf">else</span>:</a>
<a class="sourceLine" id="cb1-81" data-line-number="81"> </a>
<a class="sourceLine" id="cb1-82" data-line-number="82"> qp.setPen(QColor(<span class="dv">255</span>, <span class="dv">255</span>, <span class="dv">255</span>))</a>
<a class="sourceLine" id="cb1-83" data-line-number="83"> qp.setBrush(QColor(<span class="dv">255</span>, <span class="dv">255</span>, <span class="dv">184</span>))</a>
<a class="sourceLine" id="cb1-84" data-line-number="84"> qp.drawRect(<span class="dv">0</span>, <span class="dv">0</span>, till, h)</a>
<a class="sourceLine" id="cb1-85" data-line-number="85"></a>
<a class="sourceLine" id="cb1-86" data-line-number="86"></a>
<a class="sourceLine" id="cb1-87" data-line-number="87"> pen <span class="op">=</span> QPen(QColor(<span class="dv">20</span>, <span class="dv">20</span>, <span class="dv">20</span>), <span class="dv">1</span>, </a>
<a class="sourceLine" id="cb1-88" data-line-number="88"> Qt.SolidLine)</a>
<a class="sourceLine" id="cb1-89" data-line-number="89"> </a>
<a class="sourceLine" id="cb1-90" data-line-number="90"> qp.setPen(pen)</a>
<a class="sourceLine" id="cb1-91" data-line-number="91"> qp.setBrush(Qt.NoBrush)</a>
<a class="sourceLine" id="cb1-92" data-line-number="92"> qp.drawRect(<span class="dv">0</span>, <span class="dv">0</span>, w<span class="dv">-1</span>, h<span class="dv">-1</span>)</a>
<a class="sourceLine" id="cb1-93" data-line-number="93"></a>
<a class="sourceLine" id="cb1-94" data-line-number="94"> j <span class="op">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb1-95" data-line-number="95"></a>
<a class="sourceLine" id="cb1-96" data-line-number="96"> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(step, <span class="dv">10</span><span class="op">*</span>step, step):</a>
<a class="sourceLine" id="cb1-97" data-line-number="97"> </a>
<a class="sourceLine" id="cb1-98" data-line-number="98"> qp.drawLine(i, <span class="dv">0</span>, i, <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb1-99" data-line-number="99"> metrics <span class="op">=</span> qp.fontMetrics()</a>
<a class="sourceLine" id="cb1-100" data-line-number="100"> fw <span class="op">=</span> metrics.width(<span class="bu">str</span>(<span class="va">self</span>.num[j]))</a>
<a class="sourceLine" id="cb1-101" data-line-number="101"> qp.drawText(i<span class="op">-</span>fw<span class="op">/</span><span class="dv">2</span>, h<span class="op">/</span><span class="dv">2</span>, <span class="bu">str</span>(<span class="va">self</span>.num[j]))</a>
<a class="sourceLine" id="cb1-102" data-line-number="102"> j <span class="op">=</span> j <span class="op">+</span> <span class="dv">1</span></a>
<a class="sourceLine" id="cb1-103" data-line-number="103"> </a>
<a class="sourceLine" id="cb1-104" data-line-number="104"></a>
<a class="sourceLine" id="cb1-105" data-line-number="105"><span class="kw">class</span> Example(QWidget):</a>
<a class="sourceLine" id="cb1-106" data-line-number="106"> </a>
<a class="sourceLine" id="cb1-107" data-line-number="107"> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>):</a>
<a class="sourceLine" id="cb1-108" data-line-number="108"> <span class="bu">super</span>().<span class="fu">__init__</span>()</a>
<a class="sourceLine" id="cb1-109" data-line-number="109"> </a>
<a class="sourceLine" id="cb1-110" data-line-number="110"> <span class="va">self</span>.initUI()</a>
<a class="sourceLine" id="cb1-111" data-line-number="111"> </a>
<a class="sourceLine" id="cb1-112" data-line-number="112"> </a>
<a class="sourceLine" id="cb1-113" data-line-number="113"> <span class="kw">def</span> initUI(<span class="va">self</span>): </a>
<a class="sourceLine" id="cb1-114" data-line-number="114"> </a>
<a class="sourceLine" id="cb1-115" data-line-number="115"> OVER_CAPACITY <span class="op">=</span> <span class="dv">750</span></a>
<a class="sourceLine" id="cb1-116" data-line-number="116"></a>
<a class="sourceLine" id="cb1-117" data-line-number="117"> sld <span class="op">=</span> QSlider(Qt.Horizontal, <span class="va">self</span>)</a>
<a class="sourceLine" id="cb1-118" data-line-number="118"> sld.setFocusPolicy(Qt.NoFocus)</a>
<a class="sourceLine" id="cb1-119" data-line-number="119"> sld.setRange(<span class="dv">1</span>, OVER_CAPACITY)</a>
<a class="sourceLine" id="cb1-120" data-line-number="120"> sld.setValue(<span class="dv">75</span>)</a>
<a class="sourceLine" id="cb1-121" data-line-number="121"> sld.setGeometry(<span class="dv">30</span>, <span class="dv">40</span>, <span class="dv">150</span>, <span class="dv">30</span>)</a>
<a class="sourceLine" id="cb1-122" data-line-number="122"></a>
<a class="sourceLine" id="cb1-123" data-line-number="123"> <span class="va">self</span>.c <span class="op">=</span> Communicate() </a>
<a class="sourceLine" id="cb1-124" data-line-number="124"> <span class="va">self</span>.wid <span class="op">=</span> BurningWidget()</a>
<a class="sourceLine" id="cb1-125" data-line-number="125"> <span class="va">self</span>.c.updateBW[<span class="bu">int</span>].<span class="ex">connect</span>(<span class="va">self</span>.wid.setValue)</a>
<a class="sourceLine" id="cb1-126" data-line-number="126"></a>
<a class="sourceLine" id="cb1-127" data-line-number="127"> sld.valueChanged[<span class="bu">int</span>].<span class="ex">connect</span>(<span class="va">self</span>.changeValue)</a>
<a class="sourceLine" id="cb1-128" data-line-number="128"> hbox <span class="op">=</span> QHBoxLayout()</a>
<a class="sourceLine" id="cb1-129" data-line-number="129"> hbox.addWidget(<span class="va">self</span>.wid)</a>
<a class="sourceLine" id="cb1-130" data-line-number="130"> vbox <span class="op">=</span> QVBoxLayout()</a>
<a class="sourceLine" id="cb1-131" data-line-number="131"> vbox.addStretch(<span class="dv">1</span>)</a>
<a class="sourceLine" id="cb1-132" data-line-number="132"> vbox.addLayout(hbox)</a>
<a class="sourceLine" id="cb1-133" data-line-number="133"> <span class="va">self</span>.setLayout(vbox)</a>
<a class="sourceLine" id="cb1-134" data-line-number="134"> </a>
<a class="sourceLine" id="cb1-135" data-line-number="135"> <span class="va">self</span>.setGeometry(<span class="dv">300</span>, <span class="dv">300</span>, <span class="dv">390</span>, <span class="dv">210</span>)</a>
<a class="sourceLine" id="cb1-136" data-line-number="136"> <span class="va">self</span>.setWindowTitle(<span class="st">&#39;Burning widget&#39;</span>)</a>
<a class="sourceLine" id="cb1-137" data-line-number="137"> <span class="va">self</span>.show()</a>
<a class="sourceLine" id="cb1-138" data-line-number="138"> </a>
<a class="sourceLine" id="cb1-139" data-line-number="139"> </a>
<a class="sourceLine" id="cb1-140" data-line-number="140"> <span class="kw">def</span> changeValue(<span class="va">self</span>, value):</a>
<a class="sourceLine" id="cb1-141" data-line-number="141"> </a>
<a class="sourceLine" id="cb1-142" data-line-number="142"> <span class="va">self</span>.c.updateBW.emit(value) </a>
<a class="sourceLine" id="cb1-143" data-line-number="143"> <span class="va">self</span>.wid.repaint()</a>
<a class="sourceLine" id="cb1-144" data-line-number="144"> </a>
<a class="sourceLine" id="cb1-145" data-line-number="145"> </a>
<a class="sourceLine" id="cb1-146" data-line-number="146"><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&#39;__main__&#39;</span>:</a>
<a class="sourceLine" id="cb1-147" data-line-number="147"> </a>
<a class="sourceLine" id="cb1-148" data-line-number="148"> app <span class="op">=</span> QApplication(sys.argv)</a>
<a class="sourceLine" id="cb1-149" data-line-number="149"> ex <span class="op">=</span> Example()</a>
<a class="sourceLine" id="cb1-150" data-line-number="150"> sys.exit(app.exec_())</a></code></pre></div>
<p>本例中,我们使用了<code>QSlider</code>和一个自定义组件由进度条控制。显示的有物体也就是CD/DVD的总容量和剩余容量。进度条的范围是1~750。如果值达到了700OVER_CAPACITY就显示为红色代表了烧毁了的意思。</p>
<p>烧录组件在窗口的底部,这个组件是用<code>QHBoxLayout</code><code>QVBoxLayout</code>组成的。</p>
<div class="sourceCode" id="cb2"><pre><code class="language-python"><a class="sourceLine" id="cb2-1" data-line-number="1"><span class="kw">class</span> BurningWidget(QWidget):</a>
<a class="sourceLine" id="cb2-2" data-line-number="2"> </a>
<a class="sourceLine" id="cb2-3" data-line-number="3"> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>): </a>
<a class="sourceLine" id="cb2-4" data-line-number="4"> <span class="bu">super</span>().<span class="fu">__init__</span>() </a></code></pre></div>
<p>基于<code>QWidget</code>组件。</p>
<pre><code class="language-python">self.setMinimumSize(1, 30)</code></pre>
<p>修改组件进度条的高度,默认的有点小。</p>
<pre><code class="language-python">font = QFont(&#39;Serif&#39;, 7, QFont.Light)
qp.setFont(font)</code></pre>
<p>使用比默认更小一点的字体,这样更配。</p>
<pre><code class="language-python">size = self.size()
w = size.width()
h = size.height()
step = int(round(w / 10.0))
till = int(((w / 750.0) * self.value))
full = int(((w / 750.0) * 700))</code></pre>
<p>动态的渲染组件,随着窗口的大小而变化,这就是我们计算窗口大小的原因。最后一个参数决定了组件的最大范围,进度条的值是由窗口大小按比例计算出来的。最大值的地方填充的是红色。注意这里使用的是浮点数,能提高计算和渲染的精度。</p>
<p>绘画由三部分组成,黄色或红色区域和黄色矩形,然后是分割线,最后是添上代表容量的数字。</p>
<pre><code class="language-python">metrics = qp.fontMetrics()
fw = metrics.width(str(self.num[j]))
qp.drawText(i-fw/2, h/2, str(self.num[j]))</code></pre>
<p>这里使用字体去渲染文本。必须要知道文本的宽度,这样才能让文本的中间点正好落在竖线上。</p>
<pre><code class="language-python">def changeValue(self, value):
self.c.updateBW.emit(value)
self.wid.repaint()</code></pre>
<p>拖动滑块的时候,调用了<code>changeValue()</code>方法。这个方法内部我们自定义了一个可以传参的updateBW信号。参数就是滑块的当前位置。这个数值之后还用来于Burning组件然后重新渲染Burning组件。</p>
<figure>
<img class="whitelist" src="docs/PyQt5/images/10-burning.png" alt="burning widget" />
</figure>