Программный доступ к свойствам компонента
IDE FireMonkey предоставляет широкие функциональные возможности для изменения различных свойств компонента (в том числе параметров стиля) во время проектирования. Параметры стиля, также включены в объектные свойства компонента. Но как быть, если объектный код не обеспечивает способ изменить стиль компонента динамически во время выполнения? Многие программисты сталкивались с этой проблемой
Ниже приводится полный исходный код компонента, где обеспечивается реализация поставленной цели. Код взят из реально работающего FireMonkey приложения.
Исходный код компонента
{****************************************************************************** Delphi FireMonkey visual component ******************************************************************************} unit LineClick; interface uses System.SysUtils, System.Classes, FMX.Types, FMX.Controls, FMX.Objects, System.UIConsts, System.UITypes, System.Types, DrawProperties, System.Rtti, System.Variants, FMX.Forms, FMX.Dialogs, FMX.ExtCtrls, FMX.StdCtrls; const kListPath = 'C:\Windows\Temp\dot.txt'; kColor = TAlphaColor($FF000000) or TAlphaColor($00FFFF); kYellow = TAlphaColor($FF000000) or TAlphaColor($FFFF00); kNullColor= TAlphaColor($00000000); type TLeftEvent = procedure( Button: TMouseButton; Shift: TShiftState; X, Y: Single; var Z : Boolean ) of object; type //Subcomponent TPens = class(TPersistent) private { Private declarations } FVersion : String; FColor : TAlphaColor; FOnChanged : TNotifyEvent; function IsColorStored: Boolean; function GetColor: TAlphaColor; procedure SetColor(const Value: TAlphaColor); public { Public declarations } constructor Create; procedure Assign(Source: TPersistent); override; published { Published declarations } property Version : String read FVersion; property Color : TAlphaColor read GetColor write SetColor stored IsColorStored; property OnChanged: TNotifyEvent read FOnChanged write FOnChanged; end; type TMarkerType = (Square, Circle); TMyFontStyle = (fsBold, fsItalic, fsStrikeOut) ; TMyFontStyles = set of TMyFontStyle; type //Visual Component TLineClick = class(TGroupBox) private { Private declarations } List : TStringList; FFocused : Boolean; //Boolean property FFocusedColor : TAlphaColor; //Color property FDrawProperties : TDrawProperties; //Reference property FSet : TMyFontStyles; //Set property FPointMarkerShape : TMarkerType; //Enumerated property //FPenProperties //Exists in non-visual component FPenProperties : TPens; //Subcomponent property FBrush : TBrush; FOrdinal : Integer; //Ordinal property FCanvasColor : TAlphaColor; //Color property FIsMarker : Boolean; FClicked : Boolean; LineColor : TAlphaColor; FLeftEvent : TLeftEvent; //My event for left button is clicked FIsCTRL : Boolean; //Will be fired when the left button //is clicked property OnEnter; property OnExit; function StrBreak(str, Delimeter: string; fromParts, Cnt: Integer) : string; procedure SetPointMarkerShape(const Value: TMarkerType); procedure SetDrawProperties(const Value: TDrawProperties); procedure SetIsMarker(const Value: Boolean); procedure SetClicked(const Value: Boolean); procedure SetFocusedColor(const Value: TAlphaColor); procedure GetHigh(const Value: Integer); procedure SetPenProperties(const Value: TPens); procedure SetCanvasColor(const Value: TAlphaColor); protected { Protected declarations } procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override; procedure Paint; override; procedure DrawDot(Dot1: TPointF; i: Integer = -1); procedure EnterEvent(Sender: TObject); dynamic; procedure ExitEvent(Sender: TObject); dynamic; public { Public declarations } constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Clear; published { Published declarations } property ResourceLink; property OnApplyStyleLookup; property Focused : Boolean read FFocused write FFocused stored TRUE default False; property FocusedColor : TAlphaColor read FFocusedColor write SetFocusedColor stored TRUE default kColor; property DrawProperties : TDrawProperties read FDrawProperties write SetDrawProperties; property SetTypeProperty : TMyFontStyles read FSet write FSet stored TRUE default [fsBold]; property PointMarkerShape: TMarkerType read FPointMarkerShape write SetPointMarkerShape stored TRUE default TMarkerType.Circle; property PenProperties : TPens read FPenProperties write SetPenProperties; property Ordinal : Integer read FOrdinal write GetHigh stored TRUE default 10; property Clicked : Boolean read FClicked write SetClicked stored TRUE default False; property IsMarker : Boolean read FIsMarker write SetIsMarker stored TRUE default False; //IsCTRL will be fired when the left button is clicked property IsCTRL : Boolean read FIsCTRL stored TRUE default False; property OnLeftEvent : TLeftEvent read FLeftEvent write FLeftEvent; property CanvasColor : TAlphaColor read FCanvasColor write SetCanvasColor stored TRUE default kYellow; procedure ApplyStyle; override; end; //============================================================================= implementation //============================================================================= { TPens Subcomponent } {****************************************************************************** Constructor ******************************************************************************} constructor TPens.Create; begin FColor := TAlphaColorRec.Red; FVersion := '1.0'; end; {****************************************************************************** Assign ******************************************************************************} procedure TPens.Assign(Source: TPersistent); begin FColor:= TPens(Source).FColor; end; {****************************************************************************** Getter ******************************************************************************} function TPens.GetColor: TAlphaColor; begin Result := FColor; end; {****************************************************************************** Is Color Stored flag ******************************************************************************} function TPens.IsColorStored: Boolean; begin Result:= True; end; {****************************************************************************** Setter (only via Assign) ******************************************************************************} procedure TPens.SetColor(const Value: TAlphaColor); begin if FColor <> Value then begin FColor := Value; if Assigned(FOnChanged) then FOnChanged(Self); end; end; { Visual TLineClick Component } {****************************************************************************** Constructor of visual component ******************************************************************************} constructor TLineClick.Create(AOwner: TComponent); begin inherited Create(AOwner); FPenProperties := TPens.Create; FBrush := TBrush.Create(TBrushKind.bkSolid, FPenProperties.FColor); CanFocus := True; List := TStringList.Create(); if Not FileExists( kListPath ) then List.SaveToFile( kListPath ); List.LoadFromFile( kListPath ); OnEnter := EnterEvent; OnExit := ExitEvent; //=============================================================== //Set defaults for props //=============================================================== FFocused := False ; //Boolean property FFocusedColor := kColor ; //Color property FSet := [fsBold] ; //Set property FPointMarkerShape := TMarkerType.Circle ; //Enumerated property FOrdinal := 10 ; //Ordinal property FClicked := False ; LineColor := FFocusedColor ; FIsCTRL := False ; //Will be fired when the left button //is clicked FCanvasColor := kYellow ; //Color property //SetFocus(); //InvalidateRect( RectF(0, 0, Width, Height) ); //Self.ApplyStyle; end; {****************************************************************************** String Parser str : your string Delimeter : Delimeter symbol fromParts : Initial position Cnt : Number of items ShowMessage(StrBreak('test1.test2.test3.test4', '.', 0,1)); //= test1 ShowMessage(StrBreak('test1.test2.test3.test4', '.', 1,1)); //= test1 ShowMessage(StrBreak('test1.test2.test3.test4', '.', 2,1)); //= test1 ShowMessage(StrBreak('test1.test2.test3.test4', '.', 3,1)); //= test4 ******************************************************************************} function TLineClick.StrBreak(str, Delimeter: string; fromParts, Cnt : integer) : string; var StrL : TStringList; ParseStr : string; i : integer; begin Result := ''; StrL := TStringList.Create; try ParseStr:= StringReplace(str, Delimeter, #13, [rfReplaceAll]); StrL.Text := ParseStr; if StrL.Count > 0 then begin for i := 0 to Cnt-1 do if (StrL.Count > i) and (StrL.Count > fromParts) then if i > 0 then Result := Result + Delimeter + StrL.Strings[fromParts+i] else Result := Result + StrL.Strings[fromParts+i]; end; finally StrL.Free; end; end; {****************************************************************************** Destructor of visual component ******************************************************************************} destructor TLineClick.Destroy; begin FPenProperties.Free; FBrush.Free; List.Free; inherited Destroy; end; {****************************************************************************** OnMouseUp event dispatcher. A control calls MouseUp in response to any of mouse-up messages, decoding the message parameters into the shift-key state and position, which it passes in the Shift, X and Y parameters, respectively: Button determines which mouse button was previously pressed: left, right, or middle. Shift indicates which shift keys--SHIFT, CTRL, ALT, and CMD (only for Mac)--were down when the pressed mouse button is released. X and Y are the pixel coordinates of the mouse pointer within the client area of the control. Override the protected MouseUp method to provide other responses when the mouse button previously pressed is released while the cursor is over the control. We are allowing the user to change CTRL-key default behavior. ******************************************************************************} procedure TLineClick.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); var item : string; iX,iY : Integer; fX,fY : Single; begin Inherited MouseUp(Button, Shift, X, Y); FClicked := True; fX := StrToFloat(Format('%.2f', [X])); fY := StrToFloat(Format('%.2f', [Y])); iX := Trunc(fX); //The Trunc rets Integer part of a Float iY := Trunc(fY); //We are allowing the user to change CTRL-key default behavior. if ssCtrl in Shift then begin FIsCTRL := True; end else begin FIsCTRL := False; end; //if exists the user-handler for event if Assigned(FLeftEvent) then FLeftEvent( Button, Shift, X, Y, FIsCTRL ); //run user-handler if FIsCTRL then begin item := IntToStr(iX) + '.' + IntToStr(iY) + '.' + '1' end else begin item := IntToStr(iX) + '.' + IntToStr(iY) + '.' + '0' end; List.Add( item ); List.SaveToFile( kListPath ); InvalidateRect( RectF(0, 0, Width, Height) ); end; {****************************************************************************** This procedure draws a dot Shape ******************************************************************************} procedure TLineClick.DrawDot(Dot1: TPointF; i: Integer = -1); var Dot2 : TPointF; Fill : TBrush; Stroke : TStrokeBrush; FillColor : TAlphaColor; begin Canvas.Stroke.Thickness := 1; Canvas.Stroke.Kind := TBrushKind.bkSolid; Canvas.Stroke.Color := LineColor; Canvas.Fill.Kind := TBrushKind.bkSolid; FillColor := LineColor; //function RectF(Left, Top, Right, Bottom: Single): TRectF; Stroke := TStrokeBrush.Create(TBrushKind.bkSolid, LineColor); try Fill := TBrush.Create(TBrushKind.bkSolid, FillColor); try if IsMarker then begin if PointMarkerShape = Circle then begin Canvas.FillEllipse(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)),0.8, Fill ); Canvas.DrawEllipse(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)),0.8, Stroke); end else begin Canvas.FillRect(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)), 0, 0, AllCorners, 0.8, Fill, TCornerType.ctBevel ); //Canvas.DrawRect(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)), 0, 0, AllCorners, 0.8, TCornerType.ctBevel ); end; end; if i >= 0 then begin if PointMarkerShape = Circle then begin Canvas.FillEllipse(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)),0.8, Fill ); Canvas.DrawEllipse(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)),0.8, Stroke); end else begin Canvas.FillRect(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)), 0, 0, AllCorners, 0.8, Fill, TCornerType.ctBevel ); //Canvas.DrawRect(RectF( (Dot1.X-3),(Dot1.Y-3),(Dot1.X+3),(Dot1.Y+3)), 0, 0, AllCorners, 0.8, TCornerType.ctBevel ); end; Dot2.X := StrToInt( StrBreak( List.Strings[i-1], '.', 0,1) ); Dot2.Y := StrToInt( StrBreak( List.Strings[i-1], '.', 1,1) ); if PointMarkerShape = Circle then begin Canvas.FillEllipse(RectF( (Dot2.X-3),(Dot2.Y-3),(Dot2.X+3),(Dot2.Y+3)),0.8, Fill ); Canvas.DrawEllipse(RectF( (Dot2.X-3),(Dot2.Y-3),(Dot2.X+3),(Dot2.Y+3)),0.8, Stroke); end else begin Canvas.FillRect(RectF( (Dot2.X-3),(Dot2.Y-3),(Dot2.X+3),(Dot2.Y+3)), 0, 0, AllCorners, 0.8, Fill, TCornerType.ctBevel ); //Canvas.DrawRect(RectF( (Dot2.X-3),(Dot2.Y-3),(Dot2.X+3),(Dot2.Y+3)), 0, 0, AllCorners, 0.8, TCornerType.ctBevel ); end; Canvas.DrawLine(Dot1, Dot2, 1.0); end; finally Fill.Free; end; finally Stroke.Free; end; end; {****************************************************************************** Paint for Component ******************************************************************************} procedure TLineClick.Paint; var i : Integer; ctrl : Boolean; Dot1 : TPointF; //Fill : TBrush; begin inherited; //if not Clicked then exit; if List.Count <= 0 then exit; //Fill := TBrush.Create(TBrushKind.bkSolid, kYellow); //function RectF( Left, Top, Right, Bottom: Single): TRectF; //Canvas.FillRect(RectF( (10),(10),(Width-10),(Height-10)), 0, 0, AllCorners, 0.8, Fill, TCornerType.ctBevel ); Dot1.X := StrToInt( StrBreak( List.Strings[0], '.', 0,1) ); Dot1.Y := StrToInt( StrBreak( List.Strings[0], '.', 1,1) ); DrawDot(Dot1); for i := 1 to List.Count-1 do begin Dot1.X := StrToInt ( StrBreak( List.Strings[i], '.', 0,1) ); Dot1.Y := StrToInt ( StrBreak( List.Strings[i], '.', 1,1) ); ctrl := StrToBool( StrBreak( List.Strings[i], '.', 2,1) ); if ctrl then begin DrawDot(Dot1, i); end else DrawDot(Dot1); end; end; {****************************************************************************** Setter ******************************************************************************} procedure TLineClick.SetClicked(const Value: Boolean); begin FClicked := Value; SetFocus(); end; {****************************************************************************** Setter ******************************************************************************} procedure TLineClick.SetIsMarker(const Value: Boolean); begin FIsMarker := Value; SetFocus(); end; {****************************************************************************** Setter ******************************************************************************} procedure TLineClick.SetDrawProperties(const Value: TDrawProperties); begin FDrawProperties := Value; SetFocus(); end; {****************************************************************************** Enter Event Handler (is focus) ******************************************************************************} procedure TLineClick.EnterEvent(Sender: TObject); begin FFocused := True; LineColor := FFocusedColor; SetFocus(); end; {****************************************************************************** Exit Event Handler (not focus) ******************************************************************************} procedure TLineClick.ExitEvent(Sender: TObject); begin FFocused := False; LineColor := DrawProperties.Brush.Color; FClicked := False; InvalidateRect( RectF(0, 0, Width, Height) ); end; {****************************************************************************** Cleares the dot list ******************************************************************************} procedure TLineClick.Clear; begin List.Clear; List.SaveToFile( kListPath ); InvalidateRect( RectF(0, 0, Width, Height) ); end; {****************************************************************************** Setter ******************************************************************************} procedure TLineClick.SetFocusedColor(const Value: TAlphaColor); begin LineColor := Value; FFocusedColor := Value; SetFocus(); end; {****************************************************************************** Setter ******************************************************************************} procedure TLineClick.SetPointMarkerShape(const Value: TMarkerType); begin FPointMarkerShape := Value; SetFocus(); end; {****************************************************************************** Getter ******************************************************************************} procedure TLineClick.GetHigh(const Value: Integer); begin FOrdinal := Succ(Value); SetFocus(); end; {****************************************************************************** Setter (only via Assign) ******************************************************************************} procedure TLineClick.SetPenProperties(const Value: TPens); begin FPenProperties.Assign(Value); SetFocus(); end; {****************************************************************************** Override ******************************************************************************} procedure TLineClick.ApplyStyle; var T : TFmxObject; s : string; sb : TStyleBook; begin inherited; if ResourceLink = nil then Exit; T := ResourceLink.Root.GetObject; if T = nil then //ShowMessage( 'ResourceLink.Root.GetObject = nil' ) else begin T := T.FindStyleResource('Visual_Component_Style'); if T = nil then //ShowMessage( 'Visual_Component_Style = nil' ) else begin sb := TStyleBook(T); T := sb.Style.FindStyleResource('Rectangle1'); if T = nil then begin //ShowMessage( 'Rectangle1 = nil' ); Exit; end; end; end; if (T <> nil) and (T is TRectangle) then begin if TRectangle(T).Fill <> nil then begin if (FCanvasColor <> kNullColor) then begin TRectangle(T).Enabled := True; TRectangle(T).Fill.Color := kNullColor; TRectangle(T).Fill.Color := FCanvasColor; TRectangle(T).Opacity := 0.1; TRectangle(T).Enabled := False; Self.Repaint; end; end; end; end; {****************************************************************************** Setter ******************************************************************************} procedure TLineClick.SetCanvasColor(const Value: TAlphaColor); begin FCanvasColor := Value; ApplyStyle; SetFocus(); end; //============================================================================= // EOF //============================================================================= end.
Как изменить стиль компонента в RunTime?
Нас интересует переопределенный код метода ApplyStyle, который добивается поставленной цели и позволяет узнать немного больше о механизме стилей.
Цель метода ApplyStyle - найти соответствующий объект стиля и изменить значения свойств компонента: Opacity, Color, Enabled. ApplyStyle ищет нужный ресурс с помощью метода FindStyleResource. При этом на форме приложения, использующего компонент, должна присутствовать соответствующая книга StyleBook. Далее все очень просто — если ресурс стиля найден, то изменение значений нужных свойств уже не является проблемой.
Вместо заключения
Такая работа метода ApplyStyle стала возможна только потому, что целевой компонент имеет уникальное имя стиля — ‘Visual_Component_Style’ и соответствующий ему файл стиля. Тот факт, что код FireMonkey компонента полагается на информацию из файла стилей подразумевает, что редактирование файла стиля может изменить функциональность самого компонента.
Submitted by Sergey Lomako