|
![]() |
|
Registriert seit: 9. Mai 2005 Ort: Nordbaden 925 Beiträge |
#1
![]() 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.
- 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. ![]() ![]() Between fehlt noch. Dazu braucht man nen weiteren TDate-Wert, nen Weiteren enum-Wert und ein paar weiter Invarianten.
- wenn uncertainty = ucRange ist, dann ist Value2 > Value1 ![]() 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?
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
Kaum macht man's richtig, schon klappts!
|
![]() |
Registriert seit: 14. Apr 2004 Ort: Karlsruhe 318 Beiträge Delphi 2010 Architect |
#2
...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. ![]() Kennst du Scrollbars?
![]() LG hansklok |
![]() |
Registriert seit: 9. Mai 2005 Ort: Nordbaden 925 Beiträge |
#3
Ich habe einen Entwurf für eine Event-Oberklasse gemacht, von ihm sind dann neue Objekttypen (z.B. Scheidung, Hochzeit etc.) abgeleitet.
![]() 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. ![]() ![]() Kennst du Scrollbars?
![]() mfg Christian
Kaum macht man's richtig, schon klappts!
|
![]() |
Registriert seit: 14. Apr 2004 Ort: Karlsruhe 318 Beiträge Delphi 2010 Architect |
#4
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:
![]() 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. 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 |
![]() |
Registriert seit: 9. Mai 2005 Ort: Nordbaden 925 Beiträge |
#5
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. 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:
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.
// Pseudocode
for elem in AllElements do begin elem.save; end; 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
Kaum macht man's richtig, schon klappts!
|
![]() |
Registriert seit: 14. Apr 2004 Ort: Karlsruhe 318 Beiträge Delphi 2010 Architect |
#6
Lange war es still, nun bin ich einen Schritt weiter.
Ich habe folgendes Problem und komme nicht weiter: Im Stammbaum (TFamilyTree ) werden 15 (zunächst, für die Frage nicht weiter wichtig) grafische "Personen" angelegt. Diese sind von der Klasse TTreePerson . Wird im TFamilyTree auf eine TTreePerson einmal geklickt, so soll diese den Focus erhalten (fSelected = True ). Alle anderen 15 Personen sollen im Gegenzug fSelected = False erhalten. Wird eine Person doppelgeklickt, so soll diese im Stammbaum an Position 1 der fTreePerson rutschen und 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. |
![]() |
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 |
Gehe zu... |
LinkBack |
![]() |
![]() |