Joel Rumerman's Blog

Thinking I'm better looking and wealthier than I really am.
posts - 15, comments - 29, trackbacks - 4

Tuesday, April 03, 2007

A Sortable GridView (I mean ListView) Control in WPF

I’ve been tinkering with WPF over the last couple of weeks or so and now that I’m getting a bit comfortable in it, I decided to make a ListView I was working with (that contains a GridView) sortable based upon the column header being clicked. After doing some research, I came across the MSDN how-to article that described a basic way of accomplishing this task. This blog post also has the same example explained. Unfortunately, though, it relied upon using the Column Header text as the property name on the underlying colllection on which to sort. After implementing the example, I decided this stank. Actually, I realized this stank beforehand, but wanted to implement the example anyways.

It stinks because Column Headers can contain anything. They don’t necessarily contain text. They could contain a button, a list, a drawing, a 3D animation, etc. Furthermore, even if they did just contain plain text, who’s to say that the text in the column header is actually a property name on my underlying object? What if I used “Birth Date” as the column header text and the property value was “DOB?” While the example was simple and made to be that way, I wanted a bit more control of how my GridView sorted.

I’m not going to dive into all the problems I see with the GridView and the ViewBase aspects of WPF in this blog, but that isn’t to say that I don’t think there are a ton of problems with this half-assed implementation. I’m just going to talk about the simple solution that I came up with.

I decided that in order to make my solution semi-intuitive, I needed to describe on the column level, what property on the underlying object it should sort on.

Something that looks like this written in XAML:

<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" SortPropertyName="ID" Width="50" />

That being said, if I wanted to add properties I needed to extend the current GridViewColumn object to contain them so I was really talking about creating a new Grid View Column, extending the existing one.

So my XAML would really look like:

<local:SortableGridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" SortPropertyName="ID" IsDefaultSortColumn=”True” Width="50" />

where local is defined as the namespace that points back to the namespace in which my new control is defined. Something like:

xmlns:local="clr-namespace:ModelingTool"

That being said I needed to define my new SortableGridViewColumn. This was actually quite easy. I created a new CS file named SortableGridViewColumn.cs and then inherited from GridViewColumn and added a new dependency property called SortPropertyName

Here’s the code.

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

 

namespace ThreatModel

{

    public class SortableGridViewColumn : GridViewColumn

    {

 

        public string SortPropertyName

