Friday, August 22, 2008

Adding an inherited custom control into Visual Studio Toolbox

Let's say we created the base control XPanel:
   public class XPanel : Panel
   
and its descendant XFinalPanel:
   public class XFinalPanel : XPanel
   
We are to show the XFinalPanel control in the Toolbox and to hide the XPanel. By default both controls should be visible in the Toolbox. There is a ToolboxItem attribute that allows to manage the control visibility. As it is inherited, it sounds logical to write the following code to solve the task:
   [ToolboxItem(false)]
   public class XPanel : Panel
   ...
   [ToolboxItem(true)]
   public class XFinalPanel : XPanel
Unfortunately, it doesn't work. The XFinalPanel is shown in the Toolbox, but you can't drag&drop it to the form. The only workaround I've found is to declare XPanel as abstract class. Or, of course, you may let both controls be shown in the toolbox.

Friday, August 8, 2008

'could not be set on property' error in custom ASP.NET controls

XPanel class in our EasyQuery.NET WebForms project contains the Appearance property of complex type XAppearance. This class has another "complex" property MenuStyle so we got a complex property inside another complex property:
 
public class XPanel : Panel, INamingContainer {
     .  .  .  .  .  .  .  .  .  . 
     private XAppearance appearance = null;

  [DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
      NotifyParentProperty(true)]
  public XAppearance Appearance {
      get {
             return appearance; 
         }
  }
     .  .  .  .  .  .  .  .  .  . 
}

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .

[TypeConverter(typeof(ExpandableObjectConverter))]
public class XAppearance : IStateManager {
    .  .  .  .  .  .  .  .  .  . 
    
 private ScriptMenuStyle scriptMenuStyle;

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
     NotifyParentProperty(true)]
    public ScriptMenuStyle ScriptMenuStyle {
        get {
            return scriptMenuStyle;
        }
    }
    .  .  .  .  .  .  .  .  .  . 
}

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .

[TypeConverter(typeof(ExpandableObjectConverter))]
public class ScriptMenuStyle : Style {
    .  .  .  .  .  .  .  .  .  . 
}

Everything will work OK at run-time. But if you try to change some sub-property of XPanel.Appearance.MenuStyle in visual designer then next time you load this form, Visual Studio will show you the following error message:
"SomeValue" could not be set on property SomeProperty
We have spent several hours trying to find the reason of this error message without any success. Finally we have found the solution but still do not know the source of the problem. The solution is quite simple. We just need to make ScriptMenuStyle property writable:
 
[TypeConverter(typeof(ExpandableObjectConverter))]
public class XAppearance : IStateManager {
    .  .  .  .  .  .  .  .  .  . 
    public ScriptMenuStyle ScriptMenuStyle {
        get {
            return scriptMenuStyle;
        }
  set {
      scriptMenuStyle = value;
  }
    }
    .  .  .  .  .  .  .  .  .  . 
}
Now everything works correctly both at run-time and at design-time but it will be great to get known what was the cause of this problem.

Thursday, July 10, 2008

Item dragging in Windows Forms ListBox control

Drag-n-drop support in general case is the ability to drag some object from one control and then drop it into the same or some other control.
But very often when we work with ListBox controls we just need an ability to move items up and down within one particular control. It is not necessary to implement all those drag-n-drop functionality in such case.
The following small class will help you to handle such situation without overkill. When it is attached to some ListBox you can grab any item and move it on any place within the control.
Here is the class source:
using System;
using System.Windows.Forms;

namespace Korzh.WinControls {

    /// 
    /// Turn on item dragging for some ListBox control
    /// 
    class ListBoxItemDragger {
        private ListBox listBox;

        private int dragItemIndex = -1;

        /// 
        /// Gets the index of the dragged item.
        /// 
        /// The index of the dragged item.
        public int DragItemIndex {
            get { return dragItemIndex; } 
        }

