Update (21Th of March 2008): Very important, you need to add the CreateChildControls override in order to work. Otherwise, because of something I can only consider a GridView bug, this will happen: the last page in a mock paging grid will have, let's say, 2 items when the PageSize is 10; on a postback, the number of rows created by the gridview will be 10! even if only 2 have data. Thus, after a postback that doesn't rebind the data in the grid, the GridView.Rows.Count will be PageSize, not the actual bound number.

Update: Recreated the code completely. Now it has both PageIndex and ItemCount.
Also: Actually there is a way to get only the rows that you need in SQL Server 2005. It is a function called Row_Number and that returns the index number of a row based on a certain ordering. Then you can easily filter by it to take items from 20 to 30, for example. In this case, another interesting property of the PagedDataSource is CurrentPageIndex, to set the displayed page number in the pager.

Now, for the actual blog entry.

Why would anyone want to change the PageCount, you ask? Well, assume you have a big big table, like hundreds of thousands of rows, and you want to page it. First you must put it in a DataTable from the SQL server, so that takes time, then the table set a datasource to the GridView, then it implements the paging.

Wouldn't it be nicer to only get the data that you need from the SQL Server, then change the PageCount to show the exact page count that should have been? However, the PageCount property of the GridView is read-only. One quick solution is to get only the data you need, then fill the resulting DataTable with empty rows until you get the real row count. However, adding empty rows to DataTables is excruciatingly slow, so you don't really gain anything, and the Grid works with a big table anyway.

So this is what you do:
First of all determine how much of the data to gather.

Afterwards you need to trick the GridView into creating a Pager that shows the real row count (and possibly page index). Unfortunately you can't do this from outside the GridView. You need to inherit the GridView control and add your stuff inside. After you do this, you need to override the InitializePager method, which is just about the only protected virtual thing related to Paging that you can find in the GridView.

Code:

using System.Web.UI.WebControls;

namespace Siderite.Web.WebControls
{
public class MockPagerGrid : GridView
{
private int? _mockItemCount;
private int? _mockPageIndex;

///<summary>
/// Set it to fool the pager item Count
///</summary>
public int MockItemCount
{
get
{
if (_mockItemCount == null)
{
if (ViewState["MockItemCount"] == null)
MockItemCount = Rows.Count;
else
MockItemCount = (int) ViewState["MockItemCount"];
}
return _mockItemCount.Value;
}
set
{
_mockItemCount = value;
ViewState["MockItemCount"] = value;
}
}

///<summary>
/// Set it to fool the pager page index
///</summary>
public int MockPageIndex
{
get
{
if (_mockPageIndex == null)
{
if (ViewState["MockPageIndex"] == null)
MockPageIndex = PageIndex;
else
MockPageIndex = (int) ViewState["MockPageIndex"];
}
return _mockPageIndex.Value;
}
set
{
_mockPageIndex = value;
ViewState["MockPageIndex"] = value;
}
}

///<summary>
///Initializes the pager row displayed when the paging feature is enabled.
///</summary>
///
///<param name="columnSpan">The number of columns the pager row should span. </param>
///<param name="row">A <see cref="T:System.Web.UI.WebControls.GridViewRow"></see> that represents the pager row to initialize. </param>
///<param name="pagedDataSource">A <see cref="T:System.Web.UI.WebControls.PagedDataSource"></see> that represents the data source. </param>
protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource)
{
if (pagedDataSource.IsPagingEnabled && (MockItemCount != pagedDataSource.VirtualCount))
{
pagedDataSource.AllowCustomPaging = true;
pagedDataSource.VirtualCount = MockItemCount;
pagedDataSource.CurrentPageIndex = MockPageIndex;
}
base.InitializePager(row, columnSpan, pagedDataSource);
}

protected override int CreateChildControls
(System.Collections.IEnumerable dataSource, bool dataBinding)
{
PageIndex = MockPageIndex;
return base.CreateChildControls(dataSource, dataBinding);
}
}
}


