ASP.NET 揭秘 第二十八章 开发自定义控件(ASP.MET)

ASP.NET 揭秘 第二十八章 开发自定义控件(ASP.MET)

1.Creating a Simple Control(建立简单的自定义控件)

 通过声明 Inherits 完成控件的继承,通过重载控件的Render 方法
 完成控件的重画.(类似于WinForm中的Paint)
 
 Render 方法只有一个参数:HtmlTextWriter 类,你可以通过HtmlTextWriter 类的Write方法显示文字.
 

 示例代码如下:

Listing 28.1 HelloWorld.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class HelloWorld: Inherits Control

  Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
    objTextWriter.Write( "Hello World!" )
  End Sub

End Class

End Namespace

 

2.Using HtmlTextWriter(使用HtmlTextWriter对象)
 HtmlTextWriter对象包括以下主要方法:

AddAttribute— Adds an HTML attribute that is rendered with a tag when the RenderBeginTag method is called; for example, Color="Red"
 添加一个HTML命名属性标记

AddStyleAttribute— Adds a Style attribute that is rendered with a tag when the RenderBeginTag method is called; for example, Style="Font:Arial;"
 添加一个样式命名标记

RenderBeginTag— Renders an opening HTML tag with attributes; for example, <Font>
 生成一个命名开始标记

RenderEndTag— Renders the closing HTML tag for the last tag opened with the RenderBeginTag method; for example, </Font>
 生成一个命名结束标记

Write— Renders text
 生成文字

WriteAttribute— Renders an attribute name and value pair; for example, Color="Red"
 添加下个命名属性

WriteBeginTag— Renders the beginning of an HTML tag without the closing >; for example, <Font
 添加一个命名开始标记,注意不包括结束后缀'>'

WriteFullBeginTag— Renders a full opening HTML tag; for example, <Font>
 添加一个HTML命名开始标记

WriteLine— Renders text followed by a line terminator
 添加一行文字

WriteLineNoTabs— Renders text without automatically adding tabs
 添加一行文字,不自动包含'Tab'

WriteStyleAttribute— Renders a style attribute name and value pair; for example, Style="Font:Arial;"
 添加一个样式命名标记


 如下示例:

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  Dim strText As String
  strText = "<font color=""green"">Hello World!</font>"
  objTextWriter.Write( strText )
End Sub


 示例二:

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "color", "green" )
  objTextWriter.RenderBeginTag( "font" )
  objTextWriter.Write( "Hello World!" )
  objTextWriter.RenderEndTag()
End Sub

在示例二中,输出的内容如下:

<font color="green">Hello World!</font>

 

 

3.Adding Properties and Methods to Controls(为自定义控件添加属性方法)
 通过 Public 为自定义控件添加属性和方法,和WinForm下是一样的.

 示例:

Listing 28.3 ShowColor.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ShowColor: Inherits Control

Public Text As String
Public Color As String

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "Color", Color )
  objTextWriter.RenderBeginTag( "Font" )
  objTextWriter.Write( Text )
  objTextWriter.RenderEndTag()
End Sub

End Class

End Namespace

 使用属性访问器:
 你可以使用Get和SET访问器建立属性.这样你可以在对应的过程中进行其它操作处理.

 使用方法:
  可以定义一个Public方法,在控件外部可以直接调用.

 示例代码下:

Listing 28.7 ReverseMethodControl.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports Microsoft.VisualBasic

Namespace myControls

Public Class ReverseMethodControl: Inherits Control

Public Text As String

Public Sub Reverse()
  Text = StrReverse( Text )
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.Write( Text )
End Sub

End Class

End Namespace


 调用方法如下:

Sub Page_Load
  ctrlReverse.Text = "Hello World!"
  ctrlReverse.Reverse()
End Sub


