Wednesday, April 14

.Framework上一个不明确的地方

在中文版.Framework SDK文档的里的"从对话框的父窗体检索信息"一节写的不清楚,MSDN联接在这里

原文如下:
根据对话框的用途,可能希望访问该对话框的父窗体提供的信息。对话框的初始化可能需要此信息,或者此信息可能涉及有关父窗体的应用程序状态的特定详细资料。
使用该对话框的 Form.ParentForm 属性访问父窗体的公共成员。应当将由 ParentForm 属性返回的引用显式转换成适当的类型。 下列代码演示如何使用 ParentForm 属性访问父窗体上的属性(在此示例中为 Text 属性):

' Visual Basic
Public Sub GetParentText()
Dim x as String
x = CType(Me.ParentForm, Form1).Text
End Sub

// C#
public void GetParentText()
{
string x = ((Form1)this.ParentForm).Text
}


可是所谓的父窗体指的是什么呢?
这里的ParentForm属性只有在MDI环境下的子窗口才有效,可见父窗体指的是MDI风格的父窗口。(在MSDN里这之前的章节里根本没有提到过MDI字样)
如果你是通过一个普通的ShowDialog(me)显出出来的窗口,那么千万不要被MSDN误导,这时候的父窗口需要通过Owner属性取得。我想Owner更常用。

读MSDN很有用。我知道了Close()并不会真正关闭窗口,只是Hide()而已。Dispose()才是生死判官。呵呵。

Monday, April 12

奇怪的C#语法:缺省参数
C#不支持缺省参数。只能通过重栽来实现缺省参数的需求。VB.NET和C++都支持。可是特性(attribute)缺支持缺省参数。
另一方面,属性property的set里面有缺省参数value。

难道不自相矛盾吗???
1,使用System.IO.Path.GetFullPath(.)或者System.IO.Path.GetFullPath("c:")这样的命令,每次运行可能得到不同的结果。特别是是使用VS.NET开发时。而且Framework1.0和1.1的运行结果也可能不一致。比如:GetFullPath("c:")「注意不是C:\」,在VS.NET 2002下经常会得到C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE这样的结果,但有时也会得到C:\。即使你将CurrentPath设成C:\也没有用。由于Path的IL内部使用经过优化或者是native的nGetFullPathHelper()函数取得FullPath,我无法分析具体的原因。MSDN上也没有给出足够的信息。

2,通过继承Control来定制自己的控件的时候,请注意属性初始化的位置。
方案(1) 变量声明既初始化
Private _path As String = System.IO.Directory.GetCurrentDirectory ''初始位置
Public Property Path() As String
Get
Return _path
End Get
Set(ByVal Value As String)
_path = System.IO.Path.GetFullPath(Value)
End Set
End Property

方案(2)在New里初始化
Private _path As String
Public Property Path() As String
Get
Return _path
End Get
Set(ByVal Value As String)
_path = System.IO.Path.GetFullPath(Value)
End Set
End Property

Public Sub New()
MyBase.New()

Me._path = System.IO.Directory.GetCurrentDirectory ''初始位置
End Sub

方案1,一般情况下不会出现问题。方案2,VS.NET的DesignMode会自动将你项目所在目录的写入Form的InitializeComponent()里。
如同,
.... ...
MyControl.Path = "C:\MyProject\TestControl"
.... ...
这样你的程序配布到其他机器上的时候,可能会出现目录不存在的错误。

以上代码为VB.NET。

Thursday, April 8

不管你是否相信,BUG永远与我们同在。包括使用JAVA的"勇气"与"机遇"号火星探测器。
今天遇到的问题是来自.NET Framework 1.0/1.1的FileStream。

现象:
当我将一些文本内容写到软盘里的一个文件的时候,出现了IOException,磁盘容量已满。我的catch部分捕获了这个异常,但是我无论如何无法关闭已经打开的文件。结果就是在软盘里生成了一个0字节的文件,在我的程序不完全关闭的情况下,我用文件管理器无法删除它。

原因:
我发现这个异常是由Close引起的,Close方法在内部调用了Flush方法将缓冲区里的内容写到软盘里去的时候,发生了这个异常。异常发生以后,FileStream没有关闭文件句柄(HANDLE),导致文件一直被占用。即使我试图用CloseHandle来关闭文件句柄也不成功,原因是取得Handle这个属性在内部会调用Flush方法,导致异常发生。

解决办法:
1,不用.NET Framework 1.0/1.1提供的FileStream,自己封装Win32 API (太不现实了吧:s)
2,在write之前,先调用SetLength。SetLength这个方法会改变stream的大小,你需要预先计算一下你准备写多少字节到文件里去。如果磁盘已满,在这个方法上就会发生异常,这时候内部缓冲区里还没有内容,所以Close会正确关闭。
3,Close之前清空内部缓冲区。FileStream的设计者为了保证数据都会被写入文件,在这个类的很多地方都调用Flush来将内部缓冲区的内容写到磁盘上,而没有给用户提供直接操作这一缓冲区的方法。
只好用一些黑客手段达到目的了。
...
catch( IOException excep )
{
// 清空内部缓冲区
Type streamType = stream.GetType();
System.Reflection.FieldInfo field;
// 得到private int _writePos 的FieldInfo
field = streamType.GetField( "_writePos",BindingFlags.NonPublic | BindingFlags.Instance );
// 设置缓冲区指针的位置为0
field.SetValue( stream, (int)0 );
}
finally
{
stream.Close(); // Close 成功。
}

总结:
方法3无法保证对.NET Framework 今后的版本也有效。方法2对于很多情况不是很有效。方法1需要高超的技术水平。方法0,期待MS改进它的Framework 。

参考:
1,http://mag.autumn.org/Content.aspx?id=20040202140342(日文)
2,http://www.gdncom.jp/general/mllog/tech/techDetail.aspx?ID=215(日文)