One scenario I've seen cause a bit of trouble on the Silverlight Controls Forum is that of putting a Button in a ListBox. There are two aspects of this that seem to cause difficulty and I thought it would be helpful to demonstrate the complete scenario in a runnable, self-contained sample. (Please Download the ZIP file attached to the bottom of this post for all the code/XAML in a ready-to-go Visual Studio 2008 + Silverlight Tools solution.) While I was developing the sample, I threw in a couple of other handy techniques that may not be widely known. The sample application shows a typical shopping cart experience where products are listed and their quantities can be interactively changed:
Details on the button scenario:
Other points of interest:
- ListBox's ItemsSource property is used to specify the items as a collection of the custom class
Product
and its ItemTemplate property is used to display those objects appropriately (coloring the text, adding Buttons, etc.). For lots more about configuring and using ListBox, please see my ListBox/ScrollViewer FAQ.
- The Add/Remove Buttons directly modify the
Quantity
property of the Product
objects. While that would not normally be enough to automatically update the UI, the Product class implements the INotifyPropertyChanged interface which can be thought of as kind of a light-weight DependencyProperty for non-DependencyObjects. More simply, it's an easy way to add change notifications to simple classes that Silverlight's data binding framework can use to automatically respond to property changes. In this case, updates to the Quantity
property fire the PropertyChanged event and the displayed quantity gets updated automatically. public int Quantity
{
get { return _quantity; }
set
{
_quantity = value;
// Fire PropertyChanged event to notify listeners of changed value
var handler = PropertyChanged;
if (null != handler)
{
handler.Invoke(this, new PropertyChangedEventArgs("Quantity"));
}
}
}
private int _quantity;
public event PropertyChangedEventHandler PropertyChanged;
- It's nice when the UI automatically prevents invalid actions - in this case it's an invalid action for the user to remove items when the quantity is already 0 (as it is for "Bananas" in the image above). While it would be possible to create a dedicated
bool
property of the Product
class and bind the Remove Button's IsEnabled property to it, there's a more elegant way. What the sample does is bind the Remove Button's IsEnabled property to the Quantity
property - using an IValueConverter to convert the int
type to a bool
automatically. This nicely avoids adding otherwise unused properties to the Product
object and helps to isolate the relevant logic. // Simple IValueConverter returns true iff the value is positive
// Used to toggle Remove button's IsEnabled when Quantity changes between 0 and 1
public class IntIsPositive : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (0 < ((int)value));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
At the risk of straying too far from the original scenario, I think the additional techniques I've shown here support it nicely and improve the user experience notably. I hope this sample helps people with their own projects - and maybe introduces a useful trick or two!
Enjoy!