Jump to content

Graphical Clock: 7 Segments


Recommended Posts

Posted (edited)

 

image.gif.41fdc8a300cf3143a0a3bb42e200efcf.gif

Most of the 7-Segment display examples I've encountered are rigid and inflexible: it's difficult to easily change the color of the clock or resize it with a single click. While they may be effective, they lack flexibility. Say no to unmaintainable code.

This script creates a dynamic graphical clock that displays the current time in segmented digits. It utilizes the GDI+ and AutoItObject libraries to draw an intuitive and responsive graphical interface. This detailed presentation highlights the technical and professional aspects of the code.

Objectives and Features Dynamic Time Display: The main goal is to visually and legibly represent the current time. The time digits are displayed as segments, with ":" separators indicating hours, minutes, and seconds.

Interface Adaptability: The graphical interface dynamically adjusts to the window size, ensuring optimal presentation on any screen. When the window is resized, the grid and graphical elements are recalculated and redrawn to maintain a consistent appearance.

Use of External Libraries: The script leverages the advanced features of GDI+ for graphical rendering and AutoItObject for object-oriented management. These external libraries offer extensive functionalities and a high-level abstraction to simplify development.

#include-once
#include <Array.au3>
#include <AutoItObject.au3>
#include <GDIPlus.au3>
#include <GUIConstantsEx.au3>
#include <WinAPI.au3>
#include <WinAPIGdi.au3>
#include <WindowsConstants.au3>

Global $g___bEXIT = False
HotKeySet("{ESC}", "_Exit")

_GDIPlus_Startup()
_AutoItObject_Startup()

Const $COLOR_RED = 0xFFFF0000
Const $COLOR_GREEN = 0xFF00FF00

; Line Class
Func Line($x1, $y1, $x2, $y2, $color = $COLOR_RED, $thickness = 2)
    Local $cLine = _AutoItObject_Class()
    With $cLine
        .AddProperty("x1", $ELSCOPE_PUBLIC, $x1)
        .AddProperty("y1", $ELSCOPE_PUBLIC, $y1)
        .AddProperty("x2", $ELSCOPE_PUBLIC, $x2)
        .AddProperty("y2", $ELSCOPE_PUBLIC, $y2)
        .AddProperty("color", $ELSCOPE_PUBLIC, $color)
        .AddProperty("thickness", $ELSCOPE_PUBLIC, $thickness)
    EndWith
    Return $cLine.Object
EndFunc   ;==>Line

; GridPane Class
Func GridPane($numCells, $title, $width, $height)
    Local $cGridPane = _AutoItObject_Class()
    Local $hGUI = GUICreate($title, $width, $height, -1, -1, $WS_SIZEBOX + $WS_SYSMENU)
    GUISetBkColor(0x000000, $hGUI)
    GUISetState(@SW_SHOW, $hGUI)

    Local $hDC = _WinAPI_GetDC($hGUI)
    Local $hGraphics = _GDIPlus_GraphicsCreateFromHDC($hDC)
    Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $width, $height)
    Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC)
    Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap)
    Local $hMemGraphics = _GDIPlus_GraphicsCreateFromHDC($hMemDC)

    Local $aArray[0]
    $width = _WinAPI_GetClientWidth($hGUI)
    $height = _WinAPI_GetClientHeight($hGUI)

    With $cGridPane
        .AddProperty("hGUI", $ELSCOPE_PUBLIC, $hGUI)
        .AddProperty("hDC", $ELSCOPE_PUBLIC, $hDC)
        .AddProperty("hGraphics", $ELSCOPE_PUBLIC, $hGraphics)
        .AddProperty("hBitmap", $ELSCOPE_PUBLIC, $hBitmap)
        .AddProperty("hMemDC", $ELSCOPE_PUBLIC, $hMemDC)
        .AddProperty("hOldBmp", $ELSCOPE_PUBLIC, $hOldBmp)
        .AddProperty("hMemGraphics", $ELSCOPE_PUBLIC, $hMemGraphics)
        .AddProperty("numCells", $ELSCOPE_PUBLIC, $numCells)
        .AddProperty("cellWidth", $ELSCOPE_PUBLIC, $width / $numCells)
        .AddProperty("cellHeight", $ELSCOPE_PUBLIC, $height)
        .AddProperty("lines", $ELSCOPE_PUBLIC, $aArray)
        .AddMethod("AddLine", "_AddLine")
        .AddMethod("Draw", "_Draw")
        .AddMethod("DisplayNumber", "_DisplayNumber")
        .AddMethod("DisplayFullTime", "_DisplayFullTime")
        .AddMethod("DisplaySeparator", "_DisplaySeparator")
        .AddMethod("Resize", "_Resize")
        .AddDestructor("_GridPaneDestroy")
    EndWith
    Return $cGridPane.Object
