看到Dev中的cxGrid组件的编辑器很强大,于是很想探究一下,跟踪cxGrid的代码比较麻烦,但原理大概知道一二.首先来研究一下设计器双击cxGrid弹出一个编辑窗体,选择窗体中的一个内容后,属性编辑器中的内容也随着变化.有了这个特性,对于控件中的一些复杂成员(继承于TPersistent类),如果需要对其进行设置,可以简化为在属性编辑器中进行.cxGrid的代码过于复杂,看看同样效果的标准控件TDataSet控件的字段编辑器的实现吧.

在Delphi的源码中找到DBReg单元,可以看到其注册的组件编辑器代码:

RegisterComponentEditor(TDataset, TDataSetEditor);//TDataSetEditor注册为TDataSet的组件编辑器,响应鼠标双击或右键事件

procedure TDataSetEditor.ExecuteVerb(Index: Integer);//双击或右键菜单触发
begin
  if Index = 0 then
    ShowFieldsEditor(Designer, TDataSet(Component), GetDSDesignerClass);
end;

继续跟踪ShowFieldsEditor函数:

procedure ShowFieldsEditor(Designer: IDesigner; ADataset: TDataset;
  DesignerClass: TDSDesignerClass);
var
  FieldsEditor: TFieldsEditor;
  vShared: Boolean;
begin
  FieldsEditor := CreateFieldsEditor(Designer, ADataSet, DesignerClass, vShared); //创建字段编辑器窗体
  if FieldsEditor <> nil then
    FieldsEditor.Show;//显示
end;

这个函数核心代码是对CreateFieldsEditor的调用,创建出字段编辑器窗体.跟踪进去看到:

function CreateFieldsEditor(Designer: IDesigner; ADataset: TDataset;
  DesignerClass: TDSDesignerClass; var Shared: Boolean): TFieldsEditor;
begin
  Shared := True;
  if ADataset.Designer <> nil then
  begin
    Result := (ADataset.Designer as TDSDesigner).FFieldsEditor;
  end
  else
  begin
    Result := TFieldsEditor.Create(Application);
    Result.DSDesignerClass := DesignerClass;
    Result.Designer := Designer;
    {Result.Form := Designer.Form;}
    Result.Dataset := ADataset;
    Shared := False;
  end;
end;

TFieldsEditor类是继承于TDesignWindow的编辑器类,TForm是其基类.F12键弹出设计界面,在FieldListBox控件的OnClick事件中,可见到如下代码:

procedure TFieldsEditor.AListBoxClick(Sender: TObject);
begin
  UpdateSelection;
end;

procedure TFieldsEditor.UpdateSelection;
var
  I: Integer;
  Field: TField;
  ComponentList: IDesignerSelections;
begin
  if Active then
  begin
    ComponentList := TDesignerSelections.Create;
    try
      with GetActiveListBox do
        for I := 0 to Items.Count - 1 do
          if Selected[I] then
          begin
            Field := TField(Items.Objects[I]){Dataset.FindField(Items[I])};
            if Field <> nil then ComponentList.Add(Field);
          end;
      if ComponentList.Count = 0 then ComponentList.Add(Dataset);
    except
      raise;
    end;
    Designer.SetSelections(ComponentList);
  end;
end;

看到目标了,最终实现属性编辑器进行切换效果的代码是Designer.SetSelections(ComponentList);我们只需要根据设计界面选择的内容,准备好需要在属性编辑器中显示的对象,添加到TDesignerSelections对象中,调用Designer.SetSelections即可.

另外跟踪一下属性编辑器的编辑框工作原理。
  Delphi的属性编辑器可以自动根据属性来调整编辑框的类别,如需要输入字符串的情况就生成一个Edit编辑框,而需要设置Boolean型的就生成CheckBox编辑框,对于集合类型的可以展开依次进行设置。这种方式比较灵活,而且运行我们定制控件的属性编辑器,指定编辑方式。
  Delphi没有公开源码,可以跟踪EControl的TInspectorList控件源码,其最终也是调用Delphi控件注册的PropertyEditor来模拟Delphi中操作方式。使用Spy++查看EControl的TInspectorList和Delphi的组件属性编辑器,其原理都是一样的,绘制出所有组件的属性图,左边是属性名称,右边是属性的值,从上到下依次绘制出所有的组件属性。根据鼠标点击的位置获取到鼠标下方属性的索引,从而得到对应的属性,并将属性编辑框移到对应的位置。根据控件的属性所注册的编辑器类型生成属性编辑框的外观,如文本输入框、下拉选择框、复选框等等。
  打开EControl的edcPropEdit单元,查看UpdateEditState函数,可看到如下代码:
    atr := GetAttributes;
    if F_ReadOnly then EditStyle := ieSimple
     else if (paValueList in atr) then EditStyle := iePickList
      else if (paDialog in atr) then EditStyle := ieEllipsis
       else EditStyle := ieSimple;
  看到属性的GetAttributes;方法调用了,这是我们下注册属性编辑器的时候指定属性编辑方式的,可以指定弹出对话框、下拉列表等等方式。这里根据注册的编辑方式修改了属性编辑框的外观,如果是paValueList则EditStyle设置为iePickList,属性编辑框的外观生成一个向下的黑色箭头;EditStyle为ieEllipsis的则在编辑框的后面绘制一个带...的按钮。
  请见ecExtEdit的PaintBtnGlyph函数:
