04. december 2007 - 16:40Der er
22 kommentarer og 1 løsning
Anlægge klasse, struktur eller?
Hej,
Det var lidt svært at finde en overskrift til dette spørgsmål, men her kommer en beskrivelse.
Jeg skal lave et program, som skal vise en oversigt over pakker, og hvad hver enkel pakke indeholder.
Til at gøre dette, har jeg opbygget oversigten for en pakke på denne måde: øverst er der et Edit-felt med en beskrivelse af pakken. Nedenunder er der så en række med 5 felter, som kan markeres efter behov(kunne være 5 Buttons eller 5 CheckBoxe eller lignende). Hver af disse felter angiver, hvad pakken indeholder. Hvis f.eks. det første felt er markeret, indeholder denne pakke 10 mm skruer. Hvis felt nr. 2 er markeret, indeholder denne pakke også møtrikker osv.
Problemet er, at jeg aldrig ved, hvor mange pakker jeg skal vise på en gang, så oversigten skal derfor være dynamisk, dvs., man skal kunne vælge, hvor mange pakker der skal vises på skærmen.
Skal jeg lave en struktur eller en klasse, som indeholder ovenstående, eller hvordan gøres det smartest? Hvis ja, hvordan opretter man f.eks. de 5 felter (TButtons) i en klasse?
Hvis ikke jeg har beskrevet spørgsmålet godt nok, så spørg endelig!
Først bør du ikke begrænse din kode til kun 5 felter. Den slags har det med at ændre sig. Til at vise hvad der er i pakken vil jeg bruge en TListView (i ReportView). Jeg ville ikke lave tjekbokse eller lignende, men skrive "Møtrikker" og lignende.
Endelig ville jeg falde tilbage til min absolut foretrukne konstruktion: En dataklasse og en liste:
uses SysUtils, ContNrs, ComCtrls;
type TDimsTypes = (dtMoetrik, dtSpaendskive ...)
const DimsTypesText : array[TDimsTypes] of string = ('Møtrik','Spændskiver'...);
type TDimsData = class private fDimsType: TDimsTypes; public constructor Create(const aDimsType: TDimsTypes; const aAntal: integer) property DimsType: TDimsTypes read fDimsType; // Skrivebeskyttet property Antal: integer read fAntal write fAntal; // Kan ændres function ToString: string; end;
Dims-klassens ToString returnerer "Møtrik" hvis typen er dtMoetrik. Dette bruges af TKasseData-klassens FillListItem() som løber sin liste igennem når den fylder data i et TListItem objekt. Endelig har jeg en TKasseList som indeholder alle TKasseData'ene
var KasseData: TKasseData; DimsData: TDimsData; begin KasseData := TKasseData.Create; KasseData.Add(TDimsData.Create(dtMoetrik,100)) KasseData.Add(TDimsData.Create(dtSpaendskive,25));
.. Det smarte med TObjectList er, at frigiver du et overordnet objekt, eksempelvis et TKasseList objekt, så frigives alt den indeholder også. Ingen ram-lækager her.
tak for svarene. Jeg er begyndt at lave det med en klasse, men vil da overveje dine forslag.
Hvordan navngiver man egentlig en ny instans af en klasse, når man ikke på forhånd ved, hvor mange instanser, man skal bruge? Man kan selvfølgelig definere 100000 instanser på forhånd, for at være på den sikre side, men det er naturligvis ikke den rigtige måde.
Jeg forstår ikke hvad du mener. Hvorfor du vil oprette 100000 i forvejen (af hvad)? Kan du ikke bare oprette efter behov? Det lyder som om du bevæger dig i forkert retning.
Hvor kommer dine data fra? Hvis du har dine data fra en fil eller en tabel er mit forslag helt sikkert en måde at lave det på.
Kunne ikke dy mig. Har lavet klasserne færdige. Det består af to listeklasser. En der indeholder alle kasse-instanser og en der indeholder det vilkårlige antal dimser.
unit UDimsClasses;
interface
uses SysUtils, ContNrs, ComCtrls, Classes;
type TDimsTypes = (dtMoetrik, dtSpaendskive, dtTandskive);
const DimsTypesText : array[TDimsTypes] of string = ('Møtrik','Spændskive','Tandskive');
type TDimsData = class private fDimsType: TDimsTypes; fAntal: integer; public constructor Create(const aReader: TReader); overload; constructor Create(const aDimsType: TDimsTypes; const aAntal: integer); overload; procedure SaveToWriter(aWriter: TWriter); property DimsType: TDimsTypes read fDimsType; // Skrivebeskyttet property Antal: integer read fAntal write fAntal; // Kan ændres procedure FillListItem(aListItem: TListItem); function ToString: string; end;
procedure TKasseData.FillListView(aListView: TListView); var i: integer; begin aListView.Items.BeginUpdate; try aListView.Items.Clear; for i := 0 to Count - 1 do Items[i].FillListItem(aListView.Items.Add); finally aListView.Items.EndUpdate; end; end;
function TKasseData.GetDimsData(const aIndex: integer): TDimsData; begin result := inherited Items[aIndex] as TDimsData; end;
procedure TKasseData.SaveToWriter(aWriter: TWriter); var i: integer; begin aWriter.WriteInteger(fID); aWriter.WriteString(fNavn); aWriter.WriteInteger(Count); for i := 0 to Count - 1 do Items[i].SaveToWriter(aWriter); end;
function TKasseData.ToString: string; begin result := format('%d: %s',[fID,fNavn]); end;
{ TKasseList }
procedure TKasseList.FillListView(aListView: TListView); var i: integer; begin aListView.Items.BeginUpdate; try aListView.Items.Clear; for i := 0 to Count - 1 do Items[i].FillListItem(aListView.Items.Add); finally aListView.Items.EndUpdate; end; end;
function TKasseList.GetKasseData(const aIndex: integer): TKasseData; begin result := inherited Items[aIndex] as TKasseData; end;
procedure TKasseList.LoadFromFile(const aFilename: string); var fs: TFileStream; begin fs := TFileStream.Create(aFilename,fmOpenRead or fmShareExclusive); try LoadFromStream(fs); finally fs.Free; end; end;
procedure TKasseList.LoadFromStream(aStream: TStream); var i: integer; Reader: TReader; begin Clear;
Reader := TReader.Create(aStream,1024); try for i := 0 to Reader.ReadInteger - 1 do Add(TKasseData.Create(Reader)); Reader.FlushBuffer; finally Reader.Free; end; end;
procedure TKasseList.SaveToFile(const aFilename: string); var fs: TFileStream; begin fs := TFileStream.Create(aFilename,fmCreate or fmShareExclusive); try SaveToStream(fs); finally fs.Free; end; end;
procedure TKasseList.SaveToStream(aStream: TStream); var i: integer; Writer: TWriter; begin Clear;
Writer := TWriter.Create(aStream,1024); try Writer.WriteInteger(Count); for i := 0 to Count - 1 do Items[i].SaveToWriter(Writer); Writer.FlushBuffer; finally Writer.Free; end; end;
end.
Data gemmes/læses vha. en stream til/fra en fil. Jeg bruger TReader og TWriter til det da det er nogle meget handy tingester. Man skal bare huske FlushBuffer
Kommentar: hrc 05/12-2007 21:01:35 Jeg vil jo netop oprette instanserne efter behov, men hvordan, når jeg ikke i forvejen ved, hvor mange jeg skal bruge??
En instans skal jo deklareres først, f.eks.: //Her er klassen Pakke = class private public constructor Create(aParent: TWinControl); destructor Destroy; override; end;
... var
//Her deklarerer jeg en instans Pakke1: Pakke;
...
//Her creater jeg instansen under runtime Pakke1 := Pakke.Create(Self);
Hvis jeg nu vil create 1000 instanser af klassen Pakke under runtime, vil jeg jo ikke først skulle have deklareret de 1000 instanser manuelt, som f.eks.: //Her deklarerer jeg 1000 instanser Pakke1: Pakke; Pakke2: Pakke; Pakke3: Pakke; Pakke4: Pakke; ... Pakke1000: Pakke;
Det jeg er ude efter er noget lignende dette: //Her creater jeg instansen under runtime var x: integer; Pakke[x] := Pakke.Create(Self);
Hvis man så skal bruge en pakke mere, forhøjer man x med 1, og udfører: Pakke[x] := Pakke.Create(Self);
Man kan gøre det på den måde som jeg har beskrevet. Jeg har en liste der kan indeholde op til 2 mia. kasser. Jeg har sågar beskrevet hvordan du propper kasser i den. Kig på eksemplet!
En TObjectList (som jeg nedarver fra) kan, som navnet antyder, indeholde en liste af objekter. Husk, at et objekt er ikke ret meget andet end en pointer til en klump data/kode. Det er pointeren til objektet der gemmes i kasseliste.
Listen kan derefter løbes igennem således:
for i := 0 to fKasseList.Count do Items[i].Antal := 0;
Det kan ikke være meget lettere - men noget fortæller mig at du ikke kender til disse listerklasser - og så er det sikkert ikke så let at forstå. Dit eksempel ovenfor antyder også at du er ret ny udi programmering.
Prøv at læse lidt i hjælpen om TList eller TObjectList for mit eksempel gør hvad du efterspørger. Sæt dig ind i hvad der sker når man eksempelvis nedarver klasser. Prøv derefter at kigge lidt mere på mit eksempel da det er et (i al beskedenhed) meget pænt og stringent eksempel jeg har lavet.
Afslutningsvis et par kommentarer til dit eksempel: En klassedefinition bør pr. de facto standard starte med et stor T => TPakke.
Du laver en constructor med en Parent af typen TWinControl. Det gør man kun når man står med en visuel komponent der kan gøre et eller andet, eksempelvis en knap eller en comboboks; altså noget der kan ses på skærmen og som brugeren kan gøre noget ved.
En destructor overrides når man opretter/tilslutter et eller andet som kræver at det frigives/frakobles igen; altså for at rydde ordentligt op.
Det nederste eksempel med at forhøje med 1 for at indsætte en ny pakke - LAD VÆRE MED AT ARBEJDE MED DYNAMISKE ARRAYS! De er tunge, klodsede og man støder hurtigt på deres grænser. Løber du gennem en liste falder hastigheden drastisk når du kommer over 1000 pakker. Du forøger i øvrigt sådan en array vha. SetLength (men lad være med at bruge den).
Jeg tror ikke, jeg har beskrevet min opgave godt nok. Programmet, jeg laver, skal være så enkelt at betjene som muligt, ikke noget smart halløj. Brugeren skal kun tage stilling til, om en pakke indeholder skruer, møtrikker osv. Hvis en pakke indeholder skruer, skal brugeren af programmet trykke på knappen for skruer under den rigtige pakke. Der skal ikke angives, hvor mange skruer pakken indeholder. Hver gang en ny instans oprettes, skal de tilsvarene inputfelter (i mit exempel er det TButtons) oprettes på skærmen (til højre for allerede existerende pakker). For at holde det overskueligt, skal brugeren så som sagt kun trykke en gang på knappen skruer, og ikke skrive "Skruer" hver gang en pakke indeholder skruer. Eller ville du have programmet til automatisk at skrive "Skruer" i din liste, når der trykkes på den tilsvarende Button?
Og yep, jeg er ny indenfor Delphi programmering. Faktisk så ny, at jeg er stolt over at lave mit første program, som indeholder klasser! Dog er jeg ikke så ny indenfor programmering generelt, at jeg ikke ved, at hver gang man viser andre programmører sin kode, siger de: "Det der - det havde jeg lavet anderledes. Den rigtige måde er at...".
Min constructor creater netop visuelle komponenter - de 5 TButtons mm. Min klasse hedder nu TPakke, og jeg har slettet destructoren, da den ikke blev brugt (var fra et andet eksempel).
Jeg håber ikke jeg spillede for meget besserwisser mht. mine kommentarer. Jeg forsøgte at rette op på noget jeg umiddelbart antog var misforståelser hos en novice udi Delphi. Beklager.
Hvad så med denne løsning? Her nedarver pakken fra et TPanel. Når det oprettes bliver knapperne oprettet og ejerskab ordnes. Knapperne kalder alle OnButtonPressed metoden og der bliver indholdet styret af knappens Tag-property.
const DimsTypesText : array[TDimsTypes] of string = ('Møtrik','Spændskiver'...);
constructor TPakke.Create(aOwner: TComponent; aParent: TWinControl); const BtnWdt = 75; var DimsType: TDimsTypes; Y: integer; Button: TButton; begin inherited Create(aOwner); // Lad ejeren rydde op efter dig Parent := aParent; // Få den på skærmen
Y := 4; for DimsType in TDimsTypes do begin // Self = Ejer = panelet som ejes af aOwner: Der bliver ryddet op ved prg.slut Button := TButton.Create(Self);
Button.Parent := self; // Vis på panelet
Button.Caption := DimsTypesText[DimsType]; // Skriv tekst på knap Button.OnClick := OnButtonPressed; // Hookup til fælles metode Button.Tag := integer(DimsType); // Gem typen sammen med knappen Button.Left := 4; Button.Width := BtnWdt; Button.Top := Y; inc(Y,Button.Height + 4); Height := Button.Top + Button.Height + 4; end; Width := BtnWth + 8 end;
procedure TPakke.OnButtonPressed(Sender: TObject); var Button: TButton; begin if Sender is TButton then begin Button := Sender as TButton; case TDimsTypes(Button.Tag) of dtMoetrik :; dtSpaendskive :; ... end; end; end;
Her gaar det fremragende, med rigeligt at give sig til! Vi snakkes ved!
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.