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#, Generics, XAML

Binding to a Checkbox List in XAML

This technique is useful outside of XAML bindings, too. I found myself working with a person object that can belong to 0 or more groups. The database spec is that it is a many-to-many relationship, something that RIA services chokes on at this time.

Group
- ID
- GroupName
- GroupDescription

Person
- ID
- FirstName
- LastName

PersonXGroup
- PersonID
- GroupID
- CreationTimeStamp // this is here because RIA doesn't play nice with *-* relationships

So, imagine here that there are two foreign keys set up on this model, you know where they go. You need to bind to two different things: The master list which contains all available groups, and the groups that this person has enabled. So, how would you go about binding this list to an ItemsControl filled with checkboxes that allow you to update the state? Use a CheckboxBinder object, which follows.


///

/// Given a master list of items and an object containing a subset of these items,
/// such as a list of groups available and a person belonging to none, some, or all groups,
/// this class allows for binding this list to an itemscontrol with checkboxes to
/// turn on/off items by adding or removing the groups from the person.
/// Adding and removing the items once chnages are to be committed can be handled
/// in a couple ways: (a) By hand, looping through the collection and adding or removing
/// each one, using HasChanged() to see what items have changed, or
/// (b) assign a delegate action for the work to be done when the state changes.
///

///
public class CheckboxBinder
{

public T Item { get; set; }

///

/// Gets or sets the current checked state for this item.
/// If the ItemCheckedChanged delegate is not null, it is called when the state of the item
/// changes.
///

public bool IsChecked {
get { return _isChecked; }
set {
if (_isChecked != value) {
_isChecked = value;
if (ItemCheckedChanged != null) { ItemCheckedChanged(Item, IsChecked); }
}
}
}
private bool _isChecked;

///

/// Optional delegate that is called when IsChecked has changed.
/// Returns the item and its current check state.
///

public Action ItemCheckedChanged { get; set; }

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

private bool InitiallyChecked { get; set; }

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

public CheckboxBinder(T item, bool initiallyChecked) {
Item = item;
_isChecked = initiallyChecked;
InitiallyChecked = initiallyChecked;
}

public bool HasChanged() {
return (IsChecked != InitiallyChecked);
}

}

It’s a small class with a lot of power. On your viewmodel (you are using viewmodels, right?) define a collection of ObservableCollection<CheckboxBinder>. This is what you bind your ItemsControl to in your XAML code.

public ObservableCollection<CheckboxBinder> Groups { get; private set; }

When a Person object has been loaded for editing, I populate this list. The constructor for the CheckboxBinder object requires an instance of the item to be bound to, and the initial state as a boolean. The current state of the object is controlled by a bool property called HasChanges(), which compares the initial state to the current state. However, that would be used in a scenario where, when you press the Save button, your code would loop through the entries or use a LINQ statement to grab everything that has changes and then perform an action on it. However, that breaks up the functionality into multiple places throughout your code file, so I enabled another method of updating the collection with the use of an Action property called ItemCheckChanged:

///

/// Handles loading the groups and performing any changes, using the CheckboxBinder's
/// provided change delegate.
///

private void loadGroups() {
Groups = new ObservableCollection<CheckboxBinder>();
foreach (Group group in Reference.PersonData.Groups) {
var cbgroup = new CheckboxBinder(group, CurrentPerson.PersonXGroups.Any(g => g.Group == group));
cbgroup.ItemCheckedChanged = (item, isChecked) => {
if (isChecked) {
CurrentPerson.PersonXGroups.Add(new PersonXGroup() { Group = item, Person = CurrentPerson });
} else {
PersonXGroup pxg = CurrentPerson.PersonXGroups.Single(px => px.Group == item);
//CurrentPerson.PersonXGroups.Remove(pxg);
Reference.PersonData.Delete(pxg);
}
};
Groups.Add(cbgroup);
}
}

You pass ItemCheckedChanged a void delegate with 2 parameters: The item being modified and its current state. It’s only called when the value actually changes (so if the current state is false and you change the value to false, this will not be called). The update code handles adding and removing items from the collection.

if (isChecked) {
CurrentPerson.PersonXGroups.Add(new PersonXGroup() { Group = item, Person = CurrentPerson });
} else {
PersonXGroup pxg = CurrentPerson.PersonXGroups.Single(px => px.Group == item);
//CurrentPerson.PersonXGroups.Remove(pxg);
Reference.PersonData.Delete(pxg);
}

This way, you’re not iterating through a loop at the start of your Save routine. However, this is possible because I can just RejectChanges on my data context if I so desire – but the other aforementioned method works well if you don’t have the ability to roll back changes like you can on a data context.

Here’s the XAML used to bind to the checkbox list:
<sdk:Label Content="Groups" Style="{StaticResource FormHeaderStyle}" Margin="5,10,5,0" />
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Path=Groups}" Height="130">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="2" Content="{Binding Path=Item.GroupName}" ToolTipService.ToolTip="{Binding Path=Item.GroupDescription}" IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>

Has this helped you? Do you know of a better way to accomplish the same task? Shout it out in the comments.