Ha valamilyen digitális kijelzővel rendelkező készüléket vagy eszközt (pl. digitális óra, LCD kijelzős számológép, műszer stb.) szeretnénk programmal modellezni, akkor a hét szegmensből álló számjegyeket meg kell tudni jeleníteni a grafikus felhasználói felületen.
Ezt célszerű azzal kezdeni, hogy megtervezzük a hétszegmenses számjegyek stílusát, formáját, vagyis azt, hogy az egyes szegmensek milyen síkalakzatok legyenek, illetve azt, hogy az alkotó szakaszok egymáshoz képes milyen arányúak. Mivel a digitális kijelzőkön a szegmensek elkülönülnek egymástól, ezért a közöttük levő rést (gap) is célszerű beállíthatóvá tenni, ami hatással van a megjelenő számformára.
A tervezési és programozó munkánk jelentősen könnyebbé válik, ha egyszerűsítő feltételként előírjuk, hogy a számjegyek vízszintesen és függőlegesen is szimmetrikusak. A szegmensek tervezéséhez a jelöléseket – amik egyúttal a programban változónevek is lesznek – a következő ábra mutatja két, részben eltérő szegmensekből álló, számjegyformákra.

Mivel szimmetrikus elrendezésűek a szegmensek, ezért elegendő csak pl. a felső (TOP), bal felső (TOP_LEFT) és a középső (CENTER) szegmenseket megtervezni, mert a többi szegmens ezekből a szélességet és hosszúságot felező tengelyre vett tükrözéssel előállítható.
A megvalósítás egy lehetséges programkódját követhetjük alább, három modulba szervezve.
A fenti ábrán mutatott két megjelenési formátumú számjegyeket a seven_segment_model modulban a DigitSegmentDefinitions két alosztálya (Digit1SegmentDefinitions és Digit2SegmentDefinitions) definiálja a szegmenseket meghatározó pontok relatív távolságainak megadásával. Ha egy ezektől különböző számjegyformát akarunk, akkor azt a DigitSegmentDefinitions egy új alosztályának létrehozásával tehetjük meg és adhatjuk meg az egyes szegmenspontok relatív távolságát a meglévő két alosztályhoz hasonló módon.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# modul: seven_segment_model.py from __future__ import annotations from typing import NamedTuple from enum import Enum from math import isclose class Point(NamedTuple): x: int | float y: int | float def shift(self, dx, dy): """Olyan új ponttal tér vissza, amelynek koordinátái a self példányéhoz képest az argumentumban megadott értékkel el vannak tolva.""" return type(self)(self.x + dx, self.y + dy) def reflect_about_line(self, p1: Point, p2: Point): """A pontnak a p1 és p2 pontok által meghatározott függőleges vagy vízszintes tengelyre vett tükörkép pontjával tér vissza. """ if isclose(p1.x, p2.x): return Point(p1.x + (p1.x - self.x), self.y) if isclose(p1.y, p2.y): return Point(self.x, p1.y + (p1.y - self.y)) # A szegemensek pozíciójának azonosítását meghatározó konstansok felsorolástípusban. SegmentId = Enum('SegmentId', 'TOP TOP_LEFT TOP_RIGHT CENTER BOTTOM_LEFT BOTTOM_RIGHT BOTTOM') class DigitSegmentDefinitions: """Egy adott felépítésű számjegy szegmenseinek leírásához a szegmenseket meghatározó pontok relatív távolságait tárolja, amelyek alapján a get_segment_points metódus előállítja szegmenspontokat az origótól vett koordinátákkal a szegmensszélesség és -magasság alapján. """ def __init__(self, segment_width, segment_height, gap): self.segment_width, self.segment_height = segment_width, segment_height self.gap = gap self.ssh = dict() # Adott szegmenshez tárolja a pontok relatív távolságait. def get_segment_points(self, segment_id: SegmentId) -> list[Point]: shifts: list = self.ssh[segment_id] points: list = [Point(0, 0).shift(*shifts[0])] for dx, dy in shifts[1:]: points.append(points[-1].shift(dx, dy)) return points class Digit1SegmentDefinitions(DigitSegmentDefinitions): """Adott felépítésű számjegy szegmenseinek leírásához a TOP, TOP_LEFT és CENTER szegmenseket meghatározó pontok relatív távolságainak megadása. """ def __init__(self, segment_width, segment_height, gap): super().__init__(segment_width, segment_height, gap) w, h = self.segment_width, self.segment_height self.ssh = {SegmentId.TOP: [(0, 0), (w, 0), (-h, +h), (-(w - 2 * h), 0)], SegmentId.TOP_LEFT: [(0, 0), (h, h), (0, (w - 2 * h)), (-h / 2, h / 2), (-h / 2, -h / 2)], SegmentId.CENTER: [(h / 2, h / 2), (h / 2, -h / 2), ((w - 2 * h), 0), (h / 2, h / 2), (-h / 2, +h / 2), (-(w - 2 * h), 0)]} class Digit2SegmentDefinitions(DigitSegmentDefinitions): """Adott felépítésű számjegy szegmenseinek leírásához a TOP, TOP_LEFT és CENTER szegmenseket meghatározó pontok relatív távolságainak megadása. """ def __init__(self, segment_width, segment_height, gap): super().__init__(segment_width, segment_height, gap) w, h = self.segment_width, self.segment_height self.ssh = {SegmentId.TOP: [(h / 2, h / 2), (h / 2, -h / 2), ((w - 2 * h), 0), (h / 2, h / 2), (-h / 2, +h / 2), (-(w - 2 * h), 0)], SegmentId.TOP_LEFT: [(h / 2, h / 2), (h / 2, h / 2), (0, (w - 2 * h)), (-h / 2, h / 2), (-h / 2, -h / 2), (0, -(w - 2 * h))]} self.ssh[SegmentId.CENTER] = self.ssh[SegmentId.TOP] class Digit7SegmentsPoints: """A számjegy felépítése a szegmensekből.""" def __init__(self, segment_definition: DigitSegmentDefinitions): self.segments: dict[SegmentId, list[Point]] = {} # A számjegy szélessége megegyezik a szegmensszélességgel. self.width = segment_definition.segment_width # Az egyes szegmensek számjegyen belüli pozicionálása. # Mivel a megtervezett számjegyek mind a magasság, mind a szélesség felezővonalra szimmetrikusak, így # a TOP, TOP_LEFT és CENTER szegmensekből a többi tengelyes tükrözéssel származtatható. s_top = segment_definition.get_segment_points(SegmentId.TOP) s_topleft = [p.shift(0, segment_definition.gap) for p in segment_definition.get_segment_points(SegmentId.TOP_LEFT)] # A TOP_LEFT legalsó pontja, amihez képest a CENTER gap mértékkel lefelé el lesz tolva. pshift: Point = max(s_topleft, key=lambda p: p.y) s_center = [p.shift(0, pshift[1] - segment_definition.segment_height / 2).shift(0, segment_definition.gap) for p in segment_definition.get_segment_points(SegmentId.CENTER)] # Vertikális tükrözési tengely két pontja, amely a felső szegmens szélességének felezővonala. pv1 = Point(self.width / 2, 0) pv2 = Point(self.width / 2, 10) # Horizontális tükrözési tengely két pontja, amely a középső szegmens vízszintes felezővonala. ph1 = min(s_center, key=lambda p: p.x) ph2 = max(s_center, key=lambda p: p.x) # A topright a topleft vertikalis tükörképe. s_topright = [p.reflect_about_line(pv1, pv2) for p in s_topleft] # A bottomleft a topleft horizontális tükörképe. s_bottomleft = [p.reflect_about_line(ph1, ph2) for p in s_topleft] # A bottomright a bottomleft vertikális tükörképe. s_bottomright = [p.reflect_about_line(pv1, pv2) for p in s_bottomleft] # A bottom a top horizontális tükörképe. s_bottom = [p.reflect_about_line(ph1, ph2) for p in s_top] # Létrejövő szegmensek pontjainak eltárolás. self.segments.update({SegmentId.TOP: s_top, SegmentId.TOP_LEFT: s_topleft, SegmentId.TOP_RIGHT: s_topright, SegmentId.CENTER: s_center, SegmentId.BOTTOM_LEFT: s_bottomleft, SegmentId.BOTTOM_RIGHT: s_bottomright, SegmentId.BOTTOM: s_bottom}) # A számjegy tényleges magasságát csak a szegmenspozíciók után tudjuk meghatározni. self.height = max(s_bottom, key=lambda p: p.y).y |
A számjegy, illetve azt alkotó szegmensek tényleges pontjait valamely DigitSegmentDefinitions alosztályból a modul Digit7SegmentsPoints osztálya állítja elő.
Ha a szegmensek definiálása megvan, akkor a kívánt számjegyeket grafikusan a seven_segment_digit_widget modul Digits7Segments osztály példányosításával lehet előállítani.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# modul: seven_segment_digit_widget.py from seven_segment_model import SegmentId, Digit7SegmentsPoints import seven_segment_model as model import tkinter as tk class One7SegmentDigit(tk.Frame): """Egy olyan grafikus elemet valósít meg, amely a render_digit() metódusnak str vagy int típusú argumentumként átadott számjegyet hétszegmenses számjegy formájában rajzolja ki. """ def __init__(self, master, digits_7segments_points: Digit7SegmentsPoints): super().__init__(master) self.digits_7segments_points = digits_7segments_points # A kirajzolandó számjegy szegmenseinek pontjai. # Egy számjegy rajzelemeit tartalmazó vászon elem létrehozása. self.cnv: tk.Canvas = tk.Canvas(self, width=digits_7segments_points.width, height=digits_7segments_points.height, highlightthickness=0) self.cnv.pack() # A szegmenseket grafikusan megvalósító sokszögek létrehozása a szegmensek modellbeli pontjai alapján. # A sokszögekhez egy közös, valamint a szegmensek elrendezési pozíciója szerinti egyedi tageket rendelünk. self.all_segment_tags = set() for key, segment_points in digits_7segments_points.segments.items(): self.all_segment_tags.add(key.name) self.cnv.create_polygon(*segment_points, fill='black', state=tk.HIDDEN, tag=('SEGMENT', key.name)) def render_digit(self, num: int | str): """A megadott számjegy kirajzolása a vásznon a megfelelő sokszögek megjelenítésének engedélyezésével.""" num = str(num) self.cnv.itemconfig('SEGMENT', state=tk.HIDDEN) # Induláskor minden szegmens megjelenítése le van tiltva. # Az egyes számjegyekhez hozzárendeljük a megjelenítendő szegmensek azonosítóit. num_segments_to_be_displayed = {'0': self.all_segment_tags - {SegmentId.CENTER.name}, '1': {SegmentId.TOP_RIGHT.name, SegmentId.BOTTOM_RIGHT.name}, '2': self.all_segment_tags - {SegmentId.TOP_LEFT.name, SegmentId.BOTTOM_RIGHT.name}, '3': self.all_segment_tags - {SegmentId.TOP_LEFT.name, SegmentId.BOTTOM_LEFT.name}, '4': {SegmentId.TOP_LEFT.name, SegmentId.TOP_RIGHT.name, SegmentId.CENTER.name, SegmentId.BOTTOM_RIGHT.name}, '5': self.all_segment_tags - {SegmentId.TOP_RIGHT.name, SegmentId.BOTTOM_LEFT.name}, '6': self.all_segment_tags - {SegmentId.TOP_RIGHT.name}, '7': {SegmentId.TOP.name, SegmentId.TOP_RIGHT.name, SegmentId.BOTTOM_RIGHT.name}, '8': self.all_segment_tags, '9': self.all_segment_tags - {SegmentId.BOTTOM_LEFT.name}} # A megjelenítendő szegmensek kirajzolását engedélyezzük. for tg in num_segments_to_be_displayed.get(num, ()): self.cnv.itemconfig(tg, state=tk.NORMAL) return self def config(self, **kw): """Felülírt config metódus, hogy a szegmensek színét be lehessen állítani.""" if 'segment_color' in kw: self.cnv.itemconfig('SEGMENT', fill=kw['segment_color']) class Digits7Segments(tk.Frame): """Egy olyan grafikus elemet valósít meg, amely a konstruktorban a 'digits' str vagy int típusú argumentummal meghatározott számjegyeket a megadott szélességű hétszegmenses számjegyek formájában rajzolja ki egymást követően, a 'segment_color' színben, és 'segmentsdefinitions_class' osztály által definiált stílusban. A szegmensek magassága és szélessége, valamint a szegmensek között függőleges rés opcionálisan megadható. """ def __init__(self, master, digits: int | str, width: int, segment_height: int | None = None, gap: int | None = None, *, segment_color: str = 'black', segmentsdefinitions_class: type[model.DigitSegmentDefinitions] = model.Digit1SegmentDefinitions): super().__init__(master) if not isinstance(digits, (int, str)): raise ValueError('A megjelenítendő számsorozat csak int vagy str típusú lehet.') elif type(digits) is str: if not digits.isdecimal(): raise ValueError('A megjelenítendő karakersorozat csak decimális számjegyeket tartalmazhat.') self.digits = str(digits) self.width = width self.segment_height = width * 0.2 if segment_height is None else segment_height self.gap = width * 0.025 if gap is None else gap self.segment_color = segment_color self.segmentsdefinitions_class = segmentsdefinitions_class for digit in self.digits: one_digit = One7SegmentDigit(self, Digit7SegmentsPoints( segmentsdefinitions_class(self.width, self.segment_height, self.gap))).render_digit(digit) one_digit.config(segment_color=self.segment_color) one_digit.pack(side=tk.LEFT, padx=self.width*0.1) |
A számjegyek egymást követően lesznek lehelyezve az osztálypéldányban mint tkinter keretben (Frame). A konstruktor első, master argumentuma a szülő grafikus elem, amely az osztálypéldányt menedzseli. A második, digits argumentummal a megjelenítendő számjegyeket kell megadni karakterláncként vagy int típusú pozitív számként. A harmadik, width argumentum a számok szélessége pixelben, ami egyben a szegmensszélesség is. A szegmensmagasságot és a szegmensek közötti függőleges rést opcionálisan lehet megadni a segment_height és gap argumentumokkal. Ha ezek az alapértelmezett None értéken vannak, akkor egy előre beállított, a szegmensszélességhez igazodó értékeket kapnak. Ezeket követően két, csak kulcsszavas opcionális argumentumot lehet megadni. A segment_color a számjegy, azaz a szegmensek színének beállítására szolgál, amit a szín érvényes nevével vagy színkóddal lehet megadni.
A Digits7Segments a szegmensek pontjai alapján rajzolja meg a számjegyeket, azaz a szegmenseket reprezentáló sokszögeket. Ehhez a Digits7Segments példányosításakor a szegmenseket leíró valamely DigitSegmentDefinitions alosztályt kell megadni az opcionális segmentsdefinitions_class argumentummal, amelynek alapértelmezett értéke a seven_segment_model modul Digit1SegmentDefinitions osztálya.
A seven_segment_digit_widget modul One7SegmentDigit osztálya alapvetően egy segédosztály, de önállóan is alkalmazható, ha csupán egyetlen számjegyet akarunk létrehozni, bár ezt a Digits7Segments osztálypéldánnyal ugyanúgy megtehetjük.
A seven_segment_test_app modul szkriptként futtatva bemutatja a Digits7Segments használatát és a hétszegmenses számjegyek megjelenítését. A grafikus beviteli felületen meghatározhatjuk a megjelenítendő számjegyeket, azok színét és méretét, valamint egy választógombbal a számjegyek formáját/stílusát váltogathatjuk.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# modul: seven_segment_test_app.py import seven_segment_model as model from seven_segment_digit_widget import Digits7Segments import tkinter as tk from tkinter import colorchooser class SevenSegmentDigitsTestApp(tk.Tk): def __init__(self): super().__init__() self.title('Hétszegmenses számjegyek') # A főablak két részre van osztva két keretobjektummal. # A felsőben a beviteli űrlap szerepel, az alsóban a hétszegmenses számok jelennek meg. self.frame_top, self.frame_bottom = tk.Frame(self, borderwidth=1, relief=tk.SOLID), tk.Frame(self) # Grafikus elemek létrehozása. # A megjeleníteni kívánt számjegyek beviteli mezője a hozzátartozó címkével. self.digits = tk.StringVar(self, value='1234567890') lbl1 = tk.Label(self.frame_top, text='Számjegyek:', font=('Arial', 14)) ebx1 = tk.Entry(self.frame_top, font=('Consolas', 16, 'bold'), textvariable=self.digits) # A megjeleníteni kívánt számjegyek színének beviteli mezője a hozzátartozó címkével. Itt nem csak a szín # kódját, hanem a szín érvényes nevét is be lehet írni. Egy külön nyomógombbal tetszőleges szín választható # a megjelenő palettáról. self.digit_color = tk.StringVar(self, value='black') lbl2 = tk.Label(self.frame_top, text='Számjegyszín:', font=('Arial', 14)) ebx2 = tk.Entry(self.frame_top, font=('Consolas', 16, 'bold'), textvariable=self.digit_color) btn_color_selection = tk.Button(self.frame_top, text='Színválasztás', font=('Arial', 14), command=lambda: self.digit_color.set( c if (c := colorchooser.askcolor(color='black')[1]) is not None else self.digit_color.get())) # A megjeleníteni kívánt számjegyek pixelben mért szélességének beviteli mezője a hozzátartozó címkével. self.digit_width = tk.StringVar(self, value='60') lbl3 = tk.Label(self.frame_top, text='Számjegyszélesség:', font=('Arial', 14)) ebx3 = tk.Entry(self.frame_top, font=('Consolas', 16, 'bold'), textvariable=self.digit_width) # A megjelenítendő számjegyek stílusának kiválasztását lehetővé tevő választógombok a hozzátartozó címkével. # Stílusváltás esetén a számok azonnal megjelennek az új kinézettel. lbl4 = tk.Label(self.frame_top, text='Számjegytípus:', font=('Arial', 14)) self.rbvar = tk.IntVar(self, value=1) rb_common_configs = dict(variable=self.rbvar, font=('Arial', 14), indicatoron=True, anchor=tk.W) rb1 = tk.Radiobutton(self.frame_top, **rb_common_configs, text='stílus1', value=1, command=self.create_digits) rb2 = tk.Radiobutton(self.frame_top, **rb_common_configs, text='stílus2', value=2, command=self.create_digits) # A beviteli mezők tartalma szerinti számjegyek megjelenítése. btn_display = tk.Button(self.frame_top, text='Megjelenítés', font=('Arial', 14, 'bold'), command=self.create_digits) # Grafikus elemek lehelyezése. self.frame_top.pack(fill=tk.BOTH) self.frame_bottom.pack(fill=tk.BOTH, pady=15) lbl1.grid(row=0, column=0, sticky='e') ebx1.grid(row=0, column=1) lbl2.grid(row=1, column=0, sticky='e') ebx2.grid(row=1, column=1) btn_color_selection.grid(row=1, column=2) lbl3.grid(row=2, column=0, sticky='e') ebx3.grid(row=2, column=1) lbl4.grid(row=3, column=0, sticky='e') rb1.grid(row=3, column=1, sticky='w') rb2.grid(row=4, column=1, sticky='w') btn_display.grid(row=5, column=0, sticky='w') self.segments_def_classes = {1: model.Digit1SegmentDefinitions, 2: model.Digit2SegmentDefinitions} def create_digits(self): """Új hétszegmenses számsorozat előállítása az aktuális jellemzők alapján.""" # Az alsó keretből az eddigi számsorozat törlése. for d in self.frame_bottom.slaves(): if type(d) is Digits7Segments: d.destroy() width: int = int(w) if (w := self.digit_width.get()) else 60 segments_def_class = self.segments_def_classes[self.rbvar.get()] digit_7segments = Digits7Segments(self.frame_bottom, self.digits.get(), width, segment_color=self.digit_color.get(), segmentsdefinitions_class=segments_def_class) digit_7segments.pack(side=tk.LEFT) # Lehelyezés az alsó keretben. def run(self): self.mainloop() if __name__ == '__main__': app = SevenSegmentDigitsTestApp() app.run() |
A modulokban az osztályok felépítésének és működésének megértését a fenti leírások mellett a részletes kommentek segítik. A program Python 3.10+ alatt fut.
Néhány eredményképet az az alábbi ábra mutat.

A modulok forráskódja a következő GitHub linken is elérhető: https://github.com/pythontudasepites/seven_segment_digits
E bejegyzésben programozási szempontból elsődlegesen a grafikus felhasználói felület tervezése és megvalósítása volt a fókuszban, amelyhez ismereteket a Python tudásépítés lépésről lépésre című e-könyvben a „Grafikus felhasználói felület készítése” fejezetben lehet részletesen olvasni. Ugyanakkor sok más nyelvi elemet és lehetőséget is alkalmaztunk, beleértve a szabványos könyvtár számos más moduljának használatát is, amelyekről az e-könyv „Készétel fogyasztás – a szabványos könyvtár moduljainak használata” fejezeten belül szerezhetünk ismereteket feladatorientáltan, számos példakóddal segítve a megértést. E mellett a programtervezési és kódolási készségeket nagyban fejleszti a bemutatott, illetve ehhez hasonló kicsit összetettebb programkódok tanulmányozása, és a feladat vagy egy részprobléma más megközelítéssel történő megoldásának megkísérlése.