Hodiny v Basicu

Použití časovače a API funkcí pro formulář

Doc. Dr. Vladimír Homola, Ph.D.

1. Úvod

Tento text je určen zájemcům o programování v jazyku Basic na průměrné úrovni. Předpokládá základní znalosti o prostředí Windows a základní orientaci v jazyce Basic jako takovém. Dále předpokládá alespoň minimální znalosti o návrhovém prostředí formulářů v MS Visual Studiu.

Ukazuje především na příkladu analogových (ručičkových) hodin použití rastrového obrázku jako pozadí formuláře a použití objektu třídy Timer (časovač) - přesněji reakci na uplynutí stanoveného časového intervalu. Pro ručičky hodin jsou použity tři objekty třídy Line (úsečka) s jedním společným koncovým bodem ve středu formuláře. Poloha jejich druhého koncového bodu se mění v závislosti na čase. Pro úplnost je zobrazeno i aktuální datum jako objekt třídy Label.

Jako ne nezbytně nutný přídavek je formulář oříznut obvodem hodin do eliptického (zde kruhového) regionu použitím funkcí API systému Windows.

Program ve tvaru EXE je k vyzkoušení: HODINY.EXE - lze stáhnout nebo i přímo spustit. Kompletní projekt ve Visual Studiu 6.0 je ke stažení zde.

2. Příprava projektu

Před vlastním sestavením programu pomocí Visual Basicu je zapotřebí připravit obraz hodin (bez ručiček) jako rastrový obrázek. Pro jednoduchost je vhodné, aby obrázek byl čtvercový a hodiny kruhové. Program sice počítá i s eliptickými hodinami s "vodorovnou" hlavní poloosou (kružnice je zvláštní případ elipsy), v každém případě by elipsa nebo kružnice měla být vepsána obdélníku tvořeném obvodem obrázku.

Pro vytvoření obrazu hodin nechť každý použije toho rastrového grafického editoru (nebo toho postupu), který je mu nejbližší. Autor tohoto článku použil vektorový editor Corel Draw (v podstatě jde o vektorové objekty) a výsledek exportoval jako bitmapu (BMP) o velikosti 560 x 560 bodů; náhled je asi takový:

 

Další text předpokládá, že obraz hodin je k disposici jako soubor HODINY.BMP uložený např. v adresáři spolu s ostatními soubory projektu. Zájemci, kteří si tvorbu celého programu chtějí vyzkoušet sami, si mohou stáhnout tento obraz hodin obvyklým stisknutím pravého tlačítka nad shora zobrazeným náhledem; získají sice formát GIF ne zrovna v super kvalitě, ale celkem vyhovující.

Má-li být cílem samostatně spustitelný EXE program, je vhodné mít pro něj připravenou ikonu (ne nezbytně nutné; nezadá-li se připravená, dodá Visual Basic standardní vlastní). Autor si připravil jako soubor HODINY.ICO ikonu

Oba zmíněné obrázky jsou rovněž k disposici ve shora nabízeném kompletním projektu.

3. Projekt ve Visual Basic

Projekt tvoří jeden formulář (pro hodiny jako takové, použit název souboru HODINY.FRM) a jeden modul s deklaracemi funkcí pro eliptické oříznutí formuláře, použit název souboru HODINY.BAS. Dále je zapotřebí mít k disposici rastrový obraz hodin, použit název souboru HODINY.BMP. Visual Basic udržuje informace o projektu v souboru, pro který byl použit název HODINY.VBP.

Postup tvorby projektu je následující:

Po těchto počátečních úkonech zobrazí průzkumník projektu jeho strukturu takto:

4. Formulář a objekty v něm

Formuláři samotnému se nastaví potřebné vlastnosti:

Dále se do formuláře vloží pět objektů.

Objekt třídy Timer se umístí na formuláři kamkoliv, protože jde o non - visual objekt (a tedy stejně není vidět). Jako objekt se pojmenuje tmHodiny a nastaví se mu vlastnosti

Objekt třídy Label bude zobrazovat datum. Umístí se tedy do formuláře tak, aby to bylo ve shodě s obrazem hodin. V uváděném příkladu bylo datum umístěno do středu spodní části hodin. Objekt se pojmenuje lbDatum a přiřadí se mu takový font, barva a velikost, jakou uzná programátor za vhodné. Nastaví se atributy

