Программный доступ к свойствам компонента
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
