中午,有個貨隨手買的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外觀樣式,而後繪製這個外觀代理