Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Komponente ableiten (https://www.delphipraxis.net/154159-komponente-ableiten.html)

hansklok 30. Aug 2010 16:42

Komponente ableiten
 
Hallo,

ich habe eine Komponente
Delphi-Quellcode:
"TPerson"
erstellt, die von
Delphi-Quellcode:
TCustomControl
abgeleitet ist. Sie besitzt eine Eigenschaft
Delphi-Quellcode:
"Markiert"
vom Typ
Delphi-Quellcode:
Boolean
. Desweiteren habe ich eine Komponente
Delphi-Quellcode:
"TStammbaum"
erstellt, die ebenfalls von
Delphi-Quellcode:
TCustomControl
abgeleitet ist. Innehalb dieser habe ich 15 "TPerson"'s erstellt und platziert. Damit ich über den Quelltext eine einzelne
Delphi-Quellcode:
"TPerson"
ansprechen kann, habe ich zusätzlich noch eine
Delphi-Quellcode:
TObjectList "Personen"
innerhalb von
Delphi-Quellcode:
"TStammbaum"
erstellt. Nun möchte ich, dass wenn der Nutzer eine
Delphi-Quellcode:
"TPerson"
klickt, diese den Status markiert (True) erhält und alle anderen den Status False erhalten. Wenn ich die Komponenten via Quelltext über die Objektliste anspreche funktioniert das auch wunderbar. Sobald ich aber die OnClick Procedure von TStammbaum überschreiben möchte in etwa so
Delphi-Quellcode:
procedure TStammbaum.Click(Sender: TObject);
weigert sich Delphi zu kompilieren, weil er mit dem Sender nichts anfangen kann. Verwende ich nur
Delphi-Quellcode:
procedure TStammbaum.Click; override;
funktioniert zwar das Programm aber sobald ich auf eine TPerson klicke, tut sich gar nichts. Aber nur über TSender könnte ich die anderen Komponenten (TPerson) die sich auf TStammbaum befinden mit einer Klick-procedure ansprechen. Hat mich jmd. verstanden und kann mir bitte nen Tipp geben?

Vielen Dank

hansklok

mkinzler 30. Aug 2010 16:46

AW: Komponente ableiten
 
In diesem Fall drafst du nicht Überschreiben, sondern Überladen

Delphi-Quellcode:
procedure TStammbaum.Click(Sender: TObject); overload;

hansklok 30. Aug 2010 16:47

AW: Komponente ableiten
 
@mkinzler: Danke erstmal, und wie muss ich das Deklarieren?

xZise 30. Aug 2010 16:48

AW: Komponente ableiten
 
Moin,
abgesehen davon, wäre es nicht sinnvoller zu speichern, welches markiert ist? Dann musst du nicht alle durchgucken um herauszufinden, welches markiert ist.

MfG
Fabian

DeddyH 30. Aug 2010 16:50

AW: Komponente ableiten
 
Das Click-Ereignis tritt doch erst einmal bei den Personen ein, oder habe ich Dich falsch verstanden? Dann könntest Du doch ein Event OnPersonClick in TStammbaum einführen. Die TPersonen leiten Klicks dann an dieses Event weiter. Das ist leider etwas unelegant, weil die Personen dann den Stammbaum kennen müssen, aber mit etwas Überlegung lässt sich das bestimmt auch noch umgehen.

hansklok 30. Aug 2010 16:51

AW: Komponente ableiten
 
@xZise: ...ich speicher den Tag der markierten TPerson in einer Integer-Variable innerhalb von TStammbaum, jedoch muss ich ja dennoch alle anderen TPersons durchlaufen lassen, um deren Markiert auf False zu setzen, oder?

@DaddyH: Wie würde so etwas aussehen? Das erscheint mir logisch.

@mkinzler: Nun löst aber das Click Ereignis überhaupt nicht mehr aus.

r2c2 30. Aug 2010 17:54

AW: Komponente ableiten
 
Hier läuft was schief. Im Ansatz schon.

Zitat:

Zitat von hansklok (Beitrag 1046127)
ich habe eine Komponente
Delphi-Quellcode:
"TPerson"
erstellt, die von
Delphi-Quellcode:
TCustomControl
abgeleitet ist.

OK soweit. Auch, wenn man sich darüber streiten kann, inwieweit es nicht besser wäre, hier Darstellung und Logik mehr zu trennen.

Zitat:

Sie besitzt eine Eigenschaft
Delphi-Quellcode:
"Markiert"
vom Typ
Delphi-Quellcode:
Boolean
.
Das ist schon fragwürdig. Muss die Person das wirklich wissen oder ist das eher eine Eigenschaft des Stammbaums. Beides könnte der Fall sein. Kommt drauf an, wie der restliche Ansatz aussieht.

Zitat:

Innehalb dieser habe ich 15 "TPerson"'s erstellt
Innerhalb? Kommt mir komisch vor...

Zitat:

und platziert. Damit ich über den Quelltext eine einzelne
Delphi-Quellcode:
"TPerson"
ansprechen kann, habe ich zusätzlich noch eine
Delphi-Quellcode:
TObjectList "Personen"
innerhalb von
Delphi-Quellcode:
"TStammbaum"
erstellt.
Hm... kann ne gute Idee sein. Wenn du weißt, was du tust.

Zitat:

Nun möchte ich, dass wenn der Nutzer eine
Delphi-Quellcode:
"TPerson"
klickt, diese den Status markiert (True) erhält und alle anderen den Status False erhalten.
Das macht mich glauben, dass die Markierung eher eine Eigenschaft des Stammbaums ist.

Zitat:

Wenn ich die Komponenten via Quelltext über die Objektliste anspreche funktioniert das auch wunderbar. Sobald ich aber die OnClick Procedure von TStammbaum überschreiben möchte in etwa so
Delphi-Quellcode:
procedure TStammbaum.Click(Sender: TObject);
weigert sich Delphi zu kompilieren, weil er mit dem Sender nichts anfangen kann.
Das ist klar. Click hat ja auch keine Parameter. Aus gutem Grund.

Zitat:

Verwende ich nur
Delphi-Quellcode:
procedure TStammbaum.Click; override;
funktioniert zwar das Programm aber sobald ich auf eine TPerson klicke, tut sich gar nichts.
Klar. Weil eine Person etwas anderes ist als der Stammbaum.

Vorschlag: Du erklärst mal genauer, was du vor hast. Und zwar wirklich was und nicht wie. Dann machen wir uns mal gemeinsam Gedanken darüber, wie man das besser machen könnte...

mfg

Christian

hansklok 30. Aug 2010 20:03

AW: Komponente ableiten
 
Liste der Anhänge anzeigen (Anzahl: 1)
Im Grunde genommen geht es um die Erstellung eines Programms zur Speicherung Genealogischer Inhalte (Familienforschung). Im Control sollen je nach Auswahl des Users 2 bis 4 Generationen angezeigt werden.
Es gibt eine Klasse, die Personendatensätze. Eine Person können Eltern, Geschwister, Partner hinzugefügt werden. Die jeweils ausgewählte Person soll als Proband (1. Generation) im Stammbaum erscheinen, dahinter Vater und Mutter (2. Generation), dann Großvater und Großmutter (väterlicherseits) und umgekehrt (mütterlicherseits) (3. Generation) usw. Wird z.B. auf den Vater geklickt, so soll dieser Markiert werden und ein Editiermodus starten. Wird er doppelt geklickt, so soll er zum Probanden werden, also an die Position 1 (1. Generation) rutschen, dem entsprechend ändern sich auch die anderen Namen in der 2. bis 4. Generation in der Stammbaumansicht.

Gruß hansklok

r2c2 30. Aug 2010 21:11

AW: Komponente ableiten
 
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) Observer Pattern. Das ist die saubere Variante
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

hansklok 30. Aug 2010 21:33

AW: Komponente ableiten
 
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

r2c2 31. Aug 2010 08:10

AW: Komponente ableiten
 
Zitat:

Zitat von hansklok (Beitrag 1046181)
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:
// 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;
Das ist in etwa Lösung b)

Zitat:

Warum nur 15 Personen dargestellt werden sollen? Das hängt mit den 4 Generationen zusammen, die ich darstellen möchte: [...]
Ja, das ist klar. Aber warum nur 4 Generationen?

Zitat:

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.
Wie siehts mit Halbgeschwistern und Stiefgeschwistern aus?

mfg

Christian

hansklok 31. Aug 2010 13:08

AW: Komponente ableiten
 
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.

r2c2 31. Aug 2010 13:52

AW: Komponente ableiten
 
Achtung nicht erschrecken. Wenn ich ein Programm auseinander nehme, sieht das immer so aus. ;-)

TPerson:

Delphi-Quellcode:
      fTag: Integer;
      fTyp: String;
Welchen Sinn haben diese Felder? Kommt mir merkwürdig vor.

Delphi-Quellcode:
      fMarkiert: Boolean;
OK, ich sehe unten, du verzweigst entsprechend beim Zeichnen. OK.

Delphi-Quellcode:
      fMouseOver: Boolean;
dito.

Delphi-Quellcode:
procedure SetTyp(Bezeichnung: String);
string-Parameter sollten const sein.

Delphi-Quellcode:
constructor Create(AOwner: TComponent; Oben: Integer);
Der zusätzliche Parameter Oben kommt mir komisch vor. Damit rechnet man nicht. Mach den weg.

Delphi-Quellcode:
//property _OnDblClickForFamilyTree: [...]
Warum hast du das auskommentiert?

Delphi-Quellcode:
property Tag: Integer read fTag write fTag;
property Typ: String read fTyp write fTyp;
s.o.

Außerdem fehlen TPerson Referenzen auf die Eltern, etc.


TStammbaum:

Delphi-Quellcode:
      fGenealogie: TGenealogie; // hier werden die Daten zu den Personen geholt
Zeig mal die Klasse. Zumindest der Bezeichner sieht verbeserungswürdig aus.

Delphi-Quellcode:
      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.

Delphi-Quellcode:
      fProband: Integer;
Nein, nicht Integer. TPerson. Also eine Referenz auf das konkrete Objekt.

Delphi-Quellcode:
      fGeneration: Integer;
Was bedeutet dieses Feld?

Delphi-Quellcode:
      fMarkiert: Integer;
Auch hier eine Referenz, keine ID.

Delphi-Quellcode:
procedure SetPersonen(Person, Y: Integer);
Parameter sind unverständlich.

Delphi-Quellcode:
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.

Delphi-Quellcode:
property Genealogie: TGenealogie read fGenealogie write fGenealogie;
Sicher, dass das Public sein muss?

Delphi-Quellcode:
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;
- properties können auch readonly sein. Manchmal ist das von Vorteil. Hier könnte man sowas überlegen. Insbesonder bei Person... äh... Personen

Delphi-Quellcode:
procedure TPerson.SetTyp(Bezeichnung: String);
begin
  Self.fTyp:= Bezeichnung;
  Self.Hint:= Bezeichnung;
end;
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.

Delphi-Quellcode:
  case Self.fMarkiert of
Bei Boolean-Werten lohnt sich case eigentlich nicht wirklich. if ist da lesbarer

Delphi-Quellcode:
      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;
Hm... da gibts jetzt mehrere Sachen zu sagen.
- 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:
procedure TStammbaum.SetPersonen(Person, Y: Integer);
begin
  case Person of
Und was, wenn Person 11 ist? Oder 22? Diese Prozedur ist ziemlich kaputt. Sie sollte viel generischer sein.

Delphi-Quellcode:
  Self.Canvas.Pen.Color:= $00A09070;
Für sowas solltest du Konstanten einführen.

Delphi-Quellcode:
procedure TStammbaum.Click;//(Sender: TObject);
var
  i: Integer;
begin
  //TPerson(Self.Person.Items[1])._OnDblClickForFamilyTree:= PersonDblClicked;
Da gehört das ja auch nicht rein. Das muss beim Erzeugen zugewiesen werden.

Delphi-Quellcode:
procedure TStammbaum.Markieren(Plus: Boolean; Person: Integer);
begin
  case Plus of
- plus ist unverständlich
- zudem ist es eine Hybridkopplung. Sollte man vermeiden.

Delphi-Quellcode:
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.

Delphi-Quellcode:
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

xZise 1. Sep 2010 15:14

AW: Komponente ableiten
 
Zitat:

Zitat von hansklok (Beitrag 1046133)
@xZise: ...ich speicher den Tag der markierten TPerson in einer Integer-Variable innerhalb von TStammbaum, jedoch muss ich ja dennoch alle anderen TPersons durchlaufen lassen, um deren Markiert auf False zu setzen, oder?[...]

Ich sehe gerade TPerson muss das wissen, um sich zu zeichnen. Aber dennoch musst du nicht alle durchlaufen. Du weißt ja wer markiert war.

Delphi-Quellcode:
type
  TStammBaum = class
  private
    FMarkiert : TPerson; // Statt Integer, so muss man nicht mehr rechnen.
    procedure SetMarkiert(const APerson : TPerson);
  public
    property Markiert : TPerson read FMarkiert write SetMarkiert;
  end;

procedure TStammBaum.SetMarkiert(const APerson : TPerson);
begin
  if Assigned(FMarkiert) then
    FMarkiert.Markiert := False;
  FMarkiert := APerson;
end;
Alternativ könnte die Person den Stammbaum auch fragen ob es markiert ist (z.B. mithilfe eines Events) aber ich denke diese Lösungs ist wohl besser.

MfG
Fabian

r2c2 1. Sep 2010 20:16

AW: Komponente ableiten
 
Liste der Anhänge anzeigen (Anzahl: 2)
So, hier ist mal ein Grundgerüst. Die Arbeit von heute Nachmittag....

Es ist ausdrücklich nicht vollständig. Kleinkram hab ich weggelassen. z.B. hab ich zu den Personen nicht noch Eigenschaften wie Geschlecht oder Namenszusätze spezifiziert. Für die Idee sind die erstmal egal. Du kannst die dann selbst ergänzen.

Die Diagramme hab ich mit dem SoftwareIdeasModeler (www.softwareideas.net) gemacht. Das Prog läuft auch unter nem aktuellen Mono, was für mich als Linuxer recht praktisch ist. Das Programm hat zwar auch seine Macken (z.B. lassen sich zumindest unter Linux reflexive Assoziationen nicht so richtig positionieren), aber es ist benutzbar. Das Projekt ist im Anhang. Kannst du dir angucken.

Die Klassendiagramme sollten eigentlich recht selbsterklärend sein. Ich hab versucht sie möglichst einfach zu halten. Wenn dir die Dinger nicht so geläufig sind, guck einfach mal in die Wikipedia. Die Grundidee ist eigentlich ganz einfach.

So, dann legen wir mal los:

Zuerst einmal bauen wir uns ein Domänenmodell. Wir bauen also einfach die relevanten Teile der Wirklichkeit nach.

- Es gibt Personen und die sind irgendwie miteinander verwandt.
- Interessant ist nun wie man miteinander verwandt sein kann. Wär ja zu einfach, wenns da nur Mutter-Vater-Kind gäbe. Also, was gibts denn da für Sonderfälle?
* Verwitwet und später nochmal geheiratet ==> Das sind zwei getrennte Familien
* Geschieden und später nochmal geheiratet ==> dito
* Annulierte/für nichtig befundene Ehen ==> haben nie existiert; werden behandelt wie uneheliche Partnerschaften
* Uneheliche Kinder/Seitensprünge ==> Das wird schon lustiger. Ne Familie is sowas ja wohl nicht. Sollte man wohl ne eigene Klasse dafür machen.
* Ungeklärte Vaterschaften ==> solche Kinder könnte man zur vermuteten/vorgeblichen/wasauchimmer Familie packen und ne Notiz drankleben. Oder man erstellt dafür ne Familie mit unbekanntem Vater. Hierzu könnte man Dummy-Objekte oder nil-Werte speichern. Oder man führt ne neue Klasse ein.
* Ungeklärte Mutterschaften sind vermutlich seltener, könnte es aber auch geben; manche Daten lassen sich historisch vllt. einfach nicht mehr rekonstruieren ==> wie ungeklärte Vaterschaften
* Künstliche Befruchtung und Austragen durch eine Leihmutter. ==> Äh... hm... Das wird wohl ein zusätzliches Attribut
* Inzest ==> Keine Familie aber dennoch eine "Verbindung"
* Adoptionen ==> Hmm... ein Adoptivkind hat biologische Eltern und Adoptiveltern. Zudem könnte es auch sein, dass unverheiratete Einzelpersonen Kinder adoptieren. Ne Adoption ist demnach wohl was separates, gibt also ne eigene Klasse.
* Achso: Und man könnte mehrmals von unterschiedlichen Leuten adoptiert worden sein.

