2006年8月7日
#
在.NET 2.0中,微软对.NET 1.0中提出的WinForm的AutoScale能力进行了调整和增强。但是,微软始终没有跳出快速开发的圈子,因此,AutoScale做的并不彻底。其中有些工作还必须我们自己完成。
1. 什么是AutoScale?
我们知道,在不同的操作系统版本或语言版本之间,系统的默认字体是不同的。Window还提供了用户设置DPI的能力。因此,很多时候,我们在特定版本和DPI下设计的窗体,在另外一个操作系统下就会有一些字或图片被裁掉。如下图:
.NET提供了一套关于这个问题的解决方案。就是WinForm上的一组关于AutoScale的方法或属性。包括:
ContianerControl上有:
AutoScaleFactor property
AutoScaleDimensions property
CurrentAutoScaleDimensions property
AutoScaleMode property
PerformAutoScale method
Control上有:
Scale method
ScaleChildren method
GetScaledBounds method
一般情况下,微软会自动帮助你完成所有关于AutoScale的功能。你并不需要写特别的代码使用这些功能。
2. AutoScale的实现细节
在DesignTime,系统会根据AutoScaleMode的设置不同,将一个参考值赋值给Form的AutoScaleDimensions属性。
例如:如果你选择AutoScaleMode为Font。DesignTime会自动为Form生成一行如下的代码来保存你设计时所使用的默认字体的宽和高:
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
Note: 当前系统为英文。因此,当前字体高度为13,宽度为6
在运行时,CurrentAutoScaleDimensions属性会提取当前系统的相关设置。生成另外一个计算值。AutoScaleFactor属性表现了两个值的比值,作为下一步缩放的缩放因子。
比如,在日文系统下运行时,CurrenAutoScaleDimensions为(6f, 12f)。AutoScaleFactor为(1f, 0.9xxxf).
System.Win.Forms.ContainerControl会在OnLayout(还包括一堆其它的事件中)中对调用自己的 PerformAutoScale方法,其中对自己和它子孙调用Scale方法,并将AutoScaleFactor传入。从而实现调整他们的位置和大小,以适应当前操作系统的设置。
3. 使用AutoScale时需要注意的问题?
上面的实现应该说比较巧妙,加上Visual Studio的设计时支持。应该说已经可以完美的解决所有通过DesignTime设计的窗体或自定义控件。
但是,微软没有为这套系统进行完整的支持。如果你通过代码在运行时修改控件的位置或大小时,可以说你没有任何一个理想的方案来确保你的修改能够适应各种不同的操作系统设置。因为......当ContainerControl触发了PerformAutoScale方法后,为了确保下一次触发 PerformAutoScale方法不会累加操作,而将AutoScaleDimensions修改为和 CurrentAutoScaleDimensions一致。也就是说,你再也无法拿到设计时所使用的AutoScaleDimensions值。因此,你也就无法使用这套机制来确保你的修改操作可以兼容各种操作系统的设置。
在我们的产品中就有很多问题是这一点引起的。毕竟,在大多数产品中,并不会仅仅通过DesignTime托托拽拽就可以完成需求。因此,我们需要:
3.1 尽量使用DesignTime设置Control的Size和Location
3.2 如果不能使用DesignTime,自己需要维护AutoScaleDimensions值
看起来,我们只能自己手动生成我们自己的AutoScaleDimensions值。VS会将那一行代码生成在 InitializeComponents方法内部,该函数推出的时候,PerformAutoScale已经被调用,因此标准的 AutoScaleDimensions属性已经不能使用了。这里需要Hard Code。 // TODO: 谁有更好的建议?
并且在下列情况中手动对Control的Location或Size进行计算。
- 修改Control的位置或大小时,手动计算正确的值。
- 添加新Control到Controls之前,手动计算正确的Size和Location.
我们需要使用前面我们自己维护的AutoScaleDimensions和当前容器的AutoScaleDimensions运算生成 AutoScaleFactor,然后自己计算新的位置和大小。这里,需要注意,不要参考Control的当前Size或Location。因为他们可能已经被Scale过一次了。
Note: 千万不要试图通过调用Control.Scale方法来完成计算。因为,ContainerControl的Scale方法会对所有子Control进行 Scale。但是,往往ContainerControl已经在它的构造函数中对它的子进行了Scale操作。结果相当于它的子进行了两次Scale。结果是错误的。
以前,我们的开发人员和测试人员有时会发现一些类似下图的异常。
这些异常的发生往往都是看起来很随机,很难重现的。调用堆栈往往是空的。并且,当程序脱离IDE运行时,都不会发生这些异常。因此,我们经常认为这些异常是IDE的Bug导致。很少在意。(至少我以前是这样)
今天详细的看了一下MSDN中关于MDA的介绍。发现这些问题往往都是很重要的和潜在的问题。比如:PInvokeStackImbalance这个MDA能够帮助我们发现PInvoke的声明有问题。StreamWriterBufferedDataLost这个MDA可以帮助我们发现我们忘记调用StreamWriter.Close()方法。
因为很多MDA异常的触发都是在GC回收时进行的,因此表现比较随机。较难重现。
如果你希望自己控制监视哪些MDA异常,可以使用IDE中的Debug->Exceptions->Managing Debugging Assistants来选择捕获哪些MDA异常。如下图:

