在复合自定义服务器控件中,管理视图状态,呈现和事件

分享于 

17分钟阅读

Web开发

  繁體

介绍

最近我构建了一个web文件管理器服务器控件。 控件嵌入另外两个服务器控件- microsoft.web.ui.webcontrols.treeview 并且 DataGrid 必须处理这些控件的呈现。事件和 ViewState。 我花了大量的试验和错误来弄清楚如何正确地把所有的东西都。 本文简单地共享我学习的课程,希望能够节省你一些时间来搜索答案。

计算 ToolboxData

让我们从简单的开始。 你知道 {0} 中的内容:

[ToolboxData( "<{0}:ExiaWebFileManager runat="server">" )]

替换为以构成HTML标记的内容。 但它被替换为什么? 直到找到了它,我才疯狂,直到你完成,从工具箱拖动控件,将产生奇怪的结果,比如用"cc1"替换 {0}。 如果你像我一样,你一直在想如何去除"cc1",你的失败,你可以能写了类似的东西:

[ToolboxData( "<Exia:ExiaWebFileManager runat="server">" )]

嗯结果是它从程序集级别属性中被填充。 将这样的属性放在命名空间之外的某个地方,比如在 AssemblyInfo.cs. 中

[assembly:TagPrefix( "Exia.Web.UI.WebControls", "Exia" )]

这告诉 Visual Studio 在将 Exia.Web.UI.WebControls 命名空间中的任何控件放到窗体上时,标记前缀将是" Exia"。 除了你的标签将被形成好的事实,<%register> 指令现在将正确地写出来。

另一个快速的。工具箱 icon

我在这里发现的是它不是一个 icon,它是一个位图。 在你的项目中创建 16位x16位图,将它的生成操作设置为嵌入资源,并将类似这里 上面的属性设置为服务器控件类:

ToolboxBitmapAttribute( typeof(ExiaWebFileManager), 
 "FileManager.bmp")

保护嵌入式服务器控件的ViewState

这让我疯狂,消耗了三天的时间。 让我off的是搜索谷歌并找到这样的代码。

private TreeView treeView
{
 get{ return (TreeView)ViewState["TreeView"]; }
 set{ ViewState["TreeView"] = value; }
}

。加上我自己的愚蠢。 behind 这个代码包含嵌入式控件,然后在上保存和恢复 TreeView,这是一个很有意义的想法。 那是我的想法。 当然,有一个错误说 TreeView 无法序列化,谷歌有一些关于如何可以序列化的线程。

最后,刚刚失败,我尝试了最简单的事情,知道它不能工作。

private TreeView treeView = new TreeView;

。whaddayaknow还有。worked !

这就是我发现( 当然,你白痴) 没有保存任何控制的时候,只是它的ViewState。 控件在每个 postback 上从零开始实例化,并且保存的ViewState 应用于新控件。

嗯,几乎成功了。

当然,控件需要在控件层次结构的上下文中管理,并在它的ViewState 可以恢复之前呈现。 我的下一个大挑战是找出 CreateChildControlsRender 是如何相互关联的,以及什么是什么,什么时候。 经过一些似乎过于复杂的例子在网上,我将它加热到下列简单规则。 我相信有它的他方法可以做到,但是这里是为我们的控制工作的方式:

将控件声明并实例化为 private 实例变量

如果控件有一个包含行的table,该行包含一个单元格,该单元格包含一个 microsoft.web.ui.webcontrols.treeview 将元素声明为 private 控件的实例变量,例如:

