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只是重绘了边框,而没有整 ...
随机推荐
- ORA-24550错误
[oracle@app-148-39 oracledata]$ sqluldr2_linux64_10204.bin USER=xxx/xxx@xxx:1521 charset=AL32UTF8 QU ...
- 黑马程序员_Java基础:十进制转换其他进制
------- android培训.java培训.期待与您交流! ---------- 平时使用中,进制转换只要使用Integer这个包装类中的方法即可完成. 但其实我们也能用自己的方法去实现,这有助 ...
- opengles2.0之图元装配和光栅化
光栅化的过程就是把三维世界中的物体转换成屏幕上像素的过程. glGetfloatv(); --------v表示的是数组 gles2.0里面有两种绘图命令.glDrawArrays和glDraw ...
- 3.3 SQLite数据库
1.使用嵌入式关系型SQLite数据库存储数据 轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能.SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用 ...
- 借助Process Explorer定位断电未保存的录音文件
话说某大神(大婶)开会常偷懒,用Windows自带的录音机进行录音并用记事本记录会议精要却没有点击Ctrl+S的习惯,结果就给我找了今天的难题.(之前都是Office的自动保存在哪里……) 还是一样, ...
- DateSort选择法、冒泡法排序
public class DateSort {public static void main(String args[]) {Date d[] = new Date[11];d[0] = new Da ...
- LDO-XC6216C202MR-G
XC6216C202MR-G 1.改产品是特瑞士(TOREX)公司电源管理芯片,输入电压可达28V,输出可调23V,最大输出电流150mA.压差最小为300mV.该系列有固定式输出和可调式 ...
- Android 自定义View 三板斧之一——继承现有控件
通常情况下,Android实现自定义控件无非三种方式. Ⅰ.继承现有控件,对其控件的功能进行拓展. Ⅱ.将现有控件进行组合,实现功能更加强大控件. Ⅲ.重写View实现全新的控件 本文重点讨论继承现有 ...
- Window程序的安装与部署
步骤: 1.新建项目—选择安装与部署—安装项目或使用安装向导,再这里我用的是安装向导 2.点击确定—下一步 3.点击下一步,选择主输出 4.点击下一步,添加文件 5.点击完成 设置: 右击安装项目 出 ...
- tar-usage
tar -c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个.下面的 ...