Skip to content

Silverlight 2: MVVM, Databinding & Cascading ComboBoxes – Part 2

So I’ve finally got a fix for this issue. The solution does feel like a bit of a hack – but I can understand why I need to go down this path. Hopefully one of the people who read this post will be inspired to come up with a more elegant solution!

See a demo of ‘cascading ComboBoxes in Silverlight 2‘, and get the source here: CitiesVisited_step3.zip.

I finished the last post with two possible solutions. The first option was to bind the ‘cities’ ComboBox to a collection containing a union of all the possible cities – and toggling a visibility property as the country changes. I attempted to code this, but it had two strange side effects. The text part of the drop down, e.g. just where it has ‘Melbourne’, became unresponsive to mouse clicks. And the ComboBoxListItems with Visibility toggles to collapsed still appeared in the ComboBox empty and a few pixels tall.

The second option was two combine the previous/new lists of cities just at the time when Visit.City is being databound, then removed all previous cities. This approach I did get to work!

The PageViewModel gets two new properties BindCity and BindableCities bound to the cities ComboBox. BindCity is just a wrapper for _SelectedVisit.City, this exists so I can control when the UI is notified about the city change.

public City BindCity
{
    get { return _SelectedVisit.City; }
    set
    {
        _SelectedVisit.City = value;
        OnPropertyChanged("BindCity");
    }
}


public ObservableCollection<City> BindableCities
{
    get { return _BindableCities; }
}

<ComboBox 
    ItemsSource="{Binding BindableCities}" 
    SelectedItem="{Binding BindCity, Mode=TwoWay}" 
    DisplayMemberPath="Name"/>

The SelectedVisit setter is now doing a lot more. There are two scenarios where I need to update the BindableCities collection: when the SelectedVisit is changed (by clicking on a left-hand visit), and when the Country ComboBox is changed. When a new SelectedVisit is set by the databinding, the SwitchBindableCities method populates the BindableCities with the combination of cities. Here I’m also attaching to the PropertyChanged event of the SelectedVisit – to populate the BindableCities when the Country ComboBox changes.

public Visit SelectedVisit
{
    get { return _SelectedVisit; }
    set
    {
        if (_SelectedVisit != null)
        {
            _SelectedVisit.PropertyChanged -= new PropertyChangedEventHandler(_SelectedVisit_PropertyChanged);
        }
        _SelectedVisit = value;
        _SelectedVisit.PropertyChanged += new PropertyChangedEventHandler(_SelectedVisit_PropertyChanged);

        SwitchBindableCities(true);
        OnPropertyChanged("SelectedVisit");
    }
}

void _SelectedVisit_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    // the country property of the visit has changed
    // update the bindable cities
    if (e.PropertyName == "Country")
    {
        SwitchBindableCities(false);
    }
}

Where it all happens. The SwitchBindableCities method:

  1. First ‘if’: we’ve arrived here from someone changing a Country drop down, set the _SelectedVisit.City to null. E.g someone changing from Australia/Melbourne to United States. The City=Melbourne gets set to null.
  2. Second ‘if’: we’ve arrived here from someone toggling between Visits. If the BindableCities collection already contains what we want – it is save to allow the data binding to happen, then we can exit with the BindableCities untouched.
  3. The remainder of the method changes the list of cities. First it stores the list of cities presently in BindableCities. Adds the ‘new’ cities to the collection. Then raises the property changed on BindCity. The UI now does the binding when the BindableCities collection contains the UNION of cities. Now its safe to remove the original list of cities from BindableCities.


private void SwitchBindableCities(bool visitChanged)
{
    if (!visitChanged)
    {
        // changed the country, and the visit city isn't in that
        // country - so set the city to null
        _SelectedVisit.City = null;
    }

    if (visitChanged && _BindableCities.Contains(_SelectedVisit.City))
    {
        // the selected vist city IS in the selected country
        // and already in the bindable cities
        OnPropertyChanged("BindCity");
        return;
    }

    List<City> toBeRemoved = _BindableCities.ToList();
    foreach (City c in _SelectedVisit.Country.Cities)
    {
        _BindableCities.Add(c);
    }

    // tell the UI that the bind city has changed
    OnPropertyChanged("BindCity");

    foreach (City c in toBeRemoved)
    {
        _BindableCities.Remove(c);
    }
}

Silverlight 2: MVVM, Databinding & Cascading ComboBoxes – Part 1

Update: see follow up post to see how I got the cascading ComboBoxes to work: I did mange to get a ‘working’ solution for cascading ComboBoxes – see the solution here:
Silverlight 2: MVVM, Databinding & Cascading ComboBoxes – Part 2