Postupně tři objekty třídy Line. Jako objekty se nazvou lnHod, lnMin a lnSec. Umístí se kamkoliv (protože jejich přesné umístění udělá program), nejlépe do polohy "za deset minut dvě a třicet vteřin". Nastaví se vlastnosti

5. Kód formuláře

Editační okno s kódem formuláře zobrazí např. pravé tlačítko myši nad položkou frHodiny (HODINY.FRM) v průzkumníku projektu, položka ViewCode. V tomto okně se programují všechny vlastní reakce na události a další potřebné podprogramy a funkce.

5.1. Záhlaví kódu formuláře

V záhlaví je vhodné umístit jednak pokyn překladači na kontrolu explicitních deklarací vlastních proměnných a funkcí, jednak konstantu p (Ludolfovo číslo) pro výpočet souřadnic bodů elipsy:

Option Explicit
Const gPi As Double = 3.14159265358979

5.2. Událost Timer

K této události dojde po uplynutí přednastavené doby, zde 1000 ms = 1 vteřina. Úkolem je přestavit ručičky, případně přepsat datum - prostě zobrazit aktuální čas. Protože jde o akci prováděnou jednak při zavedení formuláře, jednak každou vteřinu, je vhodné ji naprogramovat jako samostatnou subroutinu s názvem např. ZobrazČas s parametrem rovným formuláři, který se má použít. Zpracování události Timer je pak jednoduché:

Private Sub tmHodiny_Timer()
   ' Po uplynutí nastaveného intervalu timeru se udělá co?
   ZobrazCas Me
End Sub

5.3. Přesunutí formuláře

Pokud by byl formulář v běžném okně Windows, přesouvá se běžně uchopením za nadpis. Zde však uvažujeme s jeho oříznutím a tedy by nebyl za co uchopit. Naprogramuje se proto událost stisknutí myši: při stisknutí a držení se hodiny přesunou. Daleko jednodušší než programovat posun voláním metody Move (znamená to zjišťovat aktuální souřadnice, nové souřadnice atd) je použití funkcí API, především SendMessage oknu formuláře Me:

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
   ' Hýbání hodinama při formuláři bez nadpisu
   ReleaseCapture
   SendMessage Me.hwnd, WM_SYSCOMMAND, &HF012&, 0&
End Sub

5.4. Ukončení programu

Pokud by byl formulář v běžném okně Windows, ukončuje se program stisknutím zavíracího tlačítka okna. Zde však uvažujeme s jeho oříznutím a tedy by nebylo co stisknout. Naprogramuje se proto např. událost dvojího stisknutí myši:

Private Sub Form_DblClick()

   ' Po dbl clicknutí kamkoliv do formuláře je možnost skončit:

   Dim lOdp As Long
   Dim lT As String

   lOdp = MsgBox("Skončit?", vbQuestion + vbYesNo, "Co je?")
   If lOdp = vbYes Then Unload Me

End Sub

5.5. Zavedení formuláře

Nejobsažnější jsou akce, které je třeba vykonat při zavedení formuláře do paměti před tím, než se zobrazí. Jde o čtyři kroky popsané následující událostní procedurou:

Private Sub Form_Load()

   Dim lR As Long ' Handler regionu
   Dim lT As Long ' První pixel shora vnitřku formuláře
   Dim lL As Long ' První pixel zleva vnitřku formuláře
   Dim lW As Long ' Šířka formuláře v pixelech
   Dim lH As Long ' Výška formuláře v pixelech

   ' Po spuštění se počátky ručiček umístí do středu formuláře ...
   With Me.lnSec: .X1 = Me.ScaleWidth / 2: .X2 = Me.ScaleHeight / 2: End With
   With Me.lnMin: .X1 = Me.ScaleWidth / 2: .X2 = Me.ScaleHeight / 2: End With
   With Me.lnHod: .X1 = Me.ScaleWidth / 2: .X2 = Me.ScaleHeight / 2: End With

   ' ... a zobrazí se správný čas a datum:
   ZobrazCas Me

   ' Parametry okrajující elipsy - API vyžaduje pixely, proto funkce ScaleX(Y):
   lW = Me.ScaleX(Me.Width, vbTwips, vbPixels) - 4
   lH = Me.ScaleY(Me.Height, vbTwips, vbPixels) - 4
   lL = 4
   lT = lH - Me.ScaleY(Me.ScaleHeight, Me.ScaleMode, vbPixels) + 2

   ' Ukrojení vnějšku hodin
   lR = CreateEllipticRgn(lL, lT, lW, lH)
   SetWindowRgn Me.hwnd, lR, True