procedure TCustomEditEx.PaintBtnGlyph(Canvas: TCanvas; Rect: TRect);
const
  LeftOffs = 3;
var
  Flags: Integer;
  W, G, I: Integer;
  DC: HDC;
begin
  DC := Canvas.Handle;
  Flags := 0;
  case FEditStyle of
   iePickList:
    begin
      if FActiveList = nil then Flags := DFCS_INACTIVE
      else if FPressed then Flags := DFCS_FLAT or DFCS_PUSHED;
      DrawFrameControl(DC, Rect, DFC_SCROLL, Flags or DFCS_SCROLLCOMBOBOX);  //绘制DFCS_SCROLLCOMBOBOX风格的向下箭头
    end;
   ieEllipsis:
    begin
      W := 2;
      G := (FButtonWidth - LeftOffs * 2 - 3 * W) div 2;
      if G <= 0 then G := 1;
      if G > 3 then G := 3;
      Flags := Rect.Left + (FButtonWidth - 3 * W - 2 * G) div 2 + Ord(FPressed);
      I := Rect.Top + (ClientHeight - W) div 2 {+ Ord(FPressed)};
      PatBlt(DC, Flags, I, W, W, BLACKNESS);
      PatBlt(DC, Flags + G + W, I, W, W, BLACKNESS);
      PatBlt(DC, Flags + 2 * G + 2 * W, I, W, W, BLACKNESS);
    end;
  end;
end;
  对于下拉框风格的属性编辑器来说,下拉框的值是如何填充的呢?打开edcPropEdit单元,查看DropDown函数:
procedure TCustomPropertyEdit.DropDown;
{$IFDEF EC_VCL9_UP}
var wp: IWideProperty10;
{$ENDIF}
begin
  if FPropertyEditor <> nil then
   begin
    PickList.Items.Clear;
    {$IFDEF EC_VCL9_UP}
    FWListItems.Clear;
    TWideStringList(FWListItems).Sorted := PickList.Sorted;
    if Supports(FPropertyEditor, IWideProperty10, wp) then
      wp.GetValues(GetWStr)
    else
    {$ENDIF}
      FPropertyEditor.GetValues(GetStr);
   end;
  inherited;
end;
  核心代码是FPropertyEditor.GetValues(GetStr); 其中的GetStr是一个函数名称,定义为:
procedure TCustomPropertyEdit.GetStr(const S: string);
begin
  PickList.Items.Add(S);
end;
  由属性编辑器在获取到值后自动调用GetStr将获取到的内容填充到PickList列表中.而属性编辑器是在UpdateEditor函数中进行设置的,鼠标在点击一个属性的时候会调用UpdateEditor函数。
---------------------
作者:henreash
来源:CSDN
原文:https://blog.csdn.net/henreash/article/details/7371897
版权声明:本文为博主原创文章,转载请附上博文链接!