        {

            get { return (string)GetValue(SortPropertyNameProperty); }

            set { SetValue(SortPropertyNameProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for SortPropertyName.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty SortPropertyNameProperty =

            DependencyProperty.Register("SortPropertyName", typeof(string), typeof(SortableGridViewColumn), new UIPropertyMetadata(""));

 

 

        public bool IsDefaultSortColumn

        {

            get { return (bool)GetValue(IsDefaultSortColumnProperty); }

            set { SetValue(IsDefaultSortColumnProperty, value); }

        }

 

        public static readonly DependencyProperty IsDefaultSortColumnProperty =

            DependencyProperty.Register("IsDefaultSortColumn", typeof(bool), typeof(SortableGridViewColumn), new UIPropertyMetadata(false));

 

    }

}

 

As  you can see, I’m not doing anything real fancy here, just adding two dependency properties. One that provides the string name of the property that column should sort on when applied to the object. And one that sets a Boolean property to indicate if this column is the default sort column.

Now, whenever I declare my GridView’s columns, I use the

 

        <local:SortableGridViewColumn Header="Element Name" DisplayMemberBinding="{Binding ElementName}" SortPropertyName="ElementName" />

 syntax.

Defining the new SortableGridViewColumn control was the first step. For true encapsulation, the ListView should actually handle all of the sorting internally. (In fact, it should be the GridView that handles the sorting internally. Unfortunately, the GridView doesn’t provide any mechanism to accomplish this as it isn’t a real control, but is actually a View; a very important difference.)

To complete the ListView’s sorting capabilities, we have to create a new ListView that encapsulates the sorting functionality. We’ll call it the SortableListView and it’ll inherit from ListView.

Here’s the code in all it’s glory.

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows.Controls;

using System.Windows;

using System.Collections;

using System.ComponentModel;

using System.Windows.Data;

using System.Windows.Media;

 

namespace SortableWPFGridView

{

 

    // if the GridView exposed any methods at all that allowed for overriding at a control level, I would be

    // able to do all of this work inside it rather than the ListView. However, b/c it doesn't, I have to do the

    // work inside the ListView.

 

    // The GridView has access to the ItemSource on the ListView through the dependency property mechanism.

 

    public class SortableListView : ListView

    {

        SortableGridViewColumn lastSortedOnColumn = null;

        ListSortDirection lastDirection = ListSortDirection.Ascending;

 

 

        #region New Dependency Properties

 

        public string ColumnHeaderSortedAscendingTemplate

        {

            get { return (string)GetValue(ColumnHeaderSortedAscendingTemplateProperty); }

            set { SetValue(ColumnHeaderSortedAscendingTemplateProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for ColumnHeaderSortedAscendingTemplate.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty ColumnHeaderSortedAscendingTemplateProperty =

            DependencyProperty.Register("ColumnHeaderSortedAscendingTemplate", typeof(string), typeof(SortableListView), new UIPropertyMetadata(""));

 

 

        public string ColumnHeaderSortedDescendingTemplate

        {

            get { return (string)GetValue(ColumnHeaderSortedDescendingTemplateProperty); }

            set { SetValue(ColumnHeaderSortedDescendingTemplateProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for ColumnHeaderSortedDescendingTemplate.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty ColumnHeaderSortedDescendingTemplateProperty =

            DependencyProperty.Register("ColumnHeaderSortedDescendingTemplate", typeof(string), typeof(SortableListView), new UIPropertyMetadata(""));

 

 

        public string ColumnHeaderNotSortedTemplate

        {

            get { return (string)GetValue(ColumnHeaderNotSortedTemplateProperty); }

            set { SetValue(ColumnHeaderNotSortedTemplateProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for ColumnHeaderNotSortedTemplate.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty ColumnHeaderNotSortedTemplateProperty =

            DependencyProperty.Register("ColumnHeaderNotSortedTemplate", typeof(string), typeof(SortableListView), new UIPropertyMetadata(""));

 

 

        #endregion

 

        ///

        /// Executes when the control is initialized completely the first time through. Runs only once.

        ///

        ///

        protected override void OnInitialized(EventArgs e)

        {

            // add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.

            this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));

 

            // cast the ListView's View to a GridView

            GridView gridView = this.View as GridView;

            if (gridView != null)

            {

                // determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.

                SortableGridViewColumn sortableGridViewColumn = null;

                foreach (GridViewColumn gridViewColumn in gridView.Columns)

                {

                    sortableGridViewColumn = gridViewColumn as SortableGridViewColumn;

                    if (sortableGridViewColumn != null)

                    {

                        if (sortableGridViewColumn.IsDefaultSortColumn)

                        {

                            break;

                        }

                        sortableGridViewColumn = null;

                    }

                }

 

                // if the default sort column is defined, sort the data and then update the templates as necessary.

                if (sortableGridViewColumn != null)

                {

                    lastSortedOnColumn = sortableGridViewColumn;

                    Sort(sortableGridViewColumn.SortPropertyName, ListSortDirection.Ascending);

 

                    if (!String.IsNullOrEmpty(this.ColumnHeaderSortedAscendingTemplate))

                    {

                        sortableGridViewColumn.HeaderTemplate = this.TryFindResource(ColumnHeaderSortedAscendingTemplate) as DataTemplate;

                    }

 

                    this.SelectedIndex = 0;

                }

            }

 

            base.OnInitialized(e);

        }

 

        ///

        /// Event Handler for the ColumnHeader Click Event.

        ///

        ///

        ///

        private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)

        {

            GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;

 

            // ensure that we clicked on the column header and not the padding that's added to fill the space.

            if (headerClicked != null && headerClicked.Role != GridViewColumnHeaderRole.Padding)

            {

                // attempt to cast to the sortableGridViewColumn object.

                SortableGridViewColumn sortableGridViewColumn = (headerClicked.Column) as SortableGridViewColumn;

 

                // ensure that the column header is the correct type and a sort property has been set.

                if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))

                {

 

                    ListSortDirection direction;

                    bool newSortColumn = false;

 

                    // determine if this is a new sort, or a switch in sort direction.

                    if (lastSortedOnColumn == null

                        || String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)

                        || !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))

                    {

                        newSortColumn = true;

                        direction = ListSortDirection.Ascending;

                    }

                    else

                    {

                        if (lastDirection == ListSortDirection.Ascending)

                        {

                            direction = ListSortDirection.Descending;

                        }

                        else

                        {

                            direction = ListSortDirection.Ascending;

                        }

                    }

 

                    // get the sort property name from the column's information.

                    string sortPropertyName = sortableGridViewColumn.SortPropertyName;

 

                    // Sort the data.

                    Sort(sortPropertyName, direction);

 

                    if (direction == ListSortDirection.Ascending)

                    {

                        if (!String.IsNullOrEmpty(this.ColumnHeaderSortedAscendingTemplate))

                        {

                            sortableGridViewColumn.HeaderTemplate = this.TryFindResource(ColumnHeaderSortedAscendingTemplate) as DataTemplate;

                        }

                        else

                        {

                            sortableGridViewColumn.HeaderTemplate = null;

                        }

                    }

                    else

                    {

                        if (!String.IsNullOrEmpty(this.ColumnHeaderSortedDescendingTemplate))

                        {

                            sortableGridViewColumn.HeaderTemplate = this.TryFindResource(ColumnHeaderSortedDescendingTemplate) as DataTemplate;

                        }

                        else

                        {

                            sortableGridViewColumn.HeaderTemplate = null;

                        }

                    }

 

                    // Remove arrow from previously sorted header

                    if (newSortColumn && lastSortedOnColumn != null)

                    {

                        if (!String.IsNullOrEmpty(this.ColumnHeaderNotSortedTemplate))

                        {

                            lastSortedOnColumn.HeaderTemplate = this.TryFindResource(ColumnHeaderNotSortedTemplate) as DataTemplate;

                        }

                        else

                        {

                            lastSortedOnColumn.HeaderTemplate = null;

                        }

                    }

                    lastSortedOnColumn = sortableGridViewColumn;

                }

            }

        }

 

        ///

        /// Helper method that sorts the data.

        ///

        ///

        ///

        private void Sort(string sortBy, ListSortDirection direction)

        {

            lastDirection = direction;

            ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ItemsSource);

 

            dataView.SortDescriptions.Clear();

            SortDescription sd = new SortDescription(sortBy, direction);

            dataView.SortDescriptions.Add(sd);

            dataView.Refresh();

        }

    }

}

 

It might seem like a lot, but it really isn’t.

The dependency properties at the top,

        #region New Dependency Properties

 

        public string ColumnHeaderSortedAscendingTemplate

        {

            get { return (string)GetValue(ColumnHeaderSortedAscendingTemplateProperty); }

            set { SetValue(ColumnHeaderSortedAscendingTemplateProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for ColumnHeaderSortedAscendingTemplate.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty ColumnHeaderSortedAscendingTemplateProperty =

            DependencyProperty.Register("ColumnHeaderSortedAscendingTemplate", typeof(string), typeof(SortableListView), new UIPropertyMetadata(""));

 

 

        public string ColumnHeaderSortedDescendingTemplate

        {

            get { return (string)GetValue(ColumnHeaderSortedDescendingTemplateProperty); }

            set { SetValue(ColumnHeaderSortedDescendingTemplateProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for ColumnHeaderSortedDescendingTemplate.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty ColumnHeaderSortedDescendingTemplateProperty =

            DependencyProperty.Register("ColumnHeaderSortedDescendingTemplate", typeof(string), typeof(SortableListView), new UIPropertyMetadata(""));

 

 

        public string ColumnHeaderNotSortedTemplate

        {

            get { return (string)GetValue(ColumnHeaderNotSortedTemplateProperty); }

            set { SetValue(ColumnHeaderNotSortedTemplateProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for ColumnHeaderNotSortedTemplate.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty ColumnHeaderNotSortedTemplateProperty =

            DependencyProperty.Register("ColumnHeaderNotSortedTemplate", typeof(string), typeof(SortableListView), new UIPropertyMetadata(""));

 

        #endregion

provide a mechanism to dynamically style the column header’s based upon if that column is sorted ascending, descending, or not sorted at all.

Once you get past that, the rest of the code is similar to the code listed in the above MSDN example.

A major differences is the inclusion of the overridden OnInitialized method.

        ///

        /// Executes when the control is initialized completely the first time through. Runs only once.

        ///

        ///

        protected override void OnInitialized(EventArgs e)

        {

            // add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.

            this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));

 

            // cast the ListView's View to a GridView

            GridView gridView = this.View as GridView;

            if (gridView != null)

            {

                // determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.

                SortableGridViewColumn sortableGridViewColumn = null;

                foreach (GridViewColumn gridViewColumn in gridView.Columns)

                {

                    sortableGridViewColumn = gridViewColumn as SortableGridViewColumn;

                    if (sortableGridViewColumn != null)

                    {

                        if (sortableGridViewColumn.IsDefaultSortColumn)

                        {

                            break;

                        }

                        sortableGridViewColumn = null;

                    }

                }

 

                // if the default sort column is defined, sort the data and then update the templates as necessary.

                if (sortableGridViewColumn != null)

                {

                    lastSortedOnColumn = sortableGridViewColumn;

                    Sort(sortableGridViewColumn.SortPropertyName, ListSortDirection.Ascending);

 

                    if (!String.IsNullOrEmpty(this.ColumnHeaderSortedAscendingTemplate))

                    {

                        sortableGridViewColumn.HeaderTemplate = this.TryFindResource(ColumnHeaderSortedAscendingTemplate) as DataTemplate;

                    }

 

                    this.SelectedIndex = 0;

                }

            }

 

            base.OnInitialized(e);

        }

In this method I wire up the GridView’s Click Event to a new RoutedEventHandler, GridViewColumnHeaderClickedHandler.

            // add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.

            this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));

 

This isn’t the best programming I’ve ever done, but it does the trick. At this point, I’ve tightly coupled the GridView and ListView together. Echh! Since the ListView might contain another View other than the GridView (although 3.0 only contains the GridView), this is fairly brittle code and well, should eventually be tossed in the garbage. But for now, it works because of the limitations of the GridView View.  

Next , I determine which column, if any, is marked as the DefaultSortColumn. It’s not a guarantee that they’ve marked just one column as the DefaultSortColumn or any at all so I just choose the first one I come across and sort the data based upon it’s SortPropertyName.

                // determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.

                SortableGridViewColumn sortableGridViewColumn = null;

                foreach (GridViewColumn gridViewColumn in gridView.Columns)

                {

                    sortableGridViewColumn = gridViewColumn as SortableGridViewColumn;

                    if (sortableGridViewColumn != null)

                    {

                        if (sortableGridViewColumn.IsDefaultSortColumn)

                        {

                            break;

                        }

                        sortableGridViewColumn = null;

                    }

                }

 

                // if the default sort column is defined, sort the data and then update the templates as necessary.

                if (sortableGridViewColumn != null)

                {

                    lastSortedOnColumn = sortableGridViewColumn;

                    Sort(sortableGridViewColumn.SortPropertyName, ListSortDirection.Ascending);

 

                    if (!String.IsNullOrEmpty(this.ColumnHeaderSortedAscendingTemplate))

                    {

                        sortableGridViewColumn.HeaderTemplate = this.TryFindResource(ColumnHeaderSortedAscendingTemplate) as DataTemplate;

                    }

 

                    this.SelectedIndex = 0;

                }

The event handler for the click event and the sort method are nearly identical to the ones found in the MSDN article so check there for an explanation.

Finally, I’m all set to wire up my XAML code to the new objects. I simple implementation of this might be the following:

<Window x:Class="SortableWPFGridView.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="SortableWPFGridView"

    xmlns:local="clr-namespace:SortableWPFGridView"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:SortableWPFGridView_Data="clr-namespace:SortableWPFGridView.Data"

    >

  <Window.Resources>

    <ObjectDataProvider x:Key="TestDataSource" d:IsDataSource="True" MethodName="GetTestData" ObjectType="{x:Type SortableWPFGridView_Data:TestDataLoader}"/>

    <Style x:Key="gridViewHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">

      <Setter Property="Background" Value="AliceBlue" />

    Style>

    <Style x:Key="headLinesGridViewStyle" TargetType="{x:Type ListViewItem}">

      <Setter Property="Foreground" Value="Magenta" />

      <Style.Triggers>

        <Trigger Property="IsMouseOver" Value="true">

          <Setter Property="Background" Value="Green">Setter>

        Trigger>

      Style.Triggers>

    Style>

    <DataTemplate x:Key="HeaderTemplateArrowUp">

      <DockPanel>

        <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>

        <Path x:Name="arrow"

           StrokeThickness = "1"

           Fill = "Gray"

           Data = "M 5,10 L 15,10 L 10,5 L 5,10"/>

      DockPanel>

    DataTemplate>

    <DataTemplate x:Key="HeaderTemplateArrowDown">

      <DockPanel>

        <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>

        <Path x:Name="arrow"

              StrokeThickness = "1"

              Fill = "Gray"

              Data = "M 5,5 L 10,10 L 15,5 L 5,5"/>

      DockPanel>

    DataTemplate>

    <DataTemplate x:Key="HeaderTemplateTransparent">

      <DockPanel>

        <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>

        <Path x:Name="arrow"

              StrokeThickness = "1"

              Fill = "Transparent"

              Data = "M 5,5 L 10,10 L 15,5 L 5,5"/>

      DockPanel>

    DataTemplate>

   

  Window.Resources>

  <Grid>

    <local:SortableListView  x:Name="headLinesGridView" IsSynchronizedWithCurrentItem="True"

                           ItemsSource="{Binding Source={StaticResource TestDataSource}}" ItemContainerStyle="{StaticResource headLinesGridViewStyle}"

                           ColumnHeaderSortedAscendingTemplate="HeaderTemplateArrowUp" ColumnHeaderSortedDescendingTemplate="HeaderTemplateArrowDown" ColumnHeaderNotSortedTemplate="HeaderTemplateTransparent">

      <ListView.View>

        <GridView ColumnHeaderContainerStyle="{StaticResource gridViewHeaderStyle}" ColumnHeaderTemplate="{StaticResource HeaderTemplateTransparent}">

          <local:SortableGridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" SortPropertyName="ID" Width="50" IsDefaultSortColumn="True" />

          <local:SortableGridViewColumn Header="First Name" DisplayMemberBinding="{Binding FirstName}" SortPropertyName="FirstName" />

          <local:SortableGridViewColumn Header="Last Name" DisplayMemberBinding="{Binding LastName}" SortPropertyName="LastName" />

          <local:SortableGridViewColumn Header="Date of Birth" DisplayMemberBinding="{Binding DateOfBirth}" SortPropertyName="DateOfBirth" />

        GridView>

      ListView.View>

    local:SortableListView>

  Grid>

Window>

 

I bolded out the code that contains the SortableListView information.

There’s some other stuff that comes along with this test implementation such as test data, and it’s included in the ZIP file, but I’m leaving it for an attempt at brevity.

File Attachment: SortableWPFGridView.zip (29 KB)

If all goes well, when you run the app, you should see something like this.

Screenshot

Happy coding.

posted @ 1:17 PM

Saturday, March 03, 2007

JS Intellisense in Orcas

It’s finally here!!

JavaScript intellisense is available in the March 2007 CTP of Orcas (the next Visual Studio). It works well with plain old JavaScript, but it really shines with ASP.NET AJAX.

Jeff King does a good job of documenting the new capabilities in this blog entry.

For us ASP.NET Server Control developers that utilize client capabilities, this is a huge boon for us. When was the last time JS intellisense was updated?? Anybody? I have no idea… I don’t think it’s changed since the first release of Visual Studio. Having to program ASP.NET AJAX client runtimes with intellisense … AWESOME!!!

Also, I mentioned an excellent article in my webcast that describes the prototype/closure model. I finally found it… here it is.

IMHO, it’s the single best article that describes closures available.

posted @ 5:43 PM

Thursday, March 01, 2007

ASP.NET AJAX Client Component Development WebCast

Hello Everyone,

Thanks so much for attending today’s webcast! We had 150+ attend and this is just a testament to how important and interesting a subject MS ASP.NET AJAX is.

As promised, here is the source code from today’s webcast. This is free source code, no limits on modifications, re-use, redeployment to clients, etc. I don’t even have my name in it yet.

I will add however, that the component that is supplied in this source code only works in certain situations. Try sticking a button you want to disable within an UpdatePanel and see what happens. It’ll work the first time, but in subsequent partial postbacks. There’s another, longer version of the source code that I’m working on that is more fully-baked that can be reused successfully across web applications and in different scenarios.

Again, thanks for joining me today on the webcast. I hope you enjoyed it and learned something interesting! Stay tuned for more webcasts regarding ASP.NET AJAX custom development. I think I’m going to put a couple of more in the pipeline.

Download Source

posted @ 12:08 PM

Wednesday, February 07, 2007

February SD .NET Developers Group Talk on ASP.NET AJAX Component Creation

Thanks to everyone that attended the meeting and heard my talk about ASP.NET AJAX Component Development. We had a great time hosting and I hope it sparked some interest in ASP.NET AJAX Component development and that you were able to learn something new.

Here are the slides and code from my presentation. I’m not attaching the postback disabler control at the moment (the last thing I demoed at the meeting) as it’s not ready to go yet and I think I’ll blog more in-depth about it in the next few weeks.

Happy coding, and thanks for attending!!

File Attachment: ASP.NET AJAX Component Development.ppt (745 KB)

File Attachment: DemoAJAXWebsite.zip (22 KB)

posted @ 4:01 PM

Tuesday, November 14, 2006

Web Development Helper Release

Nikhil has just released a new version of his irreplacable Web Development Helper tool. If you’ve been coding in ASP.NET AJAX (Atlas) or in another AJAX way that submits XMLHttp requests and haven't been using this tool you've been missing out on a great tool that really helps with debugging and troubleshooting asynchrounous requests.

He’s added a bunch of new features to check out including: JSON viewers, a new viewer for partial rendering scenarios (focusing on the ASP.NET AJAX UpdatePanel), and an installer that solves some of the earlier headaches and that is Vista compliant.

Check out his blog post about the release here. And download it from his project page here.

posted @ 11:23 AM

Friday, September 22, 2006

The UserContext Property of Atlas Web Service Alternative Syntax

Atlas enabled web services are fantastic. They provide a rich, wrapped capability that abstracts a lot of the nitty gritty xmlHttp details away from the programmer.

The normal pattern of using Atlas Web Services is a static method call that contains a callBack delegate. Once the method is complete, the callback function is automatically executed. For the most part, the code looks like the following:
    function startWebService() {
        AtlasWebService.Geography.GetCities (CountryId, onCallback, onTimeout);
    }
    
    function onCallBack (results) {
        // do something with the results.
    }
    
    function onTimeout (results) { 
        alert (“call Timed out”); 
    } 
    
Here I'm executing the GetCities web service method passing in the CountryId as the parameter and registering the onCallback method as the onMethodComplete handler and the onTimeout method as the onMethodTimeout handler.

For simple situations this works great, but in a project a while back we needed to pass some context from the caller (startWebService) to the callback (onCallBack). Using the version of the web service calling pattern that I've demonstrated above, this isn’t possible. A thought we had to solve this problem was to wrap the function calls inside of a closure and use private members of the closure to hold state. Something like this:
    myClass = function () {
        _currCountryId= '';
        this.startWebService = function (CountryId) {
            _currCountryId= CountryId
            AtlasWebService.Geography.GetCities(_currCountryId, onCallback, onTimeout);
        }
        
        function onCallback (results) {
            alert (_currCountryId);
            // handle the results as needed.
        }
    }

While this looks like it might work from initial glance (besides the race condition that might be present if more than one call to startWebService occurred before the first one completed), we found that _currCountryId wouldn’t be available to the onCallback function. Without jumping into too much JavaScript, the onCallback call is private and therefore unable to access the closure’s member fields (I think that’s the reason at least). Changing the onCallback method to “this.onCallback = function (results)” didn’t work either.

It turns out we were thinking too hard and not reading enough documentation. Atlas’ web service pattern provides a pass-through capability out of the box by using the alternative syntax and the userContext property. The Atlas documentation’s alternative syntax can be found here and the example reads:

    requestSimpleService = Quickstart.Samples.SimpleService.EchoString( 
        document.getElementById('inputName').value ,  // First webservice parameter     
        secondParameter,                             // Second webservice parameter 
        { 
	  onMethodComplete:OnComplete, 
	  onMethodTimeout:OnTimeout, 
	  onMethodError:OnError,
         onMethodAborted:OnAborted, 
	  userContext: "OnbuttonGo_click", 
	  timeoutInterval: 10000
	} 
    );

The blurb below it reads: “The userContext parameter can contain any value (string, number, array, dictionary, or object) and is used to pass contextual information that can be retrieved inside of handlers for errors or time-outs.”

In this example they are passing “OnbuttonGo_click” as the user context. They state the “contextual information that can be retrieved inside of handlers for errors or time-outs,” but it can also be used in the normal callback handler and anything that can be successfully serialized into JSON can be passed.

Let’s take a look at our example using the alternative syntax.

    function startWebService(CountryId) {
        AtlasWebService.Geography.GetCities (CountryId,
            {
                onMethodComplete: onCallBack,
                onMethodTimeout: onTimeout,
                userContext:CountryId
            }
        );
    }
    
    function onCallBack (results, response, userContext) {
        alert (userContext); // will popup the countryId of before.
    }
    
    function onTimeout (results, response, userContext) {
        alert (“call Timed out for: ” + userContext);
    }

Now, when the web service method returns, the onCallBack function will have both the results as well as the userContext I assigned to the method call in startWebService method.

A couple of things to note:

  1. I didn’t include the full alternative call syntax. Since it’s a named parameterized list of properties, it isn’t required that all of them be included.
  2. The onCallBack method signature now has 3 parameters. Results, response, and userContext.
By using the userContext property of the alternative syntax I can pass extra data from my caller to my callback. This is pretty darn helpful.

Happy Coding, Joel

posted @ 10:58 AM

Wednesday, August 02, 2006

Emitting JavaScript to run on Startup in an Atlas Environment - Part 2

Hello Again,

I’ve received a few blog comments and a few direct emails regarding using my method of Emitting JavaScript to run on Startup in an Atlas Environment when using a master page scenario. Truthfully, I hadn’t tested this before and as I attempted to test this case I was a bit worried it wouldn’t work. Also, I hadn’t tested it against the June CTP much less the just released July CTP. However, my fears were unfounded and without much modification to my test example, it worked. So, rather than respond to all the emails and comments individually, I thought that I would just share the code I’m using and we can go from there.

Here’s MasterPage.master

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %>

DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

<title>Untitled Pagetitle>

head>

<body>

<form id="form1" runat="server">

<atlas:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true" />

<div>

<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">

asp:ContentPlaceHolder>

div>

form>

body>

html>

Here’s Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default"

MasterPageFile="~/MasterPage.master" %>

<asp:Content ContentPlaceHolderID="ContentPlaceHolder1" runat="server">

<div>

<atlas:UpdatePanel ID="updatePanel1" RenderMode="Block" runat="server" Mode="Conditional">

<ContentTemplate>

<asp:Label ID="lbl1" runat="server" Text="This is my initial text">asp:Label>

<asp:Button ID="btn1" runat="server" Text="Press Me1" OnClick="btn1_Click" />

ContentTemplate>

atlas:UpdatePanel>

div>

asp:Content>

 Here’s Default.aspx.cs

using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using Microsoft.Web.UI;

public partial class _Default : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

String javaScriptFunction = @"

function myStartupFunction () {

// the id of the label changes depending upon the hierarchy of .NET controls. This id should be dynamically generated using “lbl1.ClientID.”

var myLabel = new Sys.UI.Label ($('ctl00_ContentPlaceHolder1_lbl1'));

myLabel.initialize();

alert (myLabel);

}";

Page.ClientScript.RegisterStartupScript(this.GetType(), "func", javaScriptFunction, true);

ScriptManager myScriptManager = ScriptManager.GetCurrent(Page);

if (myScriptManager.IsInPartialRenderingMode)

{

Page.ClientScript.RegisterStartupScript(this.GetType(), "func1", "myStartupFunction();", true);

}

else

{

Page.ClientScript.RegisterStartupScript(this.GetType(), "func2", "Sys.Application.load.add (myStartupFunction);", true);

}

}

protected void btn1_Click(object sender, EventArgs e)

{

this.lbl1.Text = "This is my post back text: " + ScriptManager.GetCurrent(Page).IsInPartialRenderingMode;

}

}

Sorry about the horrible spacing, but I don’t feel like dealing with BlogJet’s idiosyncrasies at the moment.

Happy coding, Joel

posted @ 10:29 AM

Monday, July 17, 2006

Navigating Shanghai as a Pedestrian

So I’m in Shanghai; surprise! This is not going to be a geek blog so if you’re expecting code or algorithms, check back next week when I’m back to my normal life.

I’ve been in Shanghai for about six days now and through all of the interesting experiences I’ve had navigating the city I’ve come to learn one very important lesson; pedestrians don’t win. They don’t win against bikes, they don’t win against mopeds, they don’t win against cars, they don’t win against other pedestrians. The little old lady on the bike who you accidently cut off will happily turn around and whisper sweet nothings in your direction as she rides away.

The actual rule of law in Shanghai regarding traffic is the following: cars give way to mopeds, mopeds give way to bikes, bikes give way to pedestrians. Actual walking around proves otherwise. The order of victory goes by size and speed. Cars, especially taxis, then mopeds, and finally bikes is the order of power on the streets. Traffic laws are generally disregarded. This includes red lights as they make right turns without slowing down and pedestrian cross walks as they make left turns across traffic. If you’re legally in an intersection as a pedestrian you need to be on high alert for cars coming from any direction. I emphasize cars because they can really put a dent in your knee, but I’m referring to any moving vehicle including bikes and mopeds. What’s really interesting is that the traffic signals actually encourage this behavior! In Mexico and other non-US countries, the lights flash green or yellow before they turn red and they do the same in Shanghai. What’s different is that before a light turns green, the yellow flashes a couple of times to get the drivers ready for the green. The blinking yellow is a “start your engines” command and if you hear this, get the hell out of the intersection. It’s like a drag race once that light turns green (and they don’t penalize for leaving the starting line early) and if you’re in the intersection, watch out.

In light of my experiences, I’ve come up with a few rules for crossing the street.

    1. Whenever possible, cross with a crowd. Drivers don’t seem to want to get multiple blood types on their cars at once. Just O or AB+, but no mixtures. If everybody in the crosswalk has the same blood type as you, you’re fair game.
    2. When entering an intersection, including pedestrian cross walks, look in the normal directions, left and right, and then crane your neck around behind you and to the left and gauge how fast that car is coming down the street that might be making a right turn from any lane of traffic. If you can make it to the 1/2 way point across the street, you might be okay.
    3. Cars will get as close to you as possible in the intersection without actually hitting you. Don’t be surprised by this and fight for your space… just don’t lose.
    4. Walk with authority. Enter the intersection like you’re supposed to be there and don’t hesitate to cross in front of a car. If you do hesitate, a good taxi cab will pounce on this and proceed to scrape your nose with his right front mirror as he crosses in front of you.

So that’s crossing the street. What’s interesting is that there are rules for walking on the sidewalk too. This is mainly because that although the sidewalks are normally for walking (hence the name), bikes, motorized bikes, and mopeds are just as likely to turn up behind you at a good clip. These too can hurt you if they hit you, but at least you are normally going in the same direction so some of the velocity is lost on impact.

Here is my rules for walking on the sidewalk:  Look, listen, and feel. (Nothing like a little first aid training to provide a summation.) Look behind you if you hear or feel anything coming up. Trust that sixth sense and move!

Shanghai is great, just don’t be a tourist pedestrian.

 

posted @ 3:06 AM

Saturday, July 01, 2006

Atlas - June CTP

Microsoft has released the June CTP of Atlas. You can download it here.

Nikhilk has posted a blog entry describing the core new feature of the CTP; Dynamic UpdatePanels. This is by far the biggest new feature of the CTP (and the last 6 months) and provides a completely new pattern of development for server controls and websites in general that want to take advantage of partial postbacks. I’m totally stoked about this new feature as I’m sure all other Atlas developers are, and want to thank Eilon and the rest of the Atlas team for listening to the early adopters cries and responding. I’m sure there are going to be pitfalls and areas where the new Dynamic UpdatePanel still doesn’t do exactly what I need it to do, but it’s a step in the right direction. I’ll play around with it in the next few weeks and post an entry or two sharing any insights I glean. I just wish that we weren’t so far along in our real Atlas project that taking advantage of it at this point is probably going to cost too much.

Other than the Dynamic UpdatePanels, one feature that I’m really going to like is the ability to see the generated JS code for a service proxy within the page that contains the service proxy. This has been a problem for us as we tried to debug the web services contained within the ScriptManager and I’m excited to have this new ability.

One thing I’m a little disappointed in is that there is no new documentation in the docs section of the Atlas site. Just from reading Nikhilk’s blog entry there seems to be a lot of new server side classes that we’ll need to understand in order to take advantage of the new features. I’m sure that some client side code has changed too. I’m not too sure if MSFT plans on updating the docs for this CTP, but it sure would be nice.

posted @ 12:24 PM

Friday, June 30, 2006

Free Patterns and Practices Guidance Explorer for ASP.NET and .NET

MSFT has provided a free patterns and practice guide explorer tool to navigate the vast expanses of the patterns knowledge base held at MSFT.

Scott Guthrie provides more information here.

posted @ 5:53 AM