class.. . 
{
 private Table BorderTable = new Table();;
 private TableRow BorderTableRow = new TableRow();
 private TableCell BorderTableCell = new TableCell();
 private TreeView MyTreeView = new TreeView();
 etc.
...
...

初始化OnInit事件中的控件

OnInit 事件中,配置基本控件属性,如 AutoGenerateColumnsShowHeader 等等 和连接事件。 ( 更多关于事件连线的信息。)

protectedoverridevoid OnInit(EventArgs e)
{
 InitializeControls();
 base.OnInit (e);
}privatevoid InitializeControls()
{
 // Set basic properties of controls FileListGrid.AutoGenerateColumns = false;
 FileListGrid.ShowHeader = true;
 etc.
 // Wire up events. FileListGrid.ItemCommand += 
 new DataGridCommandEventHandler( FileListGrid_ItemCommand );
 FileUploader.DialogClosed += 
 new DialogOpenerClosedEvent(FileUploader_DialogClosed);
 DeleteBtn.Click += new EventHandler( DeleteBtn_Click );
 AddBtn.Click += new EventHandler( AddBtn_Click );
 MoveBtn.Click += new EventHandler( MoveBtn_Click );
 etc.
}

在DirectDraw中组合控件

CreateChildControls 方法中,将控件放到层次结构中。 最重要的是,将 root 控件或者控件添加到服务器控件的Controls Collection。 同时,设置需要设置的任何属性,如CSS类等:

protectedoverridevoid CreateChildControls()
{
 BuildControlHeirarchy();
. . .
. . .
}privatevoid BuildControlHeirarchy()
{
 // Add the root control to the // Controls collection of// the server control. When this // is done, the base.Render()// method will render the entire // heirarchy for you and// manage the ViewState of any child // controls in the heirarchy Controls.Add( BorderTable );
 BorderTable.CellSpacing = 0;
 // Build up the rest of the control heirarchy... BorderTable.Controls.Add( BorderTableRow );
 BorderTableRow.Controls.Add( BorderTableCell );
 BorderTableCell.CssClass = BorderCssClass;
 BorderTableCell.Controls.Add( MyTreeView );
. . .
. . .
 etc.
}

在渲染方法中执行任何自定义呈现

Render 方法中你需要做的最小的是调用 Base.Render()。 实际上,你所要做的是不重写这里方法。 基本 Render 方法负责呈现在 CreateChildControls 中构建的控件和子控件的层次结构。 你需要重写 Render 以呈现特殊的HTML,不能轻松地使用 CreateChildControls 构建。 在 Render 方法中,当网格滚动时,我们写出一个特殊样式,在固定位置固定网格 header,如下所示:

output.Write(@"<style type='text/css'>
. DataGridFixedHeader { POSITION: relative; ; 
 TOP: expression(this.offsetParent.scrollTop - 2) }
 </style>" );

替代 Render的另一个原因是速度。 直接呈现HTML比构建控件层次结构要快。 但如果不直接使用 CreateChildControls,则需要在中创建对象,这样,你就需要在中建立一些对象,这样就可以使用 GetPostBackEventReference() 和来构建事件。 )

如果你重写,请不要忘记调用 Base.Render(),否则你在 CreateChildControls 中构建的控件层次将永远不会呈现:

下面是我们的完整 Render 方法。 可以看到,除了调用 base.Render 之外,它几乎没有其他功能。

protectedoverridevoid Render(HtmlTextWriter output)
{
 if(! Visible )
 return;
 if( FileListGrid.Items.Count == 0 )
 FileListGrid.ShowHeader = false;
 // Write out the html to include the CSS file output.Write( "<link href='" + CssFile + "' 
 type='text/css' rel='stylesheet'>" );
 // Write out the CSS style for the // fixed datagrid header.  output.Write( @" <style type='text/css'>
. DataGridFixedHeader { POSITION: relative; ; 
 TOP: expression(this.offsetParent.scrollTop - 2) }
 </style>" );
 base.Render( output ); 
}

在DirectDraw中创建网格列

如果控件嵌入了 DataGrid 或者其他类型的服务器控件,应什么时候创建网格列? 它们是否使用ViewState保存?

不,列不保存为 ViewState。 它们是网格控件的子控件,与 table 单元格是 table 行的子控件相同。 因此,在创建层次结构中的其他控件时,在 CreateChildControls 中创建它们。

绑定DirectDraw中的数据

数据在 CreateChildControls 中绑定,但不在 postback 中。 下面是我们的完整 CreateChildControls 方法:

protectedoverridevoid CreateChildControls()
{
 ConfigureFileGridColumns();
 BuildControlHeirarchy();
 if(! Page.IsPostBack )
 BindData();
 base.CreateChildControls();
}

管理视图视图

现在精彩的部分。 结果是,如果正确设置了一切,几乎没有什么需要手动管理 ViewState的。 嵌入式服务器控件将自动管理它们自己的ViewState。 你唯一需要关心的是对控制特定信息的控制,这些信息需要被圆触发。 例如在文件管理器控件中,我们调用 ViewState 来跟踪以下事项:

  • 文件列表中当前选定的文件;
  • 目录树中当前选定的目录;
  • 如果有命令挂起,如目录移动,则为活动命令;

下面是跟踪当前所选文件的代码的方式:

[Browsable(false)]// Suppress display in the // Properties browser in // Visual Studiopublicstring SelectedFile
{
 get {
 if( ViewState["SelectedFile"] == null )
 ViewState["SelectedFile" ] = "";
 return (string)ViewState["SelectedFile"];
 }
 set {
 ViewState["SelectedFile"] = value; 
 }
}

所有非常直接的内容。因此总结 ViewState,只要控件组合在 CreateChildControls 控件层次结构,它们就会以基本的渲染方式呈现,并且你只需要对控件执行特定的管理。

连接事件

我们的控件模拟 Windows 文件资源管理器,其中包含一个 TreeView 显示目录和一个显示文件的网格。 当用户单击文件时,我们需要执行一些自定义操作。 为了进行这项工作,我们需要捕获构成文件列表的嵌入式 DataGridItemCommand 事件。

你会认为这很简单。 初始化 DataGrid ( 请参见上方) 时,只需将事件连接到下面的事件:

FileListGrid.ItemCommand += 
 new DataGridCommandEventHandler(FileListGrid_ItemCommand);

只有一个问题,事件处理程序永远不会被调用。 所以你一天或者两天离开,或者在绝望中,或者只是运气不佳,你尝试实施 INamingContainer。 突然间事件。 这就是说,如果控件在控件层次结构中没有自己的,命名空间,那么事件就不能正确连接。 因此,如果你想捕获嵌入控件的事件,那么不要忘记实现 INamingContainer。

那IPostbackEventHandler?

当我们在实现的时候,如何实现 IPostbackEventHandler 呢。 你必须在自定义控件中实现它? 答案是,如果你希望控件发生事件,你需要实现 IPostbackEventHandler。 换句话说,实现 IPostbackEventHandler的控件将接收到当用户与控件交互时引发的事件。 如果控件未实现 IPostbackEventHandler,则当激发事件时,或者控件的父级和实现 IPostbackEventHander的层次结构的父控件将接收事件时将忽略控件。

如果这与泥浆一样清晰,下面是一个例子。 我们的自定义控件包含嵌入式按钮。 因为这些按钮是 ASP.NET 按钮,它们实现 IPostbackEventHandler。 这就是说,当用户单击 ASP.NET 按钮时,事件会发送到 ASP.NET 按钮,这有机会响应它。 对于 ASP.NET 按钮,控件确实对它做出响应。 响应是引发 Click 事件,开发人员就像你和我一样,可以在我们的代码中捕获它们。

因此,当定制控件呈现时,它们已经实现了 IPostbackEventHandler,这意味着它们将捕获用户的点击,因此我们不需要在定制控件中实现 IPostbackEventHandler。 我们只需要利用这个事实,即按钮捕获事件并引发 Click 事件。 我们通过将 Click 事件连接到我们想要的动作来实现这一点,就像上面看到的。

DeleteBtn.Click += new EventHandler(DeleteBtn_Click);

。并做我们的定制行动。

privatevoid DeleteBtn_Click(object sender, 
 System.EventArgs e)
{
 if(!IsRootSelected() )
 { 
 Delete();
 }
}

那么什么时候你需要实现 IPostbackEventHandler? 如果希望控件响应事件,生成引发 postback的组件或者HTML不实现 IPostbackEventHandler,则需要实现 IPostbackEventHandler。 例如,假设不将 ASP.NET 按钮控件添加到控件层次结构,只需将HTML直接写入响应流,如下所示:

protectedoverridevoid Render(HtmlTextWriter output)
{ 
 output.Write(
 "<a href='" + GetPostbackEventReference() + "'>Click me</a>) 
}

此时,单击href时,控件的引用将通过 GetPostbackEventReference 进行,并且控件实现 INamingContainer,但事件实际上不会发送到控件,除非它实现了 IPostbackEventHandler

结束语

在理解控件实例化。呈现和事件连接的机制之前,开发嵌入其他自定义控件的自定义控件可能很棘手。 一般的文档似乎没有提供清晰的答案,并且很容易遵循指南。 我们开发第一个主要商业定制控件时,我们完成了许多问题,最后很高兴地发现,如果有组织的话,我们会发现这一点是。 NET框架不仅有意义,而且还为你做了很多工作,比如管理 ViewState 和沿着正确的路径发送事件。

本文没有为控制开发提供一个全面的指南,而是对我们自己经验的一个简单反映。 我希望它能节省你一些时间在自己的定制控制开发中。

其他帮助

我对帮助构建自定义控件的任何人都非常感兴趣。 如果你需要更多的例子或者源代码,尽管我不能提供商业控制的完整源代码,我当然可以提供大量的代码。 只要让我知道你有什么问题,我很高兴尽可以能帮助我尽可以能的帮助你。


COM  Server  MAN  控制  VIEW  event  
相关文章