        private bool dragging = false;

        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// The list box.
        public ListBoxItemDragger(ListBox listBox) {
            Attach(listBox);
        }

        /// 
        /// Attaches current instance to some ListBox control.
        /// 
        public void Attach(ListBox listBox) {
            this.listBox = listBox;
            this.listBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MouseDownHandler);
            this.listBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.MouseUpHandler);
            this.listBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.MouseMoveHandler);
        }

        /// 
        /// Detaches current instance from ListBox control.
        /// 
        public void Detach() {
            this.listBox.MouseDown -= new System.Windows.Forms.MouseEventHandler(this.MouseDownHandler);
            this.listBox.MouseUp -= new System.Windows.Forms.MouseEventHandler(this.MouseUpHandler);
            this.listBox.MouseMove -= new System.Windows.Forms.MouseEventHandler(this.MouseMoveHandler);
        }

        private Cursor dragCursor = Cursors.SizeNS;
        public Cursor DragCursor {
            get { return dragCursor; }
            set { dragCursor = value; }
        }

        /// 
        /// Raises the  event.
        /// 
        /// The  instance containing the event data.
        protected void OnItemMoved(EventArgs e) {
            if (ItemMoved != null) ItemMoved(this, e);
        }

        /// 
        /// Occurs when some item has been moved
        /// 
        public event EventHandler ItemMoved;

        private void MouseDownHandler(object sender, System.Windows.Forms.MouseEventArgs e) {
            dragItemIndex = listBox.SelectedIndex;
        }


        private Cursor prevCursor = Cursors.Default;

        private void MouseUpHandler(object sender, System.Windows.Forms.MouseEventArgs e) {
            dragItemIndex = -1;
            if (dragging) {
                listBox.Cursor = prevCursor;
                dragging = false;
            }
        }

        private void MouseMoveHandler(object sender, System.Windows.Forms.MouseEventArgs e) {
            if (dragItemIndex >= 0 && e.Y > 0) {
                if (!dragging) {
                    dragging = true;
                    prevCursor = listBox.Cursor;
                    listBox.Cursor = DragCursor;
                }
                int dstIndex = listBox.IndexFromPoint(e.X, e.Y);

                if (dragItemIndex != dstIndex) {
                    object item = listBox.Items[dragItemIndex];
                    listBox.BeginUpdate();
                    try {
                        listBox.Items.RemoveAt(dragItemIndex);
                        if (dstIndex != ListBox.NoMatches)
                            listBox.Items.Insert(dstIndex, item);
                        else
                            dstIndex = listBox.Items.Add(item);

                        listBox.SelectedIndex = dstIndex;
                    }
                    finally {
                        listBox.EndUpdate();
                    }
                    dragItemIndex = dstIndex;
                    OnItemMoved(EventArgs.Empty);
                }
            }
        }

    }
}

To use this class you just need to create its instance and attach it to your ListBox control. In the most simple case it will be only one line of code:
.  .  .  .  .  .  .
 new ListBoxItemDragger(someListBoxControl);
.  .  .  .  .  .  .
Nothing else is needed! We have created ListBoxItemDragger object and it was atomatically attached to the ListBox control passed in constructor's parameter. You do not even need to store the object of this class into some variable. Our object will "live" as long as the ListBox control it is attached to. However you may want to get notified when some item is dragged. Additionally you may want to change the cursor shown during dragging (the default is SizeNS cursor as you can see from the source). In such case you should get the instance of this class and set some of its properties:
.  .  .  .  .  .  .
 ListBoxItemDragger itemDragger1 = new ListBoxItemDragger(someListBox);
 itemDragger1.DragCursor = Cursor.Hand;
 itemDragger1.ItemMoved += new EventHandler(SomeListBoxItemMoved);

.  .  .  .  .  .  .

private void SomeListBoxItemMoved(object sender, System.EventArgs e) {
 //handle ListBox item moving here
}
Here we created an instance of ListBoxItemDragger class, then changed its DragCursor property and finally we atached the handler for ItemMoved event. To get the index of dragged item use DragItemIndex property. That's all. Enjoy!