EndFunc   ;==>GridPane

Func _AddLine($this, $line)
    Local $aArray = $this.lines
    _ArrayAdd($aArray, $line)
    $this.lines = $aArray
EndFunc   ;==>_AddLine

Func _Draw($this)
    _GDIPlus_GraphicsClear($this.hMemGraphics, 0xFF000000)
    Local $aArray = $this.lines
    For $line In $aArray
        Local $hPen = _GDIPlus_PenCreate($line.color, $line.thickness)
        _GDIPlus_GraphicsDrawLine($this.hMemGraphics, $line.x1, $line.y1, $line.x2, $line.y2, $hPen)
        _GDIPlus_PenDispose($hPen)
    Next
    _WinAPI_BitBlt($this.hDC, 0, 0, _WinAPI_GetClientWidth($this.hGUI), _WinAPI_GetClientHeight($this.hGUI), $this.hMemDC, 0, 0, $SRCCOPY)
EndFunc   ;==>_Draw

Func _GridPaneDestroy($this)
    _GDIPlus_GraphicsDispose($this.hMemGraphics)
    _WinAPI_SelectObject($this.hMemDC, $this.hOldBmp)
    _WinAPI_DeleteObject($this.hBitmap)
    _WinAPI_DeleteDC($this.hMemDC)
    _GDIPlus_GraphicsDispose($this.hGraphics)
    _WinAPI_ReleaseDC($this.hGUI, $this.hDC)
EndFunc   ;==>_GridPaneDestroy

Func _DisplayNumber($this, $number, $cellIndex)
    ; Define segments for each digit (0 to 9)
    Local $segments[10][7] = [ _
            [1, 1, 1, 1, 1, 1, 0], _ ; 0
            [0, 1, 1, 0, 0, 0, 0], _ ; 1
            [1, 1, 0, 1, 1, 0, 1], _ ; 2
            [1, 1, 1, 1, 0, 0, 1], _ ; 3
            [0, 1, 1, 0, 0, 1, 1], _ ; 4
            [1, 0, 1, 1, 0, 1, 1], _ ; 5
            [1, 0, 1, 1, 1, 1, 1], _ ; 6
            [1, 1, 1, 0, 0, 0, 0], _ ; 7
            [1, 1, 1, 1, 1, 1, 1], _ ; 8
            [1, 1, 1, 1, 0, 1, 1] _ ; 9
            ]

    ; Coordinates for the segments (x1, y1, x2, y2) relative to cell size
    Local $coords[7][4] = [ _
            [0.1, 0.1, 0.9, 0.1], _ ; A (top)
            [0.9, 0.1, 0.9, 0.5], _ ; B (top right)
            [0.9, 0.5, 0.9, 0.9], _ ; C (bottom right)
            [0.1, 0.9, 0.9, 0.9], _ ; D (bottom)
            [0.1, 0.5, 0.1, 0.9], _ ; E (bottom left)
            [0.1, 0.1, 0.1, 0.5], _ ; F (top left)
            [0.1, 0.5, 0.9, 0.5] _ ; G (middle)
            ]

    ; Calculate offsets
    Local $xOffset = $cellIndex * $this.cellWidth
    Local $yOffset = 0

    ; Add lines for the given digit with offsets
    For $i = 0 To 6
        If $segments[$number][$i] Then
            $this.AddLine(Line($coords[$i][0] * $this.cellWidth + $xOffset, $coords[$i][1] * $this.cellHeight + $yOffset, $coords[$i][2] * $this.cellWidth + $xOffset, $coords[$i][3] * $this.cellHeight + $yOffset, $COLOR_GREEN, 2))
        EndIf
    Next
