mirror of
https://github.com/fofolee/uTools-Manuals.git
synced 2025-06-10 16:48:17 +08:00
191 lines
20 KiB
HTML
191 lines
20 KiB
HTML
<h1 id="自定义控件">自定义控件</h1>
|
||
<p>PyQt5有丰富的组件,但是肯定满足不了所有开发者的所有需求,PyQt5只提供了基本的组件,像按钮,文本,滑块等。如果你还需要其他的模块,应该尝试自己去自定义一些。</p>
|
||
<p>自定义组件使用绘画工具创建,有两个基本方式:根据已有的创建或改进;通过自己绘图创建。</p>
|
||
<h2 id="burning-widget">Burning widget</h2>
|
||
<p>这个组件我们会在Nero,K3B,或者其他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">"""</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">"""</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">'Serif'</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">>=</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">'Burning widget'</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">'__main__'</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。如果值达到了700(OVER_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('Serif', 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>
|