Bitmapbaggrund vha. et komponent

Tags:    delphi
Skrevet af Bruger #123 @ 16.04.2002
Bitmap som baggrund på en form via en komponent

For lang tid siden skrev jeg en artikel om hvordan man kunne tilføje et bitmap til en form. Jens Borrisholt mente at det var en besværlig måde at gøre det på, og det har han for så vidt ret i.

Jeg satte mig så ned og besluttede mig for at skrive en komponent som kunne gøre det i stedet for. Jeg vil i denne artikel beskrive hvordan komponenten fungerer og derigennem samtidig fortælle lidt om hvordan man skriver komponenter.

Først og fremmest var det et krav at komponenten skulle være enkel at anvende. Der skulle ikke være en forfærdentlig masse egenskaber som skulle sættes.

Dernæst skulle jeg have fundet ud af om komponenten skulle understøtte andre billedformatter end bitmaps.

Endelig skulle jeg jo sørge for at der ikke opstår fejl som kan få programmet til at gå ned f.eks.

Inklusiv erklæringer blev det til ialt 117 linier kode. Jeg vil i det efterfølgende gennemgå denne kode, så man kan se hvordan det er gjort. Det burde være ret let at udvide komponenten til at kunne anvende andre billedformatter end de to der er her, nemlig Windows bitmaps (.BMP) og jpeg. Disse to formatter er nemlig understøttet direkte af Delphi og kræver derfor ikke tredjeparts klasser.

Komponenten kan kun anvendes med forme, og ikke f.eks. frames. Grunden til dette er at en frame ikke har en Canvas egenskab. Komponenten burde dog let kunne udvides til at anvendes med andre komponenter som har en Canvas.


Interface delen

Interface delen er jo vigtig idet den fortæller andre units hvad der befinder sig i denne unit.
unit DueBackground;

interface

// Her fortæller vi hvilke units komponenten skal bruge. En enkelt skal lige
// nævnes. Det er Graphics som rummer understøttelse af bmp billedet, og derudover
// rumme definitionen af TGraphic klassen som er den essentielle del af komponenten.
uses
  Forms, SysUtils, Graphics, Classes;

type
  TDueBackground = class(TComponent)
  private
    FExtension  : String;       // Her gemmes billedets type f.eks. .bmp eller .jpg
    FFileName   : TFileName;    // Filnavn og sti til billedet.
    FBackground : TGraphic;     // Denne variable er vigtig da den rummer selve billedet.
    FMainForm   : TForm;        // Pegepind til formen som billedet skal vises på.
    FOldPaint   : TNotifyEvent; // Pegepind til OnPaint hændelsen for formen.
    FOldResize  : TNotifyEvent; // Pegepind til OnResize hændelsen for formen.

    procedure SetFileName(const Value: TFileName); // Her sættes og fjernes billedet
    Procedure DoPaint(Sender : TObject);           // Her males billedet på formens canvas.
    Procedure DoResize(Sender : TObject);          // Her sørges for at billedet følger 
                                                   // formen når denne resizes.
  public
    Constructor Create(AOwner : TComponent); Override;
    Destructor Destroy; Override;
  published
    // Den eneste egenskab som er nødvendig
    Property FileName : TFileName read FFileName write SetFileName;
  end;

// Og så selvfølgelig register proceduren. Denne er nødvendig for at Delphi
// kan registrere komponenten.
procedure Register;
Som det ses er selve erklæringen af komponenten ikke særlig omfattende. Udover diverse pegepinde (pointere) er der kun defineret tre variabler. En af disse er kun nødvendig at hensyn til overskuelighed af koden og de to andre er så de vigtige, nemlig FFileName og FBackground.

Derudover er der tre metoder som er vigtige. SetFileName er langt den største metode og bruges til at definere billedet, dets type og starte processen med at tegne det på formen. SetFileName bliver udført når man sætter komponentens FileName egenskab.

DoPaint er en såkaldt TNotifyEvent som anvendes af Delphi og derigennem Windows til at tegne formen. DoPaint bruges til at tegne billedet på formen, og derefter udføre formens oprindelige OnPaint event. Mere om dette senere.