What, what, whaaat? What is a PagedDataSource? Inside the GridView, the paging is done with a PagedDataSource, a wrapper around a normal DataSource, which has some of the GridView paging properties like PageSize, PageCount, etc. Even if the PageCount is also a read-only property, you have the AllowCustomPaging property and then the VirtualCount and CurrentPageIndex properties that you can set.

In other words: the pager is initialized at databinding. Set MockItemCount and MockPageIndex before MockPagerGrid.DataBind();

That's it.

Update: People keep asking me to provide a code sample. Let's try together. First, let's see a classic GridView use example:

gridView.DataSource=getDataSource();
gridView.PageIndex=getPageIndex();
gridView.DataBind();
As you can see, we provide a data source programatically, then set the pageindex (let's assume we took it from the URL string) and then call DataBind(). In this situation, we would load the entire data source (say, 10000 rows) then give it to the grid, which would only render something like 20 rows. Very inefficient.

Now, let's replace the original GridView control with the with MockPagerGrid. The code would look like this:

mockPagerGrid.DataSource=getDataSource(2);
mockPagerGrid.MockPageIndex=getPageIndex();
mockPagesGrid.MockItemCount=10000;
mockPagerGrid.DataBind();
This gets the rows for the second page, sets the mock ItemCount and PageIndex to the total number of rows and the page we want and then calls DataBind(). In this situation getDataSource would load only the 20 rows of page 2, would display it, then the pager would show that it is on page 2 out of 500.

This is a very simple example. It assumes you already know the total number of rows returned. A more complete example would look like this:

// starting with an arbitrary page index
var pageIndex=getPageIndex();
// do operations on the database that would return the rows for the page
// with that index, having the size of the page size of the grid
// and also get the total number of rows in the data source
CustomDataSource dataSource=getDataSource(pageIndex,mockPagerGrid.PageSize);
// set the returned rows as the data source
mockPagerGrid.DataSource=dataSource.Rows;
// set the page index
mockPagerGrid.MockPageIndex=pageIndex;
// set the total row count
mockPagesGrid.MockItemCount=dataSource.TotalRowCount;
// databind
mockPagerGrid.DataBind();

// CustomDataSource would only have two properties: Rows and TotalRowCount
// The sql for getDataSource(index,size) would be something like
// SELECT COUNT(*) FROM MyTable -- get the total count
// SELECT * FROM MyTable WHERE RowIndex>=@index*@size
// AND RowIndex<(@index+1)*@size

// for convenience, I assumed that there is a column called RowIndex in
// the table that is set to the row index


Hopefully, this will help people use this code.

and has 2 comments
The Multiline Regex option changes the way "^" and "$" work so that they match the beginning and the end of each line in the input string. Good for quick searches of a string in a list of newline separated strings.

The WriteLine and AppendLine and other .Net text related methods that end in Line append at the end of the input string an Environment.NewLine. This is \r\n in Windows and \n in Linux based systems. But, RegexOptions.Multiline only works on... you guessed it... \n aka new line, ignoring the good old carriage return altogether.

The solutions are: either create the string that contains the lines by adding manually \n and not using *Line, or change the regular expression to something like "^something\r?$".

I was trying to make a site using a TabContainer and an UpdatePanel and I kept receiving a PageRequestManagerParserErrorException, but only sometimes. A page refresh would fix it and the message looked like "Error parsing near '<html>

<head>

'". The funny thing is that in the Ajax response output I had no <html> or <head>.
Trying desperately a fix detailed in this very nice post: Sys.WebForms.PageRequestManagerParserErrorException - what it is and how to avoid it , mainly forcing the start of a Session when the Page is first loaded, seemed to fix it. (put a if (!IsPostBack) Session["Siderite"]="rules"; in Page_Load)

So beware: the dreaded PageRequestManagerParserErrorException doesn't appear only when the response output is malformed, but apparently also when in some cases someone tries to start a Session from within an async postback.

If you have some other problem linked to this exception, read this post: ASP.Net Ajax and Response.Write or response filters The message received from the server could not be parsed.

Update: The 30 September 2009 release of the AjaxControlToolkit doesn't have the error that I fix here. My patch was applied in July and from September on the bug is gone in the official release as well. Good riddance! :)

==== Obsolete post follows

Update: On June 20th 2009, Codeplex notified me that the patch I did for the ACT has been applied. I haven't tested it yet, though. Get the latest source (not latest stable version) and you should be fine.

This post was updated on the 1st of July 2008 with some clearer explanations and some error corrections thanks to Santoé who pointed out some mistakes.

My ASP.Net app uses a TabContainer, with a TabPanel in the *x code and with additional TabPanels added dynamically in codebehind.

Well, I got a lot of errors so I've decided to debug and change the control in order to fix it.

Step 1: download the AjaxControlToolKit with source included and open the project locally.

First error : Specified argument was out of the range of valid values. Parameter name: index, somewhere in the TabPanelCollection indexer. The problem actually occurs in TabContainer in LoadClientState(string clientState) where there is a for (int i = 0; i < tabState.Length ; i++). It doesn't take into account the possibility that the number of Tabs and the number of values taken from the tabState can be different. So the code must look like this: for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

Step 2: In the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change for (int i = 0; i < tabState.Length ; i++) to for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

The second error is actually a thrown error in the ActiveTabIndex property setter: if (value >= Tabs.Count) { throw new ArgumentOutOfRangeException("value"); }, but it all comes from this: if (Tabs.Count==0 && !_initialized), because it doesn't take into account the possibility that the Tabs.Count is smaller than the ActiveTabIndex, but not zero. So that should look like this: if (value >= Tabs.Count && !_initialized).

I've downloaded the latest AjaxControlToolKit (version Version 1.0.20229 - Feb 29 2008) and the scratched fix above doesn't seem to work anymore. Instead, try patching the ActiveTabIndex property like this:

[DefaultValue(-1)]
[Category("Behavior")]
[ExtenderControlProperty]
[ClientPropertyName("activeTabIndex")]
public virtual int ActiveTabIndex
{
get
{
if (_cachedActiveTabIndex > -1)
{
return _cachedActiveTabIndex;
}
if (Tabs.Count == 0)
{
return -1;
}
return _activeTabIndex;
}
set
{
if (value < -1)
throw new ArgumentOutOfRangeException("value");
if (Tabs.Count == 0 && !_initialized)
{
_cachedActiveTabIndex = value;
}
else
{
if (ActiveTabIndex != value)
{
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = false;
}
if (value >= Tabs.Count)
{
_activeTabIndex = Tabs.Count-1;
_cachedActiveTabIndex = value;
}
else
{
_activeTabIndex = value;
_cachedActiveTabIndex = -1;
}
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = true;
}
}
}
}
}


