|
|
Delphi : ArrayButton component |
|
|
| download Delphi-7 project |
| download demo program |
| download Delphi-7 demo project |
Inleiding
Arrays zijn een handige datastructuur om een hoeveelheid gelijkvormige gegevens in op te slaan.
Een grafisch gebruikersinterface heeft vaak een aantal gelijksoortige knoppen om processen te starten
of om eigenschappen te selecteren.
Waarom dus die knoppen niet organiseren als een array?
Dit artikel beschrijft in de taal Delphi een ArrayButton Component, waarin knoppen zijn neergezet
in een rechthoek met rijen en kolommen.
Eigenschappen
Het component biedt ruimte aan maximaal 48 knoppen [0..47] die georganiseerd kunnen zijn als
enkele rij, enkele kolom of een combinatie van rijen en kolommen.
Alle knoppen hebben dezelfde breedte en hoogte.
Eigenschappen (properties):
btnHeight | de hoogte van een knop in pixels |
btnwidth | de breedte van een knop in pixels |
border | de rand langs het paneel met knoppen, in pixels |
btnEdge | de rand langs elke knop, in pixels. Waarde 1 of 2 |
btnSpacing | de afstand in pixels tussen de knoppen |
btnShape | bs3D(rechthoekig, ruimtelijk) of bsFlat(afgeronde hoeken, 2 dimensionaal) |
Het gedrag van de knoppen
Een knop kan de volgende status hebben:
stFlat | niet geactiveerd, in rust |
stDown | ingedrukt na bijvoorbeeld muisklik of programmacode |
stHI | muiswijzer staat boven knop |
stHidden | knop wordt niet getoond |
Een knop kan werken op de volgende manieren (operating modes):
omMom | momenteel: stDown als ingedrukt door muisknop,
terug naar stFlat als muisknop wordt losgelaten. |
omPress | knop wordt ingedrukt (status wordt stDown) door muisklik. |
omToggle | knop wordt ingedrukt (status wordt stDown) door muisklik en tweede muisklik deactiveert de knop (status wordt stFlat) |
Elke knop heeft een groepsnummer van 0..15.
Per groep kan maar één knop de status stDown hebben.
Wordt een knop ingedrukt, dan zal dus een andere knop van hetzelfde groepsnummer deactiveren.
Verantwoordelijkheden.
Het component zorgt voor:
- tekenen van de zijden van het paneel en de knoppen en de achtergrondkleuren
- genereren van events als de status van een knop verandert
De applicatieprogrammeur moet zorg dragen voor:
- tekenen van de informatie in de knoppen, afhankelijk van de status van een knop
Kleuren
De property "color" is de kleur van de rand en de ruimte tussen de knoppen.
Vijf kleuren in array[bcInactBG..bcLO] of longInt worden gebruikt om de status van een knop aan te geven
bcInactBG | achtergrondkleur van een inactieve knop (status = stFlat) |
bcActiveBG | achtergrondkleur van een actieve (status = stDown) knop |
bcFlat | randkleur van een inactieve knop |
bcHI | randkleur van knop waar muiswijzer boven staat |
bcLO | randkleur van knop die is ingedrukt |
Hoe de knoppen eruit (kunnen) zien
(voorgrond in knoppen is niet getekend)
| default colors, border = 10, button spacing = 5
edge = 2, shape = bs3D |
| after command: btnShape := bsFlat
button 4 is pressed down |
| after command: PColorTable := @ct, where
const ct : array[0..4] of LongInt =($ffff,$ff00ff,$d0,$ff00,$ff0000) |
| after command: border := 2; btnSpacing := 1;
btnShape := bs3D; (default colors) |
Events
onBtnChange | wordt gegenereerd als een knop activeert of deactiveert |
onBtnPaint | wordt gegenereerd als de voorgrond opnieuw getekend moet worden (status van knop is gewijzigd) |
onEnter | wordt gegenereerd als de muiswijzer boven het component wordt geplaatst |
onLeave | wordt gegenereerd als de muiswijzer buiten het component wordt geplaatst |
Bij ontvangst van een onBtnPaint event met het button nummer en de button status, moet de applicatie
de positie van de knop opvragen met ...............function GetBtnRect(btnNr) : Trect;
Deze levert de rechthoek (rectangle) van de knop in het component.
De arraybutton component is één paintbox en het is de verantwoordelijkheid van de applicatieprogrammeur
om niet over de randen van de knoppen heen te tekenen.
Application
Om de mogelijkheden te tonen van de arraybutton component heb ik een demonstatratieprogrammaatje gemaakt.
De gebruiker kan met menuknoppen een actie, de lijndikte slecteren en de kleur selecteren.
Voor de kleurkeuze is een kleurenmixer gemaakt met drie vertikaal geplaatste arraybuttons.
Hieronder staat een afbeelding met de menuknoppen load..roundrect bovenaan.
links staan de knoppen, die een dialogform openen.
Hieronder staat de dialog form voor de lijndikte.
En hieronder de kleurenmixer dialog
Hier volgt de source code van het arraybutton component.
The complete ArrayButton Component
unit davArrayBtn;
{
aug.8 2004: corrected column/row problem when using max number of buttons
columns and rows of buttons, 0..47 maximal, for menu's and controls
common properties:
- button width,height
- button edge 1..2 , borderwidth
- shape: - flat, corners rounded
- raised, 3D, sharp corners
- colortable for alternate colors of edges/background
per button:
- button belongs to group 0..15
only -1- button of a group can be down at the time
- button operating mode
- mom : released on mouse-up
- press : user press down, released by other button
- toggle: additional user 2 ND press Up action
- button status:
- hidden
- flat (inactive)
- down (activated)
component:
- paints edges and backgrounds
- triggers onBtnPaint event if button foreground needs painting
- triggers onBtnChange event when button pressed or released
application:
- paints foreground when triggered by event
- responds to button pressed/released, Enter/Leave
controlbyte per Button:
bit 0..3 : group code 0..15
bit 4,5 : action : 00:mom 01:press 10:toggle
bit 6,7 : status : 00:hidden 01:flat 10:down
}
interface
uses windows,extctrls,controls,classes,graphics,messages;
const davmaxBtn = 48;//buttons 0..47; btn 48 = no button
type TBtnShape = (bsFlat,bs3D);
TBtnStatus = (stHidden,stFlat,stDown,stHI);
TBtnOpmode = (omMom,omPress,omToggle);
TBtnColorIndex = (bcInactBG,bcActiveBG,bcFlat,bcHI,bcLO);
TBtnChangeProc = procedure(sender : TObject; BtnNr : byte;
status : TBtnStatus; button : TmouseButton) of object;
TBtnPaintProc = procedure(sender : TObject; BtnNr : byte;
status : TBtnStatus) of object;
TColorTable = array[bcInactBG..bcLO] of LongInt;
TPColorTable = ^TColorTable;
TDavArrayBtn = class(TGraphicControl)
private
FonBtnchange : TBtnChangeProc;
FonBtnPaint : TBtnPaintProc;
FonEnter : TNotifyEvent;
FonLeave : TNotifyEvent;
FHiBtn : byte; //mouse over this button
Frows : byte; //rows
Fcolumns : byte; //columns
FbtnWidth : byte; //button width
FbtnHeight : byte; //button height
FBtnEdge : byte; //button edge
FBtnSpacing : byte; //space between buttons
FBorder : byte; //border
FBtnShape : TBtnShape; //rounded flat, 3D
FPcolorTable : TPColorTable;
FBtnControl : array[0..davmaxBtn] of byte;
FNextRelease : byte;
procedure setRows(n : byte);
procedure setColumns(n : byte);
procedure setBtnWidth(n : byte);
procedure setBtnHeight(n : byte);
procedure setBtnshape(bs : TBtnShape);
procedure setBorder(b : byte);
procedure setSpacing(b : byte);
procedure setBtnEdge(edge : byte);
procedure repaintBtns;
procedure fixdimensions;
procedure BtnPaint(BtnNr : byte; bst : TBtnStatus);
procedure CMmouseLeave(var message : Tmessage); message CM_MOUSELEAVE;
procedure CMmouseEnter(var message : Tmessage); message CM_MOUSEENTER;
procedure InitBtns;
procedure SetBtnStatus(BtnNr : byte; status : TBtnStatus);
function GetBtnGroup(BtnNr : byte) : byte;
function GetBtnOpMode(BtnNr : byte) : TBtnOpMode;
protected
procedure paint; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure AssignColorTable(p : TPcolorTable);
procedure TestReleaseBtn(downBtn : byte);
public
constructor Create(AOwner: TComponent); override;
function GetBtnRect(btnNr : byte) : TRect;
function GetBtnStatus(btnNr : byte) : TBtnStatus;
procedure setBtnOpmode(BtnNr : byte; opMode: TBtnOpmode);
procedure BtnHide(btnNr : byte);
procedure BtnShow(btnNr : byte);
procedure BtnDown(btnNr : byte);
procedure BtnRelease(btnNr : byte);
procedure setBtnGroup(btnNr,group : byte);
property canvas;
property PColorTable : TPColorTable read FPColorTable
write AssignColorTable;
published
property Border : byte read FBorder write setBorder default 10;
property BtnHeight : byte read FBtnHeight write setBtnHeight default 20;
property BtnSpacing : byte read FBtnSpacing write setSpacing default 5;
property BtnShape : TBtnShape read FBtnShape write setBtnShape default bs3D;
property BtnWidth : byte read FBtnWidth write setbtnwidth default 30;
property BtnEdge : byte read FBtnEdge write SetBtnEdge default 1;
property Color;
property Columns : byte read Fcolumns write setcolumns default 2;
property Enabled;
property Font;
property onBtnChange : TBtnChangeProc read FonBtnChange write FonBtnChange;
property onBtnPaint : TBtnPaintProc read FonBtnPaint write FonBtnPaint;
property OnEnter : TNotifyEvent read FOnEnter write FOnEnter;
property OnLeave : TNotifyEvent read FonLeave write FOnLeave;
property Rows : byte read Frows write setRows default 2;
property Visible;
end;
procedure Register;
implementation
const defaultColors : TColorTable =
($c0c0c0,$f0f0f0,$808080,$ffffff,$202020);
procedure Register;
begin
RegisterComponents('system',[TdavArrayBtn]);
end;
procedure TdavArrayBtn.SetBtnOpMode(BtnNr : byte; Opmode : TBtnOpMode);
var cb : byte;
begin
cb := FBtnControl[BtnNr] and $cf;
FBtnControl[BtnNr] := cb or (byte(OpMode) shl 4);
end;
function TdavArrayBtn.GetBtnOpMode(BtnNr : byte) : TbtnOpMode;
begin
result := TBtnOpMode((FBtnControl[BtnNr] shr 4) and $3);
end;
procedure TdavArrayBtn.setBtnStatus(BtnNr : byte; status : TBtnStatus);
var bc : byte;
begin
bc := FBtnControl[BtnNr] and $3f;
FBtnControl[BtnNr] := bc or (byte(status) shl 6);
end;
function TdavArrayBtn.GetBtnGroup(BtnNr : byte) : byte;
begin
result := FBtnControl[BtnNr] and $f;
end;
procedure TdavArrayBtn.setBtnGroup(btnNr,group : byte);
//add button to group
var bc : byte;
begin
bc := FBtnControl[BtnNr] and $f0;
FBtnControl[BtnNr] := bc or group;
end;
procedure TdavArrayBtn.initBtns;
//alle buttons group -0-, press
var i,top : byte;
control : byte;
begin
top := FRows*Fcolumns-1;
control := (byte(stFlat) shl 6) or (byte(omPress) shl 4);
for i := 0 to davmaxBtn do
if i <= top then FBtnControl[i] := control
else FBtnControl[i] := 0;
end;
function TdavarrayBtn.GetBtnStatus(btnNr : byte) : TBtnStatus;
begin
result := TBtnStatus((FBtnControl[btnNr] shr 6) and $3);
end;
procedure TdavArrayBtn.BtnHide(btnNr : byte);
//hide button
begin
if GetBtnStatus(btnNr) <> stHidden then
begin
SetBtnStatus(btnNr,stHidden);
if FHiBtn = btnNr then FHiBtn := davmaxBtn;
BtnPaint(btnNr,stHidden);
end;
end;
procedure TdavarrayBtn.BtnShow(btnNr : byte);
//show a hidden button, set flat
begin
if GetBtnStatus(btnNr) = stHidden then
begin
SetBtnStatus(btnNr,stFlat);
BtnPaint(BtnNr,stFlat);
end;
end;
procedure TdavArrayBtn.BtnRelease(btnNr : byte);
//set button from DOWN to Flat
begin
if GetBtnStatus(btnNr) = stDown then
begin
SetBtnStatus(btnNr,stFlat);
BtnPaint(btnNr,stFlat);
end;
end;
procedure TdavArrayBtn.BtnDown(btnNr : byte);
begin
if GetBtnStatus(btnNr) = stFlat then
begin
SetBtnStatus(btnNr,stDown);
BtnPaint(btnNr,stDown);
TestReleaseBtn(btnNr);//to release other buttons
end;
end;
procedure TdavArrayBtn.TestReleaseBtn(downBtn : byte);
//downBtn was pressed down, test to release buttons of same group
var groupNr,i : byte;
begin
groupNr := GetBtnGroup(downBtn);
for i := 0 to Frows*Fcolumns-1 do
if (i <> downBtn) and (GetBtnGroup(i) = groupNr)
and (GetBtnStatus(i) = stDown) then
begin
SetBtnStatus(i,stFlat);
btnPaint(i,stFlat);
end;
end;
procedure TdavArrayBtn.BtnPaint(btnNr : byte; bst : TBtnStatus);
//if button hidden: erase
var r : Trect;
radius : byte;
k1,k2 : LongInt;
i : byte;
begin
r := GetBtnRect(btnNr);
with canvas do
begin
pen.Width := 1;
brush.style := bssolid;
case bst of
stHidden : begin
brush.color := color;
brush.style := bsSolid;
fillrect(r);
exit;
end;
stFlat : begin
brush.color := PColorTable^[bcInactBG];
k1 := PColorTable^[bcFlat]; k2 := k1;
end;
stHI : begin
brush.color := PColorTable^[bcInactBG];
k1 := PColorTable^[bcHI]; k2 := PColorTable^[bcLO];
end;
stDown : begin
brush.color := PColorTable^[bcActiveBG];
k1 := PColorTable^[bcLO]; k2 := PColorTable^[bcHI];
end;
end;//case
if FBtnShape = bsFlat then //vlak,ronde hoeken
begin
radius := FBtnHeight div 2;
if radius > 40 then radius := 40;
if radius < 10 then radius := 10;
pen.Width := FbtnEdge;
pen.color := k1;
roundrect(r.left+1,r.top+1,r.right,r.bottom,radius,radius);
end
else
begin
fillrect(r);
for i := 0 to FbtnEdge-1 do
begin
pen.color := k1;
moveto(r.right-1-i,r.top+i);
lineto(r.left+i,r.top+i);
lineto(r.left+i,r.bottom-1-i);
pen.color := k2;
lineto(r.right-1-i,r.bottom-1-i);
lineto(r.right-1-i,r.top+i);
end;//for
end;//else
end;//with canvas
if not (csDesigning in componentstate) and assigned(onBtnPaint) then
onBtnPaint(self,btnNr,bst);
end;
procedure TdavArrayBtn.RepaintBtns;
//na initialiseren hele paintbox
var i : byte;
begin
for i := 0 to FRows*Fcolumns-1 do BtnPaint(i,GetBtnStatus(i));
end;
procedure TdavArrayBtn.FixDimensions;
//adjust width,height na verandering van knop of spacing
//generates onPaint event
begin
if FRows = 0 then FRows := 1;
if FColumns = 0 then FColumns := 1;
width := FColumns*(FBtnWidth + FBtnSpacing) - FBtnSpacing + 2*Fborder;
height := FRows*(FBtnHeight + FBtnspacing) - FBtnSpacing + 2*Fborder;
end;
constructor TdavArrayBtn.Create(AOwner: TComponent);
begin
inherited create(Aowner);
canvas.font := font;
FHiBtn := davmaxBtn;//=off
FBtnShape := bs3D;
FbtnEdge := 1;
FPColorTable := @defaultColors;
Frows := 2;
Fcolumns := 2;
InitBtns;
FbtnWidth := 40;
FbtnHeight := 30;
FBtnSpacing := 5;
FBorder := 10;
fixDimensions;//set width , height
end;
procedure TdavArrayBtn.AssignColorTable(p : TPcolorTable);
begin
FPcolorTable := p;
invalidate;
end;
procedure TdavArrayBtn.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var status : TBtnStatus;
begin
FNextRelease := davmaxBtn;
if (FHiBtn = davmaxBtn) then exit; //no button selected
//----
status := GetBtnstatus(FHIbtn);
if status = stFlat then
begin
SetBtnStatus(FHIbtn,stDown);
BtnPaint(FHIbtn,stDown);
TestReleaseBtn(FHIbtn);
if assigned(FonBtnChange) and (not (csDesigning in componentstate)) then
onBtnChange(self,FHiBtn,stDown,button);
end;
case GetBtnOpMode(FHIbtn) of
omMom : FNextRelease := FHIbtn;
omToggle : if status = stDown then FNextrelease := FHIbtn;
end;//case
end;
procedure TdavArrayBtn.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
if FNextRelease <> davmaxBtn then
begin
SetBtnStatus(FNextRelease,stFlat);
BtnPaint(FNextRelease,stFlat);
if (not (csDesigning in componentstate)) and assigned(FonBtnChange) then
onBtnChange(self,FNextRelease,stFlat,button);
end;
end;
procedure TdavArrayBtn.MouseMove(Shift: TShiftState; X, Y: Integer);
var dx,maxX,maxY,dy : integer;
button : byte;
px,py : integer;
status : TBtnStatus;
begin
x := x - FBorder; y := y - FBorder;
dx := FBtnSpacing + FBtnWidth;
dy := FBtnSpacing + FBtnHeight;
maxX := FColumns * dx; maxY := FRows * dy;
px := x mod dx; py := y mod dy;
if (x < maxX) and (y < maxY) and
(px > FBtnEdge) and (px < dx-FBtnEdge-FBtnSpacing) and
(py > FBtnEdge) and (py < dy-FBtnEdge-FBtnSpacing) then
begin
button := x div dx + FColumns*(y div dy);
end
else button := davmaxBtn;
status := GetBtnStatus(button);
if (status = stHidden) then button := davmaxBtn;
if button = FHiBtn then exit;//if no change
//---process Btn change
if button <> davmaxBtn then cursor := crhandpoint
else cursor := crArrow;
if (FHIbtn <> davmaxBtn) and (GetBtnStatus(FHIbtn) <> stDown) then
BtnPaint(FHIbtn,stFlat);//remove HI edge
if (button <> davmaxBtn) and (GetBtnStatus(button) <> stDown) then
BtnPaint(button,stHI);//paint HI
FHIbtn := button;
end;
procedure TdavarrayBtn.Paint;
//teken de paintbox
//teken alle buttons
var i : byte;
k1,k2 : LongInt;
begin
FHiBtn := davmaxBtn;
with canvas do
begin
brush.color := color;
pen.Width := 1;
pen.color := PcolorTable^[bcFlat];
fillrect(rect(0,0,width,height));
if FBorder > 0 then
begin
if FBtnShape = bs3D then
begin
k1 := PcolorTable^[bcHI]; k2 := PcolorTable^[bcFlat];
end
else begin
k1 := Pcolortable^[bcFlat]; k2 := k1;
end;
pen.color := k1;
moveto(width-1,0);
lineto(0,0); lineto(0,height-1);
pen.color := k2;
lineto(width-1,height-1); lineto(width-1,0);
end;//if border
end;//with
for i := 0 to FColumns*Frows-1 do
if GetBtnStatus(i) <> stHidden then Btnpaint(i,GetBtnStatus(i));
end;
function TdavArrayBtn.GetBtnRect(btnNr : byte) : TRect;
//geef rectangle waarin button getekend moet worden
var x,y : integer;
begin
x := btnNr mod Fcolumns;
y := btnNr div FColumns;
with result do
begin
left := Fborder + (FBtnWidth + FBtnspacing)*x;
right := left + FBtnWidth;
top := Fborder + (FBtnHeight + FBtnspacing)*y;
bottom := top + FbtnHeight;
end;
end;
procedure TdavArrayBtn.setRows(n : byte);
begin
if n = 0 then n := 1;
if n > davmaxBtn then n := davmaxBtn;
if n * Fcolumns > davMaxBtn then Fcolumns := 1;
Frows := n;
initBtns;
FixDimensions;
end;
procedure TdavArrayBtn.setColumns(n : byte);
begin
if n = 0 then n := 1;
if n > davMaxBtn then n := davMaxbtn;
if n * Frows > davMaxBtn then Frows := 1;
Fcolumns := n;
initBtns;
FixDimensions;
end;
procedure TdavArrayBtn.setBtnWidth(n : byte);
begin
if n < 10 then n := 10;
FBtnWidth := n;
FixDimensions;
end;
procedure TdavarrayBtn.setBtnHeight(n : byte);
begin
if n < 10 then n := 10;
FBtnHeight := n;
FixDimensions;
end;
procedure TdavArrayBtn.setBtnShape(bs : TBtnShape);
begin
FBtnShape := bs;
invalidate;
end;
procedure TdavArrayBtn.setBtnEdge(edge : byte);
begin
if edge = 0 then edge := 1
else if edge > 2 then edge := 2;
FBtnEdge := edge;
repaintBtns;
end;
procedure TdavArrayBtn.setBorder(b : byte);
begin
Fborder := b;
FixDimensions;
end;
procedure TdavarrayBtn.setSpacing(b : byte);
begin
FBtnSpacing := b;
FixDimensions;
end;
procedure TdavArrayBtn.CMmouseLeave(var message : Tmessage);
begin
if (FHiBtn <> davmaxbtn) then
begin
if GetBtnStatus(FHIbtn) <> stDown then BtnPaint(FHIbtn,stFlat);
FHIbtn := davMaxBtn;
end;
if not (csDesigning in componentstate) and assigned(FOnLeave) then
onLeave(self);
end;
procedure TdavarrayBtn.CMmouseEnter(var message : Tmessage);
begin
if not (csDesigning in componentstate) and assigned(FOnLeave) then
onEnter(self);
end;
end.
|
|