65.9K
CodeProject 正在变化。 阅读更多。
Home

开发支持 Silverlight 的 ASP.NET 控件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.52/5 (50投票s)

2007年12月16日

LGPL3

6分钟阅读

viewsIcon

118368

downloadIcon

670

本文展示了如何借助新发布的 ASP.NET 3.5 Extensions CTP,将 MS Silverlight 提供的革命性 UI 引入 ASP.NET 控件开发领域。

Screenshot - button.jpg

目录

引言

在本文中,我将向您展示如何创建一个自定义 ASP.NET 控件,该控件采用 Microsoft Silverlight 提供的新一代 UI。因此,我们的目标是创建一个功能上类似于标准 ASP.NET 控件(如 Button)的自定义控件,但其呈现方式不是通过 CSS 类,而是通过 Silverlight 进行自定义。我将此类控件称为支持 Silverlight 的控件。ASP.NET 控件开发这种精彩概念的基础是 ASP.NET 3.5 Extension CTP(几天前发布的)提供的 Silverlight 控件(请勿与该技术名称混淆)。此外,Silverlight 控件也可视为 ASP.NET Futures 提供的 Xaml 控件的更新版本,但它们之间存在一些关键差异,使得我的示例很难在 ASP.NET Futures 中正常工作,因此我强烈建议您使用 ASP.NET 3.5 Extensions。

支持 Silverlight 的 ASP.NET 控件架构

Screenshot - arhitecture.jpg

支持 Silverlight 的控件是一个自定义的 AJAX 控件,其中集成了 ASP.NET 3.5 Extensions Silverlight 控件。(如果您不熟悉 ASP.NET AJAX 控件开发过程,最好在开始阅读本文之前,阅读精彩的《ASP.NET AJAX in Action》一书。)

与 AJAX 控件一样,支持 Silverlight 的控件由服务器和客户端部分组成,它们通过 ScriptDesriptors 进行交互。在服务器端,我们继承自 `WebControl` 类,实现 `IScriptControl` 接口将我们的控件与其客户端部分连接起来,在 `ScriptManager` 中注册我们的控件,最后将 Silverlight 控件添加到其控件集合中。但在客户端,与 AJAX 控件不同的是,我们继承自 `Sys.UI.Silverlight.Control` 而不是 `Sys.UI.Control`,并且传递给 `ScriptDescriptor` 类的构造函数的 `elementID` 应该是集成 Silverlight 控件的 ClientID,而不是 `this.Client.ID`。

因此,结果是,在我们的 ASP.NET 页面中,我们将有以下标记

<MyNamespace:MySilverlightEnabledControl ID="Control1" 
  runat=""server"" SkinUrl="MySkin.xaml" Width="200" 
  Height="100" Click="Control1_Clicked" />

从这个示例中可以看到,支持 Silverlight 的控件标记看起来像标准的 ASP.NET 控件,但我们有一个 `SkinUrl` 属性,而不是 `CssClass` 属性。

为了实践这种 ASP.NET 控件开发概念,我选择了 `Button` 控件。它的功能和属性(如宽度、高度、文本以及可由 ASP.NET 引擎处理的单击事件)将与标准的 ASP.NET `Button` 控件类似,但其 UI 将由 Silverlight 皮肤进行自定义。

Screenshot - button.jpg

注意:在本文中,我将不展示如何创建 XAML 皮肤(您将在演示项目中找到两个)。您只需要知道它具有一个脉冲动画(`PulseStoryBoard`)、一个根元素(`RootCanvas`)和一个文本字段(`TextBlock`)。因此,您的自定义皮肤必须包含这些具有这些名称的元素。在初始化过程中将使用它们的名称。

创建自定义支持 Silverlight 的 ASP.NET 按钮控件

首先,我们应该创建一个 ASP.NET 服务器控件项目,并添加一个 Web.Extensions 3.6.0.0 引用。

Screenshot - addproject.jpg