In other words, remove the ArgumentException code block and move the condition inside the next block, where you set the real _activeTabIndex to the highest legal value, yet you put the real value in _cachedActiveTabIndex.

Step 3: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change the ActiveTabIndex property with the code above.

The same thing must be done in Javascript, in the Tabs.js file, if you intend to use a TabContainer with no static tabs defined. In case you do that, you will get a javascript error "Microsoft JScript runtime error: Sys.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: value
". The fix is to change the set_activeTabIndex function of the TabContainer in the file tabs.js to this:
set_activeTabIndex : function(value) {
if (!this.get_isInitialized()) {
this._cachedActiveTabIndex = value;
} else {
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(false);
}
if (value < -1 || value >= this.get_tabs().length) {
this._activeTabIndex = this.get_tabs().length-1;
this._cachedActiveTabIndex=value;
} else {
this._activeTabIndex = value;
this._cachedActiveTabIndex=-1;
}
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(true);
}
if (this._loaded) {
this.raiseActiveTabChanged();
}
this.raisePropertyChanged("activeTabIndex");
}
},


Step 4: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a tabs.js file. Change the set_activeTabIndex function with the code above.

This fixed it for me for now.

Step 5: Compile the now patched AjaxControlToolKit and use the resulting dll in your project instead of the default one.

