人生是一场不能存盘的RPG,我只能尽量多搞几个Screenshot

July 29, 2006

“是否同意”按钮的实现

Filed under: ASP.NET, Code snippets

[script type=”text/javascript”]
ar secs = 3;
var agree = document.getElementById(”agreeb”);
agree.disabled=true;
for(i=1;i<=secs;i++)
{
window.setTimeout(”update(” + i + “)”, i * 1000);
}
function update(num)
{
if(num == secs)
{
agree.value =” 我 同 意 “;
agree.disabled=false;
}
else
{
printnr = secs-num;
agree.value = “请认真查看<服务条款和声明> (” + printnr +” 秒后继续)”;
}
}
[script]

Win Form 的 Dock和Splitter

Filed under: Code snippets

如果在Form上放一个 Panel ,panel.Dock = Left
在放一个Splitter,Splitter的Dock缺省为Left, 不能为None,也不能为Fill.

注意此时:
this.panel1.Dock = System.Windows.Forms.DockStyle.Left;
this.panel1.Location = new System.Drawing.Point(0, 0);

this.splitter1.Location = new System.Drawing.Point(200, 0);

this.Controls.Add(this.splitter1);
this.Controls.Add(this.panel1);

这时的拖动Splitter, Panel会随之变化.

如果把
this.Controls.Add(this.splitter1);
this.Controls.Add(this.panel1);
的顺序换一下,打开Designer就会看到,splitter就会被放在form的最左边,
运行时splitter也会被放在form的最左边.
此时代码尚无任何改变,把form的size改一下,导致designer产生代码,就会看到:
this.splitter1.Location = new System.Drawing.Point(0, 0);
this.panel1.Location = new System.Drawing.Point(3, 0);

!–可见对于使用了dock的control,他们的location实际是由deisnger,或在
运行时算出来的,指定的值并无效果.
对于指定了相同dock的多个control,比如panel1和splitter,都要dock到Left,最左边的
Control必须最后加到form.Controls中

大多情况下,form上splitter的右边还会有一个dock属性为fill的panel,
正常的顺序是先添加panel_left,再添加splitter,再添加panel_right,
此时生成的代码顺序是:

this.panel_Right.Location = new System.Drawing.Point(272, 0); //Note

this.Controls.Add(this.panel_Right);
this.Controls.Add(this.splitter1);
this.Controls.Add(this.panel_Left);

如果把代码调整为:
this.Controls.Add(this.splitter1);
this.Controls.Add(this.panel_Left);
this.Controls.Add(this.panel_Right);

panel_right就会fill到整个form,而不是splitter右边的区域:
this.panel_Right.Location = new System.Drawing.Point(0, 0); //Note
this.panel_Right.Size = new System.Drawing.Size(640, 533);

!–可见,fill的control要最先加到容器中.
查看这个问题有个好办法,把一个button放到panel_right的左上角,如果button的location不接近(0,0),
就说明有问题.

再进一步,在Panel_Right上加三个panel:
Top(Dock=Top), Center(Dock=Fill), Bottom(Dock=Bottom)
添加的顺序为Top, Botton, Center,
生成的代码顺序为:
this.panel_Right.Controls.Add(this.panel_Center);
this.panel_Right.Controls.Add(this.panel_Bottom);
this.panel_Right.Controls.Add(this.panel_Top);

现在,我已经知道这个顺序的奥妙了,我不会再尝试改变这个顺序.

July 27, 2006

Windows Forms 程序中的多线程

Filed under: .NET

参考
Safe, Simple Multithreading in Windows Forms, Part 1, 2,3
http://msdn.microsoft.com/library/en-us/dnforms/html/winforms06112002.asp
http://msdn.microsoft.com/library/en-us/dnforms/html/winforms08162002.asp
http://msdn.microsoft.com/library/en-us/dnforms/html/winforms01232003.asp

Safe, Even Simpler Multithreading in Windows Forms 2.0
http://www.mikedub.net/mikeDubSamples/SafeReallySimpleMultithreadingInWindowsForms20/SafeReallySimpleMultithreadingInWindowsForms20.htm

翻译+篡改 by RivenHuang 2006/07/27

关键字:
UI线程 工作线程 线程同步 线程通信 竞争 死锁 boxing 异步调用web service

设想实现以下的case:
一个win form application, 用来计算任意长度的pi值, from上的progress
可以显示计算的进度, from上的cancle button 可以终止计算, 计算完成后form可以得到通知.

正如作者所言,”It all started innocently enough.”

Design 1—————————————————–
button click 调用函数CalcPi(int digits), CalcPi中在一个循环中干活,每计算一次更新一下UI,
包括计算结果, progress bar:

private void button_Calc_Click(object sender, System.EventArgs e)
{
this.CalcPi((int)this.numericUpDown_Digits.Value);
}

void ShowProgress(string pi, int totalDigits, int digitsSoFar)
{
this.textBox_Result.Text = pi;
this.progressBar_Calc.Maximum = totalDigits;
this.progressBar_Calc.Value = digitsSoFar;
}

void CalcPi(int digits)
{
StringBuilder pi = new StringBuilder(”3″, digits + 2);

// Show progress
ShowProgress(pi.ToString(), digits, 0);

if( digits > 0 )
{
pi.Append(”.”);

for( int i = 0; i < digits; i += 9 )
{
int nineDigits = NineDigitsOfPi.StartingAt(i+1);
int digitCount = Math.Min(digits - i, 9);
string ds = string.Format(”{0:D9}”, nineDigits);
pi.Append(ds.Substring(0, digitCount));

// Show progress
ShowProgress(pi.ToString(), digits, i + digitCount);
}
}
}

看上去很美,尝试一下CalcPi(1000),此时,progress bar在努力地前进着,而textbox好像在偷懒,
text区域没有任何输出,但它的滚动条上的thumb又在变化(出现,变短), 然后切换到别的程序,
再切回来,form会失去响应,白屏了.如果这是一个我自己玩的程序,我会容忍,因为我知道它在干活,
如果是工作中的程序,就有必要解决一下这个问题.