4.Parsing Inner Content(分[剖]析包括的内容)

 以下我们讨论如何为控件添加用户属性,下面我们看看如何通过添加值,在Control's的开始结束标记里,为控件分配对应的值(集合).
 每个有子控件的控件,都有一个Controls集合,当添加Text到Control's的开始结束标记里时,Text实际上是添加到了Contrls集体中的LiteralControl中. 如果你添加一个TextBox控件,两个控件将添加到Controls集合中, LiteralControl 和 TextBox.
 
 示例代码如下:

Listing 28.9 ShowGreen.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ShowGreen: Inherits Control

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  Dim strInnerText As String

  If IsLiteralContent
    strInnerText = CType( Controls( 0 ), LiteralControl ).Text
    objTextWriter.AddAttribute( "color", "green" )
    objTextWriter.RenderBeginTag( "font" )
    objTextWriter.RenderBeginTag( "b" )
    objTextWriter.Write( strInnerText )
    objTextWriter.RenderEndTag()
    objTextWriter.RenderEndTag()
  End If
End Sub

End Class

End Namespace


 以上面示例中,Render 方法中的IsLiteralContent 方法用来判断,控件在开始和结束标记中是否包含literal Content(文字内容),如果控件包含LiteralControl近件,IsLiteralContent 方法返回为Ture.
 通过Controls 集合,返回LiteralControl ,再通过正确的类型转换,strInnerText变量被分配到了Text属性中.strInnerText 变量输出的文字使用的是绿粗体.
 

5.Adding Child Controls to a Control(为控件添加一个或多个子控件)
 许多标准自定义控件在定义中使用了控件,如:ListBox,RadioButtonList
 如下所示,ListBox控件中包含了三个ListItem控件,当ListBOx显示时,甩的有ListItem控件的Text被显示到了ListBox控件中.
 
<asp:ListBox
  Runat="Server">
  <asp:ListItem Text="First Item" />
  <asp:ListItem Text="Second Item" />
  <asp:ListItem Text="Third Item" />
</asp:ListBox>
 
 你可以建立轻量的ListGox控件,需要定义两个控件,并把第二个控件作为子控件嵌入到第一个控件内.
 示例代码如下:

Listing 28.11 SimpleRotator1.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class SimpleRotator1: Inherits Control

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter)
  Dim objRandom As New Random
  Dim intRanIndex As Integer
  Dim objSelectedItem As RotatorItem1

  If Controls.Count > 0 Then
    intRanIndex = objRandom.Next( Controls.Count )
    objSelectedItem = CType( Controls( intRanIndex ), RotatorItem1 )
    objTextWriter.Write( objSelectedItem.Text )
  End If
End Sub

End Class

Public Class RotatorItem1 : Inherits Control
 Public Text As String
End Class

End Namespace


 在以下示例中,第一个控件SimpleRotator1,和第二个RotatorItem1控件.
 以SimpleRotator1,控件显示时,控件中的Controls集合被选中,对应的Value(Text属性)被选中并显示.
 
 
 


6.Examining Custom Controls and Events(完成自定义控件的事件处理)
 所有的控件都有以下6个事件:
 
DataBinding— This event is typically raised when the DataBind method is called.
 在数据绑定时触发.
Disposed— This event occurs when the control is released from memory. This is a good place to close expensive resources, such as database connections.
 在控件销毁时触发.
Init— This event occurs when the control is first initialized.
 在控件第一次初始化时触发.
Load— This event occurs when the control is loaded into the Page object.
 在控件加载到页面时触发.
PreRender— This event occurs right before the control's Render method is called.
 在控件的Render方法调用后触发.
Unload— This event occurs when the control is unloaded from memory.
 在控件从内存时卸载时触发.

 示例代码如下:
Listing 28.17 ControlEvents.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ControlEvents
Inherits Control

Protected Overrides Sub OnInit( e As EventArgs )
  Context.Response.Write( "<li> OnInit Event!" )
End Sub