As a reference, my test app does the following things:
  • Starts with a TabContainer with single TabPanel defined in the aspx
  • Has a button that adds new tabs to the TabContainer dynamically on the Click event
  • The panels have buttons in them that can be clicked
  • The active tab must be preserved during postbacks
  • The page must work both on synchronous and asynchronous postbacks


Here is the code for the page

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using AjaxControlToolkit;

public partial class _Default : Page
{
private int? _tabCount;

/// <summary>
/// Keep in ViewState the number of dynamically added tabs
/// </summary>
public int TabCount
{
get
{
if (_tabCount == null)
{
if (ViewState["TabCount"] == null)
TabCount = 0;
else
TabCount = (int) ViewState["TabCount"];
}
return _tabCount.Value;
}
set
{
_tabCount = value;
ViewState["TabCount"] = value;
}
}

protected void Page_Load(object sender, EventArgs e)
{
InitTabs();
}

/// <summary>
/// Add the dynamical tabs after each postback
/// </summary>
private void InitTabs()
{
for (int c = 0; c < TabCount; c++)
AddPanel();
}

/// <summary>
/// Dynamically add a panel to the TabContainer
/// </summary>
private void AddPanel()
{
TabPanel tp = new TabPanel();
tp.HeaderText = "Test Dinamic";
TextBox tb = new TextBox();
Button btn = new Button();
btn.Text = "Click me!";
tp.Controls.Add(btn);
tp.Controls.Add(tb);
TabContainer1.Tabs.Add(tp);
}

/// <summary>
/// Click event to add a new panel
/// and update the TabCount property
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void btnAddPanel_Click(object sender, EventArgs e)
{
AddPanel();
// do this if you didn't have any staticly defined
// tabs or else the dynamic tabs will be invisible
If (TabCount==0) TabContainer1.ActiveTabIndex=0;
TabCount++;
}
}

I've spent two hours today trying to understand why my application works. I mean, I was glad it worked, but, based on my understanding of the ASP.Net cycle, it shouldn't have.

Long story short: in Page_Load I was instantiating and adding to the Page Controls collection some controls. To my astonishment, the postback worked! With events and everything. Even worse, even if the controls were being created every time (independent of IsPostBack) and the data was always added from the database, the postback data was overwriting it!

As far as I knew, the postback data is being loaded before the Page_Load event. And when you change something in the Page_Load, it stays changed. But things are different with dynamically generated controls. My guess is that the Page_Load method is being executed for the page, then recursively for the child controls. If a control has not yet retrieved it's postback data, it tries again.

Yet, if you try to do the same in a button event or in Page_LoadComplete, it doesn't work.

So, if you dynamically add the same controls in the Page_Load in between postbacks, the postback data will be saved and the events will fire.

Warning: this is only a partially working solution due to some Javascript issues described (and solved) here.

A requirement I had was to maintain the scroll position of ListBoxes on PostBack. The only solution I could find was to get the scroll through Javascript (the scrollTop property of the select) and restore it on page load, however, that would have meant a lot of custom controls, not to mention lots of work, to which I am usually against.

So, I used a ControlAdapter! The ControlAdapter is something new to the NET 2.0 framework. The Control in 2.0 looks for a ControlAdapter and delegates the usual methods (like OnLoad,OnInit,Render,etc) to the adapter. You tell the site to use an adapter for a specific type of control and possibly a specific browser type (by using a browser file), and it uses that adapter for all of the controls of the selected type and also the ones inherited from them. To disallow the "adaptation" of your control, override ResolveAdapter to always return null.