分析一下:
此时的程序是一个单线程程序(相信你攒的大多数程序都是这样),在CalcPi()中,试图调用ShowProgress
来设置textBox_Result和progressBar_Calc的值来立刻重画以显示当前的工作成绩,(progress的表现要比
textbox的表现好一些,why?我也不知道),在把form放到后台,再放到前台后,form的Paint event会被触发
(其实就是windows的WM_PAINT消息,对于windows程序来说,这是个很有些门道的message,详情见
Programming windows 5e ch5 图形基础),但实际上此时程序正在执行CalcPi(),Paint 的事件处理函数只好
排队.

要解决这个问题肯定要使用多线程, 正解是另开一个工作线程来干活,并和UI通信,报告当前的进度,但我在
我们的代码中见过另类的做法: 在UI线程中干活,再开一个线程来刷新UI,也能干活,但我担心在某些情况下
会出现一些微妙的bug.

Desing 2———————————————–

在button click中另开线程.注意ThreadStart含参数,所以要把CalcPi(digits)包装一下

private void button_Calc_Click(object sender, System.EventArgs e)
{
Thread piThread = new Thread(new ThreadStart(CalcPiThreadStart));
piThread.Start();
}

void CalcPiThreadStart()
{
CalcPi((int)this.numericUpDown_Digits.Value);
}

run, 看上去更美,唯一的缺憾就是CalcPiThreadStart这个没用的东西非常碍眼.
使用异步的delegate可以弥补这个缺憾,注意,此时的工作线程是线程池中的线程.

Desing 3———————————————–

private void button_Calc_Click(object sender, System.EventArgs e)
{
CalcPiDelegate calcPi = new CalcPiDelegate(CalcPi);
//–忆苦代码:-)
//calcPi((int)numericUpDown_Digits.Value);
calcPi.BeginInvoke((int)numericUpDown_Digits.Value, null, null);
}

有关 delegate, Chris Sells推荐了.NET Delegates: A C# Bedtime Story一文,
在Framework design guideline一书中,有 Async Pattern可供参考.

真的天下太平了吗?Windows世界中有这样一条警句:
“Though shalt not operate on a window from other than its creating thread”,

回头看看已有的代码,X! 不就在说我吗?当然目前好像没什么错误,但是在”某些情况下
会出现一些微妙的bug.”
为了安全,在ShowProgress中添加

void ShowProgress(string pi, int totalDigits, int digitsSoFar)
{
// Make sure we’re on the right thread
Debug.Assert(this.InvokeRequired == false);

}
红叉子马上就来了.

有矛就有盾,.NET中所有从System.Windows.Forms.Control派生的class,当然包含
System.Windows.Forms.Form, 都有一个InvokeRequired属性,用以返回是否需要使用一些
Control上的方法把对control的操作传递给create control的线程.这个property可以在
任何一个线程中访问,

.NET的Control上只有Invoke, BeginInvoke, EndInvoke, GreateGraphics这4个方法可以从任何线程
调用,其他的调用, 必须使用Invoke方法来传递.

目前问题的解决方案就是使用一个delegate来把对UI control的操作通过 Control上的Invoke
方法传递过去,当然还需要使用异步Invoke,否则工作线程也会被block.本例中由于工作线程只为
UI thread一人服务,使用Invoke不会有问题.

Design4 ——————–
void CalcPi(int digits)
{
StringBuilder pi = new StringBuilder(”3″, digits + 2);

// Get ready to show progress asynchronously
ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);

// Show progress
this.Invoke(showProgress, new object[] { pi.ToString(), digits, 0});

if( digits > 0 )
{
pi.Append(”.”);

for( int i = 0; i < digits; i += 9 )
{
int nineDigits = NineDigitsOfPi.StartingAt(i+1);
int digitCount = Math.Min(digits - i, 9);
string ds = string.Format(”{0:D9}”, nineDigits);
pi.Append(ds.Substring(0, digitCount));

// Show progress
this.Invoke(showProgress,new object[] { pi.ToString(), digits, i + digitCount});
}
}
}

在Design4中出现了两次this.Invoke, 重构, 把代码移动到ShowProgress中

Desing5———————————-
void ShowProgress(string pi, int totalDigits, int digitsSoFar)
{
System.Diagnostics.Debug.Assert(this.InvokeRequired == false);

if( this.InvokeRequired == false )
{
this.textBox_Result.Text = pi;
this.progressBar_Calc.Maximum = totalDigits;
this.progressBar_Calc.Value = digitsSoFar;
}
else
{
// Show progress asynchronously
ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);
this.Invoke(showProgress, new object[] { pi, totalDigits, digitsSoFar});
}
}

void CalcPi(int digits)
{
StringBuilder pi = new StringBuilder(”3″, digits + 2);

ShowProgress(pi.ToString(), digits, 0);

if( digits > 0 )
{
pi.Append(”.”);

for( int i = 0; i < digits; i += 9 )
{
int nineDigits = NineDigitsOfPi.StartingAt(i+1);
int digitCount = Math.Min(digits - i, 9);
string ds = string.Format(”{0:D9}”, nineDigits);
pi.Append(ds.Substring(0, digitCount));

// Show progress
ShowProgress(pi.ToString(), digits, 0);
}
}
}

现在可以考虑cancel 功能的实现了
1. UI上的对应: 在开始计算后, button上的”Calc”要变为”Cancel”
还可以弹出一个带progress bar和cancel button的 dialog,

2.通常会使用一个变量来标示是否操作被取消,在从UI线程得知工作线程线程应该停止
(cancel button click被执行),到工作线程自己知道将被停止,并停止发送进度之间的这
一小段时间内,应该禁用 UI。
否则,用户在第一个工作线程停后又开始别的工作, UI就线程必须判断是从新的工作线程获
取进度还是从即将关闭的旧线程获取进度。这就需要工作线程能够通知外界是否它已经停止.

Design 6

enum CalcState
{
Pending, // No calculation running or canceling
Calculating, // Calculation in progress
Canceled, // Calculation canceled in UI but not worker
}

CalcState _state = CalcState.Pending;

在本例中由ShowProgress来设置_state变量,以通知工作线程停止
并重新设置UI, 使button enable.

delegate void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar);

