XE6 FMX之控件绘制与显示

  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) + 1);
    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 <> 0 then
    try
      rgnStatus := GetUpdateRgn(Wnd, Region, False);
      if (rgnStatus = 2) or (rgnStatus = 3) then
      begin
        RegionSize := GetRegionData(Region, $FFFF, nil);
        if RegionSize > 0 then
        begin
          GetMem(RegionData, RegionSize);
          try
            RegionSize := GetRegionData(Region, RegionSize, RegionData);
            if RegionSize = RegionSize then
            begin
              SetLength(UpdateRects, RegionData.rdh.nCount);
              for i := 0 to RegionData.rdh.nCount - 1 do
              begin
                R := PRgnRects(@RegionData.buffer[0])[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) > 0) then
              begin
                // add update rects from FInPaintUpdateRects
                for I := 0 to High(InPaintUpdateRects) do
                begin
                  SetLength(UpdateRects, Length(UpdateRects) + 1);
                  UpdateRects[High(UpdateRects)] := InPaintUpdateRects[I];
                end;
              end;
              PaintControl.PaintRects(UpdateRects);
              if PlatformWin.FInPaintUpdateRects.TryGetValue(LForm.Handle, InPaintUpdateRects) and (Length(InPaintUpdateRects) > 0) then
              begin
                // paint second time - when Repaint called in painting
                PlatformWin.FInPaintUpdateRects.TryGetValue(LForm.Handle, UpdateRects);
                SetLength(InPaintUpdateRects, 0);
                PlatformWin.FInPaintUpdateRects.AddOrSetValue(LForm.Handle, InPaintUpdateRects);
                PaintControl.PaintRects(UpdateRects);
              end;
              PaintControl.ContextHandle := 0;
            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外观样式,然后绘制这个外观
时间: 2024-11-13 22:59:38

XE6 FMX之控件绘制与显示的相关文章

C# chart控件绘制曲线

在.NET中以前经常用GDI去绘制,虽然效果也不错,自从.NET 4.0开始,专门为绘制图表而生的Chart控件出现了,有了它,就可以轻松的绘制你所需要的曲线图.柱状图什么的了. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.C

控件绘制的四种方法

OWNER?DRAW实现自绘按钮 一准备工作 在您决定开发 Windows 提供的常规免费自定义控件范围之外的控件之后,您必需确定自己的控件将有多少独到之处 - 在功能和外观两方面.例如,我们假定您正在创建一个类似于计速表的控件.由于公共控件库 (ComCtrl32.dll) 中没有类似的控件,您完全需要自己进行以下操作:编写所有控件功能需要的代码,进行绘制,默认终端用户的交互,以及控件与其父窗口之间需要的任意消息处理. (#add 两方面,公共控件库中没有类似的 完全重写;? 只想调整公共控件

c# 通过.net自带的chart控件绘制饼图pie chart

c# 通过.net自带的chart控件绘制饼图pie chart 需要实现的目标是: 1.将数据绑定到pie的后台数据中,自动生成饼图. 2.生成的饼图有详细文字的说明. 具体的实现步骤: >>前台界面的设置: 1.设置chart1的属性Legends中默认的Legend1的Enable为false: 2.设置Series的ChartType为Pie 3.设置Series显示的文字内容(此处比较关键) 至此,前台的设置完成. >>下面填写后台代码: List<string&g

win32 sdk 列表视图控件绘制

[cpp] view plaincopy ////////////////////////////////////////////////////////////// LRESULT ListViewCustomDraw(HWND hwnd, LPARAM lParam) { LPNMHDR pnmh = (LPNMHDR) lParam; if (pnmh->code != NM_CUSTOMDRAW) return 0; LPNMLVCUSTOMDRAW lpNMCustomDraw = (

MFC9.0 Outlook控件的标题显示无法修改

这是我在开发中遇到的问题,现记录下来,以便帮助你们. 不想看废话的可以只看最后三行,但你会错过很多. 俗话说的好啊,"Wise men learn by other men's mistakes; fools by their own." -------------------------------------------分割线首次登场--------------------------------------------- 可能有的童鞋英语不太好,看不懂上面的东东,好吧我活跃下气氛

如何:使用TreeView控件实现树结构显示及快速查询

本文主要讲述如何通过使用TreeView控件来实现树结构的显示,以及树节点的快速查找功能.并针对通用树结构的数据结构存储进行一定的分析和设计.通过文本能够了解如何存储层次结构的数据库设计,如何快速使用TreeView控件生产树,以及如何快速查找树节点. 关键词:C# TreeView.树结构存储.树节点查找.层次结构 一.      概述: 树结构(层次结构)在项目的使用中特别常见,在不同项目中使用的控件可能不同(如:在Extjs中使用的是TreePanel控件,WinForm中可能用的是Tre

记录下UIButton的图文妙用和子控件的优先显示

UIButton的用处特别多,这里只记录下把按钮应用在图文显示的场景,和需要把图片作为按钮的背景图片显示场景: 另外记录下在父控件的子控件优先显示方法(控件置于最前面和置于最后面). 先上效果图: 1.当在某个地方既需要显示图片,还需要显示文字,另外还要有点击功能的时候,这时按钮是个很好的选择. 按钮中的图片和文字的距离可以自由调整,图片的也可以上下左右翻转.日常项目中像这些场景都是很容易碰到的. 按钮图文设置.图文位置移动.按钮中图片翻转示例代码: /** 测试图文并茂的按钮,图文移动 */

Android中使用shape来定义控件的一些显示属性

本人在美工方面一直是比较白痴的,对于一些颜色什么乱七八糟的非常头痛,但是在Android编程中这又是经常涉及到的东西,没办法,只有硬着头皮上. Android中常常使用shape来定义控件的一些显示属性,今天看了一些shape的使用,对shape有了大体的了解,稍作总结: 先看下面的代码:        <shape>            <!-- 实心 -->            <solid android:color="#ff9d77"/>

java 实现系统目录树 ,树控件使用,显示 系统文件夹

源码免积分下载: 源码下载地址:http://download.csdn.net/detail/u014112584/7343083 运行结果: java 实现系统目录树 ,树控件使用,显示 系统文件夹