While porting my VCL applications to FireMonkey I needed to find a replacement for the TBitButton
from “Additional” palette and so decided to try building my own custom TButton
with a graphic on it.
I was only looking for a stop gap minimal implementation as for any new work I would probably use some fabulous FireMonkey effect rather than a TBitButton
look alike. You can find my TISBitButton
inside the zip file containing my DFM to FMX convertor.
Creating my new TISBitButton
was not as straight forward as my earlier custom string grid component. In this instance the new component is essentially implemented as a style change to a TButton
adding a TRoundRect
containing a TImage
to a TButton
Style in the style designer and placing the information in a style file “IsBitButton.win.style”. I maintained the win.style extension so I can change the style for different platforms as detailed in the example at http://docwiki.embarcadero.com/RADStudio/en/Creating_a_FireMonkey_Component.
The style originally failed to set HitTest
but this caused a problem (see later). The addition to the Style now consists of:
object TRoundRect StyleName = 'isbitbuttonimrext' Align = alRight Position.Point = '(70,0)' Width = 50.000000000000000000 Height = 27.000000000000000000 HitTest = False object ButtonImage: TImage StyleName = 'isbitbuttonimage' Align = alClient HitTest = False Width = 50.000000000000000000 Height = 27.000000000000000000 end end
The extended style file is saved as “IsBitButton.win.style”
To wrap that style into the Delphi implementation we need to compile the style file into a res file by adding to the package project the file “IsBitButton.win.rc” containing the single line
ISBitButtonStyle RCDATA "IsBitButton.win.style"
The resulting “IsBitButton.win.res” file is included in the implementation section as follows
{$IFDEF MSWINDOWS} {$R *.win.res} {$ENDIF}
Registering the new object and adding an implementation of GetStyleObject completes the new object according to the example.
function TIsBitButton.GetStyleObject: TControl; var S: TResourceStream; const Style = 'ISBitButtonStyle'; begin if (FStyleLookup = '') then begin if FindRCData(HInstance, Style) then begin S := TResourceStream.Create(HInstance, Style, RT_RCDATA); try Result := TControl(CreateObjectFromStream(nil, S)); Exit; finally S.Free; end; end; end; Result := inherited GetStyleObject; end;
Compiling and installing the package containing the unit file “IsBitButton.pas” and the RCData file “IsBitButton.win.rc” allows us to add a BitButton
to a form and set its onClick
parameters etc. One issue that was noted at first was that the new image part of the button was not active – hovering over it did not set off the glow animation and clicking on this part of the button did not activate the onclick
event. The HitTest
is by default true so all it required was adding the line HitTest=false
to each new object in the style file.
The bitmap applied to the object could be changed in the style editor but my intention was to expose this as a property of the component. To achieve this
Step One: Establish the property in the component object
TIsBitButton = class(TButton) private FBitmap:TBitmap; procedure SetBitmap(Value:TBitmap); published property BitMap:TBitmap read FBitmap write SetBitmap; end;
Step Two: Override ApplyStyle to link the bitbat with the style
procedure TIsBitButton.ApplyStyle; var T: TFmxObject; begin inherited; T := FindStyleResource('isbitbuttonimage'); if (T <> nil) and (T is TImage) then begin if FBitmap<>nil then TImage(T).Bitmap.Assign(FBitmap) else Bitmap:= TImage(T).Bitmap; repaint; end; end;
This code locatates the TImage
object using the StyleName
‘isbitbuttonimage’ allocated in the Style File and then copies the value
Step Three: Make sure SetBitMap updates the control
procedure TIsBitButton.SetBitmap(Value: TBitmap); begin if FBitmap <> nil then FBitmap.Assign(Value); ApplyStyle; FRecalcUpdateRect:=true; Repaint; end;
I think this should have been all that was required but editing the bitmap property of our new component in the FireMonkey IDE did not cause the on form image to change. The image was changed in the FMX file and when the application ran or the form reload the new bitmap was there but the current IDE presentation was not updated.
I eventually tracked this down to what I would consider a bug in the IDE property editor for images. The property editor seems to accept a pointer to the bitmap and make changes directly to that object rather than replacing the object. The result is that the SetBitmap
routine is not called and the control is not updated.
Step Four: Implement the TBitMap.OnChange event
I then wrote an event handler for BitMapChanged
and allocated it at create time to the TBitMap.OnChange
event.
procedure TIsBitButton.BitMapChanged(Sender: TObject); begin ApplyStyle; FRecalcUpdateRect:=true; Repaint; end; constructor TIsBitButton.Create(AOwner: TComponent); var T: TFmxObject; begin inherited; FBitmap:=TBitmap.Create(10,10); FBitmap.Canvas.DrawLine(PointF(2.0,2.0),Pointf(8.0,8.0),5); FBitmap.Canvas.DrawLine(PointF(8.0,2.0),Pointf(2.0,8.0),5); FBitmap.Canvas.DrawLine(PointF(2.0,8.0),Pointf(8.0,8.0),5); FBitmap.Canvas.DrawLine(PointF(2.0,2.0),Pointf(8.0,2.0),5); FBitmap.Canvas.DrawLine(PointF(8.0,2.0),Pointf(8.0,8.0),5); FBitmap.Canvas.DrawLine(PointF(2.0,2.0),Pointf(2.0,8.0),5); FBitmap.OnChange:=BitMapChanged; end;
Ideally the create event should also find the current bitmap in the style object and apply that to the bitmap property but the style information cannot be accessed at this time. The default bitmap is drawn in the constructor and will not reflect any style changes. I did try other logic leaving FBitmap
nil unless specifically changed by the FMX file so a nil bitmap could be allocated the Style value but could not resolve the interdependencies.
Step 5: Package and install
requires rtl, fmx; contains IsAdvStringGrid in 'IsAdvStringGrid.pas' IsBitButton in 'IsBitButton.pas' IsBitButton.win.style IsBitButton.win.rc
It is probably worth mentioning a couple of other issues that caused me grief. They seem so obvious now but not so at the time.
As suggested in the example I included the files “IsBitButton.win.rc” and “IsBitButton.win.style” in the Package file and compiling the package automatically produced the “IsBitButton.win.res” file. Changing the “.rc” resulted in a new version of the “.res” file but changes to the “.style” did not result in a new automatically generated “.res” file.
I also ran into trouble when a file from an early attempt was included in a uses clause in part of the test project. This resulted in two style files being included with the same resource name. A warning was generated at runtime only and was easy to miss. The combination of these two issues meant that attempted changes to the style file seemed to cause erratic behaviour in the test project.
Thanks for this tutorial … FMX is hard for newbies
Your welcome but a word of warning this example used XE2. When I came to compile it in XE3 things had changed quite a lot. I have not tried to make it work in XE3 as it was mostly part of an early play with FireMonkey.