中午,有个货随手买的2块钱的彩票,尼玛中了540块,这是啥子狗屎气运。稍微吐槽一下,如今开始正规的笔记录入。常常有朋友说为毛个人博客不更新了或者说更新的少了,为啥呢!一来本身懒了,没学习什么新的东西,二来日常杂事多,因而这个博客更新就少了。FMX目前已经更新了好几个版本,甚至连属性方法都改过了,从之前刚出来时候的拼音输入法支持都有Bug,到如今基本上比较流畅运行,说明了进步仍是挺大的,那么学习这个东西也应该能够是提上日程了,或许不久的未来会用到。api
FMX是一套UI类库,就至关于之前的VCL,可是相比VCL来讲,支持了跨平台,同时也直接内部支持了各类特效动画甚至3D的效果,若是效率性能上来了,这个类库仍是颇有前景的。此次我主要学习的就是一个FMX窗体是如何绘制并显示出来的,相比较于VCL,有哪些不一样之处,以及一个FMX程序的启动运转的最简单剖析。至于各类特效,动画,以及3D等,之后再慢慢的去啃食,贪多嚼不烂。数组
新建一个FireMonkey的HD Desktop Application,IDE会自动创建一个工程,进入工程,能够发现FMX的程序,各个单元前面都有FMX的名称空间进行标记,FMX的Form,Application以及各类控件都已是重写的了,而不是VCL的那一套继承体系,至于这个FMX的总体继承结构,其余的都有介绍说明,能够去网上搜索,这里不记录。我这里主要剖析一个程序的运行以及显示。程序运行,首要的第一个要看的就是Application这个对象,这个对象在FMX.Forms中,一个FMX工程运行的最简单的工程代码结构为函数
begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end.
这个基本代码和VCL模式差很少,那么关键就是在于内部的实现了,因为FMX的窗体也不是之前的VCL,因此咱们先看看这个CreateForm,这个CreateForm的代码颇有意思,也会很蛋疼的
procedure TApplication.CreateForm(const InstanceClass: TComponentClass; var Reference); var Instance: TComponent; RegistryItems : TFormRegistryItems; RegItem : TFormRegistryItem; begin if FRealCreateFormsCalled then begin Instance := TComponent(InstanceClass.NewInstance); TComponent(Reference) := Instance; try Instance.Create(Self); for RegItem in FCreateForms do if RegItem.InstanceClass = InstanceClass then begin RegItem.Instance := Instance; RegItem.Reference := @Reference; end; except TComponent(Reference) := nil; raise; end; end else begin SetLength(FCreateForms, Length(FCreateForms) + 1); FCreateForms[High(FCreateForms)] := TFormRegistryItem.Create; FCreateForms[High(FCreateForms)].InstanceClass := InstanceClass; FCreateForms[High(FCreateForms)].Reference := @Reference; // Add the form to form registry in case RegisterFormFamily will not be called if FFormRegistry.ContainsKey(EmptyStr) then begin RegistryItems := FFormRegistry[EmptyStr]; end else begin RegistryItems := TFormRegistryItems.Create; FFormRegistry.Add(EmptyStr, RegistryItems); end; RegistryItems.Add(FCreateForms[High(FCreateForms)]); end; end;
如何,颇有意思吧,不知道是为啥这样写。这个代码的意思是没有真正建立主窗体以前都只会产生一个窗体注册项保存到注册的一个内部数组中,而后Run以后Application会调用性能
RealCreateForms函数进行窗体建立,此时FRealCreateFormsCalled才会为True,而后使用Application.CreateForm建立的窗体的第二个参数才会返回实际的窗体对象,不然没有Run的时候,使用本方法并不会建立对象,也就是说咱们之前在VCL中的工程代码中能够写
begin Application.Initialize; Application.CreateForm(TForm1, Form1); Form1.Caption := 'VCL窗体';//这句代码在VCL能够,FMX中此时Form1并未建立,因此这个属性赋值会出错! Application.Run; end.
可是在 FMX窗体中,咱们在Run以前使用Form1对象就会出错了。这点事切记的。学习
而后看Run方法,这个代码写的很简洁动画
procedure TApplication.Run; var AppService: IFMXApplicationService; begin {$IFNDEF ANDROID} AddExitProc(DoneApplication); {$ENDIF} FRunning := True; try if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationService, IInterface(AppService)) then AppService.Run; finally FRunning := False; end; end;
主要就是spa
if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationService, IInterface(AppService)) then这个转换,而后调用AppService的Run。
这个是针对平台的。TPlatformServices在FMX.Platform单元中,能够知道这个Current实际上就是一个单例的TPlatformServices对象,而后SupportsPlatformService进行
IFMXApplicationService接口查询转换。那么是神马时候创建的这个
SupportsPlatformService而且注册进这个TPlatformServices中的呢,咱们翻到这个单元最底部的Initialization中,能够发现会调用RegisterCorePlatformServices这个,这个就是注册这个平台服务接口的。,而后这个函数在Android,Windows,IOS等平台中都有,好比FMX.Platform.Win,FMX.Platform.Android,至于区分使用那个,使用的是编译预处理,看用户的Target选择的是什么平台就注册的什么函数。而后Windows下是TPlatformWin,Application也是在这个对象创建的时候创建,能够查看他的Create代码,而后创建AppHandle,使用CreateAppHandle函数,以后创建窗体,由于FMX中惟有一个窗体是相似于VCL WinControl的有句柄的GDI对象,因此那么必须会使用CreateWindow进行窗口创建,而后消息代理到Application上去,FMX中在Win下,这个也是必须的,因此找到对应的方法,就是CreateHandle这个,这个函数调用的其实是TPlatformWin的CreateWindow,而后返回一个Handle,这个Handle不在是VCL中的一个DWORD的句柄值,而是一个TWindowHandle对象了。在这个建立窗体过程当中,能够发现他直接将窗体的消息处理过程指定到了WndProc这个函数过程,全部的消息处理都由这个过程进行。中间的消息处理过程就不说了,下面说一个窗体以及窗体上的控件的绘制显示过程.
由于FMX窗体上的全部控件显示对象都是使用的窗体自己的设备场景句柄,因此咱们要看他的绘制显示过程直接看上面的Wndproc中的WM_Paint消息就好了。而后找到WMPaint方法以下:
function WMPaint(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; var i, rgnStatus: Integer; Region: HRgn; RegionSize: Integer; RegionData: PRgnData; R: TRect; LForm: TCommonCustomForm; UpdateRects, InPaintUpdateRects: TUpdateRects; PS: TPaintStruct; Wnd: Winapi.Windows.HWND; PaintControl: IPaintControl; begin LForm := FindWindow(hwnd); if LForm <> nil then begin Wnd := FormToHWND(LForm); GetUpdateRect(Wnd, R, False); Region := CreateRectRgn(R.Left, R.Top, R.Right, R.Bottom); if Region <> 0 then try rgnStatus := GetUpdateRgn(Wnd, Region, False); if (rgnStatus = 2) or (rgnStatus = 3) then begin RegionSize := GetRegionData(Region, $FFFF, nil); if RegionSize > 0 then begin GetMem(RegionData, RegionSize); try RegionSize := GetRegionData(Region, RegionSize, RegionData); if RegionSize = RegionSize then begin SetLength(UpdateRects, RegionData.rdh.nCount); for i := 0 to RegionData.rdh.nCount - 1 do begin R := PRgnRects(@RegionData.buffer[0])[i]; UpdateRects[i] := RectF(R.Left, R.Top, R.Right, R.Bottom); end; end; finally FreeMem(RegionData, RegionSize); end; if Supports(LForm, IPaintControl, PaintControl) then begin PaintControl.ContextHandle := BeginPaint(Wnd, PS); try if PlatformWin.FInPaintUpdateRects.TryGetValue(LForm.Handle, InPaintUpdateRects) and (Length(InPaintUpdateRects) > 0) then begin // add update rects from FInPaintUpdateRects for I := 0 to High(InPaintUpdateRects) do begin SetLength(UpdateRects, Length(UpdateRects) + 1); UpdateRects[High(UpdateRects)] := InPaintUpdateRects[I]; end; end; PaintControl.PaintRects(UpdateRects); if PlatformWin.FInPaintUpdateRects.TryGetValue(LForm.Handle, InPaintUpdateRects) and (Length(InPaintUpdateRects) > 0) then begin // paint second time - when Repaint called in painting PlatformWin.FInPaintUpdateRects.TryGetValue(LForm.Handle, UpdateRects); SetLength(InPaintUpdateRects, 0); PlatformWin.FInPaintUpdateRects.AddOrSetValue(LForm.Handle, InPaintUpdateRects); PaintControl.PaintRects(UpdateRects); end; PaintControl.ContextHandle := 0; finally EndPaint(Wnd, PS); end; end; end; end; finally DeleteObject(Region); end; Result := DefWindowProc(hwnd, uMsg, wParam, lParam); end else Result := DefWindowProc(hwnd, uMsg, wParam, lParam); end;
这个代码稍微有一点点长,能够看到若是要绘制控件,基本上须要继承IPaintControl这个接口,而后绘制的时候会调用这个接口的PaintRects方法,因此咱们而后就看Form的PaintRects方法,在TCustomForm.PaintRects中,从这里就能够看到全部窗体上显示的控件的绘制处理。调试能够发现基本上全部的控件的第一个都是一个TStyleobject的对象,这个主要是针对那个皮肤管理的用来绘制皮肤特效的,而后进入到控件的绘制,绘制控件的时候会触发TControl的PaintInternal方法。窗体的PaintRects会执行一个PareforPaint函数,这个函数主要是用来准备各个子控件的绘制。而后在执行本方法的时候,若是是TStyledControl会执行ApplyStyleLookup方法,就是绘制外观的。这个函数的主要目的是得到一个外观样式Control,而后设置成控件大小,而后插入到子控件列表做为第一个项目,而后绘制这个插入的外观样式。基本上是这么个显示概念,好比绘制Button,会在绘制的时候插入一个Button外观样式,而后绘制这个外观代理