客户要一个有滚动条的ASP.Net DataGrid控件,只好写了:
using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Web.UI.Design.WebControls; using System.Text; using System.Drawing; [assembly:TagPrefix( " Microsoft.Gtec.Dsv " , " gtecdsv " )] namespace Microsoft.Gtec.Dsv{ /// <summary> /// Summary description for WebCustomControl1. /// </summary> [ToolboxData( " <{0}:ScrollableFixedHeaderDataGrid runat=server></{0}:ScrollableFixedHeaderDataGrid> " )] public class ScrollableFixedHeaderDataGrid: System.Web.UI.WebControls.DataGrid { protected override void Render(HtmlTextWriter output) { // Use this flag to determine whether the component is in design-time or runtime. // The control will be rendered differently in IDE. // Don't bother to use DataGridDesigner.GetDesignTimeHtml bool designMode = ((Site != null ) && (Site.DesignMode)); // Backing up the properties need to change during the render process string tempLeft = Style[ " LEFT " ]; string tempTop = Style[ " TOP " ]; Unit tempHeight = Height; string tempTableStyle = Style[ " TABLE-LAYOUT " ]; // Render a "<div>" container with scrollbars. output.WriteBeginTag( " div " ); output.WriteAttribute( " id " ,ID + " _div " ); output.WriteAttribute( " style " , " HEIGHT: " + Height + " ; " + // Leave 20px for the vertical scroll bar, // assuming the end-user will not set his scroll bar to more than 20px. " WIDTH: " + (Width.Value + 20 ) + " px; " + " TOP: " + Style[ " TOP " ] + " ; " + " LEFT: " + Style[ " LEFT " ] + " ; " + " POSITION: " + Style[ " POSITION " ] + " ; " + " OVERFLOW-X: auto; " + " Z-INDEX: " + Style[ " Z-INDEX " ] + " ; " + // Render the scrollbar differently for design-time and runtime. " OVERFLOW-Y: " + (designMode ? " scroll " : " auto " ) ); output.Write(HtmlTextWriter.TagRightChar); // The DataGrid is inside the "<div>" element, so place it at (0,0). Style[ " LEFT " ] = " 0px " ; Style[ " TOP " ] = " 0px " ; // Render the DataGrid. base .Render(output); output.WriteEndTag( " div " ); // Restore the values Style[ " LEFT " ] = tempLeft; Style[ " TOP " ] = tempTop; // The following rendering is only necessary under runtime. It has negative impact during design time. if ( ! designMode) { // Render another copy of the DataGrid with only headers. // Render it after the DataGrid with contents, // so that it is on the top. Z-INDEX is more complex here. // Set Height to 0, so that it will adjust on its own. Height = new Unit( " 0px " ); StringWriter sw = new StringWriter(); HtmlTextWriter htw = new HtmlTextWriter(sw); // This style is important for matching column widths later. Style[ " TABLE-LAYOUT " ] = " fixed " ; base .Render(htw); StringBuilder sbRenderedTable = sw.GetStringBuilder(); htw.Close(); sw.Close(); Debug.Assert((sbRenderedTable.Length > 0 ), " Rendered HTML string is empty. Check viewstate usage and databinding. " ); string temp = sbRenderedTable.ToString(); if (sbRenderedTable.Length > 0 ) { // AllowPaging at the top? if ((AllowPaging) && ((PagerPosition.Top == PagerStyle.Position || (PagerPosition.TopAndBottom == PagerStyle.Position)))) { Trace.WriteLine(temp); sbRenderedTable.Replace(ID,ID + " _Pager " , 0 , (temp.IndexOf(ID) + ID.Length)); temp = sbRenderedTable.ToString(); string pager = temp.Substring( 0 , temp.ToLower().IndexOf( @" </tr> " ) + 5 ); Trace.WriteLine(pager); output.Write(pager); output.WriteEndTag( " table " ); // Start of pager's <tr> int start = temp.ToLower().IndexOf( @" <tr " ); // End of pager's </tr> int end = temp.ToLower().IndexOf( @" </tr> " ) + 5 ; // Remove the <tr> for pager from the string. Prepare to render the headers. sbRenderedTable.Remove(start,end - start); Trace.WriteLine(sbRenderedTable.ToString()); sbRenderedTable.Replace(ID + " _Pager " ,ID + " _Headers " , 0 , (temp.IndexOf(ID + " _Pager " ) + (ID + " _Pager " ).Length)); temp = sbRenderedTable.ToString(); string tableHeaders = temp.Substring( 0 , (temp.ToLower()).IndexOf( @" </tr> " ) + 5 ); Trace.WriteLine(tableHeaders); output.Write(tableHeaders); output.WriteEndTag( " table " ); string headerID = ID + " _Headers " ; string pagerID = ID + " _Pager " ; string divID = ID + " _div " ; string adjustWidthScript = @" <script language=javascript> //debugger; var headerTableRow = " + headerID + @" .rows[0]; var originalTableRow = " + ID + @" .rows[1]; " // Adjust header row's height. + @" headerTableRow.height = originalTableRow.offsetHeight; " + // Adjust pager row's height, width. pagerID + @" .rows[0].height = " + ID + @" .rows[0].offsetHeight; " + pagerID + @" .style.width = " + ID + @" .offsetWidth; for (var i = 0; i < headerTableRow.cells.length; i++) { headerTableRow.cells[i].width = originalTableRow.cells[i].offsetWidth; } " + // Also needs to adjust the width of the "<div>" at client side in addition to servier side, // since the Table's actual width can go beyond the width specified at server side under Edit mode. // The server side width manipulation is mainly for design-time appearance. divID + @" .style.width = " + ID + @" .offsetWidth + 20 + 'px'; " + // The following script is for flow-layout. We cannot get the position of the control // on server side if the the page is with flow-layout. headerID + @" .style.left = " + divID + @" .offsetLeft; " + headerID + @" .style.top = " + divID + @" .offsetTop + " + pagerID + @" .offsetHeight; " + headerID + @" .style.position = 'absolute'; " + pagerID + @" .style.left = " + divID + @" .offsetLeft; " + pagerID + @" .style.top = " + divID + @" .offsetTop; " + pagerID + @" .style.position = 'absolute'; </script> " ; Page.RegisterStartupScript( " dummyKey " + this .ID, adjustWidthScript); // output.Write(adjustWidthScript); } else { // Replace the table's ID with a new ID. // It is tricky that we must only replace the 1st occurence, // since the rest occurences can be used for postback scripts for sorting. sbRenderedTable.Replace(ID,ID + " _Headers " , 0 , (temp.IndexOf(ID) + ID.Length)); Trace.WriteLine(sbRenderedTable.ToString()); // We only need the headers, stripping the rest contents. temp = sbRenderedTable.ToString(); string tableHeaders = temp.Substring( 0 , (temp.ToLower()).IndexOf( @" </tr> " ) + 5 ); Trace.WriteLine(tableHeaders); output.Write(tableHeaders); output.WriteEndTag( " table " ); // Client side script for matching column widths. // Can't find a way to do this on the server side, since the browser can change widths on the client side. string adjustWidthScript = @" <script language=javascript> //debugger; var headerTableRow = " + this .ID + @" _Headers.rows[0]; var originalTableRow = " + this .ID + @" .rows[0]; headerTableRow.height = originalTableRow.offsetHeight; for (var i = 0; i < headerTableRow.cells.length; i++) { headerTableRow.cells[i].width = originalTableRow.cells[i].offsetWidth; } " + // Also needs to adjust the width of the "<div>" at client side in addition to servier side, // since the Table's actual width can go beyond the width specified at server side under Edit mode. // The server side width manipulation is mainly for design-time appearance. this .ID + " _div " + @" .style.width = " + this .ID + @" .offsetWidth + 20 + 'px'; " + // The following script is for flow-layout. We cannot get the position of the control // on server side if the the page is with flow-layout. this .ID + " _Headers " + @" .style.left = " + this .ID + @" _div.offsetLeft; " + this .ID + " _Headers " + @" .style.top = " + this .ID + @" _div.offsetTop; " + this .ID + " _Headers " + @" .style.position = 'absolute'; </script> " ; Page.RegisterStartupScript( " dummyKey " + this .ID, adjustWidthScript); // output.Write(adjustWidthScript); } Height = tempHeight; Style[ " TABLE-LAYOUT " ] = tempTableStyle; } } } protected override void OnInit(EventArgs e) { if ( 0 == Width.Value) Width = new Unit( " 400px " ); if ( 0 == Height.Value) Height = new Unit( " 200px " ); // Transparent header is not allowed. if (HeaderStyle.BackColor.IsEmpty) { HeaderStyle.BackColor = Color.White; } // Transparent pager is not allowed. if (PagerStyle.BackColor.IsEmpty) { PagerStyle.BackColor = Color.White; } base .OnInit (e); } [Browsable( false )] public override bool ShowHeader { get { return true ; } set { if ( false == value) throw new InvalidOperationException( " Use the original DataGrid to set ShowHeaders to false. " ); } } }}