├── 20190801_编程规范.md ├── 20190802_Delphi语法基础.md ├── Delphi过程类型函数指针详解.md ├── Delphi面向对象编程思想.md ├── Object Pascal.md ├── PMTreeList使用方法.md ├── README.md ├── RTTI.md ├── SVN账号密码.png ├── System中常用方法.md ├── TControl中鼠标事件到消息的转换过程.png ├── VCL主要架构.md ├── VCL主要架构.png ├── VCL架构.png ├── VMT结构图.png ├── Xml文件的使用.md ├── cxTreeList.md ├── delphi 消息.md ├── delphi中一些基类的使用方法.md ├── delphi中基础数据类型.png ├── delphi中的字符串详解.md ├── delphi主要基类及其派生类的关系.png ├── delphi判断文件是否在使用.md ├── delphi对象模型.md ├── delphi源代码分析.md ├── delphi看不懂的类型.md ├── delphi算法与数据结构.md ├── delphi精要.md ├── 一维动态字符数组的内存存储格式.png ├── 三维数组内存结构.png ├── 二维数组.png ├── 二维数组内存结构.png ├── 体检.md ├── 使用对象.md ├── 关系分析.md ├── 判断文件是否是本地文件.md ├── 参透Delphi.md ├── 各种库中的表结构.md ├── 基类架构.md ├── 多态.md ├── 字符指针与静态字符数组的内存布局.png ├── 对现在工作的理解.md ├── 对象占用空间.png ├── 对象,类,虚拟方法表,self.md ├── 封装.md ├── 工程造价结构图.png ├── 异常.md ├── 悟透Delphi.md ├── 投标导入流程.md ├── 投标导出流程.md ├── 招标文件导出流程.md ├── 招标文件导出流程2.md ├── 拼音缩写.md ├── 接口.md ├── 接口2.md ├── 数据类型.png ├── 新建dll1.png ├── 新建dll2.png ├── 新建dll3.png ├── 新建dll4.png ├── 新建dll流程.md ├── 新版本MDB的常用方法.md ├── 新版本XML的常用方法.md ├── 日常学习.md ├── 杭州地区的特点.md ├── 派生类内存布局.png ├── 深入核心VCL架构分析.md ├── 灵光一闪.md ├── 电子评标工作流程.md ├── 电子评标常用的单元.md ├── 电子评标笔记.md ├── 电子评标详解.md ├── 结构化存储.md ├── 继承.md ├── 编程理解.md ├── 胜算中一些功能的思考.md ├── 胜算中的响应逻辑.md ├── 胜算中的常用方法.md ├── 胜算中的常用类.md ├── 胜算中费率关系.md ├── 胜算中配置文件说明.md ├── 胜算工程和模版工具的关联.md ├── 胜算开发注意事件.md ├── 胜算待优化代码.md ├── 胜算新建工程.md ├── 胜算表结构.md ├── 虚拟方法表.png ├── 虚拟方法表1.png ├── 虚拟方法表2.png ├── 计价基础知识.md ├── 造价概念.md ├── 长字符串的内存存储格式.png └── 高手突破.md /20190801_编程规范.md: -------------------------------------------------------------------------------- 1 | ## 代码规范 2 | 3 | 2. 所有的代码缩进为2个空格。 4 | 3. Delphi中所有的关键字使用小写字母。 5 | 4. 函数或者方法名 6 | 5. 的开头需要大写。 7 | 6. 定义设置成员值的函数需要加**Set** 前缀。 8 | 7. 获得成员值的函数需要加**Get** 前缀。 9 | 8. 成员变量要以 **F** 开头,例如:**FSomeField** 。 10 | 9. 类的定义要以 **T** 开头,例如:**TSomeClass**。 11 | 10. 属性/方法中的命名方式:**SetSomeField**。 12 | 11. 不允许定义 **public** 和 **Published** 类型变量,必须以属性方式公开。 13 | 12. **Ctrl + Shift + G** 直接生成 **GUID**。 14 | 13. 接口的定义要用**I** 开头。 15 | 14. 指针类型必须以**P** 开头。 16 | 15. 结构/以及其他类型名都以**T** 开头。 17 | 16. 类当中的对象必须在**Create** 方法中创建,**Destroy** 方法中释放,针对某些特殊的对象由于参数原因等,可以推迟到初始化环节等地方创建,但必须要在**Destroy** 方法中释放(其他内存申请、释放方式类似)。 18 | 17. 函数值返回对象:推荐 19 | 20 | ## 命名规范 21 | 22 | 1. 窗体前需要加前缀,小写字母**frm** ,窗体的名称需和起功能相关联。如主窗体命名为**frmMain.dfm**。 23 | 24 | 2. 窗体的字体属性设置:宋体,小五。 25 | 26 | 3. 窗体的Position属性设置成:**poScreenCenter**。 27 | 28 | 4. 单元文件的都以小写字母**u** 开头,后接对应的窗体的名称。如主窗体对应的单元为**uFrmMain.pas**。 29 | 30 | 5. 数据模块窗体命名,加前缀**dm**,如**dmCustomers.dfm**。 31 | 32 | 6. 全局变量需在变量前加前缀**g**。 33 | 34 | 7. 标准的组件命名:如果控件名称由多个单词组成,那么就以多个单词的大写首字母来作为前缀。其他特殊的如下: 35 | 36 | **lbl** **TLabel** 37 | 38 | **edt** **TEdit** 39 | 40 | **mem** **TMemo** 41 | 42 | **btn** **TButton** 43 | 44 | **chk** **TCheckBox** 45 | 46 | **scb** **TScrollBar** 47 | 48 | **Pnl** **TPanel** 49 | 50 | **bbtn** **TBitBtn** 51 | 52 | **img** **TImage** 53 | 54 | **shp** **Tshape** 55 | 56 | **bvl** **TBevel** 57 | 58 | **sbx** **TScrollBox** 59 | 60 | **spl** **TSplitter** 61 | 62 | **stx** **TStaticText** 63 | 64 | **cht** **Tchart** 65 | 66 | ## 程序目录部署 67 | 68 | 每个单元文件的头部需要包含如下部分: 69 | 70 | ```cpp 71 | //**************************************************************** 72 | // Author: li liang wei 73 | // Create Date: 2019/08/01 74 | // Description 75 | // 改单元的详细说明 76 | // 77 | // Modify Lists 78 | // 重大功能点修改记录,需要填写的内容包含(修改人/修改时间/修改内容) 79 | //***************************************************************** 80 | ``` 81 | 82 | 动态库导出的方法或函数等,参数不能使用字符串类型string,必须使用PChar代替**string**类型。(*why*) 83 | 84 | ## 接口学习 85 | 86 | * 接口使用interface定义。 87 | 88 | * 所有接口直接或间接从**IUnknown** 继承。**IUnknown** 是所有接口类型的原始祖先,有着类概念中**TObject** 相同的地位。 89 | 90 | * 接口不能够创建实例,只有类才能够创建实例。 91 | 92 | * 一个类可以实现一个或多个接口。 93 | 94 | * 一般情况下,声明接口时需要一个能唯一标识改接口类型的GUID标识符。 95 | -------------------------------------------------------------------------------- /20190802_Delphi语法基础.md: -------------------------------------------------------------------------------- 1 | **shl** 按位左移 2 | 3 | **shr** 按位右移 4 | 5 | var 6 | 7 |     Value:Integer = 10; 8 | 9 | 这种初始化方法只能用于全程变量,不能用于过程或者方法的变量。 10 | 11 | 对于声明的常量,编译器有两种编译选择,一种是为常量分配内存,并把常量的值放入内存,第二种是在常量每次使用时复制常量值。第二种比较适合简单常量。 12 | 13 | 使用数组时,使用标准函数**Low** 和**High** 来检测它的边界,**Low** 和**High** 返回下标的下界和上界。 14 | 15 | Exit:退出过程或函数。 16 | 17 | abort:程序终止执行。 18 | 19 | ## 过程与函数 20 | 21 | 1. 引用参数,在参数前面使用**var** 关键字。 22 | 23 | 2. 常量参数,不允许在例程中给常量参数赋新值,因此编译器能优化常参的传递过程。 24 | 25 | 3. 开放数组参数,Pascal函数及过程的参数个数是预定的。如果参数个数预先没有确定,则需要通过开放数组来实现参数传递。一个开放数组参数就是一个固定类型开放数组的元素,也就是说,参数类型已定,但是数组中的元素个数是未知数。 26 | 27 | ```pascal 28 | function Sum(const A: array of Integer): Integer; 29 | var 30 | I: Integer; 31 | begin 32 | Result := 0; 33 | for I = Low(A) to High(A) do 34 | Result := Result + A[I]; 35 | end; 36 | ``` 37 | 38 | 4. 类型变化的开放数组参数,delphi允许定义类型变化的甚至无类型的开放数组。技术上,使用array of const 类型的数组就能实现把不同类型、不同个数元素组成的数组一下子传递给例程。例如 39 | 40 | ```pascal 41 | function Format(const Format: string; const Args: array of const): string; 42 | ``` 43 | 44 | 可以使用一下方式调用函数 45 | 46 | ```pascal 47 | var 48 | N := 20; 49 | S := 'Total'; 50 | Label1.Caption := Format('Totla:%d',[N]); 51 | Label2.Caption := Format('Int:%d, Float:%f',[N,12.4]); 52 | Label2.Caption := Format('%s%d',[S,N * 2]); 53 | ``` 54 | 55 | ## 字符串类型 56 | 57 | 1. delphi字符串与WindowsPChar字符串 58 | 2. ShortString就是传统的Pascal字符串类型,这类字符串是一个字符串序列,序列的头部是一个长度字节,指示当前字符串的长度,由于只用一个字节来表示字符串的长度,所以字符串不能超过255个字符,其中每一个字符属于ANSIChar类型(标准字符类型)。 59 | 3. ANSIChar常字符串类型就是新增的可变长字符串类型,这类字符串的内存动态分配,引用计数,并使用了更新前拷贝(copy-on-write)计数。其字符类型也是ANSIChar类型。 60 | 4. WideString长字符串类型与ANSIString类型相似,只是它基于WideChar字符类型,WideChar字符为双字节Unicode字符。 61 | 62 | ## 使用长字符串 63 | 64 | 1. 如果只简单使用String定义字符串,那么该字符串可能时短字符串也可能时ANSI长字符串,这取决于**$H** 编译指令的值,**$H+** (缺省)代表长字符串(ANSIString)。长字符串是delphi库中看空间使用的字符串。 65 | 66 | 2. 可以使用**SetLength(Str, 200)** 来预设字符串最大长度。**SetLength** 过程只是完成一个内存请求,并没有实际分配内存。 67 | 68 | 3. 长字符串为零终止串,这以为这长字符串完全与Windows使用的C语言零终止串兼容。所以当需要把字符串传递给Windows API函数时,可以直接把长字符串映射给PChar类型。 69 | 70 | 4. Window句柄,是delphi从Windows中引入的数据类型,数据类型名为THandle,该类型再Windows单元中定义为: 71 | 72 | ```pascal 73 | type 74 | THandle = LongWord; 75 | ``` 76 | 77 | 句柄类型通过数字实现,但并不当数字用。 78 | 79 | 5. 单元 80 | 81 | ```pascal 82 | unit Unit1; 83 | 84 | interface 85 | 86 | uses 87 | 88 | type 89 | 90 | const 91 | Zero = 0; 92 | var 93 | Total: integer; 94 | procedure MyProc; 95 | 96 | implementation 97 | 98 | uses 99 | 100 | var 101 | 102 | procedure MyProc; 103 | begin 104 | //code 105 | end 106 | ``` 107 | 108 | 6. 指针操作,符号@和^。@符号用于取得一个变量的地址,^用来取得一个指针对应的数据,^符号使用在类型前可以声明指针变量。 109 | 110 | ```pascal 111 | type 112 | PInteger = ^Integer; 113 | var 114 | I,J: Integer; 115 | PI: PInteger; 116 | begin 117 | I := 5; 118 | PI := @I; 119 | J := PI^; 120 | end; 121 | ``` 122 | 123 | Pascal中除了PChar类型一般不对指针直接作加、减运算。 124 | 125 | 使用**Addr** 可以代替@符号取得变量地址。 126 | 127 | ```pascal 128 | PI := Addr(I); 129 | ``` 130 | 131 | 判断一个指针是否有指向,可以使用**nil** 和**Assigned** 来判断。 132 | 133 | ```pascal 134 | if P <> nil then 135 | if Assigned(P) then 136 | ``` 137 | 138 | ## 类型映射 139 | 140 | 当需要把一个变量赋值给另一个不同类型的变量,有两种选择,第一种方法就是类型映射(Typecasting),它使用一个带有目标数据类型名的函数符号: 141 | 142 | ```pascal 143 | var 144 | N: Integer; 145 | C: Char; 146 | B: Boolean; 147 | begin 148 | N := Integer('X'); 149 | C := Char(N); 150 | B := Boolean(0); 151 | end; 152 | ``` 153 | 154 | ## delphi 调用协定 155 | 156 | 调用Windows API 函数。 157 | 158 | Win32 API函数必须声明使用stdcall调用协定。 159 | 160 | ## 异常 161 | 162 | ​ 异常是一种特殊的对象。任何类的实例都可以作为异常对象。但通常从**SysUtils** 单元的Exception类来派生异常。 163 | 164 | 异常类的声明 165 | 166 | ```pascal 167 | type 168 | EMathError = class(Exception) 169 | ErrorCode: Integer; 170 | ErrorInfo: String; 171 | end; 172 | ``` 173 | 174 | ​ 异常是一个对象,那么在引发异常的时候需要创建这个异常对象。所谓抛出异常就是用raise语句调用异常类的构造函数 175 | 176 | ```pascal 177 | raise EMathError.create; 178 | ``` 179 | 180 | ​ 异常对象会在异常处理后自动销毁,不能够手动销毁异常对象。 181 | 182 | ### 捕捉异常 183 | 184 | ​ Delphi中有try……except和try……finally。 185 | 186 | ## With语句 187 | 188 | ​ with语句是一种用于简化代码对的语句。如果要访问一个**记录类型变量(或一个对象)**,用with语句就不必每次重复变量的名字。 189 | 190 | ```pascal 191 | Form1 := TForm1.create(nil); 192 | try 193 | if (Form1.ShowModal = mrOK) then 194 | //必要的程序编码写在这里 195 | finally 196 | form1.Free; 197 | end; 198 | ``` 199 | 200 | ## Format的用法 201 | 202 | ```pascal 203 | Format('%d, %d', [Sizeof(A), Sizeof(B)]); 204 | ``` 205 | 206 | ## Delphi 中系统内置函数 207 | 208 | ```pascal 209 | var 210 | I: Integer; 211 | B: Integer; 212 | begin 213 | i := 1; 214 | Inc(i); // i = 2; 215 | Dec(i); // i = 1 216 | // 取有序值的后继数 217 | B := Succ(i) // B = 2; 218 | // 取有序值的前驱数 219 | B := Pred(i) // B = 0 220 | end; 221 | ``` 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /Delphi过程类型函数指针详解.md: -------------------------------------------------------------------------------- 1 | # delphi过程类型详解 2 | 3 | | | 函数 | 函数指针 | 4 | | ---- | ------------------------------------------------- | ------------------------------------------------------------ | 5 | | 声明 | 一段代码的首地址 | 一个存储指向过程或函数地址的指针变量 | 6 | | 使用 | 对首地址的引用 | 对该过程或地址的引用 | 7 | | 总结 | 在获取函数地址的时候,使用@函数名可以获得函数地址 | 使用@Func可以获得函数地址,@@Func获得函数指针地址,使用上和函数名统一。 | 8 | 9 | ## 参考C++ 10 | 11 | 1. 在delphi中,除了类引用外,没有其他的显示定义引用的方法。这点与C++不同,在C++中引用就是指针,但是在操作时不需要使用指针运算符。之所以可以这样使用,是因为引用相当于某个变量或值的别名,它没有引入任何新的存储对象,对引用的操作由编译器直接映射到该变量或值。 12 | 13 | ```c++ 14 | int n = 100; 15 | int& nRef = n; 16 | nRef = 630; 17 | ``` 18 | 19 | 2. 在上面的代码中,nRef本身并不占用任何存储空间,编译器会使标识符nRef在程序运行时映射(联编)到n上,因而, 所有对nRef的操作都是对n的操作,所以,在运行玩第三行代码后,n的值时630. 20 | 21 | ## delphi 中的引用 22 | 23 | 1. 在delphi中,可以认为过程类型既是指针,又是引用,或者跟严格的说,它事实上是对指向过程或函数地址的指针变量的引用。 24 | 25 | 2. 声明一个过程变量G,实际上就是声明了一个名为‘@G’的指针变量,该指针变量占用32位的存储空间,它指向过程或函数地址。而G标识对该指针变量的引用,因而G相当于该指针变量的别名,它本身并不占用任何存储空间,因此,下述代码 26 | 27 | ```pascal 28 | G := Func; 29 | ``` 30 | 31 | 将使得指针变量"@G"指向Func地址"@Func"。 32 | 33 | 3. 当使用"@G"的时候,总是获得它所对应的过程或函数的地址"@Func"(它保存在指针变量"@G"中)。使用"@(@G)"则可获得指针变量"@G"自身的地址。因为"@"操作符的右结合特性,"@(@G)"自然等价于"@@G"。 34 | 35 | 4. 另外,"@"操作符还可以用于将无型指针变量指派给过程变量,例如下述代码 36 | 37 | ```pascal 38 | procedure InitDriveSpacePtr; 39 | var 40 | Kernel: THandle; 41 | begin 42 | Kernel := GetModuleHandle(Windows.Kernel32); 43 | if Kernel <> 0 then 44 | @GetDiskFreeSpaceEx := GetProAddress(Kernel, 'GetDiskFreeSpaceExA'); 45 | if not Assigned(GetDiskFreeSpaceEx) then 46 | GetDiskFreeSpaceEx := @BackfillGetDiskFreeSpaceEx; // 这个"@"可有可无,有可以增强代码阅读性 47 | end; 48 | ``` 49 | 50 | 5. 任何一个过程变量都可以取nil值,为nil值的过程变量在调用的时候是会报错的,所以在使用的时候可以用Assigned函数进行测试。 51 | 52 | ```pascal 53 | if Assigned(F) then Return := F(3, 4); 54 | ``` 55 | 56 | ## 过程类型的使用 57 | 58 | 1. 声明了过程类型和过程变量后,就可以在赋值语句和表达式中使用该过程变量。 59 | 60 | 2. 过程类型实际上时过程指针,它指向过程或函数的首地址 61 | 62 | 3. 声明一个过程变量Func实际上就是声明一个过程类型的指针变量Func。 63 | 64 | ```pascal 65 | Func := Calc;// Func指向内存中函数Calc的首地址 66 | ``` 67 | 68 | 4. 当过程变量出现在赋值语句的左边时,赋值语句右边要给出相应的过程类型的值。此时,左边的过程变量获得右边过程或函数的首地址,该过程变量作为一个指针指向右边的过程或函数。 69 | 70 | 5. 过程变量获得具体值之后,就可以在表达式中使用或作为实际参数传递给其他子程序。 71 | 72 | ```pascal 73 | var 74 | F: function(X: integer): integer; 75 | Return: Integer; 76 | 77 | function Func(X: integer): integer; 78 | begin 79 | Result := X * X; 80 | end; 81 | 82 | procedure Button1Click(Sender: TObject); 83 | begin 84 | F := Func; 85 | Return := F(4); // 过程变量的使用 86 | end; 87 | 88 | // 过程变量之间也可以赋值 89 | var 90 | F, G: function: Integer; 91 | i: integer; 92 | function Func: integer; 93 | begin 94 | //Do Something 95 | end; 96 | 97 | procedure Button1Click(Sender: TObject); 98 | label 1, 2, 3; 99 | begin 100 | Caption := IntToStr(Integer(@Func)); 101 | 1: F := Func; // 将函数Func指派给过程变量F 102 | Caption := Caption + ',' + IntToStr(Integer(@F)); 103 | 2: G := F; // 将过程变量F对应的函数指派给过程变量G 104 | Caption := Caption + ', ' + IntToStr(Integer(@G)); 105 | 3: I := G; // 调用函数Func 106 | end; 107 | 108 | // 需要注意的是,过程变量的赋值不能使用括号,因此下面的语句是错误的; 109 | F := Func(); 110 | G := F(); 111 | 112 | // 下面的语句也是错误的。 113 | var 114 | F, G: function(X: integer): integer; 115 | i: integer; 116 | 117 | function Func(X: integer): integer; 118 | begin 119 | // Do something 120 | end; 121 | 122 | procedure TForm1.Button1Click(Sender: TObject); 123 | begin 124 | F := Func(); // 错误,没有声明无参类型函数Func,而且函数返回值与F类型不匹配 125 | F := Func(I); // 错误,函数返回值与F类型不匹配。 126 | F := Func; // 正确 127 | G := F(I); // 错误,函数F返回值(integer)与G类型(procedure)不匹配。 128 | end; 129 | 130 | // 从上面的语句中可以看出,书写规范会使得程序的代码更加清晰。当调用无参类型过程或者函数的时候,都是用一个空括号是一个好习惯,这使得我们一眼就能看出是过程或者函数调用,而不是过程变量的复制。 131 | 132 | // 当表达式中出现过程变量时,过程变量就表示对它指向函数的调用。(比较特殊的是判等表达式"=") 133 | var 134 | F: function(x, y: Integer): integer; 135 | G: function: integer; 136 | Return: integer; 137 | 138 | function Func1(x, y: integer;): integer; 139 | begin 140 | Func1 := X * X + Y * Y; 141 | end; 142 | 143 | function Func2: Integer; 144 | begin 145 | Func2 := 10 * 10; 146 | end; 147 | 148 | procedure TForm1.Button1Click(Sender: TObject); 149 | begin 150 | F := Func1; 151 | G := Func2; 152 | Return := F(3, 4) + G; 153 | end; 154 | 155 | // 下面的语句可以看出 156 | G = Func2; // True 157 | G() = Func2(); //True 158 | 159 | // 如果要将过程变量的值(地址)和函数 过程值(函数或过程的地址)进行比较,应该使用"@"操作符,例如 160 | if @G = @Func2 then // True 161 | 162 | // @G 将G转换为一个包含地址值的无型指针变量,而"@Func2"返回Func函数的首地址。 163 | // 如果要获得一个过程变量本身所占用的实际内存地址,而不是它所存储的某个过程或函数的地址,要用"@@"来获得该变量的地址。 164 | ``` 165 | 166 | -------------------------------------------------------------------------------- /Delphi面向对象编程思想.md: -------------------------------------------------------------------------------- 1 | 类型判断操作符:is 2 | 3 | 对象变量 is 类名; 4 | 5 | Windows程序设计中用此判断控件类型 6 | 7 | ```pascal 8 | procedure TForm1.ListBox1 9 | ``` 10 | 11 | ## 接口变量的本质(自己的思考) 12 | 13 | 接口变量是一个存储这个接口变量类型的变量(也就是实现该接口的类声明的对象)的指针,因为是一个指针,所以它有这指针的特性。通过该接口变量可以快速调用对象的方法而不用考虑该对象具体是什么,当看到使用接口的时候,就要快速将该接口映射到该对象。 14 | 15 | -------------------------------------------------------------------------------- /Object Pascal.md: -------------------------------------------------------------------------------- 1 | # 数据类型及其相互关系 2 | 3 | ## 1.1数据类型概述 4 | 5 | | Simple | | | 6 | | ------ | ----------------- | ------------------------------------------------------------ | 7 | | | Ordinal(有序类型) | | 8 | | | | Integer(integer, Cardinal(无符号32位), Shortint, Smallint……) | 9 | | | | Character(char, AnsiChar, WideChar) | 10 | | | | Boolean(Boolean, ByteBool, WordBool, LongBool) | 11 | | | | Enumerated | 12 | | | | Subrange | 13 | | | Real | | 14 | | | | Real | 15 | | | | Single | 16 | | | | Double | 17 | | | | Extended | 18 | | | | | 19 | | | | | 20 | | | | | 21 | | | | | 22 | 23 | ![](数据类型.png) 24 | 25 | 在delphi中一般使用单字节来存储字符,而widestring使用2个字节来存储字符。 26 | 27 | 使用短字符串,采用**String[MaxLength]** 的方法来定义,而不是用ShortString。 28 | 29 | **PChar** 不是Pascal 标准的数据类型,**PChar** 声明一个以空字符串(Null)结尾的字符串的指针。 30 | 31 | PChar可以像String一样使用。 32 | 33 | 一个指针占用**4** 个字节的空间,或者说,一个指针就是**4** 个字节大小的内存块。 34 | 35 | 每个类的类名称是自己的**ClassName** 属性,查看自己的类名称的方法 36 | 37 | ```pascal 38 | procedure TForm1.Button1Click(Sender: TObject); 39 | type 40 | PObject = ^TObject; 41 | var 42 | PObj: PObject; 43 | begin 44 | PObj := @Self; 45 | ShowMessage(PObj^.ClassName); 46 | end; 47 | ``` 48 | 49 | 6. Procedural(过程)类型 50 | 51 | **OnClick** 是一个事件,事件其实是一种特殊的属性。下面是这种属性的定义。 52 | 53 | ```pascal 54 | property OnClick: TNotifyEvent 55 | ``` 56 | 57 | 也就是说,OnClick 是**TNotifyEvent** 类型的属性,**TNotifyEvent** 的定义如下: 58 | 59 | ```pascal 60 | type TNotifyEvent = procedure(Sender: TObject) of object; 61 | ``` 62 | 63 | 因为有**of object** 关键字,所以**TNotifyEvent** 是对象的方法 64 | 65 | 实际上,有**of object** 的过程除了指向过程地址的指针外,还有一个附带的指针——指向所属的对象。因此,普通的过程类型不能和有**of object** 的过程类型相互赋值。 66 | 67 | 7. Variant(可变)类型 68 | 69 | 关于Variant我们需要理解一下几点: 70 | 71 | (1)**Variant** 可以存储绝大部分不同的数据类型,但是指针类型数据只能用**PVariant** 来存储。 72 | 73 | (2)**Variant** 变量在某时刻有三个可能的状态,**Unassigned** 表示没有值,可以用VarIsEmpty来测试。**Null** 值,可以用VarIsNull测试,和非Null值。声明一个**Variant** 变量后,他被置为**Unassigned** 状态。 74 | 75 | (3)可以使用VarType来判断**Variant** 变量来判断存储数据的实际类型。VarType返回一个Word类型数据,System单元中定义了一些常数来代表这个返回值,如varEmpty,varNull,varInteger等。 76 | 77 | **Variant** 的扩展类型**OleVariant** 。**Variant** 类型的数据只能在同一个应用中传递,当需要在不用应用程序、不同计算机间传送**Variant** 数据的时候,需要使用**OleVariant** 。 78 | 79 | 8. 类型别名 80 | 81 | (1) 82 | 83 | ```pascal 84 | type 85 | DWORD = LongWord; 86 | ``` 87 | 88 | (2) 89 | 90 | ```pascal 91 | type 92 | HWND = type LongWord; 93 | ``` 94 | 95 | 以上就是定义类型别名的两种方法。 96 | 97 | 第一种类型在任何时候在DWORD和LongWord都是兼容的,因为他们实际是上同样的类型,只是名字不同而已。 98 | 99 | 第二种方法定义一个LongWord类型的新别名HWND,这个时候他们HWND和LongWord是两个完全不同的类型,在简单赋值的时候,编译器认为两个是兼容的,但是在用于var和Out参数等要求类型严格匹配的时候,则被认为不兼容。 100 | 101 | ## 1.2 变量的内存分配和释放 102 | 103 | 函数或者过程内部定义的变量为局部变量;其他的变量被声明在interface和implementation部分称为全局变量。 104 | 105 | 如果变量时非指针类型的,则声明后被自动分配内存。如果是全局变量,还会被初始化为0。 106 | 107 | 指针类型的变量不会自动分配内存,如果是全局的指针会初始化值为nil,表示还没有指向的。 108 | 109 | 无论是全局变量还是局部变量,非指针类型的变量时被自动分配的,这个工作由编译器编译时完成,所以这种分配方式成为静态分配。在静态分配时,全局变量的内存分配在全局变量区,局部变量分配在应用程序栈(**Stack** )。他们的内存释放工作也被自动管理。 110 | 111 | 应用程序可用的内存区分为三类,全局变量区(专门用来存放全局变量)、栈(Stack)和堆(Heap)。应用程序开始运行时,所有全局变量的内存被分配到全局变量区,应用程序结束时被释放,被分配到栈上的变量内存呗栈管理器自动释放,堆上的变量内存必须人工释放。 112 | 113 | 1. 指针类型分配内存的方法 114 | 115 | ```pascal 116 | var 117 | P1, P2: PChar 118 | begin 119 | P1 := 'liliangwei'; 120 | P2 := P1; 121 | end; 122 | ``` 123 | 124 | 2. 类类型,调用构造函数分配内存 125 | 126 | ```pascal 127 | var 128 | Obj: TObject; 129 | begin 130 | Obj := TObject.Create;{调用构造函数创建对象,变量Obj指向该对象} 131 | Obj.Free;{释放内存,Free内部调用析构函数Destroy;也可以使用 FreeAndNil(Obj);} 132 | end; 133 | ``` 134 | 135 | 对象变量指向的内存必须人工释放,因为这块内存被分配到堆而不是栈上。 136 | 137 | 3. 分配指定大小的内存块。主要用于创建缓冲区,一些函数和过程通过缓冲区返回一些执行结果。比如文件读写,流读写以及大量的API函数。下面是一个API函数使用缓冲区的例子,该函数可以获得计算机名字。 138 | 139 | ```pascal 140 | var 141 | P: PChar; 142 | Size: Cardinal; 143 | begin 144 | Size := MAX_COMPUTERNAME_LENGTH + 1; 145 | GetMem(P, Size);{分配Size个字节的内存块(即缓冲区),并让P指向它} 146 | GetComputerName(P, Size);{API函数GetComputerName将取得的计算机名放在P中}} 147 | ShowMessage(P); 148 | FreeMen(P);{释放缓冲区占用内存} 149 | end; 150 | ``` 151 | 152 | 动态分配内存的函数,他们都是在堆中分配内存,所以必须释放: 153 | 154 | ```pascal 155 | procedure GetMem(var P: Pointer; Size: Integer); 156 | ``` 157 | 158 | 分配Size字节的内存块,并让P指向它。 159 | 160 | ```pascal 161 | function AllocMem(Size: Cardinal): Pointer; 162 | ``` 163 | 164 | 分配大小为Size字节的内存块并初始化为零,并返回地址指针。 165 | 166 | 如果希望在中途改变先前使用GetMEM或者AllocMem分配的内存大小,可以使用ReallocMen: 167 | 168 | ```pascal 169 | procedure ReallocMem(var P: Pointer; Size: Integer); 170 | ``` 171 | 172 | 使用GetMem和AllocMem分配的内存都应该用FreeMem释放: 173 | 174 | ```pascal 175 | procedure FreeMem(var P: Pointer); 176 | ``` 177 | 178 | 用New分配的内存块大小由参数P的类型确定,因此,不要使用它给无类型指针(即Pointer类型)变量分配内存,释放该内存块时使用Dispose: 179 | 180 | ```pascal 181 | procedure Dispose(var P: Pointer); 182 | ``` 183 | 184 | 185 | ## 1.3 内存数据结构 186 | 187 | 1. String变量实际上是一个指针,指向第一个字符所在位置。 188 | 189 | 2. String类型包含4个域 190 | 191 | 偏移/Byte 内容 192 | 193 | -8 存储计数引用 194 | 195 | -4 存储字符长度 196 | 197 | 0..Length - 1 存储实际字符 198 | 199 | Length 零字符(NULL或者#0) 200 | 201 | 3. ANSIString和WideString使用4个Byte(即32Bits)来存储字符长度,而32Bits所能表示的最大整数是21亿,多以最大字符长度位2GB。 202 | 203 | 4. Variant内部存储为TVarDate类型的记录。TVarData被定义在System单元。该记录主要包括两个字段 204 | 205 | (1)VType(TVarType类型),它用来存储数据类型。 206 | 207 | (2)另一个字段为8字节大小,用来存储实际数据或者指向该实际数据的指针 208 | 209 | ## 1.4 强数据类型和类型转化 210 | 211 | 1. typecasting(类型强制转化):typeIdentifier(expression) 212 | 213 | ```pascal 214 | var 215 | B: Boolean; 216 | begin 217 | B := Boolean(1); 218 | end; 219 | ``` 220 | 221 | 对于对象和接口,除了使用typeIdentitier外,还可以使用as操作符进行转化,在使用as前应该首先判断源对象和结构类型是否兼容、源对象/原接口是否支持结果接口,可分别使用is操作符和Support函数来做这种判断。 222 | 223 | 2. variant parts in records(变体记录) 224 | 225 | 定义如下 226 | 227 | ```pascal 228 | type recordTypeName = record 229 | fieldList1: type1; 230 | …… 231 | fieldListn: typen; 232 | case tag: ordinalType of 233 | constantList1: (variant1); 234 | …… 235 | constantListn: (variantn); 236 | end; 237 | ``` 238 | 239 | 其中case 到结尾部定义了多个变体字段。所有的变体字段共享一段内存,换句话说,如果对constangList1赋值,哪呢constantList2-constantListn也被赋了值。至于这些字段返回什么值,则是由他们的类型决定的。程序根据tag中的值决定应用constantList1-constantListn中的那个字段。例如: 240 | 241 | ```pascal 242 | type 243 | TDateConv = record 244 | case T: Boolean of 245 | True: (I: Byte); 246 | False: (B: Boolean); 247 | end; 248 | 249 | var 250 | D: TDataConv; 251 | begin 252 | D.I := 1; 253 | end; 254 | ``` 255 | 256 | 257 | ## 1.5 过程和函数 258 | 259 | 1. 当形参是指针类型和引用类型的区别。 260 | 261 | 引用类型,形参和实参是同一个变量,因此能够实现完全的共享。 262 | 263 | 指针类型,两个指针变量存储的是同一个地址,是两个变量。 264 | 265 | ```pascal 266 | procedure TForm1.ByVal(Obj: TObject) 267 | begin 268 | Obj := Button1; 269 | {改变形参指针指向,实参的指针指向不会改变,因为他们是两个变量} 270 | end; 271 | ``` 272 | 273 | ```pascal 274 | procedure TForm1.ByRef(); 275 | begin 276 | Obj := Button1; 277 | {改变形参指针指向,实参的指针指向会跟着改变,因为他们是同一个变量} 278 | end; 279 | ``` 280 | 281 | 2. 无类型参数 282 | 283 | 声明时没有指定数据类型的参数成为无类型参数。 284 | 285 | 无类型参数必须加const、out或var前缀;无类型参数不能指定默认值。 286 | 287 | ## 1.6 声明指令 288 | 289 | 1. 声明一个过程,可以使用register、pascal、cdecl、stdcall和safecall指令来指定参数传递顺序和参数内存管理方式,从而影响过程运作。如: 290 | 291 | ```pascal 292 | function MyFunction(X, Y: Integer): Integer; cdecl; 293 | ``` 294 | 295 | | 指令 | 参数存放位置 | 参数传递顺序 | 参数内存管理 | 使用地点 | 296 | | -------- | ------------ | ------------ | ------------ | --------------------------------------- | 297 | | register | CPU寄存器 | 从左到右 | 被调用者 | 默认。publlished属性存放方法必须使用 | 298 | | pascal | 栈 | 从左到右 | 被调用者 | 向后兼容,不再使用 | 299 | | cdecl | 栈 | 从右到左 | 调用者 | 调用c/c++共享库 | 300 | | stdcall | 栈 | 从右到左 | 被调用者 | API调用,如回调函数 | 301 | | safecall | 栈 | 从右到左 | 被调用者 | API调用,如回调函数。双接口方法必须使用 | 302 | 303 | ## 1.7 深入理解方法 304 | 305 | 1. 从调用者的角度,可以分为普通方法和类方法。类方法在方法前面有class关键字 306 | 307 | ```pascal 308 | class procedure ClassProc 309 | ``` 310 | 311 | 构造方法也是一个类方法。 312 | 313 | 类方法可以直接被类调用,就算对象没有被创建。对象也可以直接调用类方法。普通方法只能被对象调用。 314 | 315 | 类方法是从C++的static函数借鉴来的。、 316 | 317 | 实现一个类方法时,不要让他依赖任何实例信息,不要在类方法中存取字段、属性和普通方法。 318 | 319 | 2. 从调用机制上,分为静态方法、虚方法和抽象方法。 320 | 321 | 静态方法的定义方式: 322 | 323 | ```pascal 324 | TOneObject = class 325 | procedure OneProc; 326 | end; 327 | ``` 328 | 329 | 没有关键词修饰的方法被默认为静态方法。静态方法它的地址是编译时确定,运行时映射。 330 | 331 | 虚方法的定义方式: 332 | 333 | ```pascal 334 | TOneObject = class 335 | procedure OneProc; virtual; 336 | function OneFun: boolean; dynamic 337 | ``` 338 | 339 | 虚方法使用virtual或者dynamic声明。 340 | 341 | 虚方法可以在子类中覆盖,覆盖需要使用override关键字。 342 | 343 | ```pascal 344 | TParent = class 345 | procedure OneProc; virtual; 346 | function OneFun: boolean; dynamic; 347 | end; 348 | 349 | TChild = class(TParent) 350 | procedure OneProc; override; 351 | function OneFun: boolean; override; 352 | end; 353 | 354 | procedure TParent.OneProc; 355 | begin 356 | ShowMessage('TParent'); 357 | end; 358 | 359 | function TParent.OneFun: boolean 360 | begin 361 | Result := false; 362 | end; 363 | 364 | procedure TChile.OneProc; 365 | begin 366 | inheriter;//inheriter调用父类的OneProc的代码,这句的结果显示'TParent' 367 | ShowMessage('TChild'); 368 | end; 369 | 370 | function TChild.OneFun: boolean; 371 | begin 372 | Result := inherited OneFun;//调用父类的OneFun代码 373 | if not Result then 374 | Result := True; 375 | end; 376 | ``` 377 | 378 | 使用不同关键字声明的虚方法是有区别的。virtual声明的成为虚拟方法,dynamic声明的称为动态方法。 379 | 380 | 抽象方法,是虚方法的特例,在虚方法声明后加上abstract关键字构成,如: 381 | 382 | ```pascal 383 | TParent = class 384 | procedure OneProc; virtual; abstract; 385 | function OneFun: boolean; dynamic; abstract; 386 | end; 387 | ``` 388 | 389 | 抽象方法和普通虚方法的区别: 390 | 391 | a. 抽象方法只有声明,没有实现。 392 | 393 | b. 抽象方法不许再之类中覆盖并实现后调用。因为没有实现的方法不能被分配实际地址,所以抽象方法可以被称为纯虚方法。 394 | 395 | 如果一个类中含有抽象方法,那么这个类就成了抽象类。例如TStrings含有: 396 | 397 | ```pascal 398 | procedure Clear; virtual; abstract; 399 | procedure Delete(Index: Integer); virtual; abstract; 400 | ``` 401 | 402 | 等过个抽象方法。 403 | 404 | 抽象类是不应该直接用来创建实例的,因为一旦调用了抽象方法,将抛出地址异常。 405 | 406 | 因此,抽象类一般是中间类,实际使用的重视覆盖实现了抽象方法的子类。比如常用的字符串列表类TString,我们中使用他的子类而不是本身来构造实例,如: 407 | 408 | ```pascal 409 | var 410 | Strs: TStrings; 411 | begin 412 | Strs := TStringList.Create; 413 | end; 414 | ``` 415 | 416 | 3. 从用途来分: 417 | 418 | 重载方法。用overload关键字来指明。 419 | 420 | ```pascal 421 | TParent = class 422 | procedure OneProc; overload; 423 | function OneProc(S: string): boolean; overload 424 | ``` 425 | 426 | 如果位于相同类中,都必须加上overload关键字,如果分别在父类和子类中,那么父类的方法可以不加overload而子类必须加overload。 427 | 428 | 如果父类方法是虚的,那么在子类中重载该方法时应该加上reintroduce修饰字。 429 | 430 | ```pascal 431 | TParent = class 432 | procedure OneProc; virtual; 433 | end; 434 | 435 | TChild = class(TParent) 436 | procedure OneProc; reintrodure; overload; 437 | end; 438 | ``` 439 | 440 | 在published区不能出现多个相同的重载方法。例如: 441 | 442 | ```pascal 443 | TParent = class 444 | procedure OneProc; virtual; 445 | end; 446 | 447 | TChild = class(TParent) 448 | published 449 | procedure OneProc; reintroduce; overload; 450 | {和父类构成方法重载关系时可以的,因为在TChild的published区,只有一个OneProc方法,而在线面两行企图重载AntherProc则是没有可能的,编译器不允许在published区出现多个同名的方法} 451 | procedure AntherProc(S: String); overload; 452 | procedure AnotherProc; overload; 453 | end; 454 | ``` 455 | 456 | published区的类成员会生成运行时类型信息,而类成员是通过名字区分的。因此,编译器无法为成员AnotherProc生成运行时类型信息。 457 | 458 | 消息方法。消息方法的作用是截获并处理特定的一个消息。他使用的声明关键字是message。如: 459 | 460 | ```pascal 461 | TCustomForm = class(TScrollingWinControl) 462 | private 463 | procedure WMClose(var Message: TWMClose); message WM_CLOSE; 464 | …… 465 | end; 466 | ``` 467 | 468 | WMClose的作用是捕获并处理消息WM_CLOSE。当WM_CLOSE消息到来时,方法WMClose被自动调用。 469 | 470 | 消息方法的规则命名:消息类名大写 + '_' + 消息名(第一个字母大写,其余小写); 471 | 472 | 消息方法参数声明: var Message: 消息类型。消息类型可以是基本的TMessage,可以以是特定的消息类型。 473 | 474 | 消息方法的实现类似下面的代码 475 | 476 | ```pascal 477 | procedure TCustomForm.WMClose(var Message: TWMClose); 478 | begin 479 | {在本类中对消息做必要的处理} 480 | Close; 481 | {然后调用父类对该消息的处理代码} 482 | inherited; 483 | end; 484 | ``` 485 | 486 | 487 | 488 | ## 对象的克隆 489 | 490 | 1. 使用赋值运算符将一个对象引用赋值给一个对象变量; 491 | 2. 使用Assign或AssignTo方法可以将对象属性进行复制,得到两个状态相同的对象。 492 | 493 | 第一种赋值操作 494 | 495 | ```pascal 496 | var 497 | a,b: TMyObject; 498 | begin 499 | a := TMyobject.create; 500 | b := a; 501 | end; 502 | ``` 503 | 504 | 如果写成下面这样,就会造成内存泄漏 505 | 506 | ```pascal 507 | var 508 | a, b: TMyObject; 509 | begin 510 | a := TMyObject.create; 511 | b := TMyObject.create; 512 | b := a; //错误,对象b丢失导致内存泄漏。 513 | end; 514 | ``` 515 | 516 | 如果要让对象b克隆对象a,则需要考虑第二种赋值方法 517 | 518 | ```pascal 519 | var 520 | a, b: TMyObject; //这里假设TMyObject时TPersistent的派生类 521 | begin 522 | a := TMyObject.create; 523 | b := TMyObject.create; 524 | b.Assign(a); //对象b的属性和内容和对象a完全相同 525 | end; 526 | ``` 527 | 528 | **b := a** 意味这b是a的引用,即两者时同一对象。如果写成了b.Assign(a),那么b是一个单独的对象,其状态与a相同,也就可以看成是b克隆了a。 529 | 530 | ## 对象的销毁 531 | 532 | 在销毁对象的时候尽可能的使用**Free** 来销毁对象,为了确保对象即使发生异常也会正确的销毁,可以使用**try……finally** 异常句柄。 533 | 534 | ```pascal 535 | MyObj := TSomeClass.Create; 536 | try 537 | MyObj.DoSomething; 538 | MyObj.DoSomethingElse; 539 | finally 540 | MyObj.Free; 541 | end; 542 | ``` 543 | 544 | ```pascal 545 | // 将十进制转换成十六进制字符串 546 | IntToHex(int Value; int Digits); // Vaule是要转换的值,Digits是字符串的位数 547 | ``` 548 | 549 | ## 查看是否存在内存泄漏 550 | 551 | ```pascal 552 | ReportMemoryLeaksOnShutdown := True; 553 | ``` 554 | 555 | ## Delphi中 New,Getmem,ReallocMem联系与区别 556 | 557 | ```pascal 558 | procedure New(var P: Pointer); {为一个指针变量分配内存,会自动计算指针所指数据结构需要空的空间大小} 559 | procedure GetMem(var P: Pointer; Size: Integer); {分配一个指定大小的内存块(连续),并用P指向它} 560 | procedure ReallocMem(var P: Pointer; Size: Integer); {重新分配指定大小内存块,参数P必须是nil或者指向一个由GetMem, AllocMem, 或 ReallocMem分配的内存变量,其分配的内存是连续的,会把前面已有的数据移到新分配的内存中去} 561 | //通常采用New分配内存比较好。 562 | 一、New和GetMem都可以为指针类型动态分配内存,并且Delphi不会对由此分配的内存进行管理,即必须有相应的代码对其进行释放,否则内存将“丢失”,直到应用程序结束。 563 | 二、New分配的内存必须由Dispose来释放;GetMem分配的内存必须由FreeMem来释放; 564 | 三、New根据指针类型来自动计算需要分配的内存尺寸;GetMem必须指定尺寸; 565 | 因此,对于类型指针,一般用New和Dispose来进行管理;对于内存尺寸随机的指针(典型地如PChar),一般用GetMem和FreeMem来进行管理。从另一方面来说,在很多时候用哪一对例程都可以进行动态内存管理。 566 | ``` 567 | 568 | 569 | 570 | 571 | -------------------------------------------------------------------------------- /PMTreeList使用方法.md: -------------------------------------------------------------------------------- 1 | # PMTreeList中常用的方法 2 | 3 | ```pascal 4 | // 1. PMTreeList的每个列都有一个FieldName,它不同于每个Column的Name, 可以为每个fieldname赋值 5 | TPMTreeListColumn(ZbclTree.Columns[i]).FieldName := cst_Zbcl_Bm; 6 | // 2. 根据FieldName判断Column是否存在 7 | if ZbclTree.GetColumnByFieldName(cst_Zbcl_djsx) <> nil then 8 | 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Delphi-Study-Notes 2 | delphi学习笔记 3 | 1. delphi日常学习和公司软件的一些业务知识 4 | -------------------------------------------------------------------------------- /SVN账号密码.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/SVN账号密码.png -------------------------------------------------------------------------------- /System中常用方法.md: -------------------------------------------------------------------------------- 1 | ```pascal 2 | function Succ(X: SmallInt): SmallInt; // 获得变量的后继值 3 | function Pred(X: SmallInt): SmallInt; // 获得变量的前驱值 4 | // 将Source开始后面的Count位的内容替换Dest开始后Count位的内容 5 | procedure Move(const Source; var Dest; Count: Integer); 6 | ``` 7 | 8 | -------------------------------------------------------------------------------- /TControl中鼠标事件到消息的转换过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/TControl中鼠标事件到消息的转换过程.png -------------------------------------------------------------------------------- /VCL主要架构.md: -------------------------------------------------------------------------------- 1 | # 深入浅出VCL 2 | 3 | ![](VCL主要架构.png) 4 | 1. TObject (定义他的单元是System) 5 | 6 | 主要定义了四类功能的虚方法: 7 | 8 | (1) 对象构造函数和析构函数 9 | 10 | (2)返回运行时类型信息。 11 | 12 | ```pascal 13 | class function ClassName: ShortString;{返回类或者对象的类型名} 14 | function ClassType: TClass;{返回对象的类型(即类引用)} 15 | class function ClassParent:TClass;{返回类或者对象的父类类型} 16 | class function ClassInfo: Pointer;{返回类或者对象的运行时类型信息表的地址} 17 | ``` 18 | 19 | (3)支持消息处理。有方法Dispatch和DefaultHandler提供。 20 | 21 | (4)支持接口实现。有方法GetInterface和类方法GetInterfaceEntry、GetInterfaceTabel提供。 22 | 23 | 2. TPersistent(Classes,抽象类) 24 | 25 | 主要有两类功能: 26 | 27 | (1)对象相互复制。AssignTo 和Assign这两个虚方法提供,他们都要有子类具体实现。 28 | 29 | (2)在流里读写属性的能力,凡是从TPersistent继承下来的TComponent使得所有的对象都有进行流操作的能力。 30 | 31 | **流是一个以二进制数据形式封装在存储介质(例如内存或磁盘文件)中的对象,因为delphi窗体文件是通过流实现的,直接从TPersistent继承下来的TComponent使得所有的控件具有生成Form文件的能力** 。 32 | 33 | TPersistent是抽象类,不要直接创建其实例。 34 | 35 | ```pascal 36 | TPersistent = class(TObject) 37 | ``` 38 | 39 | RTTI(Run-Time Type Information),运行时类型信息,也就是说在运行期获得数据类型或类的信息。 40 | 41 | 3. TComponent(Classes,抽象类) 42 | 43 | TComponent具有四类主要功能: 44 | 45 | (1)注册后可以出现在组件页;设计时可见、可以管理;运行时不可见。 46 | 47 | (2)可以拥有别的对象而成为其他对象的拥有者(Owner)。 48 | 49 | (3)加强了流读写功能。 50 | 51 | (4)可以转化为ActiveX控件和别的COM类。 52 | 53 | (5)是第一个可以被用来创建新组件的基类,非可视化组件也是从TComponent继承下来。 54 | 55 | TComponent是抽象类,不要直接创建其实例。如果开发运行时不可见控件,可以从TComponent继承,否则可以从TWinControl或其子类继承。 56 | 57 | 4. TControl(Controls) 58 | 59 | TControl是控件类,所谓控件,是运行时可见的组件。VCL所有控件都是TControl的直接或间接子类。 60 | 61 | 两种基本的可视化空间:graphic controls 和 windowed controls,他们的代表分别是TGraphicControl类和TWinControl类。他们之间主要的区别在于TGraphicControl继承下来的控件没有窗口句柄,因而也没有输入焦点。 62 | 63 | 窗口控件进一步分可以分为两类。一类是直接从TWinControl继承下来的通过Windows内部控件实现的控件,他能够自己自动实现重绘功能。而另一类TCustomControl类则指那些需要窗口句柄控件但未封装提供重绘功能的Windows控件。 64 | 65 | 5. TWinControl(Controls) 66 | 67 | TWinControl是所有控件类控件的祖先类。窗口控件有一下特点: 68 | 69 | (1)可以有输入焦点。 70 | 71 | (2)可以接收键盘输入。 72 | 73 | (3)可以作为其他控件的容器。 74 | 75 | (4)有句柄(Handle)属性。 76 | 77 | 6. 可以将VCL分为三个主要区:组件区,通用对象区和异常区。组件区都是从TComponent继承而来的。有一些没有继承TComponent,这些非组件类分布在VCL的通用对象区和异常区。 78 | 79 | 这些非组件类主要由两个好处,一是非组件类可以定义组件属性的数据类型,如图像组件(TGraphic对象)的Picture属性或列表框(TString对象)的Items属性。这些类一般继承自TPersistent,所以时流式的,可以由子属性甚至事件;二是可以直接使用。在用户编写的delphi代码中,可以分配和处理这些类的对象。 80 | 81 | ![](.\delphi主要基类及其派生类的关系.png) 82 | 83 | ### 可视化控件和非可视化空间的区别 84 | 85 | ​ 是否有窗口句柄。 86 | 87 | **查看事件的调用者** 88 | 89 | ```pascal 90 | procedure TForm1.OnClick(Sender: TObject); 91 | var 92 | Str: string; 93 | begin 94 | Str := ''; 95 | Str := Sender.ClassName; //引发事件对象所属的类 96 | Str := Sender.classParent.ClassName;//父类名 97 | Str := IntToStr(Sender.InstanceSize);//实际字节数 98 | end; 99 | ``` 100 | 101 | **获得某组件的祖先类的类型和祖先类的相关属性** 102 | 103 | ```pascal 104 | procedure TForm1.Button1Click(Sender: TObject); 105 | var 106 | ClassRef: TClass; 107 | begin 108 | ClassRef := Sender.ClassType; 109 | while ClassRef <> nil do 110 | begin 111 | ListBox1.Items.add(ClassRef.ClassName); 112 | ClassRef := ClassRef.ClassParent; 113 | end; 114 | end; 115 | ``` 116 | 117 | ## TPersistent: 持久化对象、 118 | 119 | ​ 该类时Delphi可视化编程的基础。该类实现了对象公布(published)属性的存取,即在该类及派生类中声明的publish的属性,方法,事件等可在设计期时显示在Object Inspector窗中。能在Object Inspector中对对象的publishe属性进行设计期的设计,并可将设置的值存到窗体或者数据模块的DFM文件中。 120 | 121 | ```pascal 122 | {$M+} 123 | TPersistent = class(TObject) 124 | private 125 | procedure AssignError(Source: TPersistent); 126 | protected 127 | procedure AssignTo(Dest: TPersistent); virtual; 128 | procedure DefineProperties(Filer: TFiler); virtual; 129 | function GetOwner: TPersistent; dynamic; 130 | public 131 | destructor Destroy; override; 132 | procedure Assign(Source: TPersistent); virtual; 133 | function GetNamePath: string; dynamic; 134 | {$M-} 135 | ``` 136 | 137 | TPersistent类的声明只有七个方法,实际上完成类的属性,方法,事件的存取工作的代码并没有定义在TPersistent类中,而是由Delphi另行定义,如TReader和TWriter等对象,这些对象都是以TPersistent为服务对象的。 138 | 139 | 从TPersistent类的声明看,他与TObject的声明方式和结构基本相同,但是TObject却没有published属性,方法和事件的设计期的存取功能呢?因为TPersistent的声明中有**M** 编译开关,在**{$M+}** 和**{$M-}** 间声明时,程序编译器会为类生成与**RTTI(Runtime Type Infomation)** 相关的代码来完成类的published的属性、方法和时间的存储工作。并且该类的子类的published属性、方法和事件也具有存取特性。如果一个类或其祖先类都没有在**{$M+}** 和**{$M-}** 声明,则该类不能有published的属性、事件、和方法。 140 | 141 | 一般而言,TPersistent 的Assign方法比较常用,他主要完成两个对象的复制。对于两个对象,如 142 | 143 | ```pascal 144 | var 145 | Obj1, Obj2: TFont; 146 | begin 147 | Obj1 := Obj2; 148 | end; 149 | ``` 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /VCL主要架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/VCL主要架构.png -------------------------------------------------------------------------------- /VCL架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/VCL架构.png -------------------------------------------------------------------------------- /VMT结构图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/VMT结构图.png -------------------------------------------------------------------------------- /Xml文件的使用.md: -------------------------------------------------------------------------------- 1 | ```pascal 2 | AXmlNode := FXmlNode.Root; 3 | function GetProvinceXmlNode(XmlNode: IXmlNode): TXmlNode; 4 | var 5 | AChildXmlNode: TXmlNode; 6 | i, iCount: Integer; 7 | begin 8 | iCount := AXmlNode.NodeCount; 9 | for i = 0 to iCount - 1 do 10 | begin 11 | AChildXmlNode := AXmlNode.Nodes[i]; 12 | if AChildXmlNode.AttributeByName['provinced'] = InttoStr(FProvinceId) then 13 | begin 14 | Result := AChildXmlNode; 15 | break; 16 | end; 17 | end; 18 | end; 19 | ``` 20 | 21 | # TXMLDocument 22 | 23 | ```pascal 24 | var 25 | FXMLDoc: TXMLDocument; 26 | AXMLNode: IXMLNode; 27 | i: Integer; 28 | begin 29 | FXMLDoc.LoadFromFile(AFileName); 30 | AXMLNode := FXMLDoc.DocumentElement.ChildNodes[I]; 31 | end; 32 | ``` 33 | 34 | * XML 节点有自己的类型,其中ntComment是注释类型。 35 | 36 | ```pascal 37 | if AXMLNode.NodeType = ntComment then // 如果节点类型是注释,那么跳过 38 | begin 39 | continue; 40 | end; 41 | ``` 42 | 43 | -------------------------------------------------------------------------------- /cxTreeList.md: -------------------------------------------------------------------------------- 1 | ## 行节点和列节点 2 | 3 | ```pascal 4 | cxTreelistNode: //行节点 5 | cxTreelistColumn: //列节点 6 | ``` 7 | 8 | ## 添加新的节点 9 | 10 | ```pascal 11 | //添加新的行节点 12 | var 13 | ANewNode: TcxTreeListNode; 14 | i: integer; 15 | begin 16 | ANewNode := lst.add; 17 | ANewNode.ValueCount; //节点的列的个数 18 | for i := 0 to ANewNode.valueCount - 1 do 19 | ANewNode.values[i] := IntToStr(i); 20 | end; 21 | 22 | //给选中节点添加子节点 23 | var 24 | ANewNode: TcxTreeListNode; 25 | i: integer; 26 | begin 27 | ANewNode := lst1.FocusedNode.AddChild; 28 | for i := 0 to ANewNode.valueCount - 1 do 29 | ANewNode.values[i] := IntToStr(i); 30 | end; 31 | 32 | //获得该行节点的下一个节点 33 | lst1.FocusedNode.GetNext; //这个不是获得同一级的下一个节点,而是获得界面上的下一个节点。 34 | 35 | //获得同一级的下一个节点 36 | lst1.FocusedNode.GetNextSibling;//这里的同一级是指的在 37 | 38 | //删除节点 39 | lst1.FocusedNode.Delete;//会删除该节点和该节点的子节点 40 | 41 | //删除子节点 42 | lst.FocusedNode.DeleteChildren; //会删除该节点的所有子节点 43 | 44 | //添加新的列 45 | var 46 | AColumn: TcxTreeListColumn; 47 | begin 48 | AColumn := lst1.CreateColumn; 49 | AColumn.caption.text := '新列'; 50 | AColumn.caption.AlignHorz := taCenter; 51 | AColumn.Tag := 1; 52 | AColumn.Name := 'NewColumn'; 53 | end; 54 | 55 | //删除列 56 | lst1.FocusedColumn.Destroy; 57 | ``` 58 | 59 | ## 各个项目的值 60 | 61 | ```pascal 62 | ANewNode.ValueCount //新的节点的列的数量 63 | Col.ItemIndex //该列的索引是多少 64 | ``` 65 | 66 | 67 | 68 | ## 事件的执行顺序 69 | 70 | ​ **在获取焦点和选取节点的时候,首先是获取焦点,然后是选中节点。** 71 | 72 | -------------------------------------------------------------------------------- /delphi 消息.md: -------------------------------------------------------------------------------- 1 | # 消息 2 | 3 | ## 消息的定义 4 | 5 | 1. windows中消息的定义 6 | 7 | ```pascal 8 | PMsg = ^TMsg 9 | tabMSG = packed record 10 | hwnd: HWND; // 这个是窗口句柄,真正由句柄的类是从TWinControl继承下来的。 11 | message: UINT; 12 | wParam: WPARAM; 13 | lParam: LPARAM; 14 | time: DWORD; 15 | pt: Pointer; 16 | end; 17 | TMSG = tagMSG; 18 | MSG = tagMSG; 19 | 20 | // TMsg 是根据windows定义的消息类型使用delphi翻译过来的 21 | ``` 22 | 23 | 2. 上面的消息在delphi中不太方便,然后delphi中自己定义了新的消息类型 24 | 25 | ```pascal 26 | PMessage = ^TMessage; 27 | TMessage = packed record 28 | Msg: Cardinal; 29 | case integer of 30 | 0:( 31 | WParam: Longint; 32 | LParam: Longint; 33 | Result: Longint; 34 | ); 35 | 1:( 36 | WParamLo: Word; 37 | WParamHi: Word; 38 | LParamLo: Word; 39 | LParamHi: Word; 40 | ResultLo: Word; 41 | ResultHi: Word; 42 | ); 43 | end; 44 | 45 | // TMessage可以保存任何消息,然后delphi还定义了针对不同消息的多种记录类型。例如键盘消息记录TWMKey, 鼠标消息记录TWMMouse, 命令消息记录TWMCommand; 46 | // 最开始就有点奇怪,窗口的句柄去哪了?(现在个人认为这是在vcl中使用的消息,句柄在WinControl.) 47 | ``` 48 | 49 | ## 消息处理 50 | 51 | * 从操作系统实现上来讲,Windows会根据当前发生的事情创建一条消息,并将其放到应用程序消息队列的末尾,应用程序从消息队列中获取消息并分派给指定的窗口或组件。每个窗口都定义了所谓的**窗口过程**,该过程负责接受并响应消息,再将结果返回给操作系统。 52 | 53 | * Windows中消息的产生时间是不确定的,应用程序只有在接受到消息后才进行特殊处理,没有接受到消息时执行自己的既定任务或者什么都不干。 54 | 55 | * 实现消息的处理方法一般是消息循环。 56 | 57 | ```pascal 58 | // 处理消息 59 | function TApplication.ProcessMessage(var Msg: TMsg): Boolean; 60 | var 61 | Handled: Boolean; 62 | begin 63 | Result := False; 64 | if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then 65 | begin 66 | if Msg.Message <> WM_QUIT then 67 | begin 68 | Handled := False; 69 | if Assigned(FOnMessage) then // 这里 70 | FOnMessage(Msg, Handled); // 如果Handled返回为true,则说明消息已经处理完成,就不走DispatchMessageW(Msg) 71 | if not IsHint(Msg) and not Handle and not IsMDIMsg(Msg) and 72 | not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then 73 | begin 74 | TranslateMessage(Msg); 75 | if Unicode then 76 | DispatchMessageW(Msg) 77 | else 78 | DispatchMessageA(Msg); 79 | end; 80 | end 81 | else 82 | FTerminate := True; 83 | end; 84 | end; 85 | 86 | // 消息循环 87 | procedure TApplication.ProcessMessages; 88 | var 89 | Msg: TMsg; 90 | begin 91 | while ProcessMessage(Msg) do {loop}; 92 | end; 93 | 94 | // 对Application 的 FOnMessage 赋值 95 | procedure TForm1.MessageProc(var Msg: TMsg; var Handled: Boolean); 96 | begin 97 | if Msg.message = JM_DATA then 98 | begin 99 | Memo.Lines.Add('Application.OnMessage has processed JM_DATA.'); 100 | Handled := False; 101 | // Handled := True; 102 | end; 103 | end; 104 | 105 | // 在Create是对OnMessage进行赋值 106 | procedure TForm1.FormCreate(Sender: TObject); 107 | begin 108 | Application.OnMessage := Self.MessageProc; // 这样就 109 | end; 110 | ``` 111 | 112 | 113 | 114 | ## 消息的分类 115 | 116 | * Windows消息可以分为4大类:Windows标准消息,通知消息,命令消息和用户自定义消息 117 | 118 | ## 消息的发送 119 | 120 | * 在VCl中,一般有三种方式发送消息 121 | 122 | 1. Sendmessage,PostMessage和PostThreadMessage 123 | 2. TControl的Perform 124 | 3. TWinControl的Broadcast 125 | 126 | ```pascal 127 | type 128 | TControl = class(TComponent) 129 | public 130 | // 一般用来向自己发送消息 131 | function Perform(Msg: Cardinal; WParam, LParam: Longint): Longint; 132 | end; 133 | 134 | TWinControl = class(TControl) 135 | public 136 | procedure Broadcast(var Message); 137 | end; 138 | 139 | function PostMessage(hWnd: HWND; Msg: Cardinal; WParam, LParam: Longint): LongBool; 140 | function PostThreadMessage(idThread: Cardinal; Msg: Cardinal; WParam, LParam: Longint): LongBool; 141 | function SendMessage(hWnd: HWND; Msg: Cardinal; WParam, LParam: Longint): Longint; 142 | 143 | // 前两种方式中已经明确了消息要发送的窗口,下面三种方式可以指定窗口发送消息。 144 | // postMessage会将消息投递到创建由hWnd参数指定的窗口的线程消息队列中,该函数立即返回而不等待接受消息的线程响应完毕。 145 | // sendMessage会将消息发送到有hWnd参数指定的窗口,并且在该窗口没有处理完毕该消息是不会返回。 146 | // 从原理上来说,PostMessage会进消息队列,而sendMessage不会进消息队列。 147 | ``` 148 | 149 | ## VCL处理消息的流程 150 | 151 | ```pascal 152 | // Application 中 FOnMessage 的定义 153 | type 154 | TMessagEvent = procedure(var Msg: TMsg; var Handled: Boolean); 155 | TApplication = class(TComponent) 156 | private 157 | FOnMessage: TMessageEvent; 158 | public 159 | property OnMessage: TMessageEvent FOnMessage write FOnMessage; 160 | end; 161 | 162 | // FOnMessage处理完消息后如果要继续处理消息(Handled = False),那么由DispatchMessage将消息派发到某个窗口过程(这个DispatchMessage是user32.dll中的函数,是WindowsAPI),这个窗口过程就是StdWndProc函数,StdWndProc函数基本上起到了消息中转站的作用,由它将消息派发给某个对象 163 | 164 | // 如果接收到消息对象为TWinControl、TCommonDialog、TClipboard、TDragObject、TPopupList类的实例,则MainWndProc方法会被调用以处理该消息。之后WndProc方法将得到该消息,消息在WndProc方法中进入VCL消息派发机制,由Dispath方法将消息发送给某个消息句柄。 165 | 166 | unit Classes 167 | type 168 | TWndMethod = procedure(var Message: TMessage) of object; 169 | // TControl 170 | TControl = class(TComponent) 171 | private 172 | FWindowProc: TWndMethod; 173 | protected 174 | procedure WndProc(var Message: TMessage); virtual; // 这个是虚方法,可以重载 175 | public 176 | Constructor Create(AOwner: TComponent); 177 | property WindowProc: TWndMethod read FWindowProc write FWindowProc; 178 | end; 179 | 180 | constructor TControl.Create(AOwner: TComponent); 181 | begin 182 | inherited Create(AOwner); 183 | FWindowProc := WndProc; 184 | ....... 185 | end; 186 | 187 | procedure TControl.WndProc(var Message: TMessage); 188 | var 189 | Form: TCustomForm; 190 | KeyState: TKeyboardState; 191 | WheelMsg: TCMMouseWheel; 192 | Panned: Boolean; 193 | {$IF DEFINED(CLR)} 194 | LMsg: TMessage; 195 | {$IFEND} 196 | begin 197 | if (csDesigning in ComponentState) then 198 | begin 199 | Form := GetParentForm(Self, False); 200 | if (Form <> nil) and (Form.Designer <> nil) and 201 | Form.Designer.IsDesignMsg(Self, Message) then Exit 202 | end; 203 | if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then 204 | begin 205 | Form := GetParentForm(Self); 206 | if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit; 207 | end 208 | else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then 209 | begin 210 | if not (csDoubleClicks in ControlStyle) then 211 | case Message.Msg of 212 | WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK: 213 | Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN); 214 | end; 215 | case Message.Msg of 216 | WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message); 217 | WM_MBUTTONDOWN: 218 | begin 219 | if (csPannable in ControlStyle) and 220 | (ControlState * [csDestroyingHandle, csPanning] = []) and 221 | not Mouse.IsDragging then 222 | begin 223 | Mouse.CreatePanningWindow; 224 | Panned := False; 225 | if Assigned(Mouse.PanningWindow) then 226 | begin 227 | if Self is TWinControl then 228 | Panned := Mouse.PanningWindow.StartPanning(TWinControl(Self).Handle, Self) 229 | else if Parent <> nil then 230 | Panned := Mouse.PanningWindow.StartPanning(Parent.Handle, Self) 231 | else 232 | begin 233 | Form := GetParentForm(Self, False); 234 | if Form <> nil then 235 | Panned := Mouse.PanningWindow.StartPanning(Form.Handle, Self); 236 | end; 237 | end; 238 | if Panned then 239 | begin 240 | Message.Result := 1; 241 | Application.HideHint; 242 | end 243 | else if Assigned(Mouse.PanningWindow) then 244 | Mouse.PanningWindow := nil; 245 | end; 246 | end; 247 | WM_LBUTTONDOWN, WM_LBUTTONDBLCLK: 248 | begin 249 | if FDragMode = dmAutomatic then 250 | begin 251 | BeginAutoDrag; 252 | Exit; 253 | end; 254 | Include(FControlState, csLButtonDown); 255 | end; 256 | WM_LBUTTONUP: 257 | Exclude(FControlState, csLButtonDown); 258 | else 259 | with Mouse do 260 | if WheelPresent and (RegWheelMessage <> 0) and 261 | (Integer(Message.Msg) = Integer(RegWheelMessage)) then 262 | begin 263 | GetKeyboardState(KeyState); 264 | {$IF DEFINED(CLR)} 265 | WheelMsg := TCMMouseWheel.Create; 266 | {$IFEND} 267 | with WheelMsg do 268 | begin 269 | Msg := Message.Msg; 270 | ShiftState := KeyboardStateToShiftState(KeyState); 271 | WheelDelta := Message.WParam; 272 | Pos := SmallPoint(Message.LParam and $FFFF, Message.LParam shr 16); 273 | end; 274 | {$IF DEFINED(CLR)} 275 | LMsg := WheelMsg.OriginalMessage; 276 | MouseWheelHandler(LMsg); 277 | {$ELSE} 278 | MouseWheelHandler(TMessage(WheelMsg)); 279 | {$IFEND} 280 | Exit; 281 | end; 282 | end; 283 | end 284 | else if Message.Msg = CM_VISIBLECHANGED then 285 | with Message do 286 | SendDockNotification(Msg, WParam, LParam); 287 | Dispatch(Message); // 最后Dispatch消息。该函数在TObject中定义 288 | end; 289 | 290 | //TObject 291 | TObject = class 292 | procedure Dispatch(var Message); virtual; 293 | procedure DefaultHandler(var Message); virtual; 294 | end; 295 | procedure TObject.Dispatch(var Message); // 虚方法. 296 | {$IF not defined(CPU386)} 297 | type 298 | //THandlerProc = procedure(Self: Pointer; var Message) { of object }; 299 | THandlerProc = procedure(var Message) of object; 300 | var 301 | MsgID: Word; 302 | Addr: Pointer; 303 | M: THandlerProc; 304 | begin 305 | MsgID := TDispatchMessage(Message).MsgID; 306 | if (MsgID <> 0) and (MsgID < $C000) then 307 | begin 308 | Addr := FindDynaMethod(PPointer(Self)^, MsgID); 309 | if Addr <> nil then 310 | begin 311 | //THandlerProc(Addr)(Self, Message) 312 | TMethod(M).Data := Self; 313 | TMethod(M).Code := Addr; 314 | M(Message); 315 | end 316 | else 317 | Self.DefaultHandler(Message); 318 | end 319 | else 320 | Self.DefaultHandler(Message); 321 | end; 322 | // 空方法。 323 | procedure TObject.DefaultHandler(var Message); 324 | begin 325 | end; 326 | 327 | // TWinControl 328 | TWinControl = class(TControl) 329 | constructor TWinControl.Create(AOwner: TComponent); 330 | begin 331 | inherited Create(AOwner); 332 | ...... 333 | end; 334 | 335 | // MainWndProc不是虚函数,无法重载 336 | procedure TWinControl.MainWndProc(var Message: TMessage); 337 | begin 338 | try 339 | try 340 | WindowProc(Message); 341 | finally 342 | FreeDeviceContexts; 343 | FreeMemoryContexts; 344 | end; 345 | except 346 | Application.HandleException(Self); 347 | end; 348 | end; 349 | 350 | procedure TDragObject.MainWndProc(var Message: TMessage); 351 | begin 352 | try 353 | WndProc(Message); 354 | except 355 | Application.HandleException(Self); 356 | end; 357 | end; 358 | 359 | //在消息处理过程处理完消息之后,TControl类的DefaultHandler方法获得消息处理权。DefaultHandle方法在对消息进行最后的处理后,消息处理流程离开VCL派发机制,返回到Windows的DefWindowProc函数或其他默认的缺省窗口过程。 360 | ``` 361 | 362 | ## 消息与事件的关系 363 | 364 | * 在delphi中,事件和消息不一定是一一对应的,完全可以在程序中声明和消息不相关的事件,而VCL中事件本身是为了更好的去响应windows消息去设计的。例如OnKeyDown事件对应WM_KEYDOWN消息 365 | 366 | ```pascal 367 | unit Control 368 | type 369 | TKeyEvent = procedure(Sender: TObject; var Key: Word; 370 | Shift: TShiftState) of object; 371 | 372 | TWinControl = class(TControl) 373 | private 374 | FOnKeyDown: TKeyEvent; 375 | procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN; 376 | protected 377 | function DoKeyDown(var Message: TWMKey): Boolean; 378 | procedure KeyDown(var key: Word; Shift: TShiftState); dynamic; 379 | property OnKeyDown: TKeyEvent read FOnKeyDown write FOnKeyDown; 380 | end; 381 | // WMkeyDown 382 | procedure TWinControl.WMKeyDown(var Message: TWMKeyDown); 383 | begin 384 | if not DoKeyDown(Message) then Inherited; 385 | end; 386 | // DOKeyDown 调用 KeyDown 387 | function TWinControl.DoKeyDown(var Message: TWMKey): Boolean; 388 | var 389 | ShiftState: TShiftState; 390 | Form: TCustomForm; // TCustomForm 是从 TWinControl继承下来的,这里感觉不太好。 391 | begin 392 | Result := True; 393 | Form := GetParentForm(Self); 394 | if (Form <> nil) and (Form <> self) and Form.KeyPreview and 395 | TWinControl(Form).DoKeyDown(Message) then Exit; 396 | with Message do 397 | begin 398 | ShiftState := KeyDataToShiftState(KeyData); 399 | if not (csNostdEvents in ControlStyle) then 400 | begin 401 | KeyDown(CharCode, ShiftState); 402 | if CharCode = 0 then 403 | Exit; 404 | end; 405 | end; 406 | Result := False; 407 | end; 408 | // KeyDown 409 | procedure TWinControl.KeyDown(var Key: Word; Shift: TShiftState); 410 | begin 411 | if Assigned(FOnKeyDown) then FOnKeyDown(Self, key, Shift); 412 | end; 413 | 414 | ``` 415 | 416 | -------------------------------------------------------------------------------- /delphi中一些基类的使用方法.md: -------------------------------------------------------------------------------- 1 | # TObject 2 | 3 | ```pascal 4 | 5 | ``` 6 | 7 | # TPersistent 8 | 9 | ```pascal 10 | TPersistent = class(TObject) 11 | private 12 | procedure AssignError(Source: TPersistent); 13 | protected 14 | // AssignTo 方法是一个虚方法,这个方法是从这个类继承的类必须要实现的一个方法。这个方法是直接抛出了一个异常 15 | // 因此如果子类要用该方法,那么就必须将该方法重写。因为只有被复制的对象才知道要复制那些内容。 16 | procedure AssignTo(Dest: TPersistent); virtual; 17 | procedure DefineProperties(Filer: TFiler); virtual; 18 | function GetOwner: TPersistent; dynamic; 19 | public 20 | destructor Destroy; override; 21 | // Assign方法是一个虚方法,如果子类没有重写该方法,那么就会调用源对象的AssignTo方法。 22 | procedure Assign(Source: TPersistent); virtual; 23 | function GetNamePath: string; dynamic; 24 | end; 25 | 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /delphi中基础数据类型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/delphi中基础数据类型.png -------------------------------------------------------------------------------- /delphi中的字符串详解.md: -------------------------------------------------------------------------------- 1 | # delphi 字符串详解 2 | 3 | ## 短字符串和字符数组 4 | 5 | ```pascal 6 | // 字符串数组的定义 7 | var 8 | Str1, str2: array of [0..6] of AnsiChar; //在delphiXE中Char是双字节,这里使用AnsiChar是为了和书中保持一致。 9 | // 为了解决字符串类型不丰富的问题,delphi中引入了字符串,其中有Shortstring,ANSIString, WideString。 10 | // 为了与传统pascal字符串相兼容,ShortString使用紧缩格式。它最多只能容纳255个标准ASCII字符。 11 | var 12 | str1, str2: string[7]; 13 | 14 | // 下面时字符数组和字符串的比较 15 | var 16 | A: array[0..6] of AnsiChar; 17 | B: String[7]; 18 | begin 19 | A := 'Delphi7'; 20 | B := 'Delphi7'; 21 | end; 22 | ``` 23 | 24 | | | A[0] | A[1] | A[2] | A[3] | A[4] | A[5] | A[6] | A[7] | 25 | | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 26 | | A | D | e | l | p | h | i | 7 | | 27 | | B | 7 | D | e | l | p | h | i | 7 | 28 | 29 | ```pascal 30 | ShowMessage('a Sizeof' + IntToStr(SizeOf(a))); // 7 31 | ShowMessage('b Sizeof' + IntToStr(SizeOf(b))); // 8 32 | ShowMessage('a Length' + IntToStr(Length(a))); // 7 33 | ShowMessage('b Length' + IntToStr(Length(b))); // 7 34 | ShowMessage('a Low' + IntToStr(Low(a))); // 0 35 | ShowMessage('b Low' + IntToStr(Low(b))); // 0 36 | ShowMessage('a High' + IntToStr(High(a))); // 6 37 | ShowMessage('b High' + IntToStr(High(b))); // 7 38 | // 字符数组和字符串并不兼容。 39 | ``` 40 | 41 | ## 字符串 42 | 43 | * 这里讨论长字符串(AnisString)和宽字符串(WideString), 在delphi中默认情况下string类型就是AnsiString类型。 44 | 45 | * ANSIstring采用非紧缩格式。ANSIstring字符串可以在使用是动态分配内存。(这个东西不是字符串常量) 46 | 47 | * 当我们声明了长字符串变量,实际上分配了32位(4字节)的内存空间存储一个指针,该指针指向存储实际字符串的内存地址。 48 | 49 | * 当字符串变量为空时,此指针值为nil,字符串内容本身不需占用额外的存储空间。当字符串非空时,此指针为动态分配的内存块的首地址,该内存块存储了相应的字符串内容(包括字符串值,一个32位(4字节)的长度指示符和一个32位的引用计数器) 50 | 51 | * 存储字符串是在堆中分配的。 52 | 53 | * 因为长字符串变量为隐式指针,因为多个长字符串变量可以指向相同的内容而不需要存储字符串的多个副本。只要一个长字符串变量被释放或赋予了新值,则原字符串的引用计数自动减1,而新字符串(如果存在)的引用计数自动递增1。如果某个长字符串的引用计数递减到0,则其占用的内存被释放。当改变长字符串时,而且仅当引用计数器大于1时才生成该字符串的一个副本,此即为生存期管理 的写时复制(copy-on-write)语义。 54 | 55 | * 从本质上说,AnsiString类型与一维动态字符数组类似,他们的主要差别在于 56 | 57 | 1. 虽然使用相同的引用计数技术,但AnsiString类型与一维动态字符数组的索引方式不同。 58 | 59 | ```pascal 60 | var 61 | A: string; 62 | B: AnsiChar; 63 | N: integer; 64 | begin 65 | SetLength(A, 6); 66 | A := 'Delphi'; 67 | B := A[0]; // 错误,不能访问A[0] 68 | N := Length(A); // N = 6 69 | B := A[1]; // B := 'D'; 70 | N := SizeOf(A); // N = 4, 返回A的内存大小,而不是字符串本身占用的内存大小 71 | end; 72 | ``` 73 | 2. A的内容(p值)实际为A[1]的地址。32位引用计数、字符串长度以及最后的A[7]都是由Delphi自动维护的。程序不能进行操作。AnsiString类型变量的索引下标从1到长度值。字符串在尾部添加一个Null字符(这个字符其实是#0,不是真正意义上的Null,真正意义上了Null表示空,C语言的字符串是以'\0'为结尾的,'\0'就是#0)是为了与C语言null结尾的字符串相兼容。所以可以将长字符串赋值给某个null结尾的字符串变量。 74 | 75 | ![](长字符串的内存存储格式.png) 76 | 77 | 3. 一维动态数组存储字符串时,其分配机制有些差异,但是正是这些差异使得我们创建的数据类型不同。 78 | 79 | ```pascal 80 | var 81 | A: array of Char; 82 | B: Char; 83 | N: integer; 84 | begin 85 | SetLength(A, 5); 86 | A[0] := 'D'; 87 | A[1] := 'e'; 88 | A[2] := 'l'; 89 | A[3] := 'p'; 90 | A[4] := 'h'; 91 | A[5] := 'i'; 92 | A := 'Delphi'; // 错误,不能将字符串类型赋值给一维动态数组 93 | B := A[0]; // 正确,A[0] = 'D'; 94 | N := Length(A); // N = 5; 95 | N := Sizeof(A); // N = 4, 返回A的内存大小,而不是字符串本身所占内存大小 96 | end; 97 | ``` 98 | 99 | 4. A的内容(p值)实际为A[0]的地址。 100 | 101 | ![](一维动态字符数组的内存存储格式.png) 102 | 103 | ## 字符指针 104 | 105 | * PChar类型与字符串常量 106 | 107 | 1. PChar类型与字符串常量是赋值兼容的,也就是说我们可以使用赋值语句将一个字符串常量赋给PChar或PWideChar类型的变量。 108 | 109 | ```pascal 110 | var 111 | P: Char; 112 | begin 113 | P := 'Hello World'; 114 | end; 115 | ``` 116 | 117 | 2. 此时,P为指向null结尾字符数组的指针。执行上述语句后,编译器将把"Hello, World!"复制到P所指向的内存区中并自动在结尾出添加一个null字符,即形成以null结尾的字符串。因此,上面的程序段等价与 118 | 119 | ```pascal 120 | const TmpStr: array[0..12] of Char = 'Hello World!'#0; 121 | var 122 | P: PChar; 123 | begin 124 | P := @TmpStr; 125 | end; 126 | ``` 127 | 128 | 3. 字符串常量也可以作为实际参数传递给子程序中PChar或PWideChar类型的形式参数。但形式参数必须为常量参数或值参数。 129 | 130 | ```pascal 131 | function StrUpper(Str: PChar): PChar; 132 | 133 | procedure ButtonClick(Sender: TObject); 134 | begin 135 | Canvas.TextOut(X, Y, String(StrUpper('Hello World!'))); 136 | end; 137 | ``` 138 | 139 | 4. 也可以使用PChar或PWideChar类型声明类型常量 140 | 141 | ```pascal 142 | const 143 | Message: PChar = 'Program'; 144 | Digits: array[0..2] of PChar = ('Zero', 'One', 'Two'); 145 | ``` 146 | 147 | * PChar与字符数组 148 | 149 | 1. 可以使用静态字符数组创建以null结尾的字符串,并将它赋给PChar的指针变量。用静态字符数组创建以null结尾的字符串时,索引下标应该从0开始,在给数组赋值时,字符串中字符的数目至少要比数组长度小1(需要保留一个位置存储null字符)。 150 | 151 | ```pascal 152 | var 153 | Ch: array[0..4] of char; 154 | P: PChar; 155 | begin 156 | Ch := 'June'; 157 | P := Ch; 158 | end; 159 | ``` 160 | 161 | 2. PChar类型兼容于从索引下标0开始的静态字符数组,既可以将索引下标从0开始的静态字符数组赋给PChar类型的指针变量。此过程不可逆,即不能将PChar类型的指针变量赋给索引下标从0开始的静态字符数组。 162 | 163 | ```pascal 164 | label 1, 2, 3, 4, 5; 165 | var 166 | Ch1, Ch2: array[0..4] of Char; 167 | P1, P2: PChar; 168 | begin 169 | 1: Ch1 := 'June'; // 正确 170 | 2: P1 := Ch1; // 正确 171 | 3: P2 := StrUpper(Ch1); // 正确 172 | 4: P2 := StrLower(P1); // 正确 173 | 5: Ch2 := P1; // 错误,静态字符数组不兼容PChar; 174 | ShowMessage(IntToHex(Integer(@Ch1), 8)); // 0018F536 175 | ShowMessage(IntToHex(Integer(P1), 8)); // 0018F536 176 | ShowMessage(IntToHex(Integer(P2), 8)); // 0018F536 177 | end; 178 | ``` 179 | 180 | ![](字符指针与静态字符数组的内存布局.png) 181 | 182 | 3. PChar类型的字符串可以像数组一样使用下标来获得字符串中的字符。如果在d中,P1[2]则返回'j',P2[1]则返回'u'。 183 | 4. 可以使用“+”、“-”操作符进行PChar指针类型的加减运算,就像上面那样 184 | 185 | ```pascal 186 | P1 := 'june'; 187 | IntToHex(Integer(P1), 8); // 返回0018F536 188 | ``` 189 | 190 | 对于PChar类型,指针加1,所指向地址下移一个字节(8位)在Delphixe中是两个字节;对于PWideChar类型,指针加1,下移两个字节。 191 | 192 | ## PChar与长字符串 193 | 194 | 1. 长字符串也是基于0下标的,只是下标0并不对应与字符串中的第一个字母,而是字符串的长度。事实上,按照Delphi的实现,一个长字符串变量一旦被赋值或者使用Setlength过程确定了长度,则该长字符串就是以null结尾的。因而在Delphi中长字符串可以兼容PChar,但反之不成立。 195 | 196 | 2. 在string类型与PChar类型混用时,要注意以下几点 197 | 198 | ```pascal 199 | // 1. 可以直接把PChar类型赋值给string类型的变量 200 | var 201 | S1: string; 202 | P1: PChar; 203 | begin 204 | P1 := 'June'; 205 | S1 := P1; 206 | end; 207 | 208 | // 2. 不能将string类型的变量赋给PChar类型的变量,此时必须进行强制类型转换。例如,查找子串位置的函数AnsiPos 209 | function AnsiPos(const SubStr, S: string): integer; 210 | var 211 | P: PChar; 212 | begin 213 | Result := 0; 214 | P := AnsiStrPos(PChar(s), PChar(SubStr)); 215 | if P <> nil then 216 | Result := Integer(P) - Integer(PChar(s)) + 1; 217 | end; 218 | 219 | // 3. PChar类型和string类型可以在双目运算表达式中混合使用。在运算时会将PChar类型自动转化成string类型。 220 | // 4. 如果程序的参数或者对象方法以string为参数,那么可以传入实参PChar类型。 221 | ``` 222 | 223 | 3. 将string类型转化成PChar类型时,要注意以下问题。 224 | 225 | ```pascal 226 | // 1. 如果S为string类型的表达式,PChar(S)将S转化成以null结尾的字符串,PChar(S)为指向S中的第一个字符串。 227 | // 2. 228 | ``` 229 | 230 | 4. 动态分配内存的函数都是在堆中申请的,使用完成后必须要释放。 231 | 232 | ```pascal 233 | var 234 | P: PChar; 235 | begin 236 | GetMem(P, Size); // 使用GetMem申请的内存必须用FreeMen释放。 237 | FreeMem(P); 238 | P := nil; 239 | end; 240 | ``` 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /delphi主要基类及其派生类的关系.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/delphi主要基类及其派生类的关系.png -------------------------------------------------------------------------------- /delphi判断文件是否在使用.md: -------------------------------------------------------------------------------- 1 | # 判断文件是否被使用 2 | 3 | ```pascal 4 | // 检查文件是否被使用 5 | function CheckFileInUse(FileName: string): Boolean; 6 | var 7 | iSum: Integer; 8 | begin 9 | Result := True; 10 | iSum := 1; 11 | while iSum <= 10 do 12 | begin 13 | Result := IsFileInUse(FileName); 14 | if Not Result then 15 | Break; 16 | Sleep(1000); 17 | Inc(iSum); 18 | end; 19 | end; 20 | 21 | // 判断文件是否被使用 22 | function IsFileInUse(fName: string): boolean; 23 | var 24 | HFileRes: HFILE; 25 | begin 26 | Result := False; 27 | if not FileExists(fName) then 28 | exit; 29 | HFileRes := CreateFile(pchar(fName), GENERIC_READ or GENERIC_WRITE, 0, nil, 30 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 31 | Result := (HFileRes = INVALID_HANDLE_VALUE); 32 | if not Result then 33 | CloseHandle(HFileRes); 34 | end; 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /delphi对象模型.md: -------------------------------------------------------------------------------- 1 | # delphi对象模型 2 | 3 | ## 类 4 | 5 | 1. 所有的数据成员必须在方法和属性之前。 6 | 7 | 2. 类可以实现任意数目的接口 8 | 9 | 3. 构造函数是类函数,可以无需实例化就可以调用。 10 | 11 | 4. 对象时类的动态实例。动态实例包含了在类及它的祖先类中声明的所有类成员的值。我们使用的对象变量实际上是对象的一个引用,该引用保存在栈中而对象总是动态分配在堆上,因此对象引用是真正指向对象的指针。 12 | 13 | 5. 每个对象都有对其所有数据成员的一个独立的拷贝。一个数据成员不能在多个对象中共享。 14 | 15 | 6. 类型判断操作符is: 对象变量 is 类名。 16 | 17 | 7. 类型强制装换符as: 对象变量 as 类名。如果对象或接口变量时nil,则结果时nil。 18 | 19 | 8. 隐藏参数**self** 20 | 21 | 每一个方法中,delphi声明self变量为一个隐藏参数。在普通方法中,self变量的值时对象的引用,在一个类方法中,self变量的值时类的引用。 22 | 23 | self是一个隐藏的参数,是表示自身的变量,调用对象方法时,该参数便被传递了过来。这相当于每个方法体中都有一个隐式**with self do ** 。也就是说,所有的数据成员、方法和属性在作用域中,无需显示的引用self就可以访问他们。 24 | 25 | 它常被用于以下三种情况: 26 | 27 | * 如果在方法内调用另外一个对象的方法时,需要将自身参数传递过去,那么把self传递过去好了。 28 | * 如果派生类和基类出现了数据成员重名,想要访问基类的数据成员,而每次只能得到派生类本身的重名数据成员,使用强制转换**基类名(self).数据成员** 。 29 | * 如果在方法内调用了与数据成员同名的局部变量,则使用变量名字只能放问道局部变量,使用**self.变量名** 便访问到了数据成员。 30 | 31 | ## 方法 32 | 33 | 1. 在类方法中,Self不引用对象,而是引用类。 34 | 35 | -------------------------------------------------------------------------------- /delphi源代码分析.md: -------------------------------------------------------------------------------- 1 | # 源代码分析 2 | 3 | ## 第一章 最小化内核 4 | 5 | * 废话不多说,先把源码贴上来 6 | 7 | ```pascal 8 | //系统内核单元 9 | unit System; 10 | 11 | interface 12 | 13 | procedure _InitExe; // 程序的入口 14 | procedure _HandleFinally; // 异常处理 15 | procedure _Halt0; // 程序的出口 16 | 17 | const 18 | Kernel32 = 'kernel32.dll'; 19 | User32 = 'user32.dll'; 20 | 21 | type 22 | TGUID = record // Com支持 23 | D1: LongWord; 24 | D2: Word; 25 | D3: Word; 26 | D4: array[0..7] of Byte; 27 | end; 28 | 29 | implementation 30 | 31 | procedure ExitProcess (uExitCode: LongWord); stdcall; 32 | external kernel32 name 'ExitProcess'; 33 | 34 | procedure _InitExe; 35 | asm 36 | end; 37 | 38 | procedure _HandleFinally; 39 | asm 40 | end; 41 | 42 | procedure _Halt0; 43 | begin 44 | ExitProcess(0); 45 | end; 46 | 47 | end. 48 | ``` 49 | 50 | ```pascal 51 | //系统初始化单元 52 | unit SysInit; 53 | 54 | interface 55 | 56 | var 57 | TlsIndex: Integer = -1; 58 | TlsLast: Byte; 59 | 60 | const 61 | PtrToNil: Pointer = nil; 62 | 63 | implementation 64 | 65 | end. 66 | ``` 67 | 68 | * 主程序 69 | 70 | ```pascal 71 | //示例程序。目标文件为3584 Bytes 72 | program MiniDExe; 73 | 74 | uses 75 | SysInit in 'sys\SysInit.pas', 76 | system in 'sys\system.pas'; 77 | 78 | function ShowMessageBox(hWnd: LongWord; lpText, lpCaption: PChar; uType: LongWord): Integer; 79 | stdcall; external user32 name 'MessageBoxA'; 80 | 81 | const 82 | MB_ICONINFORMATION = $00000040; 83 | 84 | begin 85 | ShowMessageBox(0, 'Written in pure Delphi!', 'Hello World!', MB_ICONINFORMATION); 86 | end. 87 | ``` 88 | 89 | * 为什么要研究最小化内核?这个内核已经不能在精简了,但是如果要了从源代码一级分析delphi是如何将对象,组件库,接口等框架技术包裹在应用程序之上的,那么从一个最简化内核分析再简单不过了。 90 | 91 | ## 第二章 基本数据类型的实现 92 | 93 | * 深入了解delphi各种数据类型的实现,是分析delphi源代码的第一步,其中最重要的两点是 94 | 95 | 1. 数据类型的内存占用和内存布局 96 | 2. 强制类型转换 97 | 98 | ### 基本数据类型 99 | 100 | ![](delphi中基础数据类型.png) 101 | 102 | * 这里的长度仅指参考长度(字节),在代码中可以使用sizeof()来获得。 103 | 104 | ### 变量与常量 105 | 106 | * 根据变量在源代码中定义的位置,可以分为全局变量和局部变量,在一个例子中获取全局变量和局部变量的地址(下面是自己的结果) 107 | 108 | ```pascal 109 | Globl Var 1 : 00417E78 110 | Globl Var 2 : 00417E7C 111 | Local Var 1 : 0018FF58 112 | Local Var 2 : 0018FF54 113 | ``` 114 | 115 | * 根据上面的结果可以得出两个结论,1是全局变量和局部变量存放的位置不同,2是全局变量的地址是递增的,局部的是递减的。 116 | * 全局变量在引用程序的数据区分配,局部变量在栈中分配。 -------------------------------------------------------------------------------- /delphi看不懂的类型.md: -------------------------------------------------------------------------------- 1 | ```pascal 2 | HWND = type LongWord; 3 | type 4 | WPARAM = INT_PTR; 5 | LPARAM = INT_PTR; 6 | INT_PTR = Integer; 7 | UINT = LongWord; 8 | DWORD = Types.DWORD; 9 | DWORD = LongWord; 10 | ``` 11 | 12 | 13 | 14 | # absolute 15 | 16 | ```pascal 17 | // 今天看书遇到了这个关键自,还是挺好玩的,使用这个关键字来定义的变量会和其他变量共享内存 18 | procedure TForm13.btn2Click(Sender: TObject); 19 | var 20 | num: Integer; 21 | shnum: Byte absolute num; 22 | begin 23 | num := 512; // 这个时候shnum 的值为0, 因为shNum和num共享内存,他和shNum共享了低1个字节的内存,所以shnum的值为0。 24 | shnum := 1; // 将num的低1个字节设置为了1,所以值为513; 25 | ShowMessage(IntToStr(num)); // num 的值为 513; 26 | end; 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /delphi算法与数据结构.md: -------------------------------------------------------------------------------- 1 | # 数组类 2 | 3 | * 数组对象中元素的大小怎么确定,什么时候确定 4 | 5 | > 数组是相同大小元素的数据组合,所以数组类中数组元素的大小可以在构造函数赋值。 6 | 7 | * 数组对象中元素个数有没有限制,数组中元素最大多少个,数组能够动态分配内存吗?不仅仅是能够增加内存,还要减少内存。在增加或减少内存的时候能不能快速分配内存,不需要移动元素实现数据的插入或删除。数组能不能自身实现越界检查,当数组越界的时候能够自动提醒错误,不是程序崩溃,也不用人为的去控制。 8 | 9 | > 10 | 11 | * 12 | 13 | # TList 14 | 15 | * 删除TList中元素的时候要注意什么问题 16 | 17 | > 删除TList后,TList中的内容都没有得到释放,这样有一个好处就是当有多个TList中有同一个对象时,那么就不会导致释放掉一个TList后另外一个TList中的对象就不能使用了。 18 | > 19 | > 当循环删除TList中的内容时,如果从0到n的顺序删除,那么就会出现一个内部数据移动的问题,例如删掉第0个之后,那么原来的第1个编程第0个,而这时i的值为1。所以删除TList中数据的时候要倒序删除 20 | 21 | ```pascal 22 | // 当删除某些内容时 23 | for i := Pred(MyList.Count) to 0 do 24 | begin 25 | if SomeConditionApplies(i) then // 如果满足删除条件的话 26 | begin 27 | TObject(MyList[i]).Free; 28 | MyList.Delete(i); 29 | end; 30 | end; 31 | // 如果要全部删除时 32 | for i := 0 to Pred(MyList.Count) do 33 | TObject(MyList[i]).Free; 34 | MyList.Clear; 35 | ``` 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /delphi精要.md: -------------------------------------------------------------------------------- 1 | 1. 无类型参数前面必须加上Const、Out或Var前缀;无类型参数不能指定默认值。 2 | 2. 在使用无类型参数的时候必须对参数进行显示,这种限制一般在过程的实现中完成的,在运行时检查参数值的实际类型。 3 | 3. 4 | 5 | # 4 VCL入门 6 | 7 | ## 4.2 组件与控件的概念 8 | 9 | 1. VCL是可视化组件库的简称(Visual Component Library)。 10 | 2. 一个组件就是类。该类按照面向对象编程的原理封转了一系列功能。在程序中,我们可以创建类的实例,即对象。 11 | 3. 其次,在delphi的组件面板,有很多分页,如“Standard”、“Additional”、“System”等。每个组件页下有很多组件,可以拖入窗体或者数据模块进行设计。因此,我们说组件在程序设计时时可以进行可视化设计的。 12 | 4. 组件的概念就是:可以在程序设计时进行可视化设计的类。在delphi中,一个类被注册后,就会出现在组件页,从而成为一个组件。 13 | 5. 控件是组件的一种。这是根据组件在程序运行时的可视性来划分的,在程序运行时可见的组件就是控件。 14 | 15 | ## 4.3 使用VCL 16 | 17 | * 使用VCL,就是使用它提供的类,组件。可以通过两种途径使用VCL的类,组件。 18 | 1. 设计时加入组件进行属性和事件设计,然后就可以运行程序了。这种是通过静态的方式使用组件。 19 | 2. 用代码创建类的实例(对象),然后使用它提供的功能,最后销毁它。这可以看作是通过动态方式使用组件。 20 | 21 | # 5 VCL精要 22 | 23 | ![VCL架构](VCL架构.png) 24 | 25 | * TObject(单元:System,主要定义了四类功能的虚方法) 26 | 27 | 1. 对象的构造和析构函数 28 | 2. 返回运行时类型信息。虽然TObject提供了此功能,但是它隐藏了,而在子类TPersistent中公开 29 | 30 | ```pascal 31 | class function ClassName: shortString; {返回类或者对象的类型名} 32 | function ClassType: TClass; {返回对象的类型(即类引用)} 33 | Class function ClassParent: TClass; {返回类或者对象的父类类型} 34 | Class function ClassInfo: Pointer; {返回类或者对象的运行时类型信息表的地址} 35 | ``` 36 | 37 | 3. 支持消息处理。由方法Dispatch和DefaultHandler提供。 38 | 4. 支持接口实现。由方法GetInterface和类方法GetInterfaceEntry、GetInterfaceTable提供。 39 | 40 | * TPersistent(单元:Class,抽象类) 41 | 42 | 1. 对象的相互复制。AssignTo和Assign这两个虚方法提供,他们都要子类具体实现。 43 | 2. 在流里读写属性的能力。 44 | 3. 提供了RTTI的能力。 45 | 46 | ```pascal 47 | TPersistent = class(TObject) 48 | ``` 49 | 50 | * TComponent(单元:Classes,抽象类) 51 | 52 | 1. 注册后可以出现在组件页;设计时可见、可以管理; 运行时不可见。 53 | 2. 可以拥有别的对象而成为其他对象的拥有者(Owner)。 54 | 3. 加强了流读写能力。 55 | 4. 可以转化为ActiveX空间和别的COM类。 56 | 57 | TComponent 是抽象类,不要直接创建其实例。如果你需要开发运行时不可见组件,可以从TComponent 继承,否则可以从TWinControl 或其子类继承。 58 | 59 | * TControl(Controls) 60 | 61 | TControl是控件类。所谓控件,时运行时可见的组件。VCL所有控件都是TControl的直接或间接子类。 62 | 63 | * TWinControl((Controls) 64 | 65 | TWinControl时所有窗口类控件的祖先类。窗口控件有以下特点: 66 | 67 | 1. 可以有输入焦点。 68 | 2. 可以接受键盘输入 69 | 3. 可以作为其他控件的容器 70 | 4. 有句柄(Handle)属性。 71 | 72 | TWinControl 定义了窗口空间的共同属性、方法、事件。对于不同类型的窗口空间,VCL定义类TCustomComBox、TCustomEdit等多个i类。大部分窗口控件都不是直接从TWinControl 而是从其子类派生。TWinControl的窗口图像是通过调用Windows底层方法内部完成的,而不能自定义绘制图形图像。 73 | 74 | * TGraphicControl(Controls) 75 | 76 | TGraphicControl是所有非窗口类控件的祖先类。 77 | 78 | 非窗口空间也有四个特点,而这些特点和TWinControl的四个特点相反,所以它是轻量级控件,资源消耗比TWinControl 少很多。 79 | 80 | TGraphicControl 增加了非常重要的Canvas(画布,TCanvas类型)属性,从而提供在控件表面自定义绘制图形图像的能力;增加了Paint 方法类响应WM_PAINT 消息,实现自定义绘制。如在TSpeedButton上可以绘制一个图像。 81 | 82 | 特别指出:TCustomControl从TWinControl继承,是窗口类。但是也具有非窗口类的特点:具有Canvas 属性和Paint 方法。 83 | 84 | ## 5.1.2 构造和析构的内幕 85 | 86 | * TObject 中只声明了Create 方法和 Destroy 方法。没有实现这两个方法。事实上编译器会在这两个函数调用之前插入_ClassCreate 和 _ClassDestroy。也就是说,Create 和Destroy 只是在对象已经构造和析构前初始化和反初始化对象成员。 87 | 88 | * 在VCL组件开发是,常常有如下代码 89 | 90 | ```pascal 91 | type 92 | TDemoClass = class(TComponent) 93 | private 94 | OneObject: TOneClass; 95 | public 96 | Constructor Create(AOwner: TComponent); override; 97 | destructor Destroy; override; 98 | end; 99 | 100 | constructor TDemoClass.Create(AOwner: TComponent); 101 | begin 102 | inherited Create(AOwner); 103 | // 或者直接写‘inherited;’。当要继承的一个方法已被重载时,必须书写完整,不然编译器不知道你要继承那个方法。这行是继承父类的Create代码。 104 | OneObject := TOneClass.Create(Self); 105 | end; 106 | 107 | destructor TDemoClass.Destory; 108 | begin 109 | OneObject.Free; 110 | // 或者FreeAndNil(OneObject); 111 | inherited Destroy; 112 | end; 113 | ``` 114 | 115 | TComponent 中重新声明了Create方法 116 | 117 | ```pascal 118 | constructor Create(AOwner: TComponent); virtual; 119 | ``` 120 | 121 | 在TComponent及其子类中,已经不在可能使用无参数的TObject.Create了。 122 | 123 | 另外,在整个构造和析构过程中还调用了TObject两个虚方法:AfterConstruction 和 BeforeDestruction。在子类中覆盖他们之后,可以做一些发夹工作。比如:在TCustomForm(TForm的父类)中用来触发OnCreate和OnDestroy: 124 | 125 | ```pascal 126 | type 127 | TCustomForm = class(TScrollingWinControl) 128 | public 129 | procedure AfterConstruction; override; 130 | procedure BeforeDestruction; override; 131 | end; 132 | 133 | procedure TCustomForm.AfterConstruction; 134 | begin 135 | if not OldCreateOrder then DoCreate; 136 | // 然后在DoCreate中调用FOnCreate, 从而触发事件OnCreate. 137 | end; 138 | 139 | procedure TCustomForm.BeforeDestruction; 140 | begin 141 | if not OldCreateOrder then DoDestroy; 142 | //然后在DoDestroy中调用FOnDestroy, 从而触发时间OnDestroy. 143 | end; 144 | ``` 145 | 146 | 总上所述,一个对象总的构造和析构过程如下: 147 | 148 | ```pascal 149 | _ClassCreate -> Create -> AfterConstruction(->DoCreate/ OnCreate) 150 | -> 使用对象 -> 151 | BeforeDestruction -> (DoDestroy/ OnDestroy ->) Destroy -> _ClassDestroy. 152 | ``` 153 | 154 | ## 5.1.3 虚拟方法表和动态方法 155 | 156 | * 首先明确本书中的两个概念:虚方法和虚拟方法。 157 | 158 | ***虚方法***指的是用***virtual***和***dynamic***两个关键字来修饰的方法。***虚拟方法***是用***virtual***来修饰的方法,***动态方法***是用***dynamic***来修饰的方法。 159 | 160 | * VMT是***虚拟方法表***,虚拟方法表被分成了一个个的大小为4个字节的指针。 161 | 162 | * ![VMT结构图](VMT结构图.png) 163 | 164 | * 从上图可以看出,一个对象实际上是一个指针,该指针指向对象的实际数据区,也被称为对象数据区。那么对象的字段,方法,属性和事件这些对象数据在“对象数据区”中怎么组织的呢? 165 | 166 | 1. 头4个字节存放一个指针,该指针指向另一个地址区域。 167 | 2. 区域小区域分别存储对象的各种数据成员(即字段,不包括方法。方法的入口被定义在另一个内存表中,虚拟方法表中有一个指针指向该表) 168 | 169 | 头4个指针指向虚拟方法表,虚拟方法表有被分成了很多个大小为4个字节的指针,虚拟方法表中正偏移的区域的每个指针对应一个虚拟方法的入口地址。虚拟方法表中负偏移的区域的每个指针用来存放字段,属性值和所有非虚方法的入口地址。(好像所有非虚方法也有一张表来存储)。 170 | 171 | * 虚拟方法表的结构 172 | 173 | 1. VMT还可以细分成两个区域,即基础信息区和用户定义虚拟方法区。 174 | 2. VMT对应于类而不是对象。 175 | 176 | * VMT的负偏移区是基础信息区(-76~0),存储了基础性数据,如实例大小,基础性数据的指针,如接口表,运行时类型信息表,字段表,方法表,类名和父类虚拟方法表等和所有基础性虚拟方法,这些方法都是在类TObject中定义的,如:AfterConstruction,BeforeDestruction,DefaultHandler,Destroy的指针。所以基础信息区并不全是指针列表。这个区域所存放的数据和指针主要是用来帮助实现对象的构造和析构,运行时类型信息存取,字段和方法解析等。基础信息区的大小是固定的。 177 | 178 | * VMT的正偏移区(从0开始)是用户定义的虚拟方法(即所有非TObject定义的虚拟方法)所在区域,每4个字节存储一个用户定义的虚拟方法指针。这些虚拟方法不管是在本类中定义的。还包括从TObject一直到本类的所有中间类定义的所有虚拟方法。因此,这个区域的大小是由虚拟方法的多少决定的。 179 | 180 | * 不同的类总是具有独立的VMT,也就是说,VMT是根据类的定义来生成的,跟类实例没有关系。 181 | 182 | * 类引用类型的定义 183 | 184 | ```pascal 185 | function TObject.ClassType: TClass; 186 | begin 187 | Pointer(Result) := PPointer(Self)^; 188 | end; 189 | ``` 190 | 191 | 这里为什么将Result强制转化成Pointer类型? 192 | 193 | PPointer(Self) 是将Result 强制转化成指向指针的指针类型,PPointer 类型变量中的内容一定是一个指针。也就是Pointer类型,Pointer变量中存的是无类型变量的地址,他和PInteger,PBoolean不同。PInteger 类型的指针变量存的一定是一个Integer类型变量的地址,因此,必须要把Result 转化成一个指向无类型变量的指针才能够赋值,不然会出错。 194 | 195 | * 可见,类引用实际上就是指向VMT的指针。也就是说,类引用和VMT有唯一的对应关系。 196 | 197 | * 类的VMT有编译器产生,就等着一些指针区指向他。 198 | 199 | * 如果TParent定义了一个虚方法F,并且实现了部分功能,然后TChild派生于TParent,并覆盖了虚拟方法F,并做了附加功能实现。这个时候编译器在编译的时候这两个类会各自产生一个VMT,每个VMT中都会有各自的F指针,互不相关。 200 | 201 | ## 5.1.4 TObject 如何使用虚拟方法表 202 | 203 | * 虚拟方法表的分布图 204 | 205 | ![虚拟方法表](虚拟方法表.png) 206 | 207 | ![](虚拟方法表1.png) 208 | 209 | ![](虚拟方法表2.png) 210 | 211 | ## 5.2 VCL消息机制 212 | 213 | * Windows是一个基于消息的操作系统。当运行程序的时候,程序中每个有窗口的控件(TWinControl,也就是有句柄的控件)都会在Windows中注册一个窗口过程,这个过程在VCL中叫做MainWndProc。Windows消息到达MainWndProc后,由MainWndProc调用一系列方法来处理这个消息。 214 | 215 | VCL消息机制的整个流程如下: 216 | 217 | ```pascal 218 | Windows -> Delphi Application -> TWinControl -> MainWndProc —> WndProc -> Dispatch -> Handler 219 | ``` 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /一维动态字符数组的内存存储格式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/一维动态字符数组的内存存储格式.png -------------------------------------------------------------------------------- /三维数组内存结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/三维数组内存结构.png -------------------------------------------------------------------------------- /二维数组.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/二维数组.png -------------------------------------------------------------------------------- /二维数组内存结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/二维数组内存结构.png -------------------------------------------------------------------------------- /体检.md: -------------------------------------------------------------------------------- 1 | # 体检 2 | 3 | ## 重要的单元 4 | 5 | ```pascal 6 | ufmPrjCheck.pas // 体检单元 7 | uCheckConst.pas // 体检常量单元,例如接口的id 8 | uPrjCheckBusi.pas // 这个单元包含了体检事务逻辑 9 | ``` 10 | 11 | * 接口 12 | 13 | ```pascal 14 | IChkItem_Trans = interface 15 | // 执行检查,需要扫描项目结构表,然后循环 16 | // 返回值,False-不通过检查 17 | function DoExcute; 18 | // 初始化 19 | procedure Init(); 20 | end; 21 | 22 | IChkItem_Xmjg_Trans = interface(IChkItem_Trans) 23 | // 判断是否需要进行检查 24 | function IsCheck(ADataContext: IDataContext): Boolean; 25 | // 处理检查项内容 26 | function ProcessChkItem(ADataContext: IDataContext): Boolean; 27 | end; 28 | ``` 29 | 30 | 31 | 32 | * 基类 33 | 34 | ```pascal 35 | TChkItem_Trans = class(TInterfacedObject, IChkItem_Trans) 36 | 37 | TChkItem_Xmjg_Trans = class(TChkItem_Trans, IChkItem_Xmjg_Trans) 38 | function IsCheck(ADataContext: IDataContext): Boolean; 39 | function ProcessChkItem(ADataContext: IDataContext): Boolean; 40 | end; 41 | 42 | TChkItem_FBFX_Trans = class(TChkITem_Xmjg_Trans) 43 | //执行清单检查 44 | function ProcessChkItem(ADataContext: IDataContext): Boolean; override; 45 | //具体的清单检查信息(在分部分项中才出现的) 46 | procedure CheckContent(ANode: IPMNode; AModelType: TModuleType; AGcGuid: string); virtual; 47 | end; 48 | 49 | TChkItem_QD_Trans = Class(TChkItem_FBFX_Trans) 50 | ``` 51 | 52 | * 体检逻辑 53 | 54 | ```pascal 55 | // 1.先找到体检单元,遍历体检项,如果level = 2,那么就将state字段的内容赋值成待查,如果level != 2,那么将IsCheck字段赋值成True; 56 | // 2.ufmPrjCheck.pas 单元中 btn_Dialogs_CheckClick(Sender: TObject) 函数中 FPrjCheck.DoExcuteCheck(Handle) 才开始体检。 57 | var 58 | CheckItem: IChkItem_Xmjg_Trans; 59 | begin 60 | guid := FDataSet.GetFieldValueAsString(cst_chkitem_data_guid, '', pRec); 61 | CheckItem := FChkItemMgr.GetTransInterface(guid); 62 | CheckItem.Init(FPrjFile, FFileContext, FSysOption, FFileOption, FChkItemLog, FPrjCheckOption, guid); 63 | bState := CheckItem.DoExcute; 64 | end; 65 | 66 | // 3. 首先获得XmData DataContext 67 | DataContext := FPrjFile.GetDataContext(cst_DataContext_XmData); 68 | // 4. 获得XmData 中的 Xmjg DataSet 69 | DataContext.GetDataSet(cst_Table_Xmjg); 70 | 71 | function CheckItem.DoExcute: Boolean; 72 | var 73 | I: Integer; 74 | Pre: Pointer; 75 | DataContext: IDataContext; 76 | Xmjg: IPMDataSet; 77 | begin 78 | Xmjg := GetXmjgDataSet; 79 | if Xmjg <> nil then 80 | begin 81 | for i:= 0 to Xmjg.GetRecordCount - 1 do 82 | begin 83 | prec := Xmjg.GetRecList[i]; 84 | DataContext := FPrjFile.GetDataContext(xmjg.GetFieldValueAsString(cst_XmJg_Guid, '', prec)); 85 | if (DataContext <> nil) and IsCheck(DataContext) then 86 | begin 87 | Result := ProcessChkItem(DataContext) and Result; 88 | end; 89 | end; 90 | end; 91 | end; 92 | 93 | ``` -------------------------------------------------------------------------------- /使用对象.md: -------------------------------------------------------------------------------- 1 | # 使用对象 2 | 3 | ​ Windows应用程序是基于windows窗体的,通过windows程序的窗体,可以完成windows消息循环,保证程序的正常运行。如果使用纯API编写Windows应用程序,创建窗体,处理消息会很麻烦。但是delphi提供了Application对象。Application对象创建的应用程序实际上是一个没有大小,不可见的窗体,他可以出现在windows任务栏上。应该说,程序中所有的**Form** 都是它的子窗体。 -------------------------------------------------------------------------------- /关系分析.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```pascal 4 | // 项目管理类 5 | IPrjDataManager = interface 6 | // 加载文件数据 7 | function LoadPrj(const FileName: string; bDecompress: boolean = false): boolean; 8 | // 保存文件数据 9 | function SavePrj(const FileName: string = ''; bCompress: Boolean = false; 10 | bShowProgess: boolean = false; AOwnerHandle: Cardinal = 0; 11 | bInternalMode: boolean = False; bBackUp: boolean = True;): boolean; 12 | function SavePrjToStream(AStream: TStream): boolean; 13 | // 设置工程文件名,为了解决工程保存到临时文件后,FileName被更改到临时文件名了,需要通过此方法 14 | function SetFileName(const AValue: string); 15 | end; 16 | 17 | 18 | // 招标业务接口 19 | IDzpb_ZB_Trans 20 | function Export(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 21 | PrjFile: IPrjDataManager; DzpbKind: TDzpbKind): Boolean; // 导出招标文件 22 | end; 23 | // 招标业务基类 24 | TDzpb_ZB_Trans = class(TInterfacedObject, IDzpb_ZB_Trans) 25 | function Export(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 26 | PrjFile: IPrjDataManager; DzpbKind: TDzpbKind): Boolean; virtual; abstract; // 导出招标文件 27 | end; 28 | // 招标业务类(具体应用) 29 | TDzpb_ZB_XML = class(TDzpb_ZB_Trans) 30 | function Export(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 31 | PrjFile: IPrjDataManager; DzpbKind: TDzpbKind): Boolean; // 导出XML类型的招标文件 32 | end; 33 | TDzpb_ZB_MDB = class(TDzpb_ZB_Trans) 34 | function Export(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 35 | PrjFile: IPrjDataManager; DzpbKind: TDzpbKind): Boolean; override; // 导出MDB类型的文件 36 | end; 37 | // 实际应用 38 | TZB_MDB_GD_ZJ_WZ_SZYL = class(TDzpb_ZB_MDB) // 广达温州市政园林招标 39 | function GetExportZbClass: TDzpb_MDB_ExportClass; override; 40 | Result := TZB_MDB_Export_GD_ZJ_WZ_SZYL; 41 | end; 42 | // 温州市政园林电子评标 43 | TZB_MDB_Export_GD_ZJ_WZ_SZYL = class(TDzpb_MDB_Export) 44 | procedure ExportXmData_XmTree(SsDataView: IPMDataView); override; // 导出项目树 45 | procedure ExportXmData_JsxmNode(SsNode: IPMNode; DataContext: IDataContext; 46 | sManageNodeId: string); override; // 导出建设节点 47 | procedure ExportXmData_DwgcNode(SsNode: IPMNode; DataContext: IDataContext 48 | sManageNodeId: string); override; // 导出单位节点 49 | procedure ExportXmData_ZygcNode(SsNode: IPMNode; DataContext: IDataContext; 50 | sManageNodeId: string); override; // 导出专业节点 51 | end; 52 | // MDB导出业务基类 53 | TDzpb_MDB_Export = class 54 | end; 55 | 56 | // 投标业务接口 57 | IDzpb_TB_Trans 58 | function Import(DzpbService: IYsServiceManager; SysOption: IYsSysOption; 59 | PrjFile: IPrjDataManager; const FileName: string): boolean; // 导入招标文件 60 | function Update(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 61 | PrjFile: IPrjDataManager): boolean; // 跟新招标文件 62 | function Export(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 63 | PrjFile: IPrjDataManager; DzpbKind: TDzpbKind): boolean; // 导出投标文件 64 | end; 65 | // 投标业务基类 66 | TDzpb_TB_Trans = class(TObjectedInterface, IDzpb_TB_Trans) 67 | function Import(DzpbService: IYsServiceManager; SysOption: IYsSysOption; 68 | PrjFile: TPrjDataManager; const FileName: string): boolean; virtual; abstract; // 导出招标文件 69 | function Update(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 70 | PrjFile: IPrjDataManager):boolean virtual; //更新招标文件 71 | function Export(DzpbService: IYsServiceManager; FileContext: ISSFileContext 72 | PrjFile: TPrjDataManager): boolean; virtual; // 导出招标文件 73 | end; 74 | // 投标业务具体应用 (投标--Xml数据转换流程) 75 | TDzpb_TB_XML = class(TDzpb_TB_Trans) 76 | function Import(DzpbService: IYsServiceManager; SysOption: IYsSysOption; 77 | PrjFile: IPrjDataManager; const FileName: string): Boolean; override; // 导入招标文件 78 | function Update(DpzbService: IYsServiceManager; FileContext: ISSFileContext; 79 | PrjFile: TPrjDataManager): boolean; override; // 跟新招标文件 80 | function Export(DzpbService: IYsServiceManager; SysOption: IYsSysOption; 81 | PrjFile: TPrjDataManager): boolean; override; // 导出投标文件 82 | end; 83 | // 投标业务具体应用 (投标--MDB数据转换流程) 84 | TDzpb_TB_MDB = class(TDzpb_TB_Trans) 85 | function Import(DzpbService: IYsServiceManager; SysOption: IYsSysOption; 86 | PrjFile: TPrjDataManager): boolean; override; 87 | function Update(DzpbService: IYsServiceManager; FileContext: ISSFileContext; 88 | PrjFile: IPrjDataManager): boolean; override; 89 | function Export(DzpbService:IYsServiceManager; FileContext: ISSFileContext; 90 | PrjFile: IPrjDataManager; DzpbKind: TDzpbKind): boolean; override; 91 | end; 92 | // 广达温州市政园林投标 93 | TTB_MDB_GD_Zj_WZ_SZYL = class(TDzpb_TB_MDB) 94 | function GetRealFileName(SysOption: IYsSysOption; sSrcFileName: string): string; override; 95 | function GetImportBusiClass: TDzpb_MDB_ImportClass; override; 96 | function GetUpdateBusiClass: TDzpb_MDB_UpdateClass; override; 97 | function GetExportBusiClass: TDzpb_MDB_ExportClass; override; 98 | end; 99 | 100 | TDzpb_MDB_ImportClass = class of TDzpb_MDB_Import; 101 | TDzpb_MDB_Import = class 102 | end; 103 | 104 | TDzpb_MDB_UpdateClass = class of TDzpb_MDB_Update; 105 | TDzpb_MDB_Update = class 106 | end; 107 | 108 | TDzpb_MDB_ExportClass = class of TDzpb_MDB_Export; 109 | TDzpb_MDB_Export = class 110 | end; 111 | ``` 112 | 113 | -------------------------------------------------------------------------------- /判断文件是否是本地文件.md: -------------------------------------------------------------------------------- 1 | # 判断文件是否是本地文件 2 | 3 | ```pascal 4 | // GetDriveType 是Windows 单元的函数 5 | function IsLocalDisk(Drive: string): Boolean; 6 | begin 7 | Result := GetDriveType(PWideChar(Drive)) = DRIVE_FIXED; 8 | end; 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /参透Delphi.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 第一章 Object Pascal 语言精义 4 | 5 | ## 对指针的理解 6 | 7 | ```pascal 8 | // 指针变量,首先是一个变量,它里面存的是一个地址。 9 | // 它的使用和普通变量类似,例如 10 | iVar: Integer; 11 | iVar := 1; 12 | // 当使用iVar这个变量的时候,这个变量就是1。 13 | iVar := iVar + 1; // iVar = 2; 14 | 15 | //指针变量类似, 16 | Ptr: Pointer; 17 | Ptr := @iVar; 18 | // 这里Ptr中存的就是iVar的地址。当使用这个变量的时候,获得的就是iVar的地址。 19 | sVar: string; 20 | sVar := IntToHex(Integer(Ptr), 8); // sVar的值是005221E8 21 | 22 | // 过程类型的变量是一种特殊的变量,过程类型的变量是一个指向过程或函数地址的指针变量,而把过程类型变量使用解释为对该过程或函数地址的引用。 23 | ``` 24 | 25 | 26 | 27 | 28 | 29 | # 第三章 数据组织 30 | 31 | ## 3-1 数据的组织结构 32 | 33 | ### 3-1-1 同质数据 34 | 35 | * 某些数据结构保存的数据性质完全相同的数据 36 | 37 | * 一维静态数组,就是普通数组。 38 | 39 | 普通数组相同大小,类型 可以直接相互赋值 40 | 41 | ```pascal 42 | var 43 | A, B: array[1..10] of Integer; 44 | i: Integer; 45 | begin 46 | for i := 1 to 10 do 47 | begin 48 | A[i] := i; 49 | end; 50 | B := A; // 数组直接赋值 51 | end; 52 | ``` 53 | 54 | * 标准函数Low和High返回一维数组索引下标的上界和下界的序数,标准函数Length返回数组的元素个数。 55 | 56 | * 二维静态数组,二维数组中第一个元素值指的是列。 57 | 58 | ```pascal 59 | var 60 | M: array [1..3, 1..4] of Integer; 61 | i, j: Integer; 62 | begin 63 | for i := 1 to 3 do 64 | for j := 1 to 4 do 65 | A[i, j] := 4 * (i - 1) + j; 66 | end; 67 | ``` 68 | 69 | ![二维数组](二维数组.png) 70 | 71 | * 因为内存是线性的,所以矩阵在内存中也是线性存储的。将二维数据转换成一维线性表存储是,Delphi总是一行一行来存储。如果M的首地址为p,元素个数为m * n,每个元素占用4个字节,那么元素M[i, j]的地址为 72 | 73 | ```pascal 74 | p + 4(n(i - 1) + j - 1) // m[i, j] 的地址 75 | ``` 76 | 77 | 78 | 79 | ![二维数组内存结构](二维数组内存结构.png) 80 | 81 | * 类似的,将三维数据转化成一维线性表是,delphi规定,总是先存储数组最后一维数据,然后才依次存储第二维,第一维的数据。 82 | 83 | ![三维数组内存结构](三维数组内存结构.png) 84 | 85 | * 多维数组的结构: 86 | 87 | 1. 整体上时分为最后一维的数量,然后在每个部分整体分为次一维的数量,然后依次类推。 88 | 89 | 假设三维数组M的首地址为p,元素个数为m * n * q。每个元素占用4个字节,那么元素M[i, j, k] 的地址为 90 | 91 | ```c 92 | p + 4(nq(i - 1) + q(j - 1) + k - 1) // m[i, j, k]的地址 93 | ``` 94 | 95 | * 对于多维数组,标准函数Length、Low和High任然有效。如果将这三个函数直接应用与数组变量名,则Length返回的是第一维的长度,Low返回第一维下标的下界,High返回第一维下标的上界。 96 | 97 | * 短字符串和字符数组(详细间Delphi中字符串详解) 98 | 99 | * 100 | 101 | # 第四章 程序抽象 102 | 103 | ## 4-4 算法抽象 104 | 105 | ### 4-4-2 过程类型 106 | 107 | ​ 在Object Pascal中,函数名至少出现两次,一次是定义,一次是调用。编译器在编译这两次函数名时,使用不同的规范,出现在function后的过程或函数名将解释为程序代码的首地址,而出现在其他位置的过程或函数名将解释为对该地址的引用。 108 | 109 | ​ 当声明和使用过程类型时,为保持过程类型变量与过程或函数名的一致性,编译器将把过程类型变量的声明解释为一个指向过程或函数地址的指针变量,而把过程类型变量使用解释为对该过程或函数地址的引用。 110 | 111 | * **引用:按照C/C++的语义,引用就是指针,但操作时却不需要使用指针运算符。之所以可以使用引用,时因为引用相当于某个变量或值的别名,它没有引入任何新的存储对象,对引用的操作将直接由编译器映射到该变量或值** 112 | 113 | 114 | 115 | # 第十六章 类引用 116 | 117 | * 类引用类型的一个重要的用途就是指导复杂类构造。 118 | 119 | * 一个祖先类的引用类型可以在继承类中作为域和属性的类型,也可以作为方法的参数类型,作为方法的参数类型时多用于构造器的定义。 120 | 121 | ```pascal 122 | type 123 | TColloectionItemClass = class of TCollectionItem; 124 | TCollection = class(TPersistent) 125 | private 126 | //域定义为类引用类型 127 | FItemClass: TCollectionItemClass; 128 | FItems: TList; 129 | public 130 | // 构造器的参数使用类引用类型 131 | constructor Create(ItemClass: TCollectionItemClass); 132 | // 属性定义为类引用类型 133 | property ItemClass: TCollectionItemClass read FItemClass; 134 | end; 135 | 136 | constructor Create(ItemClass: TCollectionItemClass); 137 | begin 138 | FItemClass := ItemClass; 139 | FItems := TList.Create; // 创建另一个类的实例 140 | NotifyDesigner(Self, Self, opInsert); 141 | end; 142 | ``` 143 | 144 | ## 16-2-2 Application 对象与窗体创建 145 | 146 | * 我们知道,窗体对象是在delphi中自动生成的应用工程文件中通过下面的语句创建的 147 | 148 | ```pascal 149 | Application.CreateForm(TForm, Form1) 150 | ``` 151 | 152 | * Application是delphi自动创建的运行类TApplication的对象实例,delphi创建的应用程序都有Application对象。该类的CreateForm方法的原型为 153 | 154 | ```pascal 155 | procedure CreateForm(InstanceClass: TComponentClass; var Reference); 156 | ``` 157 | 158 | 159 | # 第十七章 属性与事件 160 | 161 | ​ 从本质上说,属性对应着某种访问和存储对象内部数据的机制,它可以在不破坏类声明的访问控制(保护机制)的情况下提供一种灵活的访问和存储对象数据的手段;而事件则相当于指向某个方法的属性,它构成了面向组件开发的基础。 162 | 163 | ## 17-1-3 属性的存储说明 164 | 165 | * 一般的,属性的声明中存储说明的形式为 166 | 167 | ```pascal 168 | / 169 | ``` 170 | 171 | 其中stored指示符用于说明published属性值是否存储到窗体文件中;default指示符用于说明当前属性值是否需要流入流出窗体文件;nodefault指示符用于说明属性必须流入流出。这三种说明都是可选的。 172 | 173 | * 注意事项 174 | 175 | 1. 当某个属性值说明为stored时,指示符后面只能取False或True值。 176 | 177 | ```pascal 178 | published 179 | property Year: integer read FYear write SetYear stored False; 180 | property Days: integer read GetDays write SetDays stored IsLeapYe; 181 | // IsLeapYe 是一个返回值为Boolean类型的函数 182 | ``` 183 | 184 | 2. 当某个属性通过stored指示符说明为True时,窗体文件将储存该属性值。当打开窗体文件时可以见到该属性;反之,窗体文件不保存该属性。如果声明时未通过stored指定存储形式,则缺省为True。由于stroed属性说明仅用于说明窗体文件是否保存该属性,所以只有属性为published属性时,才可以进行存储说明。 185 | 186 | 3. 当用default说明属性的流入流出条件时,default指令符后面的属性值的类型必须与属性的类型相同。例如 187 | 188 | ```pascal 189 | property Year: integer read FYear write SetYear default 2001; 190 | ``` 191 | 192 | default说明并不是属性的缺省值,它仅影响属性的流入流出操作。即在stored为True的情况下,只要属性不为后面的值就必须进行流入流出操作。 193 | 194 | 4. 如果祖先类声明了带缺省值的属性,而后代要覆盖它,则如果不指明新缺省值,应使用nodefault进行说明。 195 | 196 | ```pascal 197 | type 198 | TJuControl = class(TControl) 199 | protected 200 | property Tag: Longint read FTag write FTag default 0; 201 | end; 202 | 203 | TJuSubControl = class(TJuControl) 204 | public 205 | property Tag nodefault; 206 | end; 207 | ``` 208 | 209 | 5. 当用default或nodefault对属性进行说明时,属性只能为叙述类型或集合类型。当属性为real,指针或string类型且没有声明缺省值时,默认值为0, nil和空字符串。 210 | 211 | 6. GetYear函数方法的声明必然是: 212 | 213 | ```pascal 214 | function GetYear(): integer; 215 | ``` 216 | 217 | SetYear过程的声明必然是 218 | 219 | ```pascal 220 | procedure SetYear(AYear: integer); 221 | ``` 222 | 223 | 或 224 | 225 | ```pascal 226 | procedure SetYear(const AYear: integer); 227 | ``` 228 | 229 | 230 | 231 | ## 17-2 属性的类型 232 | 233 | ​ 属性可以声明没有***对应域***的属性,但是不可以声明***没有类型***的属性 234 | 235 | ### 17-2-4 对象类型的属性 236 | 237 | * 对象类型属性的Get方法直接使用域,而Set方法不能使用域。例如在VCL中的TShape类声明: 238 | 239 | ```pascal 240 | type 241 | TShape = class(TGraphicControl); 242 | private 243 | FPen: TPen; 244 | FBrush: TBrush; 245 | procedure SetBrush(Value: TBrush); 246 | procedure SetPen(value: TPen); 247 | public 248 | constructor Create(AOwner: TComponent); override; 249 | destructor Destroy; override; 250 | published 251 | property Brush: TBrush read FBrush write SetBrush; 252 | property Pen: TPen read FPen write SetPen; 253 | end; 254 | ``` 255 | 256 | 按照一般原则,一个类(TShape)中所包含的所有类(TPen与TBrush)的构造和析构应该在该类的构造和析构方法中进行: 257 | 258 | ```pascal 259 | constructor TShape.Create(AOwner: TComponent); 260 | begin 261 | inherited Create(AOwner); 262 | FPen := TPen.Create; 263 | FBrush := TBrush.Create; 264 | end; 265 | 266 | destructor TShape.Destroy 267 | begin 268 | FPen.Free; 269 | FBursh.Free; 270 | inherited Destroy; 271 | end; 272 | ``` 273 | ```pascal 274 | 275 | 这个就导致了一个问题,即在设置属性时,既不能直接将FPen与FBrush指向Value 参数指向的对象实例,也不能构造新的FPen与FBrush对象实例,只能调用该类的Assign方法进行对象内容的复制。这表明,只有那些由TPersistent类派生的类才可以作为类的对象属性,故而对象属性的Set方法典型形式为: 276 | 277 | procedure TShape.SetBrush(Value: TBrush); 278 | begin 279 | FBrush.Assign(Value); 280 | end; 281 | 282 | procedure TShape.SetPen(Value: TPen); 283 | begin 284 | FPen.Assign(Value); 285 | end; 286 | ``` 287 | 288 | ***也就是说,如果一个类中要实现对象属性,那么这个类必须是TPersistent 的子类,这个类必须直接或间接从TPersistent这个类继承***。 289 | 290 | 同样的原因也解释了为什么对象属性的设置必须通过Set方法。假设TShape类的Pen属性定义为 291 | 292 | ```pascal 293 | property Pen: TPen read FPen write FPen; 294 | ``` 295 | 296 | 在进行属性赋值时会发生什么情况呢?FPen将指向新的对象实例,其原来的对象实例还是没有释放对象也没有得到访问。 297 | 298 | ### 17-2-5 数组类型的属性 299 | 300 | ​ 数组属性的声明: 301 | 302 | ```pascal 303 | property 属性名[参数表]:类型 read 方法名 write 方法名 304 | ``` 305 | 306 | ​ 参数类型可以是简单类型,例如整数和字符串类型,但是不能是记录类型和类类型。 307 | 308 | ```pascal 309 | type 310 | TNames = array[0..9] of string; 311 | 312 | TJuComponent = class(TComponent) 313 | private 314 | FNames: TNames; 315 | function GetNames(const Index: integer): string; 316 | procedure SetNames(const Index: integer; const Name: string); 317 | function GetIndexs(const Name: string): integer; 318 | procedure SetIndexs(const Name: string; const Index: integer); 319 | public 320 | constructor Create(AOwner: TComponent); override; 321 | property Names[const Index: integer]: string read GetNames write SetNames; 322 | property Indexs[const Name: string]: integer read GetIndexs write SetIndexs; 323 | end; 324 | 325 | function TJuComponent.GetIndexs(const Name: string): integer; 326 | var 327 | i: integer; 328 | begin 329 | Result := -1; 330 | i := -1; 331 | repeat 332 | Inc(i); 333 | until (CompareStr(FName[i], Name) = 0) or (i = 10); 334 | if i < 10 then 335 | Result := i; 336 | end; 337 | 338 | function TJuComponent.GetNames(const Index: integer): string; 339 | begin 340 | Result := ''; 341 | if (Index >= 0 ) or (Index <= 9) then 342 | Result := FNames[index]; 343 | end; 344 | 345 | procedure TJuComponent.SetNames(const Index: integer; const Name: string); 346 | begin 347 | if (Index in (0..9]) and (FNames[Index] <> Name) then 348 | FName[Index] := Name; 349 | end; 350 | ``` 351 | 352 | ​ 使用数组属性时注意事项 353 | 354 | * 在声明数组属性时,访问说明中read或write后必须跟Get或Set方法名,出现域名是不允许的。 355 | 356 | * Get方法的参数表的参数类型和顺序必须与属性参数表相同,Set方法的参数表中必须列出属性的参数表且该列表顺序与属性参数表必须相同,Set方法的最后一个参数为与属性类型相同的值或常数。 357 | 358 | * 数组类型的属性可以使用下标进行访问。下面两句话是等价的 359 | 360 | ```pascal 361 | Memo.Lines.Add(FJuComp.Names[I]); 362 | Memo.Lines.Add(FJuComp.GetNames(I)); 363 | ``` 364 | 365 | * 数组属性声明中不允许进行存储说明,但是可以使用default指示符,此时该指示符不是存储说明,二是指定当前属性为类中的缺省属性。例如 366 | 367 | ```pascal 368 | type 369 | TStringArray = class 370 | public 371 | property Strings[Index: integer]: string ....; default; 372 | end; 373 | ``` 374 | 375 | 此时default前面是有分号的。如果类中有多个属性,只能将一个属性指定为缺省属性。对于缺省数组属性,可以直接使用对象名来访问属性 376 | 377 | ```pascal 378 | var 379 | Str: TStringArray; 380 | begin 381 | Str[1] := 'Delphi'; 382 | end; 383 | ``` 384 | 385 | * 数组属性必须有自己的属性编辑器,对象检查器不能自动编辑此类属性,另外,一般不能将数组属性声明为published 386 | 387 | ## 17-4 方法指针与事件 388 | 389 | ​ 事实上,方法指针是一种特殊的过程类型:一般的过程类型是指向子程序的指针,所以该指针只需要保存该过程或函数的内存地址即可(这需要4个字节的存储空间);而方法指针是指向某个类的某个方法的指针,它用来引用类的方法,因此需要保存两个地址,一个是方法的地址,一个是该方法所属对象的地址(这需要8个字节的存储空间)。 390 | 391 | ​ 这表明,方法指针是带有Self参数的过程类型。正是由于这个差别,一般的过程类型与方法过程类型不兼容。 392 | 393 | ​ 在形式上,与一般过程类型不同,方法指针类型要带有 of object 关键字。 394 | 395 | ```pascal 396 | type 397 | TNotifyEvent = procedure (Sender: TObject) of object; 398 | ``` 399 | 400 | # 第 18 章 运行时类型信息 401 | 402 | ​ 所谓运行时类型信息是指在运行时保存和检索对象和数据类型信息的手段。 403 | 404 | ## 18-2 获取持久类RTTI 405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /各种库中的表结构.md: -------------------------------------------------------------------------------- 1 | # 材料库 2 | 3 | ## 文件结构 4 | 5 | | ClkData | | 6 | | ------- | ---------------------------- | 7 | | | ClMasterDset | 8 | | | ClHcDset | 9 | | | clPhbLb | 10 | | | clRcjLb | 11 | | | ClScLbDset(特殊需求才会用) | 12 | | | ClMlDset(目录) | 13 | 14 | ## 表结构 15 | 16 | * ClMlDset 17 | 18 | | level | guid | id | MlMc | MlZcSm | 19 | | ----- | -------- | ------------ | -------- | ------ | 20 | | 层级 | 自动生成 | 序号(唯一) | 目录名称 | | 21 | 22 | * ClMasterDset 23 | 24 | 人工,材料,主材***都不是可分解材料***,因此没有材料构成。 25 | 26 | 材料与目录的关系是在这张表中显示的。 27 | 28 | 这个表中包含了所有的材料。其中有的材料是由其他材料组成的,材料之间的关系在ClHcDset这张表中。 29 | 30 | | id | ClBm | ClMc | ClDw | ClDej | ClLb | MlId | 31 | | ---------- | -------- | -------- | -------- | ---------- | -------- | ------ | 32 | | 序号(唯一) | 材料编码 | 材料名称 | 材料单位 | 材料定额价 | 材料类别 | 目录id | 33 | 34 | * ClMlDset 和 ClMasterDset 这两个表是通过 id 和 Mlid 来关联的。 35 | 36 | * ClHcDset 37 | 38 | | zid | cid | Hl | 39 | | ------------------ | ---------------- | ------ | 40 | | 可分解材料的id字段 | 分解材料的id字段 | 消耗量 | 41 | 42 | * clRcjLb 43 | 44 | | RcjLx | RcjMc | 45 | | ------------------------------------ | ------------------------ | 46 | | 数字(1:1人工,7:7材料,15:15机械) | 1人工,7材料,15机械等等 | 47 | 48 | # 定额库(有点复杂) 49 | 50 | * 定额库对应的清单库,定额库对应的材料库在这个库中有保存 51 | 52 | ## 文件结构 53 | 54 | | DekData | | DataContext | 55 | | ------- | -------------- | -------------------------------------- | 56 | | | deklb | 这张表类似于xmjg表 | 57 | | | DeMlDset | 这张表包含了所有专业的目录 | 58 | | | DePathRelation | 这张表中包含了清单库和材料库之间的关系 | 59 | | | QdZyDset | | 60 | | | DeGlMl | | 61 | | | DeGlLx | | 62 | | | DeGlLxDetail | | 63 | | | CGJXF | | 64 | | | CGJXFLB | | 65 | | | AZGCGMain | | 66 | | | AZGCFDetail | | 67 | | | AZGCFDetailCg | | 68 | | | clsjhs | | 69 | 70 | ## 表结构 71 | 72 | * deklb 73 | 74 | | zyid | zymc | dm | dekmc | guid | clkfilename | modid | dmen | infos | 75 | | ------ | -------- | ---------------- | ---------- | ---- | ------------------ | ----- | ---------------- | ----- | 76 | | 专业id | 专业名称 | 专业代码(汉字) | 定额库名称 | | 该定额对应的材料库 | | 英文代码(常用) | | 77 | 78 | * DeMlDset 79 | 80 | 定额目录专业和表 deklb 表中 zyid 相对应。 81 | 82 | | level | Id | DeMuLuBianMa | DeMuLuMingCheng | DeMuluZhangCeShuoMing | DeMuluZhuanYe | 83 | | -------- | ---- | ------------ | --------------- | --------------------- | ------------- | 84 | | 层级关系 | id | 定额目录编码 | 定额目录名称 | 定额目录章侧说明 | 定额目录专业 | 85 | 86 | * DeMasterDset 87 | 88 | 定额基价,有的地区称为“预算价格”或“单位价值”,它是指定额项目的单位综合价格,包括完成定额项目每单位产品所需耗费的人工费,材料费和机械费等,可以按照计算公式:基价(预算价格)=人工费 + 材料费 + 机械费 + 其他费用 89 | 90 | | id | DeBianMa | DeMingCheng | DeDanwei | DeTeXiang | DeJiJia | DeRengong | 91 | | ---- | -------- | ----------- | -------- | --------- | -------- | ---------- | 92 | | id | 定额编码 | 定额名称 | 定额单位 | 定额特项 | 定额基价 | 定额人工费 | 93 | 94 | | DeCailiao | DeJiXie | DeQiTa | DeZuCai | DeSheBei | DeLeiBie | DeZhuanYe | 95 | | ---------- | ---------- | ---------- | -------- | -------- | -------- | --------- | 96 | | 定额材料费 | 定额机械费 | 定额其他费 | 定额组材 | 定额设备 | 定额类别 | 定额专业 | 97 | 98 | | DeCehao | DeXuhao | DeGongZuoNeiRong | MlId | debmqz | | | 99 | | -------- | -------- | ---------------- | ------ | ------------ | ---- | ---- | 100 | | 定额册号 | 定额序号 | 定额工作内容 | 目录id | 定额编码前缀 | | | 101 | 102 | * DeDetaiDset 103 | 104 | 这张表中保存了定额和材料的关系。 105 | 106 | | DeId | ClId | XHL | 107 | | ------ | ------ | ------ | 108 | | 定额id | 材料id | 消耗量 | 109 | 110 | # 清单库 111 | 112 | ## 文件结构 113 | 114 | 清单分组和清单目录的关系:清单目录就类似于一本书的目录,清单分组类别相当于这本书的册号。清单库相当于一本很厚的书。这本书分为很多册,例如陆小凤传奇,分为陆小凤传奇一,陆小凤传奇二,陆小凤传奇三等等,而每一本书又有自己的目录。 115 | 116 | | QdkData | | | 117 | | ------- | ------------ | -------------------------- | 118 | | | QdFzLb | 清单分组类别 | 119 | | | QdMlDset | 清单目录 | 120 | | | QdMasterDset | 这个表中包含了所有的额清单 | 121 | | | QdGcNrDset | | 122 | | | QdXmTzDset | | 123 | 124 | -------------------------------------------------------------------------------- /基类架构.md: -------------------------------------------------------------------------------- 1 | # 基类架构 2 | 3 | 1. 电子评标业务流中心类:TDzpbBusiFlowCenter。 --------判断使用dll或者旧方式 4 | 2. 电子评标业务流类:TDzpbBusiFlowV2(dll 方式调用)。 -------------获得dll的所在位置,并将dll装入内存调用。这个类的作用就是将胜算和电子评标业务关联起来。 5 | 3. -------------------------------------------------------------------------------- /多态.md: -------------------------------------------------------------------------------- 1 | **封装可以隐藏实现细节,使得代码模块化; 继承可以扩展已存在的代码模块,他们的目的都是为了代码重用,而多态则是为了另一个目的:接口重用。** 2 | 3 | # 多态 4 | 5 | ## 什么是多态 6 | 7 | ​ 多态性是允许用户将父对象设置成为与一个或更多个它的子对象相等的技术,赋值之后,基类对象就可以根据当前赋值给它的派生类对象的特性以不同的方式运作。 8 | 9 | ​ 更简单的说,多态性允许用户将派生类类型的指针赋值给基类类型的指针。多态性在Object Pascal 中是通过虚方法(Virtual Method)实现的。 10 | 11 | **多态是通过虚方法实现的,而虚方法是通过晚绑定实现的** 12 | 13 | ## 多态的本质 14 | 15 | ​ 多态的本质就是将派生类类型的指针赋值给基类类型的指针。 16 | 17 | ```pascal 18 | parent := child; 19 | ``` 20 | 21 | 22 | 23 | ## 重载和覆盖 24 | 25 | 1. 重载: overload,他不是面向对象所专有的。 26 | 27 | 2. 覆盖:override。覆盖的本质就是以新方法代替同名的旧方法。这样就意味着,在派生类中可以通过覆盖机制来增强或者改变对象的行为,从而实现多态。 28 | 29 | 3. 对于基类要覆盖的方法,必须将其事先声明为虚方法,否则使用覆盖是会出错。 30 | 31 | 4. 有时候某些派生类不需要对基类的方法进行完全覆盖,或者完全不需要覆盖,但并不影响我们声明覆盖方法。可以在派生类的覆盖方法中使用inherited语句来解决该类问题。 32 | 33 | 5. 如果基类的析构函数时虚方法,那么对析构函数覆盖是比较特殊,在最后必须要调用基类的析构函数。 34 | 35 | ```pascal 36 | destructor TClass.destroy; 37 | begin 38 | //自己写的代码 39 | …… 40 | inherited;//最后调用基类的析构函数 41 | end; 42 | ``` 43 | 44 | 6. 声明方法时,virtual和dynamic限定符必须位于reintroduce和overload限定符之后,abstract之前。 45 | 46 | ## 抽象类和抽象方法 47 | 48 | 1. 在祖先类中定义了一个方法,希望他的派生类能够继承,并且使用覆盖的方法具体化,实例化。但这个方法对于本身而言,没有必要写任何代码,有时也是在不知道该写什么代码。这是,这个方法称为抽象方法。拥有抽象方法的类称为抽象类。 49 | 50 | 2. 抽象类是无需实例化的类,他提供的抽象方法为派生类定义了接口,他的任何派生类都必须要实现这些方法。 51 | 52 | 3. 抽象方法使用abstract来声明,抽象方法必须是虚方法或动态方法。 53 | 54 | abstract限定符必须跟在virtual、dynamic或override之后。 55 | 56 | ```pascal 57 | procedure ProcName(); virtual; abstract; 58 | procedure ProcName(); dynamic; abstract; 59 | function FuncName(); virtual; abstract; 60 | function FuncName(); dynamic; abstract; 61 | ``` 62 | 63 | 4. 如果不想在派生类中实现一个基类的抽象方法,就可以在派生类中忽略这个方法或者使用override和abstract限定符来声明这个方法。 64 | 65 | ## 类的类型转换 66 | 67 | 1. 可以将一个派生类的值赋予一个基类类型的变量 68 | 69 | ```pascal 70 | TChinese = TMan.Create; 71 | ``` 72 | 73 | 2. 向上转型,因为派生类可以继承基类的所有接口,包括方法和数据成员。 74 | 75 | 3. 每一个类只有唯一的虚拟方法表(VMT),且该类所有的对象共享类的VMT。 76 | 77 | 78 | ## 用VCL抽象类实现多态 79 | 80 | 1. 现在假设要把数据流督导一个TMemo控件中显示出来,TMemo控件提供了一个很好的TMemo.Lines.LoadFromStream方法。我们可能会这样写: 81 | 82 | ```pascal 83 | procedure TForm1.Button1Click(Sender: TObject); 84 | var 85 | MyStream:TFileStream; 86 | begin 87 | MyStream := TFileStream.Create('ji.txt',fmOpenRead); 88 | try 89 | Memo1.Lines.LoadFromStream(MyStream); 90 | finally 91 | MyStream.Free; 92 | end; 93 | end; 94 | ``` 95 | 96 | ​ 更好的写法: 97 | 98 | ```pascal 99 | procedure TForm1.Button1Click(Sender: TObject); 100 | var 101 | MyStream:TStream; 102 | begin 103 | //此处MyStream的真正类型时TFileStream,而不是TStream。 104 | MyStream := TFileStream.Create('ji.txt',fmOpenRead); 105 | try 106 | Memo1.Lines.LoadFromStream(MyStream); 107 | finally 108 | MyStream.Free; 109 | end; 110 | end; 111 | ``` 112 | 113 | 2. 多态的使用方法 114 | 115 | ​ 定义一个飞机类 116 | 117 | ```pascal 118 | type 119 | TPlane = class 120 | protected 121 | FModal: String;//型号 122 | public 123 | procedure Fly(); virtual; abstract; 124 | procedure Land(); virtual; abstract; 125 | function Modal(); string; virtual; 126 | end; 127 | ``` 128 | 129 | ​ 然后,从TPlane派生出两个派生类,直升机(TCopter)和喷气式飞机(TJet) 130 | 131 | ```pascal 132 | TCopter = class(TPlane) 133 | public 134 | constructor Create(); 135 | destructor Destory(); override; 136 | procedure Fly(); override; 137 | procedure Land(); override; 138 | function Modol(): string; override; 139 | ……………………//其他可能的操作 140 | end; 141 | 142 | TJet = Class(TPlane) 143 | public 144 | Constructor Create(); 145 | destructor Destroy(); override; 146 | procedure Fly(); override; 147 | procedure Land(); override; 148 | …………………………//其他可能的操作,没有覆盖Modal 149 | end; 150 | ``` 151 | 152 | ​ 现在,假设要完成一个机场管理系统,在有了以上的TPlane之后,在编写一个全局的函数g_FlyPlane(), 就可以让所有传递给他的飞机起飞。 153 | 154 | ```pascal 155 | procedure g_FlyPlane(const Plane: TPlane); 156 | begin 157 | Plane.fly; 158 | end; 159 | ``` 160 | 161 | ​ 这样就可以让所有传给它的飞机(TPlane的派生类对象)正常起飞。不管是直升飞机还是喷气式飞机,甚至是现在不存在的、以后会增加的飞碟。这是因为,每个派生类(真正的飞机)都可以通过**override** 来定义适合自己的起飞方式。 162 | 163 | -------------------------------------------------------------------------------- /字符指针与静态字符数组的内存布局.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/字符指针与静态字符数组的内存布局.png -------------------------------------------------------------------------------- /对现在工作的理解.md: -------------------------------------------------------------------------------- 1 | # 2019.10.31 2 | 3 | ## 电子评标是什么 4 | 5 | * 电子评标就是数据转换; 6 | * 招标是将胜算软件中的招标工程通过配置文件将招标工程导出为正确的招标文件; 7 | * 投标是将招标文件和配置文件转换成胜算软件中的投标工程,然后将投标工程根据配置文件转换成投标文件; 8 | * 这个转换的过程,会牵扯到第三方变量和胜算中的变量。 9 | 10 | ## 胜算软件是一个产品化非常高的软件 11 | 12 | * 这个具体表现是有配置文件和模版。如果这些东西都写到软件里代码量是非常大的。 13 | 14 | ## SVN账号密码 15 | 16 | ![SVN账号密码](SVN账号密码.png) 17 | 18 | ## 日常学习 19 | 20 | VAT(value added tax):增值税。 -------------------------------------------------------------------------------- /对象占用空间.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/对象占用空间.png -------------------------------------------------------------------------------- /对象,类,虚拟方法表,self.md: -------------------------------------------------------------------------------- 1 | # 对象,类,虚拟方法表,self 2 | 3 | ## 什么是对象 4 | 5 | ```pascal 6 | // 对象是用类创建的实例,对象本身在堆中存储,对象名称是一个指向对象存储空间的一个指针。 7 | // 注意,对象之间是不共享数据的,而类中的方法对每个对象而言是共享的。 8 | // self分为两种,第一是在类方法中使用的self,这个时候self指的是虚拟方法表的基地址,也就是起始地址,;第二是在普通方法中使用self,这个时候self就是对象名称的一个别名,其实就是对象本身。 9 | // 对象名称或者说self存储的是对象本身的基地址(起始地址,现在我是这样认为的,可能不对,后面了解后会修改),每个对象中的内容包括VMT和该对象的成员字段,首先保存的就是虚拟方法表的地址(注意,不是虚拟方法表的基地址,而是虚拟方法表的地址,也就是说它指向虚拟方法表)。 10 | 11 | TMyClass = class 12 | FA: Byte; 13 | FB: array [0..3] of Double; 14 | protected 15 | FC: Boolean; 16 | FD: string; 17 | private 18 | FE: Integer; 19 | procedure F0(); 20 | function G0(): Integer; 21 | procedure H0(var P: Pointer); 22 | 23 | function GetSelfAddress: string; 24 | class function GetClassSelfAddress: string; 25 | function GetVMTAddress: string; 26 | end; 27 | 28 | TProcA = procedure () of object; 29 | TFuncB = function (): Integer of object; 30 | TProcC = procedure (var P: Pointer) of object; 31 | 32 | // 获取类方法中self这个指针保存(指向)的地址 33 | class function TMyClass.GetClassSelfAddress: string; 34 | var 35 | P: Pointer; 36 | begin 37 | P := Pointer(Self); 38 | Result := IntToHex(Integer(P), 8); 39 | end; 40 | 41 | // 获取普通方法中self这个指针保存(指向)的地址 42 | function TMyClass.GetSelfAddress: string; 43 | var 44 | P: Pointer; 45 | begin 46 | P := Pointer(Self); 47 | Result := IntToHex(Integer(P), 8); 48 | end; 49 | 50 | // 获取虚拟方法表的地址 51 | function TMyClass.GetVMTAddress: string; 52 | var 53 | P: Pointer; 54 | begin 55 | P := Pointer(Self); 56 | P := Pointer(P^); 57 | Result := IntToHex(Integer(P), 8); 58 | end; 59 | 60 | // 查看各个地址信息 61 | procedure TForm13.btn1Click(Sender: TObject); 62 | var 63 | MyClass: TMyClass; 64 | Str: string; 65 | PMyClass: Pointer; 66 | P: Pointer; 67 | begin 68 | Self.mmo1.Lines.Clear; 69 | Str := '类方法的Self指向地址 ' + TMyClass.GetClassSelfAddress; 70 | mmo1.Lines.Add(Str); // 00523A60 71 | 72 | PMyClass := @MyClass; 73 | Str := 'MyClass这个对象指针本身的地址 ' + IntToHex(Integer(PMyClass), 8); 74 | mmo1.Lines.Add(Str); // 0018F53C 75 | 76 | MyClass := TMyClass.Create; 77 | Str := '普通方法中self指向的地址 ' + MyClass.GetSelfAddress; 78 | mmo1.Lines.Add(Str); // 0247DE00 79 | 80 | PMyClass := Pointer(MyClass); 81 | Str := 'MyClass这个对象指针指向的地址 ' + IntToHex(Integer(PMyClass), 8); 82 | mmo1.Lines.Add(Str); // 0247DE00 83 | 84 | Str := 'MyClass这个对象虚拟方法表的基地址' + MyClass.GetVMTAddress; 85 | mmo1.Lines.Add(str); // 00523A60 86 | 87 | P := @MyClass.FA; 88 | Str := 'MyClass中FA字段的地址 ' + IntToHex(Integer(P), 8); 89 | mmo1.Lines.Add(Str); // 023FDE04 90 | 91 | P := @MyClass.FB; 92 | Str := 'MyClass中FB字段的地址 ' + IntToHex(Integer(P), 8); 93 | mmo1.Lines.Add(Str); // 023FDE08 94 | 95 | P := @MyClass.FC; 96 | Str := 'MyClass中FC字段的地址 ' + IntToHex(Integer(P), 8); 97 | mmo1.Lines.Add(Str); // 023FDE28 98 | 99 | P := @MyClass.FD; 100 | Str := 'MyClass中FD字段的地址 ' + IntToHex(Integer(P), 8); 101 | mmo1.Lines.Add(Str); // 023FDE2C 102 | 103 | ProcA := MyClass.F0; 104 | Str := 'TMyClass中F0的地址 ' + IntToHex(Integer(@ProcA), 8); 105 | mmo1.Lines.Add(Str); // 00524044 106 | 107 | FuncB := MyClass.G0; 108 | Str := 'TMyClass中G0的地址 ' + IntToHex(Integer(@FuncB), 8); 109 | mmo1.Lines.Add(Str); // 00524050 110 | 111 | ProcC := MyClass.H0; 112 | Str := 'TMyClass中H0的地址 ' + IntToHex(Integer(@ProcC), 8); 113 | mmo1.Lines.Add(Str); // 005240E0 114 | 115 | MyClass.Free; 116 | end; 117 | ``` 118 | 119 | ## 类成员的可见性和类限定 120 | 121 | ```pascal 122 | // 首先,这是两个不同的概念。 123 | // 类成员的可见性是对外部来说的,类成员中只有声明在public和published域的成员对外才是可见的, 124 | // 类限定这个概念来源于继承。例如,父类的private域中的成员子类能否调用,是否有权限调用。 125 | ``` 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /封装.md: -------------------------------------------------------------------------------- 1 | # 封装 2 | 3 | ### 封装的实质:数据和处理过程结合在一起并隐藏在接口后面 4 | 5 | 1. 封装实际上是利用了一种抽象机制来管理事物本身的复杂性。 6 | 7 | 2. 下面的代码时对类的封装性的思考 8 | 9 | ```pascal 10 | unit Unit1 11 | 12 | uses 13 | Windows,.....,Unit2; 14 | 15 | type 16 | TForm1 = class(TForm) 17 | Button1: TButton; 18 | 19 | var 20 | Form1: TForm1; 21 | 22 | implementation 23 | 24 | procedure TForm1.FormCreate(Sender: IObject); 25 | begin 26 | Form2 := TForm2.Create(Self); 27 | end; 28 | 29 | procedure TForm1.Button1Click(Sender: TObject); 30 | begin 31 | Form2.Show; //这里把Form2作为一个全局变量使用 32 | end; 33 | //----------------------------------------------------------------- 34 | unit Unit2 35 | ............ 36 | var 37 | Form2: TForm2;//这里把Form2声明为一个全局变量 38 | 39 | ``` 40 | 41 | 下面时对上面代码的重新封装后的修改 42 | 43 | ```pascal 44 | type 45 | TForm = class(TForm) 46 | Button1: TButton; 47 | procedure Button1Click(Sender: TObject); 48 | private 49 | FForm: TForm 50 | public 51 | property FForm: TForm read FForm write FForm 52 | end; 53 | var 54 | Form1: TForm1; 55 | implementation 56 | uses Unit2; 57 | 58 | procedure TForm1.Button1Click(Sender: TObject); 59 | begin 60 | if Assigned(FForm) then 61 | TForm2(FForm).Show;//访问的时内部成员FForm,注意FForm需要转型。 62 | end; 63 | 64 | procedure TForm1.Button2Click(Sender: TObject); 65 | begin 66 | self.caption := 'HI';//在TForm1类中使用self代替Form1. 67 | end; 68 | end; 69 | 70 | //下面时项目文件中的内容 71 | program Project1; 72 | uses 73 | Forms, 74 | Unit1 in 'Unit1.pas'{Form1}; 75 | Unit2 in 'Unit2.pas'{Form2}; 76 | begin 77 | Applicantion.Initialize; 78 | Application.CreateForm(TForm1, Form1); 79 | Application.CreateForm(TForm2, Form2); 80 | //通过属性传递Form2的引用 81 | Form1.FForm := Form2; 82 | Application.Run; 83 | end; 84 | 85 | ``` 86 | 87 | 3. 尽量不要使用全局变量。 88 | 89 | 4. 对象之间交换数据,尽可能的使用属性而不是变量。 90 | 91 | ## 接口一旦公布,就不要改变他 92 | 93 | -------------------------------------------------------------------------------- /工程造价结构图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/工程造价结构图.png -------------------------------------------------------------------------------- /异常.md: -------------------------------------------------------------------------------- 1 | # 异常 2 | 3 | ​ 在Delphi的VCL中,所有的异常类都派生于Exception类。该类声明了异常的一般行为,性质。最重要的,他有一个Message属性可以报告异常发生的原因。 4 | 5 | ​ 抛出一个异常即标志一个错误的发生。使用raise保留字来抛出一个异常对象,如: 6 | 7 | ```pascal 8 | raise Exception.Create('An error occurred!'); 9 | ``` 10 | 11 | ​ 需要强调的是,异常用来标志错误发生,却并不因为错误发生而产生异常。产生异常仅仅是因为遇到了raise,在任何时候,及时没有错误发生,raise都会导致异常发生。 12 | 13 | ​ 异常时一种对象,任何类的实例都可以作为异常对象。 14 | 15 | ​ try……except和try……finally两个相关的语句用于处理异常。try……except语句设置一个异常处理程序,这个程序在错误出现时获取控制权。而try……finally语句并不明确处理异常,但可以保证语句在finally部分的代码即使在一个异常引发时也能得到运行。 16 | 17 | ​ 使用try……except……end语句可以捕获异常。其用法是: 18 | 19 | ```pascal 20 | try 21 | {异常保护的程序块} 22 | except 23 | {一系列异常处理程序} 24 | on {异常类}: do{处理程序} 25 | else 26 | {其他未指明异常类处理程序} 27 | end; 28 | ``` 29 | 30 | ​ 如果一段由try……except进行异常保护的程序块没有发生异常,异常处理程序将被忽略,程序执行try……except……end后面的代码。如果发生异常,则进入except……end中的异常处理程序。 31 | -------------------------------------------------------------------------------- /悟透Delphi.md: -------------------------------------------------------------------------------- 1 | 1. 一个对象是什么,一个对象就是一个指针,这个指针指向该对象在内存中占据的一块空间。 2 | 3 | 2. 对象指针指向的内存空间成为对象空间。对象空间的头4个字节是指向该**对象直属类**的虚方法地址表 *VMT* ,接下来的空间就是存储对象本身成员数据的空间。 4 | 5 | 3. 每一个类都有对象的一张 *VMT* ,类的 *VMT* 保存从该类的原始祖先类派生到该类的所有的虚方法的过程地址。 6 | 7 | 4. *DELPHI* 的一个类,代表着一项 *VMT* 数据。 8 | 9 | ```pascal 10 | TClass = class of TObject; 11 | ``` 12 | 13 | *TClass* 是类的类型,即,类之类。因此,类之类可以认为是为 *VMT* 数据项定义的类型,其实,他就是一个指向 *VMT* 数据的指针类型。 14 | 15 | 正是由于 *DELPHI* 在应用程序中保留了完整的类信息,才能提供诸如 *as* 和 *is* 等在运行时刻转换和判别的高级面向对象功能,而类的 *VMT* 数据在其中起了关键性作用。 16 | 17 | 有了类的类型,就可以将类作为变量来使用,可以将类的变量理解成一种特殊的对象。 18 | 19 | ```pascal 20 | TSampleClass = class of TSampleObject; 21 | TSampleObject = class(TObject) 22 | public 23 | constructor Create; 24 | destructor Destroy; override; 25 | class function GetSampleObjectCount: integer; 26 | procedure GetObjectIndex: integer; 27 | end; 28 | 29 | var 30 | aSampleClass: TSampleClass; 31 | aClass: TClass; 32 | ``` 33 | 34 | 这里可以将 *TSampleObject* 和 *TObject* 当作常量,并可以将他们赋值给 *aClass* 变量。就像将 *123* 常量赋值给变量*i*一样。所以,类类型,类和类变量的关系 就像 类型,常量和变量的关系。只不过是在类的层次上而不是对象的层次上。当然,直接将 *TObject* 赋值给 *aSampleClass* 是不合法的,因为 *aSampleClass* 是 *TObject* 派生类 *TSampleClass* 的类变量,而 *TObject* 并不包含与 *TSampleClass* 类型类型兼容的所有定义。相反,将 *TSampleObject* 赋值给 *aClass* 变量却是合法的,因为 *TSampleObject* 是 *TObject* 的派生类,是和 *TClass* 类型兼容的。这与对象变量的赋值和类型匹配关系是完全相似的。 35 | 36 | 5. 类方法,就是指在类的层次上调用的方法。在 *TObject* 中有大量的类方法,例如 *ClassName* 、 *ClassInfo* 和 *NewInstance* 等。其中 *NewInstance* 还被定义为虚方法,即虚的类方法。 37 | 38 | 6. 在类方法中可以使用 *self* 这个标识符,不过其所代表的含义与对象中的 *self* 是不同的。类方法中的 *self* 表示的是自身类,即指向 *VMT* 的指针,而对象方法中的 *self* 表示的是对象本身,即指向对象数据空间的指针。**虽然类方法只能在类层次上使用,但你仍可以通过一个对象区调用类方法**。例如,通过语句 *aObject*.*ClassName* 调用对象 *TObject* 的类方法 *ClassName* , 39 | -------------------------------------------------------------------------------- /投标导入流程.md: -------------------------------------------------------------------------------- 1 | # 投标导入流程 2 | 3 | ## 在导入前先尝试构建项目树 4 | 5 | ```pascal 6 | unit uNewProjectUIManager; 7 | 8 | type 9 | /// 10 | /// 新建工程 界面管理 11 | /// 12 | TNewProjectUIManager = class 13 | public 14 | function GetZbFileName_General(out AsFileName, AsErrMsg: string): 15 | TNewPrj_Result; 16 | end; 17 | 18 | function TNewProjectUIManager.GetZbFileName_General(out AsFileName, AsErrMsg: 19 | string): TNewPrj_Result; 20 | begin 21 | // 1. 尝试创建项目树,这个只是构建xmdata中的xmjg这个dataset。 22 | FDataMgr.DzpbIntf.GetPrjTree(FSysOption, FileName); 23 | end; 24 | 25 | function TTB_XML_PM_ZJ_Szdb_YGZ.GetPrjTree(SysOption: IYsSysOption; 26 | const FileName: string): IPmDataSet; 27 | begin 28 | Result := nil; 29 | XmlProject := GetXmlProject; ; 30 | try 31 | if XmlProject.LoadFromFile(FileName) then 32 | begin 33 | AXmTreeDataSet := TDataMgrService.NewDataSetEx; 34 | TData_ConfigManager.CreateDataStruct(SysOption, AXmTreeDataSet, cst_Dh_Xmjg); 35 | // 2. 导入项目树,调用父类的方法,父类的方法时调用子类GetImportClass方法获得投标导入类,然后用投标导入类的ImportXmTree方法调用投标导入事务类中的导入方法导入。 36 | ImportXmTree(SysOption, XmlProject, AXmTreeDataSet); 37 | Result := AXmTreeDataSet; 38 | end; 39 | finally 40 | XmlProject := nil; 41 | end; 42 | end; 43 | ``` 44 | 45 | ## 导入zbcl表 46 | 47 | ```pascal 48 | // 1. 找到zbcl表 49 | // 2. 找到材料汇总记录 50 | // 3. 根据招标材料的导入条件,导入材料 51 | // 4. 导入的内容 1. 序号, 2. 编码, 3. 名称, 4. 单位() 52 | ``` 53 | 54 | 55 | 56 | ## 导入项目信息表流程 57 | 58 | ```pascal 59 | // 1. 找到项目信息表 60 | // 2. 找到配置文件中Xmxxvar,判断Istender是否为True,如果为True则不导入。 61 | // 3. 根据配置文件导入,配置文件决定了要导入那些内容 62 | ``` 63 | 64 | ## 导入专业工程费用汇总表 65 | 66 | ```pascal 67 | // 1. 找到胜算工程专业工程中的gczj表 68 | // 2. 根据招标文件中费用类型字段判断该记录是什么类型,例如141,142是规费,15是税金,143是危险作业。 69 | // 3. (以导入规费为例),先找到胜算工程中的规费表,然后根据配置文件中这个模块中的kind字段和招标文件中费用类别字段的匹配,得到配置文件中SsVar的值,然后在根据SsVar的值找到胜算工程中的节点,最后为该节点设置费率和投标状态。 70 | ``` 71 | 72 | ## 导入分部分项表(清空导) 73 | 74 | ```pascal 75 | // 1. 先找到分部分项表中的根节点 76 | // 2. 然后删除根节点下面的所有子节点 77 | // 3. 循环招标文件中的分部分项记录导入到胜算工程中 78 | // 3.1 根据招标文件分部分项记录的标题判断是分布还是清单(因为要使用递归来导入)。 79 | // 3.2 使用分部分项服务分部分项添加新的节点 80 | FNodeBuilder := FDzpbContext.FileDataContext.FileService.GetService( 81 | cst_FileSerview_Fbfx_NodeBuilder) as ISSFbfxNodeBuilder; // 获得分部分项服务 82 | SsNode := FNodeBuilder.AddFb(PNode); // 添加分布节点 83 | // 分部要导入的内容 1. 新的GUID; 2. 分部分项的序号(序号要导入两个字段,一个是cst_Fbfx_Bh, 另一个是cst_Fbfx_OrdCode,这两个字段一个是string类型,另一个是Integer类型);3. 分部分项名称;4. 备注;5. 节点属性(cst_Fbfx_Jdsx_TBStatus). 84 | // 清单要导入的内容 1. 新的Guid; 2. 编号; 3. 名称; 4. 项目特征; 5. 单位; 6. 计算式和工程量(导入的内容相同); 8. 暂定价; 9. 预算价; 10. 最高限价; 11. 最低限价; 12. 节点属性 85 | SsNode := FNodeBuilder.AddQd(PNdoe); // 添加清单节点 86 | // 3.3 在添加完一天清单后,要添加一条空白的定额记录 87 | FNodeBuilder.AddDE(SsNode); // 添加定额节点 88 | ``` 89 | 90 | ## 导入计数措施表 91 | 92 | ```pascal 93 | // 处理方式和分部分项一样 94 | ``` 95 | 96 | ## 导入组织措施表 97 | 98 | ```pascal 99 | // 1. 找到组织措施项目表 100 | // 2. 找到组织措施根节点(GetZzcsRootNode) 101 | // 3. 为根节点添加节点属性 102 | // 4. 删掉所有子节点 103 | // 5. 利用招标文件中的数据类型和配置文件中的MeasType匹配,然后导入配置文件中配置的费用类型和计算式。 104 | // 6. 导入节点属性 105 | ``` 106 | 107 | ## 导入其他项目 108 | 109 | ```pascal 110 | // 1. 找到其他项目表的根节点 111 | // 2. 使用 SsNode.AddChildPmNode 添加新的节点 112 | // 3. 添加新的GUID 113 | // 4. 根据招标文件中其他项目名称和配置文件中其他项目模块的名称匹配,找到配置文件中相应记录。 114 | // 5. 添加投标状态 115 | ASsNode.setvalue(2, cst_Qtxm_Blws); 116 | AddJdsx(ASsNode, cst_Fbfx_Jdsx, cst_Fbfx_Jdsx_TbStatus); 117 | ``` 118 | 119 | ## 导入计日工 120 | 121 | ```pascal 122 | // 1. 找到计日工根节点 GetQtxmRootNode(FSsDataView); 123 | // 2. 根据FSsDataViw中的bl字段和字符串'P_rg', 'P_cl','P_jx'找到对应的人工,材料,机械节点,这些节点都是根节点,然后删除这些节点的子节点。 124 | // 3. 然后遍历xml记录,根据xml记录的类型分别导入到对应的根节点下面(人工导入到人工下,材料导入到材料根节点下面) 125 | ``` 126 | 127 | -------------------------------------------------------------------------------- /投标导出流程.md: -------------------------------------------------------------------------------- 1 | 投标文件导出没有导项目树 2 | 3 | ## 建设节点的导出流程 4 | 5 | ```pascal 6 | //1. 读取配置文件的节点。 7 | procedure DoExportSsDataSetDatas; 8 | var 9 | I: Integer; 10 | P: _PXmxxVarRecl 11 | sField, sValue, sDm: string; 12 | begin 13 | for i := 0 to FTransConf.ConstructProjectVars.Count - 1 do 14 | begin 15 | P := FTransConf.ConstructProjectVars.Vars[i]; 16 | SField := P.QtVar; 17 | sDm := P.SsVar; 18 | sValue := ''; 19 | ...... 20 | FRtXmlRecord.SetValue(sField, sValue); 21 | end; 22 | end; 23 | ``` 24 | 25 | ```pascal 26 | // 分部分项导出流程 27 | 1. 先导出清单标题(QdBt) 28 | 2. 再导出清单明细(Qdmx) 29 | 3. 再导出清单定额造价 30 | 4. 再导出清单人材机含量明细 31 | ``` 32 | 33 | ```pascal 34 | //导出专业节点的流程 35 | var 36 | DataContext: IDataContext; 37 | begin 38 | DataContext := FDzpbContext.FileDataContext.PrjDataManager.GetDataContext( 39 | cst_DataContext_Xmdata); 40 | FXmTreeDataSet := DataContext.GetDataSet(cst_Table_Xmjg); 41 | ExportXmData(FXmTreeDataSet); 42 | end; 43 | 44 | procedure ExportXmData(XmTreeDataSet: IPMDataSet); 45 | var 46 | ADataView: IPMDataView; 47 | _RtNode: IPMNode; 48 | begin 49 | ADataView := XmTreeDataSet.CreateView; 50 | _RtNode := ADataView.PMNodeList[0]; 51 | ExportXmData_SingleXmNode(_RtNode, FXmlProject.GetRootRecord, bError); 52 | end; 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /招标文件导出流程.md: -------------------------------------------------------------------------------- 1 | ```pascal 2 | //ufmPrjCheck.pas--------------------------------- 3 | TDzpbBusiFlowCenter.ExportZb(FWorkSpace); //判断使用dll方式还是老的方式 4 | //End_ufmPrjCheck.pas----------------------------------------------------------------------------- 5 | 6 | // uDzpbBusiFlowCenter.pas------------------------ 7 | class procedure TDzpbBusiFlowCenter.ExportTb(WorkSpace: ISSWorkSpace); 8 | begin 9 | if IsSupportNewPatternInterface(WorkSpace.FileContext.GetFileDataContext) then 10 | TDzpbBusiFlowV2.ExportTb(WorkSpace) //dll方式 11 | else 12 | TDzpbBusiFlow.ExportTb(WorkSpace); // 老的方式 13 | end; 14 | // End_uDzpbBusiFlowCenter.pas-------------------------------------------------------------------- 15 | 16 | // uDzpbBusiFlow---------------------------------- 17 | class procedure TDzpbBusiFlow.ExportZb(WorkSpace: ISSWorkSpace); 18 | var 19 | _ZBInterfaceMgr: IDzpb_ZB_Interface_Manager; 20 | _DzpbTrans: IDzpb_ZB_Trans; 21 | bSlError: Boolean; 22 | begin 23 | _ZBInterfaceMgr := TDzpb_ZB_Interface_Manager.Create; 24 | _ZBInterfaceMgr.InitInterface; // 注册所有接口 25 | _DzpbTrans := _ZBInterfaceMgr.GetTransInterface( 26 | WorkSpace.FileContext.WorkApplication, 27 | WorkSpace.FileContext.GetFileDataContext.FileOption.Xmsx.GetValue(cst_Xmsx_Key_JkbzName)); // 初始化对象 28 | ………… 29 | ………… 30 | _DzpbTrans.Export(WorkSpace.FileContext.GetFileDataContext.FileService, 31 | WorkSpace.FileContext, WorkSpace.GetPrjDataManager, dkExportZb); 32 | end; 33 | // End_uDzpbBusiFlow------------------------------------------------------------------------------ 34 | /// Xml数据转换基础流程 35 | // uDzpb_XML-------------------------------------- 36 | /// 这个文件里面一共有三个基类和两个子类 37 | /// XML导入业务基类 38 | /// TDzpb_XML_Import = class 39 | /// XML导出业务基类 40 | /// TDzpb_XML_Export = class 41 | /// XML更新业务基类 42 | /// TDzpb_XML_Update = class 43 | /// 招标----Xml数据转换流程 44 | /// TDzpb_ZB_XML = class(TDzpb_ZB_Trans) 45 | /// 投标----XML数据转换流程 46 | /// TDzpb_TB_XML = class(TDzpb_TB_Trans) 47 | function TDzpb_ZB_XML.Export(DzpbService: IYsServiceManager; FileContext: 48 | ISSFileContext; PrjFile: IPrjDataManager; DzpbKind: TDzpbKind): 49 | Boolean; 50 | var 51 | AExportBusi: TDzpb_XML_Export; 52 | AExportBusiClass: TDzpb_XML_ExportClass; 53 | begin 54 | AExportBusiClass := GetExportZbClass; //具体的电子评标接口 55 | AExportBusi := AExportBusiClass.Create(FWorkApp, DzpbService, FileContext,FTransConf,FTransLog); 56 | Result := AExportBusi.Export(PrjFile); // PrjFile 就是要导出的工程文件 57 | end; 58 | 59 | function TDzpb_XML_Export.Export(PrjFile: IPrjDataManager): Boolean; 60 | var 61 | sFileName: string; 62 | begin 63 | FPrjFile := PrjFile; // 这里FPrjFile 就是要导出的招标工程文件 64 | FXmlProject := GetXmlProject; 65 | sFileName := GetExportFileName; 66 | FFileName := sFileName; // 这里FFileName 就是要导出来的招标文件 67 | Result := DoExport; 68 | end; 69 | 70 | function TDzpb_XML_Export.DoExport: Boolean; 71 | var 72 | DataContext: IDataContext; 73 | begin 74 | DataContext := FPrjFile.GetDataContext(cst_DataContext_XmData); // 获得工程文件的XmData数据库信息。 75 | FXmTreeDataSet := DataContext.GetDataSet(cst_Table_XmJg); // 获得XmData数据库中Xmjg表的信息。 76 | ExportXmDataBefore(Result); // 导出前做的事情 77 | ExportXmData(FXmTreeDataSet); // 导出过程 78 | ExportXmDataAfter; // 导出后做的事情 79 | end; 80 | 81 | procedure TDzpb_XML_Export.ExportXmData(XmTreeDataSet: IPMDataset); 82 | var 83 | ADataView: IPMDataView; 84 | _RtNode: IPMNode; 85 | bError: Boolean; 86 | begin 87 | ADataView := XmTreeDataSet.CreateView; // 这里的DataView是XmData表的视图 88 | //导出项目结构树 89 | ExportXmData_XmTree(ADataView); 90 | //遍历项目结构,导出各节点下的数据 91 | _RtNode := ADataView.PMNodeList[0]; //该Node就是建设节点的Node -------------->@1 92 | ExportXmData_SingleXmNode(_RtNode, FXmlProject.GetRootRecord, bError); 93 | end; 94 | 95 | // ----------------->@1 96 | procedure TDzpb_XML_Export.ExportXmData_SingleXmNode(SsNode: IPMNode; 97 | XmlRecord: IDzpb_Xml_Record; var bError: Boolean); 98 | var 99 | i, iCount: Integer; 100 | ChildSsNode: IPMNode; 101 | ADataContext: IDataContext; 102 | sGuid: string; 103 | begin 104 | SGuid := SsNode.GetVauleAsString(cst_XmJg_Guid, ''); 105 | ADataContext := FPrjFile.GetDataContext(sGuid); // 这里是通过GUID找到建设节点的DataContext. 106 | case SsNode.GetValueAsInt(cst_XmJg_Type, -1) of // Type是lb(类别)0是建设节点,1是单位节点 107 | Ord(xntZt): // 2是专业节点 108 | begin 109 | ExportXmData_JsxmNode(SsNode, ADataContext, XmlRecord);// ------>2 110 | ExportXmData_DlxmNode(SsNode, ADataContext, XmlRecord); 111 | end; 112 | Ord(xntDw): 113 | ExportXmData_DwgcNode(SsNode, ADataContext, XmlRecord); 114 | Ord(xntZy): 115 | ExportXmData_ZygcNode(SsNode, ADataContext, XmlRecord); 116 | end; 117 | end; 118 | 119 | procedure TDzpb_XML_Export.ExportDataBusi(DataContext: IDataContext; 120 | DataBusiClass: TZB_XML_DataBusiIIClass; const sSsTableName: string; 121 | CurrXmlRec: IDzpb_Xml_Record; bCheckTable: Boolean = True); 122 | var 123 | DataBusi: TZB_XML_DataBusiII; 124 | SsDataSet: IPMDataset; 125 | begin 126 | SsDataSet := FindDataSetByVariable(DataContext, sSsTableName); 127 | FTransLog.SetTableName(GetZWTableName(sSsTableName)); 128 | DataBusi = DataBusiClass.Create; 129 | InitDataBusi(DataBusi); 130 | DataBusi.Init(FDzpbService, FTransConf, FTransLog); 131 | DataBusi.Export(FFileContext, DataContext, SsDataSet.CreateView, CurrXmlRec); 132 | end; 133 | // End_uDzpb_Xml.pas------------------------------------------------------------------------------ 134 | 135 | // uDzpb_Xml_XD_ZJ_HZ_ChangXing_YGZ.pas----------- 136 | procedure TZB_XML_Export_XD_ZJ_HZ_ChangXing_YGZ.ExportXmData_XmTree 137 | (SsDataView: IPMDataView); 138 | var 139 | DataBusi: TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree; 140 | SsDataSet: IPMDataset; 141 | RtXmlRecord: IDzpb_Xml_Record; // 里面保存内容的好像是导出的招标文件信息。 142 | begin 143 | RtXmlRecord := FXmlProject.AddRootRecord(cst_Xml_Table_JingJiBiao); 144 | DataBusi := TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree.Create; 145 | DataBusi.Init(FDzpbService, FTransConf, FTransLog); 146 | DataBusi.Export(SsDataView, RtXmlRecord); 147 | DataBusi.Free; 148 | end; 149 | 150 | procedure TZB_XML_Export_XD_ZJ_HZ_ChangXing_YGZ.ExportXmData_JsxmNode 151 | (SsNode: IPMNode; DataContext: IDataContext; CurrXmlRec: IDzpb_Xml_Record); 152 | begin 153 | // ------>2 154 | ExportDataBusi(DataContext, TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_JingJiBiao_ZbXx, 155 | cst_SsTableName_Xmxx, CurrXmlRec); // 156 | end; 157 | // End_uDzpb_Xml_XD_ZJ_HZ_ChangXing_YGZ.pas------------------------------------------------------- 158 | 159 | // uDzpb_XML_DataBusi----------------------------- 160 | procedure TZB_XML_DataBusi.Export(SsDataView: IPMDataView; RtXmlRecord: 161 | IDzpb_Xml_Record); 162 | begin 163 | FRtXmlRecord := RtXmlRecord; 164 | FSsDataView := SsDataView; 165 | if Assigned(FRtXmlRecord) and Assigned(FSsDataView) then 166 | DoExportSsDataSetDatas; // 开始导出数据 167 | end; 168 | // END_uDzpb_Xml_DataBusi------------------------------------------------------------------------- 169 | 170 | // uDzpb_Xml_DataBusi_ZB_XD_ZJ_HZ_ChangXing_YGZ.pas---------- 171 | procedure TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree.DoExportSsDataSetDatas; 172 | var 173 | JsxmNode: IPMNode; // 建设工程节点 174 | begin 175 | JsxmNode := FSsDataView.PMNodeList[0]; 176 | DoExportDwgcDatas(JsxmNode, FRtXmlRecord); 177 | end; 178 | 179 | procedure TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree.DoExportDwgcDatas(JsxmNode: IPMNode; 180 | JsxmRec: IDzpb_Xml_Record); 181 | var 182 | i, iCount: Integer; 183 | DwgcNode: IPMNode; // 单位工程节点 184 | DwgcRec: IDzpb_Xml_Record; // 单位工程记录 185 | begin 186 | iCount := JsxmNode.Count; 187 | for i = 0 to iCount - 1 do 188 | begin 189 | DwgcNode := JsxmNode.ChildNodes[i]; 190 | DwgcRec := JsxmRec.AddChildRecord(cst_Xml_Table_Dwgcxx); 191 | DoExportSingleDwgcDatas(DwgcNode, DwgcRec); 192 | //导出专业工程数据 193 | if DwgcNode.Count > 0 then 194 | DoExportZygcDatas(DwgcNode, DwgcRec); 195 | end; 196 | end; 197 | 198 | procedure TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree.DoExportSingleDwgcDatas(DwgcNode: IPMNode; 199 | DwgcRec: IDzpb_Xml_Record); 200 | var 201 | XmjgID: string; 202 | XmjgName: string; 203 | begin 204 | DwgcRec.SetValue(cst_Xml_Dwgcxx_Dwgcbh, DwgcNode.GetValueAsString(cst_Xmjg_Id, '')); 205 | DwgcRec.SetValue(cst_Xml_Dwgcxx_Dwgcmc, DwgcNode.GetValueAsString(cst_XmJg_Name, '')); 206 | end; 207 | 208 | procedure TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree.DoExportZygcDatas(DwgcNode: IPMNode; 209 | DwgcRec: IDzpb_Xml_Record); 210 | var 211 | i, iCount: Integer; 212 | ZygcNode: IPMNode; 213 | ZygcRec: IDzpb_Xml_Record; 214 | begin 215 | iCount := DwgcNode.Count; // 获得单位节点的数量 216 | for i := 0 to iCount - 1 do 217 | begin 218 | ZygcNode := DwgcNode.ChildNodes[i]; 219 | ZygcRec := DwgcRec.AddChildRecord(cst_Xml_Table_Zygcxx); 220 | DoExportSingleZygcDatas(ZygcNode, ZygcRec); 221 | end; 222 | end; 223 | 224 | procedure TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree.DoExportSingleZygcDatas(ZygcNode: IPMNode; 225 | ZygcRec: IDzpb_Xml_Record); 226 | var 227 | _SpecRec: _PSpecialityRec; //专业节点有自己的结构体 228 | iZylb: Integer; 229 | XmjgID: string; 230 | XmjgName: string; 231 | begin 232 | ZygcRec.SetValue(cst_Xml_Zygcxx_Zygcbh, ZygcNode.GetValueAsString(cst_Xmjg_Id, '')); 233 | ZygcRec.SetValue(cst_Xml_Zygcxx_Zygcmc, ZygcNode.GetValueAsString(cst_XmJg_Name, '')); 234 | iZylb := ZygcNode.GetValueAsInt(cst_XmJg_Spec, -1); // Spec 专业 235 | _SpecRec := FTransConf.Specialitys.GetSpecRecByRationType(iZylb); // 专业由自己单独的结构体TSpecialityRec = record 236 | ZygcRec.SetValue(cst_Xml_Zygcxx_Zylb, _SpecRec.QtSpecId); 237 | end; 238 | 239 | 240 | /// 招标信息数据 241 | TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_JingJiBiao_ZbXx = class(TZB_XML_DataBusi) 242 | protected 243 | /// 导出数据 244 | procedure DoExportSsDataSetDatas; override; 245 | end; 246 | // END-------------------------------------------------------------------------------------------- 247 | 248 | ``` 249 | 250 | -------------------------------------------------------------------------------- /招标文件导出流程2.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```pascal 4 | //**************************************************************************************************** uDzpb_ZB_Interface_Manager.pas ***************************************************************************************************// 5 | //湖州长兴新点(2013)电子招投标 营改增 6 | RegisterInterface(cst_ID_XD_XML_ZJ_HZ_ChangXing_2013_YGZ, TZB_XML_XD_ZJ_HZ_ChangXing_YGZ); 7 | ``` 8 | 9 | ```pascal 10 | //**************************************************************************************************** uDzpbInterfaceConst.aps ***************************************************************************************************// 11 | //湖州长兴新点(2013)电子招投标 营改增 12 | cst_ID_XD_XML_ZJ_HZ_ChangXing_2013_YGZ = '{54DEEB2C-A607-48DF-BDAE-7749BD35862E}'; 13 | ``` 14 | 15 | ```pascal 16 | //**************************************************************************************************** uDzpb_Xml_XD_ZJ_HZ_ChangXing_YGZ.pas ***************************************************************************************************// 17 | /// 湖州长兴(新点 营改增 )电子招标基础业务 18 | TZB_XML_XD_ZJ_HZ_ChangXing_YGZ = class(TDzpb_ZB_XML) 19 | protected 20 | /// 获取招标导出业务类 21 | /// 返回招标导出业务类 22 | function GetExportZbClass: TDzpb_XML_ExportClass; override; 23 | begin 24 | Result := TZB_XML_Export_XD_ZJ_HZ_ChangXing_YGZ; 25 | end; 26 | /// 获取标底导出业务类 27 | /// 返回标底导出业务类 28 | function GetExportBdClass: TDzpb_XML_ExportClass; override; 29 | begin 30 | Result := TZB_XML_ExportZBKZJ_XD_ZJ_HZ_ChangXing_YGZ; 31 | end; 32 | end; 33 | 34 | 35 | /// 湖州长兴(新点 营改增 )电子投标基础业务(导出招标文件) 36 | TZB_XML_Export_XD_ZJ_HZ_ChangXing_YGZ = class(TDzpb_XML_Export) 37 | private 38 | /// 检查人材机(检查回程费) 39 | procedure RcjCheck; 40 | protected 41 | /// 获取导出关键字 42 | /// 返回导出关键字 43 | function GetBusiName: string; override; 44 | /// 导出项目数据前 45 | /// 是否能导出 46 | procedure ExportXmDataBefore(var CanExport: Boolean); override; 47 | /// 导出项目树 48 | /// 项目树数据集 49 | procedure ExportXmData_XmTree(SsDataView: IPMDataView); override; 50 | var 51 | DataBusi: TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree; 52 | begin 53 | DataBusi := TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree.Create; 54 | try 55 | DataBusi.Init(FDzpbService, FTransConf, FTransLog); 56 | DataBusi.Export(SsDataView, RtXmlRecord); 57 | finally 58 | end; 59 | end; 60 | /// 导出建设节点 61 | procedure ExportXmData_JsxmNode(SsNode: IPMNode; DataContext: IDataContext; 62 | CurrXmlRec: IDzpb_Xml_Record); override; 63 | /// 导出单位工程节点 64 | procedure ExportXmData_DwgcNode(SsNode: IPMNode; DataContext: IDataContext; 65 | var CurrXmlRec: IDzpb_Xml_Record); override; 66 | /// 导出专业工程节点 67 | procedure ExportXmData_ZygcNode(SsNode: IPMNode; DataContext: IDataContext; 68 | PXmlRec: IDzpb_Xml_Record); override; 69 | end; 70 | ``` 71 | 72 | ```pascal 73 | //**************************************************************************************************** uDzpb_Xml_DataBusi_ZB_XD_ZJ_HZ_ChangXing_YGZ.pas ***************************************************************************************************// 74 | /// Xml数据转换--项目树数据 75 | TZB_XML_XD_ZJ_HZ_ChangXing_YGZ_XmTree = class(TZB_XML_DataBusi) 76 | protected 77 | /// 导出数据 78 | procedure DoExportSsDataSetDatas; override; 79 | begin 80 | var 81 | JsxmNode: IPMNode; 82 | begin 83 | if FSsDataView.GetRecCount > 0 then 84 | JsxmNode := FSsDataView.PMNodeList[0]; 85 | if Assigned(FRtXmlRecord) and Assigned(JsxmNode) then 86 | begin 87 | DoExportDwgcDatas(JsxmNode, FRtXmlRecord); 88 | end; 89 | end; 90 | /// 导出单位数据记录 91 | procedure DoExportDwgcDatas(JsxmNode: IPMNode;JsxmRec: IDzpb_Xml_Record); 92 | /// 导出单位节点数据 93 | procedure DoExportSingleDwgcDatas(DwgcNode: IPMNode;DwgcRec: IDzpb_Xml_Record); 94 | /// 导出专业数据记录 95 | procedure DoExportZygcDatas(DwgcNode: IPMNode;DwgcRec: IDzpb_Xml_Record); 96 | /// 导出专业节点数据 97 | procedure DoExportSingleZygcDatas(ZygcNode: IPMNode;ZygcRec: IDzpb_Xml_Record); 98 | end; 99 | ``` 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /拼音缩写.md: -------------------------------------------------------------------------------- 1 | | 缩写 | 名称 | 备注 | 2 | | ------ | ------------ | -------- | 3 | | ZbKzj | 招标控制价 | | 4 | | SZYL | 市政园林 | | 5 | | GT | 国泰 | 国泰新点 | 6 | | XD | 新点 | 国泰新点 | 7 | | Dzpb | 电子评标 | | 8 | | ZB | 招标 | | 9 | | TB | 投标 | | 10 | | GD | 广达 | | 11 | | mkpz | 模块配置 | | 12 | | xmjg | 项目结构 | | 13 | | xmsx | 项目属性 | | 14 | | xmjdsx | 项目节点属性 | | 15 | | ywmkzb | 业务模块子表 | | 16 | | | | | 17 | | | | | 18 | | | | | 19 | | | | | 20 | | | | | 21 | | | | | 22 | | | | | 23 | | | | | 24 | | | | | 25 | | | | | 26 | | | | | 27 | | | | | 28 | | | | | 29 | | | | | 30 | | | | | 31 | | | | | 32 | | | | | 33 | | | | | 34 | | | | | 35 | | | | | 36 | | | | | 37 | | | | | 38 | | | | | 39 | | | | | 40 | | | | | 41 | | | | | 42 | | | | | 43 | | | | | 44 | | | | | 45 | | | | | 46 | | | | | 47 | | | | | 48 | | | | | 49 | | | | | 50 | | | | | 51 | | | | | 52 | | | | | 53 | | | | | 54 | | | | | 55 | | | | | 56 | | | | | 57 | | | | | 58 | | | | | 59 | | | | | 60 | | | | | 61 | | | | | 62 | | | | | 63 | | | | | 64 | | | | | 65 | | | | | 66 | | | | | 67 | | | | | 68 | | | | | 69 | | | | | 70 | | | | | 71 | | | | | 72 | | | | | 73 | | | | | 74 | | | | | 75 | | | | | 76 | | | | | 77 | | | | | 78 | | | | | 79 | | | | | 80 | | | | | 81 | | | | | 82 | | | | | 83 | | | | | 84 | | | | | 85 | | | | | 86 | | | | | 87 | | | | | 88 | | | | | 89 | | | | | 90 | | | | | 91 | | | | | 92 | | | | | 93 | | | | | 94 | | | | | 95 | | | | | 96 | | | | | 97 | | | | | 98 | | | | | 99 | | | | | 100 | | | | | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /接口.md: -------------------------------------------------------------------------------- 1 | # 接口 2 | 3 | 1. 接口定义了能被一个类实现的方法。接口的声明和类相似,但不能直接实例化,也不能自己实现他们的方法。 4 | 5 | 2. 一 个接口类型的变量能引用一个实现了该接口的对象,但是,只有接口中声明的方法才能通过该变量调用。 6 | 7 | 3. 一个类能实现多个接口。 8 | 9 | 4. 接口的命名约定I起头。 10 | 11 | 5. 接口都是从IInterface继承而来;若是从根接口继承,可省略。 12 | 13 | 6. 接口只能是方法、属性,没有字段。 14 | 15 | 7. 接口成员都是公开的,不需要private、protected、public、published等任何访问限制。 16 | 17 | 8. 因为接口只声明、无实现,也用不到继承与覆盖相关的修饰(virtual、dynamic、abstract、override)。 18 | 19 | 9. 一个接口可以从另一个接口继承,但不能从多个接口继承。 20 | 21 | 10. 实现接口的类一般继承于TInterfaceObject,直接从TObject继承会增加一些麻烦而重复的工作 22 | 23 | 11. 接口在用完后会自释放,并同时释放拥有它的类; 24 | 25 | 12. 接口的声明方法: 26 | 27 | ```pascal 28 | type interfaceName = interface(ancestorInterface) 29 | ['{GUID}'] 30 | memberList 31 | end; 32 | ``` 33 | 34 | ancestorInterface和['{GUID}']是可选的,大多数情况下,接口声明和类声明相似,但是有一下限制: 35 | 36 | * memberList只能包含方法和属性,而不能包含数据成员(field)。 37 | * 因为接口没有数据成员,所以在接口中属性的读(read)和写(write)限定符必须是方法。 38 | * 接口没有构造函数和析构函数,他们不能直接被实例化,除非使用实现了他们方法的类。 39 | 40 | 13. 实现接口 41 | 42 | 要实现接口,需要声明一个从该接口继承的类,并实现该接口的方法。 43 | 44 | ```pascal 45 | type className = class(ancestorClass, interface1,……, interfacen) 46 | memberList 47 | end; 48 | ``` 49 | 50 | 类可以实现一个接口,或将一个接口赋值给一个属性。但是并不是任何类都可以实现接口(除非该类中已经有了支持IInterface接口或他的派生接口的实现,并保证实现了__AddRef, _Release和 _QueryInterface方法)。最简的方式就是该类继承自于TInterfacedObject类,该类已经实现了上面的三个方法。 51 | 52 | 14. 类可以通过相同的名称,参数和调用规范来声明实现一个接口所包含的每一个方法。**delphi自动将类的方法与接口的方法匹配起来。**如果想用一个不同的名称,就可以将接口方法重定向到一个不同名称的方法。被重定向的方法必须具有与接口方法相同的参数和调用规范。但一个类以同样的方法名称实现多个接口时,这一特性就显得特别重要了。 53 | 54 | ```pascal 55 | type 56 | TFootball = class(TinterfacedObject, Ifoot, IBall) 57 | function F1: Integer; 58 | function F2: Integer; 59 | end; 60 | implemetation 61 | 62 | function Tfootball.F1: integer; 63 | begin 64 | Result := 0; 65 | end; 66 | 67 | function Tfootball.F2: Integer; 68 | begin 69 | Result := 9; 70 | end; 71 | ``` 72 | 73 | 15. 如果一个类要实现多个接口,而这些接口中包含同名的方法,则必须把同名的方法另取一个别名 74 | 75 | ```pascal 76 | type 77 | IFoot: interface 78 | function F1: Integer; 79 | end; 80 | 81 | IBall = interface 82 | function F1: integer; 83 | end; 84 | 85 | TFootBall = class(TInterfacedObject, IFoot, IBall) 86 | //为同名方法取别名 87 | function IFoot.F1: FootF1; 88 | function IBall.F1: BallF1; 89 | //接口方法 90 | function FootF1: integer; 91 | function BallF1: Integer; 92 | end; 93 | implementation 94 | 95 | function TFootball.FootF1: integer; 96 | begin 97 | Result := 0; 98 | end; 99 | 100 | function TFootBall.BallF1: integer; 101 | begin 102 | Result := 0; 103 | end; 104 | 105 | ``` 106 | 107 | 16. 当在应用程序中使用接口类型变量时,要用到一些重要的语法规则。最需要记住的是,一个接口是生存期自动管理类型。这意味着,他通常被初始化为nil。接口是引用计数的,当获得一个接口时自动增加一个引用计数;当它离开作用域或赋值为nil时就被自动销毁。 108 | 109 | 17. 因为delphi编译器产生对_AddRef和 _Release 的调用来管理接口对象的生命周期,所以,为了使用delphi的自动引用计数,可以一个接口声明一个变量。当分配一个接口引用给一个接口变量时,Delphi就自动调用_ AddRef。 当该变量超出范围时,Delphi就自动调用_ Release。_ AddRef和_ Release的行为完全由用户控制。如果时从TInterfacedObject继承而来的,这些方法将实现引用计数。_ AddRef方法增加引用数,_ Release则减少他。当引用数变为零的时候,_Release 就销毁对象。如果是从一个不同的类继承而来的,则可以定义这些方法来做任何事情。但是,应当正确实现QueryInterface,因为Delphi依靠它来实现as运算符。 110 | 111 | 下面的代码演示了一个接口变量的生存期自管理机制。 112 | 113 | ```pascal 114 | var 115 | I: ISomeInterface; 116 | begin 117 | //I被初始化为nil 118 | I := FunctionReturningAnInterface;//I的引用计数加1 119 | I.SomeFunc; 120 | //I的引用计数减1,如果为0,则自动销毁。 121 | end; 122 | ``` 123 | 124 | 18. 接口也可以实现类似类的类型转换。 125 | 126 | ```pascal 127 | type 128 | IGreetable = interface 129 | function SayHello: string; 130 | end; 131 | 132 | TMan = class(TInterfacedObject) 133 | public 134 | Name: string; 135 | Language: string; 136 | end; 137 | 138 | TChinese = class(TMan, IGreetable) 139 | private 140 | function SayHello: string; 141 | end; 142 | procedure TfrmSayHello.sayhello(AMan: TMan); 143 | var 144 | G: IGreetable; 145 | begin 146 | //类实现的多态 147 | edtName.Text := AMan.Name; 148 | edtLanguage.Text := AMan.Language; 149 | //通过类型转换,就可以获得接口类型和接口方法 150 | G := AMan as IGreetable; 151 | G.SayHello; 152 | end; 153 | ``` 154 | 155 | 19. 可以将实现接口的任何类的一个实例赋给那个接口变量,当让,只有这个接口中定义的方法才可以在该对象上被予以调用。 156 | 157 | 20. 如果声明一个接口类型的变量,则他可以引用任何实现这个接口的类实例。这样的变量使我们可以调用接口的方法,而不必在编译时知道接口是在哪实现的。但要注意以下限制 158 | 159 | * 使用接口类型的表达式只能访问接口定义的方法和属性,而不能访问实现类的其他成员。 160 | * 一个接口类型的表达式不能引用实现了他的派生类接口的类实例,除非这个类(或它的继承类)还明确实现了此祖先接口。 161 | 162 | ```pascal 163 | type 164 | IGreetable = interface 165 | function SayHello: string; 166 | end; 167 | 168 | IMan = interface(TGreetable) 169 | function SayHello: string; 170 | end; 171 | 172 | TChinese = Class(TInterfacedObject, IMan) 173 | procedure SetChinese(name: string); 174 | function SayHello: string; 175 | end; 176 | 177 | var 178 | AMan: IMan; 179 | Greeting: IGreetable; 180 | begin 181 | AMan := TChinese.create; //工作正常 182 | Greeting := TChinese.create; //错误 183 | AMan.SetChinese('张三'); //错误 184 | AMan.SayHello;//工作正常 185 | end; 186 | //如果TChinese的定义为 187 | TChinese = class(TInterfacedObject, TGreetable, IMan) 188 | end; 189 | //那么可以定义 190 | Greeting := TChinese.create;//正确 191 | 192 | //如果AMan的定义为 193 | AMan: TChinese; 194 | //那么下面这句话变得可用 195 | AMan.SetChinese('张三'); 196 | ``` 197 | 198 | 要判断一个接口类型的表达式是否引用了一个对象,可以通过标准函数Assigned来完成。 199 | 200 | 21. 不同接口之间可以在自己的方法中相互调用对方,形成依赖关系。但是,相互继承的接口时不允许的。比如,IControl派生IWindow,又从IWindow派生IControl是非法的。如果要定义相互依赖的接口,需要提前声明没有定义的接口。 201 | 202 | ```pascal 203 | type 204 | IControl = inteface; //IControl的Forward声明 205 | IWindow = interface 206 | ['{GUID}'] 207 | function GetControl(Index: Integer): TControl; 208 | //如果没有IControl的Forward声明,GetControl函数返回IControl类型就是非法的。 209 | end; 210 | 211 | IControl = interface//IControl的实际声明 212 | ['GUID'] 213 | function GetWindow: IWindow; 214 | end; 215 | 216 | ``` 217 | 218 | 22. 当实现一个接口的时候必须实现接口内的所有方法。 219 | 220 | 23. 接口继承与类继承不同,接口的继承仅仅是为了输入方便,不需要重新输入很多方法声明,当类实现接口时,并不意味这类会自动实现了其祖先接口,类仅仅实现那些列入其声明(以及其祖先类声明)之中的接口。 221 | 222 | ## 注意 223 | 224 | ​ 从TComponent中派生对象中可以取出接口,却没有实现引用计数机制。这就意味着从TComponent派生对象中可以取出接口,却不能利用计数自动管理接口对象的生命期,因此,如果使用TComponent作为基类,并实现某些接口之后,可能会因为对象生命期管理的误区,造成内存泄漏,例如 225 | 226 | ```pascal 227 | TComponent = class(TPersistent, Interface, IInterfaceComponentReference) 228 | Component := TComponent.Create(nil); 229 | vIntf := (TComponent as Interface);//会造成内存泄漏 230 | ``` 231 | 232 | ​ 这样会造成内存泄漏,因为Component对象不会因为引用计数变成0而自动销毁,实际上这里的引用计数永远不会变成0。必须要手动写进free命令来销毁对象。但是下面的代码会自动销毁 233 | 234 | ```pascal 235 | Component := TComponent.Create(self); 236 | vIntf := (Component as Interface); 237 | ``` 238 | 239 | 这样就不会造成内存泄漏,因为Component对象创建是指定了属主对象。那么它的生命周期由属主来管理,最后由属主对象在销毁自己时销毁Component对象。 240 | 241 | ​ 下面时问题最大的代码 242 | 243 | ```pascal 244 | Component := TComponent.Create(nil); 245 | Component2 := TMyComponent.Create(nil); 246 | Component2.SomeInterface := Component as SomeInterface; 247 | Component.Free; 248 | …………………… 249 | Component2.free; 250 | ``` 251 | 252 | ​ 交给接口管理的对象,如果接口实现了Release方法,那么就不能够手动Free。 -------------------------------------------------------------------------------- /接口2.md: -------------------------------------------------------------------------------- 1 | # Interface 2 | 3 | ```pascal 4 | 对象名.GetInterface(接口名称,接口对象)//获得接口,返回Boolean类型。 5 | ``` 6 | 7 | ## 接口委托 8 | 9 | ​ 接口委托分为两种:1. 对象接口委托;2. 类对象委托。 10 | 11 | ​ 对象接口委托:假如已有下面的接口定义 12 | 13 | ```pascal 14 | //接口声明 15 | IImplInterface = interface(IInterface) 16 | function ConvertToUSD(const INTD: integer): Double; 17 | function ConvertToRMB(const INTD: integer): Double; 18 | end; 19 | //接着有一个类实现了该接口 20 | TImplClass = class(TObject, IImplInterface) 21 | private 22 | FRefCount: integer; 23 | public 24 | function ConvertToUSD(const INTD: integer): Double; 25 | ............. 26 | end; 27 | 28 | //现在有另外一个类TIntfServiceClass要实现IImplInterface接口,不需要重新定义,只需使用上面的TImplClass就可以: 29 | TIntfServiceClass = class(TObject, IImplInterface) 30 | private 31 | FImplService: IImplInterface; 32 | //FSrvObj: TImplClass; //如果是用类对象委托的话 33 | public 34 | constructor Create; overload; 35 | Destructor Destroy; override; 36 | constructor create(AClass: TClass); overload; 37 | property MyService: IImplInterface read FImplService implements IImplInterface; 38 | //property MyService: TImplClass read FSrvObj implements IImplInterface;//如果时用对象委托的话。 39 | 40 | //实现如下 41 | constructor TIntfServiceClass.Create; 42 | begin 43 | FImplService := TImplClass.create; 44 | end; 45 | 46 | Constructor TIntfServiceClass.Create(AClass: TClass) 47 | var 48 | instance := TImplClass 49 | begin 50 | instance := TImplClass(AClass.NewInstance); 51 | FImplService := instance.Create; 52 | end; 53 | 54 | destructor TIntfServiceClass.Destroy; 55 | begin 56 | FImplService := nil; 57 | inherited; 58 | end; 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /数据类型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/数据类型.png -------------------------------------------------------------------------------- /新建dll1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/新建dll1.png -------------------------------------------------------------------------------- /新建dll2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/新建dll2.png -------------------------------------------------------------------------------- /新建dll3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/新建dll3.png -------------------------------------------------------------------------------- /新建dll4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/新建dll4.png -------------------------------------------------------------------------------- /新建dll流程.md: -------------------------------------------------------------------------------- 1 | 1. 更新环境 2 | 2. 编译代码 3 | 3. 提交代码 4 | 5 | ## 新建DLL工程要修改的配置 6 | 7 | ## Delphi Compiler 8 | 9 | * 打开该工程的Option,修改该工程的 **Output directory** , **Search path** ,其中 **Search path** 的配置如图所示 10 | 11 | ![](新建dll1.png) 12 | 13 | 图片中的5条记录是必须添加的记录。 14 | 15 | ## Version Info 16 | 17 | * 新建的Dll必须勾选 **Include version information in project**。不选中就那么编译出的Dll没有版本信息![](新建dll2.png) 18 | 19 | ## Packages 20 | 21 | * 新的Dll接口只和PMDzpb这个包有关。![](新建dll3.png) 22 | 23 | ## Debugger 24 | 25 | * Host application 要关联相应的应用程序![](新建dll4.png) 26 | 27 | ## 新建GUID 28 | 29 | * 使用软件生成新的GUID。 30 | * 新生成配置文件,以刚才生成的GUID命名。 31 | * 修改对应路径下的Dzpb.Xml文件,将上面生成的配置文件路径添加到文件中。 32 | * 配置模版信息。 33 | 34 | {E9D94203-8998-4D90-B869-75BC79C48558} 35 | 36 | {E9D94203-8998-4D90-B869-75BC79C48558} -------------------------------------------------------------------------------- /新版本MDB的常用方法.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/新版本MDB的常用方法.md -------------------------------------------------------------------------------- /新版本XML的常用方法.md: -------------------------------------------------------------------------------- 1 | # 加载DLL并使用DLL中的方法 2 | 3 | ```pascal 4 | type 5 | // 定义一个函数指针。 6 | TEpEncrypt = function(AGSBZ: PAnsiChar; ATotalValue: PAnsiChar): PAnsiChar; stdcall; 7 | // 调用 8 | var 9 | LibHandle: THandle; 10 | Epencrypt: TEpencrypt; 11 | begin 12 | // cst_Dzpb_AH_WuHu_2018_EpencryptDLL是对方提供的一个DLL,这个DLL放在主目录下。 13 | LibHandle := LoadLibrary(cst_Dpzb_AH_WuHu_2018_EpEncryptDLL); 14 | try 15 | if LibHandle = 0 then 16 | raise Exception.Create(Format(cst_Dzpb_AH_WuHu_2018_LoadError, [QuotedStr(cst_Dzpb_AH_WuHu_2018_EpEncryptDLL)])); 17 | @EpEncrypt := GetProcAddress(LibHandle, cst_Dzpb_AH_WuHu_2018_EpEncryptStr); 18 | if not (@Epencrypt = nil) then 19 | sValue := EpEncrypt(PAnsiChar(cst_Dzpb_AH_WuHu_2018_EncryptStr), PAnsiChar('0.00')); 20 | finally 21 | FreeLibary(LibHandle); 22 | end; 23 | end; 24 | ``` 25 | 26 | # 服务 27 | 28 | ## 费率设置服务 29 | 30 | * 如果现在在Xmjg表中,要获取某个专业节点下的费率。 31 | 32 | ```pascal 33 | /// 获取专业节点中某个特项中的某个费率。 34 | /// 下面是获取专业节点下特项是99的管理费(Glf)费率和利润(Lr)费率。 35 | var 36 | ADataContext: IDataContext; 37 | AFlszService: ISSFlszService; 38 | AFl: Double; 39 | ISFind: Boolean; 40 | begin 41 | ADataContext := FDzpbContext.FileDataContext.PrjDataManager.GetDataContext( 42 | ZygcNode.GetValueAsString(cst_Xmjg_Guid, '')); 43 | if FDzpbContext.FileDataContext.FileService.ExistsService(cst_FileService_Flsz) then 44 | AFlszService := FDzpbContext.FileDataContext.FileService.GetService(cst_FileService_Flsz) 45 | as ISSFlszService; 46 | if Assigned(ADataContext) and Assigned(AFlszService) then 47 | AGlffl := AFlszService.GetFLValue(ADataContext, cst_Fltx_DefaultFltxId, cst_fylx_glf, 48 | ISFind); // 杭州地区只有一个特项,99 49 | if ISFind then 50 | ZygcRec.SetValue(cst_Xml_ZyNodeXx_Glffl, AGlffl); 51 | end; 52 | 53 | /// 尝试获取费率和系数 54 | var 55 | dFL_WithoutXs, dXs: Double; 56 | begin 57 | // 当前工程节点,特项ID,费用类型,费率,系数 58 | FFlszService.TryGetFlAndXs(FDataContext, cst_Fltx_DefaultFltxId, cst_SsTableName_aqwm, 59 | dFL_WithoutXs, dXs); 60 | end; 61 | 62 | /// 通过费率设置服务获取flsz表和fltx表 63 | var 64 | FFlszDs, FTxgcDs: IPMDataSet 65 | begin 66 | FFlszDs := FFlszService.GetFlxmDataSet(FDataContext); 67 | FTxgcDs := FFlszService.GetTxgcDataSet(FDataContext); 68 | end; 69 | ``` 70 | 71 | * 如果在某个专业节点下,那么不用去找DataContext,其中的FDataContext就是该节点的DataContext。 72 | 73 | ## 获取人材机资源表(Glhz) 74 | 75 | ```pascal 76 | var 77 | FGlhzDs: IPMDataSet; 78 | begin 79 | FGlhzDs := FDzpbContext.FileDataContext.ResourceDataContext.GetDataSet(cst_Table_Glhz); 80 | end; 81 | ``` 82 | 83 | ## 获取XmjgDataSet 84 | 85 | ```pascal 86 | /// 获得模版ID 87 | var 88 | XmjgDs: IPMDataSet; 89 | begin 90 | XmjgDs := FDzpbContext.FileDataContext.ProjectStructureDataSet; 91 | if not Assigned(XmjgDs) then Exit; 92 | Rec := XmjgDs.FindRec(cst_Xmjg_Guid, FDataContext.DataToken); 93 | if Assigned(rec) then 94 | Result := XmjgDs.GetFieldValueAsInt(cst_Xmjg_Mbid, 0, Rec); 95 | end; 96 | ``` 97 | 98 | ## 获取Xmsx中的内容 99 | 100 | ```pascal 101 | /// cst_XMSX_Key_JjmbGuid = 'JjmbGuid'; 102 | FDzpbContext.FileDataContext.Fileoption.Xmsx.GetValue(cst_XMSX_Key_JjmbGuid); 103 | // 判断文件是招标文件还是投标文件 104 | function GetProjectStatus(APrjDataManager: IPrjDataManager): TProjectStatus; 105 | function GetProjectStatus(AFileOption: IYsFileOption): TProjectStatus; 106 | function GetProjectStatus(AXmsx: IYsFileOption_Xmsx): TProjectStatus; 107 | ``` 108 | 109 | # 配置文件 110 | 111 | ## 项目信息 112 | 113 | ```pascal 114 | var 115 | P: _PVarRec; 116 | begin 117 | FDzpbContext.DzpbConfig.XmxxVars.Count; 118 | FDzpbContext.DzpbConfig.XmxxVars.Vars[i]; 119 | // 这个是遍历获得xmxx 120 | P := FDzpbContext.DzpbConfig.BaVars_Jsxm.GetVarRecSsVar(SsNode.GetValueAsString(cst_qtxm_bl, '')); 121 | // 这个是根据胜算变量来获取BaJsxm中的某条记录 122 | end; 123 | 124 | // 组织措施中使用Fylx查找配置文件中的组织措施该Fylx的记录,并为Xml中第三方变量赋值 125 | var 126 | sFylx: string; 127 | _MeasureRec: _PzyMeasure; 128 | dFl_WithoutXs, dXs: Double; 129 | begin 130 | sFylx := SsNode.GetValueAsString(cst_Fbfx_Fylx, ''); 131 | _MeasureRec := FDzpbContext.DzpbConfig.ZyMeasure.GetZyMeasureRec(sFylx); 132 | if Assigned(_MeasureRec) then 133 | begin 134 | XmlRec.SetValue(cst_Xml_Csxm1Mx_Xmlb, _MeasureRec.MeasType); 135 | end; 136 | end; 137 | ``` 138 | 139 | ## 错误日志 140 | 141 | ```pascal 142 | FDzpbContext.DzpbLog.AddErrorLog(); 143 | ``` 144 | 145 | # 胜算工程 146 | 147 | ## 分部分项 148 | 149 | ```pascal 150 | /// 招标导出分部分项 151 | /// 采用的是遍历导,找到分布分项表之后,然后获得分部分项表中的根节点,然后通过根节点获得它所有的子节点,然后判断是分布还是清单分别导出。 152 | RootNode := GetFbfxRootNode(FSsDataView); 153 | var 154 | AItemType: TItemType; 155 | SsNode: IPMNode; 156 | begin 157 | for i := 0 to RootNode.Count - 1 do 158 | begin 159 | SsNode := RootNode.ChildNodes[i]; 160 | AItemType := GetItemType(SsNode); 161 | case AItemType of 162 | ntxmFb: begin end; 163 | ntxmQd: begin end; 164 | end; 165 | end; 166 | ``` 167 | 168 | ## 措施项目 169 | 170 | ```pascal 171 | /// 获取措施项目表中的根节点 172 | RootNode := GetZzcsRootNode(FSsDataView); 173 | ``` 174 | 175 | # 杂项 176 | 177 | ```pascal 178 | FDzpbContext.LockNumber // 加密锁号 179 | GetDiskDriveID_WMI // 获取电脑信息, 该方法在 uWMIInterface 这个单元下面 180 | TrimRight(); // 去掉右边空格 181 | GetMacAdress; // 获取Mac地址 182 | FDataContext.DataToken // 比较常用的获得DataToken的方法。 183 | SimpleRoundToEx(StrtoDouble(sValue), cst_Dzpb_AH_WuHu_2018_Decimal_Two) // 保留两位小数 184 | FDzpbContext.SysOption.Path.SysTempPath // 临时路径 185 | ExtractFileName(FileName); // 取文件名 186 | ExtractFilePath(FileName); // 取文件路径 187 | ExtractFileExt(FileName); // 取文件后缀名 188 | ``` 189 | -------------------------------------------------------------------------------- /日常学习.md: -------------------------------------------------------------------------------- 1 | * 类引用 2 | 3 | 在Object Pascal 中,还有一种**类之类** 的类型,也就时所谓的**类引用** 。一般所称的类,是对其实例对象的抽象,定义一个类 4 | 5 | ```pascal 6 | TMyClass = class 7 | ``` 8 | 9 | 而**类引用** 类型却是对**类** 的抽象(元类),所以被成为**类之类** 。定义一个类之类 10 | 11 | ```pascal 12 | TMyClassClass = class of TMyClass 13 | ``` 14 | 15 | **类之类** 可以直接调用**类** 的**类方法** 。如 16 | 17 | ```pascal 18 | TMyClass = class 19 | public 20 | Class procedure Show(); 21 | end; 22 | TMyClassClass = class of TMyClass; 23 | 24 | var 25 | MyClass: TMyClassClass; 26 | MyObj: TMyClass; 27 | begin 28 | MyObj := MyClass.Create; 29 | MyClass.Show; 30 | MyObj.Free; 31 | end; 32 | ``` 33 | 34 | 在此例中,**TMyClassClass** 作为**TMyClass**的元类,可以直接调用**TMyClass**的类方法。因为构造函数也是一个类方法,下面时相同的 35 | 36 | ```pascal 37 | MyObj := MyClass.Create(); 38 | ``` 39 | 40 | ```pascal 41 | MyObj := TMyClass.Create(); 42 | ``` 43 | 44 | 但是析构函数不是类方法,二是普通方法。因为析构函数只能销毁一个对象实例,其操作结果并非用于该类的所有对象。因此,销毁对象只能通过对象来调用析构函数而不能通过类方法。 45 | 46 | ```pascal 47 | MyObj.Free 48 | ``` 49 | 50 | **类方法** 和**类引用** 有什么作用呢?它主要用在类型参数化上,因为有时在编译时无法得知某个对象的具体类型,而需要调用其类方法(如构造函数),此时可以将类型作为一个参数来传递。 51 | 52 | ```pascal 53 | type TControlClass = class of TControl; 54 | 55 | function CreateControl(ControlClass: TControlClass;const ControlName: string; 56 | X, Y, W, H: Integer): TControl; 57 | begin 58 | Result := ControlClass.Create(MainForm); 59 | with Result do 60 | begin 61 | Parent := MainForm; 62 | Name := ControlName; 63 | SetBounds(X, Y, W, H); 64 | Visible := True; 65 | end; 66 | end; 67 | ``` 68 | 69 | CreateControl 函数具体创建一个控件对象,但是,由于他在编译时期并不知道需要其创建的对象的具体类型,因此其第一个参数ControlClass的类型时TControl的类引用类型----TControlClass, 这样就可以将所需要创建控件的类型延迟到运行期去决定。例如,在运行期要创建一个TButton类型的控件对象: 70 | 71 | ```pascal 72 | var 73 | Btn: TButton; 74 | begin 75 | Btn := CreateControl(TButton, 'Button1', 0, 0, 100, 20); 76 | ............. 77 | end; 78 | ``` 79 | 80 | 还有,经常可以在Delphi生成Application的project文件中找到这样的代码: 81 | 82 | ```pascal 83 | Application.CreateForm(TForm1, Form1); 84 | ``` 85 | 86 | TApplication 的CreateForm() 方法的第一个参数,也是类引用类型的: 87 | 88 | ```pascal 89 | procedure TApplication.CreateForm( 90 | //TComponentClass = class of TComponent; 类引用类型 91 | InstanceClass: TComponentClass; 92 | vat Reference 93 | ); 94 | ``` 95 | 96 | 允许在运行期确定类型,可以给程序带来莫大的灵活性。 97 | 98 | **在Object Pascal中,类方法中还可以使用self。不过,此时Self表示的是类,而非对象,因此使用上也有一些限制。** 99 | 100 | 如果时通过对象引用调用类方法,则self的值时该对象的类型; 如果时通过类名调用类方法,则self的值是该类本身。 101 | 102 | 由于在类方法中,Self的值是类,而非对象,因此只能通过self调用类的构造函数和其他方法。 103 | 104 | 下面的代码展示了类方法中self的使用方法: 105 | 106 | ```pascal 107 | interface 108 | type 109 | TClassMethodExample = class 110 | private 111 | FnInteger: Integer; 112 | public 113 | class function ClassMethod1(): Integer; 114 | class function ClassMethod2(): Integer; 115 | function Method(): Integer; 116 | end; 117 | 118 | implementation 119 | 120 | {TClassMethodExaple} 121 | 122 | class function TClassMethodExaple.ClassMethod1: Integer; 123 | begin 124 | self.Method(); //非法,因为Method不是类方法 125 | self.ClassMethod2();//合法 126 | end; 127 | 128 | class function TClassMethodExaple.ClassMethod2: Integer; 129 | begin 130 | Result := self.nInteger;//非法,类方法中不能由数据成员 131 | end; 132 | 133 | function TClassMethodExaple.Method: integer; 134 | begin 135 | Result := 0; 136 | end; 137 | end. 138 | ``` 139 | 140 | Delphi Class of 类引用也就是类的类型,也可以说时指向类的指针。 141 | 142 | * 消息分发 143 | 144 | 类的消息分发过程: Dispatch。 145 | 146 | * 字对齐,半字对齐和字节对齐 147 | 148 | 一般情况下,字为32位(4字节)、半字为16位(2字节)、字节为8位(1字节) 149 | 150 | ## PChar和String类型的区别 151 | 152 | String类型下标是从1开始的,PChar类型是从0开始的。 153 | 154 | ## showmessage/MessageDlg/application.MessageBox 155 | 156 | ## 都可以用来显示信息.但是你知道它们之间的区别吗 157 | 158 | ```pascal 159 | 在中文的Windows: 160 | ShowMessage的Form的Caption显示的是你的Application的Title,按钮是中文的; 161 | MessageDlg用起来比较方便,可以控制他的Title和显示的内容,以及出现的Icon但是按钮是英文的; 162 | MessageBox可以控制他的Title和显示的内容,以及出现的Icon,按钮是中文的 163 | ``` 164 | 165 | ## 生成GUID的方法 166 | 167 | ```pascal 168 | function GetGUIDstr: string; 169 | var 170 | TmpGUID: TGUID; 171 | begin 172 | if CoCreateGuid(TmpGUID) = S_OK then 173 | Result := GUIDToString(TmpGUID); 174 | end; 175 | ``` 176 | 177 | ## 去掉GUID中的{} 178 | 179 | ```pascal 180 | function GetGUIDStr(sGuid: string); 181 | ``` 182 | 183 | ## 防止程序假死 184 | 185 | ```pascal 186 | Application.ProcessMessages 187 | ``` 188 | 189 | ## GetMem的使用方法 190 | 191 | ```pascal 192 | GetMem(var P: Pointer, size: integer); // PChar 类型也是Pointer类型。 193 | ``` 194 | 195 | ## 函数 196 | 197 | ```pascal 198 | // 函数保存在某一段内存中,这块内存用函数名来命名, 而函数名也相当于一个变量,这个变量就是这个函数 199 | // 通过函数名可以获得这个函数在内存中的地址。 200 | function Fn_N_N(X: Integer): Integer; 201 | begin 202 | Result := X + 1; 203 | end; 204 | 205 | var 206 | Ptr: Pointer; 207 | begin 208 | Ptr := @Fn_N_N; // 通过函数名获得了函数在内存中的地址。 209 | end; 210 | ``` 211 | 212 | ## delphi中的取整函数 213 | 214 | ```pascal 215 | // Round(四舍六入五留双) 216 | var 217 | i, j: Integer; 218 | begin 219 | i := Round(1.5); // i等于2 220 | j := Round(2.5); // j等于2 221 | end; 222 | 223 | // RoundClassic(四舍五入) 224 | function RoundClassic(R: Real) 225 | 226 | // 2.trunc(取得X的整数部分) 227 | trunc(-123.55) = -123; 228 | floor(123.55) = 123; 229 | 230 | // 3.ceil(取得大于等于X的最小的整数) 231 | ceil(-123.55) = -123; 232 | ceil(123.15) = 124; 233 | 234 | //4.floor(取得小于等于X的最大的整数) 235 | floor(-123.55) = -124; 236 | floor(123.55) = 123; 237 | 注:floor和ceil是math unit里的函数,使用前要先Uses Math 238 | ``` 239 | 240 | ## 地址是可以用加减法的 241 | 242 | ```pascal 243 | // FArry是一个数组的首地址 244 | var 245 | FArry: PAnsiChar; 246 | ElementPtr: PAnsiChar; 247 | begin 248 | Inc(ElementPtr, FElementSize); 249 | System.Move(aItem^, (FArray + (aIndex * FElementSize))^, 250 | FActElemSize); 251 | end; 252 | ``` 253 | 254 | ## 检查内存泄露 255 | 256 | ```pascal 257 | ReportMemoryLeaksOnShutdown := True; 258 | ``` 259 | 260 | ## Supports 261 | 262 | ```pascal 263 | // 判断第一个对象是否支持 IID 这个接口, 如果支持的话,则将 Intf 这个接口赋值为 Instance 这个对象。 264 | function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean; 265 | begin 266 | Result := (Instance <> nil) and (Instance.QueryInterface(IID, Intf) = 0); 267 | end; 268 | ``` 269 | 270 | ## TApplication 271 | 272 | ```pascal 273 | // 今天才知道 TApplication 是直接从 TComponent 继承下来的。 274 | TApplication = Class(TComponent) 275 | ``` 276 | 277 | -------------------------------------------------------------------------------- /杭州地区的特点.md: -------------------------------------------------------------------------------- 1 | 1. 杭州只有一个特项,就是默认的特项Default,99; 2 | 2. 杭州地区中Xmjg表中有id字段,这个字段中存储的是招标文件中各个节点的GUID,也就是说杭州地区的招标文件中整体节点,单位节点和专业节点都有自己的GUID。 3 | 3. 宁波地区使用的是品茗接口,品茗接口现在有4.0和3.2的区分。其中4.0接口在投标导入时导入的是".招标文件",3.2的接口导入时导入的是PMZ文件。同一个地区的3.2和4.0的接口在导入招标文件时是通过后缀名来区分的。不同地区是根据解压PM文件解压出来的文件夹中的Version.ini文件中的areaname的值来判断的。 4 | 5 | -------------------------------------------------------------------------------- /派生类内存布局.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/派生类内存布局.png -------------------------------------------------------------------------------- /深入核心VCL架构分析.md: -------------------------------------------------------------------------------- 1 | # 第一章 2 | 3 | ## 事件/消息模型 4 | 5 | ```pascal 6 | // 消息的定义 7 | MyMessage = packed record 8 | MessageID: Longint; //发生事件的消息 9 | wParam: Longint; // 辅助信息,例如鼠标的位置或者用户输入的字符 10 | lParam: Longint; // 同上 11 | pt: TPoint; // 存储鼠标的全域坐标 12 | end; 13 | ``` 14 | 15 | 1. 应用程序由很多窗口,如何找到该消息对应的窗口。 16 | 2. 找到了窗口之后怎样将该消息分派给应用程序或窗口。 17 | 3. 当分派完成后,窗口怎样处理该消息。 18 | 19 | ```pascal 20 | MyMessage = packed record 21 | hwnd: HWND; // 代表窗口的独特识别值 22 | message: UINT; 23 | wParam: WPARAM; 24 | lParam: LPARAM; 25 | time: DWORD; 26 | pt: TPoint; 27 | end; 28 | ``` 29 | 30 | ​ 这个时候事件/消息模型已经相当完备了,这个时候还有一个问题,就是当用户快速的在应用程序中点击了数个位置不同的鼠标键,那么会产生好几个事件,因此,需要为应用程序来暂存消息。我们只需要为每一个应用程序建立一个消息队列(Message Queue),当事件发生时执行环境就把代表它的消息分派到消息队列中,等待应用程序从其中取出并处理。 31 | 32 | ​ 这样第一个问题就解决了,下面解决第二和第三个问题。 33 | 34 | ​ 首先执行环境如何为应用程序创建窗口,那么我们要获得下面的信息。 35 | 36 | * 窗口的位置和大小。 37 | 38 | * 窗口的格式,使用的颜色以及使用的光标种类。 39 | 40 | * 窗口使用的菜单以及其他的资源。 41 | 42 | * 当这个窗口发生事件时,能处理窗口消息的函数地址。 43 | 44 | ​ 当有了上面的信息之后,执行环境就可以为应用程序创建窗口了,那么应用程序如何给应用程序提供这些信息呢? 45 | 46 | ```pascal 47 | MyWindowClassInfo = packed record 48 | style: UINT; // 窗口的格式 49 | iWidth: Integer; 50 | iHeight: Integer; 51 | lpfnWndProc: Pointer; // 函数指针 52 | hIcon: HICON; 53 | hCursor: HCURSOR; // 窗口使用的光标种类 54 | hbrBackground: HBRUSH; 55 | lpszMenuName: PAnsiChar; 56 | lpszClassName: PAnsiChar; // 类名称 57 | hIconSm: HICON; 58 | end; 59 | ``` 60 | 61 | ​ 当然,由于应用程序可以创建多个窗口,因此它可以提供多个窗口信息数据结构,他为每一种窗口提供一个MyWindowClassInfo数据结构,在分别向执行环境注册每一种窗口的MyWindowClassInfo信息,那么当它需要创建特定的窗口种类时,只要提供欲创建窗口的类名称,执行环境再根据类名称找到相对应的MyWindowClassInfo数据结构,最后根据MyWindowClassInfo之中的信息来创建窗口。因此在MyWindowClassInfo数据结构中LpszClassName字段代表的就是窗口的类名称。 62 | 63 | ​ 下面讨论剩下的两个问题:1. 找到了窗口之后如何发给这个窗口; 2. 窗口接受到消息之后怎么处理消息 64 | 65 | ​ 要让窗口处理消息最简单的方法就是让执行环境调用窗口提供的函数并传递消息给此函数来处理。但如何让窗口提供函数给执行环境,执行环境才能够知道在消息产生后调用什么函数呢?应用程序只需要把一个能够处理消息的函数的地址指定给MyWindowClassInfo中特定的字段给执行环境就可以了,所以lpfnWndProc字段就是一个Pointer类型的字段,代表应用程序可以吧任何的函数地址指定给此字段来代表可以处理此窗口消息的函数。由于这个函数会由执行环境调用,因此这种函数也被成为回调函数。 66 | 67 | ​ 由于执行环境在调用应用程序提供的回调函数时必须传递和消息相关的信息给此函数,因此执行环境必须定义此函数的原型(Prototype), 意即执行环境必须定义此回调函数接受的参数格式,回传数值以及调用惯例等,如此一来执行环境才能够正确传递必要的参数给会掉函数。例如,执行环境可以定义回调函数必须要有如下原型 68 | 69 | ```pascal 70 | function WindowProc(Window: HWnd; AMessage: UINT; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall; export; 71 | ``` 72 | 73 | ## 撰写Windows应用程序的步骤 74 | 75 | * 定义窗口类的内容,以决定窗口的创建格式以及回调函数 76 | 77 | * 注册窗口类 78 | 79 | * 创建窗口 80 | 81 | * 进入窗口消息处理循环以便让回调函数处理消息窗口,知道应用程序结束位置。 82 | 83 | ​ 下面的object Pascal 程序即遵照了上面的步骤来开发原生Windows应用程序 84 | 85 | ```pascal 86 | program ObjectPascalWinHello; 87 | 88 | uses 89 | Windows, Messages; SysUtils; 90 | const 91 | AppName = 'ObjectPascalHello'; 92 | function WindowProc(Window: HWnd; AMessage: UINT; WParam: WPARAM; 93 | LParam: LPARAM): LRESULT; stdcall; export; 94 | var 95 | dc: hdc; 96 | ps: TPaintStruce; 97 | r: TRect; 98 | begin 99 | WindowProc := 0; 100 | case AMessage of 101 | WM_ 102 | end; 103 | ``` 104 | 105 | 106 | # 第二章 VCL的诞生和设计原理 107 | 108 | ## 设计目标 109 | 110 | 1. 采用单继承模式 111 | 2. VCL FrameWork必须不限于16位或32位平台 112 | 3. 必须提供开放的组件架构,以允许程序员开发自定义空间 113 | 4. 必须进化成可在设计时期即提供功能的Framework 114 | 5. 必须使用PME(Property-Event-Method)模型 115 | 6. 必须使用面向对象计数来设计和实现 116 | 7. 必须完善的封装和分派窗口消息 117 | 118 | ## VCL对象声明的成型 119 | 120 | ​ 由于VCL使用面向对象技术设计和实现,所以第一步要提供VCL对象声明周期(Object Lifecyle)以及VCL对象管理(Object Management)的能力。一旦有了这个基础的对象能力,其他的VCL Framework就可以依据此基础服务来派生出其他的服务。 121 | 122 | ​ 基本的对象管理服务至少包括以下的服务 123 | 124 | 1. 对象的创建和初始化 125 | 2. 对象方法的分配 126 | 3. 对象的消灭 127 | 128 | ## Object Pascal 的对象模型 129 | 130 | ```pascal 131 | TObject = class 132 | constructor Create; 133 | destructor Destroy; virtual; 134 | end; 135 | ``` 136 | 137 | ```pascal 138 | TMyObject = class(TObject) 139 | destructor Destroy; override; 140 | end; 141 | 142 | Obj := TMyClass.Create; 143 | ``` 144 | 145 | ​ 当运行上面那句话的时候,到底发生了什么呢?其实发生了很多事情,包括分配内存,设定字段变量数据结构以及设定执行框架等工作,因此,上面的代码也可以分解成如下的代码 146 | 147 | ```pascal 148 | TMyObject.AllocateMemory; 149 | TMyObject.InitializeSpecialFields; 150 | Obj := TMyObject.SetupExecFrame; 151 | ``` 152 | 153 | ​ 创建对象的第一步是分配内存,Object Pascal会使用内建的内存管理器来为对象分配内存 154 | 155 | ```pascal 156 | PMemoryManager = ^TMemoryManager; 157 | TMemoryManager = record 158 | GetMen: function(Size: Integer): Pointer; 159 | FreeMem: function(P: Pointer): Integer; 160 | ReallocMen: function(P: Pointer; Size: Integer): Pointer; 161 | end; 162 | ``` 163 | 164 | ​ 在分配了对象原始内存之后,Object Pascal 的对象模型会先初始化所有的内存内容为0: 165 | 166 | ```pascal 167 | FillChar(Instance^, InstanceSize, 0); 168 | ``` 169 | 170 | ​ 之后会对一些特别的字段进行初始化,例如虚拟方法表,接口的引用计数,动态数组初始化内存块。 171 | 172 | ## 从原始内存到对象雏形 173 | 174 | ​ 把对象模型分配的内存内容变成活生生的存在于内存中的对象。这需要为对象设定正确的VMT并串联起正确的继承架构。 175 | 176 | ## Object Pascal对象服务 177 | 178 | 1. 对象创建服务 179 | 2. 对象释放服务 180 | 3. 对象识别服务:提供对象判断,识别的机制 181 | 4. 对象信息服务:提供程序代码存取对象信息的服务 182 | 5. 对象消息分派服务:提供Object Pascal分派消息的服务,和VCL封装窗口消息有密切关系。 -------------------------------------------------------------------------------- /灵光一闪.md: -------------------------------------------------------------------------------- 1 | * 想到老师和学生的例子,就是老师一声令下,学生开始打扫厨。这个就是继承的原理,同一个方法在子类中有不同的表现形式。那么这个是面向对象程序的基本服务。把这个联想到VCL架构设计中,这个是VCL framework必须要实现的服务。要实现这个功能就要有对象识别服务、对象信息服务和对象消息分派服务。 2 | 3 | -------------------------------------------------------------------------------- /电子评标工作流程.md: -------------------------------------------------------------------------------- 1 | * 当修改完代码后流程 2 | 1. 更新: 更新本地代码,得到最新版本 3 | 2. 对比: 首先选择None,不选中任何文件,然后找到要提交的文件,看一下本地代码和服务器上的代码的区别,查找冲突。 4 | 3. 修改完代码后重新编译代码,以防修改代码后导致编译出错 5 | 4. 写注释: 修正:杭州淳安18电子评标新点接口修改需求(llw)BugId:17792 。 6 | 5. 提交代码 7 | 6. 将QC中的Bug状态修改从新建修改为提交验证。 8 | * 如果修改了程序中的内容 9 | 1. 打开project->EureKaLog,勾选Activate EurekaLog 10 | 2. 打开Project->Hints and Warnings,全部改为True。 11 | * 新接口上传流程 12 | 1. 上传4个文件 13 | 2. 上传dpr和dproj文件 14 | 3. 上传res文件 15 | 4. 上传PmSs\DzpbV2\Core中的uYsDzpb.IDConst.pas文件 16 | 17 | -------------------------------------------------------------------------------- /电子评标常用的单元.md: -------------------------------------------------------------------------------- 1 | # 电子评标中常用的单元 2 | 3 | ## 常量单元 4 | 5 | ```pascal 6 | uPMSSDataConst, uFileConst, uSoftConst, uYsDzpb.TableNameConst; 7 | ``` 8 | 9 | ## 常用的核心单元 10 | 11 | ```pascal 12 | SysUtils, uYsDzpb.Xml, uYsDzpb.Api, uYsDzpb.ConstDef, uYsDzpb.Intf, uDataManagerIntf, uYsDzpb.CoreImpl, uFileUtils; 13 | 14 | uPMDataIntf: 定义了IPMDataSet, IPMDataView这种的品茗数据结构。 15 | 16 | ufrmProgress: 定义了进度条窗体。 17 | 18 | // ufrmProgress 的使用方法 19 | ShowProgress(cst_InfoMsg_ExportWaiting, XmTreeView.GetRecCount); 20 | try 21 | finally 22 | HideProgress; 23 | end; 24 | 25 | uDzpbMessageConst: 日志常量定义 26 | ``` 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /电子评标笔记.md: -------------------------------------------------------------------------------- 1 | # 电子评标笔记 2 | 3 | ## 基础知识 4 | 5 | 1. 分部,清单,定额属于分部分项模块 6 | 2. 计价方式由两种,一种是清单计价,另一种是定额计价。其中清单计价是国标;定额计价是非国标,定额计价中没有清单。 7 | 3. 分布下面能有子分部,但是清单下面不能有子清单,定额下面不能有子定额。 8 | 4. 技术措施的导出与分布分项一致。 9 | 5. 招标文件的导出有三种格式:XML,MDB和品茗流文件。 10 | 6. GT(国泰)和XD(新点)是同一个地方,国泰新点。 11 | 7. 工程量清单是建设工程的分部分项工程项目。 12 | 8. 招标的主要业务: 13 | * 导出招标文件 14 | * 导出标底文件 15 | * 导出招标控制价(一般和上面那个选其一) 16 | 9. 投标的主要业务 17 | * 导入招标文件 18 | * 跟新招标文件(用的比较少) 19 | * 导出投标文件 20 | 10. 除税价就是市场价 21 | 22 | ## 招标导出 23 | 24 | 25 | 26 | ## 投标导入 27 | 28 | ### 分部分项 29 | 30 | 1. 分部分项中的预算价(Ysj)和招标文件中分部分项模块的控制价(kzj)是同一个字段。 31 | 32 | 33 | ## 投标导出 34 | -------------------------------------------------------------------------------- /电子评标详解.md: -------------------------------------------------------------------------------- 1 | # 电子评标详解 2 | 3 | * 电子评标实质上就是数据转换,导出是将软件中的信息根据XSD(数据交换标准)转化成要求的数据格式;导入是将标准的数据转化成软件中的数据。 4 | * 电子评标中投标导入的过程相对于而言比较复杂,下面就以投标导入来说明一下。 5 | * 胜算中是将数据转化过程分成了三层,第一层根据招投标类型分成了两个基类,分别是TDzpb_TB_XML和 TDzpb_ZB_Xml,这两个基类的主要目的是获得正确的数据交换接口. 6 | 7 | ```pascal 8 | TDzpb_ZB_XML = class(TDzpb_ZB_Trans) 9 | protected 10 | function GetDzpbConfigName: string; override; 11 | function DoExport(ADzpbKind: TDzpbKind): Boolean; override; 12 | protected 13 | function GetExportZbClass: TDzpb_XML_ExportClass; virtual; // 获得招标导出接口 14 | function GetExportZbKzjClass: TDzpb_XML_ExportClass; virtual; // 获得招标控制价导出接口 15 | function GetExportBaClass: TDzpb_XML_ExportClass; virtual; // 获得备案导出接口 16 | /// 17 | /// 增加导出标底 18 | /// 19 | function GetExportBdClass: TDzpb_XML_ExportClass; virtual; // 获得标底导出接口 20 | function GetExportJzGcClass: TDzpb_XML_ExportClass; virtual; 21 | function GetExportSzGcClass: TDzpb_XML_ExportClass; virtual; 22 | end; 23 | 24 | TDzpb_TB_XML = class(TDzpb_TB_Trans) 25 | protected 26 | FXmlProject: IDzpb_Xml_Project; 27 | function GetXmlProject: IDzpb_Xml_Project; virtual; 28 | //获取配置文件 29 | function GetDzpbConfigName: string; override; 30 | function LoadFromBidFile(AFileName: string): Boolean; override; 31 | function GetPrjTree: IPmDataSet; override; 32 | function GetImportFilter: string; override; 33 | function GetUpdateFilter: string; override; 34 | procedure ImportXmTree(SysOption: IYsSysOption; XmlProject: IDzpb_Xml_Project; 35 | SsDataSet: IPMDataset); 36 | protected 37 | //导入 38 | function DoImport: Boolean; override; // 调用GetImportBusiClass, 获得导入类,导入类对象调用导入方法。 39 | //更新 40 | function DoUpdate: Boolean; override; 41 | //导出 42 | function DoExport(ADzpbKind: TDzpbKind): Boolean; override; 43 | protected 44 | function GetImportBusiClass: TDzpb_XML_ImportClass; virtual; // 获得导入类 45 | function GetUpdateBusiClass: TDzpb_XML_UpdateClass; virtual; // 获得更新类 46 | function GetExportBusiClass: TDzpb_XML_ExportClass; virtual; // 获得投标导出类 47 | public 48 | destructor Destroy; override; 49 | end; 50 | ``` 51 | 52 | * 投标类详解(DLL调用方式) 53 | 54 | ```pascal 55 | function TNewProjectUIManager.GetZbFileName_Dll(out AsFileName, AsErrMsg: string): TNewPrj_Result; 56 | begin 57 | // 1. 根据后缀名获得招标文件 FileName 58 | sOpenFilePath := ExtractFilePath(FileName); 59 | // 2. LoadFromBidFile(Filename), 对于特殊的模版需要处理,例如杭州的接口hbzb格式的文件。 60 | FDataMgr.DzpbDllIntf.LoadFromBidFile(FileName); 61 | // 3. 判断计价模版, 核心接口(基类的基类)返回True; 62 | if not FDataMgr.DzpbDllIntf.IsSameJgbb then 63 | // 4. 获得项目树,判断是否能够创建项目树 64 | if FDataMgr.DzpbDllIntf.GetPrjTree = nil then 65 | // 检查招标文件与计价规则是否一直 66 | if not FDataMgr.DzpbDllIntf.IsCheckMb(JjmbInfo.sGuid, FileName, sTip) then 67 | end; 68 | 69 | //投标基类 70 | function TDzpb_TB_XML.LoadFromBidFile(AFileName: string): Boolean; 71 | begin 72 | Result := False; 73 | if Assigned(FXmlProject) and SameText(AFileName, FBidFileName) then 74 | begin 75 | Result := True; 76 | Exit; 77 | end; 78 | if Not FileExists(AFileName) then 79 | Exit; 80 | Result := inherited LoadFromBidFile(AFileName); 81 | FXmlProject := nil; 82 | FXmlProject := GetXmlProject; 83 | Result := FXmlProject.LoadFromFile(AFileName); 84 | if Not Result then 85 | FXmlProject := nil; 86 | end; 87 | 88 | // 投标基类的基类 89 | function TDzpb_TB_Trans.LoadFromBidFile(AFileName: string): Boolean; 90 | begin 91 | FBidFileName := AFileName; 92 | Result := False; 93 | end; 94 | 95 | // 杭州的特殊处理,杭州的压缩文件是一个很长的二进制流(好像是这样,肯定是一段流) 96 | function TTB_XML_GD_ZJ_HangZhou_2018.LoadFromBidFile( 97 | AFileName: string): Boolean; 98 | begin 99 | Result := False; 100 | if Not FileExists(AFileName) then 101 | Exit; 102 | Result := inherited LoadFromBidFile(AFileName); 103 | FXmlProject := nil; 104 | FXmlProject := GetXmlProject; 105 | // 获得正确的招标文件 106 | AFileName := GetRealImportFileName(AFileName); 107 | Result := FXmlProject.LoadFromFile(AFileName); 108 | if Not Result then 109 | FXmlProject := nil; 110 | end; 111 | 112 | // 杭州需要从hbzb文件中获得正确的招标文件 113 | function TTB_XML_GD_ZJ_HangZhou_2018.GetRealImportFileName(AFileName: string): string; 114 | var 115 | sFileExt, sZipFilePath, sUnZipPath, sTempDir: string; 116 | begin 117 | FZBFile := AFileName; 118 | Result := AFileName; 119 | sFileExt := WideUpperCase(ExtractFileExt(AFileName)); 120 | if SameText(sFileExt, cst_Dzpb_ZJ_HangZhou_2018_FileExt_ZbFile) then 121 | begin 122 | Result := ''; 123 | // 解密后文件临时路径 124 | sTempDir := FDzpbContext.SysOption.Path.SysTempPath + 125 | cst_Dzpb_ZJ_HangZhou_2018_DefaultUnZipDir; 126 | // 解密后压缩文件路径 127 | sZipFilePath := DecodeXmlFilesToZip(AFileName, sTempDir);// 将流转化成压缩文件 128 | if SameText(sZipFilePath, EmptyStr) then 129 | Exit; 130 | // 解压后文件路径 131 | sUnZipPath := UnZipXmlFiles(sZipFilePath, sTempDir); // 解压压缩文件 132 | if SameText(sUnZipPath, EmptyStr) then 133 | Exit; 134 | FZBFile := GetZbXmlPath(sUnZipPath); // 获得正确的招标文件 135 | Result := FZBFile; 136 | end; 137 | end; 138 | ``` 139 | 140 | -------------------------------------------------------------------------------- /结构化存储.md: -------------------------------------------------------------------------------- 1 | # 结构化存储 2 | 3 | ​ 结构化存储机制又称为永久存储机制。COM也提供了结构化存储的实现,即复合文档技术。复合文档技术是OLE技术的基础。 4 | 5 | ​ 流对象是一个由COM实现的组件对象,它实现了基本的COM接口IStream,应用程序通过IStream接口访问流对象,进行各种数据操作。 6 | 7 | ​ 从概念上讲,流对象非常类似于单独的磁盘文件,它也是进行数据读写操作的基本对象,利用流对象可以保存各种类型的数据,他有自身的访问权限和一个独立的搜索指针。流对象为我们提供了连续的字节流存储空间,虽然实际的存储位置在文件中不一定连续,但COM封装了内部的数据结构,提供了连续的字节流抽象结构,就好像磁盘文件在磁盘上不一定占有连续的扇区一样。 8 | 9 | ​ 存储对象类似于目录对象,它也有一个字符串名称,但它本身被没有存储数据信息,他作为其子存储对象和子流对象的容器,只记录了这些子对象的信息。 10 | 11 | -------------------------------------------------------------------------------- /继承.md: -------------------------------------------------------------------------------- 1 | # 继承 2 | 3 | ## delphi 只支持C++中的public继承 4 | 5 | ```pascal 6 | type 7 | TBase = class 8 | public 9 | FBaseMem1: integer; 10 | FBaseMem2: integer; 11 | end; 12 | 13 | TDerived = class(TBase) 14 | public 15 | FDerivedMem: Integer; 16 | end; 17 | 18 | TDeriver2 = class(TDeriver) 19 | public 20 | FDerived2Mem1: integer; 21 | FDerived2Mem2: integer; 22 | end; 23 | ``` 24 | 25 | ![](派生类内存布局.png) 26 | 27 | ​ 可见 28 | 29 | ## 语义上的继承 30 | 31 | 1. 多态置换原则:当A是一种B的时候,那么A的容器(绝对)不是B的一种容器。例如**可乐是一种液体,但是可乐罐并不是一种液体罐;苹果是一种水果,但是苹果袋并不是一种水果袋** 。**还有一种就是圆不是椭圆** 32 | 33 | ```pascal 34 | TEllipse = class 35 | public 36 | ……………… 37 | procedure SetSize(x, y: Integer); 38 | end; 39 | ``` 40 | 41 | SetSize()函数可以设置椭圆的长半径和短半径,但是圆要求半径相等。 42 | 43 | ```pascal 44 | //父类声明 45 | type 46 | TEnging = class(TObject) 47 | private 48 | FCapacity: Integer; 49 | FPower: Integer; 50 | protected 51 | 52 | public 53 | property Capacity: Integer read FCapacity write FCapacity; 54 | property Power: Integer read FPower write FPower; 55 | procedure Inits(); 56 | published 57 | 58 | end; 59 | //////////////////////////////////////////////////////////////// 60 | implementation 61 | ///////////////////////////////////////////////////////////////// 62 | procedure TEnging.Inits; 63 | begin 64 | Capacity := 1; 65 | FPower := 2; 66 | end; 67 | 68 | //子类声明 69 | type 70 | TEngingson = class(TEnging) 71 | private 72 | Faa: Integer; 73 | Fbb: Integer; 74 | public 75 | property aa: Integer read Faa write Faa; 76 | property bb: Integer read Fbb write Fbb; 77 | procedure Inits(); 78 | end; 79 | /////////////////////////////////////////////////////////////////// 80 | implementation 81 | /////////////////////////////////////////////////////////////////// 82 | procedure TEngingson.Inits; 83 | begin 84 | inherited; 85 | Capacity := 3; 86 | Power := 4; 87 | end; 88 | ////////////////////////////////////////////////////////////// 89 | procedure TForm13.btnTestconClick(Sender: TObject); 90 | var 91 | Enging: TEnging; 92 | EngingSon: TEngingson; 93 | begin 94 | Enging := TEngingson.create; 95 | Enging.Inits; 96 | ShowMessage(IntToStr(Enging.Power)); 97 | ShowMessage(IntToStr(Enging.Capacity)); 98 | end; 99 | ``` 100 | 101 | ## 自己的理解 102 | 103 | ​ 继承是父类和子类之间的关系,和是不是虚方法没有关系,即使不是虚方法,子类也会从父类继承下来。继承就是把父类的所有成员全部继承下来。 104 | 105 | ​ 构造方法的原理: 当你调用那个构造方法的时候,就是使用哪个构造方法来初始化对象,和构造函数是不是虚函数没有关系。 106 | 107 | ​ 当基类中的函数为虚函数时,如果子类没有重写该方法或者重写了没有使用**override** 关键字来修饰该方法,那么像上面的声明方式 108 | 109 | ```pascal 110 | Enging: TEnging; 111 | Enging := TEngingson.create; 112 | Enging.Inits; 113 | ``` 114 | 115 | ```pascal 116 | TEnging: procedure Inits(); 117 | TEngingSon: procedure Inits(); 118 | //会调用Enging.Inits 119 | 120 | TEnging: procedure Inits(); virtual; 121 | TEngingSon: 没有申明; 122 | //会调用Enging.Inits 123 | 124 | TEnging: procedure Inits(); virtual; 125 | TEngingson: procedure Inits(); 126 | //会调用Enging.Inits; 127 | 128 | TEnging: procedure Inits(); virtual; 129 | TEnging: procedure Inits(); override; 130 | //会调用EngingSon.Inits(); 131 | 132 | ``` 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /编程理解.md: -------------------------------------------------------------------------------- 1 | # 编程理解 2 | 3 | -------------------------------------------------------------------------------- /胜算中一些功能的思考.md: -------------------------------------------------------------------------------- 1 | ## 小数点保留位数 2 | 3 | 在系统属性中有一个小数点保留位数属性字段和是否使用系统小数位数字段,在软件显示数据的时候,根据该配置去设置显示的小数位数。 4 | 5 | ## FDzpbContext 6 | 7 | ## 投标电子评标接口 8 | 9 | 该接口的功能主要包括加载xml工程,加载招标文件,获取招标文件的密码(招标文件可能是加密压缩的文件),该招标文件是否和dll模版相同,结构版本是否相同,是否进行增值税模式检查,构建项目树(因为导入时要导入项目树,这里使用导入事务类的构建项目树方法(保护类型),***这里着重说明以下,投标导出类和投标导出事务类是友元,所以可以调用)***,导入操作(调用导入事务类的导入操作),更新操作(调用更新事务类的跟新操作)和导出操作(调用导出事务类的导出操作)。 10 | 11 | -------------------------------------------------------------------------------- /胜算中的响应逻辑.md: -------------------------------------------------------------------------------- 1 | # 胜算中的响应逻辑 2 | 3 | * 响应的大体思路 4 | 5 | 点击响应后,首先初始化响应招标材料的界面,根据配置文件中的模块中的值进行调整。 6 | 7 | ```pascal 8 | // 1. 会根据CLXZ字段的值添加列 9 | ``` 10 | 11 | * 点击响应按钮 12 | 13 | 1. 根据筛选条件对映射表进行过滤,并检查价格上下限和数量上下限判断是否符合响应条件 14 | 2. 开始响应 15 | 3. 获得该映射表中材料在汇总表中的暂估材料,大才标记,主要材料,材料类I型那个,甲供材料,zbclzt, boolexta的旧值。 16 | 4. 然后根据招标材料表中的节点属性获得上面那些字段的新值。 17 | 5. 通过旧值和新值的比较选择响应方式。 18 | 6. 一般判断材料是否为同一种材料的方式:编码,名称,单位,规格型号相同。 19 | 20 | * 进入系统编码计算的字段 21 | 22 | ```pascal 23 | // LX, BH, MC, DW, GG, Dej, Scj(除税市场价), Ysj, GYj(供应价), Glhz_Zy, Glhz_Zg, Glhz_Jg, Glhz_ScBj(大才标记), GLhz_Sbtlb(材料类别)这个字段有时候拼错了,叫做Glhz_sptlb. 24 | // 在响应单元中的特殊字段意思 25 | FPrjmr: FileContext.GetPrjDataManager; 26 | FResourceTree: 工料汇总界面的TreeList; 27 | FZbTempRec: 当前要响应的招标材料表中的记录。 28 | Resource: 工料汇总服务(我也不知道为啥叫这个名字,蛮蠢的)。 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /胜算中的常用方法.md: -------------------------------------------------------------------------------- 1 | ## DataSet 的使用方法 2 | 3 | ```pascal 4 | var 5 | Rec: Pointer; 6 | mkpz: IPMDataSet; 7 | TableName: string; 8 | begin 9 | mkpz := DataContext.GetDataSet(cst_Table_ModuleZB); // cst_Table_ModuleZB = ywmkzb, 这个是DataSet名称 10 | if mkpz = nil then 11 | Rec := mkpz.FindRec(cst_mkpz_bl, 'xxmx'); // cst_mkpz_bl是字段名称 xxmx是这个字段的字段值 12 | if Rec = nil then exit; 13 | TableName := mkpz.GetFieldValueAsString(cst_mkpz_bm, '', Rec); // 找mkpz这个DataSet这个数据集中Rec这条记录中bm的值, 14 | if TableName <> '' then 15 | Result := DataContext.GetDataSet(TableName); 16 | end; 17 | 18 | var 19 | GlhzRec: Pointer; 20 | begin 21 | GlhzRec := GlhzDataSet.GetRecByFieldValue(cst_Glhz_XTBM, sXtbm); 22 | end; 23 | 24 | // 判断Dataset中是否存在某个字段 25 | IPMDataSet.CheckFieldExit(AField: string); 26 | ``` 27 | 28 | ## DataView的使用方法 29 | 30 | ```pascal 31 | var 32 | XmxxNode: IPMNode; 33 | sValue: string; 34 | begin 35 | XmxxNode := DataView.FindPmNode(cst_Xmxx_DM, 'P_bh'); // cst_Xmxx_DM 是字段名称, P_bh是该字段的值 36 | sValue := XmxxNode.GetValueAsString(cst_Xmxx_Nr, ''); // 查找XmxxNode这个节点中cst_Xmxx_Nr的值。 37 | 38 | end; 39 | 40 | /// 获得FSsDataView中NodeList 41 | var 42 | NodeList: TInterfaceList; 43 | begin 44 | FSsDataView.FindRecList(cst_Fbfx_lb, IntToStr(Ord(ntxmQD)), NodeList); 45 | end; 46 | ``` 47 | 48 | 49 | 50 | ## DataBusi的使用方法 51 | 52 | ```pascal 53 | var 54 | DataBusi: TZB_XML_DataBusi; 55 | begin 56 | InitDataBusi(DataBusi); 57 | DataBusi.Init(FDzpbService, FTransConf, FTransLog); 58 | DataBusi.Export(SsDataSet.CreateView, CurrXmlRec); 59 | end; 60 | ``` 61 | 62 | ## uFileUtils 63 | 64 | ```pascal 65 | // 返回一个表名的DataSet; 66 | // 这个是先进入DataContext的ywmkzb这个DataSet中查找bl字段值为variable值的记录,找到后找到对应的bm字段的值,bm字段就是返回的表名。 67 | function FindDataSetByVariable(DataContext: IDataContext; Variable: string): IPMDataset; 68 | ``` 69 | 70 | ```pascal 71 | var 72 | GlhzDataSet: IPMDataSet; 73 | begin 74 | GlhzDataSet.GetRecByFieldValue(); 75 | end; 76 | ``` 77 | 78 | ## uDzpbIntf 79 | 80 | ```pascal 81 | IDzp_Xml_Project = interface 82 | function GetRootRecord: IDzpb_Xml_Record; // 获得XML文件的根节点 83 | function AddRootRecord(const RecName: string): IDzpb_Xml_Record // 像XMl文件中添加根节点 84 | end; 85 | 86 | IPMDataView = interface 87 | // 往后插入记录 PmNode=nil的情况下自动最后添加 若数据集有层次 插入数据是必须提供level值 88 | function InsertAfter(PmNode: IPMNode; Level: Integer = 0): IPMNode; 89 | end; 90 | ``` 91 | 92 | ```pascal 93 | // 通过一个FileContext去寻找另一个DataContext 94 | // FileContext指的是工程 95 | var 96 | PrjFile: IPrjDataManager; 97 | ADataContext: IDataContext; 98 | ADataSet: IPMDataSet; 99 | ARec: Pointer; 100 | begin 101 | PrjFile := FileContext.GetPrjDataManager; 102 | ADataContext := PrjFile.GetDataContext(cst_DataContext_XmData); 103 | ADataContext := ADataContext.GetDataSet(cst_Table_XmJg); 104 | end; 105 | ``` 106 | 107 | ```pascal 108 | //通过DataContext去寻找该节点的GUID 109 | var 110 | GUID: string; 111 | aDC: IDataContext; 112 | begin 113 | GUID := aDc.DataToken; //返回该DataContext的GUID。 114 | end; 115 | ``` 116 | 117 | ```pascal 118 | var 119 | sValue: string; 120 | sValue := GetDzpbPrjCode(FFileContext.GetPrjDataManager, cst_SsTable_Xmxx, sDm); 121 | 122 | ``` 123 | 124 | ## 获得工程类型 125 | 126 | ```pascal 127 | function GetProjectStatus(APrjDataManager: IPrjDataManager): TProjectStatus; 128 | ``` 129 | 130 | ## 获得配置文件中模块中的内容 131 | 132 | ```pascal 133 | FFileContext.GetFileDataContext.FileOption.PrjControl.GetCommandOptByName(cst_CommandOpt_CLXZ); 134 | ``` 135 | 136 | -------------------------------------------------------------------------------- /胜算中的常用类.md: -------------------------------------------------------------------------------- 1 | ```pascal 2 | //**************************************************************************************************** uPMSSDataConst.pas ***************************************************************************************************// 3 | type 4 | // 模块类型--与模块类型常量对应 5 | TModuleType = (mtNone, mtGcxx, mtFlsz, mtGlhz, mtFbfx, mtCsxm, 6 | mtQtxm, mtGczj, mtZcSb); 7 | // 工程状态 zb(招标),tb(投标),gs(概算),ys(预算) 8 | TProjectStatus = (psZB, psTB, psGS, psYS); 9 | ``` 10 | ```pascal 11 | //************************************************************************************************ uDataManagerIntf.pas *************************************************************************************************// 12 | IDataContext = interface 13 | //****************************************表操作************************************************* 14 | // 创建dataSet 15 | function CreateDataSet(Const sDataSetName: string): IPMDataSet; 16 | // 获取DataSet 17 | function GetDataSet(const sDataSetName: string): IPmDataSet; 18 | //****************************************属性操作************************************************ 19 | property DataToken: string read GetDataContextToken; 20 | end; 21 | IPrjDataManager = interface 22 | // 创建DataContext 23 | function CreateDataContext(Const sDataName: string): IDataContext; 24 | // 获取DataContext 25 | function GetDataContext(Const sDataName: string): IDataContext; 26 | // 获取DataContext 27 | function GetDataContext(Const GUID): IDataContext; 28 | end; 29 | ``` 30 | 31 | ```pascal 32 | //************************************************************************************************ uYs.Core.API.pas *************************************************************************************************// 33 | 34 | //*************************文件数据上下文(纯数据)(纯工程文件数据访问接口)************************** 35 | IYSFileDataContext = interface 36 | // 获取工程文件数据 37 | function GetPrjDataManager: IPrjDataManager; 38 | // 获取工程资源 39 | function GetResourceDataContext: IDataContext; 40 | // 获取整体节点DataContext 41 | function GetZtDataContext: IDataContext; 42 | // 获取当前工程节点DataContext 43 | function GetDataContext: IDataContext; 44 | // 工程属性 45 | function FileOption: IYsFileOption; 46 | // 工程服务 47 | function FileService: IYsServiceManager; 48 | // 系统属性 49 | function SysOption: IYsSysOption; 50 | // 系统服务 51 | function SysService: IUnknown; 52 | // 应用程序 53 | function WorkApp: IUnknown; 54 | // 获取项目结构表 55 | function ProjectStructureDataSet: IPMDataset; 56 | //属性 57 | property DataContext: IDataContext read GetDataContext write SetDataContext; 58 | property PrjDataManger: IPrjDataManager read GetPrjDataManager; 59 | property ResourceDataContext: IDataContext read GetResourceDataContext; 60 | end; 61 | 62 | //**************************************工程属性************************************************** 63 | IYsFileOption = interface(IYsService) 64 | // 计算精度 65 | function CalcPrecision: IYsFileOption_CalcPrecision; 66 | // 计算选项 67 | function CalcOption: IYsFileOption_CalcOption; 68 | // 费用类型 69 | function CostTypes: IYsFile_CostType; 70 | // 审计审核风格 71 | function SjshStyle: IYsFileOption_SjshStyle; 72 | // 项目属性 73 | function Xmsx: IYsFileOption_Xmsx; 74 | // 专业信息 75 | function ZyInfo: IYsFileOption_ZyInfo; 76 | // 工程临时属性,当前属性 77 | function CurrOption: IYsFileOption_CurrOption; 78 | // 电子评标控制 79 | function PrjControl: IYsFileOption_PrjControl; 80 | // 刷新选项 81 | function RefReshOption; 82 | end; 83 | 84 | //***************************************服务管理************************************************* 85 | IYsServiceManager = interface(IYsService) 86 | // 注册服务 87 | procedure RegisterService(const AServiceID: string; AService: IYsService); overload; 88 | procedure RegisterService(const AServiceID: TGUID; AService: IYsService); overload; 89 | // 注销服务 90 | procedure UnRegisterService(const AServiceID: string); overload; 91 | procedure UnRegisterService(const AServiceID: TGUID); overload; 92 | // 注销所有服务 93 | procedure UnRegisterAllService; 94 | // 获取服务 95 | function GetService(const AServiceID: string): IYsService; overload; 96 | function GetService(const AServiceID: TGUID): IYsService; overload; 97 | end; 98 | 99 | //************************************分布分项定额含量服务***************************************** 100 | IYsFbfxDehlService = interface 101 | /// 获取给定定额列表的人材机(取分解 仅可控制是否取分解材料的父节点) 102 | /// 结果 103 | /// 定额列表 104 | /// 清单工程量, 有值消耗量才正确 105 | /// 是否成功 106 | function GetDehlByDeList(ADataContext: IDataContext; DeNodeList: 107 | TInterfaceList; bTotal: Boolean; bGetJxtbFj: Boolean; bGetPhbFj: Boolean; 108 | QdGcl: Double = -1): IPMDataView; overload; 109 | /// 110 | /// 通过定额列表,获取人材机明细 111 | /// 112 | function GetDehlByDeList(DeNodeList: TInterfaceList; AModuleType: TModuleType; 113 | bGetFj: Boolean = False; bTotal: Boolean = False; bCreateGroup: Boolean = 114 | True; ADataContext: IDataContext = nil; QdGcl: Double = -1): IPMDataView; 115 | overload; 116 | end; 117 | ``` 118 | 119 | ```pascal 120 | //************************************************************************************************ uPMSSFrameIntf.pas ***********************************************************************************************// 121 | 122 | //***************************************系统级应用*********************************************** 123 | ISSWorkApplication = interface 124 | // 系统服务 125 | function GetSysService: ISSService; 126 | // 获取系统设置 127 | function GetSysOption: IYsSysOption; 128 | // 设置系统设置 129 | function SetSysOption(Value: IYsSysOption); 130 | // 设置系统服务 131 | function SetSysService(Value: ISSService); 132 | //****************************************属性************************************************* 133 | // 系统设置 134 | property SysOption: IYsSysOption read GetSysOption write SetSysOption; 135 | // 系统服务 136 | property SysService: ISSService read GetSysService write SetSysService; 137 | end; 138 | //***************************************窗口管理************************************************* 139 | // 工作平台 140 | ISSFileContext = interface 141 | // 获取系统参数设置 142 | function SysOption: IYsSysOption; 143 | // 获取数据文件 144 | function GetPrjDataManager: IPrjDataManager; 145 | // 数据访问操作接口(纯数据) 146 | function GetFileDataContext: IYsFileDataContext; 147 | end; 148 | // 工程窗体 149 | ISSWorkWindow = interface(IWorkWindow) 150 | 151 | end; 152 | end; 153 | ``` 154 | 155 | ```pascal 156 | //************************************************************************************************ uFileServiceIntf ***********************************************************************************************// 157 | 158 | //************************************费率设置服务************************************************ 159 | ISSFLszService = interface 160 | // 获取费率特向数据视图 161 | function GetFltxData(ADataContext: IDataContext): IPMDataView; 162 | // 获取费率特项数据集 163 | function GetFltxDataSet(ADataContext: IDataContext): IPMDataset; 164 | // 获取费率项目(通过当前DataContext) 165 | function GetFlxmData(ADataContext: IDataContext): IPMDataSet; 166 | // 获取费率项目(通过当前DataContext) 167 | function GetFlxmDataSet(ADataContext: IDataContext): IPMDataset; 168 | // 通过特项和费用类型获取关联的费率值 169 | // 当前工程节点数据 170 | // 特项Id 171 | // 费用类型 172 | // 是否有找到指定的费率项 173 | // 默认保留小数位数(根据设置 可能不会用此值) 174 | // 费率 175 | function GetFLValue(DataContext: IDataContext; const TxID: Integer; const Fylx: string; out IsFind: Boolean): Double; overload; 176 | // 获取单价构成 177 | // 当前工程节点数据 178 | // 特项Id 179 | // 单价构成 180 | function GetTxqfData(ADataContext: IDataContext; const Txid: Integer): IPMDataView; override; 181 | end; 182 | 183 | ``` 184 | 185 | ```pascal 186 | //************************************************************************************************ uYs.Core.FbfxDehlServiceImpl.pas ***********************************************************************************************// 187 | //**********************************获取定额含量时 的给定条件************************************** 188 | TDehlConditions = record 189 | // 是否获取配合比材料的分解 190 | bGetPhbFj: Boolean; 191 | // 是否获取机械台班材料的分解 192 | bGetJxtbFj: Boolean; 193 | // 是否创建分组 194 | bCreateGroup: Boolean; 195 | // 是否合并定额含量 196 | bTotal: Boolean; 197 | // 是否统计分解材料(True时会将 系统编码 相同的分解材料与非分解材料合并,材料水和分解材料水) 198 | bTotalFjCl: Boolean; 199 | // 当分解时, 取父节点记录(False 时会将父材料去掉) 200 | end; 201 | 202 | //*************************************临时人材机数据生成器**************************************** 203 | TTempRcjDataFactory = class 204 | private 205 | // 生成人材机的条件 206 | FConditions: TDehlConditions; 207 | // 生成人材机的条件(取分解) 208 | FConditions_Fj: TDehlConditions_Fj; 209 | // 工料汇总 210 | FdsGlhz: IPMDataset; 211 | // 定额含量 212 | FdsDehl: IPMDataset; 213 | // 数据上下文 214 | FdcLocal: IDataContext; 215 | // 定额含量服务 实现 216 | FDehlBusi: TYsFbfxDehlServiceImpl; 217 | /// 处理定额含量节点 218 | /// 定额节点 219 | /// 定额含量节点 220 | /// 临时定额含量Dset(结果) 221 | /// 222 | function DealDehlNode(ADeNode, ADehlNode: IPMNode; ALsDehlDataSet: 223 | IPMDataset): Boolean; 224 | public 225 | constructor Create(ADehlService: TYsFbfxDehlServiceImpl); 226 | /// 获取给定定额列表的人材机 227 | /// 结果 228 | /// 定额列表 229 | /// 清单工程量 230 | /// 是否成功 231 | function GetRcjByDeList(AdsLsDehlResult: IPMDataset; ADeList: TInterfaceList; 232 | AQdGcl: Double): Boolean; 233 | /// 获取给定定额列表的人材机(取分解 仅可控制是否取分解材料的父节点) 234 | /// 结果 235 | /// 定额列表 236 | /// 清单工程量 237 | /// 是否成功 238 | function GetRcjByDeList_TotalFjCl(AdsLsDehlResult: IPMDataset; ADeList: 239 | TInterfaceList; QdGcl: Double): Boolean; 240 | /// 生成人材机的条件 241 | property Conditions: TDehlConditions read FConditions write FConditions; 242 | /// 生成人材机的条件(取分解) 243 | property Conditions_Fj: TDehlConditions_Fj read FConditions_Fj write 244 | FConditions_Fj; 245 | /// 工料汇总 246 | property dsGlhz: IPMDataset read FdsGlhz write FdsGlhz; 247 | /// 定额含量 248 | property dsDehl: IPMDataset read FdsDehl write FdsDehl; 249 | /// 数据上下文 250 | property dcLocal: IDataContext read FdcLocal write FdcLocal; 251 | end; 252 | ``` 253 | 254 | -------------------------------------------------------------------------------- /胜算中费率关系.md: -------------------------------------------------------------------------------- 1 | # 胜算中的费率关系 2 | 3 | * 胜算中费率相关的表就是fltx,flsz 和 fltx这三张表 4 | 5 | ```pascal 6 | //************************************************************************************************ uFileServiceIntf ***********************************************************************************************// 7 | 8 | //************************************费率设置服务************************************************ 9 | ISSFLszService = interface 10 | // 获取费率特向数据视图 11 | function GetFltxData(ADataContext: IDataContext): IPMDataView; 12 | // 获取费率特项数据集 13 | function GetFltxDataSet(ADataContext: IDataContext): IPMDataset; 14 | // 获取费率项目(通过当前DataContext) 15 | function GetFlxmData(ADataContext: IDataContext): IPMDataSet; 16 | // 获取费率项目(通过当前DataContext) 17 | function GetFlxmDataSet(ADataContext: IDataContext): IPMDataset; 18 | // 通过特项和费用类型获取关联的费率值 19 | // 当前工程节点数据 20 | // 特项Id 21 | // 费用类型 22 | // 是否有找到指定的费率项 23 | // 默认保留小数位数(根据设置 可能不会用此值) 24 | // 费率 25 | function GetFLValue(DataContext: IDataContext; const TxID: Integer; const Fylx: string; out IsFind: Boolean): Double; overload; 26 | // 获取单价构成 27 | // 当前工程节点数据 28 | // 特项Id 29 | // 单价构成 30 | function GetTxqfData(ADataContext: IDataContext; const Txid: Integer): IPMDataView; override; 31 | end; 32 | 33 | ``` 34 | 35 | * 费率库表结构 36 | 37 | 1. 费率库目录:FLMLDset 38 | 39 | | | | | | | | 40 | | ---- | ---- | ---- | ---- | ---- | ---- | 41 | | | | | | | | 42 | 43 | 44 | 45 | * 费率库服务 46 | 47 | ```pascal 48 | //****************************************费率库表格操作接口******************************************* 49 | /// 50 | /// 费率库表格操作接口 51 | /// 52 | ISSFlkService = interface 53 | [cst_FileService_Flsz_Flk] 54 | /// 55 | /// 获取费率项目主表数据 56 | /// 57 | /// 模板Id 58 | /// 专业Id 59 | /// 费率项目主表数据 60 | function GetFlMaster(const Templateid, Zyid: Integer; bVatMode: Boolean; 61 | bFilterVat: Boolean = True): IPMDataView; overload; 62 | /// 63 | /// 获取费率项目主表数据 64 | /// 65 | /// 模板Id 66 | /// 专业Id 67 | /// 工程类型 68 | /// 费率项目主表数据 69 | function GetFlMaster(const Templateid, ZyId, Gclx: Integer; bVatMode: Boolean): IPMDataView; overload; 70 | /// 71 | /// 获取费率项目主表数据(费率类型和费率目录过滤出来的表格) 72 | /// 73 | /// 模板Id 74 | /// 专业Id 75 | /// 工程类型 76 | /// 费率目录节点GUID 77 | /// 是否获取未指定工程类型的数据 78 | /// 费率项目主表数据 79 | function GetFlMasterByCondition(const Templateid, Zyid, Gclx: Integer; bVatMode: Boolean 80 | ; sMlGuid: string; IsGetEmptyGclxData: Boolean = False): IPMDataView; 81 | /// 82 | /// 获取费率项目主表数据(费率类型和费率目录过滤出来的列表) 83 | /// 84 | /// 模板Id 85 | /// 专业Id 86 | /// 工程类型 87 | /// 费率目录节点GUID 88 | /// 是否获取未指定工程类型的数据 89 | /// 结果记录列表 90 | /// 费率主表 91 | procedure GetFlMasterRecListByCondition(const Templateid, Zyid, Gclx: Integer; bVatMode: Boolean; sMlGuid: string; 92 | IsGetEmptyGclxData: Boolean;out AResultList: TList; out AFlMasterDataSet: IPMDataset); 93 | /// 94 | /// 获取费率工程类型表 95 | /// 96 | /// 模板Id 97 | /// 专业Id 98 | /// 工程类型表 99 | function GetGclx(const Templateid, Zyid: Integer): IPMDataView; 100 | /// 101 | /// 获取费率目录表 102 | /// 103 | /// 模板Id 104 | /// 专业Id 105 | /// 费率目录表 106 | function GetFlml(const Templateid, Zyid: Integer): IPMDataView; 107 | /// 108 | /// 清空费率库数据 109 | /// 110 | procedure Clear; 111 | end; 112 | 113 | ``` 114 | 115 | -------------------------------------------------------------------------------- /胜算中配置文件说明.md: -------------------------------------------------------------------------------- 1 | # 胜算中配置文件的说明 2 | 3 | 4 | 5 | ## 与按钮相关的配置文件 6 | 7 | 1. config\MenuConfig.xml文件 8 | 2. Dzpb\CommandControl.xml文件 9 | 3. 该模版的配置文件 10 | 11 | ## 在以不同方式导出时配置文件的选择问题 12 | 13 | ```pascal 14 | // 在导出招投标,省数据交换标准的时候配置文件会不同,那么这个时候如何选择正确的配置文件呢? 15 | // 他是在导出时不同的按钮传入的参数判断导出类型,然后根据参数类型进入不同类,该类中定义了配置文件的获取方式。 16 | ``` 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /胜算工程和模版工具的关联.md: -------------------------------------------------------------------------------- 1 | # 胜算工程和模版工具的关联 2 | 3 | ```pascal 4 | // 1. 胜算中Xmsx表中的JjmbGuid对应模版工具中该模版的特殊标识 5 | WorkSpace.FileContext.GetFileDataContext.FileOption.Xmsx.GetValue(cst_XMSX_Key_JjmbGuid); 6 | // 2. 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /胜算开发注意事件.md: -------------------------------------------------------------------------------- 1 | 1. 材料暂估是暂估材料,发包人中的材料是甲供材料,承包人中的材料是主要材料。 2 | 3 | 2. 在响应单价后主要材料不打对号,要进入配置文件''中添加’‘这句话。 4 | 5 | 3. 在响应后需评审材料不打对号,要进配置文件''中添加’‘ 6 | 7 | 4. 主要材料是不导单价的,响应单价字段设置为false。响应单价字段为true是将金额正向导,false是反向道。 8 | 9 | 5. 导材料的时候基本不用clzt字段 10 | 11 | 6. 费用汇总表是专业工程汇总表 12 | 13 | 7. 清空导是和配置文件一起使用的 14 | 15 | 8. 在其他项目表中没有单价,但是有计算式,要导出单价的话要通过计算式进行计算。 16 | 17 | ```pascal 18 | var 19 | dPrice: Double; 20 | sExp: string; // 计算式 21 | YsDs: IPMDataSet; 22 | begin 23 | sExp := SsNode.GetValueAsString(cst_qtxm_jss, EmptyStr); 24 | YsDs := FDataContext.GetDataSet(cst_Table_GlhzYs); 25 | dPrice := TQFBCalculator.CalQfbExpr(sExp, FDzpbContext.FileDataContext, FDataContext, YsDs); 26 | end; 27 | 28 | // 在这种表中,计算式 * Fl(费率) = 合价(Zhhj) 29 | ``` 30 | 31 | 9. 胜算中导出**清单的项目特征**和**工作内容**的方式 32 | 33 | ```pascal 34 | // 将项目特征或者工作内容读入stringList,然后拼接字符串把内容给拼出来 35 | var 36 | List: TStringList; 37 | begin 38 | List := TStringlist.create; 39 | try 40 | List.CommaText := SsNode.GetValueAsString(cst_Fbfx_Gznr, EmptyStr); 41 | for i := 0 to List.Count - 1 do 42 | Result := Result + List.Strings[i] 43 | finally 44 | List.Free; 45 | end; 46 | end; 47 | ``` 48 | 49 | 10. 当导出清单库和定额库名称时,一种处理办法是将该库的名称配置到配置文件中 50 | 51 | ```pascal 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | 11. 胜算中清单和定额都是有特项的。 69 | 70 | 12. 在Dll中获取定额库的方法 71 | 72 | ```pascal 73 | // 1. 首先后的指引库 74 | // 2. 定额库服务重新加载指引库 75 | // 3. 获得定额库记录 76 | var 77 | sZyk, sQdk, sDek: string; 78 | FDataService: IYsDbDataProviderService; 79 | begin 80 | FDataService := FDzpbContext.FileDataContext.FileService.GetService( 81 | cst_DbService_Api_DataProvider) as IYsDbDataProviderService; 82 | if Assigned(FDataService) then 83 | begin 84 | sZyk := FDpzbContext.FileDataContext.FileOption.Xmsx.GetValue(cst_Xmsx_Key_ZykFile); 85 | FDataService.ZykService.LoadFromFile(sZyk); 86 | sQdk := ExtractFileName(FDataService.QdkService.GetFileName); 87 | sDek := ExtractFileName(FDataService.DekService.GetFileName); 88 | end; 89 | end; 90 | ``` 91 | 92 | 13. 措施项目中要注意变量和计算式和费用类型 93 | 94 | * 措施项目中每一项都和费率通过费用类型(fylx)字段相关联。 95 | * 措施项目中每一项有计算式,每一项措施项目是通过计算式计算出来的。 96 | * 措施项目中的计算式是由分部分项中的变量组成的。 97 | * 其中安全文明施工费比较特殊,例如河南,它的园林和仿古专业安全文明施工费是由子节点的,子节点有计算式而父节点没有计算式,父节点的安全文明施工费(综合合价)是通过子节点相加得到的。但是像建筑专业安全文明施工费就没有子节点,安全文明施工费就有计算式,所以安全文明施工费(综合合价)直接由计算式*fl得出。 98 | 99 | 14. 工程造价中每一项也是由计算式计算得到的。 100 | 101 | 15. 当在导出专业节点中组织措施的数据,该专业需要特殊处理时,比如园林专业的规费费率需要特殊处理,这个时候需要判断该专业是否为园林专业,专业是由项目结构表(Xmjg)中专业类别字段来区分的。 102 | 103 | 16. 在导出***计日工***的时候,一般会判断以下税金能否导出,判断方法如下 104 | 105 | ```pascal 106 | function CanExp(SsNode: IPMNode): Boolean; 107 | var 108 | pSJRec: Pointer; 109 | begin 110 | pSJRec := FDzpbContext.DzpbConfig.Gfsj.GetGfSjRecByQtVar(SsNode.GetValueAsString(cst_qtxm_bl, EmptyStr)); 111 | if Assigned(pSjRec) then 112 | Result := False; 113 | end; 114 | ``` 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /胜算待优化代码.md: -------------------------------------------------------------------------------- 1 | 1. 软件取费表计算,变量提取或者说变量管理那块,有没有好的方法。 2 | 3 | -------------------------------------------------------------------------------- /胜算新建工程.md: -------------------------------------------------------------------------------- 1 | # 新建 2 | 3 | * 从 TfrmNewProjectDialog 这个类说起 4 | 5 | ```pascal 6 | TfrmNewProjectDialog = class 7 | private 8 | // 应用程序接口 9 | FWorkApp: ISSWorkApplication; 10 | // 系统选项 11 | FSysOption: IYsSysOption; 12 | // 新建工程管理 13 | FNewPrjMgr: TNewProjectManager; 14 | // 文件名 15 | FsFileName: string; 16 | end; 17 | // 工程管理对象 18 | FNewPrjMgr := TNewProjectManager.Create(AWorkApp); 19 | 20 | /// 21 | /// 新建工程 管理 22 | /// 23 | TNewProjectManager = class{old class = TNewProjectDataManager} 24 | private 25 | // 应用程序接口 26 | FWorkApp: ISSWorkApplication; 27 | // 系统服务 28 | FSysService: ISSService; 29 | // 界面管理 30 | FUIMgr: TNewProjectUIManager; 31 | // 模块管理对性 32 | FModuleManager: TModuleManager; 33 | // 主程序路径 34 | FsAppPath: string; 35 | // 配置路径 36 | FsConfigPath: string; 37 | protected 38 | // 工程数据接口 39 | FPrjMgr: IPrjDataManager; 40 | // 系统配置 41 | FSysOption: IYsSysOption; 42 | // 新建数据管理 43 | FDataMgr: TNewProjectDataManagerEx; 44 | public 45 | constructor Create(AWorkApp: ISSWorkApplication); 46 | /// 47 | /// 界面管理 48 | /// 49 | property UIMgr: TNewProjectUIManager read FUIMgr; 50 | end; 51 | // 构造函数 52 | constructor TNewProjectManager.Create(AWorkApp: ISSWorkApplication); 53 | begin 54 | FPrjMgr := nil; 55 | FModuleManager := nil; 56 | FWorkApp := AWorkApp; 57 | if FWorkApp <> nil then 58 | begin 59 | FSysOption := FWorkApp.SysOption; 60 | FSysService := FWorkApp.SysService; 61 | FsAppPath := FSysOption.Path.GetApplicationPath(); 62 | FsConfigPath := FSysOption.Path.GetSysConfigPath(); 63 | end; 64 | FDataMgr := TNewProjectDataManagerEx.Create(FWorkApp); 65 | FUIMgr := TNewProjectUIManager.Create(FWorkApp, FDataMgr); 66 | FDataMgr.PrjInfo^.eNewPrjMode := npmDefault; 67 | end; 68 | ``` 69 | 70 | * 新建工程相关的结构 71 | 72 | ```pascal 73 | unit uNewProjectType 74 | type 75 | // 模块类型--与模块类型常量对应 76 | TModuleType = (mtNone, mtGcxx, mtFlsz, mtGlhz, mtFbfx, mtCsxm, 77 | mtQtxm, mtGczj, mtZcSb); 78 | // 工程状态 zb(招标),tb(投标),gs(概算),ys(预算) 79 | TProjectStatus = (psZB, psTB, psGS, psYS); 80 | // 计价模式 :清单计价,定额计价 81 | TProjectMode = (pmQDJJ = 0, pmDEJJ); 82 | /// 83 | /// 新建工程 模式 npmDefault=默认新建 npmUserModel=按用户模板新建 npmChangRule=转换模板 84 | /// npmUserModel_ChangRule=按用户模板转换模板 npmImport=导入 85 | /// npmGeneralToSimple=一般计税转简易计税 npmSimpleToGeneral=简易计税转一般计税 86 | /// npmImportKzj=招标控制机文件 87 | /// 88 | TNewPrj_Mode = (npmDefault, npmUserModel, npmChangRule, npmUserModel_ChangRule, 89 | npmImport, npmGeneralToSimple, npmSimpleToGeneral, npmImportKzj); 90 | /// 91 | /// 新建工程 步骤 npsFirst=第一步 npsSecond=第二步 npsThird=第三步 npsFinal=最后一步 92 | /// npsFirst_Module=模板新建第一步 npsFinal_Module=模板新建最后一步 93 | /// 94 | TNewPrj_Step = (npsFirst, npsSecond, npsThird, npsFinal, 95 | npsFirst_Module, npsFinal_Module); 96 | /// 97 | /// 新建工程 添加节点类型 antDw=添加单位节点 antZy=添加专业节点 antDw_Sibling=添加同层的单位节点 98 | /// 99 | TNewPrj_AddNodeType = (antDw, antZy, antDw_Sibling); 100 | /// 101 | /// 新建工程 Key(不同类型的行标识) 102 | /// npkXmBh=项目编号 npkXmMc=项目名称 npkDqBz=地区标准 npkPath=路径 103 | /// npkDeGf=定额规范(计价规则) 104 | /// npkJjgz=计价规则 npkJjmb=计价模板 npkJkBz=接口标准 npkZbFile=招标文件 105 | /// npkZtgc=整体工程 npkDwgc=单位工程 npkZygc=专业工程 106 | /// 107 | TNewPrj_Key = ({npsFirst}npkXmBh, npkXmMc, npkDqBz, npkPath, 108 | {npsFinal}npkDeGf, 109 | {npsSecond}npkJjgz, npkJjmb, npkJkBz, npkZbFile, 110 | {npsThird}npkZtgc, npkDwgc, npkZygc 111 | ); 112 | /// 113 | /// 新建工程里部分 函数结果 nprOk=成功 npkCancel=取消 nprError=错误 nprQuestion=询问 114 | /// 115 | TNewPrj_Result = (nprOk, nprCancel, nprError, nprQuestion); 116 | /// 117 | /// 新建工程 增值税(VAT)类型 npvYys=营业税 npvZzs=增值税(一般计税) npvZzsSimple=增值税(简易计税) 118 | /// 119 | TNewPrj_VAT = (npvYys, npvZzs, npvZzsSimple); 120 | /// 121 | /// 计价模板 信息 122 | /// 123 | TJjmb_Info = record 124 | sName: string; // 名称 [jjmb] 125 | sGuid: string; // guid [JjmbGuid] 126 | sDeGf: string; // 定额规范 (如:浙江省建设工程2010) [degf] 127 | JkBzList: string; // dzpb接口列表 [JkbzList] 128 | JkBzGuid: string; // 本工程dzpb的guid [JkbzName] 129 | eJjLx: TProjectMode; // 计价类型(清单计价;定额计价) [Jjlx] 130 | sCheckFile: TFileName; // 检查文件(dzpb) [zjfile] 131 | sTemplateFile: TFileName; // 模板文件 [UiFile] 132 | sReportFile: TFileName; // 报表文件 [ReportFile] 133 | 134 | sZy: string; // 专业 [zy] 135 | sZykFile: TFileName; // 指引库文件 [ZykFile] 136 | sZbFile: TFileName; // 招标文件 [ZbFile] 137 | bIsVAT: Boolean; // 是否增值税 [IsVAT] 138 | eVAT: TNewPrj_VAT; // 增值税类型 139 | ePrjStat: TProjectStatus; // 工程状态 140 | 141 | sDqBz: string; // 地区标准 [dqbz] 142 | sDqDh: string; // 地区代号 [DqDh] 143 | 144 | sGclx: string; // 工程类型(结构向导选择的名称) 145 | sMemo: string; // 模板说明 [memo] 146 | sGcsf: string; // 工程算法 147 | 148 | // add 149 | bCanGetZbFileXmjg: Boolean; // 能够获取 招标文文件 项目结构 150 | bIsMutiXm: Boolean; // 是否是支持多层项目管理工程 151 | public 152 | procedure Init(); 153 | end; 154 | 155 | /// 156 | /// 计价模板 信息 157 | /// 158 | PJjmb_Info = ^TJjmb_Info; 159 | /// 160 | /// 计价规则 信息 161 | /// 162 | TJjgz_Info = record 163 | sFile: TFileName; // 文件名 [jjgzfile] 164 | sName: string; // 名称 [jjgz] 165 | sGuid: string; // guid [jjgzsign] 166 | Jjmb: PJjmb_Info; // 计价模板信息 167 | public 168 | procedure Init(); 169 | end; 170 | /// 171 | /// 计价规则 信息 172 | /// 173 | PJjgz_Info = ^TJjgz_Info; 174 | /// 175 | /// 新建工程 信息 176 | /// 177 | TNewPrj_Info = record 178 | eNewPrjMode: TNewPrj_Mode; // 新建模式 179 | // ePrjMode: TProjectMode; // 工程模式 180 | // ePrjStat: TProjectStatus; // 工程状态 181 | 182 | sXmBh: string; // 项目编号 [xmbh] 183 | sXmMc: string; // 项目名称 [xmmc] 184 | sXmPath: string; // 项目路径(结尾字符是'\') 185 | sOldXmMc: string; // 旧项目名称 [OldXmmc] 186 | sZbDw: string; // 招标单位 [zbdw] 187 | sBzDw: string; // 编制单位 [bzdw] 188 | 189 | sJsDw: string; // 建设单位(金华三为?) [jsdw] 190 | sSgBz: string; // 施工班组(金华三为) [Sgbz] 191 | 192 | Jjgz: PJjgz_Info; // 计价规则信息 193 | 194 | OldPrjMgr: IPrjDataManager; // 老工程(模板转换原始工程 模板转换用 npmChangRule npmUserModel_ChangRule) 195 | sImportFile: TFileName; // 导入文件(导入用 npmImport, 导入控制价用npmImportKzj) 196 | public 197 | procedure Init(); 198 | procedure UnInit(); 199 | end; 200 | /// 201 | /// 新建工程 信息 202 | /// 203 | PNewPrj_Info = ^TNewPrj_Info; 204 | 205 | /// 206 | /// 新建工程 模板信息 207 | /// 208 | TNewPrj_UserModelInfo = record 209 | sName: string; // 文件名 210 | // sXmBh: string; // 项目编号 211 | sXmMc: string; // 项目名称 212 | // sJsDw: string; // 建设单位 213 | PrjMgr: IPrjDataManager; // 模板工程 214 | JjyjInfo: IYsFileOption_Xmsx; // 计价依据 215 | public 216 | procedure Init(); 217 | end; 218 | /// 219 | /// 新建工程 模板信息列表 220 | /// 221 | TNewPrj_ModelList = TList; 222 | /// 223 | /// 新建工程 数据集列表 224 | /// 225 | TNewPrj_DataSetList = TList; 226 | 227 | /// 228 | /// 处理步骤界面 事件 229 | /// 230 | TOnDealStepUI_Event = procedure (AeStep: TNewPrj_Step; AeMode: TNewPrj_Mode) of object; 231 | 232 | /// 233 | /// 步骤界面信息 234 | /// 235 | TStepUI_Info = record 236 | sCaption: string; // 标题 237 | sCaption2: string; // 标题2 238 | end; 239 | /// 240 | /// 行信息 241 | /// 242 | TRow_Info = record 243 | sCaption: string; // 标题 244 | sDefValue: string; // 默认值 245 | eStep: TNewPrj_Step; 246 | end; 247 | /// 248 | /// 结构向导树信息 249 | /// 250 | TNewPrj_GuideTreeInfo = record 251 | nIndex: Integer; // 索引 252 | sName: string; 253 | XmjgDset: IPMDataset; // 项目结构 254 | public 255 | procedure Init(); 256 | end; 257 | /// 258 | /// 结构向导树信息 259 | /// 260 | PNewPrj_GuideTreeInfo = ^TNewPrj_GuideTreeInfo; 261 | /// 262 | /// 结构向导树词典 263 | /// 264 | TNewPrj_GuideTreeDictionary = TDictionary; 265 | /// 266 | /// 按钮列表 267 | /// 268 | TNewPrj_ButtonList = TList; 269 | /// 270 | /// 字段记录 271 | /// 272 | TNewPrj_FieldRecord = record 273 | sFieldName: string; // 字段名 274 | eFieldType: TPMFieldType; // 字段类型 275 | end; 276 | /// 277 | /// 赋值类型 278 | /// 279 | TSLAssignType = (slatNone, slatName, slatValue); 280 | /// 281 | /// 可选赋值方式的 StringList 282 | /// 283 | TStringList_Assign = class(TStringList) 284 | private 285 | // 赋值方式 286 | FAssignType: TSLAssignType; 287 | /// 288 | /// 根据value获取name(注意vlaue也许重复) 289 | /// 290 | function GetNameFormValue(Value: string): string; 291 | public 292 | constructor Create(AAssignType: TSLAssignType = slatNone); 293 | procedure AssignTo(Dest: TPersistent); override; 294 | /// 295 | /// 值(value)的索引(注意vlaue也许重复) 296 | /// 297 | /// 值 298 | /// 索引 299 | function IndexOfValue(const Value: string): Integer; 300 | /// 301 | /// 赋值方式(AssignTo) 302 | /// 303 | property AssignType: TSLAssignType read FAssignType; 304 | /// 305 | /// 根据value获取name(注意vlaue也许重复) 306 | /// 307 | property NameFormValue[Value: string]: string read GetNameFormValue; 308 | end; 309 | /// 310 | /// 下拉框 下拉列表词典 311 | /// 312 | TComboBoxListDictionary = TObjectDictionary; 313 | 314 | /// 315 | /// 检查类型 316 | /// 317 | TNewPrj_CheckType = (npctVAT, npctJjLx, npctPrjStat, npctXmBh, npctXmMc, 318 | npctDqBz, npctXmPath, npctJjgz, npctJjmb, npctJkBz, npctZbFile); 319 | /// 320 | /// 检查词典 321 | /// 322 | TNewPrj_CheckValueDictionary = TDictionary; 323 | ``` 324 | 325 | ## 新建工程结构关系 326 | 327 | ```pascal 328 | // 新建工程信息 简化版 329 | TNewPrj_Info = record 330 | eNewPrjMode: TNewPrj_Mode; 331 | Jjgz: PJjgz_Info; 332 | end; 333 | // 计价规则 信息 简化版 334 | TJjgz_Info = record 335 | Jjmb: PJjmb_Info 336 | end; 337 | // 计价模版信息 简化版 338 | TJjmb_Info = record 339 | sName: string; // 名称 [jjmb] 340 | sGuid: string; // guid [JjmbGuid] 341 | sDeGf: string; // 定额规范 (如:浙江省建设工程2010) [degf] 342 | JkBzList: string; // dzpb接口列表 [JkbzList] 343 | end; 344 | ``` 345 | 346 | ## 新建工程 数据管理 347 | 348 | ```pascal 349 | TNewProjectDataManagerEx = class{old class = TNewPrjDataHelp} 350 | private 351 | FNewPrjDset: IPMDataset; 352 | // 应用程序接口 353 | FWorkApp: ISSWorkApplication; 354 | // 系统选项 355 | FSysOption: IYsSysOption; 356 | // 工程信息 357 | FPrjInfo: PNewPrj_Info; 358 | // 当前操作的视图 359 | FCurrentView: IPMDataView; 360 | // 配置文件管理 361 | FConfigMgr: IConfigMgrService; 362 | // 设为默认 配置文件名 363 | FsDefaultSettingFile: TFileName; 364 | // 下拉框 下拉列表词典 365 | FListDic: TComboBoxListDictionary; 366 | // 默认项目结构列表 367 | FDefXmjgList: TNewPrj_DataSetList; 368 | // 计价规则管理 369 | FJjgzMgr: TJjgzManager; 370 | // 电子评标 投标管理 371 | FDzpb_Tb_Manger: IDzpb_TB_Interface_Manager; 372 | // 电子评标 投标接口 373 | FDzpbInf: IDzpb_TB_Trans; 374 | //电子评标调用模式 375 | FDzpbCallingMode: TDzpbCallingMode; 376 | // 电子评标 dll投标接口 377 | FDzpbDllInf: IDzpbV2_TB_Trans; 378 | //dll句柄管理 379 | FDllMgr: TDzpbDllBusiCaller; 380 | // 值检查 381 | FValueChecker: TNewProjectValueChangedChecker; 382 | end; 383 | ``` 384 | 385 | -------------------------------------------------------------------------------- /胜算表结构.md: -------------------------------------------------------------------------------- 1 | # 胜算表结构保存在 config 文件夹的 prjDataStruct.xml 文件中 2 | 3 | ### 项目结构表(xmjg) 4 | 5 | | GUID(FStr) | level(FInt) | bh(fStr) | mc(fStr) | zy(fInt) | lb(fInt) | tx(fStr) | gcgs(fInt) | mbid(fStr) | bz(fStr) | id(fStr) | qdtype(fInt) | rationtype(fInt) | jdsx(fInt) | idxcode(fStr) | fltxid(fInt) | sjbb(fInt) | jdbh(fStr) | gclx(fStr) | 6 | | ---------- | ----------- | -------- | -------- | -------- | -------- | -------- | ---------- | ---------- | -------- | -------- | ------------ | ---------------- | ---------- | ------------- | ------------ | ---------- | ---------- | ---------- | 7 | | Guid | 层次 | 编号 | 名称 | 专业 | 类别 | 特项 | 工程个数 | 模版ID | 备注 | ID | QdType | RationType | 节点属性 | IDxCode | FltxID | 数据版本 | 节点编号 | 工程类型 | 8 | 9 | ### 项目属性表(xmsx) 10 | 11 | | sxx(fStr) | sxz(fStr) | 12 | | --------- | --------- | 13 | | 属性项 | 属性值 | 14 | 15 | ### 项目节点属性表(xmjdsx) 16 | 17 | | guid(fStr) | sxx(fStr) | sxz(fStr) | 18 | | ---------- | --------- | --------- | 19 | | GUID | 属性项 | 属性值 | 20 | 21 | ### 模块配置表(mkpz) 22 | 23 | | guid(fStr) | mc(fStr) | lx(fInt) | pzxx(fStr) | mkxx(fStr) | sfkj(fBoo) | bl(fStr) | bm(fStr) | 24 | | ---------- | -------- | -------- | ---------- | ---------- | ---------- | -------- | -------- | 25 | | GUID | 名称 | 类型 | 配置信息 | 模块信息 | 是否可见 | 变量 | 表名 | 26 | 27 | ### 业务模块子表(ywmkzb) 28 | 29 | | guid(fStr) | pzguid(fStr) | level(fInt) | mc(fStr) | lx(fStr) | dh(fStr) | pzxx(fStr) | ms(fStr) | bl(fStr) | sfkj(fBoo) | bm(fStr) | notcalc(fBoo) | 30 | | ---------- | ------------ | ----------- | -------- | -------- | -------- | ---------- | -------- | -------- | ---------- | -------- | ------------- | 31 | | Guid | 配置表Guid | 层次 | 名称 | 类型 | 代号 | 配置信息 | 描述 | 变量 | 是否可见 | 编码 | 不计算 | 32 | 33 | ### 项目信息表(xmxx) 34 | 35 | | mkguid | mkzbguid | guid | bookmark | level | mc | nr | dm | lx | bz | jdsx(fInt) | sd(fBoo) | xh(fStr) | bm | 36 | | -------- | ------------ | ---- | -------- | ----- | ---- | ---- | ---- | ---- | ---- | ---------- | -------- | -------- | ---- | 37 | | 模块GUID | 模块子表GUID | GUID | 书签 | 层次 | 名称 | 内容 | 代码 | 类型 | 备注 | 节点属性 | 锁定 | 序号 | 编码 | 38 | 39 | ### 编制说明表(bzsm) 40 | 41 | | mkguid | mkzbguid | guid | mc | nr | 42 | | -------- | ------------ | ---- | ---- | ---- | 43 | | 模块GUID | 模块子表GUID | GUID | 名称 | 内容 | 44 | 45 | ### 取费汇总表(qfhz) 46 | 47 | | mkguid | mkzbguid | guid | boolmark | level | jdsx | xh | mc | dw | jss | fl(fDou) | dj(fDou) | hj(fDou) | blws(fInt) | fylx(fStr) | bl | bbbl | dyjss | dyxh | bz | 48 | | ------------ | ------------ | ------------ | -------------- | ----------- | ----------------- | ------------ | ------------ | ---------------- | ---------------- | ------------ | -------------- | ------------ | ---------- | ------------------ | ---------------- | -------- | ---------- | -------- | ---- | 49 | | 模块GUID | 模块子表GUID | GUID | 书签 | 层次 | 节点属性 | 序号 | 名称 | 单位 | 计算式 | 费率 | 单价 | 合价 | 保留位数 | 费用类型 | 变量 | 报表变量 | 打印计算式 | 打印序号 | 备注 | 50 | | **sc(fStr)** | **sd(fBoo)** | **lb(fInt)** | **Code(fStr)** | **IdxCode** | **paySing(fInt)** | **hzjss** | **hjjss** | **sfjs(fBoo)** | **isfygz(fBoo)** | **tx(fInt)** | **zgxj(fDou)** | **zy(fDou)** | **fbr** | **jssvalue(fDou)** | **zdxj(fInt)** | | | | | 51 | | 输出 | 锁定 | 类别 | Code | IdxCode | 付费标识 | 核增计算公式 | 核减计算公式 | 是否计算 | 是否显示跟踪 | 特项 | 最高限价 | 专业 | 发包人 | 计算式数值 | 最低限价 | | | | | 52 | 53 | -------------------------------------------------------------------------------- /虚拟方法表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/虚拟方法表.png -------------------------------------------------------------------------------- /虚拟方法表1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/虚拟方法表1.png -------------------------------------------------------------------------------- /虚拟方法表2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/虚拟方法表2.png -------------------------------------------------------------------------------- /计价基础知识.md: -------------------------------------------------------------------------------- 1 | 1. 建筑安装工程计价方法 2 | 3 | * 国标清单计价 4 | * 定额计价 5 | 6 | 2. 建筑安装工程采用的计税方式 7 | 8 | * 一般计税法 9 | * 简易计税法 10 | 11 | 3. 国家工程量清单名词解释 12 | 13 | 指根据国家“计价规范”及本省补充的要求编制的,载明建设工程分部分项工程项目、措施项目、其他项目的名称和相应数量以及规费、税金项目等的明细清单。 14 | 15 | 4. 工程量清单计价的建筑安装工程造价由哪几部分组成 16 | 17 | * 分部分项费 18 | * 措施项目费 19 | * 其他费用 20 | * 规费 21 | * 税金 22 | 23 | 5. 分部分项工程费如何计算 24 | 25 | * 按分部分项工程数量乘以综合单价进行计算 26 | 27 | 6. 措施项目包括什么 28 | 29 | * 单价措施项目(技术措施) 30 | * 总价措施项目(组织措施) 31 | 32 | 7. 组织措施一般包含的内容 33 | 34 | * 安全文明施工费 35 | * 夜间施工增加费 36 | * 二次搬运费 37 | * 冬雨季施工增加费 38 | * 工程定位复测费 39 | 40 | 8. 其他费用的组成 41 | 42 | * 暂列金额 43 | * 暂估价 44 | * 计日工 45 | * 总包服务费 46 | 47 | 9. 暂列金额名词解释 48 | 49 | 是指招标人在工程量清单中暂定并包含在合同价款中的一笔款项。用于工程合同签订时尚未确定或者不可预见的所需材料、工程设备、服务的采购,施工中可能发生的工程变更,合同约定调整因素出现时的合同价款调整,以及发生的索赔、现场签证确认等的费用和标化工地、优质工程等费用的追加。 50 | 51 | 10. 暂估价名词解释 52 | 53 | 是指招标人在工程量清单中提供的用于支付必然发生但暂时不能确定价格的材料、工程设备的单价以及施工技术专项措施项目,专业工程等的金额。 54 | 55 | 11. 专业工程暂估价名词解释 56 | 57 | 是指发包阶段已经确认发生的专业工程,由于设计未详尽,标准未明确或者需要由专业承包人完成等原因造成无法当时确定准确价格,由招标人在工程量清单中给定的一个暂估总价。 58 | 59 | 12. 材料及工程设备暂估价名词解释 60 | 61 | 是指发包阶段已经确认发生的材料,工程设备,由于设计标准未明确等原因造成无法当时确定价格,或者设计标准虽已明确,但一时无法取得合计询价,由招标人在工程量清单中给定的若干暂估单价。 62 | 63 | 13. 计日工名词解释 64 | 65 | 是指在施工过程中,承包人完成发包人提出的工程合同范围外的零星项目或工作所需的费用。 66 | 67 | 14. 规费名词解释 68 | 69 | 是指按国家法律,法规规定,由省级政府和省级有关权利部门规定必须缴纳或计取的,应计入建筑安装工程造价内的费用。内容包括 70 | 71 | 1. 社会保险费 72 | * 养老保险费 73 | * 失业保险费 74 | * 医疗保险费 75 | * 生育保险费 76 | * 工伤保险费 77 | 2. 住房公积金 78 | 79 | 15. 税金名词解释 80 | 81 | * 是指国家税法规定的应计入建筑安装工程造价内的建筑服务增值税。 82 | 83 | 16. 一条定额的组成 84 | 85 | 编码+名称+单位+人材机明细 86 | 87 | 17. 一条清单的组成 88 | 89 | 编码+名称+项目特征+单位 90 | 91 | 18. 建设工程施工过程中取费的项目一般包括那些 92 | 93 | 施工组织措施费,企业管理费,利润,规费和税金 94 | 95 | 19. 总和单价的组成,国标清单的综合单价如何计算出来 96 | 97 | 完成一个规定计量单位的分部分项工程量清单项目或措施清单项目所需要的人工费,材料费,施工机械使用费和企业管理费与利润,以及已定范围内的风险费用。 98 | 99 | 20. 清单总和单价的计算方式 100 | 101 | 定额的人,材,机管理,利润合计数除以清单行的工程量得出相应费用再累加即国标清单总和单价。 102 | 103 | -------------------------------------------------------------------------------- /造价概念.md: -------------------------------------------------------------------------------- 1 | 1. 工程量清单是建设工程的分部分项工程项目,措施项目,其他项目,规费项目和税金项目的名称和税金项目的名称和相应数量等的明细清单,由分部分项工程量清单,措施项目清单,其他项目清单,规费税金清单组成。 2 | 2. 工程造价是指工程的建设价格,是指为完成一个工程建设,预期或实际所需的全部费用总和。 3 | 4 | -------------------------------------------------------------------------------- /长字符串的内存存储格式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conba/Delphi-Study-Notes/da0a9f47bbd623f23e25dbf66fb78513e1fb50ca/长字符串的内存存储格式.png -------------------------------------------------------------------------------- /高手突破.md: -------------------------------------------------------------------------------- 1 | # 第二章 2 | 3 | ​ 堆又称为自由存储区,其中的内存空间的分配和释放必须由程序员来控制。栈又称为自动存储区,其中内存空间的分配和释放是由编译器和系统自动完成的。 4 | 5 | ​ 对象的大小,就是其数据成员所占用的内存空间的总和,其方法不占用内存空间。不过不是简单的加法,还和数据域对齐方式优化有关。 6 | 7 | ​ TObject实现了一个InstanceSize()方法,它可以取得对象实例的大小。例如下面这个类 8 | 9 | ```pascal 10 | type 11 | TMyClass = class 12 | public 13 | FMem1: integer; 14 | FMem2: integer; 15 | FMem3: word; 16 | FMem4: integer; 17 | procedure Method(); 18 | ``` 19 | 20 | ```pascal 21 | Obj.instanceSize //20 //对象的大小 22 | integer(Obj) //13443352 //对象所在地址 23 | integer(@Obj.FMem1) //13443356 //FMem1所在地址 24 | integer(@Obj.FMem2) //13443360 //FMem2所在地址 25 | integer(@Obj.FMem3) //13443364 //FMem3所在地址 26 | integer(@Obj.FMem4) //13443368 //FMem4所在地址 27 | ``` 28 | 29 | ​ 根据对象的首地址及大小可以推算出,对象占用的空间是13443352到13443363。但是第一个成员在13443356,它与对象的首地址有一个4字节的空缺,这4个字节存放的是一个指向对象的VMT(虚方法表)的指针。 30 | 31 | ​ 上面比较疑惑的是word类型的变量同样占用4个字节,也就是32位的空间,这和编译器字节对齐优化有关。编译器会将无法合并的小于32位的空间的数据填充到32位大小,以加快存取速度。如果FMem2也是word类型的话,那么FMem2和FMem3一起占用32位,那么对象大小会变成16。 32 | 33 | ![](对象占用空间.png) 34 | 35 | # 第四章 VCL库 36 | 37 | ```pascal 38 | TObject封装了Object Pascal 类/对象的基本行为 39 | TPersistent派生自TObject, TPersistent使得自身及派生类对象有自我保存,持久存在的能力。 40 | TComponent派生自TPersistent,这条分支下所有的类都可以被称为组件,组件的一般特性是 41 | (1)可以出现在开发环境的组件板上 42 | (2)能够拥有和管理其他组件 43 | (3)能够存取自身,这是因为TComponent派生自TPersistent。 44 | TControl 派生自TComponent,其分支下所有的类,都是在运行时可见的组件。 45 | TWinControl派生自TControl, 这个分支封装了Windows系统的屏幕对象,也就是一个真正的Windows窗口(拥有窗口句柄) 46 | TCustomControl派生自TWinControl。从TCustomControl开始,组件拥有了Canvas属性。 47 | ``` 48 | 49 | ## 4.2 TObject与消息分发 50 | 51 | ​ 在Tobject这个类中,有一个**Dispatch()方法**和**DefaultHandler()**方法,Dispatch()负责将特定的消息发给合适的消息处理函数。它首先会在对象本身类型的类中去寻找消息处理函数,如果找到了,那么调用它,如果没有找到而该类覆盖了TObject的DefaultHandler(),则调用该类的DefaultHandler();如果两者都不存在,则继续在其基类中寻找,直至寻到TObject这一层,而TObject已经提供了默认的DefaultHandler()方法。 52 | 53 | ​ 下面是自定义的消息类型,VCL规定自定义的消息类型的首4字节必须是消息编号,其后的类型随意。 54 | 55 | ```pascal 56 | type 57 | TMyMsg = record 58 | Msg: Cardinal; //首4字节必须是消息编号 59 | MsgText: ShortString; //消息的文字描述 60 | end; 61 | ``` 62 | 63 | ​ 在delphi中,指明类的某个方法为某一特定消息的处理函数,则在其后面添加message关键字与消息值,由此来通知编译器。 64 | 65 | ```pascal 66 | TMsgAccepter = class 67 | private 68 | //编号为2000的消息处理函数 69 | procedure AcceptMsg2000(var msg: TMyMsg); message 2000; 70 | //编号为2002的消息处理函数 71 | procedure AcceptMsg2002(var msg: TMyMsg); message 2002; 72 | public 73 | procedure DefaultHandler(var Message); override; //消息处理方法 74 | EXTERNALSYM externalsym 75 | ``` 76 | 77 | ## 4.3 TControl与windows消息的封装 78 | 79 | ​ TObject 提供了最基本的消息分发和处理机制,但是VCL真正对windows系统消息的封装是由TControl完成的。TControl将消息转换成VCL事件,以将系统消息融入VCL框架。 80 | 81 | ![](TControl中鼠标事件到消息的转换过程.png) 82 | 83 | ## 4.4 TApplication与主消息循环 84 | 85 | ​ Windows标准程序的流程 86 | 87 | 1. 从入口函数WinMain开始。 88 | 89 | 2. 注册窗口类及窗口函数(Window Procedure)。每一个窗口都是一个窗口类的实例。 90 | 91 | 3. 一个应用程序在创建某个类型的窗口前,必须首先注册该“窗口类”(Windows)。RegisterClass(RegisterClassEx)把窗口过程,窗口类型以及其他类型信息和要登记的窗口类关联起来。 92 | 93 | 4. 创建并显示窗口 94 | 95 | 5. 进入主消息循环,从消息队列中获取并分发消息。每一个窗口类都有一个窗口过程(WndProc),负责处理发送该类窗口的所有消息。 96 | 97 | 6. 消息被分发后,由Windows操作系统调用窗口,由窗口函数对消息进行处理。 98 | 99 | ​ 在Delphi中,每个项目所定义的Main Form 并不是主线程的主窗口,每个Application 的主线程的主窗口(也就是出现在任务栏中的)是由TApplication创建的0*0大小的不可见窗口。但它可以出现在任务栏上,其余由程序员创建的Form,都是该窗口的子窗口。 100 | 101 | ​ 在TApplication的构造函数中,由这样一行代码 102 | 103 | ```pascal 104 | if not ISLibrary then CreateHandle; 105 | ``` 106 | 107 | ​ 在非Dll项目中,构造函数会调用CreateHandle方法。 108 | 109 | ## 4.5 TPersistent与对象赋值 110 | 111 | ​ 在object Pascal中,所有的简单类型(或称编译器内置类型,即非“类”类型,如Integer,Char, Record等类型)的复制操作所进行的都是**位复制**,即将一个变量所在的内存空间的二进制位复制刀被赋值的变量所载的内存空间中。 112 | 113 | ​ 但是对于复杂类型,即类的实例对象,采用“引用模型”,因此在程序中所有的类的对象的传递,全部基于其“引用”,也就是对象指针。 114 | 115 | 116 | 117 | ​ --------------------------------------------------------------------------------