Silverlight 2.0 beta 1 ListBox Databinding and possible bug ?

March 12, 2008

This week I’ve started playing with the new and longly waited Silverlight 2.0 beta 1 libraries. Finally we can develop real world bussiness applications with Silverlight using all the UI controls that this beta release includes.

After doing a couple of HelloWorld samples, I decided to try the DataBinding feature. In theory, it is really simple to databind our controls to the bussiness classes by setting their DataContext property which is something I won’t cover in this post and you can follow this link if you are looking for a nice tutorial on this topic.

What I’ll cover here is a strange issue I found when trying to databind a ListBox control to a simple List<string> data source.

In my XAML code, I’ve simply added a ListBox control and set it’s DataContext property to the databinding I’ll be using:

<ListBox x:Name=”Items” ItemsSource=”{Binding Items, Mode=OneWay}” />

I also set the DataContext property of the list to the object I’ll be using to do the databinding at the codebehind of the control, there’s also a button that will call the AddItem function (see later) of my bussiness class:

public partial class Page : UserControl
{
    private Invoice _invoice = new Invoice();    

    public Page()
    {
        Items.DataContext = _invoice;
        myButton.Click += new RoutedEventHandler(myButton_Click);
    }

    void myButton_Click(object sender, RoutedEventArgs e)
    {
        _invoice.AddItem("this is a new Item!");
    }
}

And here is my business class which I’ve reduced to the list property to keep things simple:

public class Invoice : INotifyPropertyChanged
{
    private List _items = new List();

    public List Items
    {
        get { return _items; }
        set { _items = value; NotifyPropertyChanges("Items"); }
    }