据说
Asp.NET 2.0提供了用户管理的基本框架和实现。按照这个框架可以很容易的实现Web网站的用户管理功能。
呵呵,今天想学习一下。
遇到的第一个问题,就是如何建立Asp.NET用户管理数据库。好在微软提供了一个向导来帮助你完成这个工作。只需要在Visual Studio 2005 Command Line下输入“
aspnet_regsql”即可以启动向导。上面的链接中有比较细致的介绍。
可惜,我并不想在我的机器上安装Sql Server

.只想使用VS2005自带的Sql Express版。但是,上面的向导中并没有对Express版的支持。

郁闷
呵呵,微软还是做事很细致的,他为我么提供了命令行的方式。虽然,实现确实很隐晦。不管怎样,下面的代码可以成功的将数据库建立起来。
如果你还希望添加Session State的支持,只需要在后面加上“
-ssadd”就可以了。
如果你希望从数据库里面清理掉用户管理的支持,只需要将上文中的"
-A"改为"
-R"即可。
注意,这里区分大小写。当然移除Session State的指令是"
-ssremove"
关于更多的命令行参数,还是各位看官自己查MSDN或运行下面的命令行查看吧。
呵呵,对Cnblogs关注的时间很长了,也搭建了自己的技术博客。但是,平心而论,对博客园的贡献还是很小了。
关于博客园的商业化,谈点自己的想法:
首先我想问的是:
为什么博客园需要商业化?对于这个问题,我觉得可能也只能有两种解释。
a. 博客园的
生存受到了威胁,如果不进行商业化,可能就会死掉。
b. 有很多
好的想法,好的服务,因为资金的原因无法实现。
如果不是这两个原因中的一个,我觉得这个问题就不需要讨论了。:-)
我想,对于这两个问题的解决方案是完全不同的。
a. 经济问题影响了博客园的生存。我不知道Dudu老大是怎样维持博客园的正常运作的。仅仅靠首页上的广告吗?相信Dudu老大对博客园的贡献和他的辛苦是我们每个人都不能比拟的。相信相对于他的付出,他的收入是很微薄的。Dudu老大希望能够将他辛勤的结果转换为对自己相对好的的回报是很正常的想法。
如果博客园目前真的遇到了这个问题。我想,商业化中所最需要注意的问题是:“如何在商业化的过程中确保Dudu对博客园的掌握”。
因为,生存才是商业化的主要目的。我们不能也没有必要追求过高的回报率。只要能够维持正常的运作和生活就可以了。
而Dudu对博客园的完全控制,才能够保证将来进行第二轮商业化的过程时不会引入太多的复杂因素。
如果希望解决这个问题,我建议在博客园内部寻求资源。比如:提供一些收费的服务。或引入一些赞助机制等。因为我相信,博客园中的大多数人和Dudu的利益是一致的。而大多数企业和我们的利益并不总是一致。因此,我完全反对这个时候引入企业赞助。企业的赞助会影响博客园的中立性,会在将来影响博客园的发展或转型。
希望Dudu千万不要在商业化面前迷失了自己。
b. 没有足够的资源为大家提供更好的服务最近两天对于博客园商业化的讨论非常多,但是,在其中我没有看到我们需要提供哪些新的服务为博客园的作者和读者带来更多的价值的内容。
如果真的有这样的服务,我想,通过在这些增值服务上进行收费可能是一个解决经济问题的方案。同样,如果我们能够找到很好的服务。寻找VC也就不会很困难了。
哈哈,说了这么多,把自己想说什么都给忘了。简单来说:
1.
不要为了生存而放弃博客园的精神。什么是博客园的精神呢?我想:“分享、简单、纯洁”应该是我心目中的博客园的精神。
2.
寻找杀手服务,找到它,博客园就可以发展壮大。否则,我觉得不商业化也罢。
呵呵,我是一个作技术的人。想法简单。某些说法可能也有些尖刻。只是希望博客园能够越来越好。能够成为每一个在这里安家的人的永远的家园。:-)
昨日,与一同事一起在修一个多线程下使用我们的控件产品的Bug。现将相关的经验发布在这里。
1. 标准WinForm控件不支持多线程访问
这一点,其实是Windows的机制。.NET 中每一个Control其实都是一个Window,使用这些Window,原则上都应该在创建这个Window的线程中。否则,会产生异常。这一点,似乎Windows也没有强制约束。某些操作可能会扔异常,而有些情况下却不会。比如:访问这个Window的某些属性。
因此,.NET在Control上暴露了Invork方法,以实现将操作发送到Control所属的线程中执行。细节,可以参考我以前的一篇帖子。
这一点,已经是标准做法。所以不能称之为Bug。
现在我们来看一下出现了什么Bug。用户的应用程序中,某些时候需要启动一个新的线程,在这个线程中构造并显示一个Form,其中包含我们的控件。当用户启动后,发现我们的产品不能够正确地显示。
什么原因呢?
2. 静态成员是元凶
根据经验,这种情况下的问题一般都出在静态成员上。我们在开发中,经常为了优化性能,而将一些对象缓存在静态成员中。如果我们将一个包含Window的对象缓存在静态对象中,对它的调用就可能会产生异常。
大家都知道,静态成员是属于整个AppDomain的,也就是说,所有的线程在共享同一个静态对象。当另外一个线程调用静态对象上的方法时,根据前面的规则(WinForm控件不支持多线程访问),异常产生了。
呵呵,这一点,是我在以前修Bug中的经验。但是,昨天似乎并不灵验。整个出现异常的部分没有发现缓存静态的Window对象。
经验告诉我,肯定是静态成员惹得祸。先看出问题的代码,发现这里缓存了一个静态的Bitmap。
难道是,这个Bitmap不允许跨线程访问?对这个Bitmap对象进行了锁(Lock)操作后,发现问题解决。于是得出了下面的经验。
3. 某些GDI对象也不允许跨线程访问
GDI对象(包括GDI+对象)都是有Handle的。可能在某些情况下,微软也不保证跨线程访问的可靠性。从昨天的调试结果来看,这些对象应该是不允许多个线程并发访问。这一点并没有太多的跟踪和调试,如果你有兴趣,可以尝试的再跟一下。
通过对这些对象加锁,避免并发访问,似乎问题已经解决。但是因为这一部分被使用的非常频繁(否则我们也不加Cache了),加锁后,发现对控件的性能产生很大的影响。看来仅仅加锁是不能解决所有问题的。
于是,昨天从Winking那里学了一着。
4. System.ThreadStaticAttribute
这个属性标示一个静态对象在每个线程中是独立的。因此,我们只要在这些缓存字段上加上这个属性。哈哈,问题解决。喔,看起来解决的太轻松了。先别急,现实往往比想象中残酷。
5. 静态对象的初始化的问题
我们一般会使用两种方式初始化静态对象。静态构造函数 和 第一次访问时。(我们经常会在静态对象的声明后面直接进行初始化,这种情况其实也是通过静态构造函数进行初始化的。只是编译器帮助我们将这些代码移动到了静态构造函数里面。因此,这里不单独讨论。)
如果我们将静态字段标记为线程唯一的,静态构造函数就不能够正确地初始化这个字段了。因为,静态构造函数只被调用一次。(它没有办法标记为线程唯一的:))
于是,这里就需要使用延迟构建模式。我们需要将这些静态成员声明为属性,在其Get函数中判断对象是否为空(或默认值,值类型)。从而确定是否需要构造缓存对象。
6. 总结
呵呵,至此,所有的问题都解决了,Bug也已经修复。总体来说,静态对象始终是一个不安全因素。在目前我负责的项目中,我们是尽量避免使用静态成员。如果需要缓存对象,我们也会将这些缓存对象放在一个特定的池中。确保缓存对象是面向特定实例的而不是全局的。避免出现上面的问题。
相关文章:.NET下跨线程访问Control。
一般情况下,安装VS 2005时,安装程序会自动配置本地IIS服务使用ASP.NET 2.0。我们可以直接在VS 2005下编写或调试WEB应用程序。
但是,如果我们先安装了VS2005,然后才安装的IIS。这时,IIS会设置为使用ASP.NET 1.0。我们在VS2005中建立WEB程序是会出现下面的提示:
The site 'http://localhost/xxxxxx' has not been configured for use with ASP.NET 2.0. Microsoft Visual Studio has been designed for use with ASP.NET 2.0; if not configured some features may make incorrect assumptions, and pages designed with the tool may not render correctly.
并且不能自动配置成功。
这时,只需要在Visual Studio 2005 Command Prompt中执行下面的命令
aspnet_regiis -i -enable
今天发现微软已经将.NET Framework 3.0添加到Windows Update中了。但是至少目前还没有放在自动更新中。