如前所述,我们的控件有客户端和服务器部分。让我们一步一步开始创建服务器部分

  1. 创建一个包含空类 `Button` 的 *.cs* 文件,并包含以下命名空间
  2. using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.SilverlightControls;
    
    namespace SilverlightUIControls {
    
        public class Button: WebControl, IPostBackEventHandler, IScriptControl
        {
            /// <summary>
            /// Constructor for Button where we specify root element for our control
            /// 
            public Button() : base(HtmlTextWriterTag.Div)
            {
                
            }
        }
  3. 现在,我们应该为我们的控件添加必要的属性,以便用户可以根据自己的需求自定义设计。
  4. /// <summary>
    /// Source of xaml file which is used for skinning of our control
    /// 
    public string SkinUrl
    {
        get { return (string)ViewState["SkinUrl"]; }
        set { ViewState["SkinUrl"] = value; }
    }
    
    /// <summary>
    /// Width of our button control
    /// ASP.NET WebControl has its own width property, but we need to overload
    /// 'cause its format is unsuitable in our case(e.g 100px)
    /// 
    public new int Width
    {
        get { return (int)ViewState["Width"]; }
        set { ViewState["Width"] = value; }
    }
    
    /// <summary>
    /// Height of our button control
    /// 
    public new int Height
    {
        get { return (int)ViewState["Height"]; }
        set { ViewState["Height"] = value; }
    }
    
    /// <summary>
    /// Text(Name) within our button control
    /// 
    public string Text
    {
        get {
            if (ViewState["Text"] != null)
            {
                return (string)ViewState["Text"];
            }
            else return "Button";
        }
        set { ViewState["Text"] = value; }
    }
    
    /// <summary>
    /// Font family of the text
    /// 
    public string FontFamily
    {
        get
        {
            if (ViewState["FontFamily"] != null)
            {
                return (string)ViewState["FontFamily"];
            }
            else return "Comic Sans MS";
        }
        set { ViewState["FontFamily"] = value; }
    }
    
    /// <summary>
    /// Font size of the text
    /// 
    public int FontSize
    {
        get
        {
            if (ViewState["FontSize"] != null)
            {
                return (int)ViewState["FontSize"];
            }
            else return 20;
        }
        set { ViewState["FontSize"] = value; }
    }
  5. 创建一个私有方法,您将在其中初始化 ASP.NET 3.5 Extensions Silverlight 控件(这只是 Silverlight 插件的初始化;文本大小、字体系列和大小的调整将在客户端完成),并将其添加到自定义控件的 `Controls` 集合中。此方法应在 ASP.NET 控件生命周期的预呈现阶段被调用。
  6. private void CreateSilverlightControl()
    {
        _silverlightControl = new Silverlight();
        _silverlightControl.Source = SkinUrl;
        _silverlightControl.Width = Width;
        _silverlightControl.Height = Height;
        _silverlightControl.Windowless = true;
        _silverlightControl.ID = this.ID + "Silverlight";
        _silverlightControl.ClientType = "SilverlightUIControls.Button";
        _silverlightControl.PluginBackColor = System.Drawing.Color.Transparent;
    
        Controls.Add(_silverlightControl);
    }
  7. 为了连接我们控件的服务器和客户端部分,我们需要做几件事。首先,我们应该在预呈现阶段在脚本管理器中注册我们的脚本控件,并在控件生命周期的呈现阶段注册其脚本描述符。之后,我们应该实现 `IScriptConrol` 接口来指定一个指向包含客户端部分控件的 *.js* 文件的引用,以及需要发送到客户端以初始化我们 Silverlight UI 的属性。
  8. protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
    
        if (_silverlightControl == null)
        {
            CreateSilverlightControl();
        }
    
        ScriptManager manager;
        manager = ScriptManager.GetCurrent(this.Page);
    
        if (manager == null)
            throw new InvalidOperationException(
                        "A ScriptManager is required on the page.");
    
        manager.RegisterScriptControl(this);
    }
    
    protected override void Render(HtmlTextWriter writer)
    {
        base.Render(writer);
    
        Page.ClientScript.GetPostBackEventReference(this, "");
        ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
    }
    
    public IEnumerable<scriptdescriptor> GetScriptDescriptors()
    {
        ScriptControlDescriptor descriptor = 
           new ScriptControlDescriptor("SilverlightUIControls.Button", 
                                       _silverlightControl.ClientID);
        descriptor.AddProperty("text", this.Text);
        descriptor.AddProperty("width", this.Width);
        descriptor.AddProperty("height", this.Height);
        descriptor.AddProperty("fontSize", this.FontSize);
        descriptor.AddProperty("fontFamily", this.FontFamily);
        descriptor.AddProperty("buttonId", this.ID);
    
        yield return descriptor;
    }
    
    public IEnumerable<scriptreference> GetScriptReferences()
    {
        yield return new ScriptReference(
            Page.ClientScript.GetWebResourceUrl(typeof(Button), 
            "SilverlightUIControls.Button.js"));
    }
  9. 在服务器端,我们最后要做的是实现 `IPostBackEventHandler` 接口,以便我们的按钮在单击后执行 postback。
  10. public event EventHandler Click;
    
    public void RaisePostBackEvent(string eventArgument)
    {
        OnClick(new EventArgs());
        
    }
    protected virtual void OnClick(EventArgs e)
    {
        if (Click != null)
            Click(this, e);
    }

