OpenLightGroup Blog

rss

Blogs from OpenLightGroup.net


Simple Silverlight Configurator/Pivot (View Model/MVVM)

image

Live Example: http://silverlight.adefwebserver.com/simpleconfigurator/simpleconfiguratorweb/

The application allows your users to query a collection (over web services), using drop downs and sliders. The results appear in a animated display.

You may ask, why not just use Microsoft Pivot? The main reasons are:

  • This one uses View Model / MVVM so the user interface can be completely designed by a designer.
  • There is not a lot of code, you can adapt this for your own use quickly.
  • This does not focus on images. Microsoft Pivot is designed to display high quality images. However, you can adapt this to display images.
  • This is a smaller application and it loads faster.
  • This is designed to work with your data coming directly from a database.

If the above does not apply to you, you may wish to use Microsoft Pivot instead.

The Application

image

Basically you choose Gender, Weight, and Age, and the people who fall into the selected criteria are displays as boxes that are animated, and re-order themselves.

With help from Adam Kinney

Funny story when I was hanging out on the Microsoft campus. I ran into Adam Kinney (Former Microsoft Evangelist). I told him about a idea I was working on for a Silverlight configurator, and if he had any ideas on how to best display the data. He suggested using the Silverlight Wrap Panel with the FluidMoveBehavior. He started to describe it to me, and I started taking notes, then he felt sorry for me, and since he had his laptop, he whipped up some code and emailed it to me.

The boxes flying around, and fading in and out, is from his example. It is all done with no code, and everything can be easily altered using Microsoft Expression Blend.

Building The Application – The Web Application

image

First we create a Person class:

 

    public class Person
    {
        public string Name {get; set;}
        public string Description { get; set; }
        public string Gender { get; set; }
        public int Age { get; set; }
        public int Weight { get; set; }
    }

 

Then we we create a People collection:

 

    public static class People
    {
        public static List<Person> GetPeople()
        {
            List<Person> colPeople = new List<Person>();
            colPeople.Add(new Person { Name = "Tom", Description = "Cook", Age = 44, Weight = 224, Gender = "M" });
            colPeople.Add(new Person { Name = "Mary", Description = "Taxi Driver", Age = 22, Weight = 114, Gender = "F" });
            colPeople.Add(new Person { Name = "Jane", Description = "Nurse", Age = 28, Weight = 109, Gender = "F" });
            colPeople.Add(new Person { Name = "Lex", Description = "Doctor", Age = 51, Weight = 194, Gender = "M" });
            colPeople.Add(new Person { Name = "Paul", Description = "Saleman", Age = 23, Weight = 208, Gender = "M" });
            colPeople.Add(new Person { Name = "Frank", Description = "Butler", Age = 32, Weight = 184, Gender = "M" });
            colPeople.Add(new Person { Name = "Joe", Description = "Programmer", Age = 30, Weight = 290, Gender = "M" });
            colPeople.Add(new Person { Name = "John", Description = "Clerk", Age = 25, Weight = 202, Gender = "M" });
            colPeople.Add(new Person { Name = "Mark", Description = "Student", Age = 14, Weight = 102, Gender = "M" });
            colPeople.Add(new Person { Name = "Paula", Description = "Student", Age = 12, Weight = 98, Gender = "F" });
            colPeople.Add(new Person { Name = "Beth", Description = "Student", Age = 18, Weight = 119, Gender = "F" });
            colPeople.Add(new Person { Name = "Jack", Description = "Butcher", Age = 31, Weight = 114, Gender = "M" });
            colPeople.Add(new Person { Name = "Bob", Description = "Student", Age = 13, Weight = 114, Gender = "M" });
            colPeople.Add(new Person { Name = "Robert", Description = "Student", Age = 12, Weight = 99, Gender = "M" });
            colPeople.Add(new Person { Name = "Tony", Description = "Student", Age = 10, Weight = 105, Gender = "M" });
            colPeople.Add(new Person { Name = "Ray", Description = "Lawyer", Age = 35, Weight = 212, Gender = "M" });
            return colPeople;
        }       
    }

 

Next we create a web service that will query this collection:

 

    public class WebService : System.Web.Services.WebService
    {
        [WebMethod]
        public List<Person> SearchPeople(string Gender, int AgeHigh, int AgeLow, int WeightHigh, int WeightLow)
        {
            List<Person> colPeople = new List<Person>();
            var result = from objPeople in People.GetPeople().AsQueryable()
                         where objPeople.Age <= AgeHigh
                         where objPeople.Age >= AgeLow
                         where objPeople.Weight <= WeightHigh
                         where objPeople.Weight >= WeightLow
                         select objPeople;
            if (Gender != "All")
            {
                colPeople = (from finalresult in result
                             where finalresult.Gender == Gender
                             select finalresult).ToList();
            }
            else
            {
                colPeople = result.ToList();
            }
            return colPeople;
        }
    }

 