Hab ich jetzt alle möglichen und unmöglichen Fälle?

Aber zumindest ist eins sicher: Jeder Mensch hat nen Vater und ne Mutter - mögen sie bekannt sein oder nicht. Da ist wohl kaum dran zu rütteln. Deshalb kriegt TPerson je eine Referenz auf nen Vater und auf ne Mutter.

Bei solchen Objektgeflechten hat man zwei Möglichkeiten: Jede Info nur einmal (DRY principle) speichern und dadurch Redundanzen vermeiden oder das Objektgeflecht leichter navigierbar machen, dann aber eine hohe Zahl von Invarianten in Kauf nehmen. Beides hat seine Vor- und Nachteile. Hier, würde ich sagen, müssen wir leider das DRY-Prinzip etwas verletzen, damit wir leichter navigieren können. Also beispielsweise nicht nur von den Kindern zu den Eltern, sondern auch umgekehrt.

Nächster Schritt: Was haben wir an unterschiedlichen "Verbindungen"? Ehe, uneheliche Partnerschaft, Seitensprünge, Inzestfälle, teilweise ungeklärte Fälle, ... Wir könnten das in eine Klasse packen und einen Typ mit speichern oder für jedes eine eigene Klasse nehmen. Zumindest aber Ehen haben zusätzliche Attribute: Ein Datum der Eheschließung; ein Datum der Scheidung (durch Tod, Gerichtsbeschluss oder wie auch immer das gehen mag); wenn du die auch noch erwähnen willst: Trauzeugen, u.ä. ==> Am einfachsten ist, wir unterscheiden Ehen und sonstige Partnerschaften.

Ich hab zwar von Genealogie keine Ahnung, aber teilweise kann es wohl sein, dass es Daten gibt, die einfach unbekannt sind. Oder nur ungefähr bekannt "muss vor 1350 gestorben sein", "geboren um 1720", etc. Deshalb bauen wir uns eine Klasse bzw. hier einen Record, der mit sowas umgehen kann. TVagueDate. Durch überladene Operatoren kann man sowas dann fast benutzen wie ein normales TDate. Solltest du ne ältere Delphi-Version ahben, die noch keine Operatorenüberladung gestattet, musst du dafür eben Funktionen schreiben.

Jetzt haben wir ein Domänenmodell. Das tut aber bisher noch nix. Jetzt brauchen wir Operationen, die das Objektgefecht aufbauen und verändern. Dabei tun das die Objekte selbst. Es wird nicht von außen etwas gebaut und dann an den Objekten geändert, beispielsweise eine marriedTo-Eigenschaft gesetzt. Das wäre nicht objektorientiert. Stattdessen machen die Tperson-Objekte genau das, was reale Personen auch tun. Sie heiraten. Also gibt es eine Methode
Delphi-Quellcode:
marry(TPerson)
. Das Prinzip nennt sich "Tell, don't ask!" oder "Do ist myself"-Prinzip. Die Objekte werden nicht von außen geändert, sondern sie machen alles selbst.

In deiner kleinen Modellwelt machen die Personen also alles genau so, wie echte Menschen:
Delphi-Quellcode:
// Pseudocode
var
  Alice, Bob: TPerson;
  Charlie, Doris: TPerson;
  Peter: TPerson;

...
 
Alice.marry(Bob, Now);
Charlie := Alice.giveBirthToChildFrom(Bob, Now);
Doris := Alice.giveBirthToChildFrom(Bob, Now);
Bob.die(Now);
Alice.marry(Peter, Now);
Alice.die(Now);
Und genau das ist es, was die OO so toll macht. Man kriegt sehr schönen lesbaren Code. Den Code oben kannst du jemandem zeigen, der von Programmierung keine Ahnung hat und mit n bisschen Überlegen wird er ihn trotzdem verstehen. Und auch du wirst das besser verstehen, weil sich der Code im Idealfall (und das Beispiel oben ist ziemlich nah dran an diesem Idealfall) liest wie ein englischer Text.

Tell, don't ask sieht man hier ganz deutlich:
Delphi-Quellcode:
Alice.marry(Bob, Now);
"Alice, marry Bob now!" Wir sagen Alice, was sie zu tun hat und die wird das auch pflichbewusst tun. Prozedural würde das so aussehen:
Delphi-Quellcode:
// vereinfacht
Alice.marriedTo := Bob;
Alice.marriageDate := Now;
Bob.marriedTo := Alice;
Bon.marriageDate := Now;
Eigentlich würde das noch nichtmal reichen. Was, wenn Bob bereits verheiratet ist? Also müssten weitere Abfragen rein. Und das außerhalb von TPerson. Aber eigentlich sollten die Personen doch selbst wissen, wie man heiratet. Deshalb gehört der Code in TPerson rein und die Personen tun alles, was zum Heiraten nötig ist, selbst.

Wie würde TPerson.marry() nun aussehen? In etwa folgendermaßen:
Delphi-Quellcode:
// Pseudocode; gerade bei dem ganzen Moglichkeits-Gedöns müsste man nochmal drüber gucken

const
  MAX_AGE = 150; // it is supposed that there are no older people
  UNCERTAINTY = 200; // if a date is marked uncertain, it is uncertain w.r.t. this number of years in the one or the other direction

function TPerson.marry(AOtherPerson: TPerson; ADate: TVagueDate): TMarriage;
var
  Exception: EMarriageImpossibleException;
begin
  assert(AOtherPerson <> nil);
 
  ////////////   first handle error cases  ////////////////////////////////////
 
  // prevent overlapping marriages:
  if not (ADate.isUnknown or ADate.isUncertain) then
  begin
    for m: TMarriage in Self.Marriages do
    begin
      if (m.Start < ADate) and (m.End > ADate) then
      begin
        Exception := EMarriageImpossibleException.Create('Overlapping marriage');
        Exception.ConflictingMarriage := m;
        raise Exception;
      end;
    end;
    for m: TMarriage in AOtherPerson.Marriages do
    begin
      if (m.Start < ADate) and (m.End > ADate) then
      begin
        Exception := EMarriageImpossibleException.Create('Overlapping marriage');
        Exception.ConflictingMarriage := m;
        raise Exception;
      end;
    end;
  end
  else
  begin
    // do nothing
    // do not prevent marriage, if end of other marriage is uncertain or unknown
  end;
 
  // prevent marriage of probably dead people
  if Self.isProbablyDead(ADate) or AOtherPerson.isProbablyDead(ADate) then
  begin
    raise EMarriageImpossible.Create('Dead people cannot be married.');
  end;
 
  // prevent marriage of people probably not born yet
  if Self.hasProbablyBeenBorn(ADate) or AOtherPerson.hasProbablyBeenBorn(ADate) then
  begin
    raise EMarriageImpossible.Create('People have to be born before they can marry.');
  end;
 
 
  ////////////   error cases handled, now do the actual work  /////////////////
 
  Result := TMarriage.Create();
 
  // if there is already a partnership convert partnership to marriage:
  for p: TPartnershup in Self.Partnerships do
  begin
    // partnership with this person
    if ((p.Mother = Self) and (p.Father = AOtherPerson))
      or ((p.Father = Self) and (p.Mother = AOtherPerson)) then
    begin
      if not (p is TMarriage) then
      begin
        Result.Assign(p); // copy values
        p.Free; // can be freed here because all references to it will be replaced
        break; // there can be only one partnership
      end
      else
      begin
        // this cound be ether a former marriage with is person which has been devorced
        // or an overlapping marriage; The overlapping marriage is prevented above
      end;
    end;
  end;

  // set values
  Result.Start := ADate;
 
  // homosexual marriages leads to random assignment to Father and Mother
  // this is no problem as they won't get own children and adoptation
  // is treated separately
  if Self.isFeamle then
  begin
    Result.Mother := Self;
    Result.Father := AOtherPerson;
  end
  else
  begin
    Result.Mother := AOtherPerson;
    Result.Father := Self;
  end;
 
  Self.Partnerships.Add(Result);
  AOtherPerson.Partnerships.Add(Result);
 
  assert(Result <> nil);
  assert(Self.Partnerships.Contains(Result));
  assert(AOtherPerson.Partnerships.Contains(Result));
end;

function TPerson.isDead(ADate: TVagueDate): Boolean;
begin
  Result := ADate > Self.DateOfDeath;
  // overloaded operator '>' takes care that this is only true when the date is certain
end;