Protected Overrides Sub OnLoad( e As EventArgs )
  Context.Response.Write( "<li> OnLoad Event!" )
End Sub

Protected Overrides Sub OnPreRender( e As EventArgs )
  Context.Response.Write( "<li> OnPreRender Event!" )
End Sub

End Class

End Namespace

 为控件添加自定义事件:
 许多ASP.NET标准控件触发自定义事件:如AdRotator控件触发AdCreated事件
       Calendar 控件触发DayRender事件.

 通过以下两步可以为控件添加自定义事件:
 a.Declare an event by using the Event statement.
  通过Event 声明事件.
 b.Raise the event by creating a subroutine that contains the RaiseEvent statement.
  在子过程中通过RaiseEvent 触发事件.


 示例代码如下:

Listing 28.18 RandomEvent.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class RandomEvent
Inherits Control

Public Event Event1( s As Object, e As EventArgs )
Public Event Event2( s As Object, e As EventArgs )

Protected Sub OnEvent1( e As EventArgs )
  RaiseEvent Event1( Me, EventArgs.Empty )
End Sub

Protected Sub OnEvent2( e As EventArgs )
  RaiseEvent Event2( Me, EventArgs.Empty )
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  Dim objRandom As New Random
  Dim intRanSelect As Integer

  intRanSelect = objRandom.Next( 2 )
  If intRanSelect = 0 Then
    OnEvent1( EventArgs.Empty )
  Else
    OnEvent2( EventArgs.Empty )
  End If
End Sub

End Class

End Namespace

 

7.Participating in Postbacks(处理"提交后")
 设想一下,如果你希望你的网站上全有文本框都是Blue背景,Yelow前景,最简单的办法是建立一个定义具有正确属性的自定义控件.如下示例:control in Listing 28.20 renders an <input> tag with Blue背景,Yelow前景.

Listing 28.20 BlueTextBox.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class BlueTextBox
Inherits Control

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter)
  objTextWriter.AddAttribute( "Name", Me.UniqueID )
  objTextWriter.AddStyleAttribute( "background-color", "Blue" )
  objTextWriter.AddStyleAttribute( "color", "Yellow" )
  objTextWriter.RenderBeginTag( "input" )
  objTextWriter.RenderEndTag
End Sub

End Class

End Namespace


 如果你提义你的的BlueTextBox控件,就会发现BlueTextBox控件不能保持已有的值,也就是说,这个控件不支持PostBacks.
 对了让BlueTextBox控件支持PostBacks,你需要现实IPostBackDataHandler接口:该接口定义了以下两个方法.
 LoadPostData— Processes the data being posted back to the server
  处理服务器端已存在的提交后的数据.

 RaisePostDataChangedEvent— Raises the change event for a control
  触发相应的事件.
 示例代码如下:

Public Class BlueTextBoxPostBack
Inherits Control
Implements IPostBackDataHandler

Public Text As String

Public Function LoadPostData( PostDataKey As String, Values As NameValueCollection) As
 Boolean _
Implements IPostBackDataHandler.LoadPostData
  Text =  Values( Me.UniqueID )
  Return False
End Function

Public Sub RaisePostDataChangedEvent() _
Implements IPostBackDataHandler.RaisePostDataChangedEvent
  ' Raise Change Event
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "Name", Me.UniqueID )
  objTextWriter.AddStyleAttribute( "background-color", "Blue" )
  objTextWriter.AddStyleAttribute( "color", "Yellow" )
  objTextWriter.AddAttribute( "value", Text )
  objTextWriter.RenderBeginTag( "input" )
  objTextWriter.RenderEndTag
End Sub

End Class


 在上面示例中,我们添加了两个过程,LoadPostData过程有以下两个参数:
  PostDataKey :represents the unique ID for the control
   指出当前控件的唯一ID.
  Values :represents the values of all the form elements being posted back to the server.  
   批出服务器端已存在的提交后的数据.
  
 在LoadPostData过程中,我们注意到为当前控件的Values集合中的当前值分配了Text变量,接成返回False.
 注意:RaisePostBackDataChanged过程在LoadPostData过程返回为True时被调用. 
 在Render 方法中,你可以通过AddAttribute 添加名称为Value的属性,同时为Value属性分配Text变量.

 
 为了提交RaisePostBackDataChanged事件,可以进行以下处理:


Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections.Specialized

Namespace myControls

Public Class BlueTextBoxChanged
Inherits Control
Implements IPostBackDataHandler

Public Event TextChanged( s As Object, e As EventArgs )

Public Property Text As String
Set
  ViewState( "Text" ) = Value
End Set
Get
  If ViewState( "Text" ) <> Nothing Then
    Return ViewState( "Text" )
  Else
    Return String.Empty
  End If
End Get
End Property

Public Function LoadPostData( PostDataKey As String, Values As NameValueCollection ) As
 Boolean _
Implements IPostBackDataHandler.LoadPostData
  Dim strNewValue As String

  strNewValue = Values( Me.UniqueID )
  If strNewValue <> Text Then
    Text = strNewValue
    Return True
  Else
    Return False
  End If
End Function

Public Sub RaisePostDataChangedEvent() _
Implements IPostBackDataHandler.RaisePostDataChangedEvent
  OnTextChanged( EventArgs.Empty )
End Sub

Protected Sub OnTextChanged( e As EventArgs )
  RaiseEvent TextChanged( Me, e )
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "Name", Me.UniqueID )
  objTextWriter.AddStyleAttribute( "background-color", "Blue" )
  objTextWriter.AddStyleAttribute( "color", "Yellow" )
  objTextWriter.AddAttribute( "value", Text )
  objTextWriter.RenderBeginTag( "input" )
  objTextWriter.RenderEndTag
End Sub

End Class

End Namespace

 处理PostBacks事件;
 
 你可以使用以下三种ASP.NET标准控件向Form提交数据:Button, ImageButton, LinkButton.当你提交一个Form时,所有的这三个控件自动生成并执行客户端JSP脚本.
 以下介绍如果建立自动生成并JSP脚本,并触发PostBack事件的Form元素.
 
Listing 28.26 TextBoxColor.vb
[View full width]
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections.Specialized

Namespace myControls

Public Class TextBoxColor
Inherits Control
Implements IPostBackDataHandler
Implements IPostBackEventHandler

Public BoxColor As String = "Blue"

Public Text As String

Public Sub RaisePostBackEvent(EventArgument As String) _
Implements IPostBackEventHandler.RaisePostBackEvent
  If (eventArgument = "Red" )
    Me.BoxColor = "Red"
  Else
    Me.BoxColor = "Blue"
  End If
End Sub

Public Function LoadPostData(PostDataKey As String, Values As NameValueCollection) As
 Boolean _
Implements IPostBackDataHandler.LoadPostData
  Dim strNewValue As String

  Text = Values( Me.UniqueID )
  Return False
End Function

Public Sub RaisePostDataChangedEvent() _
Implements IPostBackDataHandler.RaisePostDataChangedEvent
  ' Raise Change Event
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "Name", Me.UniqueID )
  objTextWriter.AddStyleAttribute( "background-color", BoxColor )
  objTextWriter.AddStyleAttribute( "color", "Yellow" )
  objTextWriter.AddAttribute( "value", Text )
  objTextWriter.RenderBeginTag( "input" )
  objTextWriter.RenderEndTag
  objTextWriter.WriteLine( "<p>" )
  objTextWriter.AddAttribute( "Type", "Button" )
  objTextWriter.AddAttribute( "Value", "Display Red!" )
  objTextWriter.AddAttribute( "OnClick", "JScript:" & Page.GetPostBackEventReference( Me,
 "Red" ) )
  objTextWriter.RenderBeginTag( "Input" )
  objTextWriter.RenderEndTag()
  objTextWriter.WriteLine( "&nbsp;&nbsp;" )
  objTextWriter.AddAttribute( "Type", "Button" )
  objTextWriter.AddAttribute( "Value", "Display Blue!" )
  objTextWriter.AddAttribute( "OnClick", "JScript:" & Page.GetPostBackEventReference( Me,
 "Blue" ) )
  objTextWriter.RenderBeginTag( "Input" )
  objTextWriter.RenderEndTag()