Delphi组件编辑器的更多相关文章

  1. delphi 组件容器TObjectList代替List

    delphi 组件容器TObjectList代替List TObjectList objList->delete(0); 这个会释放第0行元素的对象 class TTabFormInfo { p ...

  2. Delphi组件开发教程指南目录

    用Delphi开发的时间也不短了!刚接触Delphi的时候,就觉得组件这个东西非常方便!根本不必知道组件内部的作为,只要使用就好了!然而用的多了,也不免会对各种delphi组件的内部实现方式产生兴趣! ...

  3. delphi 组件安装工具开发

    当一个组件的dpk文件数量较多且安装工具不顺手的时候,写一个属于自己的组件安装工具就很有必要了. 本例以 Dev Express 16.1.2 为例,设计一个组件安装工具,以便更深入理解 delphi ...

  4. 删除delphi组件TStringlist中的重复项目

    https://blog.csdn.net/ozhy111/article/details/87975663 删除delphi组件TStringlist中的重复项目 2019年02月27日 15:41 ...

  5. delphi 组件安装教程详解

    学习安装组件的最好方法,就是自己编写一个组件并安装一遍,然后就真正明白其中的原理了.   本例,编写了两个BPL, dclSimpleEdit.bpl 与 SimpleLabel.bpl ,其中,dc ...

  6. Delphi 组件渐进开发浅谈(二)——双简合璧

    2.双简合璧2.1.带有T[x]Label的T[x]Edit组件 请允许我用[x]的书写方式来表示不同的对象.因为随后将大量提及TLabeledEdit与TTntLabeledEdit.TCustom ...

  7. Delphi 组件渐进开发浅谈(一)——由简入繁

    最近业余时间在写游戏修改器玩,对于Delphi自带的组件总觉得差强人意,需要书写大量冗余代码,如果大量使用第三方组件,在以后的移植和与他人交互时也不是很方便,因此便产生了自己封装组件的想法. 实际上这 ...

  8. Delphi组件indy 10中IdTCPServer修正及SSL使用心得

    indy 10终于随着Delphi2005发布了,不过indy套件在我的印象中总是复杂并且BUG不断,说实话,不是看在他一整套组件的面子上,我还是喜欢VCL原生的Socket组件,简洁,清晰.Indy ...

  9. Delphi组件开发-在窗体标题栏添加按钮(使用MakeObjectInstance(NewWndProc),并处理好多消息)

    这是一个在窗体标题栏添加自定义按钮的组件(TTitleBarButton)开发实例,标题栏按钮组件TTitleBarButton以TComponent为直接继承对象,它是一个可以在窗体标题栏上显示按钮 ...

随机推荐

  1. PAT Basic 1092 最好吃的月饼 (20 分)

    月饼是久负盛名的中国传统糕点之一,自唐朝以来,已经发展出几百品种. 若想评比出一种“最好吃”的月饼,那势必在吃货界引发一场腥风血雨…… 在这里我们用数字说话,给出全国各地各种月饼的销量,要求你从中找出 ...

  2. uvalive 5905 Pool construction

    题目链接 题意: 有一个花园,有些地方是草地,有些地方是洞,现在要在这个花园中修建一个泳池,泳池全部由洞构成. 把洞变成草地需要花费一定的费用,把草地变成洞也需要花费一定的费用,并且泳池的边缘的草地上 ...

  3. 【AGC002 E】Candy Piles

    本来实在写不动这题 sol 了,但一想这是个经典的模型转化问题,于是就写了(.jpg) 题意 有一个序列 \(a_i\). 两人轮流操作,每次操作为二选一: 1. 把最大的 \(a_i\) 减成 \( ...

  4. VSCode远程连接Docker

    一.Docker开启远程访问 [root@local host ~]# vi /lib/systemd/system/docker.service #修改ExecStart这行 ExecStart=/ ...

  5. CF875F Royal Questions[最大生成基环树森林]

    这题这场比赛一堆人秒切..果然还是我太菜了吗 题意:二分图,右边$m$个点每个点$i$向左边有且仅有两条连边,边权都是$a_i$.求最大匹配. 一个朴素思想,二分图匹配,用贪心带匈牙利搞一搞,但是复杂 ...

  6. 高并发下的Nginx优化

    高并发下的Nginx优化 2014-08-08 13:30 mood Nginx    过去谈过一些关于Nginx的常见问题; 其中有一些是关于如何优化Nginx. 很多Nginx新用户是从Apach ...

  7. 四 java web考点

    一.GET和POST区别(参考Servlet&JSP学习笔记) <form>中method属性默认为GET. 1.使用POST的情况 GET跟随URL之后,请求参数长度有限,过长的 ...

  8. git 版本撤销,回退等

    git checkout -- <file>       #丢弃工作区的修改, 不要省略 -- ,这是只在工作区(work tree)修改了内容,还没有add 到暂存区,此时想撤销修改. ...

  9. cookbook 10.1生成随机密码

    任务: 随机地创造出新密码.比如,有时需要给新的用户账号分配新的密码. 解决方案: from random import choice import string def GenPasswd(leng ...

  10. Codeforces Gym Joyride(分层图,dijkstra)

    题意:有一张图,每条边有一个边权t表示经过所花时间,每个点有两个权t.p,分别表示经过该点所花时间和所花费用,要求找一条路径,从点1出发再回到点1,所花时间恰好为x且费用最小,输出其费用,找不到则输出 ...