void ShowProgress(string pi, int totalDigits, int digitsSoFar)
{
lock(this._stateLock)
{
if( _state == CalcState.Canceled )
{
_state = CalcState.Pending;
}
}

// Make sure we’re on the right thread
if( this.InvokeRequired == false )
{
this.textBox_Result.Text = pi;
this.progressBar_Calc.Maximum = totalDigits;
this.progressBar_Calc.Value = digitsSoFar;

// Check for completion
if( _state == CalcState.Pending || (digitsSoFar == totalDigits) )
{
_state = CalcState.Pending;
this.button_Calc.Text = “Calc”;
this.button_Calc.Enabled = true;
}
}
// Transfer control to correct thread
else
{
ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);

// Show progress synchronously (so we can check for cancel)
Invoke(showProgress, new object[] { pi, totalDigits, digitsSoFar});
}
}

void CalcPi(int digits)
{
StringBuilder pi = new StringBuilder(”3″, digits + 2);

// Show progress (ignoring Cancel so soon)
ShowProgress(pi.ToString(), digits, 0);

if( digits > 0 )
{
pi.Append(”.”);

for( int i = 0; i < digits; i += 9 )
{
int nineDigits = NineDigitsOfPi.StartingAt(i+1);
int digitCount = Math.Min(digits - i, 9);
string ds = string.Format(”{0:D9}”, nineDigits);
pi.Append(ds.Substring(0, digitCount));

// Show progress (checking for Cancel)
ShowProgress(pi.ToString(), digits, i + digitCount);
if( this._state == CalcState.Canceled ) break;
}
}
}

此时,工作线程和UI线程通过一个内部变量_state来记录工作状态,为了避免竞争,需要加上一个监视锁,
在.NET中为了共享对象提供了 Monitor 类,其作用类似于为数据加了一把锁.
Design 6.1

object _stateLock = new object();

void ShowProgress(string pi, int totalDigits, int digitsSoFar, out bool cancel)
{
lock( _stateLock )
{ // 监视锁
if( _state == CalcState.Cancel )
{
_state = CalcState.Pending;
cancel = true;
}
}

}

但是这样的做法又有可能引起死锁,需要强调的是:
“通过共享数据进行的多线程编程很难做到十全十美”.

可通过在线程之间传递数据的副本来避免使用共享数据,只有在数据很大时才考虑使用共享数据.

此处,只让UI线程(在UI线程中执行的ShowProgress)来检查_state,并返回给工作线程一个状态值表示是否取消计算.
但返回值通常用来表示操作是否正常进行,所以使用一个out参数来传递信息.

注意1:
此时工作线程通过调用ShowProgress来查看操作是否已被取消,所以不能使用Control.BeginInvoke来执行ShowProgress,
使用Control.BeginInvoke又会需要同步. 有一个卖糕的.所以还是使用Invoke

注意2:
不能直接向 Control.Invoke 简单传递bool变量来获得 cancel 参数!
因为 bool 是valut type,而 Invoke 采用object array 作为参数,其结果是作为对象传递的 bool
将被boxing而保持实际的 bool , 为此必须使用自己的对象变量 (inoutCancel) 传递它,
在同步调用 Invoke 后,我们将 object cast为 bool 以查看是否应该取消操作。

!任何时候调用带有 out 或 ref 参数的 Control.Invoke或 Control.BeginInvoke时,都必须注意
值类型和引用类型数据之间的区别。

Desing 7———————————–

void CalcPi(int digits)
{
bool cancel = false;
StringBuilder pi = new StringBuilder(”3″, digits + 2);

// Show progress (ignoring Cancel so soon)
ShowProgress(pi.ToString(), digits, 0, out cancel);

if( digits > 0 )
{
pi.Append(”.”);

for( int i = 0; i < digits; i += 9 )
{
int nineDigits = NineDigitsOfPi.StartingAt(i+1);
int digitCount = Math.Min(digits - i, 9);
string ds = string.Format(”{0:D9}”, nineDigits);
pi.Append(ds.Substring(0, digitCount));

// Show progress (checking for Cancel)
ShowProgress(pi.ToString(), digits, i + digitCount,out cancel);
if( cancel ) break;
}
}
}

delegate void ShowProgressDelegate(string pi, int totalDigits, int digitsSoFar,out bool cancel);

void ShowProgress(string pi, int totalDigits, int digitsSoFar,out bool cancel)
{
// Make sure we’re on the right thread
if( this.InvokeRequired == false )
{
this.textBox_Result.Text = pi;
this.progressBar_Calc.Maximum = totalDigits;
this.progressBar_Calc.Value = digitsSoFar;
// Check for Cancel
cancel = (_state == CalcState.Canceled);

// Check for completion
if( cancel || (digitsSoFar == totalDigits) )
{
_state = CalcState.Pending;
this.button_Calc.Text = “Calc”;
this.button_Calc.Enabled = true;
}
}
// Transfer control to correct thread
else
{
ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);

// Avoid boxing and losing our return value
object inoutCancel = false; // Avoid boxing and losing our return value

// Show progress synchronously (so we can check for cancel)
Invoke(showProgress, new object[] { pi, totalDigits, digitsSoFar, inoutCancel});
cancel = (bool)inoutCancel;
}
}

一路挣扎到这,我已经满头大汗,可故事还没有结束, 代码还有可优化的余地.
作者称之为 message passing model:
工作线程create一个message, 交给UI线程处理,然后检查UI线程的处理结果,整个处理过程很安全,
不存在多线程之间纠缠不清的问题,而且具有很好的可扩充性,又什么要交互的信息,就加到
ShowProgressArgs中

Desing8————————————————-

void CalcPi(int digits)
{
StringBuilder pi = new StringBuilder(”3″, digits + 2);

// Show progress (ignoring Cancel so soon)
object sender = System.Threading.Thread.CurrentThread;
ShowProgressArgs e = new ShowProgressArgs(pi.ToString(), digits, 0);

ShowProgress(sender, e);

if( digits > 0 )
{
pi.Append(”.”);

for( int i = 0; i < digits; i += 9 )
{
int nineDigits = NineDigitsOfPi.StartingAt(i+1);
int digitCount = Math.Min(digits - i, 9);
string ds = string.Format(”{0:D9}”, nineDigits);
pi.Append(ds.Substring(0, digitCount));

// Show progress (checking for Cancel)
e.Pi = pi.ToString();
e.DigitsSoFar = i + digitCount;
ShowProgress(sender, e);
if( e.Cancel ) break;
}
}
}