End Sub

5.6. Zobrazení času

Jednak v události časovače, jednak při zavádění formuláře se zobrazuje čas (v obou případech stejným způsobem). Je proto vhodné mít k tomu účelu samostatný podprogram, jehož parametrem je formulář, pro který se akce provádí:

Private Sub ZobrazCas(qF As Form)

   ' Nastaví koncové body ručiček do bodů odpovídajících aktuálnímu času.

   Dim lX As Double      ' Pro spočtenou x-ovou souřadnici X2
   Dim lY As Double      ' Pro spočtenou y-ovou souřadnici Y2
   Dim lT                ' Pro aktuální čas

   ' Aktuální čas a datum:
   lT = Now()
   qF.Caption = Format(lT, "hh:mm:ss")             ' Nadpis okna - čas
   qF.lbDatum.Caption = Format(lT, "dd/mm/yyyy")   ' Text datumu - datum

   ' Výpočty a nastavení koncových bodů ručiček:
   KonSec qF, lT, 05, lX, lY: With qF.lnSec: .X2 = lX: .Y2 = lY: End With
   KonMin qF, lT, 15, lX, lY: With qF.lnMin: .X2 = lX: .Y2 = lY: End With
   KonHod qF, lT, 25, lX, lY: With qF.lnHod: .X2 = lX: .Y2 = lY: End With

End Sub

Podprogram nejprve umístí do nadpisu okna (Caption) aktuální čas. Pozor - formulář je sice kruhově okrojený a nemá nadpis, ale je zobrazen dole v liště s tlačítkem Start (Task Bar) a tam nadpis vidět je! Dále změní text objektu lbDatum na aktuální datum.

Poté změní koncové souřadnice (X2,Y2) všech tří ručiček. Mechanismus je u všech třech stejný, trochu se liší způsob výpočtu. Proto jsou připojeny tři samostatné subroutiny pro výpočet koncového bodu každé ručičky KonHod, KonMin a KonSec. Při zobrazení času jsou použity jak shora vidno.

5.7. Výpočet polohy ručiček

Každá z ručiček je vlastně průvodičem bodu, který se pohybuje po kružnici v případě čtvercového formuláře (obecně po elipse v případě obdélníkového formuláře). Poloměr takové kružnice je totožný s délkou ručičky. Nechť je délka ručičky A o P% menší než je poloměr největší kružnice vepsané formuláři - tj polovina strany formuláře (analogicky pro elipsu). Jedním koncovým bodem každé z ručiček (X1, Y1) je tedy střed formuláře (proto musí mít rastrový obrázek hodin tvar, který je přesně vepsán do formuláře).

Druhý koncový bod (X2, Y2) leží na kružnici tak, že odpovídá aktuálním hodinám (minutám, vteřinám). Úhel, který svírá ručička s osou X (vodorovná zleva doprava), je např. 90º pro 0 hodin (minut, vteřin), 0º pro přesně 3 hodiny (přesně 15 hodin, 15 minut, 15 vteřin), -90º pro přesně 6 (18) hodin (30 minut, 30 vteřin) a 180º pro přesně 9 (21) hodin (45 minut, 45 vteřin). Označme tento úhel f.

Je-li úhel f úhlem, který svírá průvodič bodu kružnice (elipsy) s osou X, pak souřadnice bodu [x=X2,y=Y2] na kružnici (elipse)  lze nejjednodušeji získat z parametrických rovnic elipsy (kružnice je zvláštním případem elipsy s poloosami stejné délky):

x = A . sin (f) + x0
y = B . cos (f) + y0

kde A je velikost velké, B velikost malé poloosy elipsy. Velikosti poloos jsou velikosti poloviny výšky a šířky formuláře zmenšené o P% (viz výše). Hodnoty x0 a y0 jsou souřadnice středu elipsy (zde tedy středu formuláře).

