Skip to content

Latest commit

 

History

History

07_countdown

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Odpočet

V tomto díle vytvoříme jednoduchou aplikaci na odpočet.

Smyčka událostí a čekání

V aplikaci někdy potřebujeme nějaký čas počkat, nebo nějakou činnost provádět jednou za nějaký čas. Při skriptování v Pythonu se pro čekání obvykle využívá funkce time.sleep(<cas>), která počká <cas> sekund a potom skončí. V principu bychom mohli použít tuto funkci i v grafickém rozhraní, ale mělo by to několik nevýhod. Pokud si vzpomeneme na první díl, je zde popsáno, jak funguje smyčka událostí - když přijde nějaká událost, je odbavena (ať už Pythonem nebo Qt) a poté se čeká na další událost. Co přesně znamená odbavit událost v Pythonu? Znamená to, že funkce, která byla zavolána, aby událost odbavila, musí skončit. Teprve poté může být odbavena další událost. Pokud v rámci odbavování události potřebujeme počkat a použili bychom time.sleep(), pak by odbavování události skončilo až po tomto čekání a dokončení následných činností. Protože ale reakce na tlačítka nebo zvětšení či zmenšení okna jsou také události, muselo by jejich odbavení počkat, až skončí událost s čekáním a tudíž by aplikace "zamrzla" a nereagovala na žádné podněty od uživatele.

Paralelismus

Možná vás napadlo, proč nejsou jednotlivé události obsluhovány paralelně, čímž by se předešlo podobným problémům. Byla by to samozřejmě možnost, ale paralelní zpracování přináší kromě zřejmých výhod i mnoho ne až tak zřejmých problémů. Nejčastějším problémem je souběh (anglicky race condition) - pokud více obslužných funkcí manipuluje s těmiže daty, při současném běhu těchto obslužných funkcí by si funkce tato společná data přepisovaly "pod rukama", což by mohlo vést k nekonzistentnímu stavu. Psát takový kód, aby k problematickým situacím při současném běhu nedocházelo, není jednoduché a vede často k špatně odhalitelným chybám, proto je vhodné se takovéto "neřízené" paralelizaci vyhnout.

Pokud chceme v našem programu provádět časově náročné operace, je možné si vytvořit vlákno, které obvykle sdílí jen minimum informací se zbytkem programu a komunikuje se zbytkem programu pomocí postupů, které zajišťují správnou synchronizaci. K vláknům se možná vrátíme v jednom z budoucích dílů, zatím se bez nich obejdeme.

Časovač

Abychom zachovali naši aplikaci responzivní (odpovídající na činnosti uživatele) i když chceme čekat, budeme potřebovat prostředek, který nám vyvolá událost za stanovený čas nebo ve stanoveném intervalu. Takovýto prostředek v Qt je QTimer. QTimer je běžný objekt, který můžeme vytvořit (i vícekrát) a nastavit, aby jednorázově za nějaký čas, nebo opakovaně v určitém intervalu vyslal signál timeout. Pokud připojíme tento signál do nějakého vytvořeného slotu a časovač spustíme, je jednorázově nebo opakovaně vyvolávána naše funkce. Aplikace přitom zůstává responzivní, protože funkce, která spustí časovač okamžitě vrátí a časovač pak běží v pozadí, zatímco ve smyčce událostí jsou odbavovány další nastalé události. Nepotřebujeme tedy řešit paralelismus (i když může být někde v pozadí použit) a pomocí jednoduchého rozhraní získáváme možnost spouštět činnosti opožděně nebo opakovaně ve stanoveném intervalu.

Při použití QTimeru prvně vytvoříme časovač, připojíme signál timeout a pak buď nastavíme interval v milisekundách pomocí setInterval(<msec>) a spustíme pomocí start() nebo uvedeme interval přímo jako parametr funkce start(<msec>). V obou případech se časovač spustí a po každém intervalu vyšle signál, pracuje tedy opakovaně. Pokud chceme časovač pouze jednorázový, musíme nastavit setSingleShot(False) nebo časovač zaráz vytvořit a spustit pomocí statické metody (metody třídy) singleShot.

Popis programu