在反编译微软的代码后,发现下面的代码:
1 internal static int GetCombinedHashCodes(params int[] args)
2 {
3 int num1 = -757577119;
4 for (int num2 = 0; num2 < args.Length; num2++)
5 {
6 num1 = (args[num2] ^ num1) * -1640531535;
7 }
8 return num1;
9 }
这个方法在System.Windows.Forms.WindowsFormsUtils中。
实在很想知道,其中的两个魔法数字有什么特别的,或者只是某人心情好?
因为工作需要,写了个简单的测试程序测试了一下List<T>, LinkedList<T>, Dictionary<TKey, TValue>, SortedList<TKey, TValue>的性能。
测试结果中有两点出乎意料:
1. LinkedList的内存占用很大,比List大了将近60%。
我没有细看为什么多出这么多内存占用。但是如果用我自己写的一个单链表,内存占用比List低。因为工作重点不在这里,我也不打算细究为什么了。但是感觉上,LinkedList的优势应该在小数据量,频繁添加删除的场景下。
2. SortedList的访问性能比Dictionary低。
一般认为向SortedList中添加节点的性能会比较低,因为它需要维护一个有序表,存在性能开销(当数据有序时,添加节点的开销大约是Dictionary的170%)。但是,事实上,对SortedList中节点的读写性能也要低于Dictionary(近7倍的开销)。这一点我觉得比较难以理解。
附件中是这次测试使用的程序,有兴趣的你,也许可以下载后试验一下,看看为什么是这样?
附件:
http://www.cnblogs.com/Files/Cajon/TestListPerformance.zip
powered by performancing firefox
看到的
EnterLib ObjectBuild vs Castle WindsorContainer, part 1中的评论,发表点自己的看法,同时测试一下博客园的TraceBack。
"解耦"应该是基于接口依赖而进行的,也就是说,所有的契约都在接口中定义了。耦合的双方都应该不知道对方是什么,只知道对方符合某个契约。
从这一点来说,配置文件和属性都可以实现解耦的目的。
另一方面来说,我们为什么要解耦?
一、降低复杂度
二、提高柔性
实现这两个目标,应该说封装变化是一个重要的途径。于是,我们需要将变化和复杂的耦合关系封装起来。从这一方面来说,配置文件应该说是优于属性的。因为配置文件将复杂度和变化全部集中在了一个点上(配置文件上)。因此最大化了解耦的价值。而属性,依然将复杂度分散在系统内部的很多点上,因此没有达到最大化解耦的价值。
一篇不错的文章,介绍了.NET开发中性能的方方面面。看起来也是翻译过来的,翻译的确实不太好 *_*! 但是值得学习和参考。
首页:
http://pingce.zhongsou.com/SoftChannel/72342380484755456/20030828/1724489.shtml关于JIT优化的章节,可以直接看其中的
第四页。其中介绍了JIT在什么时候会将一个函数内联编译,从而降低方法调用的开销。
powered by performancing firefox