Avatar billede js_delphi Nybegynder
04. december 2007 - 16:40 Der 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!

Tak for evt. hjælp!
Avatar billede hrc Mester
05. december 2007 - 11:45 #1
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;

  TKasseData = class(TObjectList)
  private
  public
    property Items[const aIndex: integer]: TDimsData read GetDimsData; default;
    procedure FillListItem(aListItem: TListItem);
  end;

  TKasseList = class(TObejctList)
  private
  public
    property Items[const aIndex: integer]: TKasseData read GetKasseData; default;
  end;

... Skal gerne lave den færdig for dig.

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
Avatar billede hrc Mester
05. december 2007 - 11:48 #2
Du fylder data i strukturen på følgende måde:

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.
Avatar billede js_delphi Nybegynder
05. december 2007 - 15:06 #3
Hej hrc,

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.

Forstår du, hvad jeg mener?
Avatar billede hrc Mester
05. december 2007 - 21:01 #4
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å.
Avatar billede hrc Mester
05. december 2007 - 21:26 #5
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;

  TKasseData = class(TObjectList)
  private
    fNavn: string;
    fID: integer;
    function GetDimsData(const aIndex: integer): TDimsData;
  public
    constructor Create(const aReader: TReader); overload;
    constructor Create(const aID: integer; const aNavn: string); overload;
    procedure SaveToWriter(aWriter: TWriter);
    property Items[const aIndex: integer]: TDimsData read GetDimsData; default;
    procedure FillListItem(aListItem: TListItem);
    procedure FillListView(aListView: TListView);
    property ID: integer read fID;
    property Navn: string read fNavn;
    function ToString: string;
  end;

  TKasseList = class(TObjectList)
  private
    function GetKasseData(const aIndex: integer): TKasseData;
  public
    property Items[const aIndex: integer]: TKasseData read GetKasseData; default;
    procedure FillListView(aListView: TListView);
    procedure LoadFromFile(const aFilename: string);
    procedure SaveToFile(const aFilename: string);
    procedure LoadFromStream(aStream: TStream);
    procedure SaveToStream(aStream: TStream);
  end;

implementation

{ TDimsData }

constructor TDimsData.Create(const aDimsType: TDimsTypes; const aAntal: integer);
begin
  inherited Create;
  fDimsType := aDimsType;
  fAntal := aAntal;
end;

constructor TDimsData.Create(const aReader: TReader);
begin
  inherited Create;
  fDimsType := TDimsTypes(aReader.ReadInteger);
  fAntal := aReader.ReadInteger;
end;

procedure TDimsData.FillListItem(aListItem: TListItem);
begin
  aListItem.SubItems.Clear;
  aListItem.Caption := ToString;
  aListItem.SubItems.Add(IntToStr(fAntal));
  aListItem.Data := self;
end;

procedure TDimsData.SaveToWriter(aWriter: TWriter);
begin
  aWriter.WriteInteger(integer(fDimsType));
  aWriter.WriteInteger(fAntal);
end;

function TDimsData.ToString: string;
begin
  result := format('%s (%d stk.)',[DimsTypesText[fDimsType,fAntal]]);
end;

{ TKasseData }

constructor TKasseData.Create(const aID: integer; const aNavn: string);
begin
  inherited Create;
  fID := aID;
  fNavn := trim(aNavn);
end;

constructor TKasseData.Create(const aReader: TReader);
var
  i: integer;
begin
  inherited Create;

  fId := aReader.ReadInteger;
  fNavn := aReader.ReadString;
  for i := 0 to aReader.ReadInteger - 1 do
    Add(TDimsData.Create(aReader));
end;

procedure TKasseData.FillListItem(aListItem: TListItem);
begin
  aListItem.SubItems.Clear;
  aListItem.Data := self;
  aListItem.Caption := ToString;
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
Avatar billede js_delphi Nybegynder
06. december 2007 - 12:11 #6
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);

Hvordan kan man gøre dette?
Avatar billede hrc Mester
06. december 2007 - 22:45 #7
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).
Avatar billede hrc Mester
06. december 2007 - 22:46 #8
Gennemløbet ser således ud (det gik lidt hurtigt)

for i := 0 to fKasseList.Count do
  fKasseList[i].Antal := 0;
Avatar billede js_delphi Nybegynder
07. december 2007 - 11:40 #9
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).
Avatar billede hrc Mester
07. december 2007 - 12:11 #10
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.

TPakke = class(TPanel)
private
  procedure OnButtonPressed(Sender: TObject);
public
  constructor Create(aOwner: TComponent; aParent: TWinControl); reintroduce;
end;

implementation

type
  TDimsTypes = (dtMoetrik, dtSpaendskive ...);

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;
Avatar billede js_delphi Nybegynder
14. december 2007 - 10:04 #11
Jeg er ved et andet projekt i oejeblikket, saa jeg vender tilbage senere.
Avatar billede js_delphi Nybegynder
19. marts 2008 - 22:39 #12
Jeg har ikke glemt dig endnu, men arbejder med lidt for mange andre sager i oejeblikket...
Avatar billede js_delphi Nybegynder
22. april 2008 - 08:38 #13
hrc -> har du tid og lyst til at give privatundervisning i Delphi paa timebasis?
Hvis ja, giv mig lige din e-mail adresse, for videre detaljer.

Vh. js_delphi
Avatar billede hrc Mester
22. april 2008 - 10:56 #14
Du må godt få den: hrc_public på hotmailen. Hvis der kommer noget der ligner guldkorn ud af det, smækker jeg det på eksperten som en artikel.
Avatar billede js_delphi Nybegynder
23. april 2008 - 08:28 #15
Har du faaet min mail?
Avatar billede hrc Mester
23. april 2008 - 10:21 #16
Ja, Har du ikke fået mit svar (åbenbart ikke, sender det igen)?
Avatar billede js_delphi Nybegynder
23. april 2008 - 15:12 #17
Har stadigvaek ikke modtaget noget..
Avatar billede hrc Mester
23. april 2008 - 21:30 #18
Jeg svarer direkte på din mail. Skal den ende på .de?
Avatar billede js_delphi Nybegynder
24. april 2008 - 08:33 #19
Jeps, .de er rigtigt.
Avatar billede js_delphi Nybegynder
25. april 2008 - 08:13 #20
Nu ved jeg ikke, om du har sendt noget, men jeg har i hvert faldt ikke modtaget nogen e-mail.
Avatar billede js_delphi Nybegynder
01. juli 2009 - 18:40 #21
Hej hrc,

Laeg lige et svar, saa vi kan faa lukket denne traad.
Vi snakkes ved.
Avatar billede hrc Mester
03. juli 2009 - 20:42 #22
Hej Jeppe. Get es gut in Deutschland?
Avatar billede js_delphi Nybegynder
06. juli 2009 - 08:43 #23
Her gaar det fremragende, med rigeligt at give sig til!
Vi snakkes ved!
Avatar billede Ny bruger Nybegynder

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.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester