苛评VCL: 失望的TMenu

本文讨论了VCL中TMenu的局限性,特别是在扩展性和定制化方面的问题,并提出了几种可能的解决方案,包括事件覆盖和HOOK替换等方法。
<p style="text-indent: 21pt;">TMenu是什么?他是VCL中封装的TMainMenu和TPopupMenu的基类。与其说对TMenu失望,其实是对VCL中对TMainMenu和TPopupMenu的失望。</p>
<p style="text-indent: 21pt;">为什么呢?这基本上还得归咎于微软自己。微软在推出Windows的同时,却坚决的在推Office系列产品。而且Office的更新速度和Windows几乎一样快。更重要的是,Office系列产品的界面风格,特别是菜单,和标准Windows的完全不同。</p>
<p style="text-indent: 21pt;">当微软的Office推广地非常好的时候,我们都在渴望也能设计出一样的界面。对于只是外表改变的需求来讲,很自然地可以考虑重新覆盖TMenuItem的Draw方法。</p>
<p style="text-indent: 21pt;">可是,当你真的想这么干的时候,你会发现两件事,一件让你高兴,一件让你失望。先说高兴的事吧:</p>
<p style="text-indent: 21pt;">负责Draw工作的两个方法:AdvancedDrawItem和MeasureItem都是声明的Virtual类型的函数。也就是说,我们完全可以通过覆盖这些方法的方式去实现。而且,显然的,TMenuItem的设计者,显然已经考虑到这样的扩展了。</p>
<p style="text-indent: 21pt;">而且,通过观察AdvancedDrawItem的实现,你更可以发现你需要修改的代码点。</p>
<blockquote>
<pre><div style="padding-right: 5.4pt; padding-left: 5.4pt; background: #e6e6e6; padding-bottom: 4px; width: 95%; padding-top: 4px;"><div>
<span style="color: #000000;"></span><span style="color: #0000ff;">if</span><span style="color: #000000;">(ParentMenu</span><span style="color: #000000;"><></span><span style="color: #000000;">nil)and(ParentMenu.OwnerDrawor(ImageList</span><span style="color: #000000;"><></span><span style="color: #000000;">nil))and<br>(Assigned(FOnAdvancedDrawItem)orAssigned(FOnDrawItem))then<br>begin<br>DrawItem(ACanvas,ARect,Selected);<br></span><span style="color: #0000ff;">if</span><span style="color: #000000;">Assigned(FOnAdvancedDrawItem)then<br>FOnAdvancedDrawItem(Self,ACanvas,ARect,State);<br>end</span><span style="color: #0000ff;">else</span><span style="color: #000000;"><br></span><span style="color: #0000ff;">if</span><span style="color: #000000;">(ParentMenu</span><span style="color: #000000;"><></span><span style="color: #000000;">nil)and(notParentMenu.IsRightToLeft)then<br>NormalDraw<br></span><span style="color: #0000ff;">else</span><span style="color: #000000;"><br>BiDiDraw;<br></span>
</div></div></pre>
</blockquote>
<p style="text-indent: 21pt;">请注意看NormalDraw和BiDiDraw。那么我们的OfficeDraw只要并列选择就可以了。当然了,如果要完全放弃原来的画法,那就更容易了。</p>
<p style="text-indent: 21pt;">遗憾的是,我们必须听另一个让人失望的事。那就是,Menus单元中,所有创建TMenuItem的地方,都是直接调用TMenuItem.Create的方式。如果你想派生一个TOfficeMenuItem类,你无法找到能创建你的类的地方。</p>
<p style="text-indent: 21pt;">这种情况,一般的设计就是,在TMenu类中有一个虚拟方法,返回一个类型为TMenuItem的实例。至于用哪个具体的类型,可以有TMenu的派生类来决定。比如,你自定一个TOfficeMenu,当创建TMenuItem的实例的时候,使用TOfficeMenuItem创建。简单一点就是下面的代码:</p>
<p style="text-indent: 21pt;">function CreateMenuItem: TMenuItem; override;</p>
<p style="text-indent: 21pt;">而实现的时候,简单用下面的代码实现:</p>
<p style="text-indent: 21pt;">Result := TOfficeMenuItem.Create;</p>
<p style="text-indent: 21pt;">每当我不得不面对这个现实的时候,我就开始考虑是不是有办法绕过去。有一种办法,是实现TMenuItem的OnDrawItem事件。还有一种办法,就是直接使用HOOK的方式替换TMenuItem类。</p>
<p style="text-indent: 21pt;">我能想到的上面两个方法,都只算是在特定应用中解决问题的办法。作为热衷组件化设计的我来讲,显然更愿意自己实现一个TOfficeMenu和TOfficeMenuItem。并且不是仅仅通过修改事件,而是完全通过面向对象的多态性来完成这个功能。</p>
<p style="text-indent: 21pt;">失望TMenu是因为其扩展性方面的欠考虑,因为此,导致VCL中默认就代用两种方式。而第三方蓬勃发展的如TBX系列和Raze系列界面控件,也都证明了其设计方面的欠缺。</p>
<p style="text-indent: 21pt;">其实我们也可正好可以思考一下,设计,应该考虑好开闭原则。特别是“对未来的派生是开放的”这一点。怎么样的设计都可以完成目前的功能。可是能不能考虑到以后的扩展,那就是设计的好坏了。</p>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值