Tři podprogramy, které počítají souřadnice koncových bodů ručiček, mohou mít proto následující tvar: 

 

Private Sub KonSec(qF As Form, qT, qP As Double, ByRef qX As Double, ByRef qY As Double)

   ' Spočte souřadnice koncového bodu vteřinové ručičky v čase qT,
   ' přičemž délka ručičky je o qP% menší než "poloměr" formuláře qF.

   Dim lA As Double
   Dim lB As Double
   Dim lF As Double
   Dim lP As Double
   Dim lS As Double

   lS = Second(qT)
   lP = lS * 2 * gPi / 60
   lF = IIf(lS >= 0 And lS < 45, gPi - lP, 3 * gPi - lP)

   lA = qF.ScaleWidth * (50 - qP) / 100
   lB = qF.ScaleHeight * (50 - qP) / 100
   qX = lA * Sin(lF) + qF.ScaleWidth / 2
   qY = lB * Cos(lF) + qF.ScaleHeight / 2

End Sub

 

Private Sub KonMin(qF As Form, qT, qP As Double, ByRef qX As Double, ByRef qY As Double)

   ' Spočte souřadnice koncového bodu minutové ručičky v čase qT,
   ' přičemž délka ručičky je o qP% menší než "poloměr" formuláře qF.

   Dim lA As Double
   Dim lB As Double
   Dim lF As Double
   Dim lP As Double
   Dim lS As Double

   lS = Minute(qT)
   lP = lS * 2 * gPi / 60
   lF = IIf(lS >= 0 And lS < 45, gPi - lP, 3 * gPi - lP)

   lA = qF.ScaleWidth * (50 - qP) / 100
   lB = qF.ScaleHeight * (50 - qP) / 100
   qX = lA * Sin(lF) + qF.ScaleWidth / 2
   qY = lB * Cos(lF) + qF.ScaleHeight / 2
End Sub

 

Private Sub KonHod(qF As Form, qT, qP As Double, ByRef qX As Double, ByRef qY As Double)

   ' Spočte souřadnice koncového bodu hodinové ručičky v čase qT,
   ' přičemž délka ručičky je o qP% menší než "poloměr" formuláře qF.

   Dim lA As Double
   Dim lB As Double
   Dim lF As Double
   Dim lP As Double
   Dim lS As Double

   lS = (Hour(qT) * 60 + Minute(qT)) / 60: If lS >= 12 Then lS = lS - 12
   lP = lS * 2 * gPi / 12
   lF = IIf(lS >= 0 And lS < 45, gPi - lP, 3 * gPi - lP)

   lA = qF.ScaleWidth * (50 - qP) / 100
   lB = qF.ScaleHeight * (50 - qP) / 100
   qX = lA * Sin(lF) + qF.ScaleWidth / 2
   qY = lB * Cos(lF) + qF.ScaleHeight / 2

End Sub

6. Deklarace API funkcí

Formální deklarace hlaviček API funkcí pro oříznutí formuláře eliptickým regionem a pro posun okna jsou umístěny v samostatném modulu ProgApi (soubor HODINY.BAS):

Option Explicit

Public Const WM_SYSCOMMAND = &H112

Public Declare Function CreateEllipticRgn Lib "gdi32" _
  (ByVal X1 As Long, ByVal Y1 As Long, _
   ByVal X2 As Long, ByVal Y2 As Long) As Long


Public Declare Function SetWindowRgn Lib "user32" _
  (ByVal hwnd As Long, ByVal hRgn As Long, _
   ByVal bRedraw As Boolean) As Long

Public Declare Function SetWindowPos Lib "user32" _
  (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
   ByVal x As Long, ByVal y As Long, _
   ByVal cx As Long, ByVal cy As Long, _
   ByVal wFlags As Long) As Long

Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
  (ByVal hwnd As Long, ByVal wMsg As Long, _
   ByVal wParam As Long, lParam As Any) As Long

Public Declare Function ReleaseCapture Lib "user32" () As Long

 

7. Závěr

Článek podává kompletní text poměrně jednoduchého programu v syntaxi Visual Basicu pro prostředí Windows. Používá jednak standardní metody Basicu, jednak funkce přímo jádra Windows. Výsledkem je celkem efektní prezentace, která může být základem mnoha dalším vlastním aplikacím.

 

 

 

Rev. 01 / 2021