Silverlight, UI Design, XAML

Bind a list of items to an accordion control with grouping

My model looks like this:


public sealed class ReportItem
{

public string Name { get; set; }
public string Description { get; set; }
public string GroupName { get; set; }

}

I want to display these reports in an accordion control, where the items are grouped by the
GroupName property. The group name would be the accordion header buttons, and under each
accordion group would appear the items containing that group name.

You’ll need a CollectionViewSource:


<navigation.Page ... xmlns:data="clr-namespace:System.Windows.Data;assembly=System.Windows"

 

	<navigation:Page.Resources>
		<data:CollectionViewSource x:Key="ReportsViewSource" Source="{Binding Reports}">
			<data:CollectionViewSource.GroupDescriptions>
				<PropertyGroupDescription PropertyName="GroupName" />
			</data:CollectionViewSource.GroupDescriptions>
		</data:CollectionViewSource>
	</navigation:Page.Resources>

I’m using a model object so my report collection is a property called Reports.

The GroupName is the property I am grouping by.

		<toolkit:Accordion ItemsSource="{Binding BindsDirectlyToSource=True, Source={StaticResource ReportsViewSource}, Path=View.Groups}" Grid.Row="1" HorizontalAlignment="Left" Name="accordion1" Width="315" VerticalAlignment="Stretch">
			<toolkit:Accordion.ContentTemplate>
				<DataTemplate>
					<ListBox ItemsSource="{Binding Items}" BorderThickness="0"><!-- this is the Group.Items -->
						<ListBox.ItemTemplate>
							<DataTemplate>
								<TextBlock FontWeight="Bold" Text="{Binding Name}" /><!-- this is the Report.Name -->
							</DataTemplate>
						</ListBox.ItemTemplate>
					</ListBox>
				</DataTemplate>
			</toolkit:Accordion.ContentTemplate>
			<toolkit:Accordion.ItemTemplate>
				<DataTemplate>
					<TextBlock Text="{Binding Name}" /><!-- this is the Group.Name -->
				</DataTemplate>
			</toolkit:Accordion.ItemTemplate>
		</toolkit:Accordion>


ReportsViewSource is my CollectionViewSource. The property View contains some objects, like Items for the items without grouping.
The property Groups contains a collection of Groups, each with a Name and Items property. The accordion’s ItemTemplate defines the
headers, while the ContentTemplate defines the content to go into each accordion. Here I am using a ListBox to display the subitems.

C#, Silverlight, XAML

Binding to Page Title in Silverlight

In SL4, the title of a page in a Silverlight navigation project is not implemented as a depenency property. You can’t bind the title of a page from a view model directly to it. While this article was a great starting point, the first 2 examples did not work for me.

I implemented a dependency property to handle it. Gets to the property may not work well, but its purpose is to set the page title, so that should not matter.

///

/// This allows a page title in a Silverlight Navigation application to be bound to.
/// Silverlight page titles are not DPs so they cannot be bound by normal means.
/// This replaces the event used in codebehind called OnDataLoaded, whose only
/// purpose was to set the page title.
/// Taken & Modified from http://forums.silverlight.net/forums/p/97849/223734.aspx
///

public class PageTitleBindingHelper : FrameworkElement //, INotifyPropertyChanged
{

public string PageTitle
{
get { return (string)GetValue(PageTitleProperty); }
set { SetValue(PageTitleProperty, value); }
}

// Using a DependencyProperty as the backing store for PageTitleProperty. This enables animation, styling, binding, etc...
public static DependencyProperty PageTitleProperty;

public PageTitleBindingHelper()
{
PageTitleProperty = DependencyProperty.Register("PageTitleProperty", typeof(string), typeof(PageTitleBindingHelper), new PropertyMetadata("", new PropertyChangedCallback(PageTitleChanged)));
}

private void PageTitleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
HtmlPage.Document.SetProperty("title", e.NewValue);

Page p = App.Window.ContentFrame.Content as Page;
if (p != null)
p.Title = (string)e.NewValue;

//NotifyPropertyChanged("PageTitle");
}
}