EndFunc   ;==>_DisplayNumber

Func _DisplaySeparator($this, $cellIndex)
    Local $xOffset = $cellIndex * $this.cellWidth
    Local $yOffset = 0
    ; Draw two small dots to form the ":"
    $this.AddLine(Line($xOffset + 0.5 * $this.cellWidth, $yOffset + 0.3 * $this.cellHeight, $xOffset + 0.5 * $this.cellWidth, $yOffset + 0.4 * $this.cellHeight, $COLOR_RED, 4))
    $this.AddLine(Line($xOffset + 0.5 * $this.cellWidth, $yOffset + 0.6 * $this.cellHeight, $xOffset + 0.5 * $this.cellWidth, $yOffset + 0.7 * $this.cellHeight, $COLOR_RED, 4))
EndFunc   ;==>_DisplaySeparator

Func _DisplayFullTime($this)
    ; Clear current lines
    Local $aArray = $this.lines
    ReDim $aArray[0]
    $this.lines = $aArray

    ; Get the current time
    Local $hours = @HOUR
    Local $minutes = @MIN
    Local $seconds = @SEC

    ; Display each part of the time in separate cells
    $this.DisplayNumber(Int(StringMid($hours, 1, 1)), 0) ; First digit of hours
    $this.DisplayNumber(Int(StringMid($hours, 2, 1)), 1) ; Second digit of hours

    ; Add first separator
    $this.DisplaySeparator(2) ; First separator

    $this.DisplayNumber(Int(StringMid($minutes, 1, 1)), 3) ; First digit of minutes
    $this.DisplayNumber(Int(StringMid($minutes, 2, 1)), 4) ; Second digit of minutes

    ; Add second separator
    $this.DisplaySeparator(5) ; Second separator

    $this.DisplayNumber(Int(StringMid($seconds, 1, 1)), 6) ; First digit of seconds
    $this.DisplayNumber(Int(StringMid($seconds, 2, 1)), 7) ; Second digit of seconds

    $this.Draw()
EndFunc   ;==>_DisplayFullTime

Func _Resize($this)
    Local $width = _WinAPI_GetClientWidth($this.hGUI)
    Local $height = _WinAPI_GetClientHeight($this.hGUI)
    $this.cellWidth = $width / $this.numCells
    $this.cellHeight = $height

    ; Update memory bitmap size
    _GDIPlus_GraphicsDispose($this.hMemGraphics)
    _WinAPI_SelectObject($this.hMemDC, $this.hOldBmp)
    _WinAPI_DeleteObject($this.hBitmap)

    $this.hBitmap = _WinAPI_CreateCompatibleBitmap($this.hDC, $width, $height)
    $this.hOldBmp = _WinAPI_SelectObject($this.hMemDC, $this.hBitmap)
    $this.hMemGraphics = _GDIPlus_GraphicsCreateFromHDC($this.hMemDC)

    $this.DisplayFullTime()
EndFunc   ;==>_Resize

Func _Exit()
    $g___bEXIT = True
EndFunc   ;==>_Exit


; Example usage
Global $gridPane = GridPane(8, "Horloge", 480, 130)
AdlibRegister("displayTime", 1000)

While 1
    If $g___bEXIT Then ExitLoop
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $GUI_EVENT_RESIZED
            $gridPane.Resize()
    EndSwitch
WEnd

$gridPane = 0
_AutoItObject_Shutdown()
_GDIPlus_Shutdown()


Func displayTime()
    $gridPane.DisplayFullTime()
