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只是重绘了边框,而没有整 ...
随机推荐
- html写法对gzip压缩率的影响
前几天在群里看到小杜分享一篇文章,<html写法对gzip压缩率的影响>,为此我也对这点分析了一下.不知道大家有没有看过这文章,作者是来自微博懒懒交流会,其内容我这里先简述一下. Gzip ...
- nginx安装启动
参考:http://network.51cto.com/art/201005/198198_4.htm 下载nginx tar.gz安装包下载pcre tar.gz安装包 安装pcretar zxvf ...
- Java String.split()用法小结(转)
第一种方法:string s=abcdeabcdeabcde;string[] sArray=s.Split('c') ;foreach(string i in sArray)Console.Writ ...
- 采用动态代理方式调用WEB服务(转载+整理)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- EJDB 1.1.18 发布,嵌入式JSON数据库
EJDB 1.1.18 增加对 MongoDB 操作符 $and 和 $or 的支持,支持 MongoDB 的 $ 推断操作符,修复了 $fields 提示的bug,提升了查询处理的性能. EJDB ...
- 配置iDempiere源码开发环境
你需要一个较为快速通畅的互联网连接来下载源代码! 安装软件: OS: Windows Server 2008 R2 SP1 x64 英文版 Database: Oracle 11G R2 x64 英文 ...
- 需要弥补的那部分SQL
一.前言 虽然我们大多数人都学习过SQL,但是经常忽略它.总是会自以为学到的已经足够用了,从而导致我们在实际开发的过程中遇到复杂的问题后只能在检索数据后通过传统的代码来完成,但是其中很多的功能利用SQ ...
- 重新延时运行的Js 实现
场景 1. AutoComplete 插件, 当用户的输入空闲0.5s 时,才向服务发送请求.而不是用户输入每一个字符都要请求服务器. 2. 图片懒加载时,用户拖动滚动条空闲0.5s时,才遍历懒加载的 ...
- Javah提示未找到 ..的类文件
D:\我的文档\workspace\PrepareForExam\src>javah -classpath D:\我的文档\workspace\ PrepareForExam\src\com\e ...
- AlwaysON同步性能监控的三板斧
延迟是AlwaysOn最大的敌人之一 延迟是AlwaysON的最大敌人之一.对AlwaysON而言,其首要目标就尽量减少(无法避免)主副本.辅助副本的数据延迟,实现主副本.辅助副本的“数据同步”.只有 ...