Wednesday, July 2, 2008

IE quirk: offsetParent of DOM element added into document.body

We spent about 1 day of work while find out the following crazy feature of DOM implementation in Internet Explorer. Just look at the HTML page below and try to say what will be shown on execution of the test() JavaScript function:

  
    DOM Element offsetParent test
  
  
  
    

 Test 
    


You think it will show you "BODY" text?
You will be right in case of FireFox or Opera. But in Internet Explorer (we tested it both on versions 6 and 7) test() function will print "HTML" instead of "BODY" because element.offsetParent in this case is equal to document.documentElement instead of document.body as you may suppose.

Why is it implemented in such strage way? I do not know. Moreover, for some reason it works right (shows "BODY" text) if you change the DTD declaration for this page to the following:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"">
So just be careful when implementing such functions as described in this post.
Actually we had to make changes in our existing code when found this strange behaviour, for example in our GetElementAbsolutePos() function.
The following piece of code:
 
 if (offsetParent != document.body) {  
 res.x -= offsetParent.scrollLeft;  
 res.y -= offsetParent.scrollTop;  
} 

 
had been changed to this one:
     if (offsetParent != document.body && offsetParent != document.documentElement) {  
 res.x -= offsetParent.scrollLeft;  
 res.y -= offsetParent.scrollTop;  
}  

     
Hope this information will save somebody's time.
Enjoy!

Wednesday, May 28, 2008

Absolute coordinates of DOM element within document

The problem

Sometimes (especially in AJAX projects) it is necessary to get the position of some DOM element in "absolute" coordinates within current document. 
For example such "absolute" position is needed if you would like to show some hidden DIV object exactly on the position (or with some offset) of another element. We use this function in our EasyQuery.NET WebForms library to show popup menu under some condition element (you can see an example here).

The solution

Such properties as style.left, style.top or offsetLeft, offsetTop can be used to get (or set) the position of element within its parent. So to get absolute element's position within document we should move upward on element's tree and add the position of all element's parents (except the latest document element).