class ShowProgressArgs : EventArgs
{
public string Pi;
public int TotalDigits;
public int DigitsSoFar;
public bool Cancel;

public ShowProgressArgs(string pi, int totalDigits, int digitsSoFar)
{
this.Pi = pi;
this.TotalDigits = totalDigits;
this.DigitsSoFar = digitsSoFar;
}
}

delegate void ShowProgressHandler(object sender, ShowProgressArgs e);

void ShowProgress(object sender, ShowProgressArgs e)
{
// Make sure we’re on the right thread
if( this.InvokeRequired == false )
{
this.textBox_Result.Text = e.Pi;
this.progressBar_Calc.Maximum = e.TotalDigits;
this.progressBar_Calc.Value = e.DigitsSoFar;
// Check for Cancel
e.Cancel = (_state == CalcState.Canceled);

// Check for completion
if( e.Cancel || (e.DigitsSoFar == e.TotalDigits) )
{
_state = CalcState.Pending;
this.button_Calc.Text = “Calc”;
this.button_Calc.Enabled = true;
}
}
// Transfer control to correct thread
else
{
ShowProgressHandler showProgress = new ShowProgressHandler(ShowProgress);
Invoke(showProgress, new object[] { sender, e});
}
}

最后,来看看最实用的case: Asynchronous Web Services,代码堪称典范,以致可以copy-paste:

CalcState state = CalcState.Pending;
localhost.CalcPiServiceProxy service = new localhost.CalcPiServiceProxy();

void calcButton_Click(object sender, System.EventArgs e)
{
switch( state )
{
case CalcState.Pending:
state = CalcState.Calculating;
calcButton.Text = “Cancel”;

// Start web service request
service.BeginCalcPi((int)digitsUpDown.Value, new AsyncCallback(PiCalculated), null);
break;

case CalcState.Calculating:
state = CalcState.Canceled;
calcButton.Enabled = false;
service.Abort(); // Fail all outstanding requests
break;

case CalcState.Canceled:
Debug.Assert(false);
break;
}
}

void PiCalculated(IAsyncResult res)
{
try
{
ShowPi(service.EndCalcPi(res));
}
catch( WebException ex )
{
//maybe time-out
ShowPi(ex.Message);
}
}

delegate void ShowPiDelegate(string pi);

void ShowPi(string pi)
{
if( this.InvokeRequired == false )
{
piTextBox.Text = pi;
state = CalcState.Pending;
calcButton.Text = “Calc”;
calcButton.Enabled = true;
}
else
{
ShowPiDelegate showPi = new ShowPiDelegate(ShowPi);
this.BeginInvoke(showPi, new object[] {pi});
}
}

在.NET2.0中,MS派来了弥赛亚 BackgroudWorker 来救助为了UI,耗时计算而抓破头皮的程序员.
ToolBox->Components->BackgroundWorker, drag it to the form.
>处理BackgroundWorker仅有的3个event:
DoWork
ProgressChanged
RunWorkerCompleted

>设置
WorkerReportsProgress = true; //实现进度条
WorkerSupportsCancellation = true; //实现cancel

想干活,请调用:BackgroundWorker的 RunWorkerAsync 方法, 搞定!

//———————————
private void button_Calc_Click(object sender, EventArgs e)
{
if (this.button_Calc.Text == “Cancel”)
{
this.backgroundWorker_Calc.CancelAsync();
return;
}

this.button_Calc.Text = “Cancel”;
this.backgroundWorker_Calc.RunWorkerAsync(this.numericUpDown_Digits.Value);
this.progressBar_Calc.Maximum = Convert.ToInt32(this.numericUpDown_Digits.Value);
}

// This method will run on a thread other than the UI thread.
// Be sure not to manipulate any Windows Forms controls created
// on the UI thread from this method.
private void backgroundWorker_Calc_DoWork(object sender, DoWorkEventArgs e)
{
int digits = int.Parse(e.Argument.ToString()); // <= RunWorkerAsync(this.numericUpDown_Digits.Value);
StringBuilder pi = new StringBuilder(”3″, digits + 2);
CalcPiUserState userState = new CalcPiUserState(pi.ToString(), digits, 0);
this.backgroundWorker_Calc.ReportProgress(0, userState);

// Calculate rest of pi, if required
if (digits > 0)
{
pi.Append(”.”);

for (int i = 0; i < digits; i += 9)
{

// Calculate next i decimal places
int nineDigits = NineDigitsOfPi.StartingAt(i + 1);
int digitCount = Math.Min(digits - i, 9);
string ds = string.Format(”{0:D9}”, nineDigits);
pi.Append(ds.Substring(0, digitCount));

// Show current progress
userState.Pi = pi.ToString();
userState.DigitsSoFar = i + digitCount;
this.backgroundWorker_Calc.ReportProgress(0, userState);

// Check for cancellation
if (this.backgroundWorker_Calc.CancellationPending)
{
// Need to set Cancel if you need to distinguish how a worker thread completed
// ie by checking RunWorkerCompletedEventArgs.Cancelled
e.Cancel = true;
break;
}
}
}

e.Result = “Finished Normally.”;
}

private void backgroundWorker_Calc_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
CalcPiUserState userToken = (CalcPiUserState)e.UserState;
this.progressBar_Calc.Value = userToken.DigitsSoFar;
this.textBox_Result.Text = userToken.Pi;
}

private void backgroundWorker_Calc_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.button_Calc.Text = “Calc”;
this.textBox_Result.Text = “”;
this.progressBar_Calc.Value = 0;

if (!e.Cancelled)
{
this.Text = e.Result.ToString();
}
}
}

July 26, 2006

看 Developing ASP.NET 2.0 Applications using Atlas

Filed under: ASP.NET

Video - Developing ASP.NET 2.0 Applications using “Atlas”
by Scott Guthrie, General Manager, .NET Development Platform

