2009年6月16日
#
前二天机缘巧合,在四五年后重新用起了fastreport,不得不感叹其强大之处,定制能力实在是太强了,今天把一些以前写的放在大富翁论坛的东西翻出来,如果现在还有用这个东西的,如果你也有一些另类的运用,可以有所帮助。
这里有一个FR报表简繁体转化的解决方案,只做一份报表,即可在简繁体下用。
报表运行时可编辑,这并不是什么用法,只是一个当时解决问题跟踪FR源码时的所得,以前在delphi下开发跟踪跟踪人家的源码受益匪浅。
数据集行列转换不是什么新鲜事,关于SQL的用法网上随便搜搜即有,这里有不用改变数据集,在FR里直接行列转换输出的例子,这样在就不用在表单层专门为报表重新生成一个数据集。
这里的很多技巧其实是因为不想为了报表显示重新用SQL生成全新的数据集,下面这个对帐单式的交错复合报表即是。
上面是以前做的,前二天做报表时,又遇到了新情况,报表的分组页码显示,这个其实简单得很,只是好久没用FR报表了所以觉得有搞,其实象那种在分组头显示合计之类的都是用类似技巧实现的。
这里的情况适用于FastReport2.53版本。
报表页码一般情况下就象在Word里一样不用费什么脑筋,FastReport里直接从系统变量可以得到。但特殊情况下就无法轻松得到了。
这里谈的是分组页码的问题,就是在报表分组的情况下。首先是每个分组强制分页,然后页码重新计数。比如5页分为二组,一组占了2页,另一组占3页。平常显示只要
1/5 2/5 3/5 4/5 5/5
这时要变成
1/2
2/2 1/3 2/3 3/3
第几页这是很容易算出来的,用个变量来表示页码,分组结束后重新算页码就行了,难点就在分组的页数,基本思路就是利用两遍报表,第一次得到各个分组的总页数,第二次再显示之。
首先定义三个变量,GPage用来表示页码初始值为1,PageList用来存放分组的页码数初始值为{0},GIndex用来计算分组的索引初始值为0,FGIndex用来第二次遍历时计算分组索引,其实这个与GIndex可以共用一个,但为了清晰起见分开用。
在PageFooter的BeforePrint里写
GPage := GPage + 1;
在GroupFooter的BeforePrint里写
if FinalPass then //二次遍历时增加分组索引
FGIndex := FGIndex + 1
else
begin
setLength(PageList,GIndex +
1);
PageList[GIndex] := Gpage;
//存放分组的总页数
GIndex := GIndex + 1;
end;
GPage := 0;
这样所有的东西都有了,在显示页码的Memo里填[GPage]。
在显示页数的Memo下面脚本段里写
if not FinalPass then exit;
MemoPageCount.Lines[0] :=
PageList[FGIndex];
(MemoPageCount
是这个Memo的名字)
这样就达到上述的分组页码的要求了。
2007年8月2日
#
该总结的东西都总结得差不多了,剩下的都是一些细枝末节的了,基本可以收尾了,这一篇讲一讲设计模式。
设计模式是个什么东西,它其实只是前人在当前系统实现基本手段基础上提取出来的一些方法论,目的是为了更好更快的解决我们在系统实现中碰到的各种问题。其实如果总是处于那种“你来我往”的状态之中,没有静下心来细细的思量,无法把握到项目的全貌,没有精益求精的态度,那些方法论不用也罢。可幸这一次上述的如果都没有,所以才能一直写这个系统的总结写了二十来篇,可谓收获颇丰。
一、创建型模式
创建型模式里用得比较多,也比较好掌握的就是Singleton模式了,这个模式对于实现者的设计能力要求不甚高,这也是我最早运用的模式之一。在此次系统实现中当然也不会少,不过其中由于环境特性,我有些实现的地方并没有用标准的方法,而是简化了。那些地方可以说徒有其名而无其实,但最终的效果还是一样的。
Factory与Abstract Factory模式是比较耳熟能详的二个模式,其二者很容易混淆起来,其实我在运用它们的时候也不是非常的得心应手,总的来说实现了一种设计后并不能严格意义上区分它是哪一个模式。对于很多的模式都有这种感觉,特别是当一个设计开始趋向有层次的复杂之后,这从另一方面印证了设计模式是一种特定条件下的方法论的观点。
在这一次的系统中并没有很多的、很复杂的类的创建,所以Prototype与Builder模式也无从谈起。系统中类的创建要化点脑筋的就数硬件访问与服务加载了,二者实现手段是一致的。都是先提取一个公共的接口,系统启动创建时,根据配置文件创建相关类,并设置相关属性,最后把创建出来的类放入到一个集合中供系统调用。这里有Abstract Factory,也有Bridge以及Iterator。
二、结构型模式
Adapter在这新的系统用处不大,但Bridge则大有用武之地。特定硬件与系统的解耦就是通过Bridge来实现的,还记得硬件访问时提取出来的那个接口吗?那个接口与我们封装过的特定硬件访问类之间就是桥的关系。
硬件技术总归是一种比较复杂的东西,我们的系统利用的只是它们功能的小部分,同时厂商提供的硬件访问API对于系统是很不友好的,它对于我们暴露了过多的细节,这时Facade模式就派上用场了。所以硬件访问这一块是在Facade基础上的Bridge。
其实数据通讯这一块跟硬件访问有异曲同工之妙,通讯组件的逻辑封装与技术封装之间可以认为有Bridge,即不同的通讯技术对于客户端的最终调用是不透明的;而技术封装本身就是一个Facade。
Composite模式最通常的应用就是菜单类了。而Decorator模式在我看来是无处不在,这个模式是“优先使用对象组合,而不用类继承”的典型体现。我说这个模式无处不在并不是说系统中大量充斥着一个个的Decorator,而是说明对象组合的普遍性。最能体现Decorator的价值的地方是数据对象的查询条件、排序设置,数据对象EntityBase与查询条件WhereCondition之间是Decorator关系,而WhereCondition内部则是Composite关系。
Flyweight与Proxy在系统中没有很明显的应用。
三、行为型模式
相比前二类模式,行为型模式似乎比较的深奥一点,以前有几次出去面试时,居然不止一次的发现有些人不了解其中的一些模式。抛开一些个体的因素不谈,个人认为行为模式显得“深奥”正是反应了我们真实世界的多变复杂性。我们的世界是如此的难以琢磨,而行为型模式对于这个世界相比前二类模式有更深的介入,以至于我们在运用这一类的模式时有时会很不好权衡利弊。
Command模式,每次提到时好象都拿菜单与工具栏说事,这个运用也是比较的普遍,这一次实现系统当然也就没有放过它。
Iterator模式在行为型模式中算是比较“大众化”的一个模式了,因为集合的运用对于每一个系统实现者来说都是非常必要的,而现在绝大多数的系统框架在集合类的实现上都体现了这个模式的精髓。而Observer是另一个系统框架涉及比较多的模式,事件-委托机制就可以认为是这个模式一种表现。集合与事件是在系统设计与实现中大量运用的,上述二种模式当然就逃不过。
State模式在这次的系统实现的运用中不是非常的到位,因为虽然窗体状态看起来也挺多,但对于界面变化来讲只分为浏览与非浏览二种,而各个状态的操作只有一个“确定”是各异的。State模式在这个场景显得有点鸡肋。
Strategy本来是没什么机会用的,不过在管控端有一个功能,就是显示座位,座位号的排列并不是每次都是从左到右来的,这里有个排座策略问题,这个模式恰好排上用场。
Template Method是行为模式中在我看来运用得很频繁的一个模式,这次系统实现中也大量的运用,比如在单数据窗体中工具栏按钮事件中的一串方法,还有在数据访问对象中的很多方法。其实象这种由父类放一个钩子出去,然后子类Override相关方法实现具体操作的用法,在很久以前还不知道设计模式这个东西的时候就在使用了。
剩下的其他行为型模式有些有太特殊的应用场景(如Interpret),而有些虽然可以应用到系统中,但鉴于系统需求没有那么深入,犯不着为了一个设计模式复杂化整个系统(如Memento)。
最后数了一下,在这次系统实现中共用了12个模式,占了Gof设计模式的一半强,可以说“麻雀虽小,五脏俱全”。半年来的经历比之过去几年的收获还大,当然没有之前的积累也一下子用不上这么多的设计模式,总之过去的那半年虽然没有投身到新技术的大潮中,但还是很值得。
2007年7月30日
#
这一篇其实没什么可讲的,只提一下跟客户端不太一样的一些地方。
服务端跟客户端最大的区别是它面对的不是单单一个连接,而是有一些个连接。对于接收与发送来讲它是要具体到accept进来的每一个连接的,所以这里有一个SocketStateObject参数会贯穿始终,这个参数主要就是放对应客户端的Socket连接及一些状态变量,在accept进来一个连接后即创建一个这个对象。
public void Listen(int port)
{
IPEndPoint ipe = new IPEndPoint(IPAddress.Any,port);
workSock = new Socket(ipe.AddressFamily,SocketType.Stream,ProtocolType.Tcp);
workSock.Bind(ipe);
workSock.Listen(100);
acpT = new Thread(new ThreadStart(acceptThread));
acpT.IsBackground = true;
acpT.Start();
}
private void acceptThread()
{
while(true)
{
Socket asock = workSock.Accept();
SocketStateObject state = new SocketStateObject(asock);
if (OnConnect != null)
OnConnect(state,System.EventArgs.Empty);
ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveThreadEntryPoint),state);
}
}
象上面OnConnect事件及ReceiveThreadEntryPoint方法里都会跟进这么一个参数。
管理连接是个很复杂、很讲究技术的活。可用连接的数据接收与发送(甚至有些接收与发送必须是相关的)、连接的可用性监测、失效连接的移除。所有这些都要作应有的记录,并且一些还要往UI界面触发事件,事件触发这一点至关重要,也最为棘手,这其中涉及到很多线程同步、线程阻塞的问题。
对于这种极为复杂的问题,最好的解决方法是参照现有的成熟方案进行设计、实现或者拿来主义。但之前也没涉足到这些方面的开发,一时还真找不到比较理想的东西,加之项目本身对于通讯的要求不甚高,10个连接顶天了(如果真有哪个客户要10个签到终端,那真是绝对的上帝了,至目前为止最多的也就5个终端,一般不会多于3个),所以在这次开发中我就用了一些简单的方法来管理客户端的连接。
由于这一块并没有经过认真的设计,也暂时无力作认真的设计,最后实现的东西自己也不满意,这里就不去讲述如何实现管理连接的了。
2007年7月26日
#
摘要: 数据窗体的状态大概可以概括为以下几种,即初始、浏览、新增、修改、删除、查询(单数据窗体的查询是一种简单地基于当前表的查询,各个条件之间只能“与”运算,关系运算只有“等于”,当然字符串字段会有“like”)。在各种状态下,窗体的UI呈现是不相同的,而且在不同状态下,一些按钮的动作是各异的。比如“确定”按钮,在新增状态下按“确定”和在查询状态下按“确定”明显是不同的二种动作。上述的这些情况可以用GoF...
阅读全文
2007年7月25日
#
摘要: 所谓的单数据窗体是指那种在窗体只显示一张单表的数据的窗体,主要就是用于显示基本表,因为这个系统要显示的差不多就是基本表为主,这一篇主要讲这个数据显示窗体的基类。既然是基类嘛,当然要提供一些虚方法给子类来改写,要有一些变量必须由子量来初始化。首先必须由子量来提供实例的变量主要是跟子类关联的数据表有关,因为窗体的始化及UI的呈现都是与数据有关的,所以上述变量的初始化必须是在构造函数的最开始的地方进行。...
阅读全文
2007年7月24日
#
摘要: 经过上二篇的铺垫,现在终于可以加载菜单与工具栏了。先来看一下IService的接口实现里LoadCommand方法是如何来加载菜单与工具栏的:IService接口的实现Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->publicclassSignInR...
阅读全文
2007年7月23日
#
2007年7月18日
#
摘要: 从这一篇开始要讲管控端是如何来加载菜单与工具栏的了,.Net Framework自带的菜单与工具栏用来做一个简单的小程序是大大胜任的。但用它们来实现一个可以称之为系统的东西则不够强大,变化多端的环境转瞬间就使它们的功能是捉襟见肘了,这里我们必须先要对它们做出一定的改造。菜单与工具栏只是UI的二种不同表现,二者在系统的动作执行上是没有什么不同的。由此说起二者的系统集成,一般都会提到Command这个...
阅读全文
2007年7月11日
#
本要接上一篇开始讲管控端程序的菜单与工具栏的加载,但发现还是先要讲一下整个管控端的窗体组织,否则会无法理解菜单事件为何要那么写。
多窗体组织很经典的模型就是MDI了,但MDI在窗体最大化、最小化、还原一些动作之后,窗体会乱掉,很乱七八糟。现在的程序很少有用那种原始的MDI来作界面的了,至少我不会去用,实在是难看。但开发工具在这里也是没有长进,如果我们直接用IDE只能生成那种原始的MDI窗体。
只能另想办法,其实也没多想,因为以前(一年多前)曾经为同学做过一个DEMO,当时也是基于难看的MDI,在SourceForge上找了这个DockPanel(从作者名字看象是一个华人的作品,现在已经是2.0了,我当时还是1.0。下面我所述的都是基于1.0的基础上的),做出了相对比较悦目的界面,这东西没有用GPL之类的开源协议,这次当然也就顺手用上了(其实即使它用了GPL,我用它了,公司也没人管我,他们只关心项目有没有按时完成。有否金玉其外、败絮其中都无瑕顾及,还会去顾及这个国人很少注意的Open Source License吗?)。
用这个DockPanel能做出什么界面这里不具体说了,基本可以用它做出如VS.net那样的界面,不过对我来说用不了那么多功能,用得最多的就是“多Tab显示窗体”。当然基于那个DockPanel我作了一些小小的修改。
当双击Tab时,原先是直接把当前Tab所表示的这个窗体,从主窗体的框架上分离现来,成为一个浮动的窗体。这不是我想要的,我把它改成了双击关闭。
在DockPaneStripBase的WndProc方法里,对于左键双击消息重新作了处理(下面注释掉的一行是原先的写法,它下面那行是我改的):
else if (m.Msg == (int)Win32.Msgs.WM_LBUTTONDBLCLK)
{
base.WndProc(ref m);
int index = GetHitTest();
if (DockPane.DockPanel.AllowRedocking && index != -1)
{
IDockContent content = Tabs[index].Content;
try {
// content.DockHandler.IsFloat = !content.DockHandler.IsFloat;
content.DockHandler.Close();
}
catch { }
}
return;
}
另外改的一个地方,这一次系统里没有派上用场,不过也顺便提一下。是关于主窗背景色(图)的,因为这次的管控端系统,系统运行中一般是会有一个窗体一直开启着的(就是那个签到统计显示的窗体),背景对于使用者反而是不可见的。
DockPanel的OnPaint方法里,原来对于背景设置是不起作用的,我把它改了一下:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
// g.FillRectangle(SystemBrushes.AppWorkspace, ClientRectangle);
if (this.BackgroundImage != null)
g.FillRectangle(new TextureBrush(this.BackgroundImage),ClientRectangle);
else
g.FillRectangle(new SolidBrush(this.BackColor),ClientRectangle);
}