However it is not quite easy. There are still some problems:
  1. First, we need to take into account possible scrolling in element's parents and decrease our result accordingly.
  2. Second, there are some distinctions in behavior of different browsers (as usual :-( ). For Internet Explorer we always can just subtract scrolling position of the object stored in element's offsetParent prooperty. But for FireFox we also need to take into consideration all parents accessible by parentNode properties.
  3. Finally, we should take into account the border width for some parent elements. Unfortunately this task is not so easy as it can be supposed especially for Internet Explorer browser.
So here is the function we get in result:

 
function __getIEVersion() {
    var rv = -1; // Return value assumes failure.
    if (navigator.appName == 'Microsoft Internet Explorer') {
        var ua = navigator.userAgent;
        var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
        if (re.exec(ua) != null)
            rv = parseFloat(RegExp.$1);
    }
    return rv;
}

function __getOperaVersion() {
    var rv = 0; // Default value
    if (window.opera) {
        var sver = window.opera.version();
        rv = parseFloat(sver);
    }
    return rv;
}

var __userAgent = navigator.userAgent;
var __isIE =  navigator.appVersion.match(/MSIE/) != null;
var __IEVersion = __getIEVersion();
var __isIENew = __isIE && __IEVersion >= 8;
var __isIEOld = __isIE && !__isIENew;

var __isFireFox = __userAgent.match(/firefox/i) != null;
var __isFireFoxOld = __isFireFox && ((__userAgent.match(/firefox\/2./i) != null) || (__userAgent.match(/firefox\/1./i) != null));
var __isFireFoxNew = __isFireFox && !__isFireFoxOld;

var __isWebKit =  navigator.appVersion.match(/WebKit/) != null;
var __isChrome =  navigator.appVersion.match(/Chrome/) != null;
var __isOpera =  window.opera != null;
var __operaVersion = __getOperaVersion();
var __isOperaOld = __isOpera && (__operaVersion < 10);

function __parseBorderWidth(width) {
    var res = 0;
    if (typeof(width) == "string" && width != null && width != "" ) {
        var p = width.indexOf("px");
        if (p >= 0) {
            res = parseInt(width.substring(0, p));
        }
        else {
       //do not know how to calculate other values (such as 0.5em or 0.1cm) correctly now
      //so just set the width to 1 pixel
            res = 1; 
        }
    }
    return res;
}


//returns border width for some element
function __getBorderWidth(element) {
 var res = new Object();
 res.left = 0; res.top = 0; res.right = 0; res.bottom = 0;
 if (window.getComputedStyle) {
  //for Firefox
  var elStyle = window.getComputedStyle(element, null);
  res.left = parseInt(elStyle.borderLeftWidth.slice(0, -2));  
  res.top = parseInt(elStyle.borderTopWidth.slice(0, -2));  
  res.right = parseInt(elStyle.borderRightWidth.slice(0, -2));  
  res.bottom = parseInt(elStyle.borderBottomWidth.slice(0, -2));  
 }
 else {
  //for other browsers
  res.left = __parseBorderWidth(element.style.borderLeftWidth);
  res.top = __parseBorderWidth(element.style.borderTopWidth);
  res.right = __parseBorderWidth(element.style.borderRightWidth);
  res.bottom = __parseBorderWidth(element.style.borderBottomWidth);
 }
   
 return res;
}


//returns the absolute position of some element within document
function getElementAbsolutePos(element) {
 var res = {};
 res.x = 0; res.y = 0;
 if (element !== null) { 
  //use getBoundingClientRect function available in new browsers
  if (element.getBoundingClientRect) {
          var box = element.getBoundingClientRect();
      
       var body = document.body;
       var docElem = document.documentElement;
       
       var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
       var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
       
       var clientTop = docElem.clientTop || body.clientTop || 0;
       var clientLeft = docElem.clientLeft || body.clientLeft || 0;
        

      res.x = Math.round(box.left + scrollLeft - clientLeft);
      res.y = Math.round(box.top + scrollTop - clientTop);
      
  }
  else { //for old browsers
   res.x = element.offsetLeft;
   res.y = element.offsetTop;
   
   var parentNode = element.parentNode;
   var borderWidth = null;

   while (offsetParent != null) {
    res.x += offsetParent.offsetLeft;
    res.y += offsetParent.offsetTop;
    
    var parentTagName = offsetParent.tagName.toLowerCase(); 

    if ((__isIEOld && parentTagName != "table") || ((__isFireFoxNew || __isChrome) && parentTagName == "td")) {      
     borderWidth = __getBorderWidth(offsetParent);
     res.x += borderWidth.left;
     res.y += borderWidth.top;
    }
    
    if (offsetParent != document.body && offsetParent != document.documentElement) {
     res.x -= offsetParent.scrollLeft;
     res.y -= offsetParent.scrollTop;
    }


    //next lines are necessary to fix the problem with offsetParent
    if (!__isIE && !__isOperaOld || __isIENew) {
     while (offsetParent != parentNode && parentNode !== null) {
      res.x -= parentNode.scrollLeft;
      res.y -= parentNode.scrollTop;
      if (__isFireFoxOld || __isWebKit) {
       borderWidth = __getBorderWidth(parentNode);
       res.x += borderWidth.left;
       res.y += borderWidth.top;
      }
      parentNode = parentNode.parentNode;
     }    
    }

    parentNode = offsetParent.parentNode;
    offsetParent = offsetParent.offsetParent;
   }
  }
 }
    return res;
}

To use this function just pass your element in function's parameter and get the result object with left and top coordinates stored in x and y properties accordingly:
   
     var pos = getElementAbsolutePos(myElement);  
     window.alert("Element's left: " + pos.x " and top: " + pos.y);  

GetElementAbsolutePos function was tested on all most used browsers:

  • Internet Explorer 7.0 and higher
  • FireFox 2.x and FireFox 3.x.
  • Opera 9.x, 10.x
  • Chrome 5.0

Saturday, May 10, 2008

Update ListBox item in Windows Forms

Problem description

Sometimes in Windows Forms applications you use the following code pattern to fill some ListBox control with necessary data:
 
public class MyObject {
  
 private string text;

 public string Text {
  get { return text; }
  set { text = value; }
 }

 public MyObject(string text) {
  this.text = text;
 }
  

 public override string ToString() {
  return this.text;
 }

}

.  .  .  .  .  .  .

 myListBox.Items.Add(new MyObject("Item 1"));
 myListBox.Items.Add(new MyObject("Item 2"));
 myListBox.Items.Add(new MyObject("Item 3"));
.  .  .  .  .  .  .


Now let's suppose you need to change some item and set new value to it's Text property:
 
.  .  .  .  .  .  .
 MyObject item = (MyObject)myListBox.Items[0];
 item.Text = "New value";
 //Corresponding item in list box is not updated accordingly.
.  .  .  .  .  .  .

  
However the corresponding item in your ListBox will not reflect to such change even if you manually "tell" the ListBox to update (through Update or Refresh methods). The solution The right solution fot this problem is to use data binding (you can read this MSDN article for more information). But very often it is too complicated way for such simple situation so I would like to propose you some gimmick which allows to solve the problem without all that binding stuff (and it coud work even faster in most cases). We just need to re-assign modified object to necessary item in the list. So I have wrotten the following simple function:
 
 private void UpdateListBoxItem(ListBox lb, object item) {
  int index = lb.Items.IndexOf(item);
  int currIndex = lb.SelectedIndex;
  lb.BeginUpdate();
  try {
   lb.ClearSelected();
   lb.Items[index] = item;
   lb.SelectedIndex = currIndex;
  }
  finally {
   lb.EndUpdate();
  }
 }


 
Now when I need to update modified item in the list I just call this function with my ListBox object at first parameter and an update item object in second:
.  .  .  .  .  .  .
 MyObject item = (MyObject)myListBox.Items[0];
 item.Text = "New value";
 UpdateListBoxItem(myListBox, item);
.  .  .  .  .  .  .
     
As I said before, this is not quite correct solution but it does what we need and it helped me in many cases.

Wednesday, April 23, 2008

Applying XP visual styles to Windows Forms applications

It looks ridiculous but your programs on such modern Microsoft's technology as .NET Windows Forms by default will look like old-sytle Win32 API application. Nobody (even inside Microsoft I think) can tell why the Windows Forms controls don't use Windows XP themes whenever it is possible.

However it is not so bad as it could be. Microsoft allows to include XP themes support into your project using one of the following ways:

  • Through adding a manifest file into your project. Here is a MSDN Article which describes this way;
  • Through Application.EnableVisualStyles() method call - much more simple but sometimes buggy in .NET 1.1;
Personally I prefer the second way especially after Microsoft proposed a workaround for the bug with EnableVisualStyles() method on .NET 1.1.

So, to enable XP styles in your Windows Forms application you just should to place Application.EnableVisualStyles() call in your project's main() procedure and make an additional Application.DoEvents() call to fix the possible problems in .NET 1.1. 
That is all? Not exactly.

If you simply placed your controls on the forms from Toolbox then most of buttons, checkboxes and other "button-like" controls will still have old look even if you call EnableVisualStyles() at the begining of your program. 
Why? Because all those components have FlatStyle property equal to Standard value by default while it should be set to System value.

So we need to run through all controls on each form and set its FlastStyle property to FlatStyle.System. To simplify all described tasks we have wrotten special XPStyle class which contains several static methods. Using these methods you can easily add XP styles support into your existing Windows Forms program.

 
using System;
using System.Windows.Forms;

namespace Korzh.EasyQuery.ModelEditor
{
 /// 
 /// Represents different procedures that allows to turn on XP visual style for Windows Forms application
 /// 
 public class XPStyle
 {
        /// 
        /// Gets a value indicating whether XP themes feature is present.
        /// 
        /// true if XP themes feature is present; otherwise, false.
  public static bool IsXPThemesPresent {
   get { return OSFeature.Feature.IsPresent(OSFeature.Themes); }
  }

        /// 
        /// Enables the visual styles for application.
        /// 
  public static void EnableVisualStyles() {
            if (!IsXPThemesPresent) return;
   Application.EnableVisualStyles();
   Application.DoEvents();  
  }

        /// 
        /// Applies the visual styles for some control.
        /// 
        /// The control.
  public static void ApplyVisualStyles(Control control) {
            if (!IsXPThemesPresent) return;
   ChangeControlFlatStyleToSystem(control);
  }

  private static void ChangeControlFlatStyleToSystem(Control control) {
   // If the control derives from ButtonBase, 
   // set its FlatStyle property to FlatStyle.System.
   if(control.GetType().BaseType == typeof(ButtonBase)) {
    ((ButtonBase)control).FlatStyle = FlatStyle.System; 
   }

   // If the control holds other controls, iterate through them also.
   for(int i = 0; i < control.Controls.Count; i++) {
    ChangeControlFlatStyleToSystem(control.Controls[i]);
   }
  }

 }
}

Now you should call EnableVisualStyles() method of this class at the begining of your program and then call ApplyVisualStyles() for each form (at the end of form's constructor or in Load event handler).
 
 .  .  .  .  .  .  .  
 static void Main(string[] args) {
  XpStyle.EnableVisualStyles();

  Application.Run(new MainForm());
 }
 .  .  .  .  .  .  .  


 .  .  .  .  .  .  .  

 private void MainForm_Load(object sender, System.EventArgs e) {
  XPStyle.ApplyVisualStyles(this);
 }

  

Saturday, March 15, 2008

ASP.NET AJAX support in custom controls

Introduction

This article will be useful for any custom component developer who wants to update his/her controls so they will work correctly with ASP.NET AJAX. First, we will describe the most common problems that occur during such customizations and then propose the solution for those issues.

Problem Description

Let's suppose you develop some visual control for ASP.NET (e.g., some kind of edit box with several improvements). Let's also suppose your control uses some client scripts to implement those "improvements" in its behaviour.

The question is: will your control work well with ASP.NET AJAX enhancements? Or more precisely: will everything be all right if somebody places your control inside an UpdatePanel to enable partial-page updates?
The general answer is: No. And I will tell you why.
If your control uses some client-side scripts, then you should register those scripts on the PreRender stage using the RegisterClientScriptBlock method of the ClientScriptManager class. Usually, it looks similar to the following piece of code:
 
 protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);
    string scriptCode = "<script type=\"text/javascript\">\n";
    . . . . . . . . 
    //script code definition
    . . . . . . . . 
    scriptCode += "/script>";


    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "MyScriptName",
        scriptCode);
}
        

