问题是这样的,要在Chart中画一个背景区域。这个很容易,窗口里在Chart的OnBeforeDrawAxes里写下绘制的代码就OK了。
下一步的问题是在不同的步骤可能需要多个背景区域,那么每次都在窗口里拷贝代码明显是不合适的。我准备把绘制工作放到一个专门的类中处理。在类创建的时候把需要绘制的Chart传入并接管它的事件,每次Chart重绘时触发OnBeforeDrawAxes就会调用Area的绘制方法。那么每当需要为某个Chart增加这种背景时只要:
Area := TArea.Create(Chart);
好了,目前都比较简单,下来我想要能够绘制多个区域,我希望可以这样:
Area1 := TArea.Create(Chart);
Area2 := TArea.Create(Chart);
问题来了,Delphi的事件机制是采用函数指针来处理的,也就是Area把自己的函数的地址交给Chart,让它在合适的时候调用,可是现在有两个函数,却只有一个指针。简单的象刚才那样写就只会画出第二个区域。
我想起来原来遇到过这样的问题,当时是这么解决的,在需要处理事件的对象接管之前,保存原有的事件处理函数的指针,并在每次触发后执行原有的事件。代码大概会这样:
procedure TArea.Create(aChart: TChart);
begin
...
FOldEventFunc := aChart.OnBeforeDrawAxes; {保留原有处理}
aChart.OnBeforeDrawAxes := DrawArea; {接管事件}
end;
procedure TArea.DrawArea(Sender: TObject);
begin
... {绘制}
if Assigned(FOldEventFunc) then
FOldEventFunc(Sender);
end;
这样,多个处理事件的对象就会象一个链条一样挨个触发一遍。
很不幸的是,我也想起原来使用这个方法的一个问题,如果不小心一个对象接管了两次,那么就会形成一个环,这个事件处理就会死循环了。另一方面,在处理某个具体事件的对象中强制加入这样一个约定,感觉在结构上也不太完善。
这样,我开始考虑更好的方案。可以写一个EventManager来管理Chart的所有事件,对每个事件,EventManager里都有一个事件列表,而每个需要监听事件的对象,都不是直接更改Chart的事件指针,而是通过EventManager的AddXXXEventListener来加入。在每次事件触发的时候,由EventManager来遍历调用列表里的每个监听者的事件处理函数。那么在TArea的构造函数中应该这样:
EventManager.AddBeforDrawAxesLintener(DrawArea);
考虑到我并不想在创建区域的时候还要自己创建一个EventManager,所以应该有一个全局的工厂类,负责为需要绘制的Chart维持一个EventManager。构造函数中改为这样:
EMFactory.GetChartEM(aChart).AddBeforDrawAxesLintener(DrawArea);
好了,现在基本上已经有了一个我满意的方案,我已经开始考虑写一个示例用的测试用例,生成一个事件源和两个监听者,表示一下这个结构是如何工作的。
可是……可是,在我正在为测试用例起名字的时候,突然脑子里响起了那句名言:“你不会需要它的。”我真的需要这么复杂的一个东西么?虽然说我相信它足够通用,结构也足够清晰,不过好像还是太复杂了,我不会用到这么复杂的东西,不是么?
回头再看看,我需要绘制多个区域。(但愿我还没忘了要作什么),那么一个绘制对象在一次响应的时候依次绘制不就可以了么。我只需要稍稍调整一下原来设想的使用方法:
AreaPainter := TAreaPainter.Create(Chart);
AreaPainter.NewArea;
... {设置区域属性}
AreaPainter.NewArea;
...
这样不就解决了么?而且在真的要画第二块区域前完全不需要实现NewArea方法,那么这个绘制对象,几乎不会比最初设想的Area复杂多少,哈哈,真是自己把自己搞糊涂了。
现在可以不用去理会那个复杂的东西,开始编写简单的实现了,唯一的遗憾是界面绘制不适合采用单元测试。