EndFunc   ;==>displayTime

 

image.png

Edited by Numeric1
Implementation of double buffering to prevent flickering during clock updates.
Link to comment
Share on other sites

Good work but it flickers on refresh. I would suggest to use double buffering to avoid flashing.

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

1 hour ago, UEZ said:

Good work but it flickers on refresh. I would suggest to use double buffering to avoid flashing.

Thank you @UEZ for the pertinent suggestion. I have edited and revised the code by applying double buffering, and everything works correctly. 

Link to comment
Share on other sites

Hi!

if it has to look more like a "real" 7-segment ...how about this? (I wrote it many years ago....@UEZ, do you remember^^)

#include <GDIPlus.au3>
#include <WindowsConstants.au3>
#include <GUIConstantsEx.au3>



Dim $ws[12] = [0, 0, 10, -7, 50, -7, 60, 0, 50, 7, 10, 7]  ;Koordinaten Polygonzug: von links nach rechts WAAGRECHTER Balken, von rechts nach links SENKRECHTER Balken
Dim $ziffer[10] = [239, 10, 118, 94, 154, 220, 253, 14, 254, 222] ;alle gesetzten bits des Indexes in der 7-segmentanzeige ergeben die Ziffer
Dim $balkenpos[8] = [0, "60.0", "0.0", "60.60", "0.60", "0.60", "0.120", "0.0"] ;position der Leuchtbalken der 7-Segmentanzeige innerhalb der Ziffer
Dim $p[7][2]                                               ;nimmt die polygonzug-koordinaten zum Zeichnen auf
$p[0][0] = 6                                               ;6 Punkte im Polygonzug



_GDIPlus_Startup()



Global $hgui = GUICreate('Uhr', 620, 175, -1, -1, $WS_POPUP) ;GUI erstellen ohne Rahmen
Global $hWnd = WinGetHandle($hgui)                         ;Handle holen
Global $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hWnd)  ;"Leinwand" erstellen, auf der gezeichnet werden kann
GUICtrlCreateLabel("", 0, 0, 620, 175, Default, $GUI_WS_EX_PARENTDRAG) ;verschieben des Fensters möglich machen durch 2. Fenster
GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT)            ;das 2. Fenster transparent machen
GUISetBkColor(0x000000)                                    ;Hintergrund der GUI schwarz
GUISetState()                                              ;GUI anzeigen
AdlibRegister("_time", 1000)                               ;jede Zehntelsekunde ein Refresh der Zeitanzeige
Do                                                         ;Endlosschleife, solange bis..
Until GUIGetMsg() = -3                                     ;..ESC gedrückt wird
_GDIPlus_GraphicsDispose($hGraphic)                        ;freigeben
_GDIPlus_Shutdown()



Func _time()                                               ;Uhrzeit anzeigen :o)
    For $k = 1 To 6                                        ;die 6 Ziffern der Uhrzeit
        $setbits = $ziffer[StringMid(String(@HOUR & @MIN & @SEC), $k, 1)] ;gesetzte Bits in der siebensegmentanzeige anhand der Ziffer in der Uhrzeit holen
        For $bitnr = 7 To 1 Step -1                        ;alle Bits durchlaufen
            _drawpolygon(BitAND($setbits, 128), $k * 100 - 80 + ($k = 1 Or $k = 3 Or $k = 5) * 20, $bitnr) ;parameter: bit gesetzt ja/nein, position der gesamten ziffer,nummer des bits(gerade=waagrechter balken, ungerade=senkrechter balken)
            $setbits = BitShift($setbits, -1)              ;nächstes Bit holen
        Next
    Next
    $brush = _GDIPlus_BrushCreateSolid(0xFF440000 + (@SEC / 2 = Int(@SEC / 2)) * 0xBB0000) ;Pinsel erstellen, wenn Ziffer gerade, dann farbig, ansonsten schwarz
    _GDIPlus_GraphicsFillEllipse($hGraphic, 202, 55, 15, 15, $brush) ;Punkte zeichnen
    _GDIPlus_GraphicsFillEllipse($hGraphic, 402, 55, 15, 15, $brush)
    _GDIPlus_GraphicsFillEllipse($hGraphic, 202, 105, 15, 15, $brush)
    _GDIPlus_GraphicsFillEllipse($hGraphic, 402, 105, 15, 15, $brush)
    _GDIPlus_BrushDispose($brush)                          ;Pinsel auflösen