function TPerson.isProbablyDead(ADate: TVagueDate): Boolean;
begin
  Result := isDead or (age > MAX_AGE) or (probableAge > (MAX_AGE + UNCERTAINTY));
end;
Genau so geht es mit divorce(), die(), giveBirthToChildFrom(), ...

Zusätzlich zu den offensichtlichen Methoden gibt es noch ein paar weitere. Personen können nämlich nicht nur Kinder kriegen, sondern auch Eltern. Also natürlich nicht "Guck ma, ich hab heut ne Mama gekriegt!", sondern dadurch, dass man Ahnenforschung betreibt und so neue Namen erfährt. Das muss natürlich auch noch rein. Über addParents z.B. kann man eine bestehende Partnerschaft als Eltern einfügen. addMother würde eine neue Partnerschaft erzeugen, die Mutter-Rolle spezifizieren, den Vater erstmal undefiniert lassen und die neue Partnerschaft als Eltern der Person eintragen. addFather ebenfalls. Wenn addFather ausgeführt wird, wenn schon ne halbe Partnerschaft da ist (Mutter bekannt, Vater noch nicht), wird natürlich die bestehende Partnerschaft genutzt. Und wenn der Vater bereits bekannt ist, aber trotzdem addFather aufgerufen wird, ist wohl was faul ==> Exception

Prinzip verstanden?

Ein weiterer Punkt noch:
Wie oben erwähnt, gibt es diverse Invarianten also Bedingungen, die immer gelten müssen, damit die Daten konsistent sind:
- Eine Person kann nicht gleichzeitig Vater und Mutter sein.
- Im Stammbaum darf es keine Zyklen geben, d.h. keiner kann sein eigener Vater sein.
- Es können nur Personen heiraten, die auch gleichzeitig leben.
- Kinder können nicht älter als ihre Eltern sein.
- Die Kinder, die in TPartnership aufgeführt sind müssen auch diese Partnership als Eltern angegeben haben
- ...

Auf diese Invarianten musst du besonders aufpassen. Wenn sich einmal Kinder und Eltern nicht mehr einig darüber sind, wer nun wen geboren hat, gibts furchtbaren Streit und du hast wunderbare Bugs, die du jagen darfst. Werfe also entsprechende Exceptions. Zudem gibt es Programmierfehler, die du über Assertions abfangen kannst. Oft sind das nil-Werte. Wenn du mal irgendwo ein nil stehen hast, wo keines hin gehört, wirds auch lustig. Also auch hier aufpassen.

Ich hatte dich ja gewarnt, dass es viel Arbeit sein wird. Hier kommt sie nämlich:

Folgendes solltest du jetzt tun:
- verstehen, was ich geschrieben habe
- bei Unklarheiten nachfragen
- meine Idee verstehen
- bei Unklarheiten nachfragen
- gucken, ob das fachlich passt. Ich hab von Genealogie nicht wirklich Ahnung. Vielleicht hab ich ja einen fachlichen Kapitalfehler gemacht.
- überlegen, ob du Adoptionen berücksichtigen willst; die kann man beispielsweise auch gut vorerst mal ausklammern und später einbauen
- überlegen, ob das mit TVagueDate nötig/sinnvoll/kontraproduktiv/wasauchimmer ist
- ggf. logische Fehler in meinem Ansatz finden

Dann solltest du
- weitere Invarianten finden und dokumentieren
- das Diagramm um wichtige weitere Operationen und Attribute ergänzen; verlieb dich da nicht allzu sehr ins Detail; das kostet nur Zeit. Also nicht jetzt Namenszusätze und anderen unwichtigen Kleinkram spezifizieren, sondern das, was wirklich gebraucht wird.
- mir das Ergebnis zeigen

Wenn du willst, kannst du probehalber auch schon was implementieren. Es kann nur sein, dass sich da noch was ändert.

Die dann folgenden Schritte wären:
- Speichern und Laden modellieren; dazu gibts wohl separate Klassen
- Die grafischen Repräsentationen, die den Stammbaum darstellen, modellieren
- Das Ganze auch implementieren und testen
- Jetzt den Kleinkram dazu


//Nachtrag: Ich sehe gerade: Die Grafik hat tansparenten Hintergrund und das Forum wählt Schwarz statt weiß als Default. ==> einfach auf der Platte speichern und so ansehen...

mfg

Christian

hansklok 2. Sep 2010 00:45

AW: Komponente ableiten
 
Lieber Christian,

das ist ja ne Menge.

Was die Verwandtschaftsverhältnisse betrifft, so hast du meines Wissens nach alle Eventualitäten genannt. Das ist für den Anfang ganz schön viel.
Den Vorschlag einer Adoption eine eigene Klasse zu widmen finde ich sehr gut, auch, wenn die gängigen Genealogieprogramme Adoptiveltern zulassen. Es wird eine neue Familie (TPartnership) erzeugt und das Kind hinzugefügt. Diesem wird dann der Status "adoptiert" zugeordnet. Im Gedcom-Standard ist diese Eigenschaft unter "PEDIGREE_LINKAGE_TYPE" definiert. Siekann folgende Werte annehmen:
  • adopted - zeigt Adoptiveltern an
  • birth - zeigt biologische Eltern an
  • foster - zeigt an, dass das Kind in einer Pflegefamilie oder Vormundschaft war
  • sealing - zeigt an, dass das Kind an andere als seine leibliche Eltern gesiegelt wurde (speziell bei Mormonen)
Erklär mir deine Überlegung mit "TAdoption" bitte nochmal genauer.

