|
![]() |
|
Registriert seit: 9. Mai 2005 Ort: Nordbaden 925 Beiträge |
#1
OK, da haben wir doch schonmal was. Folgender Ansatz:
- Personen haben Referenzen auf Eltern und Partner, so entsteht der Stammbaum - Eine Klasse TFamilyTree übernimmt die Darstellung. Der eigentliche Stammbaum ist aber das Objektgeflecht - TFamilyTree hat ne Referenz auf die markierte Person - TFamilyTree hat Operationen um den Stammbaum zu erweitern. AddFather() z.B. diese Methode ruft AddFather() der markierten Person auf. Die Person selbst weiß, wie sie einen Vater kriegt, nicht der Stammbaum! Die OOP sagt: "Do it myself!" - TFamilyTree hat eine Referenz auf die Wurzel des Stammbaums. Das reicht, um am Ende alle freigeben zu können. Außerdem kannst du so wieder an den Anfang zurückspringen. - Zusätzlich kannst du, wenn du willst, noch ne ObjectList mit allen Personen halten. Das macht das Freigeben später einfacher (OwnsObjects macht das für dich), dafür musst du aber die Liste pflegen. Dazu würdest du beispielsweise AddFather() einen Rückgabewert verpassen und den dann auswerten. Eigentlich brauchst du das aber nicht. Stattdessen kannst du am Ende rekursiv eine Liste aufstellen. Das ist einfacher. Du kannst auch versuchen direkt rekursiv freizugeben. Da solltest du aber aufpassen. Ansonsten kann es in gewissen Fällen (Inzest) zu Zugriffsverletzungen kommen. - TFamilyTree hat ne Referenz auf den Probanden und ne Methode makeProband(TPerson), die die übergebene Person zum Probanden macht. - TPerson muss TFamilyTree mitteilen können, wann es doppelgeklickt wurde, damit makeProband() aufgerufen wird. Da gibt jetzt mehrere Möglichkeiten: a) ![]() b) ein zusätzliches public, aber nicht published Event (zusätzlich zu onDblClick) das den Doppelklick an das TFamilyTree-Objekt meldet. Noch einigermaßen saubere Lösung. c) TPerson hat ne Referenz auf TFamilyTree und ruft selbst makeProband(Self) auf. Das ist nicht so schön, weil es die Kopplung erhöht. - makeProband(), macht zuerst die bisher angezeigten Personen invisible, setzt dann die Referenz auf den neuen Probanden, setzt die Positionen der anzuzeigenden Probanden und macht diese Visible. Weiteres: - Du könntest überlegen, ob du getrennte Klassen für Darstellung und Logik machst. Also TPerson von TPersistent ableiten und TGraphicPerson von TGraphicControl ableiten und eine TPerson komponieren. Das wäre sauberer, ist aber etwas schwieriger. Mach das nur, wenn du sicher bist, dass du es verstehst. Fragen: - du schreibst was von Geschwistern. Wie hast du dir das vorgestellt? - so richtig verstanden, warum du nur max. 15 Personen anzeigen willst, hab ich immer noch nicht. mfg Christian
Kaum macht man's richtig, schon klappts!
|
![]() |
Registriert seit: 14. Apr 2004 Ort: Karlsruhe 318 Beiträge Delphi 2010 Architect |
#2
Lieber Christian,
danke für die tollen Hinweise. Ich habe bereits daran gedacht, den Doppelklick der TPerson an den Stammbaum weiterzuleiten, leider habe ich überhaupt keine Idee, wie ich das anstellen soll. Warum nur 15 Personen dargestellt werden sollen? Das hängt mit den 4 Generationen zusammen, die ich darstellen möchte: 1. Generation: Proband (1 Person in der Ansicht) 2. Generation: Vater, Mutter (2 Personen in der Ansicht) 3. Generation: Großväter, Großmütter (4 Personen in der Ansicht) 4. Generation: Urgroßväter, Urgroßmütter (8 Personen in der Ansicht) das ergibt insgesamt 15 Personen. Sollte eine Person Geschwister haben, so werden diese erstmal nicht in der Ansicht dargestellt, es sei denn sie werden über eine Auswahlliste (z.B. TListBox) ausgewählt, dann sind sie der neue Proband des Stammbaums. Ich hoffe das bringt etwas Licht ins dunkle. Gruß hansklok |
![]() |
Registriert seit: 9. Mai 2005 Ort: Nordbaden 925 Beiträge |
#3
danke für die tollen Hinweise. Ich habe bereits daran gedacht, den Doppelklick der TPerson an den Stammbaum weiterzuleiten, leider habe ich überhaupt keine Idee, wie ich das anstellen soll.
Delphi-Quellcode:
Das ist in etwa Lösung b)
// Pseudocode:
TPerson = class private OnDblClickForFamilyTree: TNotifyEvent; public property _OnDblClickForFamilyTree: TNotifyEvent read F_OnDblClickForFamilyTree write OnDblClickForFamilyTree; procedure DblClick; override; end; procedure TPerson.DblClick; begin inherited; if Assigned(F_OnDblClickForFamilyTree) then begin F_OnDblClickForFamilyTree; end; end; TFamilyTree.Somewhere; begin SomePerson._OnDblClickForFamilyTree := PersonDblClicked; end; procedure TFamilyTree.PersonDblClicked(Sender: TObject); begin makeProband(Sender as TPerson); end; ![]() Warum nur 15 Personen dargestellt werden sollen? Das hängt mit den 4 Generationen zusammen, die ich darstellen möchte: [...]
![]() Sollte eine Person Geschwister haben, so werden diese erstmal nicht in der Ansicht dargestellt, es sei denn sie werden über eine Auswahlliste (z.B. TListBox) ausgewählt, dann sind sie der neue Proband des Stammbaums.
mfg Christian
Kaum macht man's richtig, schon klappts!
|
![]() |
Registriert seit: 14. Apr 2004 Ort: Karlsruhe 318 Beiträge Delphi 2010 Architect |
#4
So, ich stelle mal ein bisschen Source von der grafischen Ausgabe des Stammbaums rein. Vielleicht hilft das ja jmd. bei der Suche nach Lösungen weiter. Vielleicht ist mein Arbeitsansatz auch völlig falsch.
Gruße hansklok
Delphi-Quellcode:
type
TPerson = class(TCustomControl) private fTag: Integer; fID: Integer; // Verweis auf die ID der Person, sie wird später aus TStammbaum.fGenealogie... zugewiesen fTyp: String; fVorname: String; // holt den Vornamen der Person auf Grundlage von fID später aus TStammbaum.fGenealogie... fNachname: String; // holt den Nachnamen der Person auf Grundlage von fID später aus TStammbaum.fGenealogie... fMarkiert: Boolean; fMouseOver: Boolean; procedure SetTyp(Bezeichnung: String); public constructor Create(AOwner: TComponent; Oben: Integer); procedure Paint; override; procedure Click;override;//(Sender: TObject); //overload; procedure MouseEnter; override; procedure MouseLeave; override; //property _OnDblClickForFamilyTree: TNotifyEvent read OnDblClickForFamilyTree write OnDblClickForFamilyTree; //procedure DblClick; override; property Tag: Integer read fTag write fTag; property ID: Integer read fID write fID; property Typ: String read fTyp write fTyp; property Markiert: Boolean read fMarkiert write fMarkiert; property Vorname: String read fVorname write fVorname; property Nachname: String read fNachname write fNachname; published property OnClick; property OnMouseEnter; property OnMouseLeave; end; TStammbaum = class(TCustomControl) private fGenealogie: TGenealogie; // hier werden die Daten zu den Personen geholt fPerson: TObjectList; fProband: Integer; fGeneration: Integer; fMarkiert: Integer; procedure SetPersonen(Person, Y: Integer); procedure Markieren(Plus: Boolean; Person: Integer); public constructor Create(AOwner: TComponent); procedure Paint; override; procedure Click; override;//(Sender: TObject); overload; procedure MouseEnter; override; procedure PersonenUpdate(Proband_ID: Integer); property Genealogie: TGenealogie read fGenealogie write fGenealogie; property Person: TObjectList read fPerson write fPerson; property Proband: Integer read fProband write fProband; property Generation: Integer read fGeneration write fGeneration; property Markiert: Integer read fMarkiert write fMarkiert; published // property OnClick; property OnEnter; end; implementation constructor TPerson.Create(AOwner: TComponent; Oben: Integer); begin inherited Create(AOwner); Self.fID:= -1; // keine Person verknüpft Self.Top:= Oben; Self.Left:= 30; Self.Height:= 24; // 24 = einzeilig (nur Name), 36 = zweizeilig Textausgabe (Name & Geburts- & Todesdatum) Self.Width:= 200; end; procedure TPerson.SetTyp(Bezeichnung: String); begin Self.fTyp:= Bezeichnung; Self.Hint:= Bezeichnung; end; procedure TPerson.Paint; begin case Self.fMarkiert of True: begin Self.Canvas.Font.Color:= clWhite; Self.Canvas.GradientFill(Self.ClientRect, $00D5BCAD, $0091522B, gdVertical); end; False: begin Self.Canvas.Font.Color:= clBlack; case Self.fMouseOver of True: Self.Canvas.GradientFill(Self.ClientRect, $00F6F2EE, $00D9C2B5, gdVertical); False: Self.Canvas.GradientFill(Self.ClientRect, $00FAF8F6, $00E8DED6, gdVertical); end; end; end; Self.Canvas.TextRect(Self.ClientRect, 6, 5, IntToStr(Self.fTag) + ' ' + Self.fTyp); // hier soll später der Name der Person ausgegeben werden Self.Canvas.Pen.Color:= $00A09070; Self.Canvas.Brush.Style:= bsClear; Self.Canvas.Rectangle(Self.ClientRect); end; procedure TPerson.MouseEnter; begin Self.fMouseOver:= True; Self.Repaint; end; procedure TPerson.MouseLeave; begin Self.fMouseOver:= False; Self.Repaint; end; procedure TPerson.Click; begin inherited; Self.fMarkiert:= not Self.fMarkiert; Self.Repaint; end; constructor TStammbaum.Create(AOwner: TComponent); var i: Byte; Pers: TPerson; begin inherited Create(AOwner); fGeneration:= 4; // Darstellung von 4 Generationen fPerson:= TObjectList.Create; for i:= 0 to 14 do begin // Positionierung ist nicht angepasst, muss noch gemacht werden case i of 0: Pers:= TPerson.Create(AOwner, 0); // Proband 1: Pers:= TPerson.Create(AOwner, 170); // Vater 2: Pers:= TPerson.Create(AOwner, -170); // Mutter 3..6: Pers:= TPerson.Create(AOwner, 300); // Großeltern 7..14: Pers:= TPerson.Create(AOwner, 540); // Urgroßeltern end; Pers.Parent:= Self; Pers.Tag:= i; { if i = 0 then Pers.Top:= 10 else Pers.Top:= TPerson(Person.Items[i-1]).Top + TPerson(Person.Items[i-1]).Height + 5;} case i of 0: Pers.SetTyp('Proband'); 1: Pers.SetTyp('Vater'); 2: Pers.SetTyp('Mutter'); 3,5: Pers.SetTyp('Großvater'); 4,6: Pers.SetTyp('Großmutter'); 7,9,11,13: Pers.SetTyp('Urgroßvater'); 8,10,12,14: Pers.SetTyp('Urgroßmutter'); end; Person.Add(Pers); end; Self.Width:= 750; Self.Height:= 600; fGenealogie:= TGenealogie.Create; end; procedure TStammbaum.SetPersonen(Person, Y: Integer); begin case Person of 1: TPerson(Self.Person.Items[Person]).Left:= 176; 2: TPerson(Self.Person.Items[Person]).Left:= 176; 3: TPerson(Self.Person.Items[Person]).Left:= 176; 4: TPerson(Self.Person.Items[Person]).Left:= TPerson(Self.Person.Items[Person-3]).Left + Trunc(TPerson(Self.Person.Items[Person-3]).Width / 2) + 20; 5: TPerson(Self.Person.Items[Person]).Left:= TPerson(Self.Person.Items[Person-3]).Left + Trunc(TPerson(Self.Person.Items[Person-3]).Width / 2) + 20; 6: TPerson(Self.Person.Items[Person]).Left:= TPerson(Self.Person.Items[Person-4]).Left + Trunc(TPerson(Self.Person.Items[Person-4]).Width / 2) + 20; 7: TPerson(Self.Person.Items[Person]).Left:= TPerson(Self.Person.Items[Person-1]).Left + Trunc(TPerson(Self.Person.Items[Person-1]).Width) + 20; 8: TPerson(Self.Person.Items[Person]).Left:= TPerson(Self.Person.Items[Person-2]).Left + Trunc(TPerson(Self.Person.Items[Person-2]).Width) + 20; 9: TPerson(Self.Person.Items[Person]).Left:= TPerson(Self.Person.Items[Person-4]).Left + Trunc(TPerson(Self.Person.Items[Person-4]).Width) + 20; 10: TPerson(Self.Person.Items[Person]).Left:= TPerson(Self.Person.Items[Person-5]).Left + Trunc(TPerson(Self.Person.Items[Person-5]).Width) + 20; end; end; procedure TStammbaum.Paint; begin Self.Canvas.Pen.Color:= $00A09070; Self.Canvas.Brush.Style:= bsClear; Self.Canvas.Rectangle(Self.ClientRect); TPerson(Self.Person.Items[0]).Top:= Trunc((Self.Height - TPerson(Self.Person.Items[0]).Height) / 2); { Proband } SetPersonen(1, 110); { Vater } SetPersonen(2, 110); { Mutter } SetPersonen(3, 170); { Vater des Vaters } SetPersonen(4, -50); { Mutter des Vaters } SetPersonen(5, 50); { Vater der Mutter } SetPersonen(6, 170); { Mutter der Mutter } SetPersonen(7, 200); { Vater des Vaters } SetPersonen(8, -160); { Mutter des Vaters } SetPersonen(9, -160); { Vater des Vaters } SetPersonen(10, 200); { Mutter des Vaters } end; procedure TStammbaum.Click;//(Sender: TObject); var i: Integer; begin //TPerson(Self.Person.Items[1])._OnDblClickForFamilyTree:= PersonDblClicked; Self.Repaint; end; procedure TStammbaum.MouseEnter; begin Self.SetFocus; end; procedure TStammbaum.Markieren(Plus: Boolean; Person: Integer); begin case Plus of True: begin TPerson(Self.Person.Items[Self.fMarkiert]).fMarkiert:= False; TPerson(Self.Person.Items[Self.fMarkiert+Person]).fMarkiert:= True; Self.fMarkiert:= Self.fMarkiert+Person; end; False: begin TPerson(Self.Person.Items[Self.fMarkiert]).fMarkiert:= False; TPerson(Self.Person.Items[Self.fMarkiert-Person]).fMarkiert:= True; Self.fMarkiert:= Self.fMarkiert-Person; end; end; Self.Repaint; end; procedure TStammbaum.PersonenUpdate(Proband_ID: Integer); begin if Proband_ID > -1 then begin { Proband } if Proband_ID > -1 then begin Proband:= Proband_ID; TPerson(Person.Items[0]).ID:= Proband_ID; TPerson(Person.Items[0]).Vorname:= TINDI(Genealogie.INDI.Items[TPerson(Person.Items[0]).ID]).GIVN end else Exit; { Vater } if TINDI(Person.Items[0]).FATH >= 0 then begin TPerson(Person.Items[1]).ID:= TINDI(Person.Items[0]).FATH; TPerson(Person.Items[1]).Vorname:= TINDI(Genealogie.INDI.Items[TPerson(Person.Items[1]).ID]).GIVN; end else begin TPerson(Person.Items[1]).Vorname:= '(Vater)'; end; { Mutter } if TINDI(Person.Items[0]).MOTH > -1 then TPerson(Person.Items[2]).ID:= TINDI(Person.Items[0]).MOTH else Exit; { Großvater I - Vater des Vaters } if TINDI(Person.Items[1]).FATH > -1 then TPerson(Person.Items[3]).ID:= TINDI(Person.Items[1]).FATH else Exit; { Großmutter I - Mutter des Vaters } if TINDI(Person.Items[1]).MOTH > -1 then TPerson(Person.Items[4]).ID:= TINDI(Person.Items[1]).MOTH else Exit; { Großvater II - Vater der Mutter } if TINDI(Person.Items[2]).FATH > -1 then TPerson(Person.Items[5]).ID:= TINDI(Person.Items[2]).FATH else Exit; { Großmutter II - Mutter der Mutter } if TINDI(Person.Items[2]).MOTH > -1 then TPerson(Person.Items[6]).ID:= TINDI(Person.Items[2]).MOTH else Exit; end; end; end. Geändert von hansklok (31. Aug 2010 um 13:25 Uhr) |
![]() |
Registriert seit: 9. Mai 2005 Ort: Nordbaden 925 Beiträge |
#5
Achtung nicht erschrecken. Wenn ich ein Programm auseinander nehme, sieht das immer so aus.
![]() TPerson:
Delphi-Quellcode:
Welchen Sinn haben diese Felder? Kommt mir merkwürdig vor.
fTag: Integer;
fTyp: String; fMarkiert: Boolean; OK, ich sehe unten, du verzweigst entsprechend beim Zeichnen. OK. fMouseOver: Boolean; dito. procedure SetTyp(Bezeichnung: String); string-Parameter sollten const sein. constructor Create(AOwner: TComponent; Oben: Integer); Der zusätzliche Parameter Oben kommt mir komisch vor. Damit rechnet man nicht. Mach den weg. //property _OnDblClickForFamilyTree: [...] Warum hast du das auskommentiert?
Delphi-Quellcode:
s.o.
property Tag: Integer read fTag write fTag;
property Typ: String read fTyp write fTyp; Außerdem fehlen TPerson Referenzen auf die Eltern, etc. TStammbaum: fGenealogie: TGenealogie; // hier werden die Daten zu den Personen geholt Zeig mal die Klasse. Zumindest der Bezeichner sieht verbeserungswürdig aus. fPerson: TObjectList; Zumindest der Bezeichner ist falsch. Wenn es nur eine Person ist, kann es keien ObjectList sein. Wie aber oben erwähnt, brauchst du keine ObjectList. Zumindest hier nicht. fProband: Integer; Nein, nicht Integer. TPerson. Also eine Referenz auf das konkrete Objekt. fGeneration: Integer; Was bedeutet dieses Feld? fMarkiert: Integer; Auch hier eine Referenz, keine ID. procedure SetPersonen(Person, Y: Integer); Parameter sind unverständlich. procedure PersonenUpdate(Proband_ID: Integer); Prozedurnamen sind Verben. wenn dann sollte es heißen UpdatePersonen und nicht anders herum. Aber auch dann passt der Name nicht. Denn die Prozedur soll ja den Probanden setzen. Also makeProband() wie in vorherigem Post genannt. Außerdem gilt natürlich auch hier: Vergiss die IDs. Wenn du IDs fürs dateiformat brauchst o.ä. dann lass sie drin. Nimm die aber nur zum Speichern und laden. Ansonsten solltest du mit Referenzen arbeiten. property Genealogie: TGenealogie read fGenealogie write fGenealogie; Sicher, dass das Public sein muss?
Delphi-Quellcode:
- properties können auch readonly sein. Manchmal ist das von Vorteil. Hier könnte man sowas überlegen. Insbesonder bei Person... äh... Personen
property Person: TObjectList read fPerson write
property Proband: Integer read fProband write fProband; property Generation: Integer read fGeneration write fGeneration; property Markiert: Integer read fMarkiert write fMarkiert;
Delphi-Quellcode:
Das überschreibt den Hint. Das ist so nicht ersichtlich. Wenn, dann solltest du zusätzlich die Möglichkeit bieten, das über ne Property abzustellen. Besser den Hint von außen setzen, wenn nötig. Wie der Hint aussehen soll ist nicht Sache von TPerson.
procedure TPerson.SetTyp(Bezeichnung: String);
begin Self.fTyp:= Bezeichnung; Self.Hint:= Bezeichnung; end; case Self.fMarkiert of Bei Boolean-Werten lohnt sich case eigentlich nicht wirklich. if ist da lesbarer
Delphi-Quellcode:
Hm... da gibts jetzt mehrere Sachen zu sagen.
case i of
0: Pers:= TPerson.Create(AOwner, 0); // Proband 1: Pers:= TPerson.Create(AOwner, 170); // Vater 2: Pers:= TPerson.Create(AOwner, -170); // Mutter 3..6: Pers:= TPerson.Create(AOwner, 300); // Großeltern 7..14: Pers:= TPerson.Create(AOwner, 540); // Urgroßeltern end; - case ist in wirklich objektorientierten programmen selten. Wenn du es doch brauchst, solltest du zumindest mal überlegen, ob es notwendig ist. - So langsam beginne ich zu verstehen, wie du das meinst. TPerson ist nicht wirklich eine Person, sondern nur die Grafische Darstellung und die Daten sind irgendwie in TGenealogie? Zeig unbedingt mal TGenealogie her. - Du hast immer noch nicht erklärt, warum du die Beschränkung auf 4Generationen bzw. 15 Personen hast. Warum willst du niemals mehr anzeigen?
Delphi-Quellcode:
Und was, wenn Person 11 ist? Oder 22? Diese Prozedur ist ziemlich kaputt. Sie sollte viel generischer sein.
procedure TStammbaum.SetPersonen(Person, Y: Integer);
begin case Person of Self.Canvas.Pen.Color:= $00A09070; Für sowas solltest du Konstanten einführen.
Delphi-Quellcode:
Da gehört das ja auch nicht rein. Das muss beim Erzeugen zugewiesen werden.
procedure TStammbaum.Click;//(Sender: TObject);
var i: Integer; begin //TPerson(Self.Person.Items[1])._OnDblClickForFamilyTree:= PersonDblClicked;
Delphi-Quellcode:
- plus ist unverständlich
procedure TStammbaum.Markieren(Plus: Boolean; Person: Integer);
begin case Plus of - zudem ist es eine Hybridkopplung. Sollte man vermeiden. TINDI(Person.Items[0]) Hier castest du TPerson-Objekte auf TINDI. Das ist merkwürdig und problematisch. Sei überhaupt vorsichtig mit casts. Vermeide sie wos nur geht. Exit; exit ist nicht gut. Vermeide es. exit kann man für so genannte Wächter verwenden. Also ganz am Anfang einer Methode. Aonsten solletst du exit vermeiden,w eil es ein Programm tendenziell unleserlich macht. ========================================= Langer Rede kurzer Sinn: Ich denke wir sollten nochmal Schritt für Schritt von vorne anfangen. Dann kriegen wir auch etwas Sauberes hin. Das wird ein bisschen Arbeit, aber du solltest dadurch einiges lernen (und ich auch). Lies trotzdem mal meine Kommentare oben und versuche, sie zu verstehen. Zeig erstmal TGenealogie und TINDI. Dann bau ich dir mal ein Grundgerüst. mfg Christian
Kaum macht man's richtig, schon klappts!
|
![]() |
Themen-Optionen | Thema durchsuchen |
Ansicht | |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
LinkBack |
![]() |
![]() |