|
|
Idő kiválasztó komponens, avagy WeekGrid
|
|
Példaprogram letöltése
10879 bájt
|
Ebben a példában egy olyan komponenst készítünk, ami egy táblázatot jelenít meg, amelyben a hét napjai adják a sorokat, az órák pedig az oszlopokat. A táblázat celláiban igaz vagy hamis értékeket tárolunk, melyeket különböző színekkel jelölünk. A komponens jól használható például arra, hogy megadjuk, egy felhasználó a hét mely napjain, és azon belül milyen órákban jelentkezhet be a programba.
A mellékelt példaprogram megnyitása előtt a WeekGrid.pas-ban lévő komponenst telepítenie kell a Delphi alá.
Először nézzük, hogy hogyan működik maga a komponens felhasználói szemszögből. Amikor a felhasználó kijelöl egy cellát a táblázatban, akkor a fókuszt jelző keret (kijelölés) arra a cellára áll. Ha még egyszer ugyanezen a cellán kattint, akkor a cella értéke az ellenkezőjére változik. Ha kattint a cellán, de nem engedi fel az egérgombot, hanem elhúzza egy másik cellára, akkor a két cella közötti összes többi cella is kijelölt lesz. Ebben az esetben ezeknek a celláknak az értéke egyszerre állítható, például a szóköz billentyűvel. Ha a Minden feliratú gombra kattint, akkor egyrészt kijelöli az összes cellát, másrészt az összes cella ugyanazon értékre vált. Az érték igaz, ha előzőleg nem volt az összes cella kijelölve, és hamis, ha igen. Lehetőség van teljes sorok és oszlopok kijelölésére is a nap nevére, vagy az oszlop fejlécére kattintva. Ebben az esetben is lehet az egér mozgatásával több sort vagy oszlopot kijelölni. A kijelölt cellák értéke hasonlóan változik, mint a Minden gomb használatánál.
A kijelölést lehet a billentyűzetről is mozgatni a kurzorbillentyűk használatával. Ha a Shift gombot is nyomva tartjuk közben, akkor a kijelölés bal felső cellája lesz a fix pont, az összes többi a lenyomott billentyűknek megfelelően változik. Így lehetőség van a billentyűzetről is több cella kijelölésére. A kijelölt cellák értékét a szóköz vagy Enter billentyűkkel lehet szabályozni. A Delete gomb a kijelölt cellák értékét hamisra, míg az Insert gomb igazra állítja.
A komponenst a TCustomControl osztályból származtatjuk. Ebben már eleve van több olyan property is, amit jól fel tudunk használni a készítendő komponensnél, és a programozásával nem kell foglalkozni (pl. Align, Color, stb.), de a komponens számos új property-vel is rendelkezik. Ezek a következők:
AlwaysShowFocus: logikai érték, ami azt mondja meg, hogy a cellakijelölés akkor is látszik-e, ha nem a komponens birtokolja a fókuszt.
ColorActiveCell: azoknak a celláknak a színe, amiknek az értéke igaz.
ColorInactiveCell: a hamis értékű cellák színe.
CellWidth: az oszlopok szélessége. Az első oszlop szélessége (amiben a napok nevei találhatók) ettől, és a komponens szélességétől függ.
LabelsAlignment: az első oszlopban található feliratok vízszintes igazítása.
LabelsLayout: a feliratok függőleges igazítása.
OwnerDraw: logikai érték. Igaz esetén a kirajzolásnál használja az OnDrawCell, OnDrawRowCell és OnDrawColumnCell eseményeket.
OnDrawCell: az érték cellák kirajzolásánál létrejövő esemény, ami lehetőséget ad a cellák egyedi megjelenítésére. Ehhez egy új eseményt kell deklarálni, ami a következőképpen néz ki:
TDrawCellEvent = procedure(Sender: TObject; rect: TRect; Day: TWeekDay; Hour: THour) of object;
Ha használjuk az eseményt, de az eredeti megjelenítést szeretnénk használni, akkor használjuk a DefaultDrawCell eljárást, aminek a Sender-en kívül ugyanezek a paraméterei.
A Rect paraméter a cella koordinátáit, a Day a napot (0-6) és a Hour az órát (0-23) tartalmazza.
OnDrawRowCell: az első oszlop celláinak kirajzolásakor létrejövő esemény, amiben ezeknek a celláknak a megjelenítését végezhetjük el.
TDrawRowCellEvent = procedure(Sender: TObject; rect: TRect; Row: integer; text: string) of object;
Ha az eredeti megjelenítést szeretnénk használni, akkor hívjuk meg DefaultDrawRowCell eljárást. Ennek csupán két paraméterre van szüksége, a Rect-re és a Text-re.
OnDrawColumnCell: az oszlopok fejlécének kirajzolásakor létrejövő esemény. Az oszlop fejlécek az alapesetben üres négyzetek, amik az óra feliratok alatt láthatók. Az óra feliratok minden esetben megjelennek, a hold és nap képekkel együtt. Ebben az eseményben a négyzetek területére rajzolhatunk.
TDrawColumnCellEvent = procedure(Sender: TObject; rect: TRect; Column: integer) of object;
Ha az eredeti fejlécet szeretnénk megjeleníteni, akkor használjuk a DefaultDrawColumnCell eljárást. Ennek két paramétere van: a Rect és a Hour, ami megegyezik a Column-nal. A Minden feliratú gomb nem oszlop fejlécnek, hanem sor fejlécnek tekintendő, ezért annak megjelenítését az OnDrawRowCell eseménynél módosíthatjuk.
A cellák értékeit az FCells változóban tároljuk. Ennek típusa TWeekCells, aminek a deklarációja így néz ki:
TWeekCells = array[0..6, 0..23] of boolean;
Látható, hogy ez egy kétdimenziós tömb, amiben logikai értékeket tárolunk. Az első index a nap sorszáma, míg a második az óra.
A kijelölés két TPoint típusú változóban van tárolva. A kijelölés kezdetét az FFocusStart, míg a végét az FFocusEnd változóban tároljuk. Az nem biztos, hogy a FFocusStart a bal felső cella, az FFocisEnd pedig a jobb alsó cella pozícióját tartalmazza!
A hold és nap képeket erőforrásként tároljuk, és a konstruktorban onnan töltjük be:
FSunPic:=TBitmap.Create;
FSunPic.Handle:=LoadBitmap(HInstance, 'SUN');
FMoonPic:=TBitmap.Create;
FMoonPic.Handle:=LoadBitmap(HInstance, 'MOON');
A komponensnek figyelnie kell az egérkattintás, mozgatás és a gomb felengedésének eseményét, ezért a MouseDown, MouseMove és MouseUp eljárásokat felül kell írnunk.
Attól függően, hogy a felhasználó hova kattintott a komponensen, különböző állapotokat állítunk be, amit az FState változóban tárolunk. Ennek típusa TWeekGridState:
TWeekGridState = (wsUnknown, wsSelectAll, wsRowSelect, wsColumnSelect, wsCellSelect);
wsUnknown: ez az az állapot, amikor nem történik semmi.
wsSelectAll: a felhasználó a Minden gombra kattintott.
wsRowSelect: sorok kijelölése van folyamatban.
wsColumnSelect: oszlopok kijelölése van folyamatban.
wsCellSelect: cellák kijelölése van folyamatban.
Minden állapot addig tart csak, amíg a felhasználó fel nem engedte az egérgombot.
Az állapot változás minden esetben a MouseDown eljárásban történik meg. Nézzük ezt meg egy kicsit részletesebben. Az esemény mindig abban az esetben következik be, amikor a felhasználó kattint a komponensen valamelyik egérgombbal. Mi csak a bal egérgombot figyeljük, ezért első lépésben ezt kell ellenőrizni (Button=mbLeft). Ebben az esetben a komponensre kell állítani a fókuszt, ha még nem azon volt:
if not (csDesigning in ComponentState) and not Focused then begin
Windows.SetFocus(Handle);
end;
A cell változó TPoint típusú, ebben tároljuk el annak a cellának a pozícióját, amelyikre a felhasználó kattintott. Ehhez a CellAtPos függvényt használjuk fel. Ennek két paramétere van: az egér X és Y koordinátája. A függvény visszatérési értéke egy TPoint típusú változó. Ha a felhasználó nem cellára kattintott, akkor a cella koordinátái negatív számok. Ebben az esetben nem történik semmi.
Itt kell szólnunk az FApplyChanges globális logikai változóról. Ennek csak a wsSelectAll és wsCellSelect állapotnál van értelme. Kezdetben igazra állítjuk, de például a wsSelectAll állapotnál, ha a felhasználó lehúzza az egeret a gombról mielőtt felengedné az egérgombot, az értékét hamisra állítjuk. A gomb felengedésénél a fent említett két állapotnál figyeljük ennek a változónak az értékét, és a cellák értékét csak akkor változtatjuk meg, ha ennek értéke igaz.
Ha a cella X és Y koordinátája is 0, akkor az a Minden gomb, tehát beállítjuk az FState változó értékét wsSelectAll-ra, és a kijelölést az összes cellára.
Ha az előbbi feltétel nem igaz, akkor három eset lehetséges. A sorok fejlécére (napok nevei), az oszlopok fejlécére, vagy a cellákra kattintott a felhasználó. Menjünk tovább azon a vonalon, hogy a felhasználó vagy sor, vagy oszlop fejlécre kattintott. Ebben az esetben szükséges a cellák értékeinek mentése az FBackup változóba, ami szintén TWeekCells típusú:
Ha a cell változó x mezőjének értéke 0, akkor sort kell kijelölni. Az FSelect változóba eltároljuk azt a logikai értéket, amit a kijelölt cellák fel fognak venni. Ez igaz, ha a sorban található olyan mező, aminek értéke hamis, és hamis, ha az összes mező értéke igaz. Ez ugyanígy fog működni az oszlop kijelölés esetében is. A kijelölést beállítjuk az adott sorra, és ha megvan az FSelect változó értéke, akkor a kijelölt sorban ezt be is állítjuk minden cellára a SelectFullDay eljárással. Ennek első paramétere a sor, a második pedig az érték. Azt, hogy egy sor minden cellájának értéke igaz-e, a FullDaySelected függvénnyel kérdezhető le. Ennek egyetlen paramétere a sor száma.
FState:=wsRowSelect;
FSelect:=not FullDaySelected(cell.y-1);
SelectFullDay(cell.y-1, FSelect);
Ha a cell változó y mezőjének értéke 0, akkor oszlop kiválasztás történik. Ennek lépései megegyeznek a sor kiválasztásáéval, csak a használt függvények és eljárások mások. Azt, hogy egy oszlop minden cellája ki van-e jelölve a FullHourSelected függvénnyel kérdezhető le, a cellák értékei pedig a SelectFullHour eljárással történhet.
FState:=wsColumnSelect;
FSelect:=not FullHourSelected(cell.x-1);
SelectFullHour(cell.x-1, FSelect);
Ha a cell változó x és y mezője közül az egyik sem 0, akkor cella kijelölés történik. Ha a kattintás egy olyan mezőn történt, ami már egyébként is ki volt jelölve, és csak az az egy cella volt kijelölve, akkor beállítjuk az FApplyChanges változó értékét igazra, egyébként pedig hamisra. Ennek akkor lesz jelentősége, amikor a felhasználó felengedi az egérgombot. Ha közben elmozgatta egy másik cellára az egeret, akkor a változó értékét hamisra állítjuk. Ez a MouseMove eljárásban történik, nézzük meg ezt is egy kicsit részletesebben:
A cell változóba a CellAtPos függvénnyel lekérdezzük annak a cellának a pozícióját, ami fölött az egérkurzor áll.
Ezután az aktuális állapottól függ, hogy éppen mit teszünk. A wsSelectAll-nál csak az FApplyChanges változó értékét kell megadnunk, ami akkor igaz, ha az egér a Minden gomb fölött áll, tehát a cell változó x és y mezőjének értéke 0, minden más esetben hamis.
FApplyChanges:=(cell.x=0) and (cell.y=0);
A wsRowSelect-nél az FBackup változóba elmentett táblázatot visszamásoljuk az FCells változóba. Ha az egér a sor fejlécek, vagy a cellák fölött van, akkor a kijelölést ennek megfelelően változtatjuk. A first változóba az első, a last-ba pedig a második kijelölt sor sorszámát tároljuk, és egy ciklusban, az ezekben a sorokban található cellák értékét beállítjuk az FSelect változóban tárolt értékre. Az oszlop kijelölésnél teljesen hasonló módon járunk el.
FFocusEnd.y:=cell.y;
first:=Min(FFocusStart.y, cell.y);
last:=Max(FFocusStart.y, cell.y);
for i:=first to last do SelectFullDay(i-1, FSelect);
A cellák kijelölése annyiban különbözik a sor és oszlop kijelöléstől, hogy itt a cellák értékeit nem változtatjuk meg, kivéve a MouseDown-nál megtárgyalt esetben, vagyis akkor, ha a gomb felengedésekor az FApplyChanges változó értéke igaz. Ez viszont csak akkor igaz, ha az elsőnek kijelölt celláról még nem mozgatta el az egeret a felhasználó. Ezt egy viszonylag egyszerű kifejezéssel megkapjuk:
FApplyChanges:=
FApplyChanges and
(FFocusStart.x=FFocusEnd.x) and
(FFocusStart.y=FFocusEnd.y);
A wsSelectAll és wsCellSelect állapotoknál tehát az FApplyChanges változó értékétől tesszük függővé, hogy a kijelölt cella vagy cellák értékét megváltoztatjuk-e. Mindez a MouseUp eljárásban történik meg:
if (Button=mbLeft) and FApplyChanges then begin
case FState of
wsSelectAll: SelectAllCells(not AllCellsSelected);
wsCellSelect: Cells[FFocusStart.y-1, FFocusStart.x-1]:=not Cells[FFocusStart.y-1, FFocusStart
.x-1];
end;
end;
A komponenst billentyűzetről is lehet kezelni, ehhez viszont a CN_KEYDOWN és CN_KEYUP üzenetekhez kell írnunk egy-egy eseménykezelő eljárást:
procedure CNKeyDown(var Msg: TWMKeyDown); message CN_KEYDOWN;
procedure CNKeyUp(var Msg: TWMKeyUp); message CN_KEYUP;
A CK_KEYDOWN eseménynél az Msg változó CharCode mezőjéből olvasható ki a lenyomott billentyű kódja. Egy Case szerkezetben ezeket ellenőrizzük, és a lenyomott billentyűnek megfelelő műveleteket hajtjuk végre. Ha a kurzormozgató billentyűk mellett a Shift gombot is lenyomva tartjuk, akkor a kijelölés méretét állíthatjuk, és nem a pozícióját. Ehhez viszont tudnunk kell, hogy lenyomták-e a Shift billentyűt. Ezt az FShiftDown logikai változóban tároljuk. A CNKeyDown eljárásban a változó értékét igazra, a CNKeyUp eljárásban pedig hamisra állítjuk, ha a lenyomott billentyű kódja VK_SHIFT.
A WM_SETFOCUS és WM_KILLFOCUS üzenetekhez is kell egy-egy eljárást írnunk, amikben csupán a komponenst rajzoltatjuk újra.
|
Könyv
Ez a cikk megtalálható ebben a könyvben:
Delphi Software Offline 2001 évkönyv 470. oldal
Felhasználási feltételek
A Software Online szoftverfejlesztői magazin mindegyik cikke, minden megjelent képe, és egyéb publikált anyaga szerzői jog védelme alatt áll! Bármilyen formában történő másodlagos terjesztésük, közzétételük vagy felhasználásuk kizárólag a kiadó előzetes írásbeli engedélyével történhet!
|