End Sub

End Class

End Namespace

 GetPostBackEventReference返顺一个客户端JSP脚本的引用,这个引用将导致触发Form的服务器端的PostBacks.
 第一个参数指出触发哪一个控件服务器端PostBacks事件.在上面示例中,使用me表示当前控件.
 第二个参数是要选的,做为PostBacks事件的参数被传到服务器端的PostBacks.在上面示例中,使用了"Red".

 当Display Red! button is  rendered 时,以下代码发送到了浏览器
 <input Type="Button" Value="Display Red!" OnClick="JScript:__doPostBack ('ctrl4','Red') " />
 Display Red! button 触发了客户端名为__doPostBack 的JSP脚本过程,注意:控件的ID被做为参数传递到了该过程中.

 当选择查看源码时,__doPostBack内容如下:

function __doPostBack(eventTarget, eventArgument) {
    var theform = document.ctrl1
    theform.__EVENTTARGET.value = eventTarget
    theform.__EVENTARGUMENT.value = eventArgument
    theform.submit()
}
 __doPostBack初始了两个参数,并向服务器端进行提交.
 也就是执行了以下过程:


Public Sub RaisePostBackEvent(EventArgument As String) _
Implements IPostBackEventHandler.RaisePostBackEvent
  If (eventArgument = "Red" )
    Me.BoxColor = "Red"
  Else
    Me.BoxColor = "Blue"
  End If
End Sub
 
 在上面过程中,修改了控件的背景颜色.
 当上在的控件显示Text Box时,可以使用BoxColor属性设置控件的背景颜色.

 

8.Creating Composite Controls(建立复合控件)
 类似于WinForm下的复合控件的建立.
 一个简单的登录的复合控件示例如下:
 
Listing 28.28 Login.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace myControls

Public Class Login
Inherits Control
Implements INamingContainer

Public Property Username As String
Get
  Me.EnsureChildControls()
  Return CType( Controls( 2 ), TextBox ).Text
End Get
Set
  Me.EnsureChildControls()
  CType( Controls( 2 ), TextBox ).Text = Value
End Set
End Property

Public Property Password As String
Get
  Me.EnsureChildControls()
  Return CType( Controls( 5 ), TextBox ).Text
End Get
Set
  Me.EnsureChildControls()
  CType( Controls( 5 ), TextBox ).Text = Value
End Set
End Property


Protected Overrides Sub CreateChildControls()
  Me.Controls.Add( New LiteralControl( "<div style=""border: 5px " & _
    "inset #cccccc;background-color:#eeeeee;width:50%;padding:10px"">" ) )

  ' Add Username
  Me.Controls.Add( New LiteralControl( "<b>Username:</b> " ) )
  Me.Controls.Add( New TextBox )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' Add Password
  Dim txtPass As New TextBox
  Me.Controls.Add( New LiteralControl( "<b>Password:</b> " ) )
  txtPass.TextMode = TextBoxMode.Password
  Me.Controls.Add( txtPass )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' Add Submit Button
  Dim btnButton As New Button
  btnButton.Text = "Login!"
  Me.Controls.Add( btnButton )
  Me.Controls.Add( New LiteralControl( "</div>" ) )
End Sub

End Class

End Namespace


 通过AddHandler ,处理复合控件中的事件.
 示例代码如下:

Listing 28.30 LoginEvent.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace myControls