Ok, the code!
C# code
///<summary>
/// This class saves the vertical scroll of listboxes
/// Set Attributes["resetScroll"] to something when you want to reset the scroll
///</summary>
public class ListBoxScrollAdapter : ControlAdapter
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        if ((Page != null) && (Control is WebControl))
        {
            WebControl ctrl = (WebControl) Control;
            string scrollTop = Page.Request.Form[Control.ClientID + "_scrollTop"];
            ScriptManagerHelper.RegisterHiddenField(Page, Control.ClientID + "_scrollTop", scrollTop);
            string script =
                string.Format(
                    "var hf=document.getElementById('{0}_scrollTop');var lb=document.getElementById('{0}');if(hf&&lb) hf.value=lb.scrollTop;",
                    Control.ClientID);
            ScriptManagerHelper.RegisterOnSubmitStatement(Page, Page.GetType(), Control.UniqueID + "_saveScroll",
                                                          script);
            if (string.IsNullOrEmpty(ctrl.Attributes["resetScroll"]))
            {
                script =
                    string.Format(
                        "var hf=document.getElementById('{0}_scrollTop');var lb=document.getElementById('{0}');if(hf&&lb) lb.scrollTop=hf.value;",
                        Control.ClientID);
                ScriptManagerHelper.RegisterStartupScript(Page, Page.GetType(), Control.ClientID + "_restoreScroll",
                                                          script, true);
            } else
            {
                ctrl.Attributes["resetScroll"] = null;
            }
        }
    }
}


Browser file content<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType ="System.Web.UI.WebControls.ListBox"
adapterType="Siderite.Web.WebAdapters.ListBoxScrollAdapter" />
</controlAdapters>
</browser>
</browsers>


Of course, you will ask me What is that ScriptManagerHelper? It's a little something that tries to get the ScriptManager class without having to reference the System.Web.Extensions library for Ajax. That means that if there is Ajax around, it will use ScriptManager.[method] and if it is not it will use ClientScript.[method]. To.Int(object) is obviously something that gets the integer value from a string.

There is another thing, at the beginning I've inherited this adapter from a WebControlAdapter, but it resulted in showing all the options in the select (all the items in the ListBox) with empty text. The value was set as well as the number of options. It might be because in WebControlAdapter the Render method looks like this:
protected internal override void Render(HtmlTextWriter writer)
{
  this.RenderBeginTag(writer);
  this.RenderContents(writer);
  this.RenderEndTag(writer);
}

instead of just calling the control Render method.

While working on a Windows app that used Crystal Reports 9 with Visual Studio 2003 on .NET 1.1 I've stumbled upon a problem. For no apparent reason, the saving of a report took minutes rather than seconds, the .cs file associated with the report sometimes disappeared and randomly errors like the one below appeared:

Custom tool error: "Code generator 'ReportCodeGenerator' failed. Exception stack = System.IO.FileNotFoundException: File or assembly name CrystalDecisions.CrystalReports.Engine, or one of its dependencies, was not found.
File name: "CrystalDecisions.CrystalReports.Engine"
at CrystalDecisions.VSShell.CodeGen.ReportClassWriter..ctor(String filePath)
at CrystalDecisions.VSShell.CodeGen.ReportCodeGenerator.GenerateCode(String inputFileName, String inputFileContent)


I've searched the net and found a lot about wrong versions, deploying, etc. But I wasn't deploying anything, for crying out loud! First I thought it was about installing the Crystal Reports X runtime. I removed it, but that didn't solve anything. What was I to do?

Well, a temporary solution was to backup the .cs file somewhere, as it seemed not to change. Then I would just copy it back and add it to the project and it would work, but again, the time consumed by saving the report was huge! And there were caching problems. Sometimes I would see the report without the last modification in the application.

After a while I realised that actually I was deploying something. The project also had an installer, with the Crystal Reports .msm files. I remembered that I did install the application once, in order to test the installation process. Then, of course, I did remove it from the installed applications. That was it! All I had to do was to reinstall the application, thus repairing some Crystal Reports files or configuration that the uninstall process previously messed up.

I was looking for an answer to the problem of a grid inside an update panel. You see, since the rows and cells of a DataGrid or a GridView are special controls that can't be put inside panels, only in specific parent controls like tables and rows, there is no way to update only a row or a cell of a grid. If the grid is big, it takes a long time to render it entirely, it takes the CPU to 100%, it even blocks the animation of gifs. That results in ugly Ajax.

So, my first thought was: is there a way to update only what has changed? As I was saying in a previous post, a Page is rendered as its HTML string the first time it is loaded and then each Ajax postback makes it render like a list of tokens. The token format is this:

length|type|id|content|


For example 100|updatePanel|UpdatePanel1|<inner HTML of panel of 100 bytes>|

What if I would to insert my own tokens, then? Could I, let's say, change the innerHTML of a control outside of the UpdatePanel? And the answer is YES!

There are 20 token types:
  • updatePanel
  • hiddenField
  • arrayDeclaration
  • scriptBlock
  • expando
  • onSubmit
  • asyncPostBackControlIDs
  • postBackControlIDs
  • updatePanelIDs
  • asyncPostBackTimeout
  • childUpdatePanelIDs
  • panelsToRefreshIDs
  • formAction
  • dataItem
  • dataItemJson
  • scriptDispose
  • pageRedirect
  • error
  • pageTitle
  • focus


Most are not interesting, but 4 of them are!

type:updatePanel
If you add a token to the rendered page string that has the type updatePanel and the id is the UniqueID or ClientID of a control, the content will replace the innerHTML of that control, even if the control is not in an UpdatePanel.

type:hiddenField
If you add a token to the rendered page string that has the type hiddenField and the id is the UniqueID or ClientID of a control, the content will replace the value html property of that control. You can use it on hidden fields, but also on any type of input or html element that has a value. If the control does not exist, a hidden input will be created with that id and then the value will be set. You could read that value after a normal PostBack, let's say.

type:expando
If you add a token to the rendered page string that has the type expando a script will be executed in Javascript that looks like this:
id=content

Example: 5|expando|document.getElementById('TextBox1').style.backgroundColor|'red'|
This will result in the change of the background color of the control with id TextBox1 to red.

type:focus
If you add a token to the rendered page string that has the type focus and the content is a ClientID, then the focus will be set to that control provided that the focus.js script has been loaded. This script is loaded when you use Page.SetFocus. So, in order to set the focus to a control using this method, you must use SetFocus in PageLoad on any control you would like.

Why not use SetFocus, then, and be done with it? Well, because this, as all the methods above work on ANY control in the page, not just the ones in the update panel.

And now the code
using System;
using System.IO;
using System.Web.UI;
 
public partial class _Default : Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Use SetFocus so that focus.js is loaded
        SetFocus(TextBox1);
    }
 
    // get the token for a javascript property change
    private string TokenizeProperty(string value, string property)
    {
        return string.Format("{0}|expando|{1}|{2}|", value.Length, property, value);
    }
 
    // get the token for a javascript value change
    private string TokenizeValue(string content, string controlID)
    {
        return string.Format("{0}|hiddenField|{1}|{2}|", content.Length, controlID, content);
    }
 
    // get the token for setting focus to a control through javascript
    private string TokenizeFocus(string controlID)
    {
        return string.Format("{0}|focus||{1}|", controlID.Length, controlID);
    }
 
    // get the token to replace the innerHTML through javascript
    private string TokenizeInnerHtml(string content, string controlID)
    {
        return string.Format("{0}|updatePanel|{1}|{2}|", content.Length, controlID, content);
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        // we only do this in the case of an Async Postback
 
        ScriptManager sm = ScriptManager.GetCurrent(this);
        if ((sm == null) || !sm.IsInAsyncPostBack)
        {
            base.Render(writer);
            return;
        }
 
        // Get the rendered page string 
        // (which should be a list of Ajax tokens)
 
        HtmlTextWriter tw = new HtmlTextWriter(new StringWriter());
        base.Render(tw);
        string content = tw.InnerWriter.ToString();
 
        // Get some meaningless text that changes over time
        string insert = DateTime.Now.ToLongTimeString();
 
        //Change the inner html of Panel2 and some 
        // table cell with the id 'testTD' with the string
        content += TokenizeInnerHtml(insert, Panel2.UniqueID);
        content += TokenizeInnerHtml(insert, "testTD");
 
        // Set value of TextBox2 to the string
        content += TokenizeValue(insert, TextBox2.UniqueID);
 
        // change the background color of TextBox2 to red
        string property = string.Format(
            "document.getElementById('{0}').style.backgroundColor",
            TextBox2.ClientID);
        string value = "'red'";
        content += TokenizeProperty(value, property);
 
        // Set focus to TextBox2
        content += TokenizeFocus(TextBox2.ClientID);
 
        // write the content with the extra tokens
        writer.Write(content);
    }
     
}