This code will work well, and your script will be included on the page on normal page rendering. It will even work inside an UpdatePanel, but only if your component is static - I mean, if it is placed on the page as usual and is created at the first page rendering. But, such a piece of code will not work if your component is placed inside an UpdatePanel and is created dynamically in response to some asynchronous update.

The Solution

To solve the problem, we need the following abilities:


  • the ability to determine if our control is inside an UpdatePanel;
  • the ability to determine whether the current post-back is being executed in partial-rendering mode or is an usual synchronous post-back;
  • a way to register our client scripts during an asynchronous post-back.

Moreover, the code which implements all the described features must not use static linking for an ASP.NET AJAX assembly (System.Web.Extensions.dll) because otherwise our control will not work on web-sites which do not have ASP.NET AJAX installed.

The first two features are quite easy to implement. We just need to look for control parents and check if any of them is an UpdatePanel control. For the second task, we will also need to check the IsInPartialRendering property of the found UpdatePanel. Please remember our limitation about static linking: so we can just access the property in the usual way but need to use the abilities provided by the classes from the System.Reflection namespace.

As for the third problem (client-script registering on partial updates): we should use the RegisterClientScriptXXX methods of the ScriptManager object which should be included on each page that supports ASP.NET AJAX. So, we need to find that object on our page first and just call the necessary method. The only problem (again): we can not do that directly. Instead, we should try to load the ASP.NET AJAX assembly first, then find the ScriptManager object and call the necessary method of that object using reflection.

