Update : this project now is hosted on Github.
Introduction
The VCL Styles are great but it seems that was designed to hide (and protect?) a lot of useful properties and classes. Because that I wrote a small (for the moment) library that using class helpers (and another tricks) can access to hidden properties and classes of the VCL Styles. Today I will show you how using this library you can create a previewer for your vcl styles.
When you design a GUI and include the option to change the current VCL Style of an application you generally provide a list of the availables VCL Styles, then the user without knowing nothing about the appearance of the style must choose one and then apply the changes. Well you can improve the user experience showing a preview image of the VCL style before to apply the selection.
Check out this sample image of a settings option of the WDCC, that shows a preview of the VCL Styles.
The internals
The TStyleManager class contains an internal collection with all the registered (loaded) styles, this list is stored in a Dictionary class var like this FRegisteredStyles: TDictionary; and each TPair contains the style name and a TSourceInfo value.
This is the definition of the TSourceInfo type
TStyleServicesHandle = type Pointer; strict private type TSourceInfo = record Data: TStyleServicesHandle; StyleClass: TCustomStyleServicesClass; end;
The Data field point to a TStream that contains all the objects and bitmaps related to the style and the StyleClass field has the type of the class Style Service. Now in order to access the visual elements of the style we need interpret the content of the Data field. The logic to interpret the streams with the VCL style info is placed in two files StyleUtils.inc and StyleAPI.inc, these files are in the source\vcl folder of your Rad Studio installation. As probably you must know these files are not units and are embedded in the implementation part of the Vcl.Styles unit, because that the classes and methods of these files are not accesible in any way (for now).
Note : The StyleUtils.inc and StyleAPI.inc files contains code that originally was part of the SkineEngine library of Eugene A. Kryukov (Yeah Eugene is the original author of the DXScene the antecesor of FireMonkey).
Accesing the TSourceInfo
The first task is gain access to the class var FRegisteredStyles of the TStyleManager class. So using a class helper we can do the trick.
//we need redeclare these types because are defined as <em>strict private</em> types inside of the <em>TStyleManager </em>class and are not accesible of outside. TStyleServicesHandle = type Pointer; TSourceInfo = record Data: TStyleServicesHandle; StyleClass: TCustomStyleServicesClass; end; //the class helper TStyleManagerHelper = Class Helper for TStyleManager strict private class function GetStyleSourceInfo(const StyleName: string): TSourceInfo; static; public class function GetRegisteredStyles: TDictionary<string, TSourceInfo>; class property StyleSourceInfo[const StyleName: string]: TSourceInfo read GetStyleSourceInfo; end; class function TStyleManagerHelper.GetRegisteredStyles: TDictionary<string, TSourceInfo>; var t : TPair<string, TStyleManager.TSourceInfo>; SourceInfo : TSourceInfo; begin Result:=TDictionary<string, TSourceInfo>.Create; for t in Self.FRegisteredStyles do begin SourceInfo.Data:=t.Value.Data; SourceInfo.StyleClass:=t.Value.StyleClass; Result.Add(t.Key,SourceInfo); end; end; class function TStyleManagerHelper.GetStyleSourceInfo(const StyleName: string): TSourceInfo; Var LRegisteredStyles : TDictionary<string, TSourceInfo>; begin LRegisteredStyles:=TStyleManager.GetRegisteredStyles; try if LRegisteredStyles.ContainsKey(StyleName) then Result:=LRegisteredStyles[StyleName]; finally LRegisteredStyles.Free; end; end;
So in this point we have access to the TSourceInfo of each registered style. Now we can use the above class helper in this way
var SourceInfo: TSourceInfo; begin SourceInfo:=TStyleManager.StyleSourceInfo[StyleName]; //do something end;
Writting a TCustomStyle
The second part of the task is interpret the stream stored in TSourceInfo.Data, to do this we need create a TCustomStyle descendant class and use the code of the StyleUtils.inc and StyleAPI.inc files. The TCustomStyle class has a private field FSource: TObject; that store the VCL Style content (objects, fonts, colors, bitmaps and so on) this field must be filled with the content of the Stream stored in the TSourceInfo.Data. After of that you will have a new Style Class ready to use as you want.
This is the definiton of the TCustomStyleExt class.
type TCustomStyleHelper = Class Helper for TCustomStyle private function GetSource: TObject; public property Source: TObject read GetSource; End; TCustomStyleExt = class(TCustomStyle) strict private FStream : TStream; public function GetStyleInfo : TStyleInfo; public constructor Create(const FileName :string);overload; constructor Create(const Stream:TStream);overload; destructor Destroy;override; property StyleInfo : TStyleInfo read GetStyleInfo; end; //we need include this files in the implemnetation part to use the TseStyle class {$I 'C:\Program Files (x86)\Embarcadero\RAD Studio\9.0\source\vcl\StyleUtils.inc'} {$I 'C:\Program Files (x86)\Embarcadero\RAD Studio\9.0\source\vcl\StyleAPI.inc'} //Gain acess to the FSource field of the TCustomStyle function TCustomStyleHelper.GetSource: TObject; begin Result:=Self.FSource; end; //with this constructor we can load a Vcl Style file, without register in the system constructor TCustomStyleExt.Create(const FileName: string); var LStream: TFileStream; begin LStream := TFileStream.Create(FileName, fmOpenRead); try Create(LStream); finally LStream.Free; end; end; //Load an stream with the Vcl Style Data constructor TCustomStyleExt.Create(const Stream: TStream); begin inherited Create; FStream:=TMemoryStream.Create; Stream.Seek(0, soBeginning); //set position to 0 before to copy FStream.CopyFrom(Stream, Stream.Size); //copy the content in a local stream Stream.Seek(0, soBeginning); //very importan restore the index to 0. FStream.Seek(0, soBeginning);//set position to 0 before to load TseStyle(Source).LoadFromStream(FStream);//makes the magic, fill the end; //free the resources destructor TCustomStyleExt.Destroy; begin if Assigned(FStream) then FStream.Free; inherited Destroy; end; //Get misc info about the Vcl Style function TCustomStyleExt.GetStyleInfo: TStyleInfo; begin Result.Name := TseStyle(Source).StyleSource.Name; Result.Author := TseStyle(Source).StyleSource.Author; Result.AuthorEMail := TseStyle(Source).StyleSource.AuthorEMail; Result.AuthorURL := TseStyle(Source).StyleSource.AuthorURL; Result.Version := TseStyle(Source).StyleSource.Version; end;
Creating the preview
Finally now we can create a image that represent the VCL Style.
Check out the code to create a simple image of a form using a TCustomStyle.
//draws a form (window) over a Canvas using a TCustomStyle procedure DrawSampleWindow(Style:TCustomStyle;Canvas:TCanvas;ARect:TRect;const ACaption : string); var LDetails : TThemedElementDetails; CaptionDetails : TThemedElementDetails; IconDetails : TThemedElementDetails; IconRect : TRect; BorderRect : TRect; CaptionRect : TRect; ButtonRect : TRect; TextRect : TRect; CaptionBitmap : TBitmap; function GetBorderSize: TRect; var Size: TSize; Details: TThemedElementDetails; Detail: TThemedWindow; begin Result := Rect(0, 0, 0, 0); Detail := twCaptionActive; Details := Style.GetElementDetails(Detail); Style.GetElementSize(0, Details, esActual, Size); Result.Top := Size.cy; Detail := twFrameLeftActive; Details := Style.GetElementDetails(Detail); Style.GetElementSize(0, Details, esActual, Size); Result.Left := Size.cx; Detail := twFrameRightActive; Details := Style.GetElementDetails(Detail); Style.GetElementSize(0, Details, esActual, Size); Result.Right := Size.cx; Detail := twFrameBottomActive; Details := Style.GetElementDetails(Detail); Style.GetElementSize(0, Details, esActual, Size); Result.Bottom := Size.cy; end; function RectVCenter(var R: TRect; Bounds: TRect): TRect; begin OffsetRect(R, -R.Left, -R.Top); OffsetRect(R, 0, (Bounds.Height - R.Height) div 2); OffsetRect(R, Bounds.Left, Bounds.Top); Result := R; end; begin BorderRect := GetBorderSize; CaptionBitmap := TBitmap.Create; CaptionBitmap.SetSize(ARect.Width, BorderRect.Top); //Draw background LDetails.Element := teWindow; LDetails.Part := 0; Style.DrawElement(Canvas.Handle, LDetails, ARect); //Draw caption border CaptionRect := Rect(0, 0, CaptionBitmap.Width, CaptionBitmap.Height); LDetails := Style.GetElementDetails(twCaptionActive); Style.DrawElement(CaptionBitmap.Canvas.Handle, LDetails, CaptionRect); TextRect := CaptionRect; CaptionDetails := LDetails; //Draw icon IconDetails := Style.GetElementDetails(twSysButtonNormal); if not Style.GetElementContentRect(0, IconDetails, CaptionRect, ButtonRect) then ButtonRect := Rect(0, 0, 0, 0); IconRect := Rect(0, 0, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON)); RectVCenter(IconRect, ButtonRect); if ButtonRect.Width > 0 then if Assigned(Application.MainForm) then DrawIconEx(CaptionBitmap.Canvas.Handle, IconRect.Left, IconRect.Top, Application.MainForm.Icon.Handle, 0, 0, 0, 0, DI_NORMAL); Inc(TextRect.Left, ButtonRect.Width + 5); //Draw buttons //Close button LDetails := Style.GetElementDetails(twCloseButtonNormal); if Style.GetElementContentRect(0, LDetails, CaptionRect, ButtonRect) then Style.DrawElement(CaptionBitmap.Canvas.Handle, LDetails, ButtonRect); //Maximize button LDetails := Style.GetElementDetails(twMaxButtonNormal); if Style.GetElementContentRect(0, LDetails, CaptionRect, ButtonRect) then Style.DrawElement(CaptionBitmap.Canvas.Handle, LDetails, ButtonRect); //Minimize button LDetails := Style.GetElementDetails(twMinButtonNormal); if Style.GetElementContentRect(0, LDetails, CaptionRect, ButtonRect) then Style.DrawElement(CaptionBitmap.Canvas.Handle, LDetails, ButtonRect); //Help button LDetails := Style.GetElementDetails(twHelpButtonNormal); if Style.GetElementContentRect(0, LDetails, CaptionRect, ButtonRect) then Style.DrawElement(CaptionBitmap.Canvas.Handle, LDetails, ButtonRect); if ButtonRect.Left > 0 then TextRect.Right := ButtonRect.Left; //Draw text Style.DrawText(CaptionBitmap.Canvas.Handle, CaptionDetails, ACaption, TextRect, [tfLeft, tfSingleLine, tfVerticalCenter]); //Draw caption Canvas.Draw(0, 0, CaptionBitmap); CaptionBitmap.Free; //Draw left border CaptionRect := Rect(0, BorderRect.Top, BorderRect.Left, ARect.Height - BorderRect.Bottom); LDetails := Style.GetElementDetails(twFrameLeftActive); if CaptionRect.Bottom - CaptionRect.Top > 0 then Style.DrawElement(Canvas.Handle, LDetails, CaptionRect); //Draw right border CaptionRect := Rect(ARect.Width - BorderRect.Right, BorderRect.Top, ARect.Width, ARect.Height - BorderRect.Bottom); LDetails := Style.GetElementDetails(twFrameRightActive); Style.DrawElement(Canvas.Handle, LDetails, CaptionRect); //Draw Bottom border CaptionRect := Rect(0, ARect.Height - BorderRect.Bottom, ARect.Width, ARect.Height); LDetails := Style.GetElementDetails(twFrameBottomActive); Style.DrawElement(Canvas.Handle, LDetails, CaptionRect); end;
Finally joining all the pieces we can access the objects, bitmaps, colors and fonts of any VCl style, no matter where is located (embedded in a resource or in a external file).
The library and the demo application of the above image is available in the code google site.
Stay tuned for more updates of this library, the next updates will be include HSL, RGB effects to VCL Styles, vcl style explorer and so on.
Pingback: RAD Studio XE2 정보 모음
February 23, 2012 at 2:12 pm
Thanks for this info.
My question: How can I preview window in the “Windowes”-style display?
February 24, 2012 at 6:56 pm
Edmund, please rephrase your question.