创建 HTML 文本区域服务器端控件






4.78/5 (54投票s)
使用 VB.NET 和 JavaScript 创建 ASP.NET 富文本编辑器服务器端控件。
引言
本文演示了如何使用 VB .NET 创建一个支持 HTML 格式的富文本编辑器服务器端控件。这是我编写的第一个 .NET 控件。撰写本文的目的是验证我使用的技术,并了解是否有其他编写 .NET 控件的更好方法。那么,我们开始吧。
理解 JavaScript 编辑器
首先,我将演示如何使用 HTML 和 JavaScript 创建一个编辑器。之后,我们将将其转换为 .NET 服务器端控件。
创建编辑器的核心技术是使用 execCommand 方法(JavaScript)。我们来看一个简单的例子。
<HTML> <Head> <Title>Simple Demo of HTML Area </Title> <Script Language="JavaScript"> function doBold() { frameEdit.document.execCommand('bold', false, null); } function doItalic() { frameEdit.document.execCommand('italic', false, null); } </Script> </Head> <Body onLoad="frameEdit.document.designMode='on';"> <form id="frmEdit"> <IFRAME id="frameEdit" style="width:600px; height:300px" align=center> </IFRAME> <br> <P align=center> <Input type="Button" value="Bold" onClick="doBold()"> <Input type="Button" value="Italic" onClick="doItalic()"> </P> </form> </Body> </HTML>
只需将上面的代码复制并粘贴到某个编辑器中,然后保存为 HTML。在浏览器中运行,您将看到 IFrame 表现得像一个 TextArea HTML 控件。好的,在那里输入一些内容,选中它,然后单击“Bold”按钮。您将看到您键入的文本已转换为粗体,斜体也是如此。这意味着它奏效了。让我们分析一下我们上面编写的代码。
我们从 Body 标签开始。Head 标签内的 Script 部分稍后讨论。在 body 加载时,我们让浏览器开始我们内联框架(IFrame,名为 frameEdit)的**设计模式**。此属性会将 IFrame 转换为可编辑的文本框。好的,然后,在按钮的 Click 事件上,我们调用了 JavaScript 函数 **doBold** 和 **doItalic**。这些函数是在我们 HTML 文档的 Head 部分定义的。每个函数(doBold 和 doItalic)中只有一条语句。那就是 **frameEdit 的 document 属性的 execCommand 方法**。execCommand 有三个参数:Command、UserInterface 和 UserValue。让我们详细看看它们。
execCommand 方法的参数
让我们看一下 execCommand 的参数。正如我之前告诉你的,这个方法是我们 HTML Area 的核心。只要理解了这个方法的参数,其余的就真的很容易了。
Command(第一个参数)
execCommand 的第一个参数是 UserCommand。它是一个字符串值,定义要执行的操作。在我们上面的例子中,我们分别传递了 **bold** 和 **italic**。可以传递很多命令给这个函数。这些命令的完整列表可以在 MSDN 中找到。只需在你的 MSDN 库的索引中查找**Command Identifiers** 或 **execCommand**。我在这里写一些我在我的 HTML Area 中使用的命令。
- BackColor 设置文本的背景颜色
- Bold 将文本设置为粗体
- Copy 将文本复制到剪贴板。(复制将以 HTML 格式进行)
- CreateLink 选中一些文本并执行此命令。这将使你的文本成为超链接
- Cut 剪切文本。
- FontName 更改选中文本的字体
- FontSize 更改字号。
- ForeColor 更改文本的前景色。
- Indent 增加文本/段落的缩进。
- InsertImage 在你的编辑器区域插入图像。
- Italic 将文本设置为斜体
- JustifyCenter 文本居中对齐
- JustifyLeft 文本左对齐
- JustifyRight 文本右对齐
- Outdent 减少缩进。
- Redo 重做你的最后一次操作(Undo 的逆操作)
- SaveAs 打开一个对话框来保存你的工作。
- StrikeThrough 将文本设置为删除线。
- Subscript 将选中的文本稍微向下移动到比正常文本略低的位置。
- Supercript 将选中的文本稍微向上移动到比正常文本略高的位置。
- UnderLine 创建带下划线的文本
- Undo 撤销最后一次操作。
- Unlink 移除超链接(如果存在)
还有更多命令可用于格式化你的 HTML 文档。你可以从 MSDN 体验更多命令。但目前,这就足够了。
User Interface(第二个参数)
第二个参数询问你 execCommand 是否应该为你在第一个参数中传递的命令显示用户界面。它是 true 或 false。如果设置为 true,那么 Explorer 将为此命令提供适当的用户界面;如果没有,那么你必须手动(如果需要)将值传递给第三个参数(第三个参数 Value 稍后讨论)。在我们上面的例子中,我们为 Bold 或 Italic 命令只传递了 false,因为它们不需要用户界面。现在我们来看一个简单的例子,它将使用此参数来显示用户界面。
<HTML> <Head> <Title>Simple Demo of HTML Area </Title> <Script Language="JavaScript"> function doLink1() { //This method will show the User Interface frameEdit.document.execCommand('createlink', true, null); } function doLink2() { //This method will not show any user interface //But we are providing the value of third parameter manually frameEdit.document.execCommand('createlink', false, 'http://msdn.microsoft.com'); } </Script> </Head> <Body onLoad="frameEdit.document.designMode='on';"> <form id="frmEdit"> <IFRAME id="frameEdit" style="width:600px; height:300px" align=center> </IFRAME> <br> <P align=center> <Input type="Button" value="Link 1 User Interface" onClick="doLink1()"> <Input type="Button" value="Link 2 No User Interface" onClick="doLink2()"> </P> </form> </Body> </HTML>
当你运行这个例子时,你会看到类似这样的东西
当你将 true 作为 execCommand 的第二个参数传递时,Explorer 提供了这个界面。这是 Explorer 自带的用户界面。请注意,并非所有命令都支持用户界面,只有那些需要的命令才支持。例如,如果你为 Bold 命令传递 true,则不会提供用户界面。实际上,此参数将被忽略,因为进行粗体、斜体或下划线操作不需要用户界面。
当你点击第二个按钮(Link 2 No User Interface)时,不会提供用户界面,但仍然会创建链接。如果你查看代码,你会注意到我们在链接位置传递了第三个参数。让我们讨论第三个参数。
Value(第三个参数)
Value 参数是在 execCommand 需要某个值且 user interface 参数设置为 false 的情况下传递给 execCommand 方法的。就像在我们上面的例子中,我们使用了第二个按钮(Link 2 No User Interface)。如果你看到这个按钮后面的代码,你会注意到我们传递了命令 **createlink**,与第一个按钮的命令相同,User Interface 为 false,值为 MSDN 站点的 URL。由于我们让 execCommand 创建链接,它必须知道用于创建链接的位置或 URL。现在我们有两种选择:要么从用户那里提示,要么硬编码(手动)。所以,如果我们想手动设置值,那么我们可以将 user interface 设置为 false,并将某个值(URL)作为 value 参数。
那么,关于 execCommand 方法就说到这里。如果你理解了我上面说的话,那么用 HTML 和 JavaScript 制作你自己的 HTML Area 应该没有任何障碍。只需创建按钮并在每个按钮上执行相应的命令。我在这篇文章中也附上了我的 TextArea 的 HTML 版本。现在,让我们开始我们真正的任务,HTML 文本区域服务器端控件。
转向 ASP .NET 的服务器端控件
现在让我们看看如何为我们的文本区域创建服务器端控件。ASP .NET 的服务器端控件非常容易构建,并且不需要用户下载任何权限或关注。因为 .NET 的服务器端控件会生成等效的 HTML 代码,然后发送给请求用户的浏览器。
要创建我们的服务器端控件,只需启动你的 VS .NET 并选择新项目,然后在项目类型中,从 Visual Basic 的项目模板中选择 **Web Control Library**。在项目名称部分,将名称设置为 RTFBox。创建新项目后,它将有一个模块,其中包含一些 Server Side Control 所需的代码。暂时删除所有代码,只保留以下内容。
Imports System.ComponentModel Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Xml Imports System.Drawing Imports System.Windows.Forms.Design Imports System Imports System.IO Imports System.Web Imports System.Drawing.Design Namespace HangamaHouse Public Class RTFBox Inherits System.Web.UI.WebControls.WebControl End Class End Namespace
好的,现在创建一些私有方法,它们将为我们返回控件的 HTML 代码。你的方法将看起来像这样。
Private Function GenerateCSSCode() As String Dim mCSS As String mCSS = vbCrLf mCSS = "<STYLE> " & vbCrLf mCSS += ".EditControl { " & vbCrLf mCSS += "width:" & Me.mWidth & "px; " & vbCrLf mCSS += "height:300px; } " & vbCrLf mCSS += ".tblTable { " & vbCrLf mCSS += "width : " & Me.mWidth & "px; " & vbCrLf mCSS += "height: 30px; " & vbCrLf mCSS += "border:0; " & vbCrLf mCSS += "cellspacing:0; " & vbCrLf mCSS += "cellpadding:0; " & vbCrLf mCSS += "background-color: #D6D3CE ; " & vbCrLf mCSS += " } " & vbCrLf mCSS += ".butClass { " & vbCrLf mCSS += "width:22; " & vbCrLf mCSS += "height:22; " & vbCrLf mCSS += "border: 0px solid; " & vbCrLf mCSS += "border-color: #D6D3CE ; " & vbCrLf mCSS += "background-color: #D6D3CE ; " & vbCrLf mCSS += " } " & vbCrLf mCSS += ".tdClass { " & vbCrLf mCSS += "padding-left: 0px; " & vbCrLf mCSS += "padding-top:0px; " & vbCrLf mCSS += "background-color: #D6D3CE ; " & vbCrLf mCSS += "</STYLE>" & vbCrLf Return (mCSS & vbCrLf) End Function
此函数将为我们返回文本区域的样式表。同样,我们将创建其他函数,它们将返回创建工具栏和 IFrame 的 HTML 代码、处理 IFrame 中 HTML 格式的 JavaScript(使用 execCommand)等。我在这里不写完整的代码,只是告诉你如何设计项目。你可以在本文附带的示例中看到。
当你写完所有返回字符串以创建看起来像顶部图片中的 HTML 文档的函数后,就该编写负责将代码发送到客户端的代码了。
如果你正在编写一个可能向用户浏览器发送输出的服务器端控件,那么在该控件中,你通常会在 **Overrides function Render** 中编写代码。但在本例中,我们还需要将 JavaScript 发送给客户端浏览器,所以我们首先注册我们的 JavaScript 到浏览器,然后通过 Render 覆盖函数发送我们的普通 HTML。所以,要注册你的脚本,你可能会在 **OnPreRender Orverrides functions** 中编写代码。你的代码可能看起来像这样
Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs) 'Register Style Sheet for our HTML area Page.RegisterClientScriptBlock("myStyleSheetScript", Me.GenerateCSSCode()) 'Register main Function which will deal with execCommand Page.RegisterClientScriptBlock("myCommandScript", Me.GenerateCommandScript()) 'Register Script which will handle with the code when button(s) is down Page.RegisterClientScriptBlock("mySelUpDownScript", Me.GenerateSelDown_UpScript()) 'Register Script which will handle with the code when mouse is away from the button(s) Page.RegisterClientScriptBlock("myselOFFScript", Me.GenerateSelOFFScript()) 'Register Script which will handle with the code when mouse is on from the button(s) Page.RegisterClientScriptBlock("myselONScript", Me.GenerateSelONScript()) End Sub
**Page 对象**的 **RegisterClientScriptBlock** 方法接受两个参数,如上面的代码所示。一个是我们脚本的唯一键,另一个是脚本本身。键用于检查脚本是否已注册,如果已注册,则不发送给客户端。好的做法是先检查脚本是否已注册。此方法还将所有脚本发送到客户端浏览器。请注意,我们必须完全描述我们的脚本,包括 <Script> 标签。
当我们的脚本发送到客户端后,我们需要发送实际的 HTML 代码来生成我们 HTML Area 的界面。这段代码将使用 OnRender 覆盖函数发送。
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) writer.Write(Me.GenerateHTMLAreaBody) 'Convert the Simple IFrame to Editable TextBox writer.RenderBeginTag("Script") writer.Write("RTFEdit_" & Me.UniqueID & ".document.designMode='on';") 'writer.Write("RTFEdit_" & Me.UniqueID & ".document.body.innerText = '" & _ Text & "'") writer.RenderEndTag() 'End Converting IFrame to Editable Textbox End Sub
正如我们在第一个例子中看到的,我们需要告诉我们的浏览器它应该开启 IFrame 的设计模式。所以,我们在这里发送用于此工作的代码。 **HtmlTextWrite 类**的 **RenderBeginTag** 方法负责将某个标签写入客户端输出流。我们只传递了标签的名称,这里是 script。然后我们使用 **write 方法**写入标签的内容,然后调用 **RenderEndTag**。RenderEndTag 将自动关闭最后一个打开的标签(这里是 script)。请注意,我们将此代码作为我们控件的最后输出发送。因为,当这段代码发送到客户端时,它会查找我们在这里提到的对象。如果我们先于发送我们文档的 HTML 发送这段代码,客户端浏览器将生成错误,并显示诸如对象未找到等消息。
别被 Me.UniqueID 迷惑,这将在最后一节“管理 View State”中讨论。
到这里,我们的服务器端控件就完成了。只需编译它,它将创建一个与你的项目名称同名的 DLL 文件。现在是时候在任何 ASP .NET 页面中使用我们的控件了。让我们看看这个控件的输出,然后我们回到我们的控件讨论一些关于这个控件的更多事情。
为我们的控件创建测试页面
打开记事本并在此处输入以下代码。
<%@ Page Language="VB" %> <%@ Register TagPrefix="mArea" Assembly="RTFBox" namespace="HangamaHouse" %> <html><Head> <Title>Testing HTML Area Server Side Control</Title> </Head> <Bod> <Form id="Form1" Runat="server"> <h1 align=center>HTML Text Area Demo</h1> <mArea:RTFBox Runat="server" /> </Form> </Body> </html>
将此代码命名为 test.aspx 并将其保存在某个文件夹中。将你的服务器端控件的编译后的 DLL 文件复制到名为 Bin 的文件夹中。请注意,Bin 文件夹应位于你保存 test.aspx 的文件夹内部。将此文件夹设为虚拟文件夹。然后在浏览器中输入 URL。你将看到以下输出。
嘿,你在 HTML Area 中看到的文本是我手动输入的。如果你在浏览器中看不到,不要感到困惑。(好吧,好吧,我知道你非常聪明。只是开玩笑)。总之,我们已经看到了我们的服务器端控件的工作。现在让我们回到讨论一下我们在 HTML Area 服务器控件中使用的其他一些东西。
服务器端控件的颜色管理
此控件具有可用于根据你的站点颜色设置 HTML Area 颜色的属性。正如你在本文开头和上一个图片中看到的。实际上,颜色是在我们使用 `GenerateCSSCode` 函数(私有函数,用户创建)生成的样式表中设置的。我创建了一个属性,用于设置或获取 HTML Area 不同部分的颜色,例如表格背景。颜色属性的类型是 Color。但我们知道 HTML 不理解 Color 对象类型。因此,微软很聪明,它提供了一个方法将 Color 对象转换为 HTML 颜色字符串,反之亦然。我使用了 ColorTranslator.FromHtml("#D6D3CE") 将 HTML 颜色 #D6D3CE 转换为 Color 对象对应的颜色,同样,ColorTranslator.ToHtml(Color.Red) 将 Color 对象值转换为 HTML 对应的颜色字符串。
管理 HTML Area 控件的 View State
本文的最后一个主题是演示服务器端控件的 View State 处理。View state 会在表单对象被发布到服务器时保留其值。为我们的控件实现 view state 的主要问题是 MSDN 明确定义只有特定的 HTML 控件才能拥有 view state,其中包括 CheckBox、CheckBoxList、DropDownList、HTMLInputCheckBox、HTMLInputFile、HTMLInputHidden、HTMLInputImage、HTMLInputRadioButton、HTMLInputText、HTMLInputSelect、HTMLInputTextArea、ImageButton、ListBox、RadioButtonList 和 TextBox。IFrame 不包含在这些元素中,它不支持 View State。所以我所做的是,我获取了一个隐藏字段,并将 IFrame 中的所有文本移到了那个隐藏字段中。然后我在那个隐藏字段上实现了 view state。它奏效了。所以,让我们先看看我是怎么做到的。
当 HTML Area 服务器端控件的用户界面被写入客户端时,除了 IFrame,我还发送了隐藏字段的代码。在 HTML 生成代码中是这样的。
' Code before this for HTML Interface mStr += " <IFrame name=RTFEdit_" & Me.UniqueID & " ID=RTFEdit_" & _ Me.UniqueID & " class=""EditControl"" ></IFrame>" & vbCrLf mStr += " <Input type=""hidden"" name=" & Me.UniqueID & " ID=" & _ Me.UniqueID & " value='" & Text & "' >" & vbCrLf 'Code after this for HTML Interface
请注意,**input type=hidden** 的 Value 属性是 **Text**。Text 是我们上面创建的公共属性,稍后会讨论。当这段代码发送到客户端时,它会生成一个 IFrame 和一个隐藏字段。我还将文本从 IFrame 传输到了这个隐藏字段。这部分代码在 **GeneratePostBackScript** 函数中。你还应该注意到,这个隐藏字段的名称是 Me.UniqueID。UniqueID 是每个服务器控件的属性,它返回该控件的唯一 ID。如果你硬编码某个名称,它可能会与其他同一控件的实例在客户端发生冲突。UniqueID 使得客户端上的每个控件都能拥有不同的 ID。它也用于 View State 管理。现在,让我们讨论一下 View State。
要实现 View State,我们必须实现 **IPostBackDataHandler** 接口。为了实现 View State,我实现了这个接口,并为这个接口的两个函数编写了一些代码。但首先,让我们创建一个名为 Text 的属性,它应该返回我们控件的文本(HTML)。
Public Property Text() As String Get Return CType(Me.ViewState("Text"), String) End Get Set(ByVal Value As String) Value = Replace(Value, "\", "\") '\ is a Special Character, Replace with \\ Value = Replace(Value, "'", "’") ' ' is cause of Error, replace with \' Value = Replace(Value, vbCrLf, " ") Me.ViewState("Text") = Value End Set End Property
Text 属性从 View State 的集合中返回一个字符串。当我们设置值时,它也会将其设置在 ViewState 集合中。但在将其设置到 view state 集合之前,它会格式化传递给它的值。它将 \ 转换为等效的 HTML 代码 \,因为我们使用的是 JavaScript,在 JavaScript 中 \ 表示我们要写一个特殊字符。同样,单引号 (') 和换行符(回车符)也会被转换以适合 JavaScript。
现在来看 **IPostBackDataHandler** 接口的实现。这是代码。
Public Event TextChanged As EventHandler Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData Dim currentValue As String = Text Dim postedValue As String = postCollection(postDataKey) If currentValue Is Nothing Or Not postedValue.Equals(currentValue) Then Text = postedValue Return True End If Return False End Function Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent OnTextChanged(EventArgs.Empty) End Sub Protected Overridable Sub OnTextChanged(ByVal e As EventArgs) RaiseEvent TextChanged(Me, e) End Sub
我们首先声明了一个 Event。然后我们实现了接口的函数/方法。第一个函数是 LoadPostData,顾名思义,它负责加载已发布的(posted)数据。它有两个参数:key 和一个数据集合。我们从该集合中获取值到变量 **postedValue**,然后检查它是否不等于我们之前的值或为空(null),然后将其传递给我们的 Text 属性。否则,就返回 false 并退出。
第二个方法 **RaisePostDataChangedEvent** 用于为我们的自定义控件引发事件(如果我们想的话)。我们简单地调用了 OnTextChanged 可覆盖方法,并在该方法中引发了我们的事件。我们没有在 **RaisePostDataChangedEvent** 方法中引发事件,而是在 OnTextChanged 方法中,这样如果有人想覆盖此方法,他就可以做到。
这就是创建 HTML Area 作为服务器端控件的所有内容。它还需要提供一些函数,例如特殊字符处理。如果你觉得这个控件应该有所改进,请告诉我。如果你在理解代码或其他方面遇到任何问题,你可以写信给我:theangrycoder@yahoo.com。你的评论和建议将受到欢迎。