So, here is the code (the AJAX class) which implements all the described tasks. All methods of this class are defined as static so we will not need to create an instance of this class to call them.
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;

namespace Korzh.WebControls {
    /// <summary>
    /// Represents different procedures for ASP.NET AJAX extentions support
    /// </summary>
    public class Ajax {
        /// <summary>
        /// Determines whether the specified control is inside UpdatePanel
        /// </summary>
        /// <param name="control">The control.</param>
        /// <returns>
        /// <c>true</c> if the specified control is inside UpdatePanel; otherwise,
        /// <c>false</c>.
        /// </returns>
        public static bool IsControlInsideUpdatePanel(Control control) {
            Control parent = control.Parent;
            while (parent != null) {
                if (parent.GetType().FullName.Equals("System.Web.UI.UpdatePanel"))
                    return true;
                parent = parent.Parent;
            }
            return false;            
        }

        /// <summary>
        /// Determines whether the specified control is in partial rendering.
        /// </summary>
        /// <param name="control">The control.</param>
        /// <returns>
        /// <c>true</c> if the specified control is in partial rendering; otherwise,
        /// <c>false</c>.
        /// </returns>
        public static bool IsControlInPartialRendering(Control control) {
            Control parent = control.Parent;
            while (parent != null) {
                if (parent.GetType().FullName.Equals("System.Web.UI.UpdatePanel")) {
                    System.Reflection.PropertyInfo propInfo =
                        parent.GetType().GetProperty("IsInPartialRendering");
                    if (propInfo != null) 
                        return (bool)propInfo.GetValue(parent, null);
                    else
                        return false;
                }
                parent = parent.Parent;
            }
            return false;
        }