Note, this is where you can have your web service simply connect to your database.

That's it! Hopefully you find all this easy so far, and can see where you can easily alter this to pull in your own data.

Building The Application – The Silverlight Application

image

The Model, that calls the web service, is very simple:

 

    public class Model
    {
        #region SearchPeople
        public static void SearchPeople(string Gender, int AgeHigh, int AgeLow, int WeightHigh, 
            int WeightLow, EventHandler<SearchPeopleCompletedEventArgs> eh)
        {
            // Set up web service call
            WebServiceSoapClient WS = new WebServiceSoapClient();
            // Set the EndpointAddress
            WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());
            WS.SearchPeopleCompleted += eh;
            WS.SearchPeopleAsync(Gender, AgeHigh, AgeLow, WeightHigh, WeightLow);
        }
        #endregion
        // Utility
        #region GetBaseAddress
        private static Uri GetBaseAddress()
        {
            // Get the web address of the .xap that launched this application     
            string strBaseWebAddress = App.Current.Host.Source.AbsoluteUri;
            // Find the position of the ClientBin directory        
            int PositionOfClientBin =
                App.Current.Host.Source.AbsoluteUri.ToLower().IndexOf(@"/clientbin");
            // Strip off everything after the ClientBin directory         
            strBaseWebAddress = Strings.Left(strBaseWebAddress, PositionOfClientBin);
            // Create a URI
            Uri UriWebService = new Uri(String.Format(@"{0}/WebService.asmx", strBaseWebAddress));
            // Return the base address          
            return UriWebService;
        }
        #endregion
    }

 

image

The View Model, contains all the logic for the application. Hopefully, this demonstrates the power of View Model / MVVM style programming and how you can end up using less code than if you coded this using code behind.

First, we create a Property to hold the main collection. This collection is the only thing that Adam Kinney's code is looking at. All the rest of the code in the application is simply changing this collection:

 

        #region ColPeople
        private ObservableCollection<Person> _ColPeople = new ObservableCollection<Person>();
        public ObservableCollection<Person> ColPeople
        {
            get { return _ColPeople; }
            private set
            {
                if (ColPeople == value)
                {
                    return;
                }
                _ColPeople = value;
                this.NotifyPropertyChanged("ColPeople");
            }
        }
        #endregion

 

Next, we have Properties to hold the other parameters. These parameters are what the Sliders and the ComboBox are bound to:

 

        #region AgeLow
        private int _AgeLow = 10;
        public int AgeLow
        {
            get { return _AgeLow; }
            set
            {
                if (AgeLow == value)
                {
                    return;
                }
                _AgeLow = value;
                this.NotifyPropertyChanged("AgeLow");
            }
        }
        #endregion
        #region AgeHigh
        private int _AgeHigh = 100;
        public int AgeHigh
        {
            get { return _AgeHigh; }
            set
            {
                if (AgeHigh == value)
                {
                    return;
                }
                _AgeHigh = value;
                this.NotifyPropertyChanged("AgeHigh");
            }
        }
        #endregion
        #region WeightLow
        private int _WeightLow = 90;
        public int WeightLow
        {
            get { return _WeightLow; }
            set
            {
                if (WeightLow == value)
                {
                    return;
                }
                _WeightLow = value;
                this.NotifyPropertyChanged("WeightLow");
            }
        }
        #endregion
        #region WeightHigh
        private int _WeightHigh = 300;
        public int WeightHigh
        {
            get { return _WeightHigh; }
            set
            {
                if (WeightHigh == value)
                {
                    return;
                }
                _WeightHigh = value;
                this.NotifyPropertyChanged("WeightHigh");
            }
        }
        #endregion
        #region Gender
        private string _Gender = "All";
        public string Gender
        {
            get { return _Gender; }
            set
            {
                if (Gender == value)
                {
                    return;
                }
                _Gender = value;
                this.NotifyPropertyChanged("Gender");
            }
        }
        #endregion

 