http://download.microsoft.com/download/8/5/8/85803fdd-fe9a-4783-ab37-e0c565172ffd/asp_net_atlas.wmv

讲座共18分26秒.
在12分44秒时开始涉及atlas.
在此之前,Scott搞了个page实现了以下的功能:
1.一个table adapter来充当数据访问层.用一个objectdatasource来进行数据绑定.

2.一个gridview, 显示数据,并可以edit, sort, paging
设置 gridview 的 CssClass属性和AlternatingRowStyle-CssClass,很直接.

3. 用一个dropdown list为驱动gridview的显示.

4.一个detailview, default mode设为inster, 在同一页面上实现inster

显然,此时的种种操作会引发页面提交和重画,用Atlas正好对症.

进入正题:
引用Microsoft.web.Atlas.dll
在 asp:content 内添加:
<atlas: ScriptManager ID=”s1″ EnablePartialRendering =”true” runat=”server” />

再添加
<atlas:UpdatePanel ID=”p1″ runat=”server”>
<ContentTemplate>

</ContentTemplate>

<Triggers>
<atals:ControlValue ControlID=”DropDownList1″ PropertyName=”SelectedValue”/>
</Triggers>
</atlas:UpdatePanel>

将gridview移至其中. 实现了gridview的局部重画.

————–
再添加
<atlas:UpdatePanel ID=”p2″ Mode=”Condition” runat=”server”>
<ContentTemplate>
将detail view置入其中,使detail view也实现局部重画
</ContentTemplate>
</atlas:UpdatePanel>
有关Condition见:
http://atlas.asp.net/docs/Server/Microsoft.Web.UI/T_UpdatePanelMode.aspx
If the Mode property is set to Always, the panel is updated as part of each postback.
If the Mode property is set to Conditional, the panel is updated only when a client
trigger defined in the UpdatePanel.Triggers property is raised, or when you call the
UpdatePanel.Update method during a postback.

注意 detailview的操作也可引发gridview的重画.

—————
下面是一个很花的功能
在页面上添加:
<atlas:UpdateProgress ID=”progress” runat=”server”>
<ProgressTemplate>
拖个图片过来,
</ProgressTemplate>
</atlas:UpdateProgress>
这就是一个进度条!
为了充分演示此功能,
在objectDataSource的Updating时间中sleep一下,

–The End

Assembly执行路径

Filed under: Code snippets

//————
string assemblyLocation = Assembly.GetExecutingAssembly().Location;

//————
assemblyLocation = Assembly.GetCallingAssembly().Location;

//————
string exeFolder = Path.GetDirectoryName(Application.ExecutablePath);

ADO.NET 中的抽象工厂

Filed under: Design Pattern

用Interface或抽象类来生成一组相关的对象.
以ADO.Net2.0为例
见(Abstract Factory Design Pattern in ADO.NET 2.0 by Muhammad Mosa)
ado.net 定义了一组抽象的class: DbConnection, DbCommand, DbParameter等,
每个抽象class都有几个对应的实现: SqlConnection, OralceConnection.

ado.net 2.0中的DbProviderFactory class充当了abstract factory的角色,
DbProviderFactory中定义了一系列careateXXX函数来生成connection, command等class.
从它派生的SqlClientFactory, OracleClientFactory 等重载了这些createXXX方法,
可以生成具体的connection, command.

优点在于
1. 隔离具体的class
2. 可以任意替换一系列的class
3. 强制使用统一系列的class

缺点:
AbstractFactory interface 定义了需要产生的class的规格,如果需要增加一个新的系列,
需要从头到尾搞一套Factory class.

如果AbstractFactory 定义一个CreateProduct()方法,使用.net的reflection来生成对象,就
可以省略Concrate Factory的定义.

正确使用DataSet和DataReader

Filed under: SQL&DB Accessing

By Riven Huang 2006.07.26
参照
Why I Don’t Use DataSets in My ASP.NET Applications(by Scott Michell)
http://aspnet.4guysfromrolla.com/articles/050405-1.aspx

Performance Comparison: Data Access Techniques
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetarch031.asp

首先需要理解DataSet 和 DataReader的设计目标:
DataSet可以理解成一个小型的,存在于内存中的数据库,包含多个data table,table之间
可存在约束关系.
DataSet和数据库无关,由DataAdapter来负责对数据库的处理,一旦数据填充结束,就和数据库
断开连接.
DataSet对XML的支持比较好.

DataReader可以理解成程序和数据库之间的桥梁.只能顺序的,从数据库中读取记录.
DataReader是和数据库相关的,所以存在sql, ole等多个版本的DataReader.

从使用上来看,

使用DataReader需要以下步骤:
// 1. 建立连接
SqlConnection myConnection = new SqlConnection(conn);

// 2. 执行查询
SqlCommand myCommand = new SqlCommand(myConnection, sqlText);

SqlReader myReader = myCommand.ExecuteReader();

// 3. Read
while(myReader.Read())
{

}

// 4. Close connection
myConnection.Close();

使用DataSet需要以下步骤:
// 1. 建立连接
SqlConnection myConnection = new SqlConnection(conn);

// 2. 生成command 和 adapter
SqlCommand myCommand = new SqlCommand(myConnection, sqlText);
SqlDataAdapter myAdapter = new SqlDataAdapter(myCommand);

// 3. 生成dataset并填充
DataSet myDataSet = new DataSet();
myAdapter.Fill(myDataSet);

// 4. Close connection
myConnection.Close();

如果使用UIControlo显示数据,对于dataset和datareader的操作是相同的:
把dataset或datareader赋给control的DataSource属性,
再调用control的DataBind()方法.

比较:
DataSet的性能比DataReader差很对,同时占用大量的内存.

何时使用DataSet:
1.数据传输.
2.桌面应用.

July 25, 2006

DataTable在.net2.0中的增强

Filed under: SQL&DB Accessing

–dataTable可以独立于dataset而存在,
–dataTable可从datareader中读取数据:

using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Create a Command
using (SqlCommand command = new SqlCommand(Sql, connection))
{
// Call ExecuteReader to return a DataReader
using (SqlDataReader reader = command.ExecuteReader())
{
// Create a DataTable
DataTable table = new DataTable();
// Fill DataTable
table.Load(reader, LoadOption.OverwriteChanges);
// Display data in GridView
dataGridView1.DataSource = table;
}
}
}
–也可用Adapter填充

SqlDataAdapter adapter = new SqlDataAdapter(Sql, connection);
DataTable table = new DataTable(”Employees”);
adapter.Fill(table);

–用DataTableReaderCreate(it is disconnected)从datatable中读取数据:
DataTableReader dtReader = table.CreateDataReader();
while (dtReader.Read())
{
str = dtReader.GetValue(0).ToString();
}

如果
DataTableReader dtReader = dataset.CreateDataReader();
dtRader会读取DataSet中所有DataTable的数据.

–可以merge多个data table

dtTable1.Merge(dtTable2);

–Serialize的支持,从而可以在web method中传递DataTable
DataTable table = new DataTable();

table.Load(reader, LoadOption.OverwriteChanges);
// 设置序列化的格式,default为SerializationFormat.Xml
table.RemotingFormat = SerializationFormat.Binary;

BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream(”Data.txt”, FileMode.OpenOrCreate);
bf.Serialize(fs, table);

读Martin Fowler的Inversion of Control Containers and the Dependency Injection pattern

Filed under: Design Pattern

出处: http://www.martinfowler.com/articles/injection.html

—by Riven Huang 2007.04.25

假设有这样一个case, 需要列出某个导演的作品, 为此需要以下的class:

namespace RivenStudy
{
public class MovieLister
{
private IMovieFinder finder;

public Movie[] MoviesDirectedBy(String arg)
{
List<Movie> allMovies = finder.FindAll();
foreach(Movie movie in allMovies )
{
if (!movie.Director.Equals(arg))
allMovies.Remove(movie);
}
return (Movie[])allMovies.ToArray();

}
}
}
为了让MoiveLister不关系数据的存储方式, 引入了一个finder Object,
MovieLister会依赖于finder Object的FindAll() 方法,为此我们定义了
如下的接口:

public interface IMovieFinder
{
List<Movie> FindAll();
}

在真正的操作中,MovieLister会用到一个具体的MovieFinder比如ColonDelimitedMovieFinder,
此movie finder会从一个text文件中读取影片的列表.

在这种情况下,我们的代码会写做:

public class MovieLister
{
private IMovieFinder finder;

public MovieLister()
{
finder = new ColonDelimitedMovieFinder(”movies1.txt”);
}
public Movie[] MoviesDirectedBy(String arg)
{
List<Movie> allMovies = finder.FindAll();
foreach(Movie movie in allMovies )
{
if (!movie.Director.Equals(arg))
allMovies.Remove(movie);
}
return (Movie[])allMovies.ToArray();

}
}