Of course, that doesn't solve my initial problem, of speeding up the Ajax rendering of large grids. That's because, even if I would solve the ViewState issues and the quirks that are bound to appear, I still can't change the innerHTML property of tables or table rows, as it is a readonly property.

So where am I to use this? It's easy: first of all, put a button (and only a button) inside an UpdatePanel. Any click on that button will trigger an Ajax postback, but will send a minimal amount of data. Then, put outside the UpdatePanel a Panel. Now you can override the Render of the page and on every Ajax postback, add whatever HTML you want to that panel. If you want to do it from javascript, put the button in a div with style="display:none" and then trigger the button click whenever you want to cause the postback. I am certain that for large readonly grids, that is a way faster method than the putting the grid inside the updatepanel.

To do this the traditional Atlas way you would have had to declare a web service, and then to set a javascript onclick event on the button, that would have executed a WebService method that returned a string, and manually change the innerHTML of the panel.

and has 0 comments
VB has a feature that it missing in C#, that is indexed properties. There are various methods to emulate this behaviour in C# and here is a small article about it:
Three C# Tips: Indexed properties, property delegates, and read-only subproperties.

However, a problem arises. What happens if you have a library written in VB and having indexed properties and you try to use it in a C# project? Well, it works, and the generated code is something like:
 public override bool get_PropertyName(string index)
{
}
public override bool set_PropertyName(string index)
{
}


Somehow, this compiles :) Anyway, the problem now is that you can't use the VB code that used to use the VB library if you convert it to C# like this. I haven't found any way to do this:
VBLib(with indexed properties)+VBApplication(inheriting or overriding indexed properties) -> C#Lib(translated)+VBApplication(unchanged).

and has 0 comments
You want to create a report with Crystal Reports and you create that weird ADO.Net DataSet, then you add a Crystal Report, you select the tables you want from the created DataSet, then you drag the Id references to create the table links, you go through all that weird Report Expert, you add a Crystal ReportViewer to a Windows Form and you press F5! And you get "Query Engine Error".

Well, two main reasons for this are explained in this nice article: "Query Engine Error" With Crystal Reports .NET, but you just created the report, there is no way you changed the XSD or the name of the tables.

I tried a lot of things until I found out what was going on. You see, I had this tables that had an "Id" column and a many-to-many table that had "UserId" and "MenuId" columns. In order to link them, I did what I also did in the XSD, I dragged the Id (primary key of each table) to the UserId and MenuId columns. That was the problem! You have to do it the other way around, drag the Foreign Key columns to the Primary Key columns.

Hopefully, you would have read this article before wasting hours to find out what the hell is that error and where it comes from... It happened to me with Crystal Reports 9.0 and Visual Studio 2003.

Oh! And don't bother to link the tables in the DataSet XSD, since Crystal Reports seems oblivious to that.

I am working on this windows app that uses Crystal Reports. I've never used it before and I thought "Hey! I could learn something new". So I opened this .rpt file in Visual Studio 2003 and I got the most incomprehensible interface ever. I mean, I will have to invest some hours just to understand what the report interface is all about.

But anyway, I thought I would take an existing report and then just change something, like adding a new field. I went with the mouse on an empty space, right click, context menu, Insert... I could insert text. I could insert Special Fields (report data like number of the page, creation date and so on). I could insert Fields. Only that option was disabled! I've tried every option in the context menu, no avail.

Finally I've decided I am to dumb to figure it out, I went to man's best friend: Google! The answer found in an obscure forum was:
- Go to View (in Visual Studio 2003)
- click Other Windows
- click Document Outline

Now a Field Explorer window is open and I can see all fields and drag them to the report. That was it! :-/