To use: Place an instance in your page resources and bind the PageTitle property to whatever property is on your view model.

EDIT: HtmlPage seemed to crash randomly for other users of my app, so I’ve changed this. I have a reference to my main window available from the App class, where I was using it to hide parts of the UI for a kiosk, etc. I use that and figure out what page I am on, and set the title that way. It may not be the cleanest solution, but I will revisit this once I have some spare time.

C#, Silverlight

Silverlight and Asynchronous Calls

LOL, people still whine about having to do everything asynchronously in Silverlight. When I dove into Silverlight head-first around September, I didn’t know much about asynchronous calls, besides the usual BeginInvoke() calls for long running threads. Needing to use async calls for all data transfers was a bit daunting. For a long time, I tried making all calls synchronous using the dispatcher, wait loops… most anything I could think of. However, I have found that learning async patterns has made me a better programmer, and I would use the async calling model even if I was in desktop WPF/WinForms and had the sync model available to me.

The project I am working on for Morris Consulting (I’m on an NDA, so I can’t go into details) has a list of people represented by buttons whose states can be turned on and off. When I first started to work on the project, I was using all “faked” synchronous calls to get the data. I noticed that the SL app would stall for three seconds while loading all 1000+ people into a custom usercontrol I made. Three seconds is bad, but it’s not horrible, I told myself. But, what if the list was composed of 50,000 people? I would be looking at a pause of two and a half minutes, which is very unacceptable!

Firstly, use the MVVM pattern. Setting up a viewmodel to handle the workload, rather than cowboy-coding it into the UI, is super-beneficial in the long run. It’s testable, and you can use the viewmodel in SL, WPF, WinForms, MVC, or whatever UI you want. You could even use a string tokenizer to take input and make a console app that runs your program. You only have to write your business rules one time, and your different UIs will consume your view model. Mind you, writing a clean, well thought out UI only seems easy, but in some aspects can be harder than writing the business logic! You don’t want to repeat yourself at all, so it’s important you separate your business logic from the UI for any medium to large application.

Your viewmodel should not rely on the UI at all, so no dependency properties should be on it. Use regular properties and INotifyPropertyChanged, and then any XAML binding (or WinForms binding, shudder) won’t have any issues. DO make a base ViewModel class that is used by your view models. DO make a view model that is used by a single view. I’ve used a single view model that is used by a couple views, but I make a copy of it once one of the views needs major changes that could break the first view. Your view models should implement INotifyPropertyChanged for binding notifications, IDisposable if you have resources that need to be released when you dispose of the view model (also allowing you to use the “using” statement), IDataErrorInfo optionally if you have extra data in the view model that needs to be validated.

If you’re using RIA services, you can mark the view models as .shared.cs so they are shared by the client also! I’ll write more on this soon.

C#, Generics, Reflection, Silverlight, Web Development

