V sedmém článku věnovaném tvorbě grafického uživatelského rozhraní v Pythonu s využitím frameworku PySide si ukážeme některé možnosti nabízené třídou QPainter při vytváření 2D grafiky použité v GUI.
V sedmém článku věnovaném tvorbě grafického uživatelského rozhraní v Pythonu s využitím frameworku PySide si ukážeme některé možnosti nabízené třídou QPainter při vytváření 2D grafiky použité v GUI.
1. Tvorba GUI v Pythonu s využitím frameworku PySide: grafické operace používající třídu QPainter
2. Základní operace podporované třídou QPainter
3. Praktický postup při vykreslování
4. První demonstrační příklad: použití metod QPainter.begin a QPainter.end
5. Použití „chytrého“ konstruktoru objektu typu QPainter
6. Druhý demonstrační příklad: použití „chytrého“ konstruktoru objektu typu QPainter
7. Modifikace barvy a šířky pera použitého při vykreslování
8. Názvy barev s paletě knihovny PySide
9. Třetí demonstrační příklad: modifikace barvy a šířky pera
10. Čtvrtý demonstrační příklad: použití antialiasingu při vykreslování
11. Čárkované a čerchované úsečky
12. Pátý demonstrační příklad: kresba čárkovaných a čerchovaných úseček
13. Použití štětců při kreslení vyplněných grafických objektů
14. Šestý demonstrační příklad: vzorník standardních štětců
15. Barva pozadí oblasti nevyplněné štětcem
16. Sedmý demonstrační příklad: specifikace barvy pozadí a použití standardních štětců
17. Repositář s demonstračními příklady
Na předchozí část seriálu o tvorbě grafického uživatelského rozhraní v Pythonu, v níž jsme si popsali základní vlastnosti tříd QImage, QPixmap a QBitmap, dnes navážeme. Ukážeme si totiž, jakým způsobem je možné vykreslovat základní geometrické (2D) tvary s využitím metod třídy QPainter. Tato třída totiž programátorům nabízí možnost vykreslení následujících tvarů a objektů:
Tvar/objekt | Metoda |
---|---|
bod | drawPoint() |
sada bodů | drawPoints() |
úsečka | drawLine() |
sada úseček | drawLines() |
obdélník | drawRect(), fillRect() |
sada obdélníků | drawRects() |
obdélník se zakulacenými rohy | drawRoundRect(), drawRoundedRect() |
polyčára (lomená úsečka) | drawPolyline() |
polygon | drawPolygon(), drawConvexPolygon() |
oblouk | drawArc() |
kruhová úseč | drawChord() |
kruhová výseč | drawPie() |
kružnice nebo elipsa | drawEllipse() |
obecná cesta | drawPath(), fillPath() |
text | drawText(), drawTextItem() |
rastrový obrázek | drawImage(), drawPicture(), drawPixmap(), … |
Podobně, jako je tomu například ve vektorovém formátu SVG, PostScriptu či v knihovně OpenVG, se i při použití QPainteru setkáme se třemi důležitými pojmy:
Dnes se seznámíme především s použitím per a štětců.
Samotný objekt QPainter provádí vykreslení na nějaké „plátno“, což může být buď přímo hardwarové zařízení (grafický subsystém), nebo rastrový obrázek. Podívejme se nyní na způsob vykreslení velmi jednoduché grafiky na rastrový obrázek s využitím možností nabízených třídou QPainter. Jeden z možných postupů je následující:
V praxi se jednotlivé body výše uvedeného postupu budou implementovat následujícím způsobem:
Nejprve vytvoříme rastrový obrázek, který bude představovat „plátno“, na které bude třída QPainter vykreslovat obrazce:
image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32)
Dále vytvoříme objekt typu QPainter:
qp = QtGui.QPainter()
Informujeme tento objekt o začátku změn (vykreslování) do instance třídy QImage:
qp.begin(self.image)
Provedeme vlastní vykreslování – nastavíme barvu pera a vykreslíme tímto perem diagonální úsečku (povšimněte si, že se nemusíme nijak starat o to, kdy zanikne platnost objektu typu QColor – některé jiné toolkity totiž vyžadují explicitní uvolnění prostředků):
qp.setPen(QtGui.QColor(64, 255, 64)) qp.drawLine(10, 10, MainWindow.IMAGE_WIDTH-10, MainWindow.IMAGE_HEIGHT-10)
Informujeme objekt o konci změn (vykreslování):
qp.end()
V předposledním kroku se provede konverze objektu typu QImage na QPixmap:
pixmap = QtGui.QPixmap.fromImage(image)
Někde v další části kódu již můžeme pixmapu bez problémů vložit na navěští a tím ji vykreslit (resp. přesněji řečeno zobrazit uživateli):
label = QtGui.QLabel("test") label.setPixmap(pixmap) self.setCentralWidget(label)
Obrázek 1: Úsečka vykreslená příkazy popsanými v této kapitole.
Výše uvedený postup pro vykreslení grafiky používající třídy QPainter a QImage je použit v dnešním prvním demonstračním příkladu, jehož úplný zdrojový kód vypadá následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() self.prepareImage() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareImage(self): # vytvoření instance třídy QImage self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32) # vytvoření objektu typu QPainter qp = QtGui.QPainter() # začátek změn qp.begin(self.image) # nastavení barvy kreslení qp.setPen(QtGui.QColor(64, 255, 64)) # vykreslení úsečky qp.drawLine(10, 10, MainWindow.IMAGE_WIDTH-10, MainWindow.IMAGE_HEIGHT-10) # konec změn qp.end() # vytvoření instance třídy QPixmap z objektu QImage self.pixmap = QtGui.QPixmap.fromImage(self.image) def prepareGUI(self): # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru # self.resize(256, 300) self.setWindowTitle('QPainter') # tlačítko Quit quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'), '&Quit', self) quitAction.triggered.connect(self.close) quitAction.setStatusTip('Quit the application') quitAction.setShortcut('Ctrl+Q') # nástrojový pruh self.toolbar = self.addToolBar('title') self.toolbar.setMovable(False) # přidání tlačítka na nástrojový pruh self.toolbar.addAction(quitAction) # doprostřed okna přidáme návěští s rastrovým obrázkem self.addLabelWithPixmap() # zobrazení hlavního okna self.show() def addLabelWithPixmap(self): # vytvoření návěští label = QtGui.QLabel("test") # přiřazení rastrového obrázku k návěští label.setPixmap(self.pixmap) # vložení návěští do hlavního okna self.setCentralWidget(label) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Třídu QPainter jsme prozatím používali takovým způsobem, že jsme nejprve vytvořili (zkonstruovali) instanci této třídy, následně jsme zavolali metodu QPainter.begin() s předáním obrázku, na který se bude kreslit, provedli jsme vlastní vykreslení a posléze jsme vykreslení explicitně ukončili metodou QPainter.end():
# vytvoření objektu typu QPainter qp = QtGui.QPainter() # začátek změn qp.begin(self.image) # nastavení barvy kreslení qp.setPen(QtGui.QColor(64, 255, 64)) # vykreslení úsečky qp.drawLine(10, 10, MainWindow.IMAGE_WIDTH-10, MainWindow.IMAGE_HEIGHT-10) # konec změn qp.end()
Tento postup měl nevýhodu v tom, že si programátor musí sám ohlídat použití metod QPainter.begin() a QPainter.end(). Tyto metody se vždy musí vyskytovat v páru a nesmí se překrývat, tj. nelze například dvakrát zavolat QPainter.begin() pro různé obrázky bez předchozího zavolání QPainter.end(). Navíc by se měla hlídat i návratová hodnota z první uvedené metody (True/False), což je test, který jsme pro jednoduchost vynechali. Tento poměrně nízkoúrovňový přístup můžeme obejít tak, že už při konstrukci objektu QPainter předáme konstruktoru rastrový obrázek, na který se má provést vlastní vykreslení:
# vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) # nastavení barvy kreslení qp.setPen(QtGui.QColor(64, 255, 64)) # vykreslení úsečky qp.drawLine(10, 10, MainWindow.IMAGE_WIDTH-10, MainWindow.IMAGE_HEIGHT-10)
V tomto případě nemusíme explicitně volat metodu QPainter.begin(), protože tato operace se provede automaticky. Mnohem zajímavější je, že i metoda QPainter.end() se zavolá automaticky, a to konkrétně v destruktoru objektu typu QPainter (to mj. znamená, že by tento objekt měl mít pouze lokální viditelnost resp. oblast platnosti – pro jednoduchost by se celé vykreslení mělo provést v jediné metodě).
Obrázek 2: Výsledkem je naprosto stejné okno, jako v prvním příkladu.
Jakým způsobem je možné využít „chytrý“ konstruktor objektu typu QPainter je ukázáno v dnešním druhém demonstračním příkladu, jehož úplný zdrojový kód je vypsán pod tímto odstavcem:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() self.prepareImage() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareImage(self): # vytvoření instance třídy QImage self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32) # vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) # nastavení barvy kreslení qp.setPen(QtGui.QColor(64, 255, 64)) # vykreslení úsečky qp.drawLine(10, 10, MainWindow.IMAGE_WIDTH-10, MainWindow.IMAGE_HEIGHT-10) # vytvoření instance třídy QPixmap z objektu QImage self.pixmap = QtGui.QPixmap.fromImage(self.image) def prepareGUI(self): # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru # self.resize(256, 300) self.setWindowTitle('QPainter') # tlačítko Quit quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'), '&Quit', self) quitAction.triggered.connect(self.close) quitAction.setStatusTip('Quit the application') quitAction.setShortcut('Ctrl+Q') # nástrojový pruh self.toolbar = self.addToolBar('title') self.toolbar.setMovable(False) # přidání tlačítka na nástrojový pruh self.toolbar.addAction(quitAction) # doprostřed okna přidáme návěští s rastrovým obrázkem self.addLabelWithPixmap() # zobrazení hlavního okna self.show() def addLabelWithPixmap(self): # vytvoření návěští label = QtGui.QLabel("test") # přiřazení rastrového obrázku k návěští label.setPixmap(self.pixmap) # vložení návěští do hlavního okna self.setCentralWidget(label) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Podívejme se nyní na modifikaci stylu vykreslovaných objektů. Již v úvodní kapitole jsme si řekli, že při kreslení obrysů uzavřených objektů i všech liniových objektů se používá nástroj nazvaný pero (pen) představovaný instancí třídy QPen. Při konstrukci objektu typu QPen můžeme specifikovat mj. i barvu kreslení. Následně se pero přiřadí instanci třídy QPainter. Změna barvy kreslení tedy může v tom nejjednodušším případě vypadat následovně:
pen = QtGui.QPen(barva) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen)
Barva je představována instancí třídy QColor a lze ji specifikovat v konstruktoru této třídy následujícími způsoby:
Parametry | Význam |
---|---|
rgb | jediná celočíselná hodnota (long) s kódem RGB |
red, green, blue | tři barvové složky: červená, zelená, modrá |
red, green, blue, alpha | tři barvové složky doplněné o průhlednost |
name | jméno barvy (viz následující kapitolu) |
PySide.QtCore.Qt.GlobalColor | jedna z dvaceti globálních barev |
Pozor: pokud se barva explicitně nespecifikuje, je pro kreslení použita černá barva, takže na implicitně černém obrázku nebude kresba viditelná!
Šířku kreslení (stopy) je možné zadat buď v konstruktoru třídy QPen (ovšem potom nelze specifikovat barvu), nebo zavoláním metody QPen.setWidth():
pen = QtGui.QPen(barva) # změna šířky pera pen.setWidth(width) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen)
Implicitně je šířka stopy nastavena na nulu; to odpovídá nejmenší možné stopě kreslení (jeden pixel bez použití antialiasingu).
Názvy všech barev uložených do standardní palety knihovny PySide získáme velmi snadno zavoláním statické metody QtGui.QColor.colorNames(), která vrátí seznam názvů známých barev. Tento seznam si samozřejmě můžeme velmi snadno vypsat, například následujícím jednořádkovým příkazem:
print("\n".join(QtGui.QColor.colorNames()))
Výstup by měl vypadat následovně (PySide 1.2.x):
aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato transparent turquoise violet wheat white whitesmoke yellow yellowgreen
Příklad použití jména barvy:
# vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen("gold") # kreslit se bude právě vytvořeným perem qPainter.setPen(pen)
V dnešním třetím příkladu je ukázáno, jak je možné kreslit různobarevné úsečky s volitelnou šířkou stopy. O samotné kreslení úseček se stará uživatelská funkce drawLine():
# funkce pro vykreslení úsečky zadanou barvou def drawLine(qPainter, color, x1, y1, x2, y2, width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(width) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2)
Tato funkce v příkladu několikrát zavolána, takže vznikne následující kresba:
Obrázek 3: Okno vykreslené třetím příkladem.
Následuje výpis úplného zdrojového kódu tohoto příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys import math # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # funkce pro vykreslení úsečky zadanou barvou def drawLine(qPainter, color, x1, y1, x2, y2, width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(width) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 320 IMAGE_HEIGHT = 240 def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() self.prepareImage() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareImage(self): # vytvoření instance třídy QImage self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32) # vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) # konstanty s n-ticemi představujícími základní barvy BLACK = (0, 0, 0) BLUE = (0, 0, 255) CYAN = (0, 255, 255) GREEN = (0, 255, 0) YELLOW = (255, 255, 0) RED = (255, 0, 0) MAGENTA = (255, 0, 255) WHITE = (255, 255, 255) # Vykreslení čar různou barvou drawLine(qp, BLUE, 10, 10, 160, 20) drawLine(qp, CYAN, 10, 20, 160, 30) drawLine(qp, GREEN, 10, 30, 160, 40) drawLine(qp, YELLOW, 10, 40, 160, 50) drawLine(qp, RED, 10, 50, 160, 60) drawLine(qp, MAGENTA, 10, 60, 160, 70) # Vykreslení čar s různým sklonem for i in range(1, 90, 5): # převod ze stupňů na radiány angle = math.radians(i) radius = 150 # výpočet koncových bodů úseček x = radius * math.sin(math.radians(i)) y = radius * math.cos(math.radians(i)) # vykreslení jedné úsečky drawLine(qp, WHITE, MainWindow.IMAGE_WIDTH-1, 0, MainWindow.IMAGE_WIDTH-x, y) # vykreslení čar různou šířkou for i in range(1, 10): drawLine(qp, WHITE, 10 + i*15, 90, 20 + i*15, 230, i) # vytvoření instance třídy QPixmap z objektu QImage self.pixmap = QtGui.QPixmap.fromImage(self.image) def prepareGUI(self): # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru # self.resize(256, 300) self.setWindowTitle('QPainter') # tlačítko Quit quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'), '&Quit', self) quitAction.triggered.connect(self.close) quitAction.setStatusTip('Quit the application') quitAction.setShortcut('Ctrl+Q') # nástrojový pruh self.toolbar = self.addToolBar('title') self.toolbar.setMovable(False) # přidání tlačítka na nástrojový pruh self.toolbar.addAction(quitAction) # doprostřed okna přidáme návěští s rastrovým obrázkem self.addLabelWithPixmap() # zobrazení hlavního okna self.show() def addLabelWithPixmap(self): # vytvoření návěští label = QtGui.QLabel("test") # přiřazení rastrového obrázku k návěští label.setPixmap(self.pixmap) # vložení návěští do hlavního okna self.setCentralWidget(label) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Při kreslení objektů do rastrového obrázku je možné kdykoli zapnout či naopak vypnout antialiasing (aa). To se provádí metodou QPainter.setRenderHint(), které se předá konstanta QPainter.Antialiasing, popř. kombinace konstant QPainter.Antialiasing, QPainter.TextAntialiasing a QPainter.HighQualityAntialiasing:
# vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) qp.setRenderHint(QtGui.QPainter.Antialiasing)
V případě, že potřebujeme antialiasing vypnout (kresba různých schémat atd.), předejte metodě QPainter.setRenderHint() pojmenovaný parametr on=False (implicitně má totiž hodnotu True):
# vypneme antialiasing qp.setRenderHint(QtGui.QPainter.Antialiasing, on=False)
Obrázek 4: Stejná scéna jako v předchozím příkladu, ovšem nyní vykreslená s využitím antialiasingu.
Pro porovnání rozdílů mezi nepoužitím a použitím antialiasingu si porovnejte následující dva obrázky:
Obrázek 5: Detail šikmých úseček vykreslených bez použití antialiasingu (zvětšeno).
Obrázek 6: Detail šikmých úseček vykreslených s použitím antialiasingu (zvětšeno).
Dnešní čtvrtý demonstrační příklad obsahuje ukázku zapnutí antialiasingu při kreslení úseček:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys import math # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # funkce pro vykreslení úsečky zadanou barvou def drawLine(qPainter, color, x1, y1, x2, y2, width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(width) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 320 IMAGE_HEIGHT = 240 def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() self.prepareImage() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareImage(self): # vytvoření instance třídy QImage self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32) # vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) qp.setRenderHint(QtGui.QPainter.Antialiasing) # konstanty s n-ticemi představujícími základní barvy BLACK = (0, 0, 0) BLUE = (0, 0, 255) CYAN = (0, 255, 255) GREEN = (0, 255, 0) YELLOW = (255, 255, 0) RED = (255, 0, 0) MAGENTA = (255, 0, 255) WHITE = (255, 255, 255) # Vykreslení čar různou barvou drawLine(qp, BLUE, 10, 10, 160, 20) drawLine(qp, CYAN, 10, 20, 160, 30) drawLine(qp, GREEN, 10, 30, 160, 40) drawLine(qp, YELLOW, 10, 40, 160, 50) drawLine(qp, RED, 10, 50, 160, 60) drawLine(qp, MAGENTA, 10, 60, 160, 70) # Vykreslení čar s různým sklonem for i in range(1, 90, 5): # převod ze stupňů na radiány angle = math.radians(i) radius = 150 # výpočet koncových bodů úseček x = radius * math.sin(math.radians(i)) y = radius * math.cos(math.radians(i)) # vykreslení jedné úsečky drawLine(qp, WHITE, MainWindow.IMAGE_WIDTH-1, 0, MainWindow.IMAGE_WIDTH-x, y) # vykreslení čar různou šířkou for i in range(1, 10): drawLine(qp, WHITE, 10 + i*15, 90, 20 + i*15, 230, i) # vytvoření instance třídy QPixmap z objektu QImage self.pixmap = QtGui.QPixmap.fromImage(self.image) def prepareGUI(self): # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru # self.resize(256, 300) self.setWindowTitle('QPainter') # tlačítko Quit quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'), '&Quit', self) quitAction.triggered.connect(self.close) quitAction.setStatusTip('Quit the application') quitAction.setShortcut('Ctrl+Q') # nástrojový pruh self.toolbar = self.addToolBar('title') self.toolbar.setMovable(False) # přidání tlačítka na nástrojový pruh self.toolbar.addAction(quitAction) # doprostřed okna přidáme návěští s rastrovým obrázkem self.addLabelWithPixmap() # zobrazení hlavního okna self.show() def addLabelWithPixmap(self): # vytvoření návěští label = QtGui.QLabel("test") # přiřazení rastrového obrázku k návěští label.setPixmap(self.pixmap) # vložení návěští do hlavního okna self.setCentralWidget(label) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Při kresbě liniových obrazců nebo okrajů plošných obrazců je možné zvolit vzorek (styl) úseček – plná (výchozí nastavení), čárkovaná, čerchovaná, střídavá apod. K nastavení vzorku vykreslení úsečky slouží metoda QPen.setDashPattern(). Parametrem této metody je seznam délek vykreslených segmentů prokládaných délkami segmentů nevykreslených. Podívejme se na jednoduchý příklad. Nejprve si vytvoříme pomocnou funkci pro vykreslení úsečky s volitelným vzorkem:
# funkce pro vykreslení úsečky zadanou barvou def drawDashedLine(qPainter, color, x1, y1, x2, y2, pattern): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna typu čáry pen.setStyle(QtCore.Qt.CustomDashLine) pen.setDashPattern(pattern) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2)
Tuto funkci použijeme pro vykreslení čar s různým vzorkem:
# Vykreslení čar různým stylem drawDashedLine(qp, YELLOW, 10, 10, 160, 10, [1, 1]) drawDashedLine(qp, YELLOW, 10, 20, 160, 20, [1, 10]) drawDashedLine(qp, YELLOW, 10, 30, 160, 30, [10, 1]) drawDashedLine(qp, YELLOW, 10, 40, 160, 40, [10, 10]) drawDashedLine(qp, YELLOW, 10, 50, 160, 50, [10, 1, 10, 5]) drawDashedLine(qp, YELLOW, 10, 60, 160, 60, [5, 5])
Podívejme se nyní na výsledek:
Obrázek 7: Různé vzorky čar (zvětšeno 2×).
Při kreslení šikmých úseček je situace složitější, protože délky nebudou přesně odpovídat (ty platí jen pro vodorovné či svislé tvary):
# Vykreslení čar s různým sklonem for i in range(1, 90, 5): # převod ze stupňů na radiány angle = math.radians(i) radius = 150 # výpočet koncových bodů úseček x = radius * math.sin(math.radians(i)) y = radius * math.cos(math.radians(i)) # vykreslení jedné úsečky drawDashedLine(qp, WHITE, MainWindow.IMAGE_WIDTH-1, 0, MainWindow.IMAGE_WIDTH-x, y, [5, 5])
Obrázek 8: Šikmé úsečky (zvětšeno 2×, používá se antialiasing).
Zajímavé bude také sledovat, co se stane při změně šířky pera, tj. šířky kreslené stopy. Výše uvedenou funkci si nepatrně upravíme tak, aby akceptovala další nepovinný parametr s šířkou:
# funkce pro vykreslení úsečky zadanou barvou def drawDashedLine(qPainter, color, x1, y1, x2, y2, pattern, width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(width) # změna typu čáry pen.setStyle(QtCore.Qt.CustomDashLine) pen.setDashPattern(pattern) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2)
Zkusíme si vykreslit několik úseček s různou tloušťkou:
# vykreslení čar různou šířkou for i in range(1, 10): drawDashedLine(qp, WHITE, 10 + i*15, 90, 10 + i*15, 230, [5, 5], i)
Výsledek je možná překvapivý:
Obrázek 9: Šířka pera ovlivňuje i délku vzorků (zvětšeno 2×).
Postup při kreslení úseček s různým stylem je ukázán v dalším – dnes již pátém – demonstračním příkladu, po jehož spuštění by se měl vykreslit tento obrázek:
Obrázek 10: Okno vykreslené pátým demonstračním příkladem.
Následuje výpis zdrojového kódu tohoto příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys import math # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # funkce pro vykreslení úsečky zadanou barvou def drawDashedLine(qPainter, color, x1, y1, x2, y2, pattern, width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(width) # změna typu čáry pen.setStyle(QtCore.Qt.CustomDashLine) pen.setDashPattern(pattern) # kreslit se bude právě vytvořeným perem qPainter.setPen(pen) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 320 IMAGE_HEIGHT = 240 def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() self.prepareImage() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareImage(self): # vytvoření instance třídy QImage self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32) # vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) # konstanty s n-ticemi představujícími základní barvy BLACK = (0, 0, 0) BLUE = (0, 0, 255) CYAN = (0, 255, 255) GREEN = (0, 255, 0) YELLOW = (255, 255, 0) RED = (255, 0, 0) MAGENTA = (255, 0, 255) WHITE = (255, 255, 255) # Vykreslení čar různým stylem drawDashedLine(qp, YELLOW, 10, 10, 160, 10, [1, 1]) drawDashedLine(qp, YELLOW, 10, 20, 160, 20, [1, 10]) drawDashedLine(qp, YELLOW, 10, 30, 160, 30, [10, 1]) drawDashedLine(qp, YELLOW, 10, 40, 160, 40, [10, 10]) drawDashedLine(qp, YELLOW, 10, 50, 160, 50, [10, 1, 10, 5]) drawDashedLine(qp, YELLOW, 10, 60, 160, 60, [5, 5]) qp.setRenderHint(QtGui.QPainter.Antialiasing) # Vykreslení čar s různým sklonem for i in range(1, 90, 5): # převod ze stupňů na radiány angle = math.radians(i) radius = 150 # výpočet koncových bodů úseček x = radius * math.sin(math.radians(i)) y = radius * math.cos(math.radians(i)) # vykreslení jedné úsečky drawDashedLine(qp, WHITE, MainWindow.IMAGE_WIDTH-1, 0, MainWindow.IMAGE_WIDTH-x, y, [5, 5]) # vykreslení čar různou šířkou for i in range(1, 10): drawDashedLine(qp, WHITE, 10 + i*15, 90, 10 + i*15, 230, [5, 5], i) # vytvoření instance třídy QPixmap z objektu QImage self.pixmap = QtGui.QPixmap.fromImage(self.image) def prepareGUI(self): # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru # self.resize(256, 300) self.setWindowTitle('QPainter') # tlačítko Quit quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'), '&Quit', self) quitAction.triggered.connect(self.close) quitAction.setStatusTip('Quit the application') quitAction.setShortcut('Ctrl+Q') # nástrojový pruh self.toolbar = self.addToolBar('title') self.toolbar.setMovable(False) # přidání tlačítka na nástrojový pruh self.toolbar.addAction(quitAction) # doprostřed okna přidáme návěští s rastrovým obrázkem self.addLabelWithPixmap() # zobrazení hlavního okna self.show() def addLabelWithPixmap(self): # vytvoření návěští label = QtGui.QLabel("test") # přiřazení rastrového obrázku k návěští label.setPixmap(self.pixmap) # vložení návěští do hlavního okna self.setCentralWidget(label) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
V úvodní kapitole jsme si řekli, že při kreslení uzavřených tvarů (obdélník, polygon, elipsa, cesta) je možné takové tvary vyplnit, přičemž pro vyplnění se používá nástroj nazvaný jednoduše štětec (brush. Štětec může být představován bitmapou (je to jedna z několika praktických možností použití bitmap ve frameworku PySide), popř. je možné štětec vybrat z připraveného vzorníku. Dnes se pro jednoduchost budeme zabývat právě existujícími vzorky štětců. Každý vzorek je představován jednou z konstant:
QtCore.Qt.SolidPattern QtCore.Qt.HorPattern QtCore.Qt.VerPattern QtCore.Qt.CrossPattern QtCore.Qt.BDiagPattern QtCore.Qt.FDiagPattern QtCore.Qt.DiagCrossPattern QtCore.Qt.Dense1Pattern QtCore.Qt.Dense2Pattern QtCore.Qt.Dense3Pattern QtCore.Qt.Dense4Pattern QtCore.Qt.Dense5Pattern QtCore.Qt.Dense6Pattern QtCore.Qt.Dense7Pattern
Obrázek 11: Vzorník výše vypsaných štětců (v daném pořadí).
Vzorky štětců se nastavují takto (podtržená část kódu):
# funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce brush = QtGui.QBrush(QtGui.QColor(*color)) brush.setStyle(brush_style) qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height)
Povšimněte si, že musíme specifikovat i barvu štětce, která se může lišit od barvy pera.
V šestém příkladu jsou ukázány základní vzorky štětců, které jsme si popsali v předchozí kapitole, takže se jen podívejme, jak může vypadat kód, který vzorník po svém spuštění vytiskne:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys import math # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce brush = QtGui.QBrush(QtGui.QColor(*color)) brush.setStyle(brush_style) qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 440 IMAGE_HEIGHT = 140 def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() self.prepareImage() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareImage(self): # vytvoření instance třídy QImage self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32) # vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) # konstanty s n-ticemi představujícími základní barvy BLACK = (0, 0, 0) BLUE = (0, 0, 255) CYAN = (0, 255, 255) GREEN = (0, 255, 0) YELLOW = (255, 255, 0) RED = (255, 0, 0) MAGENTA = (255, 0, 255) WHITE = (255, 255, 255) # Vykreslení obdélníků různým stylem drawRectangleUsingBrush(qp, YELLOW, 10, 10, 50, 50, QtCore.Qt.SolidPattern) drawRectangleUsingBrush(qp, YELLOW, 70, 10, 50, 50, QtCore.Qt.HorPattern) drawRectangleUsingBrush(qp, YELLOW, 130, 10, 50, 50, QtCore.Qt.VerPattern) drawRectangleUsingBrush(qp, YELLOW, 190, 10, 50, 50, QtCore.Qt.CrossPattern) drawRectangleUsingBrush(qp, YELLOW, 250, 10, 50, 50, QtCore.Qt.BDiagPattern) drawRectangleUsingBrush(qp, YELLOW, 310, 10, 50, 50, QtCore.Qt.FDiagPattern) drawRectangleUsingBrush(qp, YELLOW, 370, 10, 50, 50, QtCore.Qt.DiagCrossPattern) drawRectangleUsingBrush(qp, WHITE, 10, 70, 50, 50, QtCore.Qt.Dense1Pattern) drawRectangleUsingBrush(qp, WHITE, 70, 70, 50, 50, QtCore.Qt.Dense2Pattern) drawRectangleUsingBrush(qp, WHITE, 130, 70, 50, 50, QtCore.Qt.Dense3Pattern) drawRectangleUsingBrush(qp, WHITE, 190, 70, 50, 50, QtCore.Qt.Dense4Pattern) drawRectangleUsingBrush(qp, WHITE, 250, 70, 50, 50, QtCore.Qt.Dense5Pattern) drawRectangleUsingBrush(qp, WHITE, 310, 70, 50, 50, QtCore.Qt.Dense6Pattern) drawRectangleUsingBrush(qp, WHITE, 370, 70, 50, 50, QtCore.Qt.Dense7Pattern) # vytvoření instance třídy QPixmap z objektu QImage self.pixmap = QtGui.QPixmap.fromImage(self.image) def prepareGUI(self): # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru # self.resize(256, 300) self.setWindowTitle('QPainter') # tlačítko Quit quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'), '&Quit', self) quitAction.triggered.connect(self.close) quitAction.setStatusTip('Quit the application') quitAction.setShortcut('Ctrl+Q') # nástrojový pruh self.toolbar = self.addToolBar('title') self.toolbar.setMovable(False) # přidání tlačítka na nástrojový pruh self.toolbar.addAction(quitAction) # doprostřed okna přidáme návěští s rastrovým obrázkem self.addLabelWithPixmap() # zobrazení hlavního okna self.show() def addLabelWithPixmap(self): # vytvoření návěští label = QtGui.QLabel("test") # přiřazení rastrového obrázku k návěští label.setPixmap(self.pixmap) # vložení návěští do hlavního okna self.setCentralWidget(label) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Při vyplňování uzavřených tvarů štětcem je možné si zvolit i barvu pozadí, přičemž pozadím jsou v tomto kontextu myšleny ty pixely, které sice leží uvnitř tvaru, ale štětec je při kreslení vynechá (nejedná se tedy o pozadí celé kreslicí plochy – plátna). Barva pozadí je stavová veličina a je ji možné kdykoli změnit (to ovlivní další vykreslování, nikoli však již vykreslené objekty). Navíc nezapomeňte, že kromě barvy pozadí je nutné nastavit i režim vykreslování pozadí na hodnotu QtCore.Qt.BGMode.OpaqueMode, protože implicitně se používá průhledné pozadí (přesněji řečeno nejsou pixely pozadí vůbec modifikovány). Podívejme se na příklad:
# vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) # barva pozadi a povoleni vykreslení pozadi (vyplne) qp.setBackgroundMode(QtCore.Qt.BGMode.OpaqueMode) qp.setBackground(QtGui.QColor(red, green, blue))
Sedmý a současně i dnešní poslední demonstrační příklad se od příkladu šestého odlišuje jen tím, že je v něm explicitně specifikována barva pozadí při použití standardních štětců. Vykreslený obrázek se tedy změní z:
Obrázek 12: Rozdíl mezi použitím a nepoužitím pozadí při vykreslování plochy tvarů štětci.
Samozřejmě opět následuje výpis zdrojového kódu tohoto příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys import math # import "jádra" frameworku Qt i modulu pro GUI from PySide import QtCore from PySide import QtGui # funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce brush = QtGui.QBrush(QtGui.QColor(*color)) brush.setStyle(brush_style) qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 440 IMAGE_HEIGHT = 140 def __init__(self): # zavoláme konstruktor předka super(MainWindow, self).__init__() self.prepareImage() # konfigurace GUI + přidání widgetu do okna self.prepareGUI() def prepareImage(self): # vytvoření instance třídy QImage self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, QtGui.QImage.Format_RGB32) # vytvoření objektu typu QPainter s předáním # reference na "pokreslovaný" objekt qp = QtGui.QPainter(self.image) # konstanty s n-ticemi představujícími základní barvy BLACK = (0, 0, 0) BLUE = (0, 0, 255) CYAN = (0, 255, 255) GREEN = (0, 255, 0) YELLOW = (255, 255, 0) RED = (255, 0, 0) MAGENTA = (255, 0, 255) WHITE = (255, 255, 255) # barva pozadi a povoleni vykreslení pozadi (vyplne) qp.setBackgroundMode(QtCore.Qt.BGMode.OpaqueMode) qp.setBackground(QtGui.QColor(*BLUE)) # Vykreslení obdélníků různým stylem drawRectangleUsingBrush(qp, YELLOW, 10, 10, 50, 50, QtCore.Qt.SolidPattern) drawRectangleUsingBrush(qp, YELLOW, 70, 10, 50, 50, QtCore.Qt.HorPattern) drawRectangleUsingBrush(qp, YELLOW, 130, 10, 50, 50, QtCore.Qt.VerPattern) drawRectangleUsingBrush(qp, YELLOW, 190, 10, 50, 50, QtCore.Qt.CrossPattern) drawRectangleUsingBrush(qp, YELLOW, 250, 10, 50, 50, QtCore.Qt.BDiagPattern) drawRectangleUsingBrush(qp, YELLOW, 310, 10, 50, 50, QtCore.Qt.FDiagPattern) drawRectangleUsingBrush(qp, YELLOW, 370, 10, 50, 50, QtCore.Qt.DiagCrossPattern) drawRectangleUsingBrush(qp, WHITE, 10, 70, 50, 50, QtCore.Qt.Dense1Pattern) drawRectangleUsingBrush(qp, WHITE, 70, 70, 50, 50, QtCore.Qt.Dense2Pattern) drawRectangleUsingBrush(qp, WHITE, 130, 70, 50, 50, QtCore.Qt.Dense3Pattern) drawRectangleUsingBrush(qp, WHITE, 190, 70, 50, 50, QtCore.Qt.Dense4Pattern) drawRectangleUsingBrush(qp, WHITE, 250, 70, 50, 50, QtCore.Qt.Dense5Pattern) drawRectangleUsingBrush(qp, WHITE, 310, 70, 50, 50, QtCore.Qt.Dense6Pattern) drawRectangleUsingBrush(qp, WHITE, 370, 70, 50, 50, QtCore.Qt.Dense7Pattern) # vytvoření instance třídy QPixmap z objektu QImage self.pixmap = QtGui.QPixmap.fromImage(self.image) def prepareGUI(self): # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru # self.resize(256, 300) self.setWindowTitle('QPainter') # tlačítko Quit quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'), '&Quit', self) quitAction.triggered.connect(self.close) quitAction.setStatusTip('Quit the application') quitAction.setShortcut('Ctrl+Q') # nástrojový pruh self.toolbar = self.addToolBar('title') self.toolbar.setMovable(False) # přidání tlačítka na nástrojový pruh self.toolbar.addAction(quitAction) # doprostřed okna přidáme návěští s rastrovým obrázkem self.addLabelWithPixmap() # zobrazení hlavního okna self.show() def addLabelWithPixmap(self): # vytvoření návěští label = QtGui.QLabel("test") # přiřazení rastrového obrázku k návěští label.setPixmap(self.pixmap) # vložení návěští do hlavního okna self.setCentralWidget(label) def run(self, app): # zobrazení okna na obrazovce self.show() # vstup do smyčky událostí (event loop) app.exec_() def main(): app = QtGui.QApplication(sys.argv) MainWindow().run(app) if __name__ == '__main__': main()
Zdrojové kódy všech sedmi dnes popsaných demonstračních příkladů byly opět, podobně jako tomu bylo i v předchozích článcích, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Pokud nechcete klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
Příklad | Adresa |
---|---|
63_qpainter.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/63_qpainter.py |
64_qpainter_smart_constructor.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/64_qpainter_smart_constructor.py |
65_pen_color_and_width.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/65_pen_color_and_width.py |
66_qpainter_aa.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/66_qpainter_aa.py |
67_dashed_line.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/67_dashed_line.py |
68_brush_patterns.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/68_brush_patterns.py |
69_pattern_background.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/69_pattern_background.py |
Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.
Internet Info Root.cz (www.root.cz)
Informace nejen ze světa Linuxu. ISSN 1212-8309
Copyright © 1998 – 2019 Internet Info, s.r.o. Všechna práva vyhrazena.