XE6 FMX之控件绘制与显示
中午,有个货随手买的2块钱的彩票,尼玛中了540块,这是啥子狗屎气运。稍微吐槽一下,现在开始正规的笔记录入。经常有朋友说为毛我的博客不更新了或者说更新的少了,为啥呢!一来自己懒了,没学习什么新的东西,二来平常琐事多,于是这个博客更新就少了。FMX目前已经更新了好几个版本,甚至连属性方法都改过了,从以前刚出来时候的拼音输入法支持都有Bug,到现在基本上比较流畅运行,说明了进步还是挺大的,那么学习这个东西也应该可以是提上日程了,或许不久的将来会用到。
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) + );
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;
主要就是
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 <> then
try
rgnStatus := GetUpdateRgn(Wnd, Region, False);
if (rgnStatus = ) or (rgnStatus = ) then
begin
RegionSize := GetRegionData(Region, $FFFF, nil);
if RegionSize > then
begin
GetMem(RegionData, RegionSize);
try
RegionSize := GetRegionData(Region, RegionSize, RegionData);
if RegionSize = RegionSize then
begin
SetLength(UpdateRects, RegionData.rdh.nCount);
for i := to RegionData.rdh.nCount - do
begin
R := PRgnRects(@RegionData.buffer[])[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) > ) then
begin
// add update rects from FInPaintUpdateRects
for I := to High(InPaintUpdateRects) do
begin
SetLength(UpdateRects, Length(UpdateRects) + );
UpdateRects[High(UpdateRects)] := InPaintUpdateRects[I];
end;
end;
PaintControl.PaintRects(UpdateRects);
if PlatformWin.FInPaintUpdateRects.TryGetValue(LForm.Handle, InPaintUpdateRects) and (Length(InPaintUpdateRects) > ) then
begin
// paint second time - when Repaint called in painting
PlatformWin.FInPaintUpdateRects.TryGetValue(LForm.Handle, UpdateRects);
SetLength(InPaintUpdateRects, );
PlatformWin.FInPaintUpdateRects.AddOrSetValue(LForm.Handle, InPaintUpdateRects);
PaintControl.PaintRects(UpdateRects);
end;
PaintControl.ContextHandle := ;
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外观样式,然后绘制这个外观
XE6 FMX之控件绘制与显示的更多相关文章
- C# chart控件绘制曲线
在.NET中以前经常用GDI去绘制,虽然效果也不错,自从.NET 4.0开始,专门为绘制图表而生的Chart控件出现了,有了它,就可以轻松的绘制你所需要的曲线图.柱状图什么的了. using Syst ...
- 用Chart控件绘制动态图表
进行程序设计时,选用一个合适的ActiveX控件,有时可大大减少编程工作量.ActiveX 控件(又称OCX)基于COM技术,作为独立的软件模块,它可以在任何程序设计语言中插入使用.本文仅以VC++为 ...
- c# 通过.net自带的chart控件绘制饼图pie chart
c# 通过.net自带的chart控件绘制饼图pie chart 需要实现的目标是: 1.将数据绑定到pie的后台数据中,自动生成饼图. 2.生成的饼图有详细文字的说明. 具体的实现步骤: > ...
- 记录下UIButton的图文妙用和子控件的优先显示
UIButton的用处特别多,这里只记录下把按钮应用在图文显示的场景,和需要把图片作为按钮的背景图片显示场景: 另外记录下在父控件的子控件优先显示方法(控件置于最前面和置于最后面). 先上效果图: 1 ...
- ZedGrap控件绘制图表曲线
问题描述: 使用C#中ZedGrap控件绘制图表曲线图 ZedGrap 介绍说明: 安装ZedGrap控件 ZedGraph控件dll文件: 添加ZedGraph控件,首先在新建立的C#图像工 ...
- C# WinForm中 让控件全屏显示的实现代码
夏荣全 ( lyout(at)163.com )原文 C#中让控件全屏显示的实现代码(WinForm) 有时候需要让窗口中某一块的内容全屏显示,比如视频播放.地图等等.经过摸索,暂时发现两种可行方法, ...
- DuiLib(四)——控件绘制
duilib的所有控件均绘制在唯一的真实窗口之中,本篇就具体看下这个绘制的过程.所有的绘制过程均在WM_PAINT消息处理过程中完成.由窗口及消息篇可以看到,窗口消息处理最终流到了CPaintMana ...
- jQuery里面的datepicker日期控件默认是显示英文的,如何显示中文或其他语言呢?
jQuery里面的datepicker日期控件默认是显示英文的,如何让他显示中文或其他呢? [官方的写法]: (1)引入JS文件: <script type="text/javascr ...
- 五种情况下会刷新控件状态(刷新所有子FWinControls的显示)——从DFM读取数据时、新增加子控件时、重新创建当前控件的句柄时、设置父控件时、显示状态被改变时
五种情况下会刷新控件状态(刷新控件状态才能刷新所有子FWinControls的显示): 在TWinControls.PaintControls中,对所有FWinControls只是重绘了边框,而没有整 ...
随机推荐
- getline函数的用法
函数声明 bool getline(istream &in, string &s) 功能说明: 从输入流读入一行到变量string s,及时是空格也可以读入. –直到出现以下情况为止: ...
- 10 个超酷的加载中的 Gif 动画
#1: Calming cat loading gif #2: Stylish loading gif #3: Shape changing loading gif #4: Loading gif f ...
- 微软分布式框架Orleans开源了
开源地址: https://github.com/dotnet/orleans 昨天编译了一下,这个最新的Orleans安装程序(用github源码编译的) 下载地址:http://pan.baidu ...
- 从源代码的角度聊聊java中StringBuffer、StringBuilder、String中的字符串拼接
长久以来,我们被教导字符串的连接最好用StringBuffer.StringBuilder,但是我们却不知道这两者之间的区别.跟字符串相关的一些方法中总是有CharSequence.StringBuf ...
- Bluetooth Low Energy——蓝牙低功耗
Android4.3(API级别18)引入内置平台支持BLE的central角色,同时提供API和app应用程序用来发现设备,查询服务,和读/写characteristics.与传统蓝牙(Classi ...
- 到底是 const 还是 static readonly
真的一样? const 和 static readonly 常在程序中用来声明常量,调用方法也没有什么不同,他们真的一样吗?我们可以做个试验. 程序集内的常量 现在我们建立一个程序,里面有一个MyCl ...
- SignalR初体验
简介 ASP .NET SignalR[1] 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信.什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端可以 ...
- 【WEB API项目实战干货系列】- 导航篇(十足干货分享)
在今天移动互联网的时代,作为攻城师的我们,谁不想着只写一套API就可以让我们的Web, Android APP, IOS APP, iPad APP, Hybired APP, H5 Web共用共同的 ...
- Atitit.实现继承的原理and方法java javascript .net c# php ...
Atitit.实现继承的原理and方法java javascript .net c# php ... 1. 实现继承的问题 1 2. 如何拷贝基类方法?采用prototype原型方式,通过冒充对象 1 ...
- 连接数据库——模拟ATM机查、存、取、开户功能
1.界面:包含开户.查询.存款.取款.功能 package com.bank.test; /** * * @author Administrator *界面类 */ public class Jiem ...