Map query string parameters to properties of an object, easily`

So, how many people have done this?


URL: http://.../something.aspx?PersonID=4&ChildID=16

void () {
// Assuming o = some object with properties PersonID and ChildID
PersonWithChild o = new PersonWithChild();
string _personID = Request.QueryString["PersonID"];
if (int.TryParse(_personID, out personID) {
o.PersonID = personID;
}
string _childID = Request.QueryString["ChildID"];
if (int.TryParse(_childID, out childID) {
o.ChildID = childID;
}
...
}

What a pain! This kind of plumbing is unnecessary, although frameworks like MVC have this taken care of.
Silverlight uses the concept of querystrings to pass data between pages of a navigation business application, so the code below can be used with Silverlight in addition to whatever .Net-based web framework of your choosing.

(I know code is cut off, copy and paste it and it will be fine)

Non-SL:

void init() { // This is where you initialization code may be
PersonWithChild o = new PersonWithChild();
MapQueryString(Request.QueryString, ref o, false);
// Properties are now set
}

Silverlight:

void init() { // This is where you initialization code may be
PersonWithChild o = new PersonWithChild();
// Silverlight querystring object is an IDictionary so use extension method ToQueryString()
MapQueryString(NavigationContext.QueryString.ToQueryString(), ref o, false);
// Properties are now set
}

Here are the functions: Put them into a static class in your project.

///

/// Transforms a dictionary of key/value pairs strongly typed as strings
/// into a querystring that can be parsed by MapQueryString of T.
///

///
///
public static string ToQueryString(this IDictionary kvps) {
string query = "";
foreach (var key in kvps) {
query += string.Format("{0}={1}&", key.Key, key.Value);
}
return query.TrimEnd('&');
}

///

Takes a query string and maps each key value pair to public properties of an object of type TEntity.
///

/// The class of the type of object.
/// A string containing key/value pairs.
/// The object whose properties will be populated.
/// If true, the property names and keys from the key-value pairs must match exactly.
/// An array of strings that separates each key-value pair. Passing null will default the list to {&, &}.
/// The string that separates each key and value in a key-value pair. By default this is "=".
/// If multiple parameters with the same key are specified, they are concatenated with this string, by default ",".
///
/// Since a query string can have multiple keys with the same name, a Dictionary generic was not used.
///
public static void MapQueryString(string queryString, ref TEntity o, bool caseSensitive = false, string[] separatorStrings = null, string keyValueSeparator = "=", string multipleValueJoinSeparator = ",") {
if (separatorStrings == null) { separatorStrings = new string[] { "&", "&" }; }
List kvps = GetKeyValuePairs(queryString, separatorStrings, keyValueSeparator);
Type type = o.GetType();
var properties = type.GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties) {
List kv;
// Get all matching values
if (caseSensitive) {
kv = kvps.Where(p => p.Key == property.Name).ToList();
} else {
kv = kvps.Where(p => p.Key.ToLower() == property.Name.ToLower()).ToList();
}
if (kv.Count > 0) {
// Concatenate the values
string value = "";
foreach (var kvp in kv) {
if (value.Length > 0) { value += multipleValueJoinSeparator; }
value += kvp.Value;
}
object target = null;
Type ptype = property.PropertyType;
if (ptype.FullName.Contains("Nullable")) {
ptype = ptype.GetProperty("Value").PropertyType;
}
if (ptype != typeof(string)) {
try {
target = ptype.InvokeMember("Parse", BindingFlags.InvokeMethod, null, target, new object[] { value });
property.SetValue(o, target, null);
} catch (Exception ex) { }
} else {
property.SetValue(o, value, null);
}
}
}
//return o;
}

///

Turns a query string into a map of key-value pairs.
///

///
///
///
///
public static List GetKeyValuePairs(string queryString, string[] separatorStrings, string keyValueSeparator) {
List output = new List();
string[] kvps = queryString.Split(separatorStrings, StringSplitOptions.RemoveEmptyEntries);
foreach (string kvp in kvps) {
string[] t = kvp.Split(new string[] { keyValueSeparator }, StringSplitOptions.RemoveEmptyEntries);
NonUniqueKeyValuePairStruct op = new NonUniqueKeyValuePairStruct();
if (t.Length == 1) {
op.Key = null;
op.Value = t[0];
} else {
op.Key = t[0];
op.Value = t[1];
}
output.Add(op);
}
return output;
}

///

Used to hold non-unique key-value pairs.
///

public struct NonUniqueKeyValuePairStruct
{
public string Key;
public string Value;
}

C#, EF Entity Framework, Generics, RIA Services, Silverlight

Executing RIA Service Calls One-At-A-Time

Without going into details (because I’m not allowed to), I’m working on a RIA services app where one page has a large data-bound collection of buttons. Each button toggles a state, on or off. When the button is clicked, a RIA service call is performed to modify the state of the bound data, which sets the button state to Busy, setting a BusyIndicator to busy. When the callback returns, the busy state is turned off, and the details on the button are updated to reflect the change in data that I requested. While the button is “busy”, you’re not able to click it again until the callback is executed, which then re-enables the button. This list of buttons is in a wrap panel, and in my scenario, it represents a list of 1000+ objects whose state can be toggled on or off.

Everything was working very well, except if you clicked more than one button without waiting for the previous button’s callback to execute. On my local network, the callback time is very small (well under one second), but I could easily click a bunch of buttons really quickly, which would in turn cause RIA services to try to execute more than one call to update data at a time, which is not allowed.

InvalidOperationException: A SubmitChanges operation is already in progress on this DomainContext.

That throws a wrench in my gears! My options were:

  1. Add a BusyIndicator to the wrap panel, and setting it to be busy after each click;
  2. Try to get the calls to execute one-at-a-time by queuing them.

The first option was considered, but the problem there is that no other button could be toggled while the RIA service was running. Speed in clicking buttons in this list is key, and having the whole wrap panel be busy and not busy would make it flash a lot, and it’s just not as clean as having the RIA service calls execute in a queue.

My ExecuteOneAtATimeQueue class takes care of queuing RIA service calls, or any other asynchronous calls that can only run one-at-a-time such as WebClient.

Class code:

 

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;

namespace Derreck Dean
{


 // item being modified
 // item in queue
 // function to execute

 // 1. Object is added to the queue...
 //    a. Item (userstate) being modified
 //    b. Function defining how to load image

 internal class ExecuteOneAtATimeQueue : IDisposable, INotifyPropertyChanged
 {
 public ObservableCollection<ExecuteOneAtATimeQueueItem> Queue = null;

 public event EventHandler QueueItemWorkStarted;
 public event EventHandler QueueItemWorkCompleted;
 public event EventHandler AllQueueItemsCompleted;

 private NotifyCollectionChangedEventHandler ncceh;
 private bool canProcessQueue = false;

 public ExecuteOneAtATimeQueueItem CurrentItem {
 get {
 return _currentItem;
 }
 internal set {
 _currentItem = value;
 OnPropertyChanged("CurrentItem");
 }
 }
 private ExecuteOneAtATimeQueueItem _currentItem;

 /// <summary>
 /// Init
 /// </summary>
 public ExecuteOneAtATimeQueue() {
 ncceh = new System.Collections.Specialized
 .NotifyCollectionChangedEventHandler(Queue_CollectionChanged);
 Queue = new ObservableCollection<ExecuteOneAtATimeQueueItem>();
 Queue.CollectionChanged += ncceh;
 }

 public void BeginProcessingQueue() {
 canProcessQueue = true;
 if (Queue.Count > 0)
 processQueue();
 }

 /// <summary>
 /// Adds an item to the queue.
 /// </summary>
 /// <param name="queueItem"></param>
 public void Add(ExecuteOneAtATimeQueueItem queueItem) {
 queueItem.WorkStarted += new EventHandler(queueItem_WorkStarted);
 queueItem.WorkCompleted += new EventHandler(queueItem_WorkCompleted);
 queueItem.PropertyChanged += 
 new PropertyChangedEventHandler(queueItem_PropertyChanged);
 lock (Queue) {
 Queue.Add(queueItem);
 }
 }

 /// <summary>
 /// Called when the item in the collectiom changes
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 void queueItem_PropertyChanged(object sender, PropertyChangedEventArgs e) {
 processQueue();
 }

 /// <summary>
 /// Adds work to the queue.
 /// </summary>
 /// <param name="itemToModify">Item being modified and returned.</param>
 /// <param name="userState">Optional extra data to be passed in; can be null.</param>
 /// <param name="method">Void function taking in queue item, 
 /// itemToModify and userState.</param>
 public void Add(object itemToModify, object userState, 
 Action<ExecuteOneAtATimeQueueItem, object, object> method) 
 {
 ExecuteOneAtATimeQueueItem qi = new ExecuteOneAtATimeQueueItem(itemToModify, userState);
 qi.DoWork = method;
 Add(qi);
 }

 internal void queueItem_WorkCompleted(object sender, EventArgs e) {
 if (QueueItemWorkCompleted != null) { QueueItemWorkCompleted(sender, e); }
 //processQueue(); // Continue processing queue
 }

 internal void queueItem_WorkStarted(object sender, EventArgs e) {
 if (QueueItemWorkStarted != null) { QueueItemWorkStarted(sender, e); }
 }

 /// <summary>
 /// Process the contents of the queue, if we are able.
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void Queue_CollectionChanged(object sender, 
 System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
 {
 processQueue();
 }

 /// <summary>
 /// Processes the queue if we're allowed, and starts work if we are able to.
 /// </summary>
 internal void processQueue() {
 if (canProcessQueue) {
 // See if there are any busy items in the queue. 
 // If there is not, then pop one off the stack and start executing it.
 // If there are no more items in the queue, we're finished.
 bool areAnyBusy = Queue.Any(qq => qq.IsBusy == true && qq.IsCompleted == false);
 var workToDo = Queue
 .Where(qq => qq.IsBusy == false && qq.IsCompleted == false).ToList();
 if (!areAnyBusy && workToDo.Count > 0) {
 var q = workToDo.First();
 q.StartWork();
 // Done in Item object: if (QueueItemWorkStarted != null) { 
 QueueItemWorkStarted(q, null); 
 }
 } else {
 // See if everything has been completed
 if (Queue.All(qq => qq.IsCompleted == true)) {
 if (AllQueueItemsCompleted != null) { 
 AllQueueItemsCompleted(this, new EventArgs()); 
 }
 }
 }
 }
 }

 /// <summary>
 /// Queue item for work to complete.
 /// </summary>
 internal class ExecuteOneAtATimeQueueItem : INotifyPropertyChanged
 {

 /// <summary>
 /// Time work was started.
 /// </summary>
 public DateTime? StartedWork {
 get { return _startedWork; }
 internal set {
 _startedWork = value;
 OnPropertyChanged("StartedWork");
 OnPropertyChanged("IsBusy");
 OnPropertyChanged("IsCompleted");
 }
 }
 private DateTime? _startedWork = null;

 /// <summary>
 /// Time work was completed, with or without errors.
 /// </summary>
 public DateTime? EndedWork {
 get { return _endedWork; }
 internal set {
 _endedWork = value;
 OnPropertyChanged("EndedWork");
 OnPropertyChanged("IsBusy");
 OnPropertyChanged("IsCompleted");
 }
 }
 private DateTime? _endedWork = null;

 /// <summary>
 /// The item that is being worked on
 /// </summary>
 public object ItemToModify { get; set; }

 /// <summary>
 /// Another object passed to DoWork that can be modified/used
 /// </summary>
 public object UserState { get; set; }

 /// <summary>
 /// Describes if the current item is busy or not.
 /// We know this if startedWork is null or if endedWork is not null.
 /// </summary>
 public bool IsBusy {
 // IsBusy: Work has started but not completed
 get { return (_startedWork != null && _endedWork == null); }
 }

 /// <summary>
 /// Returns true if the work for this queue item has been completed.
 /// </summary>
 public bool IsCompleted {
 get { return (_startedWork != null && _endedWork != null); }
 }

 // Describes if there was a problem running the function or not.
 public bool IsError {
 get { return _isError; }
 set { _isError = value; OnPropertyChanged("IsError"); }
 }
 private bool _isError = false;

 ////////////////////////////////////////////////////////////////////////////////////////

 internal bool _hasWorkCompletedEventBeenRaised = false;

 internal event EventHandler WorkCompleted;
 internal event EventHandler WorkStarted;

 ////////////////////////////////////////////////////////////////////////////////////////

 public ExecuteOneAtATimeQueueItem() { }
 public ExecuteOneAtATimeQueueItem(object itemToModify) {
 this.ItemToModify = itemToModify;
 }
 public ExecuteOneAtATimeQueueItem(object itemToModify, object userState) {
 this.ItemToModify = itemToModify;
 this.UserState = userState;
 }

 /// <summary>
 /// Function that is called to begin work on an item.
 /// The ExecuteOneAtATimeQueueItem.EndWork() method must be called at the 
 /// end to mark the queue item as completed.
 /// The first object is the object being modified.
 /// The second object is extra stuff that can be passed in to aid in the work being run.
 /// </summary>
 public Action<ExecuteOneAtATimeQueueItem, object, object> DoWork { get; set; }

 /// <summary>
 /// Called when work ends and the queue item can be marked as done.
 /// </summary>
 /// <param name="isError"></param>
 /// <param name="userState"></param>
 public void EndWork(bool isError, object userState) {
 EndedWork = DateTime.Now;
 this.IsError = isError;
 if (!_hasWorkCompletedEventBeenRaised) {
 if (WorkCompleted != null) { WorkCompleted(this, new EventArgs()); }
 _hasWorkCompletedEventBeenRaised = true;
 }
 }

 /// <summary>
 /// Called when work is started.
 /// </summary>
 public void StartWork() {
 if (this.IsBusy)
 throw new InvalidOperationException("Work has already started for this item.");
 StartedWork = DateTime.Now;
 if (WorkStarted != null) { WorkStarted(this, new EventArgs()); }
 if (DoWork != null) { DoWork(this, ItemToModify, UserState); }
 }

 #region INotifyPropertyChanged Members

 public event PropertyChangedEventHandler  PropertyChanged;

 internal void OnPropertyChanged(string s) {
 if (PropertyChanged != null) {
 PropertyChanged(this, new PropertyChangedEventArgs(s));
 }
 }

 #endregion
 }


 #region IDisposable Members

 public void Dispose() {
 Queue.CollectionChanged -= ncceh;
 Queue = null;
 }

 #endregion

 #region INotifyPropertyChanged Members

 public event PropertyChangedEventHandler  PropertyChanged;

 private void OnPropertyChanged(string s) {
 if (PropertyChanged != null) {
 PropertyChanged(this, new PropertyChangedEventArgs(s));
 }
 }

 #endregion
 }

}

Using The Class

Using the class is pretty easy. First, create a queue object in your viewmodel or control:

 

 private ExecuteOneAtATimeQueue queue;

Initialize the queue when you’re ready to use it:

 

 queue = new ExecuteOneAtATimeQueue();
 queue.BeginProcessingQueue();

BeginProcessingQueue() instructs the queue to start processing whatever items it already has or receives immediately. You can pre-populate the queue and then call BeginProcessingQueue(), or do what I did and just start adding items whenever you like.

So here’s my offending code, which failed when the buttons were clicked too fast:

void setObjectState(...) {
 ...
 Reference.ObjectData.SubmitChanges(so => { // BOOM
 if (callback != null) {
 if (so.HasError) {
 so.MarkErrorAsHandled();
 }
 callback(userState, so.Error);
 }
 }, true);
 ...
 }

 

Here’s the change to that call that was made, to queue it up and execute it.

 

 void setObjectState(...) {
 ...
 queue.Add(null, null, (qi, item, us) => {
 Reference.ObjectData.SubmitChanges(so => {
 if (callback != null) {
 if (so.HasError) {
 so.MarkErrorAsHandled();
 }
 callback(userState, so.Error);
 }
 qi.EndWork(so.HasError, null);
 }, true);
 });
 ...

}

I didn’t need to pass in any objects to modify during the callback, so I set both to null. In the queue.Add(…) statement, “qi” refers to the internal class instance ExecuteOneAtATimeQueueItem, which you must call EndWork() to tell the queue to move onto the next QueueItem instance and begin work. “item” and “us” (for user state) are both null, since nulls were passed in as the first two parameters to the Add() method. You can pass in two objects to work with if you need, I didn’t need them here though.

With this class, I added five lines of code to queue up RIA service calls, although your mileage may vary. Now I’m not disabling the whole wrap panel, I’m only setting each button instance to busy/disabled until the callback is called.