客户要一个有滚动条的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. " );
}
}
}
}
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. " );
}
}
}
}