DoResize er ligesom DoPaint en TNotifyEvent og den fortæller Windows hvad der skal ske med formen når den skal resizes (ændret størrelse). Det er det der sker når man tager fat i et hjørne, eller siden af formen og trækker. Ligesom DoPaint erstatter DoResize formens egen OnResize hændelse for at sørge for at billedet følger med formen, når den bliver resizet.

Nu er vi vist klar til at fortsætte med implementationsdelen.

Implementationsdelen

Implementationsdelen af en unit er den del som rummer selve koden. Udefra kan man ikke se hvad der ligger her, og det er en af de ting som objektorientering handler om. Interface delen fortæller hvad der er af klasser og hvilke metoder disse har, men implementationsdelen rummer selve (ha, ha) implementationen. Grunden til denne adskilles er at man kan ændre på måden en given metode afvikles på, uden at det har betydning for andre dele af et program. I dette tilfælde ville man f.eks. kunne tilføje understøttelse af gif billeder uden at det ville påvirke komponentens udseende for de programmer der anvender den.
implementation

// Her kan du tilføje andre units som f.eks. TGifImage af Anders Melander
//  hvis du ønsker at kunne anvende gif billeder med denne komponent.
// Her og nu er der kun understøttelse for jpg billeder.
Uses
  jpeg;

// Her fortæller vi Delpi at komponenten TDueBackground skal registreres under
// fanebladet DueComps. Du kan læse om hvad Register kan anvendes til i Delphis
// hjælp.
procedure Register;
begin
  RegisterComponents('DueComps', [TDueBackground]);
end;
Nu er vi så nået til implementationen af selve komponenten. Først har vi konstruktoren og destruktoren som de metoder der anvendes til at skabe og nedlægge objekter af denne komponent eller klasse.
constructor TDueBackground.Create(AOwner: TComponent);
begin
  // Sørg for at udføre andre opgaver som er implementeret i
  // forfaderen (ancestor) til komponenten. Husk altid denne
  // linie, da der kan være vigtige ting der skal udføre under
  // skabelsen af et nyt objekt.
  inherited;

  // Nu sætter vi pegepindene som nævnt tidligere. Dette er vigtigt da vi
  // ellers overskrive vigtige hændelser for formen.
  FMainForm        := TForm(Owner);
  FOldPaint        := FMainForm.OnPaint;
  FOldResize       := FMainForm.OnResize;

  // Til sidst sætter vi vores egen hændelser. Dette er nødvendigt for at
  // kunne udføres vores opgave, nemlig at tegne et billede på formens baggrund.
  with FMainForm do
  begin
    OnPaint        := DoPaint;  // Vores egen OnPaint hændelse.
    OnResize       := DoResize; // Vores egen OnResize hændelse.
    DoubleBuffered := TRUE;     // Denne linie kan undværes, men så vil billedet
                                // flimre når vi bla. resizer.
  end;
end;
Der er ikke meget der skal gøres i konstruktoren, som det ses sætter vi pegepinde og hændelser og det er det hele. Nogle gange kan det være nødvendigt med f.eks. udregninger i konstruktoren, men ikke her.
destructor TDueBackground.Destroy;
begin
  // Hvis billedet er sat skal vi have ryddet op efter os, eller kan vi risikere
  // at efterlade affald (garbage) som så binder vigtige Windows resourcer.
  if assigned(FBackground) then
  begin
    FBackground.free;
    FBackground := nil;
  end;

  // Så skal vi lige have sat de oprindelige hændelser tilbage. Det kunne jo være
  // at de skal bruges.
  FMainForm.OnPaint := FOldPaint;
  FMainForm.OnResize:= FOldResize;

  // Og så tilsidst husk inherited. Der kan jo være yderligere oprydningsopgaver
  // skal udføres.
  inherited;
end;
Destruktoren er altid vigtig i objektorientering. Den er nødvendig for at kunne rydde op efter sig. Hvis man ikke tager den med, vil programmet ikke rydde ordenligt op og i værste fald vil Windows stå og mangle resourcer som kunne have været nyttide andre steder. Husker man ikke dette kan man risikere at rende i en besked fra Windows om at systemet er lavt på resourcer, og man bør genstarte. Det er der jo ikke meget sjovt i.
procedure TDueBackground.DoPaint(Sender: TObject);
begin
  // Hvis der er defineret et billede ...
  if assigned(FBackground) then
    // skal det strækkes ud over hele formen og tegnes.
    FMainForm.Canvas.StretchDraw(FMainForm.ClientRect, FBackground);

  // Hvis formens OnPaint er defineret ...
  if assigned(FOldPaint) then
    // udfør da denne.
    FOldPaint(Sender);