现在,我们准备开始开发客户端部分

  1. 在项目中添加一个 JavaScript 文件,并将其标记为嵌入资源。
  2. Screenshot - addjs.jpg

    Screenshot - mark.jpg

  3. 在项目的 *AssemblyInfo.cs* 中,将此 JavaScript 文件描述为 `WebResource`。
  4. [assembly: WebResource("SilverlightUIControls.Button.js", "text/javascript")]
  5. 在 *.js* 文件中,创建一个继承自 `Sys.UI.Silverlight.Control` 的 JavaScript 类,该类将初始化我们的 Silverlight 按钮皮肤(设置文本及其字体大小和系列、调整大小、文本居中),并处理其事件。
  6. 注意:JavaScript 中没有类,因此它们是通过原型和函数模拟的。Microsoft AJAX 库标准化了 JavaScript 中类的模拟过程。您可以在我文章开头提到的书中详细了解此过程。

  7. 初始化:关于此步骤,您需要了解的一切都可以在代码注释中找到。
  8. _initializeFields: function(slhost) 
    {
        var host = slhost.content;
        
        this._rootCanvas = host.findName('RootCanvas');
        this._pulseStoryBoard = host.findName('PulseStoryBoard');
        this._textBlock = host.findName('TextBlock');
               
        this._textBlock.text = this._text;
        this._textBlock.fontFamily = this._fontFamily;
        this._textBlock.fontSize = this._fontSize;  
        
        // before we call xaml resizing function, 
        // we should calculate X and Y resizing coefficients
        // and resize the root canvas
        
        var horizontalResizePercent = parseFloat(this._width) / this._rootCanvas.width;
        var verticalResizePercent = parseFloat(this._height) / this._rootCanvas.height;
    
        this._rootCanvas.width = this._rootCanvas.width * horizontalResizePercent;
        this._rootCanvas.height = this._rootCanvas.height * verticalResizePercent;
        
        this._rootCanvas.setValue("Canvas.Left", 
          this._rootCanvas.getValue("Canvas.Left") * horizontalResizePercent);
        this._rootCanvas.setValue("Canvas.Top", 
          this._rootCanvas.getValue("Canvas.Top") * verticalResizePercent);          
        
        this._Resize(this._rootCanvas, horizontalResizePercent, verticalResizePercent);
        
        this._centerText(this._rootCanvas, this._textBlock);      
    
    },
    
    // function for centering textblock in the canvas
    
    _centerText: function (canvas, textBlock)
    {
        var left = (canvas.Width - textBlock.ActualWidth) / 2;
        var top = (canvas.Height - textBlock.ActualHeight) / 2;
        textBlock.SetValue("Canvas.Left", left);
        textBlock.SetValue("Canvas.Top", top);   
    },
    
    // recursive function for xaml resizing
        
    _Resize: function (root, horizontalResizePercent, verticalResizePercent)
    {
        for (var i = 0; i < root.children.count - 1; i++)
        {
    
            var child = root.children.getItem(i);
            if (child.toString() != 'TextBox')
            {
                child.width = child.width * horizontalResizePercent;
                child.height = child.height * verticalResizePercent;
                
                child.setValue("Canvas.Left", 
                  child.getValue("Canvas.Left") * horizontalResizePercent);
                child.setValue("Canvas.Top", 
                  child.getValue("Canvas.Top") * verticalResizePercent);  
                              
                if (child.toString() == 'Rectangle')
                {               
                    child.RadiusX = child.RadiusX * horizontalResizePercent;
                    child.RadiusY = child.RadiusY * verticalResizePercent;
                    if (horizontalResizePercent > verticalResizePercent)
                    {
                        child.strokeThickness = 
                          child.strokeThickness * horizontalResizePercent;
                    }
                    else
                    {
                        child.strokeThickness = 
                          child.strokeThickness * verticalResizePercent;                    
                    } 
                }
                
                if (child.toString() == 'Canvas')
                {
                    this._Resize(child,horizontalResizePercent, 
                                 verticalResizePercent);
                }
            }
        }           
    },
  9. 最后我们需要做的是处理事件。所有事件都由我们皮肤的根画布调用。以下是其处理程序
    • `_onButtonMouseEnter` 和 `_onButtonMouseLeave` 处理程序用于启动和停止按钮脉冲动画。
    • `_onButtonClicked` 调用 `__doPostBack` 函数,告知 ASP.NET 引擎调用按钮单击事件。`__doPostBack` 需要控件 ID,这就是为什么我们通过脚本描述符将其发送到客户端。
    _onButtonMouseEnter: function(sender, e)
    {
        this._pulseStoryBoard.begin(); 
    },    
    _onButtonMouseLeave: function(sender, e)
    {
        this._pulseStoryBoard.stop(); 
    },
    _onButtonClick: function(sender, e)
    {    
        __doPostBack(this._buttonId,'');
    },

软件要求

  1. Visual Studio 2008
  2. ASP.NET 3.5 Extensions CTP

结论

希望您喜欢阅读我的文章,现在您已准备好创建自己的控件。使用这种控件开发概念,您还可以创建令人惊叹的网格、菜单和其他复杂的控件。还有一种方法可以使用托管代码而不是 JavaScript,我将在未来的文章中详细介绍。

如果您有任何不明白的地方,请在评论部分留言,我将很乐意回答您的问题。如果您觉得文章有用,请不要忘记投票!;-)

参考文献

  1. Nikhil Kothari “使用 Silverlight 开发 ASP.NET AJAX 控件”,MIX07 视频。
  2. Manning, Alessandro Gallo, ASP.NET AJAX in Action。
开发支持 Silverlight 的 ASP.NET 控件 - CodeProject - 代码之家
© . All rights reserved.