我们希望MovieLister 类能够与MovieFinder的 任何 实现类协同工作,并且允许在运行期插入具体的实现类,
插入动作完全脱离原作者的控制。在Patterns of Enterprise Application Architecture
(http://www.martinfowler.com/books.html#eaa)一书中,作者称此模式为PlugIn

而此刻设计中,MovieLister 类它直接实例化IMovieFinder的具体类。这样一来,
MovieLister既依赖于IMovieFinder接口,也依赖于实现IMovieFinderde类。
IMovieFinder 也就不成其为一个插件了,因为它并不是在运行期插入应用程序中的。

通过Interface对用到的组件加以抽象,并通过Interface与具体的组件通信,(如果组件并没有设计一个接口,
也可以通过适配器与之交流),同时我们希望以PlugIn的方式以多种方式部署这个系统,所以,现在的核心问题就是:
如何将这些插件组合成一个应用程序?
这正是(lightweight containers)轻量级容器所面临的问题,解决这个问题的手段就是是控制反转(Inversion of Control)

在前面的例子中,需要翻转(Inversion)的是应用程序对插件的依赖,达到这个目的,可以使用
Dependency Injection 模式并不是唯一的选择,你也可以用ServiceLocator 模式获得同样的效果.

Dependency Injection 模式的基本思想是:
用一个单独的对象(assembler)来获得IMovieFinder的一个具体实现,并将其实例赋给MovieLister的一个字段。

Dependency Injection的三种形式:
1. Constructor Injection
2. Setter Injection
3. Interface Injection

依赖注入的最大好处在于:它消除了MovieLister类对具体MovieFinder实现类的依赖。把MovieLister 类交给辅助类,
让他们根据实际环境插入(构造)一个合适的IMovieFinder实现

* 使用PicoContainer (http://www.picocontainer.org/)进行构造子注入

PicoContainer 通过一个构造器来判断如何将IMovieFinder的实例注入MovieLister:

class MovieLister…
{
public MovieLister(IMovieFinder finder)
{
this.finder = finder;
}
}

IMovieFinder 实例也由PicoContainer来管理
class ColonMovieFinder
{
private string fileName;
public ColonMovieFinder(String fileName)
{
this.fileName = fileName;
}
}

随后,在一个单独的class中配置PicoContainer,说明各个接口分别与哪个实现类关联,将哪个字符串注入MovieFinder组件。

private MutablePicoContainer configureContainer()
{
MutablePicoContainer pico = new DefaultPicoContainer();
Parameter[] finderParams = {new ConstantParameter(”movies1.txt”)};
pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
pico.registerComponentImplementation(MovieLister.class);
return pico;
}

* 使用Spring 进行设值方法注入
为了让MovieLister 类接受注入, 需要为它定义一个set方法,
class MovieLister
{
private MovieFinder finder;
public void setFinder(MovieFinder finder)
{
this.finder = finder;
}
}

在MovieFinder的实现类中,也定义了一个set方法

class ColonMovieFinder
{
public void setFilename(String filename)
{
this.filename = filename;
}
}

然后设定Spring的配置文件
<beans>
<bean id=”MovieLister” class=”spring.MovieLister”>
<property name=”finder”>
<ref local=”MovieFinder”/>
</property>
</bean>

<bean id=”MovieFinder” class=”spring.ColonMovieFinder”>
<property name=”filename”>
<value>movies1.txt</value>
</property>
</bean>
</beans>

使用如下:
public void TestWithSpring()
{
ApplicationContext ctx = new FileSystemXmlApplicationContext(”spring.xml”);
MovieLister lister = (MovieLister) ctx.getBean(”MovieLister”);
Movie[] movies = lister.moviesDirectedBy(”Sergio Leone”);
assertEquals(”Once Upon a Time in the West”, movies[0].getTitle());
}

* 接口注入
1 . 定义一个接口,这个接口的用途是将 一个IMovieFinder实例注入该接口的实现者。
public interface InjectFinder
{
void injectFinder(MovieFinder finder);
}

这个接口应该由提供IMovieFinder 接口的人提供。任何想要使用IMovieFinder 实例的类
如MovieLister 类,都必须实现这个接口。

class MovieLister : InjectFinder
{
public void injectFinder(MovieFinder finder) {
this.finder = finder;
}

然后,我使用类似的方法将文件名注入MovieFinder的实现类:
public interface InjectFilename
{
void injectFilename (String filename);
}

class ColonMovieFinder : IMovieFinder, InjectFilename
{
public void injectFilename(String filename) {
this.filename = filename;
}

通过一些配置代码装配所有的组件并使用。
class IfaceTester
{
private MovieLister lister;
private void configureLister()
{
ColonMovieFinder finder = new ColonMovieFinder();
finder.injectFilename(”movies1.txt”);
lister = new MovieLister();
lister.injectFinder(finder);
}
测试代码则可以直接使用这个字段:
public void TestIface()
{
configureLister();
Movie[] movies = lister.moviesDirectedBy(”Sergio Leone”);
assertEquals(”Once Upon a Time in the West”, movies[0].getTitle());

}
}

使用Service Locator来进行依赖注入:
Service Locator 的基本思想是:有一个对象(即Service Locator)知道如何获得一个应用
程序所需的所有服务。即在上面的例子中,服务定位器应该有一个方法用于获得一个IMovieFinder的实例。

class MovieLister
{

IMovieFinder finder = ServiceLocator.movieFinder();
}

class ServiceLocator
{
private static ServiceLocator soleInstance;
private MovieFinder movieFinder;

public static IMovieFinder movieFinder()
{
return soleInstance.movieFinder;
}

public static void load(ServiceLocator arg)
{
soleInstance = arg;
}

public ServiceLocator(IMovieFinder movieFinder)
{
this.movieFinder = movieFinder;
}
}

使用
class Tester
{
private void configure()
{
ServiceLocator.load(new ServiceLocator(new ColonMovieFinder(”movies1.txt”)));
}

public void testSimple()
{
configure();
MovieLister lister = new MovieLister();
Movie[] movies = lister.moviesDirectedBy(”Sergio Leone”);
assertEquals(”Once Upon a Time in the West”,
movies[0].getTitle());
}
}

Dependency Injection 和Service Locator 并不互斥,你可以同时使用它们,
一个简单的Avalon 实现版本:
public class MyMovieLister implements MovieLister, Serviceable
{
private MovieFinder finder;
public void service( ServiceManager manager )
{
finder = (MovieFinder)manager.lookup(”finder”);
}
}

service() 方法就是接口注入的例子, 它使容器可以将一个ServiceManager 对象注入
MyMovieLister 对象。
ServiceManager则是一个服务定位器。MyMovieLister 并不把ServiceManager 对象保存在字段中,
而是马上借助它找到IMovieFinder 实例,并将后者保存起来。

July 24, 2006

我所喜欢的音乐(待整理)

Filed under: 娱乐

———————————————————–
Cabiraries
———————————————————–
Zombie
Joe
Dreams
Animal Instinct

———————————————————–
Gun and rose
———————————————————–
November rain
Don’t cry
Knockin On heavens Door

———————————————————–
Eagles
———————————————————–
Hotel california
Love will keep us alive

———————————————————–
Scorpions
———————————————————–
Wind of change
Send me an angel

———————————————————–
Phil collins
———————————————————–
do you remenber
Another day in paradise
come with me

sql parameter的类型检查

Filed under: SQL&DB Accessing

如果在生成SQL Parameter时不指定类型会如何?

<appSettings>
<add key=”ConnectionString” value=”Server=.\SQL2k5;Database=Northwind;Integrated Security=SSPI” />
</appSettings>

string strConn = ConfigurationSettings.AppSettings[”ConnectionString”];
using (SqlConnection connection = new SqlConnection(strConn))
{
connection.Open();
string orderId = “10248′”;

string sql = “SELECT * FROM ORDERS WHERE OrderId= @OrderId”;
SqlCommand command = new SqlCommand(sql, connection);
command.Parameters.Add(new SqlParameter(”@OrderId” ,orderId) ); //非法的字符串
try
{
command.ExecuteNonQuery();
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
}
}

上面的例子中, OrderId在数据库中的类型为int, 我在生成parameter时故意
传入一个包含单引号的字符串,运行时会报错:
Conversion failed when converting the nvarchar value ‘10248′’ to data type int.

看来MS会进行数据类型检查.

July 23, 2006

我所喜欢的电影(待整理)

Filed under: 娱乐

红高梁

篮风筝

活着

十七岁的单车

卡拉是条狗

盲井

—————————————————————–

旺角卡门

无间道1

黑社会1

枪火

成龙颠峰之作: A计划, 飞鹰行动, 警察故事

—————————————————————–

牯岭街少年杀人案

黑金

—————————————————————–

阿甘正传

情枭的黎明

教父1

教父2

美国往事

盗火线

侠圣(Saint_1997)

肖约克的救赎

愤怒的公牛

毁灭者1

毁灭者2

加勒比海盗

战争之王

July 13, 2006

发现了一个申请Orkut的好地方

Filed under: 网络资源

http://xn0.org/invite.do/language/chinese/

July 8, 2006

SqlCommand Parameter And “Like” 条件

Filed under: SQL&DB Accessing

SqlCommand Parameter And "Like" 条件

为了生成这样一个条SQL语句
SqlCommand cmd = new SqlCommand("SELECT * FROM People WHERE MENU LIKE ‘%包子%’");

我使用了如下的代码:
SqlCommand cmd = new SqlCommand("SELECT * FROM People WHERE MENU LIKE %@Name%");
cmd.Parameters.Add("@Name", SqlDbType.NVarChar);
cmd.Parameters["@Name"].Value = "包子";
执行时报错.

进而改为
SqlCommand cmd = new SqlCommand("SELECT * FROM People WHERE MENU LIKE ‘%@Name%’");
cmd.Parameters.Add("@Name", SqlDbType.NVarChar);
cmd.Parameters["@Name"].Value = "包子";
可执行.
改成
SqlCommand cmd = new SqlCommand("SELECT * FROM People WHERE MENU LIKE ‘%’ + @Name + ‘%’");
也可以干活,从而可以看出MS的做法是在字符类型的@Name参数前后加单引号,而不管%

但我实在不想在代码中出现单引号这种讨厌的东西.
后改为:
SqlCommand cmd = new SqlCommand("SELECT * FROM People WHERE MENU LIKE @Name");
cmd.Parameters.Add("@Name", SqlDbType.NVarChar);
cmd.Parameters["@Name"].Value = "%包子%";

NAnt的代码不能Debug

Filed under: .NET

用VS2003打开NAnt的代码,断点断不到,Why?

我检查了Project的属性设定,是dubug版,一切正常,最后我发现是app.config中的下列代码导致断点不能工作:

  <startup>
        <!– .NET Framework 2.0 –>
        <supportedRuntime version="v2.0.50727" />
        <!– .NET Framework 2.0 Beta 2 –>
        <supportedRuntime version="v2.0.50215" />
        <!– .NET Framework 2.0 Beta 1 –>
        <supportedRuntime version="v2.0.40607" />
        <!– .NET Framework 1.1 –>
        <supportedRuntime version="v1.1.4322" />
        <!– .NET Framework 1.0 –>
        <supportedRuntime version="v1.0.3705" />
    </startup>

因为我使用的vs2003,所以只有把<supportedRuntime version="v1.1.4322" />移动到最顶端,断点才能工作.

这些代码是怎么生成的?

在project的propery dialog的General页上,设置Supported runtimes,在app.config中就会生成上述代码.

July 7, 2006

如何用enter key正确提交Form.

Filed under: ASP.NET

一开始我使用了这个方法.
&lt;script language="JavaScript" type="text/javascript"&gt;
    &lt;!–
    //event handler
    if (document.addEventListener)   //Mozilla
        document.addEventListener("keyup",OnKeyup,true);
    else
        document.attachEvent("onkeyup",OnKeyup);

    function OnKeyup(event)
    {
        if(event.keyCode == 13  && event.srcElement.id =="txtAccountID" ) //&& event.shiftKey==true
        {
            var b = document.getElementById("btnUpdateOne");
            b.click();
        }
    }
    //–&gt;
&lt;/script&gt;

在Textbox上,还有一个validator 来验证用户输入,如果输入非法的字符,再使用button来提交,
validator 会提示错误,页面也不会被提交.但如果使用enter key来提交,validator会提示错误,
同时页面也会被提交.
在asp .net 2.0中,这个问题已被修正,如果输入非法字符,再使用enter key,页面不会被提交.

后来我使用了一下的方法:

if (!Page.IsPostBack)
{
    TextBox1.Attributes.Add("onkeydown", "if(event.keyCode){if ((event.which == 13) || (event.keyCode == 13)) {document.getElementById(’" + this.Button_OK.ClientID + "’).click();return false;}} else {return true}; ");  

    StringBuilder sb = new StringBuilder();
    sb.Append("if(event.keyCode)");
    sb.Append("{");
    sb.Append("     if (event.keyCode == 13)");
    sb.Append("     {");
    sb.Append("         document.getElementById(’" + this.Button_OK.ClientID + "’).click();");
    sb.Append("          return false;");
    sb.Append("      }");
    sb.Append("}");
    sb.Append("else");
    sb.Append("{return true}; ");
    this.TextBox1.Attributes.Add("onkeydown", sb.ToString());
}

July 6, 2006

如何禁止重复提交

Filed under: ASP.NET

一开始我使用了
this.button_OK.Attributes.Add("onclick", "window.document.getElementById(’" + this.Button_OK.ClientID + "’).disabled = true;")
但这样做的结果是server端的button click处理函数不会被执行.

正确的做法是在Page Load中添加代码:
//.net 2.0
string script = ClientScript.GetPostBackEventReference(this.Button_OK, null);
if (!Page.IsPostBack)
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    sb.Append("window.document.getElementById(’" + this.Button_OK.ClientID + "’).disabled = true;");
    sb.Append(script);
    sb.Append(";");
    this.Button_OK.Attributes.Add("onclick", sb.ToString());
}
       

// .net 1.0
string script = this.GetPostBackEventReference(this.button_OK);

If (!Page.IsPostBack)
{
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    sb.Append("window.document.getElementById(’" + this.button_OK.ClientID + "’).disabled = true;");
    sb.Append(script);
    sb.Append(";")
    this.button_OK.Attributes.Add("onclick", sb.ToString());
}

GetPostBackEventReference会生成html代码:
<script type="text/javascript">
<!–
var theForm = document.forms[’form1′];
if (!theForm)
{
    theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
// –>
</script>

生成的script值为__doPostBack(’Button_OK’,'’)

July 5, 2006

ZBlog writer不能用了

Filed under: Blog使用

今天发现ZBlog writer 在公司不能用了,错误如下
—————————
发布错误
—————————
发布项错误 — ‘ascii’ codec can’t encode characters in position 19-24: ordinal not in range(128)
—————————
OK
—————————
—————————
下载错误
—————————
下载日志出错 — XmlRpc 列表分类请求失败:RpcPublisherError[’mt.getCategoryList’ type:Socket Error, code:0 msg:(7, ‘getaddrinfo failed’)]
—————————
OK
—————————

很显然公司的firewall又搞了什么花样.只好在IE里写,很不爽.等待ZBolg的proxy 功能.






















Get free blog up and running in minutes with Blogsome
Theme designed by Hadley Wickham