end;
Hvis man ønsker det kan man udvide komponenten med forskellige muligheder. Man kan f.eks. tegne billedet centreret eller tiled, ligesom med baggundsbilledet i Windows. Jeg har valgt at nøjes med at billedet skal være trukket.
En lille hjemmeopgave: Udvid komponenten så billedet tegnes centreret istedet for strukket.
procedure TDueBackground.DoResize(Sender: TObject);
begin
  // Her gentegnes billedet når formens størrelse bliver ændret.
  FMainForm.Invalidate;
  // Hvis formens OnResize er defineret ...
  if assigned(FOldResize) then
    // udfør da denne.
    FOldResize(Sender);
end;
Der er forskellige metoder man kan bruge til at gentegne formen. Jeg har ikke været i stand til at se nogen forskel på dem, selvom den nok skal være der. Udover Invalidate, som forøvrigt anvendes når hele formen skal gentegnes, kan man anvende Refresh, eller Repaint. Hvilken en der er bedst at anvende, er efter min bedste viden underordnet. Formålet er under alle omstændigheder at gentegne formen og det gør både Invalidate, Refresh og Repaint.

Nu kommer vi så til monster metoden SetFileName på hele 34 linier.
procedure TDueBackground.SetFileName(const Value: TFileName);
begin
  // Hvis det er det samme billede foretager vi os intet. Det er der ingen grund til.
  if Value <> FFileName then
  begin
    // Sæt FFileName variable.
    FFileName := Value;
    // Hvis den er tom, skal billedet slettes.
    if FFileName = '' then
    begin
      // Hvis den er tildelt ...
      if assigned(FBackground) then
      begin
        // Frigiver vi baggrunden, så den ikke optager resourcer unødigt.
        FBackground.free;
        FBackground := nil;
      end;
    end
    else
    begin
      // Find ud af hvad billedet type er.
      FExtension := ExtractFileExt(FFileName);

      // Hvis der er et jpg billede ...
      if SameText(FExtension, '.jpg') or
         SameText(FExtension, '.jpeg') then
      begin
        // Så skab en jpg grafik objekt
        FBackground := TJPEGImage.Create;
        // Og indlæs billedet.
        FBackground.LoadFromFile(FFileName);
      end
      else
      // ellers hvis det er et bitmap ...
      if SameText(FExtension, '.bmp') then
      begin
        // så skab et bmp grafik objekt
        FBackground := TBitmap.Create;
        // og indlæs billedet.
        FBackground.LoadFromFile(FFileName);
      end;
    end;
    // Sørg for at formen bliver gentegnet uanset om billedet er blevet sat,
    // eller fjernet.
    FMainForm.Invalidate;
  end;
end;

end. // vi er færdige med komponenten
SetFileName anvender en særlig teknik som man kun finder i objektorienteringen. Hvis man kigger tilbage på erklæringen af komponenten ser man at FBackground er defineret som værende af typen TGraphic.
Alligevel kan objektet oprettes både som TJPEGImage og som TBitmap. Hvordan kan det være? Teknikken hedder polymorfi og det betyder at eftersom både TJPEGImage og TBitmap nedarver fra TGraphic kan FBackground skabes ud fra begge klasser. Det er nyttigt, når man som her ikke ved hvilken af klasserne man skal bruge, før man skal bruge den...
Hvis man vil tilføje gif understøttelse og TGifImage klassen nedarver fra TGraphic skal man bare gøre det samme en gang til. En grundig gennemgang af polymorfi begrebet vil være for meget her, men du kan finde masser af information om både dette og andre objektorienterings emner i bøger og ude på nettet.

Der er iøvrigt nogle udemærkede artikler om objektorienteret programmering i dette forum, hvis man vil starte et sted. Komplet kode kan downloades herfra.



Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (1)

User
Bruger #3564 @ 27.07.04 11:15
Nu er det godtnok formen man sætter et baggrund billede på, men er det muligt at få det til at skinde igennem f.eks notebook??
Du skal være logget ind for at skrive en kommentar.
t