Now, for the complex part (it is not really complex, that is my point). This is the method that calls the Model. Note, that it does not take parameters, it simply passes to the Model, whatever the values are set for the various properties:

 

        #region GetPeopleFromModel
        private void GetPeopleFromModel()
        {
            // Call the Model to get People
            Model.SearchPeople(Gender, AgeHigh, AgeLow, WeightHigh, WeightLow, (Param, EventArgs) =>
            {
                if (EventArgs.Error == null)
                {
                    // Remove people who are not part of the recent query
                    var DeletedPeople = (from people in ColPeople
                                         where !EventArgs.Result.Select(x => x.Name).Contains(people.Name)
                                         select people).ToList();
                    // loop thru each item
                    foreach (var Person in DeletedPeople)
                    {
                        // Remove from the collection
                        ColPeople.Remove(Person);
                    }
                    // Add people that are not part of ColPeople
                    var NewPeople = from people in EventArgs.Result
                                    where !ColPeople.Select(x => x.Name).Contains(people.Name)
                                    select people;
                    // loop thru each item
                    foreach (var Person in NewPeople)
                    {
                        // Add to the collection
                        ColPeople.Add(Person);
                    }
                }
            });
        }
        #endregion

 

This class implements INotifyPropertyChanged, so we use this method to call the GetPeopleFromModel() method whenever one of the properties changes:

 

        #region MainPageModel_PropertyChanged
        void MainPageModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (
                e.PropertyName == "AgeLow" ||
                e.PropertyName == "AgeHigh" ||
                e.PropertyName == "WeightLow" ||
                e.PropertyName == "WeightHigh" ||
                e.PropertyName == "Gender")
            {
                GetPeopleFromModel();
            }
        }
        #endregion

 

That's it!

If you are not used to View Model Style programming, you may be wondering “how do the properties for things like Age and Weight get set?”. The answer is, that we will bind controls in the View to these properties. All of that is done in Microsoft Expression Blend, using no code at all.

 

image

 

The View looks like this in Expression Blend:

 

image

 

This diagram shows what is bound to what:

 

image

 

Note, that the Range Sliders are from and article posted by Josh Twist.

 

Further Reading

Adam Kinney suggested I pass these URL’s on if you want to know how he did the animated list:

 

Download

You can download full source code here:

http://silverlight.adefwebserver.com/simpleconfigurator/simpleconfigurator.zip





Comments are closed.
Showing 8 Comments
Avatar  Christophe 7 years ago

@Michael: finally, after some additionnal testing, my problem seems to be due to the ContextMenu that I apply on each element. This creates memory leaks. I've tried to patch my toolkit version with the fix on codeplex, but no success for the moment.&lt;br&gt;VirtualizingStackPanel is just for better performance, but is not mandatory. So I have to find a way to correct the contextmenu problem.&lt;br&gt;

Avatar  Christophe 7 years ago

It seems that the problem is related on the fact that the WrapPanel doesn't use any UI virtualization technique. (as the standard ItemsPanel of a listbox is a VirtualizingStackPanel normally, and the wrappanel doesn't) &lt;br&gt;

Avatar  Christophe 7 years ago

@Christophe - All I can suggest is that you can still use paging. This is what you would use with any other list of data :)

Avatar  Christophe 7 years ago

@Michael: I found the reason of the OrderBy() problem.&lt;br&gt;It seems that silverlight doesn't provide anything that says that an element has moved in an observable collection. In details, there's no &quot;Move&quot; Action available in NotifyCollectionChangedAction thrown by the silverlight observablecollection, whereas in WPF it has !!&lt;br&gt;&lt;br&gt;Now I have another problem. When I use this nice list, when I have a some real data, let's say 200 items, memory grows and the system becomes slow. I have done some basic templating (changed item layout and added some little flag on each item, and also added contextual menu using the toolkit stuff, and that's it!). I really don't know what is going on, and even worse, I don't know how I can debug that.&lt;br&gt;Any ideas?&lt;br&gt;Thanks !

Avatar  Michael Washington 7 years ago

@Christophe - I don't have the answer to that one. Let us know what you find out.

Avatar  Christophe 7 years ago

Thanks Michael !! Very nice article. This is really usefull! &lt;br&gt;I tried it out, and it is fine. I only have one question: I would like to also use a reordering option. But in this case I don't know how to proceed, because I don't know what triggers the fluid animation. If I simply set the &quot;list = oldlist.OrderBy()&quot;, I don't get the animation...&lt;br&gt;Do you have an idea?

Avatar  Michael Washington 7 years ago

@Rob - Thanks for the feedback!

Avatar  Rob 7 years ago

Thanks Michael! I've been wanting to do something very similar for a while, but never seem to find the time. This will be very helpful. I also like the aesthetics of the article where you highlight which part of MVVM the code is associated with. It makes it an easy quick reference.&lt;br&gt;&lt;br&gt;Rob