AccessKeyHighlighter v1.0.1 released

May 31, 2008

I’ve released an update to the AccessKeyHighlighter to resolve a couple of issues and add some new features. Check it out on its CodePlex page.


Announcing the AccessKeyHighlighter control for ASP.NET AJAX

May 25, 2008

I’ve finally managed to get the first release of my AccessKeyHighlighter control for ASP.NET AJAX published. When the control is placed on a form it highlights the access keys assigned to form fields when the user holds down the access key shortcut key in their browser (Alt for IE & Safari, Shift+Alt for Firefox). You can see a live demo of it in action here.

If you find it useful, be sure to leave me a note on the CodePlex site, and if you find any issues or have any suggestions for improvements or new features, please create a feature request on the Issue Tracker page.


Living with ASP.NET AJAX: MasterPages, UpdatePanels and cross-content AsyncPostBackTriggers

March 31, 2007

One of the great things about the ASP.NET AJAX UpdatePanel control is the ability to trigger a refresh of the UpdatePanel’s contents based on the event of another control on the same page. This means you can have multiple UpdatePanels hosted in your page and only have them update themselves when necessary based on the events of the other controls on the page (you will need to explicitly set the UpdateMode property of the UpdatePanels to Conditional to achieve this). The markup to achieve this is very simple:

<asp:UpdatePanel ID="updToolbar" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:Button ID="btnRefresh" runat="server" Text="Refresh" />
    </ContentTemplate>
</asp:UpdatePanel>
<asp:UpdatePanel ID="updDynamicContent" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:GridView ID="gvListOfThings" runat="server"></asp:GridView>
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="btnRefresh" EventName="Click" />
    </Triggers>
</asp:UpdatePanel>

Here we have two UpdatePanels defined with the second one including a declaration for an AsyncPostBackTrigger that wires up the click event of the button contained in the other UpdatePanel. When the user clicks the button, the second UpdatePanel will refresh its contents. Easy.

Issues arise however when you start to use MasterPages with multiple ContentPlaceHolders defined, a fairly common scenario when designing ASP.NET UI beyond the basic tutorials. For some reason (I admit I don’t know why) you cannot specify a control in a different Content pane as the source of an AsyncPostBackTrigger. So this:

<asp:Content ID="conMain" runat="server" ContentPlaceHolderID="cphMain">
    <asp:UpdatePanel ID="updToolbar" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:Button ID="btnRefresh" runat="server" Text="Refresh" />
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Content>
<asp:Content ID="conRight" runat="server" ContentPlaceHolderID="cphRight">
    <asp:UpdatePanel ID="updDynamicContent" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:GridView ID="gvListOfThings" runat="server">
            </asp:GridView>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="btnRefresh" EventName="Click" />
        </Triggers>
    </asp:UpdatePanel>
</asp:Content>

Results in this:

Server Error in ‘/AjaxSample’ Application.


A control with ID ‘btnRefresh’ could not be found for the trigger in UpdatePanel ‘updDynamicContent’.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: A control with ID ‘btnRefresh’ could not be found for the trigger in UpdatePanel ‘updSummary’.

Not much fun! So how do we go ahead and make this possible?

A Solution

To make this possible and stay with the declarative model of defining AnsycPostBackTriggers as much as possible we can create a small custom server control to proxy the event across the Content panes of our content page. We can drop this control into the same content pane as the UpdatePanel we wish to define the trigger on, set the trigger to wire up to the control’s EventProxied event, and then in the handler for the actual control’s event we wish to update on we call the proxy control’s ProxyEvent method.

First of all, add a new class file to your website called EventProxy.cs and paste in the following code:

public class EventProxy : Control, IPostBackEventHandler
{
    public EventProxy() { }
    public void RaisePostBackEvent(string eventArgument) { }
    public event EventHandler<EventArgs> EventProxied;
    protected virtual void OnEventProxy(EventArgs e)
    {
        if (this.EventProxied != null)
        {
            this.EventProxied(this, e);
        }
    }
    public void ProxyEvent(EventArgs e)
    {
        OnEventProxy(e);
    }
}

This very basic control defines a public event EventProxied and a public method ProxyEvent(EventArgs) which just raises the EventProxied event. The control must implement the IPostBackEventHanlder interface in order for it to be allowed to be delcared as the source of an AsyncPostBackTrigger.

Now in your content page add a @Register directive to register the application’s dll as a source of controls with the following line:

<%@ Register Assembly="AJAXSample" Namespace="AJAXSample.Controls" TagPrefix="as" %>

(Be sure to change the assembly name and namespace to match those of your application)

Now we can change our markup sample from before to use the new control like so:

<%@ register assembly="AJAXSample" namespace="AJAXSample.Controls" tagprefix="as" %>
<asp:Content ID="conMain" runat="server" ContentPlaceHolderID="cphMain">
    <asp:UpdatePanel ID="updToolbar" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:Button ID="btnRefresh" runat="server" Text="Refresh" OnClick="btnRefresh_Click" />
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Content>
<asp:Content ID="conRight" runat="server" ContentPlaceHolderID="cphRight">
    <asp:UpdatePanel ID="updDynamicContent" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:GridView ID="gvListOfThings" runat="server">
            </asp:GridView>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="epRefreshButtonClicked" EventName="EventProxied" />
        </Triggers>
    </asp:UpdatePanel>
    <as:EventProxy ID="epRefreshButtonClicked" runat="server" />
</asp:Content>

Change your page’s code-behind to call the EventProxy control’s ProxyEvent method in the handler for the button’s click event like so:

protected void btnRefresh_Click(object sender, EventArgs e) { epRefreshButtonClicked.ProxyEvent(e); }

And that’s it! We now have a reusable control that allows us to declaratively use the event of a control in a different content pane as the trigger for a partial postback of our UpdatePanel. You’ll need to add an instance of the EventProxy control to your content pane for each event of a control in a different content pane you wish to use as an AsyncPostBackTrigger. The only code we have to write each time is to call the appropriate EventProxy control’s ProxyEvent method from the event handler for the event we wish to use as the trigger.

I hope this helps someone.