        /// <summary>
        /// Determines whether the current postback is being executed in
        /// partial-rendering mode.
        /// </summary>
        /// <param name="page">The page object.</param>
        /// <returns>
        /// <c>true</c> if  the current postback is being executed in
        /// partial-rendering mode; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsInAsyncPostBack(Page page) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                System.Reflection.PropertyInfo propInfo = smClass.GetProperty(
                    "IsInAsyncPostBack");
                if (propInfo != null)
                    return (bool)propInfo.GetValue(scriptManager, null);
                else
                    return false;
            }
            return false;

        }

        private static bool ObjectIsInheritedFrom(Object obj, string fullTypeName) {
            Type type = obj.GetType();
            while (type != null) {
                if (type.FullName.Equals(fullTypeName)) return true;
                type = type.BaseType;
            }
            return false;
        }

        private static object FindScriptManager(Control parent) {
            foreach (Control control in parent.Controls) {
                if (ObjectIsInheritedFrom(control, "System.Web.UI.ScriptManager"))
                    return control;
                object result = FindScriptManager(control);
                if (result != null) return result;
            }
            return null;
        }

        private static System.Reflection.Assembly ajaxAssembly = null;

        private static void LoadAjaxAssembly() {
            if (ajaxAssembly == null) {
                ajaxAssembly = System.Reflection.Assembly.LoadFrom(
                    "System.Web.Extensions.dll");
            }
        }

        private static System.Type GetTypeFromAjaxAssembly(string className) {
            LoadAjaxAssembly();
            if (ajaxAssembly != null)
                return ajaxAssembly.GetType(className);
            else
                return null;
        }


        private static Type GetScriptManagerType(Object obj) {
            Type type = obj.GetType();
            while (type != null) {
                if (type.FullName.Equals("System.Web.UI.ScriptManager")) return type;
                type = type.BaseType;
            }
            return null;
        }

        /// <summary>
        /// Registers a client script block which will be rendered on each
        /// asynchronous postback.
        /// Works only if ScriptManager control is existed on the page. Otherwise
        /// does nothing.
        /// </summary>
        /// <param name="page">The Page object that is registering the client
        /// script block.</param>
        /// <param name="type">The type of the client script block. </param>
        /// <param name="key">The string that uniquely identifies the
        /// script block.</param>
        /// <param name="script">A string that contains the script.</param>
        /// <param name="addScriptTags">
        ///  A Boolean value that indicates whether to enclose the script
        /// block in <script> tags.
        /// </param>
        public static void RegisterClientScriptBlock(Page page, Type type,
            string key, string script, bool addScriptTags) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                if (smClass != null) {
                    Object[] args = new Object[] { page, type, key, script,
                        addScriptTags };
                    smClass.InvokeMember("RegisterClientScriptBlock", 
                            System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.Public | 
                            System.Reflection.BindingFlags.InvokeMethod,
                            null, null, args);
                }
            }
        }

        /// <summary>
        /// Registers a script file which to be rendered each time an asynchronous
        /// postback occurs.
        /// Works only if ScriptManager control is existed on the page. Otherwise
        /// does nothing.
        /// </summary>
        /// <param name="page">The Page object that is registering the client
        /// script file.</param>
        /// <param name="type">The type of the client script file.</param>
        /// <param name="key">The string that uniquely identifies the script file.</param>
        /// <param name="url">The URL that points to the script file.</param>
        public static void RegisterClientScriptInclude(Page page, Type type,
            string key, string url) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                if (smClass != null) {
                    Object[] args = new Object[] { page, type, key, url };
                    smClass.InvokeMember("RegisterClientScriptInclude", 
                            System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.Public | 
                            System.Reflection.BindingFlags.InvokeMethod,
                            null, null, args);
                }
            }
        }

        /// <summary>
        /// Registers a script file which to be rendered each time an asynchronous
        /// postback occurs.
        /// Works only if ScriptManager control is existed on the page. Otherwise
        /// does nothing.
        /// </summary>
        /// <param name="page">The page.</param>
        /// <param name="type">The type of the client script file.</param>
        /// <param name="resourceName">The name of resource that contains the
        /// script file.</param>
        public static void RegisterClientScriptResource(Page page, Type type,
            string resourceName) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                if (smClass != null) {
                    Object[] args = new Object[] { page, type, resourceName };
                    smClass.InvokeMember("RegisterClientScriptResource",
                            System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.Public |
                            System.Reflection.BindingFlags.InvokeMethod,
                            null, null, args);
                }
            }
        }
    }
}
Now, using this class, we can modify the OnPreRender method in our code from the first chapter:
protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);

    string scriptCode = "<script type=\"text/javascript\">\n<!--\n";
     . . . . . . . . 
    //script code definition
     . . . . . . . . 
    scriptCode += "// -->\n</script>";

    //register our client script for usual post-back
    //(will do nothing in case of partial udpate)
    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "MyScriptName",
        scriptCode);

    //register our script during asynchronous postback
    //this code will just do nothing if there is no ASP.NET AJAX installed
    if (Ajax.IsControlInPartialRendering(this))
        Ajax.RegisterClientScriptBlock(Page, this.GetType(), "MyScriptName",
        scriptCode, false)
}

As you can see using Ajax class you should add only two additional lines of code to make your control fully support ASP.NET AJAX and work properly inside UpdatePanel during partial update