Zu
Delphi-Quellcode:
TVagueDate
Auch diese Überlegung ist richtig. Aber die Sache verkompliziert sich noch, da hier der Gedcom-Standard ebenfalls detailliertere Optionen bietet:
  • DATE_APPROXIMATED - Datum, genähert
  • ABT = “um” oder “circa”
  • CAL = das Datum ist nicht exakt
  • EST = mathematisch errechnet, z. B. durch ein Ereignis und ein Alter. auf der Basis eines Algorithmus und eines anderen Datums geschätzt.
  • DATE_EXACT - exaktes Datum (z.B. 01.01.2000)
  • DATE_PERIOD - Datum, Zeitspanne
    • FROM = kennzeichnet den Beginn eines Ereignisses oder Status
    • TO = kennzeichnet das Ende eines Ereignisses oder Status
    • FROM Datum TO Datum
  • DATE_PHRASE - beliebige Aussage zum Datum, sozusagen benutzerdefiniert
  • DATE_RANGE - Datum, zeitlich eingegrenzt
    • AFT = Das Ereignis fand nach dem angegebenen Datum statt
    • BEF = Das Ereignis fand vor dem angegebenen Datum statt
    • BET Datum AND Datum = Das Ereignis fand irgendwann zwischen dem 1. und dem 2. Datum statt
  • Da muss also TVagueDate noch überarbeitet werden :)

    Das Beispiel, das eine Person eine weitere Person heiratet ist auch super verständlich. Warum kompliziert, wenns auch einfach geht :)

    Was eine Exception ist weiß ich, aber was ist das:
    Delphi-Quellcode:
    Exception: EMarriageImpossibleException;
    bzw. was hast du Dir darunter vorgestellt?

    Selbst wenn eine Person bereits ein Vater zugewiesen ist, so muss ein erneuter Aufruf von
    Delphi-Quellcode:
    AddFather
    nicht zwangsläufig zu einem Fehler führen, dann wird der vorhandene Vater einfach durch einen Anderen ersetzt!

    Alle genannten Invarianten leuchten mir ein, stimme Dir zu!

    Eine zusätzliche Invariante wäre z.B. noch
    • Sterbedatum der Ehepartner muss nach dem Heiratsdatum liegen!
    So das sind erstmal meine Anmerkungen zu deinem letzten Beitrag.

    Eine ganz blöde Frage noch. Ich implementiere die Klasse TPerson und vorher TPartnership, da diese ja innerhalb der Klasse TPerson verwendet wird. Nun ruft aber die Klasse TPartnership innerhalb eventuell auch einmal TPerson auf. Beim Kompilieren kommt es zu einem Abbruch, logisch, woher soll TPartnership TPerson kennen, wenn es erst danach implementiert ist. Verstehst du? Gibts da ne Lösung für, bestimmt, nur ist sie mir nicht geläufig!

    VG

    hansklok

    r2c2 2. Sep 2010 09:07

    AW: Komponente ableiten
     
    Zitat:

    Zitat von hansklok (Beitrag 1046866)
    Was die Verwandtschaftsverhältnisse betrifft, so hast du meines Wissens nach alle Eventualitäten genannt. Das ist für den Anfang ganz schön viel.

    Genau. Deshalb mein Vorschlag, die Adoption erst hinterher zu machen. Das geht in dem Fall ganz gut.

    Zitat:

    Den Vorschlag einer Adoption eine eigene Klasse zu widmen finde ich sehr gut, auch, wenn die gängigen Genealogieprogramme Adoptiveltern zulassen. Es wird eine neue Familie (TPartnership) erzeugt und das Kind hinzugefügt. Diesem wird dann der Status "adoptiert" zugeordnet. Im Gedcom-Standard ist diese Eigenschaft unter "PEDIGREE_LINKAGE_TYPE" definiert. Siekann folgende Werte annehmen:
    • adopted - zeigt Adoptiveltern an
    • birth - zeigt biologische Eltern an
    • foster - zeigt an, dass das Kind in einer Pflegefamilie oder Vormundschaft war
    • sealing - zeigt an, dass das Kind an andere als seine leibliche Eltern gesiegelt wurde (speziell bei Mormonen)

    So kannst du das auch machen. Nur musst du dann Unterscheiden zwischen TPartnership-Objekten, die wirklich eine Partnerschaft darstellt, wobei ein Teil aber unbekannt ist und Einzelpersonen, die Kinder adoptieren. Da müsste man überlegen, was fachlich besser passt. Möglich ist aber beides.

    Zitat:

    Erklär mir deine Überlegung mit "TAdoption" bitte nochmal genauer.
    Ein TGenealogyEntity kann Kinder adoptieren. Deshalb bietet diese Klasse eine Methode adopt() an. Diese Methode erstellt ein neues TAdoption-Objekt, weist die Werte zu und träge eine Referenz in den Adoptivling, sowie in die Adoptiveltern (TGenealogyEntity also entweder eine TPartnership oder eine TPerson) ein.

    Zitat:

    [INDENT]
    • DATE_APPROXIMATED - Datum, genähert
    • ABT = “um” oder “circa”
    • CAL = das Datum ist nicht exakt
    • EST = mathematisch errechnet, z. B. durch ein Ereignis und ein Alter. auf der Basis eines Algorithmus und eines anderen Datums geschätzt.

    ABT entspricht "uncertain". CAL müsste man noch einführen. EST versteh ich nicht ganz. Das läuft doch auch wieder auf sowas wie CAL hinaus.

    Zitat:

    [*]DATE_EXACT - exaktes Datum (z.B. 01.01.2000)
    ist drin.

    Zitat:

    [*]DATE_PERIOD - Datum, Zeitspanne
    Wenn man sowas braucht, sollte man daraus wohl eher nen weiteren Typen machen. TVagueTimePeriod, das zwei TVagueDate-Werte enthält.

    Zitat:

    [*]DATE_PHRASE - beliebige Aussage zum Datum, sozusagen benutzerdefiniert
    Wenn man das braucht, kann mans leicht ergänzen. Einfach nen zusätzlichen String-Wert.

    Zitat:

    [*]DATE_RANGE - Datum, zeitlich eingegrenzt
    • AFT = Das Ereignis fand nach dem angegebenen Datum statt
    • BEF = Das Ereignis fand vor dem angegebenen Datum statt
    • BET Datum AND Datum = Das Ereignis fand irgendwann zwischen dem 1. und dem 2. Datum statt
    [/LIST]
    Before und after ist drin. Between fehlt noch. Dazu braucht man nen weiteren TDate-Wert, nen Weiteren enum-Wert und ein paar weiter Invarianten.

    Zitat:

    Was eine Exception ist weiß ich, aber was ist das:
    Delphi-Quellcode:
    Exception: EMarriageImpossibleException;
    bzw. was hast du Dir darunter vorgestellt?
    Eine Exception ist letztendlich ja auch ne Klasse. Die von dir zitierte Zeile tut also nix anderes als eine Variable von der Klasse EMarriageImpossibleException deklarieren. Instanziiert wird die Exception dann weiter unten. EMarriageImpossibleException ist also eine Klasse, die von Exception erbt und ggf. zusätzliche Attribute und Methoden enthält. Wie im Quelltext ersichtlich hat sie eine Property namens "ConflictingMarriage".

    Zitat:

    Selbst wenn eine Person bereits ein Vater zugewiesen ist, so muss ein erneuter Aufruf von
    Delphi-Quellcode:
    AddFather
    nicht zwangsläufig zu einem Fehler führen, dann wird der vorhandene Vater einfach durch einen Anderen ersetzt!
    Nein. Das macht Probleme.
    Delphi-Quellcode:
    Charlie := Alice.giveBirthToChildFrom(Bob, Now);
    Doris := Alice.giveBirthToChildFrom(Bob, Now);
    // Charlie und Doris haben beide als Vater Bob
    // jetzt fällt uns auf, dass wir uns verlesen haben. Charlie stammt aus ner früheren Ehe:
    Charlie.addFather(Archibald);
    Die Folge:
    - Auch Doris ist jetzt ein Kind von Archibald
    - Alice ist nicht mehr mit Bob verheiratet o.ä.
    - Alice ist nun zwei Mal mit Archibald verheitatet/"verpartnert"; womöglich sogar nach Archibalds Tod.

    Deshalb muss hier ne Exception geworfen werden.

    Zitat:

    Eine ganz blöde Frage noch. Ich implementiere die Klasse TPerson und vorher TPartnership, da diese ja innerhalb der Klasse TPerson verwendet wird. Nun ruft aber die Klasse TPartnership innerhalb eventuell auch einmal TPerson auf. Beim Kompilieren kommt es zu einem Abbruch, logisch, woher soll TPartnership TPerson kennen, wenn es erst danach implementiert ist. Verstehst du? Gibts da ne Lösung für, bestimmt, nur ist sie mir nicht geläufig!
    Sehr guter Punkt! Normalerweise hat man diese Probleme nicht, weil man Kreuzreferenzen eh vermeiden sollte. Die Dinger sind eigentlich schlecht. Ziemlich schlecht. Wie in vorherigem Post erläutert, brechen wir diese Regel hier bewusst [1] um anderen Problemen aus dem Weg zu gehen. Also: Wir haben erstmal dieses Problem, dass wir Kreuzreferenzen haben und der Compiler die nicht will. Da müssen wir was dagegen tun.

    Die einfachste Lösung ist, das alles in eine Unit zu packen und Forward-Deklarationen zu verwenden:
    Delphi-Quellcode:
    TFoo = class; // forward declaration

    TBar = class
      foo: TFoo;
    end;

    TFoo = class
      bar: TBar;
    end;
    Das ist nicht allzu schön, allerdings sind die Klassen TPerson und TPartnership eh schon recht stark aneinander gekoppelt (was auch nicht schön ist, aber nicht besser geht). Man muss eben Kompromisse eingehen. Das ist normal.

    BTW. Bevor ichs vergesse. Das wollte ich gestern schon schreiben: Hier ist Typsicherheit recht wichtig. Du solltest also besser statt normalen TObjectLists, typisierte Listen verwenden. Also entweder ein neues Delphi einsetzen, das Generics kann oder von TObjectList ableiten und eine TPersonList, etc. draus machen oder Pseudo-Templates einsetzen.


    [1] Softwareentwicklung ist IMHO das ständige Ausbalancieren von Prinzipien und Daumenregeln. Die oberste Daumenregel heißt dabei "Wenn du das Gefühl hast, eine Regel brechen zu müssen, tu es. Wundere dich aber nicht über die Konsequenzen." Da sich viele Regeln widersprechen, muss man diese Regel ziemlich häufig anwenden. Immer ein Mittelmaß finden...


    mfg

    Christian

    hansklok 2. Sep 2010 17:26

    AW: Komponente ableiten
     
    Zitat:

    So kannst du das auch machen. Nur musst du dann Unterscheiden zwischen TPartnership-Objekten, die wirklich eine Partnerschaft darstellt, wobei ein Teil aber unbekannt ist und Einzelpersonen, die Kinder adoptieren. Da müsste man überlegen, was fachlich besser passt. Möglich ist aber beides.
    Erklär mir bitte nochmal, wie Du dir das vorgestellt hast, verstehe ich nicht.

    Zitat:

    Between fehlt noch. Dazu braucht man nen weiteren TDate-Wert, nen Weiteren enum-Wert und ein paar weiter Invarianten.
    Was wäre denn eine Invariante dafür? Mir fällt keine ein.

    Der Rest leuchtet mir ein.

    Nun ist mir was Ein- bzw. Aufgefallen. Heirat, Scheidung, Geburt, Tod, Jugendweihe, Taufe etc. sind ja alles Ereignisse, nennen wir sie TEvents. Jedes Event besitzt ein Start- und ein Enddatum. Wäre es also nicht günstiger eine Klasse TEvents zu erstellen und ihr widerrum eine Variable zuweisen, die ihr sagt um welches Event es sich handelt (Hochzeit, Taufe...). So wird das auch im GEDCOM-Standard bearbeitet.

    Und nochmal zu einer alten Frage von dir, war ich nur 15 Personen im Stammbaum darstellen will. Wenn nach mir ginge, gern auch mehr Generationen, da ist jedoch das Problem mit der Bildschirmbreite & -höhe, dass muss man halt bei der erstellung eines solchen Controls beachten.

    Wie verfahren wir weiter?

    Neue Gedankengänge oder Geistesblitze?

    HG hansklok

    r2c2 3. Sep 2010 10:26

    AW: Komponente ableiten
     
    Zitat:

    Zitat von hansklok (Beitrag 1047052)
    Zitat:

    So kannst du das auch machen. Nur musst du dann Unterscheiden zwischen TPartnership-Objekten, die wirklich eine Partnerschaft darstellt, wobei ein Teil aber unbekannt ist und Einzelpersonen, die Kinder adoptieren. Da müsste man überlegen, was fachlich besser passt. Möglich ist aber beides.
    Erklär mir bitte nochmal, wie Du dir das vorgestellt hast, verstehe ich nicht.

    Möglichkeit a)
    - Adoption ist keine Partnership, sondern was anderes ==> ne eigene Klasse
    - Eine Adoption ist eine Beziehung zwischen einem Adoptivling und einem Adoptierer
    - Adoptierer kann eine Einzelperson sein, aber auch eine "Familie" ==> eine TPartnership
    - TGenealogyEntity ist Superklasse zu TPartnership und TPerson ==> Alle Objekte dieser Klasse - seien es jetzt TPersons oder TPartnerships - können adoptieren

    Damit geschieht Adoption folgendermaßen:
    - TGenealogyEntity hat ne Methode adopt(TPerson)
    - adopt() erzeugt ein neues TAdoption-Objekt und registriert es bei Adoptivling und bei Adoptierer jeweils in einer Liste.

    Möglichkeit b)
    - Eine Adoption ist nur eine weitere Art Partnership; dazu verpasst du TPartnership ein typ-Feld, das es als Adoption ausweist; alternativ kannst du das auch tun, indem du TAdoption von TPartnership ableitest
    - diese Partnership hat nun den Adoptivling als Kind in der Liste

    Hier müssen aber ein paar Details berücksichtigt werden:
    - Die Adoption-Partnership kann eine Person enthalten oder auch zwei; man kann ja auch als Einzelperson Kinder adoptieren
    - Du musst aufpassen, dass du die Adoption-Partnerships nicht mit den normalen Partnerships mischst. Du kannst sie nicht mischen, weil du sonst nicht zwischen eigenen und adoptierten Kindern einer Partnerhip unterscheiden könntest. Du brauchst also für das verheiratete Ehepaar Alice und Bob, die bereits ihren biologischen Sohn Charlie haben eine weitere Partnership von Alice und Bob, wenn diese Doris adoptieren.
    - Dadurch hast du eine bisher bestehende Invariante verändert. Bisher gilt ja, dass zwei Personen zu einem Zeitpunkt nur eine Partnership miteinander haben können. Sie können also nicht gleichzeitig zweimal miteinander verheiratet sein oder verheiratet sein, und gleichzeitig miteinander(!) uneheliche Kinder zeugen. Jetzt hast du ne Ausnahme in der Invariante drin. Bei Adoptionen geht sowas nämlich doch.
    - Eine weitere Invariante kriegt ne Ausnahme: Bisher gilt: Eine Partnership gesteht immer aus zwei Personen. Es ist möglich dass eine der beiden unbekannt ist, aber es sind immer zwei. Jetzt kann es sein, dass eine Partnership aus nur einer Person besteht (was den Namen Partnersip ad absurdum führt).
    - vielleicht gibts noch anderes zu beachten; wer weiß...

    ==> IMHO ist Variante b) komplizierter, da die Invarianten komplizierter werden und schlechter nachvollziehbar/lesbar, weil Partnership auch Einzelpersonen beschreiben kann und nun auch Überlappungen existieren können. Ich würde also Variante a) empfehlen.

    Zitat:

    Zitat:

    Between fehlt noch. Dazu braucht man nen weiteren TDate-Wert, nen Weiteren enum-Wert und ein paar weiter Invarianten.
    Was wäre denn eine Invariante dafür? Mir fällt keine ein.
    - wenn uncertainty <> ucRange ist (also der between-Fall), dann ist Value2 immer 0
    - wenn uncertainty = ucRange ist, dann ist Value2 > Value1

    Zitat:

    Nun ist mir was Ein- bzw. Aufgefallen. Heirat, Scheidung, Geburt, Tod, Jugendweihe, Taufe etc. sind ja alles Ereignisse, nennen wir sie TEvents. Jedes Event besitzt ein Start- und ein Enddatum. Wäre es also nicht günstiger eine Klasse TEvents zu erstellen und ihr widerrum eine Variable zuweisen, die ihr sagt um welches Event es sich handelt (Hochzeit, Taufe...). So wird das auch im GEDCOM-Standard bearbeitet.
    Nein, das ist keine allzu gute Idee. Warum? Wenn man so eine Klassifizierung einführt, muss man sie auch nutzen. Ansonsten ist sie nur unnötiger Overhead. Das ganze nennt sich "vapor class" (siehe hier: http://www.christian-rehn.de/2009/08...hinen-und-ood/). Du nutzt die Abstraktion der "Events" nicht, sondern musst bei jedem Event wieder unterscheiden um was genau es sich nun handelt. Dadurch kriegst du fehleranfällige und vor allem unnötige if-Abfragen rein: "Wenn das ne Taufe ist, dann mal da Wasser hin, wenns ne Trauung ist, zwei Ringe und bei dem Sterbedatum n Kreuz und bei..." Das macht alles viel komplizierter. Du könntest jetzt sagen, dann leite ich eben Taufe, Trauung, Tod, etc. von Event ab und benutze Polymorphie. Das wäre wieder objektorientierter und du sparst dir die if-Konstruktionen. Einfacher macht es das aber trotzdem nicht. ==> Bleib mal lieber bei den einfachen Attributen.

    Zitat:

    Und nochmal zu einer alten Frage von dir, war ich nur 15 Personen im Stammbaum darstellen will. Wenn nach mir ginge, gern auch mehr Generationen, da ist jedoch das Problem mit der Bildschirmbreite & -höhe, dass muss man halt bei der erstellung eines solchen Controls beachten.
    Kennst du Scrollbars?

    Zitat:

    Wie verfahren wir weiter?
    Ergänze meinen Ansatz um alles Nötige. Operationen zum sterben, sich scheiden lassen, Ehen entfernen (wenn man falsche Daten hatte), ...
    Das kannst du direkt im Diagramm tun. Wenn du aus irgendwelchen Gründen nicht so gerne das Zeug im Diagramm machen willst, schreib den interface-Teil des Codes. Also Felder und Methoden. Wenn du mir das Ergebnis zeigst, kann ich drüber gucken, eventuelle Probleme anmerken und danach beschäftigen wir uns mit der Datensepicherung.

    mfg

    Christian

    hansklok 4. Sep 2010 14:50

    AW: Komponente ableiten
     
    ...also nun hab ich das verstanden und Möglichkeit A scheint mir auch besser und vor allem weniger anfällig für Fehler.

    Ich habe einen Entwurf für eine Event-Oberklasse gemacht, von ihm sind dann neue Objekttypen (z.B. Scheidung, Hochzeit etc.) abgeleitet.

    Zitat:

    Kennst du Scrollbars?
    :-) Ja die kenne ich. Natürlich kann man später auch alle Generationen darstellen, mal sehen wann wir an die stelle kommen. Ursprünglich dachte ich an die Übersichtlichkeit. lass uns mal gucken, wies aussieht, wenn wir das Problem gelöst haben.

    LG hansklok

    r2c2 5. Sep 2010 10:40

    AW: Komponente ableiten
     
    Zitat:

    Zitat von hansklok (Beitrag 1047507)
    Ich habe einen Entwurf für eine Event-Oberklasse gemacht, von ihm sind dann neue Objekttypen (z.B. Scheidung, Hochzeit etc.) abgeleitet.

    Aus welchem Grund? Tu sowas nur, wenn du einen Vorteil daraus hast. Das macht die Sache erstmal deutlich komplizierter. Damit du das wirklich nutzen kannst, müsstest du Polymorphie einsetzen können und da sehe ich momentan keine sinnvolle Möglichkeit. Modelliere wirklich nur das, was du auch brauchst. Ansonsten machst du dir nur das Leben schwer: http://de.wikipedia.org/wiki/KISS-Prinzip

    Weitere Daten kannst du - wenn nötig - entweder zu TPerson (oder andere Klassen) packen oder in ne generische Notizklasse auslagern. Du brauchst nicht zu allem, was dir einfällt, ne eigene Klasse.

    Alles weitere in der Antwort auf deine Mail.

    Zitat:

    Zitat:

    Kennst du Scrollbars?
    :-) Ja die kenne ich. Natürlich kann man später auch alle Generationen darstellen, mal sehen wann wir an die stelle kommen. Ursprünglich dachte ich an die Übersichtlichkeit. lass uns mal gucken, wies aussieht, wenn wir das Problem gelöst haben.
    Ja, das sehen wir dann, wenn wir uns der grafischen Darstellung widmen.

    mfg

    Christian

    hansklok 7. Sep 2010 18:08

    AW: Komponente ableiten
     
    Zitat:

    Zitat von r2c2 (Beitrag 1047597)
    Aus welchem Grund? Tu sowas nur, wenn du einen Vorteil daraus hast. Das macht die Sache erstmal deutlich komplizierter. Damit du das wirklich nutzen kannst, müsstest du Polymorphie einsetzen können und da sehe ich momentan keine sinnvolle Möglichkeit. Modelliere wirklich nur das, was du auch brauchst. Ansonsten machst du dir nur das Leben schwer: http://de.wikipedia.org/wiki/KISS-Prinzip

    Weitere Daten kannst du - wenn nötig - entweder zu TPerson (oder andere Klassen) packen oder in ne generische Notizklasse auslagern. Du brauchst nicht zu allem, was dir einfällt, ne eigene Klasse.

    Das verstehe ich, nur ich hatte mir folgendes überlegt (wieder in Hinblick auf den Standard):

    Eine Geburt oder eine Hochzeit sind genauso Ereignisse wie eine Taufe oder eine Scheidung. Nicht immer gibt es eindeutige Belege über den Zeitpunkt eines Ereignisses, deshalb können auch Zeitspannen (z.B. zwischen dem 31.01.2009 und 03.04.2009) angegeben werden. Alle diese exemplarischen Ereignistypen haben dieselben Standardeigenschaften (z.B. Datum, Ort). Deshalb habe ich neue Klassen von TEvent abgeleitet und beispielsweise bei TMarriage noch den Taufpaten als Eigenschaft hinzugefügt, verstehst du?

    Ein Umzug, also ein Wohnortwechsel ist genauso ein Ereignis wie z.B. eine Volkszählung. Das war mein Hintergrundgedanke bei der ganzen Sache.

    Erkläre mir bitte nochmal deine Alternative genauer!

    HG hansklok

    r2c2 7. Sep 2010 21:13

    AW: Komponente ableiten
     
    Zitat:

    Zitat von hansklok (Beitrag 1048076)
    Eine Geburt oder eine Hochzeit sind genauso Ereignisse wie eine Taufe oder eine Scheidung.

    Das ist klar und unproblematisch. Nur heißt das nicht, dass man das auch modellieren muss. Jede Person ist ein Lebewesen. Außerdem gibt es unterschiedliche Personen: Männer und Frauen. Achso und Kinder gibt es auch noch. Also Mädchen und Jungen. ... Trotzdem wirst du in deinem Programm keine TLivingBeing, TMan, TWoman, TGorl und TBoy finden. UNd TNose, TLeftEye und TRightEye auch nicht. OK, das sind jetzt Extrembeispiele. Was ich damit sagen will: Nur die Tatsache, dass es logisch gesehen diese Klassifizierung gibt, impliziert noch lange nicht, dass das auch so modelliert werden muss. Jede zusätzliche Klasse bringt zusätzliche Komplexität.

    Zitat:

    Nicht immer gibt es eindeutige Belege über den Zeitpunkt eines Ereignisses, deshalb können auch Zeitspannen (z.B. zwischen dem 31.01.2009 und 03.04.2009) angegeben werden.
    Das ist auch unstrittig. Deshalb gibt es TVagueDate.

    Zitat:

    Alle diese exemplarischen Ereignistypen haben dieselben Standardeigenschaften (z.B. Datum, Ort). Deshalb habe ich neue Klassen von TEvent abgeleitet und beispielsweise bei TMarriage noch den Taufpaten als Eigenschaft hinzugefügt, verstehst du?

    Ein Umzug, also ein Wohnortwechsel ist genauso ein Ereignis wie z.B. eine Volkszählung. Das war mein Hintergrundgedanke bei der ganzen Sache.
    Dein Gedanke ist mir klar. Er resultiert meiner Meinung nach aus der typischen Überbewertung der Vererbung.

    Wenn man anfängt, OO zu lernen, bleibt einem wohl aus irgendwelchen Gründen die Vererbung besonders gut im Gedächtnis. Der Vorteil erscheint augenblicklich klar: Gleiche Eigenschaften und Methoden werden in Basisklassen definiert und an die Kindklassen vererbt. So muss man das gleiche Zeug nicht mehrmals schreiben. Der Punkt ist: Eigentlich ist Vererbung, genauer: Generalisierung/Spezialisierung viel mehr. Ich hab jetzt nicht die Zeit, das vollständig zu erläutern, aber um mal einen Eindruck zu geben: Der eigentlich Punkt ist, dass man mit den Kindklassen genauso umgehen kann wie mit der Basisklasse. Das werden wir uns wohl später bei der Datenspeicherung zunutze machen [1]: Wir leiten alle zu speichernden Klassen von ner gemeinsamen Basisklasse ab und schreiben Code, der uns erlaubt ein beliebiges Objekt dieser Klasse zu speichern. Wir machen uns also ne Liste mit allen Elementen, die gespeichert werden sollen. Unser Code zum Speichern wird nun die liste in ner Schleife durchgehen und je eine Methode (save) zum Speichern aufrufen.
    Delphi-Quellcode:
    // Pseudocode
    for elem in AllElements do
    begin
      elem.save;
    end;
    Diese vier Zeilen werden es wohl letztendlich im Grunde genommen sein. elem kann eine TPerson sein. Oder ein TPartnership oder ein TAdoption. Egal. Der Code behandelt alle gleich. Es wird auch keine explizite Unterscheidung gemacht "if elem is TPerson then". Das alles passiert - wenn man richtig macht - automatisch durch Vererbung und Polymorphie, d.h. durch dynamische Bindung.

    Jetzt meine Frage: Wo wirst du im Code eine Variable als TEvent und nicht als TBirth deklarieren? Wo? Nirgends, denn überall ist der Unterschied zwischen TBirth und TMarriage signifikant. Das sollte schonmal ein Alarmsignal sein. Wenn du einen Typen definierst, den du nie benutzt, ist zwar nicht zwangsläufig, aber doch vermutlich was faul.

    Zweiter Punkt: Ist TMarriage nun ein TEvent oder ein TPartnership? Aus gutem Grund kann es nicht beides sein.

    Dritter Punkt: Leere Klassen. Du wirst dadurch Klassen haben, die ganz oder fast leer sind. Dem Tod kannst du maximal noch ne Ursache zuweisen, aber essenziell wird das Ding leer sein. TDeath tut nichts anderes als TEvent, es verhält sich mangels Methoden sogar überhaupt nicht. Auch das ist wieder so etwas, wo die Alarmglocken läuten sollten. Das *kann* OK sein. Ist aber oft ein Zeichen für falsches Vorgehen. Oben hab ich irgendwo "OOD and Coffee" verlinkt. Lies das mal. Robert C. Martin beschreibt da wirklich gut, was falsche Abstraktionen sind.

    Was ist also die Alternative?
    - TPerson hat ein DateOfBirth: TVagueDate
    - Und ein DateOfDeath: TVagueDate.
    - Wenn du unbedingt noch die Todesursache haben willst, hat TPerson eben auch noch ein CauseOfDeath: string.
    - Ansonsten kann die Todesursache, sofern sie mal ausnahmsweise von Bedeutung ist, ja auch als Notiz hinzugefügt werden. Wenn du in ner späteren Version n Feature "Die häufigsten Todesursachen" einbauen willst, kannst du immer noch das CauseOfDeath-Feld einführen. Bis dahin reicht IMHO die Notiz.
    ==> So wird ein Programm bedeutend einfacher. Wenn du das dann mal implementierst, wirst du merken, dass es auch so schon kompliziert genug ist. Allein schon das freigeben wird dich vermutlich ein paar Stunden kosten, bis dus einigermaßen fehlerfrei implementiert hast. Je nachdem, wie viel ich dir dabei helfe. Denke also nicht, dass du nicht auf Einfachheit achten müsstest. Das, was du vor hast, ist kein kleines Programm mehr.

    [1] Achtung! Das ist noch nicht durchdacht. Bisher nur meine vage Vorstellung von dem, was wir letztendlich tun werden. Das muss ich mir später mal genauer durch den Kopf gehen lassen.

    mfg

    Christian

    hansklok 1. Jan 2011 22:53

    AW: Komponente ableiten
     
    Lange war es still, nun bin ich einen Schritt weiter.

    Ich habe folgendes Problem und komme nicht weiter:

    Im Stammbaum (
    Delphi-Quellcode:
    TFamilyTree
    ) werden 15 (zunächst, für die Frage nicht weiter wichtig) grafische "Personen" angelegt. Diese sind von der Klasse
    Delphi-Quellcode:
    TTreePerson
    . Wird im
    Delphi-Quellcode:
    TFamilyTree
    auf eine
    Delphi-Quellcode:
    TTreePerson
    einmal geklickt, so soll diese den Focus erhalten (
    Delphi-Quellcode:
    fSelected = True
    ). Alle anderen 15 Personen sollen im Gegenzug
    Delphi-Quellcode:
    fSelected = False
    erhalten. Wird eine Person doppelgeklickt, so soll diese im Stammbaum an Position 1 der
    Delphi-Quellcode:
    fTreePerson
    rutschen und
    Delphi-Quellcode:
    fSelected = True
    erhalten, dementsprechend aktualisieren sich alle nachfolgenden Generationen aufgrund des neuen Probanden. Genauso verhält es sich mit dem MouseOver. Ich habe keine Ahnung, wie ich das anstellen soll, da sich ja die Personen innerhalb der Komponente "TFamilyTree" befinden.

    Hat mich jemand verstanden und kann mir eventuell bitte helfen?

    Danke und HG hansklok


    Delphi-Quellcode:
    unit uGenealogyFamilyTree;
    {$mode objfpc}{$H+}

    interface

    uses
      Classes, Controls, Graphics, SysUtils, uGenealogyFile;

    const
      ControlCol = $0099887F;
      LineCol   = $00E2E2E2;

      PersBorderColor = $00606060;
      PersHoverCol   = $00746155;

      NormalPersonColStart = $00F2F2F3;
      NormalPersonColEnd  = $00C5C5C5;
      NormalPersonFontCol = clBlack;

      NormalPersonHoverColorStart = $00F5F1EE;
      NormalPersonHoverColorEnd  = $00E0CEC3;

      SelectedPersonColStart = $00D3C3B3;
      SelectedPersonColEnd  = $00B39B83;
      SelectedPersonFontCol = clWhite;

      PersonWidth = 210;
      PersonHeight = 60;

    type
      TTreePerson = class(TObject)
        private
          fName: String;
          fSelected,
          fMouseOver: Boolean;
        public
          property Name: String read fName write fName;
          property Selected: Boolean read fSelected write fSelected;
          property MouseOver: Boolean read fMouseOver write fMouseOver;

          procedure Draw(AName: String; ACanvas: TCanvas; ATop, ALeft: Integer);
      end;

      TFamilyTree = class(TCustomControl)
        private
          fGeneration,
          fProband,  // erste Person eines Stammbaums
          fSelectedPerson: Integer; // ausgewählte Person
          fGenealogyFile: TGenealogyFile;
          fTreePerson: array[1..15] of TTreePerson; // erstmal werden nur 15 dargestellt
        public
          constructor Create(AOwner: TComponent);
          destructor Destroy;

          property Generation: Integer read fGeneration write fGeneration;
          property Proband: Integer read fproband write fProband;
          property SelectedPerson: Integer read fSelectedPerson write fSelectedPerson;
          property GenealogyFile: TGenealogyFile read fGenealogyFile write fGenealogyFile;

          procedure Paint; override;
      end;

    implementation

    var
      i: Integer;
      PersonRect, PersonOverRect: TRect;
      Style: TTextStyle;

    procedure TTreePerson.Draw(AName: String; ACanvas: TCanvas; ATop, ALeft: Integer);
    begin
      ACanvas.Pen.Width:= 1;
      ACanvas.Pen.Color:= PersBorderColor;
      ACanvas.Font.Size:= 10;
      Style.Layout:= tlCenter;
      Style.Alignment:= taCenter;
      Style.SystemFont:= False;

      PersonRect:= Rect(ATop, ALeft, PersonWidth, PersonHeight);
      PersonOverRect:= Rect(ATop-5, ALeft-5, PersonWidth+5, PersonHeight+60);

      if fSelected and fMouseOver then
         begin
           ACanvas.Brush.Style:= bsSolid;
           ACanvas.Pen.Width:= 0;
           ACanvas.Brush.Color:= PersHoverCol;
           ACanvas.RoundRect(PersonOverRect, 5, 5);
           ACanvas.GradientFill(PersonRect, SelectedPersonColStart, SelectedPersonColEnd, gdVertical);
           ACanvas.Font.Color:= SelectedPersonFontCol;
         end
      else
      if not fSelected and fMouseOver then
         begin
           ACanvas.Brush.Style:= bsSolid;
           ACanvas.Pen.Width:= 0;
           ACanvas.Brush.Color:= PersHoverCol;
           ACanvas.RoundRect(PersonOverRect, 5, 5);
           ACanvas.GradientFill(PersonRect, NormalPersonHoverColorStart, NormalPersonHoverColorEnd, gdVertical);
           ACanvas.Font.Color:= NormalPersonFontCol;
         end
      else
      if not fSelected and not fMouseOver then
         begin
           ACanvas.GradientFill(PersonRect, NormalPersonColStart, NormalPersonColEnd, gdVertical);
           ACanvas.Font.Color:= NormalPersonFontCol;
         end
      else
      if fSelected and not fMouseOver then
         begin
           ACanvas.Brush.Style:= bsSolid;
           ACanvas.GradientFill(PersonRect, SelectedPersonColStart, SelectedPersonColEnd, gdVertical);
           ACanvas.Font.Color:= SelectedPersonFontCol;
         end;

      ACanvas.Brush.Style:= bsClear;
      ACanvas.TextRect(Rect(ATop + 5, ALeft + 5, PersonWidth - 5, PersonHeight - 5), 1, 1, AName, Style);
      ACanvas.Rectangle(PersonRect);
    end;

    constructor TFamilyTree.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      Color:= ControlCol;
      fGenealogyFile:= TGenealogyFile.Create;
      fProband:= fGenealogyFile.Proband;
      fSelectedPerson:= fProband;
      for i:= 1 to 15 do
        fTreePerson[i]:= TTreePerson.Create;
    end;

    destructor TFamilyTree.Destroy;
    begin
      inherited Destroy;
      for i:= 1 to 15 do
        fTreePerson[i].Free;
      fGenealogyFile.Free;
    end;

    procedure TFamilyTree.Paint;
    begin
      if fProband = -1 then
         fTreePerson[1].Name:= 'Proband hinzufügen'
      else
         fTreePerson[1].Name:= fGenealogyFile.Persons[fSelectedPerson].FirstName + ' ' +
                             fGenealogyFile.Persons[fSelectedPerson].LastName;

      fTreePerson[1].Draw(fTreePerson[1].Name, Canvas, 20, 20);

      inherited Paint;
    end;

    end.


    Alle Zeitangaben in WEZ +1. Es ist jetzt 07:14 Uhr.

    Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
    LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
    Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz