首页 > 代码库 > 学习: Delphi FireMonkey 结构性初略分析

学习: Delphi FireMonkey 结构性初略分析

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
IFMXFullScreenWindowService, IFMXListingService, IFMXSaveStateService,
IFMXDeviceMetricsService, IFMXGestureRecognizersService,
IFMXWindowsTouchService

 

看似这些接口很全很复杂。其实,所有接口都转到一个平台服务类来进行处理。这个平台服务类根据操作系统的不同分为 TPlatformWin, TPlatformIOS,  TPlatformMac, TPlatformAndroid.

用接口转到这个类来处理,与直接用这类来处理有什么区别?

在调用服务接口类完成相应任务时,被调用的服务是一个公用的接口,与平台无关。所以程序中的所有对象都可以调用接口来实现相关服务任务。而不用考虑这些服务是怎样实现的。调用者只需提交指令。
然后服务接口会根据指令。并根据指定的平台,交由平台服务类去进行具体服务任务的完成。

当然,也可以让程序中所有对象直接调用服务平台类来实现任务。但是每个对象在发出任务指令时,都要先进行平台判断与指定。
每发出一次指令都指定一次平台,与通过通一的接口,让接口统一去指定平台。可想而知,哪个更科学,更方便。

当然完成这个接口体系,任务宏大。先要规划十几个服务接口的服务内容。然后再由平台去完成这些服务任务。而每个接口要负责的指令都得先归类规划好。

 


了解了跨平台,再来细说具体一个程序的运行流程。

程序启动流程分析:

  1. FM的程序在启动时,会在装载单元文件FMX.Platform 时首先执行 RegisterCorePlatformServices 过程。来注册对应的平台服务类对象。

  2. FM是根据宏定义(MSWINDOWSIOSANDRODIO)来决定 uses 那个平台的 Services 单元。并执行这个单元里的 RegisterCorePlatformServices。如Windows系统,uses的是 MX.Platform.Win 单元

  3. RegisterCorePlatformServices 执行,首先创建的一个服务类对象TPlatformWin,这个类对象有N多个接口(前面所列的各种服务接口),再将这个TPlatformWin注册到各个接口服务中去。如:  TPlatformServices.Current.AddPlatformService(IFMXMouseService, PlatformWin); 以便让程序中的对象(组件)调用相应接口时,能转回到平台服务对象来处理。

  4. PlatformWin 在创建的时候,先向全局原子表添加一个字符串,(FM 就是用这个字符串标识来查找自己的 FORM 的)。同时创建一个 Application类对象。来接管系统的消息和负责窗体的创建。

  5. 完成服务类 PlatformWin 与应用类 Application 的创建后。程序的初始化基本完成。(当然FM还执行了其他N多配套的初始化动作。如创建一个Screen对象,建立一个管理窗口的堆栈。来负责管理应用程序的所有窗体)。

  6. 初始化完成后,开始启动 Applicaion。进入程序的主体运行机制。启动步骤如下:

  7. 执行ApplicationInitialize。只是方便让开发者员加入程序初始化代码。

  8. 执行 Application.CreateForm, 创建第一个窗体 From。并指定为MainForm

  9. 执行 Application.Run; 进入消息循环。

 

总结:

  1. 程序启动

  2. 装入FMX.Platform

  3. 执行RegisterCorePlatformServices

  4. 创建平台服务类TPlatformWin

  5. 注册各种平台服务接口

  6. 创建应用类TApplication

  7. Application. Initialize用户初始化

  8. Application.CreateForm创建第一个窗体,并指定为MainForm

  9. MainForm指定 ParneWnd时,创建 Application的影子窗体,

  10. Application的影子窗体创建时,绑定消息处理过程 WndPro

  11. Application.Run 进入消息循环

  12. 程序开始运行并处理消息。

 

TApplication分析

  1. TApplicationCreate并没太多的动作。只是创建了一个TIdleMessageTApplicationFormFactor,这两个的具体作用以后再了解。

  2. Application Initialize 也不执行任何动作。

  3. Application CreateForm负责程序所有窗体的构建。Application 的 FCreateForms 类 来取得所创建的窗体的指针与Class类型, Instance 信息。并将创建的类保存一个TFormRegistry 堆栈

  4. Application 的创建窗体,只是创建了窗体的类对象 Form。而真正显示在桌面上我们能看到的窗体,是在 Form 创建时,通过Form CreateHandle 来创建完成。而CreateHandle事件又调用了窗体服务接口WinService CreateWindow。这个WinService,其实就是初始化过程中注册了的服务类 PlatformWin。真正执行创建窗体的动作,经过七转八拐最后交给了 PlatformWin CreateWindow。(FM 的很多任务,都是通过这种接口转移来实现的)

  5. PlatformWinCreateWindow是个复杂的过程。他根据传来的Form类,设定WindowClass的各个参数。这中间还考虑到了窗体在DelphiIDE环境下的一些设定。同时,他还判定窗体是普通窗体还是Popup型窗体。还会为窗体注册各种服务接口,如Menu,DropDrag等。

  6. Form在创建实体窗口时。会指定他的ParneWnd ApplicationHWND; ApplicationHWND会调用 PlatformWinCreateAppHandle来创建一个FMAppClass。这时,Application的真正的实体隐形窗口FMAppClass Window 就出生了。

  7. FM的消息循环,正是绑定FMAppClass窗体的句柄以及消息过程 WndPro 来实现的。

  8. Applicaion 会在创建第一个窗体时,将其指定 MainForm用户的交互操作,都是在 MainForm 上实现的。

 Application的消息循环:

  1. Application执行Run. 进入消息循环

  2. Run 会调用 App服务接口 AppService。当 然AppService又指向了PlatformWin. 执行PlatformWin Run.

  3.  PlatformWin.Run 中会执行 Application HandleMessage。当然又是通过AppService 回到执行 PlatformWin HandleMessage

  4. PlatformWinHandleMessage 才开始正式的消息循环。在这里,我们能看到熟悉的 PeekMessage, TranslateMessage DispatchMessage

  5. HandleMessage 通过消息处理过程 WndPro. 来过滤和分发所有消息

  6. 当得到 WM_QUIT或 FMAppClass 的 WM_CLOSE 时,退出消息循环,终止程序。

     

总结:

  1. Application创建 (没太多动作)

  2. Application初始化 (没动作)

  3. Application CreateForm 创建主窗体Form

  4. Form类执行 CreateHandle 创建窗口实体,将指定ParneWnd

  5. Form指定 ParneWnd时,创建 Application的影子窗体,

  6. 创建Application的影子窗体,并绑定消息过程 WndPro

  7. Application执行Run. 进入消息循环

  8. Run执行 PlatformWinHandleMessage

  9. HandleMessage执行WndPro 过滤与分发消息。

  10. 程序进入正常的消息过程。

  11. 处理消息,实现交互操作。

  12. 得到 WM_QUIT或 FMAppClass 的 WM_CLOSE 时,退出消息循环,终止程序。

 

消息分发机制分析:

  1. Screen对象会把Application CreatForm产生的Form全加入一个队列。

  2. 在消息循环中,根据消息句柄 hWnd Screen对象中找对应的Form。并将消息派发给Form。如果Form=nil,执行 Application的消息检测。侦测WM_CLOSEWM_DESTROY等消息,判断是否退出程序。

  3. 进行窗口消息过虑,响应 WM_LBUTTONDOWNWM_LBUTTONUPWM_MOUSEMOVEWM_MOUSEWHEEL 等鼠标消息WM_CHAR WM_KEYDOWNWM_KEYUP等按键消息。

  4. 对不同的消息作不同的处理,主要是鼠标与按键类的消息分发。执行相应的 MouseDown, MouseUp, KeyDown, KeyUp 事件。

 

    Form的鼠标消息处理:

    Form有三个私有对象 FCaptured, FFocusedFHovered

        FCaptured:记录按下时的鼠标移所在位置的对象。

        FHovered:记录正常(未按下时)的鼠标移所在位置的对象。

        FFocused:记录键盘焦点对象。

 

  1. 在消息体制内,From 通过ObjectAtPoint来确定消息对象Obj。并将对应的消息分发给Obj. MouseUpMouseDownMouseMove等,先取得鼠标位置的Obj.然后根据情况是否派发消息给Obj

  2. MouseUp,MouseDown 事件会先侦测 ObjDrag (拖曳事件) 执行BeginAutoDrag或终止 Drag。在没有Drag时,直接将消息分发给 Obj。这时FCaptured 对象在处理Drag起到了关键作用。

  3. Form在处理MouseMove时,会更新 Hovered。并触发原 HoveredMouseLeave和新的 Hovered(即当前ObjMouseEnter。这时再将MouseMove消息再派发给Obj

  4. CursorService 来负责鼠标形状的改变。

  5. 按键消息 KeyDown,KeyUp 是通过  Focused 派发。

  6. Captured是在鼠标按下时取得的对象。全程将鼠标消息派发给 Captured, 以确保按下后鼠标拖动时与MouseUp, MouseLeave消息处理。

 

总结:

  1. Run执行循环,并通过WndPro过滤,并分发消息

  2. 根据hWnd查找Form,将消息派发给Form

  3. 处理Form的消息,过滤消息,

  4. 处理FORM自身的消息,并响应相关事件。

  5. 派发相关消息 如鼠标,键盘等给Form里的Object

  6. 通过CapturedHoveredFocused 三个对象来细化消息处理

  7. Object响应,并执行相应的事件。实现交互操作。

 


学习: Delphi FireMonkey 结构性初略分析