Delphi 下的FireMonkey,很好地实现了 DirectUI与跨平台。学习了解他,对DirectUI编程及项目的跨平台实现有一定帮助。
虽然作为开发者个体,并不需要了解太多这些东西,只要求拿来能用能实现功能就行,但对 FireMonkey的学习分析,对自己程序设计思想的提升,会有一定帮助。
昨天用FireMonkey控件写了一个小例子,发现他的 Animation类在实现控件的小动画时,很高效,很灵活。
初步印象是 FireMonkey的内核有很多值得学习的地方,尤其他的界面渲染上,可以深入了解。鉴于都有代码,了解只是时间上的问题。
今天才开始认真地看了下 FireMonkey的代码。跟踪查看了他的程序启动的各个步骤。先初略整理一下。在了解框架之后,再逐步深入了解其他各方面。
FireMonkey跨平台实现:
FM为了考虑跨平台,使用了很多服务接口类,来转嫁各种服务任务处理。
应用程序管理接口 | IFMXApplicationService |
系统字体接口 | IFMXSystemFontService |
窗体创建接口 | IFMXWindowService |
系统菜单接口 | IFMXMenuService |
计时器接口 | IFMXTimerService |
鼠标拖曳接口 | IFMXDragDropService |
粘贴板接口 | IFMXClipboardService |
鼠标形状接口 | IFMXCursorService |
鼠标消息接口 | IFMXMouseService |
系统桌面接口 | IFMXScreenService |
本地化与文本输出接口 | IFMXLocaleService, IFMXTextService |
上下文菜单显示接口 | IFMXContextService |
绘图与设备接口 | IFMXCanvasService, IFMXDeviceService |
界面外观与窗口外观接口 | IFMXStyleService, IFMXWindowBorderService |
其他接口 |
IFMXSystemInformationService, IFMXLoggingService |
看似这些接口很全很复杂。其实,所有接口都转到一个平台服务类来进行处理。这个平台服务类根据操作系统的不同分为 TPlatformWin, TPlatformIOS, TPlatformMac, TPlatformAndroid.
用接口转到这个类来处理,与直接用这类来处理有什么区别?
在调用服务接口类完成相应任务时,被调用的服务是一个公用的接口,与平台无关。所以程序中的所有对象都可以调用接口来实现相关服务任务。而不用考虑这些服务是怎样实现的。调用者只需提交指令。
然后服务接口会根据指令。并根据指定的平台,交由平台服务类去进行具体服务任务的完成。
当然,也可以让程序中所有对象直接调用服务平台类来实现任务。但是每个对象在发出任务指令时,都要先进行平台判断与指定。
每发出一次指令都指定一次平台,与通过通一的接口,让接口统一去指定平台。可想而知,哪个更科学,更方便。
当然完成这个接口体系,任务宏大。先要规划十几个服务接口的服务内容。然后再由平台去完成这些服务任务。而每个接口要负责的指令都得先归类规划好。
了解了跨平台,再来细说具体一个程序的运行流程。
程序启动流程分析:
FM的程序在启动时,会在装载单元文件FMX.Platform 时首先执行 RegisterCorePlatformServices 过程。来注册对应的平台服务类对象。
FM是根据宏定义(MSWINDOWS 或 IOS 或 ANDRODIO)来决定 uses 那个平台的 Services 单元。并执行这个单元里的RegisterCorePlatformServices。如Windows系统,uses的是 MX.Platform.Win 单元
RegisterCorePlatformServices 执行,首先创建的一个服务类对象TPlatformWin,这个类对象有N多个接口(前面所列的各种服务接口),再将这个TPlatformWin注册到各个接口服务中去。如: TPlatformServices.Current.AddPlatformService(IFMXMouseService, PlatformWin); 以便让程序中的对象(组件)调用相应接口时,能转回到平台服务对象来处理。
PlatformWin 在创建的时候,先向全局原子表添加一个字符串,(FM 就是用这个字符串标识来查找自己的 FORM 的)。同时创建一个Application类对象。来接管系统的消息和负责窗体的创建。
完成服务类 PlatformWin 与应用类 Application 的创建后。程序的初始化基本完成。(当然FM还执行了其他N多配套的初始化动作。如创建一个Screen对象,建立一个管理窗口的堆栈。来负责管理应用程序的所有窗体)。
初始化完成后,开始启动 Applicaion。进入程序的主体运行机制。启动步骤如下:
执行Application的Initialize。只是方便让开发者员加入程序初始化代码。
执行 Application.CreateForm, 创建第一个窗体 From。并指定为MainForm
执行 Application.Run; 进入消息循环。
总结:
程序启动
装入FMX.Platform
执行RegisterCorePlatformServices
创建平台服务类TPlatformWin
注册各种平台服务接口
创建应用类TApplication
Application. Initialize用户初始化
Application.CreateForm创建第一个窗体,并指定为MainForm
MainForm指定 ParneWnd时,创建 Application的影子窗体,
Application的影子窗体创建时,绑定消息处理过程 WndPro
Application.Run 进入消息循环
程序开始运行并处理消息。
TApplication分析
TApplication的Create并没太多的动作。只是创建了一个TIdleMessage和TApplicationFormFactor,这两个的具体作用以后再了解。
Application 的Initialize 也不执行任何动作。
Application 的CreateForm负责程序所有窗体的构建。Application 的 FCreateForms 类 来取得所创建的窗体的指针与Class类型, Instance 信息。并将创建的类保存一个TFormRegistry 堆栈。
Application 的创建窗体,只是创建了窗体的类对象 Form。而真正显示在桌面上我们能看到的窗体,是在 Form 创建时,通过Form 的CreateHandle 来创建完成。而CreateHandle事件又调用了窗体服务接口WinService 的CreateWindow。这个WinService,其实就是初始化过程中注册了的服务类 PlatformWin。真正执行创建窗体的动作,经过七转八拐最后交给了 PlatformWin 的 CreateWindow。(FM 的很多任务,都是通过这种接口转移来实现的)
PlatformWin的CreateWindow是个复杂的过程。他根据传来的Form类,设定WindowClass的各个参数。这中间还考虑到了窗体在Delphi的IDE环境下的一些设定。同时,他还判定窗体是普通窗体还是Popup型窗体。还会为窗体注册各种服务接口,如Menu,DropDrag等。
Form在创建实体窗口时。会指定他的ParneWnd为 ApplicationHWND; ApplicationHWND会调用 PlatformWin的CreateAppHandle来创建一个FMAppClass。这时,Application的真正的实体隐形窗口FMAppClass Window 就出生了。
FM的消息循环,正是绑定FMAppClass窗体的句柄以及消息过程 WndPro 来实现的。
Applicaion 会在创建第一个窗体时,将其指定为 MainForm。用户的交互操作,都是在 MainForm 上实现的。
Application的消息循环:
Application执行Run. 进入消息循环
Run 会调用 App服务接口 AppService。当 然AppService又指向了PlatformWin. 执行PlatformWin 的 Run.
PlatformWin.Run 中会执行 Application 的HandleMessage。当然又是通过AppService 回到执行 PlatformWin 的 HandleMessage。
PlatformWin 的HandleMessage 才开始正式的消息循环。在这里,我们能看到熟悉的 PeekMessage, TranslateMessage 和DispatchMessage。
HandleMessage 通过消息处理过程 WndPro. 来过滤和分发所有消息
当得到 WM_QUIT或 FMAppClass 的 WM_CLOSE 时,退出消息循环,终止程序。
总结:
Application创建 (没太多动作)
Application初始化 (没动作)
Application CreateForm 创建主窗体Form类
Form类执行 CreateHandle 创建窗口实体,将指定ParneWnd
Form指定 ParneWnd时,创建 Application的影子窗体,
创建Application的影子窗体,并绑定消息过程 WndPro
Application执行Run. 进入消息循环
Run执行 PlatformWin的HandleMessage
HandleMessage执行WndPro 过滤与分发消息。
程序进入正常的消息过程。
处理消息,实现交互操作。
得到 WM_QUIT或 FMAppClass 的 WM_CLOSE 时,退出消息循环,终止程序。
消息分发机制分析:
Screen对象会把Application CreatForm产生的Form全加入一个队列。
在消息循环中,根据消息句柄 hWnd 在Screen对象中找对应的Form。并将消息派发给Form。如果Form=nil,执行 Application的消息检测。侦测WM_CLOSEWM_DESTROY等消息,判断是否退出程序。
进行窗口消息过虑,响应 WM_LBUTTONDOWN,WM_LBUTTONUP,WM_MOUSEMOVE,WM_MOUSEWHEEL 等鼠标消息WM_CHAR, WM_KEYDOWN,WM_KEYUP等按键消息。
对不同的消息作不同的处理,主要是鼠标与按键类的消息分发。执行相应的 MouseDown, MouseUp, KeyDown, KeyUp 事件。
Form的鼠标消息处理:
Form有三个私有对象 FCaptured, FFocused,FHovered。
FCaptured:记录按下时的鼠标移所在位置的对象。
FHovered:记录正常(未按下时)的鼠标移所在位置的对象。
FFocused:记录键盘焦点对象。
在消息*内,From 通过ObjectAtPoint来确定消息对象Obj。并将对应的消息分发给Obj. 如MouseUp,MouseDown,MouseMove等,先取得鼠标位置的Obj.然后根据情况是否派发消息给Obj。
MouseUp,MouseDown 事件会先侦测 Obj的Drag (拖曳事件) 执行BeginAutoDrag或终止 Drag。在没有Drag时,直接将消息分发给Obj。这时FCaptured 对象在处理Drag起到了关键作用。
Form在处理MouseMove时,会更新 Hovered。并触发原 Hovered的MouseLeave和新的 Hovered(即当前Obj)MouseEnter。这时再将MouseMove消息再派发给Obj。
CursorService 来负责鼠标形状的改变。
按键消息 KeyDown,KeyUp 是通过 Focused 派发。
Captured是在鼠标按下时取得的对象。全程将鼠标消息派发给 Captured, 以确保按下后鼠标拖动时与MouseUp, MouseLeave消息处理。
总结:
Run执行循环,并通过WndPro过滤,并分发消息
根据hWnd查找Form,将消息派发给Form
处理Form的消息,过滤消息,
处理FORM自身的消息,并响应相关事件。
派发相关消息 如鼠标,键盘等给Form里的Object
通过Captured,Hovered,Focused 三个对象来细化消息处理
让Object响应,并执行相应的事件。实现交互操作。
参考:http://my.oschina.net/isixth/blog/361097