Public Class LoginEvent
Inherits Control
Implements INamingContainer

Sub CheckPassword( s As Object, e As EventArgs )
  Dim strUsername, strPassword As String
  Dim lblLabel As Label
  strUsername = CTYPE( Controls( 2 ), TextBox ).Text
  strPassword = CTYPE( Controls( 5 ), TextBox ).Text
  lblLabel = CTYPE( Controls( 9 ), Label )
  If strUsername = "joe" and strPassword = "secret" Then
    lblLabel.Text = "Welcome Joe!"
  Else
    lblLabel.Text = "Invalid Password!"
  End If
End Sub

Protected Overrides Sub CreateChildControls()
  Me.Controls.Add( New LiteralControl( "<div style=""border: 5px " & _
    "inset #cccccc;background-color:#eeeeee;width:50%;padding:10px"">" ) )

  ' Add Username
  Me.Controls.Add( New LiteralControl( "<b>Username:</b> " ) )
  Me.Controls.Add( New TextBox )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' Add Password
  Dim txtPass As New TextBox
  Me.Controls.Add( New LiteralControl( "<b>Password:</b> " ) )
  txtPass.TextMode = TextBoxMode.Password
  Me.Controls.Add( txtPass )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' Add Submit Button
  Dim btnButton As New Button
  btnButton.Text = "Login!"
  AddHandler btnButton.Click, AddressOf CheckPassword
  Me.Controls.Add( btnButton )
  Me.Controls.Add( New LiteralControl( "</div>" ) )

  ' Add Label Control
  Dim lblLabel As New Label
  lblLabel.EnableViewState = False
  Me.Controls.Add( lblLabel )
End Sub

End Class

End Namespace

 

9.Inheriting from Existing Controls(继承现有的控件)
 你可以继承任何一个ASP.NET Framework 中现有的HTML或者Web Control.

 以下示例通过重载RenderContents方法,完成更高级的自定义控件的应用.
  Public Class myWebControl
  Inherits WebControl
  Overrides Protected Sub RenderContents( objTextWriter As HtmlTextWriter )
     objTextWriter.Write( "Hello World!" )
  End Sub
  End Class

 

10.Accessing the Current Context(访问当前上下文环境信息)
 通过自定义控件的Context 属性中的Response 和 Request 对象来访问.
 如:通过Request 对象的 UserHostAddress 和 UserAgent 得到用户信息.

 示例代码:
    strHostAddress = Context.Request.UserHostAddress
    strUserAgent = Context.Request.UserAgent
 同理,也可以通过Request对象,得到当前浏览器的相应信息.
  
 示例代码:
    strBrowserType = Context.Request.Browser.Browser
    strVersion = Context.Request.Browser.Version
    blnJavascript = Context.Request.Browser.Javascript

 

  


11.Debugging Controls(自定义控件的调试)
 使用自定义控件的Context 属性中的Trace对象显示跟踪信息.

 示例代码:
Listing 28.36 ControlTrace.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ControlTrace
Inherits Control

Overrides Protected Sub OnLoad( e As EventArgs )
  Context.Trace.Warn( "Executing OnLoad Method!" )
End Sub

Overrides Protected Sub OnPreRender( e As EventArgs )
  Context.Trace.Warn( "Executing OnPreRender Method!" )
End Sub

Overrides Protected Sub Render( objTextWriter As HtmlTextWriter )
  Context.Trace.Warn( "Executing Render Method!" )
  objTextWriter.WriteLine( "Hello World!" )
End Sub

End Class

End Namespace

 

总结:本章祥细的描述了在ASP.NET下自定义控件的开发.你可以通过继承和复合现在Web Control建立自已的包括多种属性,方法,事件.的WEB控件.
    同时也介绍了自定义控件的Context 属性中的 Request, Response, Trace 等对象.
下一章将介绍高级自定义控件的发开,包括包含模板,数据绑定等. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值