讲座展示:TechEd Europe DEV344 - ASP.NET AJAX Control Toolkit(下)
2006-11-18 19:18 by 老赵, 3566 visits讲座内容
这次我选择的讲座内容是最近在TechEd 2006 Europe中Shawn Burke的讲座“ASP.NET AJAX Control Toolkit Unleashed: Creating Rich Client-Side Controls and Components”。Shawn Burke是微软.NET Developer Platform总监。这个讲座的PPT和演示代码的下载地址已经由他本人公布在他的Blog上(本地下载)。另外,在MSDN's Showtime上也已经有了这个讲座的完整视频。
此次讲座的内容主要是对于ASP.NET AJAX Control Toolkit进行简单的介绍,展示了Extender控件是如何帮助ASP.NET开发人员简单地将丰富的用户体验集成到他们的Web应用程序中。在这次讲座里将看到应该如何在您的应用中使用ASP.NET AJAX Control Toolkit中的组件,并且了解开发人员是如何方便地开发一个APS.NET AJAX Extender的。
此次讲座分为两部分:“ASP.NET AJAX Control Toolkit介绍和使用”以及“开发一个Extender控件”。本文将对于该讲座的第二部分进行讲述,并且对其第二个演示的剩余部分进行分析。
讲座演示
请注意,在Shawn提供的压缩包内有三个空文件夹:Demo_Base、Demo_Step_2和Demo_Step_3,现在它们在演示时也是有内容的,我已经根据讲座视频设法将它们恢复到了演示时的样子,请点击这里下载。
Step 2:Consuming other behaviors / Handling events / Coding Client-Side Logic
我们可以发现,Extender在客户端生成了代码,能够构造Behavior对象,也正确设置了Behavior的属性。在大多数情况下,下面要做就是可能会使用Toolkit中其他一些组件的功能。这也是我们在早期版本Toolkit中的一个挑战:如何才能使各种组件能够组合使用?结合各种组件的功能能够使UI变得更加有趣,我们也能获得更灵活的控制能力。实现这个功能的一个难点就是,这些组件都是JavaScript。每个组件都有自己的JavaScript文件,他们会被编译在程序集中。在使用Extender时,Tookit会知道应该使用哪些JavaScript文件,并将它们引入到页面中。
那么我们该如何引入别的Component的JavaScript文件呢?如果确定它们的脚本被正确加载了?在服务器端有一个CustomAttribute:RequiredScriptAttribute。它的作用就是告诉服务器:“嘿,你们必须在我加载之前加载到页面中”,这样在客户端就能出现能够正确运行的脚本类库了。在客户端,我们能够使用$create方法创建一个组件。我们一会儿就来看一下应该如何使用。
现在我们会使用另外两个组件,它们原本定义在Atlas中,但是在ASP.NET Beta版本中被移除了。不过它们被转移到了开源的Toolkit中,这样开发人员就能继续使用它们了。他们就是HoverExtender和PopupExtender。我们先在客户端Behavor的构造函数中添加这两个组件和一些其它的变量声明。如下:
FontSize.FontSizeBehavior = function(element) { ... // the behaviors we create // this._popupBehavior = null; this._hoverBehavior = null; // Handler delegates // this._hoverHandler = null; this._unhoverHandler = null; this._incrementHandler = null; this._decrementHandler = null; // state bits // this._fontSize = 0; this._popupVisible = false; this._fontUnit = "px"; }
然后我们需要在Initialize方法中使用这两个Behavior。首先我们使用HoverBehavior,添加如下代码:
// setup the hover behavior // if (this._hoverCssClass && this._sizePanelElement) { this._hoverHandler = Function.createDelegate(this, this._onTargetHover); this._unhoverHandler = Function.createDelegate(this, this._onTargetUnhover); this._hoverBehavior = $create(
AjaxControlToolkit.HoverBehavior,
{unhoverDelay:200, hoverElement: this._sizePanelElement},
null, null, this.get_element());
this._hoverBehavior.add_hover(this._hoverHandler); this._hoverBehavior.add_unhover(this._unhoverHandler); }
HoverBehavior的hover事件会在鼠标移到某个元素上被触发,我们可以使用它在UI上显示一个良好的鼠标移进/移出效果。首先我们会建立两个用于触发事件的handler:_hoverHandler和_unhoverHandler。然后使用$create方法创建一个HoverBehavior并且与当前Behavior的元素绑定起来。$create方法会使ASP.NET AJAX跟踪这个对象,例如可以在合适的时候销毁它。在$create的第二个参数中会指定一些属性,unhoverDelay表示鼠标移开在多少毫秒后会触发unhover事件,hoverElement指定了另一个对象,以避免在移动到这个元素后unhover事件被触发。最后一个参数就是Behavior需要绑定的元素,通过get_element方法可以获得当前的元素,比如在使用WatermarkExtender时就会得到那个文本框,这里就是我们希望改变字体大小的Panel。最后将handler注册给hover和unhover事件。
接着我们使用PopupBehavior,代码如下:
// setup the popup behavior // if (this._sizePanelElement) { this._popupBehavior = $create(
AjaxControlToolkit.PopupBehavior, {id: this.get_id() + "_Popup", // make the ID derive from our ID for uniqueness positioningMode: AjaxControlToolkit.PositioningMode.TopRight, parentElement: this.get_element()}, null, null, this._sizePanelElement); }
PopupBehavior的作用是帮助我们显示和隐藏某个UI。这里的代码和上面的比较相似,我们通过当前的Panel的客户端ID来获得PopupBehavior的ID,这样保证了这个ID的唯一性。positioningMode指定了元素在弹出时相对于parentElement的位置。
然后我们增加一些我们需要的Handler。代码如下:
_onTargetHover : function(eventArgs) { var e = this.get_element(); Sys.UI.DomElement.addCssClass(e, this._hoverCssClass); if (this._popupBehavior && !this._popupVisible) { this._popupVisible = true; // call show to make the the popup visible // this._popupBehavior.show(); } }, _onTargetUnhover : function(eventArgs) { var e = this.get_element(); Sys.UI.DomElement.removeCssClass(e, this._hoverCssClass); if (this._popupBehavior && this._popupVisible) { this._popupVisible = false; this._popupBehavior.hide(); } },
在Hover事件被触发时,也就是用户将鼠标移动到了Panel上,我们会调用PopupBehavior的show方法,用以显示它的UI。在Unhover事件触发时,则调用hide方法使UI隐藏。
我们现在编译代码,然后刷新页面。哎,出错了。如图:
这里出错的原因是因为HoverBehavior没有定义。因此我会回到Extender的代码,添加RequiredScriptAttribute:
[Designer(typeof(FontSizeDesigner))] [ClientScriptResource("FontSize.FontSizeBehavior", "FontSize.FontSizeBehavior.js")] [TargetControlType(typeof(Control))] [RequiredScript(typeof(PopupExtender))] [RequiredScript(typeof(HoverExtender))] public class FontSizeExtender : ExtenderControlBase { ... }
在这里我们传入PopupExtender和HoverExtender的类型,这使Toolkit在使用当前Behavior时保证了PopupBehavior和HoverBehavior的脚本已经被加载了。我们重新编译代码,打开页面。没有发现异常。
当我们将鼠标移动至上方的Panel时,可以发现含有按钮的Panel显示在了右上角。如图:
可以发现,当我们把鼠标移动到右上角的按钮时,它们也不会消失,这个就是HoverBehavior的hoverElement属性的作用。当鼠标移到其它地方时,右上角的按钮就消失了。而PopupBehavior的作用就是显示和隐藏制定的元素。
现在我们需要来处理按钮的功能了。我们回到Behavior的代码,首先在initialize方法内添加一些简单的代码。如下:
initialize : function() { FontSize.FontSizeBehavior.callBaseMethod(this, 'initialize'); ... // set up our handlers for the +/- behavior // if (this._incrementElement) { this._incrementHandler = Function.createDelegate(this, this._onIncrementFontSize); $addHandler(this._incrementElement, "click", this._incrementHandler); } if (this._decrementElement) { this._decrementHandler = Function.createDelegate(this, this._onDecrementFontSize); $addHandler(this._decrementElement, "click", this._decrementHandler); } },
就像我们之前所提到的那样,ASP.NET AJAX为了跨浏览器,提供了一些方法让我们进行统一的操作。在这里,我们使用了$addHandler方法,它为任意浏览器中都提供了相同的注册客户端事件功能。比如我们使用了$addHandler方法为增大字体的按钮注册了click事件。如果您写JavaScript代码的话,使用的是“onclick”,请注意这里只是“click”,因为ASP.NET AJAX需要在多个浏览器中工作。$addHandler方法的第一个参数则是需要为它添加事件的HTML元素,在这里就是“增大”按钮。最后一个则是我们需要注册给click事件的方法。
接着我们需要添加一些方法。首先我们在这里要添加的是一个辅助方法_setFontSize,如下:
_setFontSize : function(size) { // helper function for setting the font size // this._fontSize = size; // append the units. // this.get_element().style.fontSize = size + this._fontUnit; },
由于用户可能以不同的单位来设置字体,例如px或者pt,因此我们提供了_fontUnit。在这里get_element()方法获得了当前Behavior所在的元素,也就是需要改变字体大小的Panel。然后就以最普通不过的方法设置其字体。
然后我们再定义增大和减小字体的方法,如下:
_onDecrementFontSize : function(eventArgs) { var fontSize = this._getCurrentFontSize(); if (fontSize > this.get_MinFontSize()) { fontSize -= this._fontSizeIncrement; } this._setFontSize(fontSize); eventArgs.preventDefault(); }, _onIncrementFontSize : function(eventArgs) { var fontSize = this._getCurrentFontSize(); if (fontSize < this.get_MaxFontSize()) { fontSize += this._fontSizeIncrement; } this._setFontSize(fontSize); eventArgs.preventDefault(); },
这是两个非常简单的Handler,在点击两个按钮时会调用它们。在这两个方法里,首先使用_getCurrentFontSize方法得到当前的字体大小,然后改变目标元素的字体大小。需要注意的是,我们这里还调用了eventArgs的preventDefault方法。这里又带来了一个跨浏览器的问题。如何在跨浏览器的情况下组织一个事件被继续传播(propagation)?例如我们需要在相应一个Label的click事件后阻止默认的响应方式。在IE您会使用“return false”这样的作法,而在FireFox里您则会使用“e.returnValue = false”,而使用ASP.NET AJAX话您只需要使用同一种方法。
下面则是_getCurrentFontSize方法。这是一个巧妙的方法,可以用来“测量”出某个元素当前字体大小,在这里就是当前Panel的文字大小。如下(代码较长,就不完整贴出了,朋友们可以看一下附件里的代码):
_getCurrentFontSize : function() { // this function figures out the current font size of the element. // ... return this._fontSize; },
然后就需要为页面上其它的Panel添加相应的UI和Extender了。如下:
<fontSize:FontSizeExtender ID="FontSizeExtender1" runat="server" SizePanelControlID="extenderUI1" TargetControlID="pnlLeftColumn" DecreaseSizeControlID="btnDec1" IncreaseSizeControlID="btnInc1" PanelHoverCssClass="panelHover" /> <asp:Panel ID="extenderUI1" runat="server" CssClass="popup"> <asp:Button ID="btnDec1" runat="server" Text="-" /> <asp:Button ID="btnInc1" runat="server" Text="+" /> </asp:Panel> <fontSize:FontSizeExtender ID="FontSizeExtender2" runat="server" SizePanelControlID="extenderUI2" TargetControlID="pnlMiddleColumn" DecreaseSizeControlID="btnDec2" IncreaseSizeControlID="btnInc2" PanelHoverCssClass="panelHover" /> <asp:Panel ID="extenderUI2" runat="server" CssClass="popup"> <asp:Button ID="btnDec2" runat="server" Text="-" /> <asp:Button ID="btnInc2" runat="server" Text="+" /> </asp:Panel> <fontSize:FontSizeExtender ID="FontSizeExtender3" runat="server" SizePanelControlID="extenderUI3" TargetControlID="pnlRightColumn" DecreaseSizeControlID="btnDec3" IncreaseSizeControlID="btnInc3" PanelHoverCssClass="panelHover" /> <asp:Panel ID="extenderUI3" runat="server" CssClass="popup"> <asp:Button ID="btnDec3" runat="server" Text="-" /> <asp:Button ID="btnInc3" runat="server" Text="+" /> </asp:Panel>
编译,重新打开页面。可以发现,现在点击增大/减小按钮字体已经能够变化了(大家可以打开Demo_Step_3的页面察看一下效果)。可能大家会想一个问题,能不能在Panel之间共享一组控制按钮?在这里是不行的,因为一组按钮会被绑定多个Behavior,在click被触发时也会调用多个事件,这样您会发现所有的Panel字体一起变大或者变小了。所以我们在页面中使用了重复的按钮。
Step 3:Saving Client State / Adding Animation Support
可能大家已经发现了,在页面右下方有一个按钮,当我们点击这个按钮时,页面进行了一个PostBack。这里有个问题,页面上字体大小的改变在PostBack之后失效了,我们需要解决这个问题。
ASP.NET AJAX Control Toolkit提供了一系列的高级特性,其中一点就是提供了Client State机制可以在服务器端和客户端保存和传递状态。另外就像我之前提到的,Toolkit中提供了一套非常灵活的动画效果。我们来看一下应该如何使用它们。
如果要使用Client State机制,在服务器端我们要为Extender添加一个构造函数,将EnableClientState属性设为true。如下:
public FontSizeExtender() { EnableClientState = true; }
然后需要添加客户端的脚本,您可以直接使用辅助方法,在客户端_setFontSize方法里添加如下代码:
_setFontSize : function(size) { // helper function for setting the font size ... // set the clientState // this.set_ClientState(size + ":" + this._fontUnit); },
您可以使用set_ClientState方法设置一个字符串,在页面PostBack之后在服务器端就可以得到这个值。更加重要的是,在PostBack之后,服务器段也会重新设置ClientState的值。在这里我们设定ClientState的值为当前的字体大小+冒号+当前使用的单位。
当然,我们还必须保证在组件被构造时我们能够重新取回这个值。我们在客户端initialize方法内添加如下代码:
initialize : function() { FontSize.FontSizeBehavior.callBaseMethod(this, 'initialize'); ... // check client state // var clientState = this.get_ClientState(); if (clientState) { var stateItems = clientState.split(":"); if (stateItems.length) { this._fontUnit = stateItems[1]; this._setFontSize(parseInt(stateItems[0])); } } },
通过另外一个辅助方法get_ClientState我们可以得到ClientState的值。由于initialize方法会在组件被初始化时被调用,因此组件会在一开始重新获得字体大小和单位等状态。这就是我们在PostBack之间保存字体状态的方法。
编译,重新打开页面,点击按钮改变某个Panel的字体大小。再点击下方按钮,可以发现在PostBack之后字体大小被正确恢复了。
最后我们来为UI的显示和隐藏添加动画效果,首先修改_onTargetHover方法。如下:
_onTargetHover : function(eventArgs) { var e = this.get_element(); Sys.UI.DomElement.addCssClass(e, this._hoverCssClass); if (this._popupBehavior && !this._popupVisible) { this._popupVisible = true; this._stopAnimations(); // call show to make the the popup visible // this._popupBehavior.show(); // now animate it's opacity // var anim = $create( AjaxControlToolkit.Animation.FadeInAnimation, {target: this._sizePanelElement, duration: .25, minimumOpacity: 0, maximumOpacity:.75}); // we cache the running animation so we can cancel it if we need to hide. // this._sizePanelElement._runningAnimation = anim; var handler = Function.createDelegate(this, function() { this._sizePanelElement._runningAnimation = null; }); anim.add_ended(handler); anim.play(); } },
动画效果也是组件,也能够通过$create方法创建。同样我们还需要修改_onTargetUnhover方法,如下:
_onTargetUnhover : function(eventArgs) { var e = this.get_element(); Sys.UI.DomElement.removeCssClass(e, this._hoverCssClass); if (this._popupBehavior && this._popupVisible) { this._popupVisible = false; // make sure another animation isn't already running. // this._stopAnimations(); var anim = $create( AjaxControlToolkit.Animation.FadeOutAnimation, {target: this._sizePanelElement, duration: .15, minimumOpacity: 0, maximumOpacity:.75}); this._sizePanelElement._runningAnimation = anim; var handler = Function.createDelegate(this, function() { // clear out our state. // this._sizePanelElement._runningAnimation = null; this._popupVisible = false; this._popupBehavior.hide(); }); anim.add_ended(handler); anim.play(); } },
我们还需要添加一个_stopAnimations方法。如下:
_stopAnimations : function() { // stop any running animation // if (this._sizePanelElement && this._sizePanelElement._runningAnimation) { this._sizePanelElement._runningAnimation.stop(); this._sizePanelElement._runningAnimation = null; } },
自然也别忘了给服务器端的Extender添加所需的CustomAttribute:
[RequiredScript(typeof(AnimationScripts))]
编译,重新打开页面。现在可以看到,页面上的按钮的显示和隐藏就会产生美妙的淡入淡出效果了(大家可以打开页面观察一下效果)。
我们现在还在招募志愿的开发人员。我们计划最终会在ASP.NET AJAX Control Tookit中有超过50个组件。最终我们会把这个项目完全交付给社区,这是我们的长期计划。而且随着ASP.NET AJAX的改进,Toolkit也会相应的进步。
感想:
1、说话真比写字简单多了
2、一个示例需要真的需要精心设计,如此优秀的示例实在不简单。
3、累……