![]() |
Delphi-Version: XE4
Klasseninstanz zur Laufzeit bestimmen
Hi zusammen
Ich habe eine Klasse, der ich (Schrift-)Attribute zuweisen kann, die ich per eigenem Frame einstelle. Den Frame habe ich ![]()
Delphi-Quellcode:
Instanzen dieser Klasse erstelle ich so:
TAttributsClass = Class(TPersistent)
public BackGround : TColor; ForeGround : TColor; StyleBold: Boolean; StyleItalic : Boolean; StyleUnderLine : Boolean; StyleStrikeOff : Boolean; AttributName: String; end;
Delphi-Quellcode:
Der erste Parameter bezeichnet dabei die Instanz, die erzeugt werden soll, der zweite den String, der in der Combobox angezeigt wird.
constructor TCssAttriTLBXFrame.Create(AOwner: TComponent);
begin inherited; FCssAttributsList := TDataObjectList<TAttributsClass>.Create(); FCssAttributsList.OwnsObjects := True; CreateAttribute(FCommentAttri, 'Kommentare'); CreateAttribute(FPropertyAttri, 'Eigenschaften'); CreateAttribute(FKeyAttri, 'Schlüsselworte'); CreateAttribute(FSpaceAttri, 'Leerzeichen'); CreateAttribute(FStringAttri, 'Strings'); CreateAttribute(FColorAttri, 'Farben'); CreateAttribute(FNumberAttri, 'Zahlen'); CreateAttribute(FSymbolAttri, 'Symbole'); CreateAttribute(FTextAttri, 'Text'); CreateAttribute(FValueAttri, 'Werte'); CreateAttribute(FUndefPropertyAttri, 'Undefinierte Eigenschaften'); CreateAttribute(FImportantPropertyAttri, 'Wichtige Eigenschaften'); end; Nun geht es darum, die Werte, die ich zB. aus einer Colorbox auslese, im Closeup per Event weiterzureichen. Gefeuert werden die Events, wenn ein Wert gewählt wurde, zB. wenn eine Checkbox geklickt wird oder beim CloseUp einer Colorbox:
Delphi-Quellcode:
Die Eventpropertys sind wie folgt deklariert:
procedure TCssAttriTLBXFrame.CmbxValueForegroundColorCloseUp(Sender: TObject);
begin FActiveAttribut := TAttributsClass(CmbxAttributes.Items.Objects[CmbxAttributes.ItemIndex]); Label1.Caption := FActiveAttribut.Name; // if Assigned(FActiveAttribut) then // FActiveAttribut(FActiveAttribut); end;
Delphi-Quellcode:
Nun brauche ich ja beim Feuern des Events den Eventtip, der gefeuert werden soll. Bloss: Wie bestimme ich den? Dazu brauche ich ja die Instanz meiner Attributklasse.
property CSSCommentEvent: TCSSCommentEvent read FCSSCommentEvent write FCSSCommentEvent;
property CssPropertyEvent: TCssPropertyEvent read FCssPropertyEvent write FCssPropertyEvent; property CssKeyEvent: TCssKeyEvent read FCssKeyEvent write FCssKeyEvent; property CssSpaceEvent: TCssSpaceEvent read FCssSpaceEvent write FCssSpaceEvent; property CssStringEvent: TCssStringEvent read FCssStringEvent write FCssStringEvent; property CssColorEvent: TCssColorEvent read FCssColorEvent write FCssColorEvent; property CssNumberEvent: TCssNumberEvent read FCssNumberEvent write FCssNumberEvent; property CssSymbolEvent: TCssSymbolEvent read FCssSymbolEvent write FCssSymbolEvent; property CssTextEvent: TCssTextEvent read FCssTextEvent write FCssTextEvent; property CssValueEvent: TCssValueEvent read FCssValueEvent write FCssValueEvent; property CssUndefPropertyEvent: TCssUndefPropertyEvent read FCssUndefPropertyEvent write FCssUndefPropertyEvent; property CssImportantPropertyEvent: TCssImportantPropertyEvent read FCssImportantPropertyEvent write FCssImportantPropertyEvent; Gruss Delbor |
AW: Klasseninstanz zur Laufzeit bestimmen
Wieso sind deine Eventhandler eigentlich alle unterschiedlich deklariert? Reicht nicht ein Typ?, z.B.
Delphi-Quellcode:
Na ja, wie Du meinst. Wenn das so wäre, dann reicht folgender Code.
TAttributEvent = Procedure (Sender : TObject; Attribut : TAttributsClass) of Object;
Delphi-Quellcode:
Ja, das ist eine ziemlich lange if-else-Folge. Sieht blöd aus, ist aber normal. Da Deine Eventhandler alle individuell deklariert sind, kannst Du das nette 'CallEventHandler' nicht verwenden, sondern musst die 'If Assigned(Event)' Abfrage für jeden Event neu implementieren.
Function TMyForm.CreateEvent(Attribut : TAttributsClass) : TAttributEvent;
begin if Attribut=FCommentAttri then result := FCSSCommentEvent else if Attribut=FPropertyAttri then Result := FCSSPropertyEvent else if ... ... else Raise Exception.Create('Unknown Attribut: '+Attribut.Name); end; Procedure TMyForm.FireEvent(Attribut : TAttributsClass); Var Event : TAttributEvent; begin Event := CreateEvent(Attribut); CallEventHandler(Event, Attribut); end; Procedure TMyForm.CallEventHandler (Event : TAttributEvent; Attribut : TAttributsClass); Begin if Assigned (Event) Then Event(Self, Attribut); end; Noch einfacher geht es übrigens mit einem einfachen
Delphi-Quellcode:
, denn wenn das 'CommentEvent' gefeuert wird, ist ja klar, das mit den CommitAttributen etwas los ist, ergo muss man das Attribut nicht übergeben.
TNotifyEvent
Delphi-Quellcode:
Function TMyForm.CreateEvent(Attribut : TAttributsClass) : TNotifyEvent;
begin if Attribut=FCommentAttri then result := FCSSCommentEvent else if Attribut=FPropertyAttri then Result := FCSSPropertyEvent else if ... ... else Raise Exception.Create('Unknown Attribut: '+Attribut.Name); end; Procedure TMyForm.FireEvent(Attribut : TAttributsClass); Var Event : TNotifyEvent; begin Event := CreateEvent(Attribut); CallEventHandler(Event); end; Procedure TMyForm.CallEventHandler (Event : TNotifyEvent); Begin if Assigned (Event) Then Event(Self); end; |
AW: Klasseninstanz zur Laufzeit bestimmen
Nun ja, bei XE4 gibt es natürlich auch schon nettere Arten, damit umzugehen
Delphi-Quellcode:
unit Unit1;
interface uses {System.}Generics.Collections, {System.}SysUtils; type TObjectHandler = class abstract private type TAction = TProc<TObject>; private FRoutes: TDictionary<TClass, TAction>; protected type TRoute<T> = procedure( Argument: T ) of object; protected procedure RegisterRoute<T: class>( ARoute: TRoute<T> ); procedure DoRaise( Argument: TObject ); end; TFoo = class end; TBar = class end; TFooBar = class( TObjectHandler ) private procedure Apply( Argument: TFoo ); overload; procedure Apply( Argument: TBar ); overload; public constructor Create( ); procedure Handle( Argument: TObject ); end; implementation { TObjectHandler } procedure TObjectHandler.DoRaise( Argument: TObject ); var LAction: TAction; begin if FRoutes.TryGetValue( Argument.ClassType, LAction ) then LAction( Argument ) else raise ENotImplemented.CreateFmt( 'Handler für %s fehlt', [ Argument.ClassName ] ); end; procedure TObjectHandler.RegisterRoute<T>( ARoute: TRoute<T> ); begin FRoutes.Add( T, procedure( Argument: TObject ) begin ARoute( Argument as T ); end ); end; { TFooBar } procedure TFooBar.Apply( Argument: TFoo ); begin end; procedure TFooBar.Apply( Argument: TBar ); begin end; constructor TFooBar.Create; begin inherited Create; RegisterRoute<TFoo>( Apply ); RegisterRoute<TBar>( Apply ); end; procedure TFooBar.Handle( Argument: TObject ); begin DoRaise( Argument ); end; end. |
AW: Klasseninstanz zur Laufzeit bestimmen
Zu sehr verwirren wollte ich den TE nun auch nicht.
'Case' (oder hier: if/else-Schlangen) sind zwar 'böse', aber in Fabrikmethoden durchaus erlaubt. Denn es ist ja nun kein Mehrwert ggü dem if/else bzw. 'case' (geht hier leider nicht), eine 1:1 Abbildung in eine Liste (oder Dictionary) zu stopfen. Obwohl.. bei endlose if/else-Wicklungen würde ich nicht drauf beharren. |
AW: Klasseninstanz zur Laufzeit bestimmen
Wenn ich CleanCode haben möchte, dann nehmen ich so ein Routing-Dictionary.
(Gerade fällt mir auf, dass die Basis-Klasse keinen Destruktor hat ... ts ts ts) |
AW: Klasseninstanz zur Laufzeit bestimmen
Hi zusammen
Vielen Dank für eure Antworten! @Dejan Vu:
Delphi-Quellcode:
Hier entspricht FActiveAttribut deiner Variablen 'Attribut' in der Prozedur CreateEvent. Als ClassName wird mir hier allerdings TAttributClass zurückgegeben. Gebe ich im Label FActiveAttribut.AttributName aus, erhalte ich allerdings den von mir an die Instanz übergebenen String. Das bedeutet aber auch: die Info über die Klasseninstanz ist in meiner Feldvariablen enthalten - das zeigt ja auch dein Code mit der Abfrage:
procedure TCssAttriTLBXFrame.CmbxAttributesCloseUp(Sender: TObject);
begin FActiveAttribut := TAttributsClass(CmbxAttributes.Items.Objects[CmbxAttributes.ItemIndex]); Label1.Caption := FActiveAttribut.ClassName; //FActiveAttribut.AttributName; CmbxAttributes.Items[CmbxAttributes.ItemIndex]; end;
Delphi-Quellcode:
Deshalb denke ich, die Info ist im Property 'Tipinfo' von Tpersistent-Nachfolgern enthalten. Die Frage ist (oder war) nur: wie komme ich da ran?
if Attribut=FCommentAttri then result := FCSSCommentEvent
Zitat:
Delphi-Quellcode:
Ein Auszug aus der dfm:
fCommentAttri: TSynHighlighterAttributes;
fPropertyAttri: TSynHighlighterAttributes; fKeyAttri: TSynHighlighterAttributes; fSpaceAttri: TSynHighlighterAttributes; fStringAttri: TSynHighlighterAttributes; fColorAttri: TSynHighlighterAttributes; fNumberAttri: TSynHighlighterAttributes; fSymbolAttri: TSynHighlighterAttributes; fTextAttri: TSynHighlighterAttributes; fValueAttri: TSynHighlighterAttributes; fUndefPropertyAttri: TSynHighlighterAttributes; fImportantPropertyAttri: TSynHighlighterAttributes;
Delphi-Quellcode:
Das sind die Werte, die meine Klasse übergeben muss.
AndAttri.Background = clRed
CommentAttri.Foreground = clOlive CommentAttri.Style = [fsBold, fsItalic] IdentifierAttri.Foreground = clBlue IdentifierAttri.Style = [fsBold, fsUnderline] KeyAttri.Foreground = clBlue SpaceAttri.Foreground = clMoneyGreen SpaceAttri.Style = [fsUnderline] SymbolAttri.Foreground = clGray TextAttri.Foreground = clRed TextAttri.Style = [fsItalic] UndefKeyAttri.Background = clYellow ValueAttri.Background = clSilver
Delphi-Quellcode:
Das heisst: im Eventhandler, der diesen Event entgegennimmt, muss ich erstmal die übergebene Instanz identifizieren. Also eigentlich das Gegenstück zu deiner Prozedur CreateEvent schreiben und dann an passender Stelle die Zuweisung machen - pro identifizierter Instanz je einmal. Oder ich kann die 'passende Stelle' in jeweils eine eigene Prozedur auslagern. Das wären dann 9 - 12 Zuweisungsproceduren, gleichviel, wie einzelne Eventhandler nötig wären.
Procedure TMyForm.CallEventHandler (Event : TAttributEvent; Attribut : TAttributsClass);
Begin if Assigned (Event) Then Event(Self, Attribut); end; Einzig bei den 'Kanonieren' (die das Event abfeuern) wäre was einzusparen. Inzwischen habe ich deinen neuen Beitrag mitbekommen, Dejan Vu. Zitat:
Zitat:
Gruss Delbor |
AW: Klasseninstanz zur Laufzeit bestimmen
Nein, mein
Delphi-Quellcode:
hat keinen, obwohl dort das Dictionary aufgeräumt werden muss.
TObjectHandler
|
AW: Klasseninstanz zur Laufzeit bestimmen
Zitat:
Zitat:
|
AW: Klasseninstanz zur Laufzeit bestimmen
Zitat:
Man kanns übertreiben... |
AW: Klasseninstanz zur Laufzeit bestimmen
Zitat:
|
AW: Klasseninstanz zur Laufzeit bestimmen
Hi zusammen
Sorry, wenn ich auf Sir Rufos Vorschlag bisher nicht eingegangen bin; der Grund liegt vor allem darin, dass ich bisher sehr wenig mit Generics arbeite, da ich die Dinger zu wenig durchschaue. Eigentlich ist die einzige generische Klasse, die ich bisher benutze, eine Objectliste, die mir einst DeddyH vorgeschlagen hat. Ein weiterer seiner Vorschläge betraf die Verwendung einer ![]()
Delphi-Quellcode:
Meinen Frames muss ich dann noch folgendes verpassen:
uses Generics.Collections, Vcl.Forms, System.Classes, System.SysUtils;
type EFrameNotRegistered = class(Exception); TFrameClass = class of TFrame; TFrameFactory = class abstract strict private class var FAssociations: TDictionary<string, TFrameClass>; class constructor Create; class destructor Destroy; public class procedure RegisterFrameClass(const Extension: string; FrameClass: TFrameClass); class function GetRegisteredFrameClass(const Extension: string; AOwner: TComponent): TFrame; end; implementation { TFrameFactory } class constructor TFrameFactory.Create; begin FAssociations := TDictionary<string, TFrameClass>.Create; end; class destructor TFrameFactory.Destroy; begin FAssociations.Free; end; class function TFrameFactory.GetRegisteredFrameClass(const Extension: string; AOwner: TComponent): TFrame; var AFrameClass: TFrameClass; //TCustomFrameClass begin if FAssociations.TryGetValue(AnsiLowerCase(Extension), AFrameClass) then Result := AFrameClass.Create(AOwner) else raise EFrameNotRegistered.CreateFmt('Für die Endung %s ist keine Frameklasse registriert.', [Extension]); end; class procedure TFrameFactory.RegisterFrameClass(const Extension: string; FrameClass: TFrameClass); begin FAssociations.AddOrSetValue('.' + AnsiLowerCase(Extension), FrameClass); end; end.
Delphi-Quellcode:
Dein Vorschlag ist offenbar das generische Gegenstück zu dieser Fabrikklasse, und wenn mich meine bisherigen Kenntnisse der Generics nicht täuschen, könnte ich da jede Klasse registrieren, die, in deinem Beispiel, von TFoo, bzw. TBar erbt oder selbst von einem dieser Typen ist.
initialization
TFrameFactory.RegisterFrameClass('css', TCSSFrame); Somit könnte ich da jeden meiner Eventhandler registrieren. Hab ich das richtig verstanden? Gruss Delbor |
AW: Klasseninstanz zur Laufzeit bestimmen
Genau so isses :)
|
AW: Klasseninstanz zur Laufzeit bestimmen
Hi zusammen
Wie man nur so blind sein kann!! Nochmal zur Erinnerung die CreateAufrufe und die Createprozedur selbst:
Delphi-Quellcode:
Damit wird bei jedem Aufruf eine Instanz der Klasse TAttributsClass erzeugt und nicht eine Klasseninstanz FCommentAttri - somit kann ich auch nicht auf eine Instanz FCommentAttri zugreifen. Einfach, nicht?
constructor TJavaScriptAttriTLBXFrame.Create(AOwner: TComponent);
begin inherited; FJavaScriptAttributsList := TDataObjectList<TAttributsClass>.Create(); FJavaScriptAttributsList.OwnsObjects := True; CreateAttribute(FCommentAttri, 'Kommentare', 'FCommentAttri'); // fCommentAttri: TSynHighlighterAttributes; CreateAttribute(FIdentifierAttri, 'Bezeichner', 'FIdentifierAttri'); // fPropertyAttri: TSynHighlighterAttributes; CreateAttribute(FKeyAttri, 'Schlüsselworte', 'FKeyAttri'); // fKeyAttri: TSynHighlighterAttributes; CreateAttribute(FNonReservedKeyAttri, 'Nicht reservierte Schlüssel', 'FNonReservedKeyAttri'); // fSpaceAttri: TSynHighlighterAttributes; CreateAttribute(FEventAttri, 'Ereignisse', 'FEventAttri'); // fStringAttri: TSynHighlighterAttributes; CreateAttribute(FNumberAttri, 'Zahlen', 'FNumberAttri'); // fNumberAttri: TSynHighlighterAttributes; CreateAttribute(FSpaceAttri, 'Leerzeichen', 'FSpaceAttri'); // fColorAttri: TSynHighlighterAttributes; CreateAttribute(FStringAttri, 'Strings', 'FStringAttri'); // fTextAttri: TSynHighlighterAttributes; CreateAttribute(FSymbolAttri, 'Symbole', 'FSymbolAttri'); // fSymbolAttri: TSynHighlighterAttributes; end; destructor TJavaScriptAttriTLBXFrame.Destroy; begin FJavaScriptAttributsList.Free; inherited; end; procedure TJavaScriptAttriTLBXFrame.CreateAttribute(var AAttrib: TAttributsClass; const AName: string; AInstanz : String); begin AAttrib:= TAttributsClass.Create(Self); AAttrib.AttributName := AName; AAttrib.InstanzName := AInstanz; FJavaScriptAttributsList.Add(AAttrib); CmbxAttributes.Items.AddObject(AName,TObject(AAttrib)); Application.ProcessMessages; end; Um die richtigen Instanzen zu erzeugen, müsste ich wohl eher so etwas macen:
Delphi-Quellcode:
Wobei dies auch nicht ganz stimmt: Der rechte Teil muss ein Klassenbezeichner sein - der muss aber zum Instanzbezeichner passen. Das property Instanzname ist auch nicht das gelbe vom Ei...
procedure TJavaScriptAttriTLBXFrame.CreateAttribute(var AAttrib: TAttributsClass; const AName: string; AInstanz : String);
begin AAttrib:= AAttrib.Create(Self); AAttrib.AttributName := AName; AAttrib.InstanzName := AInstanz; FJavaScriptAttributsList.Add(AAttrib); CmbxAttributes.Items.AddObject(AName,TObject(AAttrib)); Application.ProcessMessages; end; Das heisst nichts anderes, als dass es einige von TAttributsClass abgeleitete Klassen geben wird, wie zum Beispiel TCommentAttri. Die Erzeugung ist dann:
Delphi-Quellcode:
Damit habe ich ohne Verrenkungen zugriff auf die richtige Instanz.
FCommentAttri:= TCommentAttri.Create
Gruss Delbor |
AW: Klasseninstanz zur Laufzeit bestimmen
Liste der Anhänge anzeigen (Anzahl: 1)
Hi zusammen
Die Idee aus meinem vorigen Beitrag war wirklich nicht schlecht - sie liefert eine Antwort auf die Frage, die der Threadtitel vorgibt - aber sie ist noch weniger das Gelbe vom Ei, wie das Property 'InstanzName' der Klasse TAttributsClass, da sie zu einem, wie ich meine, ziemlich seltsamen Konstrukt führt:
Delphi-Quellcode:
Dabei führen die von TAttributsClass abgeleiteten Klassen keinerlei neue Member ein, weder Eigenschaften Methoden oder Ereignisse - wobei ich mir schon überlegt habe, ob die Dinger nicht selbst ein Event abfeuern könnten...
TAttributsClass = Class(TPersistent)
public BackGround : TColor; ForeGround : TColor; StyleBold: Boolean; StyleItalic : Boolean; StyleUnderLine : Boolean; StyleStrikeOff : Boolean; AttributName: String; InstanzName: String; Constructor Create(AOwner: TComponent); Destructor Destroy; override; end; TJavaScriptCommentClass = Class(TAttributsClass); TJavaScriptIdentifierClass = Class(TAttributsClass); TJavaScriptKeyClass = Class(TAttributsClass); TJavaScriptNonReservedKeyClass = Class(TAttributsClass); TJavaScriptEventClass = Class(TAttributsClass); TJavaScriptNumberClass = Class(TAttributsClass); // TJavaScriptSpaceClass = Class(TAttributsClass); TJavaScriptStringClass = Class(TAttributsClass); TJavaScriptSymbolClass = Class(TAttributsClass); Aber ich habe etwas viel besseres ![]() Ein Test in einem meiner Synedit-Frames brachte das gewünschte Resultat, ersichtlich im angehängten Jpeg:
Delphi-Quellcode:
Wie der Anhang zeigt, liefert mir das alle Propertys als String. Um meine neuen Einstellungen aus meinem Frame richtig zuordnen zu können, brauche ich entweder einen Kalssenbezeichner, der dem Probertynamen entspricht oder einen String, den ich mitt dem als String vorliegenden Propertynamen vergleichen kann.
procedure TCSSFrame.SynEdit1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); var LContext: TRttiContext; LType: TRttiType; LProperty: TRttiProperty; LMethod: TRttiMethod; LField: TRttiField; begin if Self.SynEdit1.SelText <> '' then begin Label1.Caption := SynEdit1.SelText; Label1.Caption := Label1.Caption + ' SynEdit1.TabWidth := '+ IntToStr(SynEdit1.TabWidth); if assigned(FOnSelectText) then // Dieser Event wird gefeuert, FOnSelectText(Self); // wenn Text markiert wird. end; LContext := TRttiContext.Create; try LType := LContext.GetType(TSynCssSyn); SynEdit1.lines.Add(LType.ToString); for LProperty in LType.GetProperties do begin SynEdit1.lines.Add (LProperty.ToString); end; finally LContext.Free; end; end; Natürlich muss ich aus der sich ergebenden Liste noch den (Teil-)String filtern, den ich in TAttributsClass.Instanzname übergebe. Aber das dürfte wohl kaum ein Problem sein. Ein weiterer Effekt: Ich brauche zur Übergabe noch genau einen Eventtyp. Oder gibts noch eine bessere Lösung? Gruss Delbor |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:20 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz