最近这段时间在为California的一家保险公司做外包项目,该公司的技术选型为MVC1.0+Silverlight3 +WCF+Multi DB(MS SqlServer2008+IBM DB2),从年初启动到现在半年过去了,项目基本上算是完了,正在最后测试准备上线之际。不过,我今天总结的并不是这个项目,而是在这个项目做到一半的时候,该公司想为他们的多个网站加个免费计算器的一个Silverlight 小工具。由我在Escrum上总用时差不多40个小时完成的,工具虽小,但五脏俱全,所以我还是做个小小的技术总结。
先说一下作用背景(按照意思的总结,并非翻译哈)吧:
1、可以很轻便的使用于不同的网站上,即无缝的挂接到现有的网站上去。
2、基于Silverlight3实现,因为Silverlight4好象只能在vs2010上做吧,貌似这样子。
3、需要尽快完成,一两周内就要提供上去。
4、尽可能的可以运行在不同浏览器上(这个听起来有点不对劲,多些一举的说法,不过,做到中间我还真遇到了复制至剪贴板不支持IE以外的情况。)
5、工具需要提供能够输入,复制剪贴板,打印,语音帮助,文字帮助功能。
6、一些业务规则和数据输出计算规则随后由BRD文档提供。
OK,就这些要求,看起来应该是蛮简单的。而事实上也确是如此,最终,我使用的是一个xaml文件,一个Model和一些辅助类来完成的这些。对,我选用的是MVVM模式来弄的。因为最初我还傻傻的以为他们最终会找个美工来实现界面漂亮点。从现在看来,对方公司好象对我做的页面也没有多大的意见,似乎也没请美工捉刀美化页面的意思,不过这些也不是重要的,重要的咱的技术总结:
1、要可以在不同的asp.net网站上运行,少不了要跟html页面打交道了,而html上的Dom控制的王者不言而喻的是Javascript了。Silverlight要和JavaScript交互使用,需要这样子来弄:
1-A,Silverlight调用Javascript方法:
直接在xaml.cs文件中使用
HtmlPage.Window.Eval( " javascript:window.close(); " ); // 或者
HtmlPage.Window.Invoke( " closePage " );
上面closePage是在Javascript中定义的一个方法,不带参数的,如果带参数,也是可以的,实现做法一个样。另外上面的HtmlPage的完整类名叫System.Windows.Browser.HtmlPage。
1-B,Javascript调用Silverlight中的方法:
这个稍微要多点步骤了,首先是在xaml.cs的构造需要注册成scriptable对象
HtmlPage.RegisterScriptableObject( " PrintPage " , this ); // 这里的PrintPage可以理解为ID,Javascript靠这个来识别到哪一个类中找方法,所以定义一个好的命名是会在写Javascript时有很大帮助的。
然后还在要被调用的方法或属性的前面申明[ScriptableMember],当然要是想把整个类都公开给Javascript,也可以在类的前面申明为[ScriptableType],建议不该公开的东西还是别公开吧。
最后,在Javascript就可以使用这个方式来访问了:
fucntion getPropertyFromSilverlight(){}
var slCtrl = document.getElementById( " silverlightControl " ); // 获得Silverlight控件
var page = slCtrl.Content.PringPage; // 取得将要调用的方法的所在类。
alert(page.PrintText); // 这里假设在xaml.cs类中定义了一个名为PrintText的public属性.
1-C,将一些Javascript方法或CSS写进C#中,这个花费过我一些时间,尤其是将CSS写入进去的时候,一定要写在div并且一定要在<style>前面加上一个<br/>才调成功,至于是否写成一行或者多行就没有关系了。下面这段代码是实现打印部分生成的文字的一段CSS代码
public class PrintHelper
{
static string StyleId = Guid.NewGuid().ToString( " N " );
public static void PrintText( string text)
{
var body = HtmlPage.Document.Body;
if (HtmlPage.Document.GetElementById(StyleId) == null )
{
var style = HtmlPage.Document.CreateElement( " div " );
style.SetAttribute( " id " , StyleId);
style.SetProperty( " innerHtml " , @" <br />
<style type='text/css'>
#printHost
{
display: none;
}
@media print
{
#form1
{
display: none;
}
#printHost
{
display: block;
}
}
</style> " );
body.AppendChild(style);
}
// var obj = HtmlPage.Document.CreateElement("object");
// obj.SetAttribute("id", "wb");
// obj.SetAttribute("name", "wb");
// obj.SetAttribute("height", "0");
// obj.SetAttribute("width", "0");
// obj.SetAttribute("classid", "CLSID:8856F961-340A-11D0-A96B-00C04FD705A2");
var innerHtml = HtmlPage.Document.GetElementById( " printHost " );
if (innerHtml == null )
{
innerHtml = HtmlPage.Document.CreateElement( " span " );
innerHtml.SetAttribute( " id " , " printHost " );
innerHtml.SetAttribute( " name " , " printHost " );
innerHtml.SetProperty( " innerHTML " , text);
}
// body.AppendChild(obj);
body.AppendChild(innerHtml);
HtmlPage.Window.Eval( " javascript:window.print(); " );
// HtmlPage.Window.Eval("javascript:var wb=document.getElementById('wb');wb.execwb(7, 1);"); // print view
// HtmlPage.Window.Eval("javascript:var wb=document.getElementById('wb');wb.execwb(6, 1);"); // print
// HtmlPage.Window.Eval("javascript:var wb=document.getElementById('wb');wb.execwb(8, 1);"); // print page setup
}
}
以上我们约定aspx页面的Form id是form1。
1-D,也是想用C#调用clipboradData来复制文本至剪贴板。当然这段代码是复制于网上,不过我最终没有真正的去跨非IE浏览器,因为对方说可以不做这个,直接给个不支持非IE的信息就可以了。先贴段代码在这里吧,以免哪一天打不开上面这段链接。
public class ClipboardHelper
{
const string HostNoClipboard = " The clipboard isn't available in the current host. " ;
const string ClipboardFailure = " The text couldn't be copied into the clipboard. " ;
const string BeforeFlashCopy = " The text will now attempt to be copied... " ;
const string NotSupportBrowser = " The clipboard isn't available in the current host. " ;
const string FlashMimeType = " application/x-shockwave-flash " ;
// HARD-CODED!
const string ClipboardFlashMovie = " ZeroClipboard.swf " ;
/// <summary>
/// Write to the clipboard (IE and/or Flash)
/// </summary>
public static void SetText( string text)
{
// document.window.clipboardData.setData(format, data);
var clipboardData = (ScriptObject)HtmlPage.Window.GetProperty( " clipboardData " );
if (clipboardData != null )
{
bool success = ( bool )clipboardData.Invoke( " setData " , " text " , text);
if ( ! success)
{
HtmlPage.Window.Alert(ClipboardFailure);
}
}
else
{
HtmlPage.Window.Alert(NotSupportBrowser);
// Append a Flash embed element with the data encoded
string safeText = HttpUtility.UrlEncode(text);
var elem = HtmlPage.Document.CreateElement( " div " );
HtmlPage.Document.Body.AppendChild(elem);
elem.SetProperty( " innerHTML " , " <embed src=\ "" +
ClipboardFlashMovie + " \ " " +
" FlashVars=\ " clipboard = " + safeText + " \ " width=\ " 0 \ " " +
" height=\ " 0 \ " type=\ "" + FlashMimeType + " \ " ></embed> " );
}
}
}
2、如果你不希望你的Xaml文件中的Style啊Resource之类的充斥其中的话。你可以新建一个文件夹专门用来存放这些内容的,然后在App.xaml中合并这些,作为StaticResource来使用。
< Application.Resources >
< ResourceDictionary >
< ResourceDictionary.MergedDictionaries >
< ResourceDictionary Source ="Themes/PopupWindow.xaml" />
< ResourceDictionary Source ="Themes/Converter.xaml" />
</ ResourceDictionary.MergedDictionaries >
</ ResourceDictionary >
</ Application.Resources >
很明显的,我是建了一个Themes的文件夹,里面加了两个ResourceDictionary,做了以上这些操作之后,如果在页面中,想使用Style也好,Converter也好,直接StaticResource Key就可以完成了。而不需要在页面中再申明了。这就是全局资源的好处,当然也不是定义全局资源就是好方法,页面资源就一无是处了,这个取舍还在于一个平衡度的问题。
3、讲到Converter了,我有一点深刻印象的是,要实现一个textBox的背景颜色的切换的问题,一般的直接用IValueConverter来实现的话,还不一定达到效果,我最后是在一位同事的指点下,利用一个集合来完成的。OK,先看我最终的IValueConverter吧:
public class StringToGrayColorConverter : DependencyObject,System.Windows.Data.IValueConverter
{
#region IValueConverter Members
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// if (String.IsNullOrEmpty(value.ToString()))
// return Colors.White;
// if (value.ToString().ToLower() == "yes")
// return Colors.LightGray;
// else
// return Colors.White;
if ( null == value)
{
throw new System.ArgumentNullException( " value " );
}
BrushCollection brushes = Brushes;
if (parameter != null )
brushes = (BrushCollection)parameter;
return (value.ToString().ToLower() == " yes " ) ? brushes[ 0 ] : brushes[ 1 ];
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
public BrushCollection Brushes
{
get { return (BrushCollection)GetValue(BrushesProperty); }
set { SetValue(BrushesProperty, value); }
}
public static readonly DependencyProperty BrushesProperty =
DependencyProperty.Register( " Brushes " , typeof (BrushCollection), typeof (StringToGrayColorConverter), new PropertyMetadata( null ));
}
public class BrushCollection : List < Brush >
{
}
然后,我们在Converter.xaml中如是定义,才可以使用:
< Color x:Key ="DisableBackgroundColor" > LightGray </ Color >
< Color x:Key ="EnableBackgroundColor" > White </ Color >
< Converters:StringToGrayColorConverter x:Key ="stringToGrayColorConverter" >
< Converters:StringToGrayColorConverter.Brushes >
< Converters:BrushCollection >
< SolidColorBrush Color =" {StaticResource EnableBackgroundColor} " />
< SolidColorBrush Color =" {StaticResource DisableBackgroundColor} " />
</ Converters:BrushCollection >
</ Converters:StringToGrayColorConverter.Brushes >
</ Converters:StringToGrayColorConverter >
4、Behavior和Action
在VS2008中,如果有装Blend话,会有建Behavior或Action的模板,但不知为什么,在VS2010里面反倒没有了。不过,用Expression Studio4的话就还是有这些选项的。对于Triggers、Actions和Behavior,我还是在网上做了一些功课的。摘抄地址在这里:
Triggers、Actions 和Behaviors使得在Silverlight应用程序中进行交互操作变得更为容易,用XAML即可完成诸多功能,可以减去复写后台代码的烦恼,需要借助Blend 3 SDK的System.Windows.Interactivity.dll和Microsoft.Expression.Interactions.dll程序集。
Triggers和Actions是因果关系模型,一个触发器可以调用一个或多个操作,而Behaviors则大致相当于两者的一个小综合体。他们的类关系图如下:
所谓Trigger,就是监听某些条件的变化,比如事件触发,属性值改变等,进而触发一些动作的发生。这些Triggers可能是EventTrigger、CollisionTrigger 等,当然更多的或许是创建自己的Trigger。自定义Trigger只需要从TriggerBase<DependencyObject>继承,并覆盖OnAttached和OnDetaching方法即可。
所谓Action,就是去执行某些操作。可以根据需要创建自己的Action,常见的需要创建Action的情况有:改变属性、调用方法、打开窗口、导航到某个页面、设置焦点等。自定义Action可从 TriggerAction<DependencyObject>或TargetedTriggerAction<DependencyObject>继承,区别在于操作对象是关联对象还是特定的目标对象,实现时覆盖Invoke方法即可。
Triggers和Actions理论是可以相互独立,任意组合的。当你在定义时发现有些逻辑上需要相互确定或者假定发生时,Behaviors需要登台了。Behaviors乍看起来像是Actions,但它是逻辑独立功能自备的独立单元,它无需触发器,定义Behavior时就已经确定。
创建自定义Behavior需要从Behavior<DependencyObject>继承,//自定义行为默认继承Behavior<DependencyObject>使用DependencyObject类型的行为是不能访问对象的鼠标事件的,如果要访问鼠标操作的事件,可以使用具体的UI组件类型或者直接使用UI元素基类UIElement。//
并覆盖OnAttached和OnDetaching方法,复杂行为时需要用到ICommand. 当然在Blend 3中已经预定义了不少Behaviors,如MouseDragElementBehavior等。可以利用,同时在Expression Gallery 也可以共享他人或自己的Behavior。(PS:Effects 和 Themes也可以在这里共享。)
我在这个项目中,只使用了一个Action和一个Behavior,都作用于textBox上面,分别用来获得焦点时就全选文本和控制只能输入数字的作用。
首先来看Action:
public class TextboxAction : TargetedTriggerAction < TextBox >
{
public TextboxAction()
{
// Insert code required on object creation below this point.
}
protected override void Invoke( object o)
{
// Insert code that defines what the Action will do when triggered/invoked.
TextBox tbx = Target;
tbx.SelectAll();
}
}
然后看一下只允许输入数字的一个Behavior。
public class DigitalOnlyBehavior : Behavior < UIElement >
{
public DigitalOnlyBehavior()
{
// Insert code required on object creation below this point.
//
// The line of code below sets up the relationship between the command and the function
// to call. Uncomment the below line and add a reference to Microsoft.Expression.Interactions
// if you choose to use the commented out version of MyFunction and MyCommand instead of
// creating your own implementation.
//
// The documentation will provide you with an example of a simple command implementation
// you can use instead of using ActionCommand and referencing the Interactions assembly.
//
// this.MyCommand = new ActionCommand(this.MyFunction);
}
protected override void OnAttached()
{
base .OnAttached();
this .AssociatedObject.KeyDown += new KeyEventHandler(AssociatedObject_KeyDown);
// Insert code that you would want run when the Behavior is attached to an object.
}
void AssociatedObject_KeyDown( object sender, KeyEventArgs e)
{
char c = ( char )e.PlatformKeyCode;
switch (e.PlatformKeyCode)
{
case 190 :
case 110 :
c = ' . ' ;
break ;
case 96 :
case 97 :
case 98 :
case 99 :
case 100 :
case 101 :
case 102 :
case 103 :
case 104 :
case 105 :
c = IntToChar(e.PlatformKeyCode - 96 );
break ;
}
if ( ! Regex.IsMatch(c.ToString(), Filter))
{
e.Handled = true ;
}
}
protected override void OnDetaching()
{
base .OnDetaching();
this .AssociatedObject.KeyDown -= AssociatedObject_KeyDown;
// Insert code that you would want run when the Behavior is removed from an object.
}
public static readonly DependencyProperty FilterProperty =
DependencyProperty.Register( " Filter " , typeof ( string ), typeof (DigitalOnlyBehavior), new PropertyMetadata( @" .* " ));
public string Filter
{
get { return ( string )GetValue(FilterProperty); }
set { SetValue(FilterProperty, value); }
}
char IntToChar( int intN)
{
return ( char )(intN + 48 );
}
/*
public ICommand MyCommand
{
get;
private set;
}
private void MyFunction()
{
// Insert code that defines what the behavior will do when invoked.
}
*/
}
调用时分别使用:
< i:Interaction.Triggers >
< i:EventTrigger >
< actions:TextboxAction />
</ i:EventTrigger >
</ i:Interaction.Triggers >
< i:Interaction.Behaviors >
< behaviors:DigitalOnlyBehavior Filter ="{StaticResource DigitalOnlyFilter}" />
</ i:Interaction.Behaviors >
<!-- DigitalOnlyFilter定义在我们前面介绍的Converter.xaml中
<system:String x:Key="DigitalOnlyFilter" >[0-9.\t]</system:String>
-->
5、验证,本小项目中,验证全部是在Property的Set中验证的。然后在xmal文件中使用Binding时加上以下两个属性,NotifyOnValidationError=true, ValidatesOnExceptions=true 这两个属性由于较长,一般容易写错,所以我特意挑出来放在这里作为总结。
6、ViewModel和View之间的沟通,在View构造时,将DataContext赋值为ViewModel即可。当然也可以写在Page Resource中,这是很多网上的一些教程使用的方法。
7、有个向下靠千取整的问题,目前我使用的是Math.Round的方法来实现的。我也知道,这个算法肯定是登不了大雅之堂的。如果哪位看到了这里,可否留言指教一下这个算法应该如何写最高效最简洁最优雅。
var temp = 总金额 / 总人数 / 1000 ;
if (( int )Math.Round(temp) == ( int )temp)
平均金额 = Math.Round(temp, 0 ) * 1000 ;
else
平均金额 = Math.Round(temp - 0.5m , 0 ) * 1000 ;
结论:其实,Silverlight使用起来也是蛮方便,也不是很难,特别是用MVVM这种模式来做的话,其实这个模式一大好处之一是跟MVC一样,可以让程序员更放心的交付可靠代码,分离程序员和美工的职责,让项目同步进行。做Silverlight的ViewModel没有多大的不同以往,注意Converter,Behavior,Action,Trigger这些,然后界面可能就要强调一下VSM,Animation等内容了。
后记:天下事,事与愿违之事有之,昨天接到邮件,该公司最终决定不用Silverlight来做这个小工具,搞着玩的嘛?我辛苦花了40多个小时的时间完成,其间也来来回回有过QA记录,难道到了最后,才发现用Silverlight不好?要改用传统的Asp.net才好?郁闷!不过,事还是要做的,按照他们的要求做成Asp.net的,OK,好,改就改吧。与Silverlight几点不同。
1、textBox要显示Currency的格式。这点不同于Silverlight。Silverlight只需写个Converter即可实现,而在Asp.net中,要实现的话,只能靠Javascript的onfocus和onblur事件来改变了。
function formatCurrencyTextBox(ctrl) {
ctrl.value = formatCurrencyValue(ctrl.value);
}
function formatCurrencyValue(amount) {
var delimiter = " , " ; // replace comma if desired
var a = amount.split( ' . ' , 2 )
var d = a[ 1 ];
var i = parseInt(a[ 0 ]);
if (isNaN(i)) { return '' ; }
var minus = '' ;
if (i < 0 ) { minus = ' - ' ; }
i = Math.abs(i);
var n = new String(i);
var a = [];
while (n.length > 3 ) {
var nn = n.substr(n.length - 3 );
a.unshift(nn);
n = n.substr( 0 , n.length - 3 );
}
if (n.length > 0 ) { a.unshift(n); }
n = a.join(delimiter);
if (d == null || d.length < 1 ) { amount = n; }
else { amount = n + ' . ' + d; }
amount = minus + amount;
return " $ " + amount;
}
function unFormatCurrencyTextBox(ctrl) {
ctrl.value = unFormatCurrencyValue(ctrl);
ctrl.select();
}
function unFormatCurrencyValue(ctrl) {
var value = ctrl.value.replace( new RegExp( ' , ' , ' g ' ), '' );
value = value.replace( new RegExp( ' \\$ ' , ' g ' ), '' );
return value;
}
2、复制剪贴板和打印部分区域的。可以沿用原Silverlight的思路,因为asp.net的打印/复制的目标文字是写在Html标签中和Asp.net标签(动态内容)中的,而不像Silverlight时,直接在ViewModel中公开一个string属性,传过来给PrintHelper打印或CopyHelper复制即可。因此,需要在上面提到的这些标签的内容的前后加个识别标识,为不引起不必要的显示或岐义,加上<!--startprint-->共17个字符开始和<!--endprint-->字符结束。单单加个这个还没有什么作用的,需要用一段Javascript代码来获取这区间段内的文字
function getCopyPrintText() {
var strBody = window.document.body.innerHTML;
var strBegin = " <!--startprint--> " ;
var strEnd = " <!--endprint--> " ;
var strPrint = strBody.substr(strBody.indexOf(strBegin) + 17 );
strPrint = strPrint.substring( 0 , strPrint.indexOf(strEnd));
return strPrint;
}
复制时,IE下直接这样子
clipboardData.setData( " Text " ,getCopyPrintText());
然而,不知是什么原因,该公司对Firefox似乎很有情感,这也说明可能美国那边的Firefox的市场占有率还是蛮高的。要求这个asp.net的要支持Firefox,于是,复制的这段代码也被封装成了一个支持IE和Firefox的函数了
function copyToClipboard(s)
{
if ( window.clipboardData && clipboardData.setData )
{
clipboardData.setData( " Text " , s);
}
else
{
// You have to sign the code to enable this or allow the action in about:config by changing
// ("signed.applets.codebase_principal_support", true);
try {
netscape.security.PrivilegeManager.enablePrivilege( ' UniversalXPConnect ' );
}
catch (e) {
alert( " Access Denial!\nPlease enter 'about:config' in the address bar,\n and make sure the 'signed.applets.codebase_principal_support' is true " );
}
var clip = Components.classes[ ' @mozilla.org/widget/clipboard;1 ' ].createInstance(Components.interfaces.nsIClipboard);
if ( ! clip) return ;
// create a transferable
var trans = Components.classes[ ' @mozilla.org/widget/transferable;1 ' ].createInstance(Components.interfaces.nsITransferable);
if ( ! trans) return ;
// specify the data we wish to handle. Plaintext in this case.
trans.addDataFlavor( ' text/unicode ' );
// To get the data from the transferable we need two new objects
var str = new Object();
var len = new Object();
var str = Components.classes[ " @mozilla.org/supports-string;1 " ].createInstance(Components.interfaces.nsISupportsString);
var copytext = s;
str.data = copytext;
trans.setTransferData( " text/unicode " ,str,copytext.length * 2 );
var clipid = Components.interfaces.nsIClipboard;
if ( ! clip) return false ;
clip.setData(trans, null ,clipid.kGlobalClipboard);
}
}
然后复制也变成
copyToClipboard(RemoveHtmlTag(getCopyPrintText(), false ));
上面的RemoveHtmlTag是一个去掉Html标签的脚本,在修改这段代码的时候,让我深感学好正则表达式的好处。当然,如果是在IE下,不用下面这个函数去掉标签,复制进clipboard之后,也是没有标签的,但firefox却有,为保一致,还是加上这个。
function RemoveHtmlTag(str, noEnter) {
var html = str;
html = html.replace( / ^[ ]* / img, " " ); // space
html = html.replace( / <!--[\s\S]*?--> / img, "" ); // comment
html = html.replace( / <[\ / ] * table[ ^> ] *> / img, "\n"); // table
html = html.replace( / <[\ / ] * tbody[ ^> ] *> / img, ""); // tbody
html = html.replace( / <[\ / ] * tr[ ^> ] *> / img, "\n"); // tr
html = html.replace( / <[\ / ] * td[ ^> ] *> / img, "\n"); // td
html = html.replace( / <[\ / ] * p[ ^> ] *> / img, "\n"); // p
html = html.replace( / <[\ / ] * a[ ^> ] *> / img, ""); // a
html = html.replace( / <[\ / ] * col[ ^> ] *> / img, "\n"); // col
html = html.replace( / <[\ / ] * br[ ^> ] *> / img, "\n"); // br
html = html.replace( / <[\ / ] * [ ^> ] *> / img, ""); / /
html = html.replace( / <[\ / ] * span[ ^> ] *> / img, ""); // span
html = html.replace( / <[\ / ] * center[ ^> ] *> / img, ""); // center
html = html.replace( / <[\ / ] * ul[ ^> ] *> / img, ""); // ul
html = html.replace( / <[\ / ] * i[ ^> ] *> / img, ""); // i
html = html.replace( / <[\ / ] * li[ ^> ] *> / img, ""); // li
html = html.replace( / <[\ / ] * b[ ^> ] *> / img, ""); // b
html = html.replace( / <[\ / ] * hr[ ^> ] *> / img, ""); // hr
html = html.replace( / <[\ / ] * h\d + [ ^> ] *> / img, ""); // h1,2,3,4,5,6
html = html.replace( / <STYLE[\s\S]*?<\ / STYLE > / img, ""); // style
html = html.replace( / <script[\s\S]*?<\ / script > / img, ""); // reference script
// html = html.replace(/<[\?!A-Za-z\][^><]*>/img, "");alert("str:"+html)
html = html.replace( / \r / img, "" ); // break
html = html.replace( / \n / img, " \r\n " ); // enter
// html = html.replace(/[ |\s]*\r\n[ |\s]*\r\n/img, "\r\n");
if (noEnter) {
html = html.replace( / \r\n / img, "" );
html = html.replace( / \n / img, "" );
html = html.replace( / \r / img, "" );
}
return (html);
}
至于思路同于Silverlight打印的那段代码,在原html里加个<div id="printHost"></div>,用CSS控制打印区域,当然Javascript需要给printHost区域赋值,然后用window.print()即可。
function printText() {
var printHost = document.getElementById( " printHost " );
printHost.innerHTML = getCopyPrintText();
window.print();
}
3、业务规则,跟Silverlight一样。
4、至于是否使用Ajax,就不总结记录了。
5、使用Ajax,如果要在cs后台中显示alert,需要这样子包装:
ScriptManager.RegisterStartupScript(up1,up1.GetType(), " zeroAmount " , " <script type='text/javascript'> alert('up1 is UpdatePanel.');</script> " , false );
没有用Ajax的话,就这样子
Page.Response.Write( " <script type='text/javascript'> alert('This is a test.');</script> " );
就好了。
总结:Silverlight跟Asp.net还是有一些区别的,至少在实现一些细节的时候,Silverlight要方便些,比方说与Javascript之间的交互啊(因为都是客户端嘛),自定义显示啊等。
最后,希望这次做完之后,没有多少大的改变了,至少我想不会做成WPF了的,或者再回到Winform里去吧。
又续:决定升级silverlight4,需要安装vs2010,却出现:Error code 1601...,解决方法是msiexec /unreg和msiexec /regserver.据说2003类的用net stop msiserver和net start msiserver。