EndFunc                                                    ;==>_time



Func _drawpolygon($bit, $xpos, $bitnr)                     ;zeichnet einen polygonzug ("Balken") an die entsprechende Position
    $split = StringSplit($balkenpos[$bitnr], ".", 3)       ;x- und y-koordinaten des Balkens innerhalb der Ziffer holen
    $b = (($bitnr / 2) = Int($bitnr / 2))                  ;$bit gerade => $b = true => Balken waagrecht, ansonsten Balken senkrecht
    $step = -1 + 2 * $b                                    ;schrittweite durch das $WS-Array
    For $i = 11 - 11 * $b To 11 * $b Step 2 * $step        ;array mit waagrechten (bit=gerade) oder (Bit=ungerade) senkrechten Balken füllen
        $r = Int(Abs((11 * (Not ($b))) - $i) / 2) + 1      ;abhängig von der Reihenfolge, egal ob $i von 0 bis 11 oder von 11 bis 0, $r muss immer 1,2,3,4,5,6
        $p[$r][0] = $ws[$i] + $split[0] + $xpos            ;x- und
        $p[$r][1] = $ws[$i + $step] + $split[1] + 30       ;y-position in das polygonarray schreiben
    Next
    $brush = _GDIPlus_BrushCreateSolid(0xFF440000 + ($bit <> 0) * 0xBB0000) ;wenn bit gesetzt, dann farbig, ansonsten schwarz
    _GDIPlus_GraphicsFillPolygon($hGraphic, $p, $brush)    ;Balken zeichnen
    _GDIPlus_BrushDispose($brush)
EndFunc                                                    ;==>_drawpolygon

If you want to know how it works....read the code!:muttley:

Link to comment
Share on other sites

And if we're going to do a proper comparison then one should include the efforts of @timmy2 + @UEZ with enhancements by @Beege, @MrCreatoR, and @Malkey (cobbled together by yours truly).

Link to comment
Share on other sites

I highlighted this in my first message. However, I am rather referring to the rigidity of your codes. My clock, for example, allows for: a change of color, the resizing of the graphical interface, as well as the display of only the hour, the hour and minutes, or the hour, minutes, and seconds. And all of this without having to modify the code. Here, I am expressing the inherent flexibility of object-oriented programming.

 

; Example usage of the GridPane function
; GridPane($numCells, $title, $width, $height)

; You can easily omit the seconds
; By setting the parameter $numCells to 5, the seconds are omitted.
Global $gridPane = GridPane(5, "Clock", 480, 130)

; You can display only the hours if you want
; By setting the parameter $numCells to 2, only the hours are displayed.
Global $gridPane = GridPane(2, "Clock", 480, 130)

 

Link to comment
Share on other sites

1 hour ago, Numeric1 said:

I am rather referring to the rigidity of your codes. My clock, ....

I see you making a valiant push for more OO code. Wouldn't it be beautiful if the whole of AutoIt was written as OO ?
In any case, as handy as the AutoItObject is, if I can code something in Vanilla AutoIt, I'll take that route.

And thanks for all the OO examples you are posting. They are simple to understand and take OO for a spin :)

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Link to comment
Share on other sites

1 hour ago, argumentum said:

I see you making a valiant push for more OO code. Wouldn't it be beautiful if the whole of AutoIt was written as OO ?:)

What would be interesting is to integrate more object-oriented programming into AutoIt. I don't want all of AutoIt to be object-oriented, but we could add some. This mix would enhance AutoIt's conceptual capabilities. It's true that a train cannot run off the tracks, but it can go faster and further.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...