    public void AddItem(string newItem)
    {
        _items.Add(newItem);
        NotifyPropertyChanges("Items");
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanges(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

The interesting part in the above code is the AddItem function. Since I wan’t to be able to add new items in the Items collection and automatically update the UI control responsible for displaying the list of items, I can’t simply call the Items.Add method because we must call NotifyPropertyChanges to get our databinding updated. We could also have created our custom list class that implemented INotifyPropertyChanged interface and took care of this (which is something I will surely do in my applications) but for this example is not necessary.What one would expect to happen with this code is that whenever the button click event is clicked, a new item is added in the “Items” list and the ListBox is updated. However, this is not working this way. What happens is that the item is added in the “Items” list but the ListBox control is not updated, the new item is not showing in the UI at all, futhermore, if we set up some breakpoints right after the AddItem method is called, we will see that the ItemsSource collection of the ListBox control is correctly updated and will contain a new item as it should, but the control is not updating it’s UI.

Finally, if I change the AddItem method of the bussiness class to the following implementation, everything works as expected and the list UI is correctly updated:

public void AddItem(string newItem)
{
    List temp = new List(_items);
    temp.Add(newItem);
    _items = new List(temp);
    NotifyPropertyChanges("ITems");
}

I haven’t still found an explanation of this strange behaviour and would really appreciate if somebody could point me out if there is something I have done wrong or if it’s some kind of bug in the beta 1 library. So any comment regarding this will be appreciated !Thanks for your time and come back again for more interesting topics on the highly expected Silverlight 2.0 release !

Advertisements

Ajax webpart: Register scripts on UpdatePanel postbacks

February 25, 2008

These days I’m developing custom ajax enabled webparts for a sharepoint solution. I’ve created a base webpart class named AjaxEnabledWebpart from which all the webparts inherit. This webpart has an updatepanel control where all the child webparts are supposed to create the controls they need so finally, everycontrol is rendered inside the updatepanel and the webpart has the expected asp.net ajax behaviour of partial rendering the pages. Some of the webparts have to render content created dinamically by javascript code as you will see later in the code, in this case, content is rendered this way because we are supposed to give an accessible front end in our webpart (this is supporting non-script enabled browsers) so we are using the typical <noscript><script> tag approach. However, the problem we faced is that the content rendering script code (or any script code at all) was not executed during Ajax postbacks of the webpart. The solution we found to this problem was to register the scripts with the Sys.WebForms.PageRequestManager.getInstance().add_endRequest() function. Following is the code we used. First, for reference only, I’ll post the main code of the AjaxEnabledWebpart which somebody might find useful

protected override void CreateChildControls()
{
    base.CreateChildControls();

    EnsurePanelFix();

    _updatePanel = new UpdatePanel();

    _scriptHandler = ScriptManager.GetCurrent(this.Page);
    if (_scriptHandler == null)
    {
        //no script manager registered
        _scriptHandler = new ScriptManager();
        _scriptHandler.ID = "scriptManager";
        this.Controls.Add(_scriptHandler);
    }

    _updatePanel.ID = "updatePanel_";
    _updatePanel.UpdateMode = UpdatePanelUpdateMode.Conditional;
    _updatePanel.ChildrenAsTriggers = true;

    CreatePanelControls(_updatePanel.ContentTemplateContainer.Controls);

    this.Controls.Add(_updatePanel);
}

protected virtual void CreatePanelControls(ControlCollection PanelControls)
{
}

The only relevant code from the AjaxEnabledWebpart is the CreateChildControls method, which creates the UpdatePanel control and calls the virtual method CreatePanelControls passing as a parameter the control collection where child pages are supposed to add the controls. Also, you can find the EnsurePanelFix implementation on my previous post at this linkAfter that, here is the code of a custom HtmlTable used in some webparts which contents are rendered using javascript code (if scripting is enabled in the client browser). The posted code is a function which returns the html code to be inserted in a cell’s InnerHtml property of a table:

private string _getSelectAllHtml(HtmlTableCell cell)
{
    string ret = "";

    ret += "" +
                "select all" +
             "" +
             "" +
                "// " +
            "" +
           "";

    string fn = "" +
                    "//<!&#91;CDATA&#91; n" +
                        "function _loadSelectAllHeader() { " +
                            "cab = '<a href="#" title="CellTitle">" +
                            "<img src="_layouts/img/selectall.gif" alt="select all">';" +
                            "var capa = document.getElementById("" + cell.Attributes&#91;"Id"&#93; + "");" +
                            "capa.innerHTML = cab;n" +
                        " }" +
                    "//&#93;&#93;> " +
                "";

    Page.ClientScript.RegisterClientScriptBlock(typeof(ResultsTableMod47),
                                "_loadSelectAllHeader",
                                fn);

    Page.ClientScript.RegisterStartupScript(typeof(ResultsTableMod47),
                                "initRequestScriptHandler_loadSelectAllHeader",
                                "Sys.WebForms.PageRequestManager.getInstance().add_endRequest(_loadSelectAllHeader);");

    return ret;
}

The relevant lines of the code are the calls to RegisterClientScriptBlock and RegisterStartupScript methods. The first one register our content-rendering script while the second one adds this function to the end_request handler of the PageRequestManager instance. This will ensure that the script is called after each Ajax postback of the pages.Further scripts can be registered in the end_request handler by calling the add_endRequest method for each script we want to be executed after a postback. However, we have to considered that this script will be executed for all the postbacks and not only for the ones generated by this webpart’s updatepanel control, so if more than one updatepanel is present on the page, we might have to include some checking at the startup of our code to ensure that our script must be executed. In this case this was not necessary since the script could be executed again without any problem.

That’s all for now ! I hope it’s useful…. coments are welcome 🙂


Ajax enabled webpart not working on second postback

February 20, 2008

Trying to develope an Ajax webpart with aspx.net 2.0 I came up with this msdn example  which seemed to be exactly what I was looking for. I followed the steps suggested by the article but I ended up with some weird behaviour from the webpart when finally deployed in my Sharepoint server.

Apparently everything was working as expected and when I clicked the webpart the first time, the ajax method was correctly executed and the “Hello! XXX” text succesfully displayed. However, clicking again the button didn’t seem to do anything, also, other page actions that should submit the form and do some action (like the “Edit Page” item from the “Site Actions” menu) stopped working.

I finally found that the problem was in the EnsurePanelFix function of the example which was not working as expected

private void EnsurePanelFix()
{
   if (this.Page.Form != null)
   {
     String fixupScript = @"
     _spBodyOnLoadFunctionNames.push(""_initFormActionAjax"");
     function _initFormActionAjax()
     {
       if (_spEscapedFormAction == document.forms[0].action)
       {
         document.forms[0]._initialAction =
         document.forms[0].action;
       }
     }
     var RestoreToOriginalFormActionCore =
       RestoreToOriginalFormAction;
     RestoreToOriginalFormAction = function()
     {
       if (_spOriginalFormAction != null)
       {
         RestoreToOriginalFormActionCore();
         document.forms[0]._initialAction =
         document.forms[0].action;
       }
     }";
   ScriptManager.RegisterStartupScript(this,
     typeof(SayHelloWebPart), "UpdatePanelFixup",
     fixupScript, true);
   }
}

So what I did is to provide my own implementation of that function. What we needed is to invalidate the onSubmit wrapper that Sharepoint calls in order to ensure some callback scenarios, this can be done by setting to true the _spSuppressFormOnSubmitWrapper variable so I replaced the original function by the following implementation

private void EnsurePanelFix()
        {
              ScriptManager.RegisterStartupScript
                (this,
                 typeof(AjaxEnabledWebpart),
                 "UpdatePanelFixup",
                 "_spOriginalFormAction = document.forms[0].action; _spSuppressFormOnSubmitWrapper=true;",
                 true);
}

And that’s it ! my Ajax webpart is working properly no matter how many times we click the Hello World ! button 🙂