I’m presently working on a Silverlight 2 application at work, and really enjoying the getting into the new technology. Hitting a few issues along the way that are going to make good blog articles when I find the time. This article intends to be the first in a series to demonstrate some of the issues I’m discovering.

Issue no. 1: Databinding Cascading ComboBoxes to a ViewModel.

If you’ve been following Silverlight 2 developer blogs you’ll notice Model-View-ViewModel (MVVM) is a popular separation pattern for UI. Luckily Craig of ConceptDev has already pulled together a good list of links for those wanting to read up on MVVM: Silverlight + Model-View-ViewModel (MVVM). My first introduction to MVVM was the Divelog app from Jonas Follesø. The Divelog app is a good introduction to several concepts: silverlight unit tests, dependency injection, command pattern and MVVM

The actual problem. Let’s say we are building an app of all the cities you’ve visited around the world. The Model classes involved: A Country class contains a collection of Cities, and a Visit class contains references to a Country and a City. The PageViewModel contains: Countries collection of all countries, Visits collection of all visit, and a SelectedVisit property.

The databinding seems simple…

…but clicking on the 2nd visit causes the city on the first option to disappear. See a demo of the issue: here.

Anything related to databinding issues is best understood by attaching some ‘do nothing’ Converter classes. Inside the Converter I’ve added debugging statements to try work out what order things are happening. The debugging output from the Converters looks like this:

The statements after the red line were created after the second option was clicked. This is how I interpreted the output after the red line:

  1. The ItemSelected property of the countries ComboBox is set to ‘United States’
  2. The ItemSource property of the cities ComboBox is populated with the cities belonging to the ‘United States’
  3. The interesting bit: ConvertBack is telling us that the city selected in the first option (previously Melbourne) is now null!

My theory: in step number 2 we are changing the contents of a ComboBox still bound to the ‘Melbourne’ visit. The databinding between the ComboBox and Visit.City fires, cannot find a city in the Visit.Country.Cities and interprets this as null.

The fix…. comes in my next blog post. I’ve experimented with two approaches for the issue. Approach 1: binding the city ComboBox to a different collection which is temporarily populated with the ALL the possible cities when the PageViewModel.SelectedVisit changes. Approach 2: again populate the city ComboBox will all possible cities – and use databinding to modify the city’s visibility.

Grab the source code for this demo: CitiesVisited_step1.zip

Google App Engine – Tetris Challenge

Here’s my attempt to learn Google App Engine / python: The Tetris code
challenge, you write a tetris playing algorithm, and the app
challenges your algorithm.

Anyone can write an auto-player algorithm in anything that can do cgi: php, asp.net,
jsp, etc. I’ve provided a sample client that can easily be hosted on
App Engine itself.

Check it out here: http://tetrisapp.appspot.com/ – it will default to
my sample algorithm.. You’ll need a gmail/google account to log in.

Read more on how to write your own algorithm here:
http://tetrisapp.appspot.com/static/howto.html

Its a bit rough at the moment.. If you struggle with any of the
details let me know – I really need to get the documentation a bit
more readable. I’ve got more features to add, depending on how
popular it is..

IIS, SSL and Host-Headers

Update (3-Aug-2010): Multiple SSL domains on a single IP are now possible using Unified Communications UC SSL Certificates (Subject Alternative Name) – see my follow up article: Subject Alternative Names for SSL

Here’s a knowledge base article I use to explain why an SSL site needs its own IP address: HTTP 1.1 host headers are not supported when you use SSL. Host headers allow a web server to host several websites on the same IP address. When a browser makes a request the domain name of the website is passed in the request host header. The server uses this to check against the list of websites it is serving. When this is combined with SSL the server needs to know which website’s private certificate to establish the secure session – which is impossible as the session is established before any HTTP headers are sent.

Yet the article says: “Beginning in Windows Server 2003 Service Pack 1 (SP1) and IIS 6.0, Secure Sockets Layer (SSL) host headers are supported in IIS.”, and points you to the article: Configuring SSL Host Headers (IIS 6.0). So we are all covered now? Well, no. The issue of SSL session being established before the host header still exists. IIS 6 only supports host headers where all the sites being served use the same wildcard SSL certificate; e.g. *.mydomain.com.au. The server doesn’t need to know the host header when establishing the session, as there is only one certificate to choose from.

Why not include the the host header when we establish SSL? That is the plan of Server Name Indication – this does mean web browsers and servers all need to be updated to support the new standard. So it will be a while before SNI is well supported on the web.