关于一个Silverlight3的小项目总结

最近这段时间在为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就可以使用这个方式来访问了: 

基于前面的步骤用以下方式JS调SL方法
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代码 

C#嵌入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的信息就可以了。先贴段代码在这里吧,以免哪一天打不开上面这段链接。 

ClipboardData
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来使用。 

App.xaml中合并资源
<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吧: 

背景颜色切换Converter
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中如是定义,才可以使用: 

颜色变换的Converter
<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,我还是在网上做了一些功课的。摘抄地址在这里

TriggersActions Behaviors使得在Silverlight应用程序中进行交互操作变得更为容易,XAML即可完成诸多功能,可以减去复写后台代码的烦恼,需要借助Blend 3 SDK的System.Windows.Interactivity.dll和Microsoft.Expression.Interactions.dll程序集。    

 TriggersActions是因果关系模型,一个触发器可以调用一个或多个操作,而Behaviors则大致相当于两者的一个小综合体。他们的类关系图如下:

 


所谓Trigger,就是监听某些条件的变化,比如事件触发,属性值改变等,进而触发一些动作的发生。这些Triggers可能是EventTriggerCollisionTrigger 等,当然更多的或许是创建自己的Trigger。自定义Trigger只需要从TriggerBase<DependencyObject>继承,并覆盖OnAttachedOnDetaching方法即可。


所谓Action,就是去执行某些操作。可以根据需要创建自己的Action,常见的需要创建Action的情况有:改变属性、调用方法、打开窗口、导航到某个页面、设置焦点等。自定义Action可从 TriggerAction<DependencyObject>TargetedTriggerAction<DependencyObject>继承,区别在于操作对象是关联对象还是特定的目标对象,实现时覆盖Invoke方法即可。

       

 TriggersActions理论是可以相互独立,任意组合的。当你在定义时发现有些逻辑上需要相互确定或者假定发生时,Behaviors需要登台了。Behaviors乍看起来像是Actions,但它是逻辑独立功能自备的独立单元,它无需触发器,定义Behavior时就已经确定。

       

创建自定义Behavior需要从Behavior<DependencyObject>继承,//自定义行为默认继承Behavior<DependencyObject>使用DependencyObject类型的行为是不能访问对象的鼠标事件的,如果要访问鼠标操作的事件,可以使用具体的UI组件类型或者直接使用UI元素基类UIElement。//

并覆盖OnAttachedOnDetaching方法,复杂行为时需要用到ICommand. 当然在Blend 3中已经预定义了不少Behaviors,如MouseDragElementBehavior等。可以利用,同时在Expression Gallery 也可以共享他人或自己的Behavior。(PSEffects Themes也可以在这里共享。)

我在这个项目中,只使用了一个Action和一个Behavior,都作用于textBox上面,分别用来获得焦点时就全选文本和控制只能输入数字的作用。

首先来看Action:  

SelectAll in textBox
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。 

只允许输入数字的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.
}
*/
}

调用时分别使用: 

自定义Action和Behavior的调用
<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的方法来实现的。我也知道,这个算法肯定是登不了大雅之堂的。如果哪位看到了这里,可否留言指教一下这个算法应该如何写最高效最简洁最优雅。 

向下靠1000取整
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事件来改变了。

显示或不显示$的textBox
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代码来获取这区间段内的文字

获取Html区间文本
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的函数了

支持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却有,为保一致,还是加上这个。

正则式去掉html标签
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。

原文地址:https://www.cnblogs.com/SLKnate/p/1794725.html