and has 2 comments
When one wants to replace a text, say, case insensitive, one uses the .NET Regex class. In order to make sure the text to be replaced is not interpreted as a Regex pattern, the Regex.Escape method is used. But what about the replacement string? What if you want to replace the text with "${0}", which in Regexian means "the entire matched string" ? You need to somehow escape the replace pattern.

I have no idea where to find this information on MSDN, although I am sure it is hidden somewhere in all that Regex labyrinth. Here is the link on MSDN: Substitutions

So here is the info: You only need escaping the dollar sign, so the code would look like this:

Regex reg=new Regex("Text to replace",RegexOptions.CaseInsensitive);

string s="here is the text to replace";

s=Regex.Replace(s,"$${0}");

Now the value of s is "here is the ${0}".

I found this little page while searching for a DataGridDropDownListColumn for NET 1.1 Windows Forms. It works and seems elegant enough.

To bind the column values, use column.myComboBox.DataSource, DisplayMember and ValueMember.

If you look for solutions to get rid of huge ViewStates from your pages you will get a lot of people telling you to override SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium in your pages and do complicated stuff like keeping the ViewState in the Cache or in a database, calculating strange keys, etc.

No more! Net 2.0 has something called a PageStatePersister. It is an abstract class and every Page has one. In case no override occurs, the default is a HiddenFieldPageStatePersister, but you can also use the provided SessionPageStatePersister like this:
protected override PageStatePersister PageStatePersister
{
get
{
return new SessionPageStatePersister(this);
}
}


And that's it! It works with Ajax and UpdatePanel, too.

However, this is no "Silver Bullet", as the SessionPageStatePersister will have issues with multiple windows open with the same session (like pop up windows) as exampled in this nice article. Also check out this situation when, during Ajax callbacks, a full ViewState is returned due to the ever troublesome ImageButtons.

There is no reason not to create your own PageStatePersister, though. The abstract class is public (not internal and sealed as Microsoft likes their most useful classes) and you can inherit it. You can even store the state in the Cache! :)

A very comprehensive article on ViewState is here.

and has 0 comments
A while ago I wrote a post about the bug in the DataTable.Select method with columns with comma in their names.

Today I discovered another bug, when using DataTable.Select with an empty sort string, but not an empty filter string, there is an implicit sorting by the first column. Short example: a DataTable with a single column called "words" containing values c,b,a,d , when Selected with a filter like "words is not null" and a null or empty sort string, will return a,b,c,d.

The only solution for this was to drop DataTable.Select entirely and use DataView, with its NET 2.0 method DataView.ToTable. So the code to take a DataTable and return the filtered and sorted table would look like this:
public static DataTable Select(DataTable table, string filter, string sort)
{
if (table == null) return null;
var dv=new DataView(table);
dv.RowStateFilter=DataViewRowState.CurrentRows;
dv.RowFilter = filter;
dv.Sort = sort;
return dv.ToTable();
}

But DataView has the same problem with columns with comma in their names. We solve it in the same way we solved it in the previous post: we change the column names, the sort and filter strings, we select, then we change the column names back:
public static DataTable SelectSafe(this DataTable table, string filter, string sort)
{
var originalColumnNames = new Dictionary<string, string>();

foreach (DataColumn dc in table.Columns)
{
if (dc.ColumnName.IndexOf(',') > -1)
{
var columnName = dc.ColumnName;
var safeColumnName = columnName.Replace(",", ";");
var reg = new Regex(Regex.Escape("[" + columnName + "]"), RegexOptions.IgnoreCase);
dc.ColumnName = safeColumnName;
if (!String.IsNullOrEmpty(filter)) {
filter = reg.Replace(filter, "[" + safeColumnName + "]");
}
if (!String.IsNullOrEmpty(sort)) {
sort = reg.Replace(sort, "[" + safeColumnName + "]");
}
originalColumnNames[safeColumnName] = columnName;
}
}

var newTable = Select(table, filter, sort);

foreach (KeyValuePair<string, string> pair in originalColumnNames)
{
table.Columns[pair.Key].ColumnName = pair.Value;
newTable.Columns[pair.Key].ColumnName = pair.Value;
}
return newTable;
}