Program má jednoduché rozhraní s textovým polem a třemi tlačítky - Start, Pause a Stop. V textovém poli je zobrazen zbývající čas. Po stisknutí tlačítka Start začne běžět odpočet. Odpočet lze pozastavit stiskem tlačítka Pause, po stisknutí tlačítka Start program pokračuje v odpočtu. Stiskem tlačítka Stop se odpočet zastaví a vrátí se na poslední hodnotu zadanou když časovač neběžel. Po doběhnutí času se zobrazí přes okno dialog s textem Time out! a po jeho odkliknutí se časovač vrátí na poslední hodnotu zadanou když časovač neběžel.

Modelem aplikace je třída CountdownModel, která drží hodnotu časovače v property remaining, poslední nastavenou hodnotu v atributu total a samotný časovač v atributu timer. Dále pak obsahuje sloty pro obsluhu tlačítek a časovače.

Protože pro nastavování výchozí hodnoty odpočtu i zobrazování zbývajícího času slouží jedno textové pole a chceme mít možnost po zastavení odpočtu znovu nastavit výchozí hodnotu, je potřeba v rámci setteru mezi těmito situacemi rozlišit. Připomeňme, že abychom nemuseli mít tlačítko "Nastavit", musíme mít textové pole propojené obousměrnou vazbou s odpovídající property, aby to, co uživatel napíše se ihned přeneslo do modelu. Bohužel se ale stejným způsobem přenese i změna při odpočtu, musíme tedy mezi těmito situacemi rozlišit, což se zde děje pomocí detekce, jestli časovač běží. Pokud ano, výchozí hodnota v total se neaktualizuje, pokud neběží, je výchozí hodnota v total aktualizována. Odpovídá to chování, co byste od odpočtu očekávali, obdobně se chová například odpočet na mobilu.

Zkuste si rozmyslet, zda vám tento způsob ovládání časovače přijde intuitivní nebo jestli byste očekávali jiné chování. Zkuste si své případné změny implementovat a ověřte, jestli se vámi upravený časovač ovládá pohodlněji.

Popis grafického rozhraní

Rozhraní je tvořeno textovým polem a třemi tlačítky v jednom sloupci. TextInput obdobně jako v převodu souřadnic má pomocí Binding nastavenou oboustrannou vazbu s property remaining modelu. Tlačítka Start, Pause a Stop jsou připojena na sloty modelu.

Pro zobrazení informace, že časový limit vypršel, používáme komponentu Popup. Tato komponenta obvykle slouží ke zobrazení nějakého dialogového okna (např. Soubor tohoto jména již existuje. Přejete si jej přepsat?), ale dá se také využít pro snadné překrytí původního obsahu okna. Popup má nastavenou velikost podle sloupce, ve kterém jsou tlačítka a textové pole a je na tento sloupec také vystředěn, takže po zobrazení ho akorát překryje. Vlastností visible nastavujeme, že nemá být po spuštění viditelný, do vlastnosti contentItem pak vložíme samotný obsah popupu. Tím je červený obdélník pro přilákání pozornosti a text Time out!. Také je zde nastavena reakce na kliknutí myši a tou je zavření popupu, abychom mohli znovu spustit časovač. Vlastnost focus nám říká, že popup má dostávat události od klávesnice a vlastnot modal říká, zda se jedná o tzv. modální dialog.

Pokud aplikace otevře další okno / dialog, mohou nastat dvě situace. Buď vyžadujeme, aby uživatel musel vyřešit situaci, o které se dozvěděl v nově otevřeném okně (např. zvolit soubor k otevření, potvrdit přepsání souboru, ...) a do jejího vyřešení nemohl interagovat se zbytkem aplikace, pak takovéto okno nazýváme modální. Nebo jde o obyčejné okno nebo například okno s nastavováním vlastností aktuálně zvoleného objektu, pak obvykle chceme, aby uživatel mohl dále interagovat s dalšími okny aplikace a prvky v nich, a takovéto okno nazýváme nemodální.

Poslední komponentou je komponenta Connections, pomocí které můžeme definovat akci, která se vykoná při určitém signálu. V našem případě chceme při timeoutu časovače nastavit dialog Time out! jako viditelný, aby si uživatel všiml, že časovač doběhl. Všimněme si, že zde nesvazujeme žádné property, ale pouze říkáme "Když doběhne časovač, nastav popup jako viditelný". Když uživatel popup zavře, žádná další akce se nevykoná, ani to z modelu nijak nepoznáme.

Zdroje