The blog of dlaa.me

Posts from April 2008

Buttons in a ListBox, and More [Demonstration of some useful Silverlight techniques]

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:

"Buttons in a ListBox" Sample Application

Details on the button scenario:

  • One problem with putting a Button in a ListBox is that by default the Button can be clicked only when the corresponding ListBoxItem is selected. This is an unfortunate consequence of a very late change to Silverlight Beta 1 that causes ListBox to receive duplicate GotFocus/LostFocus events. For most controls the duplication is harmless, but for ListBox it interferes with ListBox's attempts to preserve WPF's focus/selection behavior when a focusable control (like Button) is clicked on. Fortunately, Button (and its subclasses like CheckBox) expose the ClickMode property which can be used to work around the problem. Simply changing the Button's ClickMode enumeration from the default value of Release to Press does the trick.
    <Button ... ClickMode="Press" ... />
  • Another problem people tend to have is hooking up an event handler to Buttons in a ListBox. While this can be a little tricky to do in code (specifically when the ItemTemplate is being used), it's quite easy to do in XAML by specifying a value for the Click event. Visual Studio generates the code for the event handler automatically, so this ends up being elegant and simple.
    <Button ... Click="Add_Click" ... />

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!

[ListBoxWithButtons.zip]

Proof-of-concept Silverlight XPS reader gets some Beta 1 love [SimpleSilverlightXpsViewer updated for Silverlight 2 Beta 1!]

After a few customer requests to update my SimpleSilverlightXpsViewer proof-of-concept XPS reader for Silverlight 2 Beta 1, I finally found time to do so. :) I've just updated the original SimpleSilverlightXpsViewer demonstration page and also updated the original source code download - so you can try it out in your own browser and/or download the code to see how it works!

SimpleSilverlightXpsViewer Application

Click on the image above to play around with the application in your browser. More details about what it is doing and how it works are available in the original SimpleSilverlightXpsViewer post.

Notes:

  • This was a fairly straightforward port from the original 1.1 Alpha implementation to 2 Beta 1. I have not changed the manual Canvas-based layout to automatic layout via Grid/StackPanel/etc. - but I'd definitely take that approach if I were writing it from scratch today!
  • The XAML didn't change at all - and most of the necessary code changes are detailed in the Breaking Changes in Silverlight 2 document on MSDN.
  • Because of the file:// URL restrictions introduced a while ago as part of the 1.1 Refresh, the sample code includes a separate web site. I explain more in the second half of my post outlining changes for the 1.1 Refresh.
  • One notable improvement is that I was able to #define SETSOURCE (see the discussion in the original post) and therefore the images are all being accessed from the XPS document itself instead of needing to be pulled out as separate files! I removed the associated #if/#endif blocks because they are no longer relevant.
  • I still didn't see any way around the Glyphs.FontUri issue (discussed in the same section of the original post), so the fonts remain separate like before.
  • Silverlight occasionally renders the "pageGraphic" Rectangle incorrectly. The obvious consequence of this is that the white page background disappears if it extends outside the Silverlight control's edges (e.g., you've zoomed in or panned). It's possible to verify that you're in this situation because the black page border is also not drawn properly. I've found that a simple refresh of the web page typically corrects the problem.

Blogging code samples a tad more easily [Updated free ConvertClipboardRtfToHtmlText tool and source code!]

Kind readers gave some great feedback on my previous post of the ConvertClipboardRtfToHtmlText tool and source code. Accordingly, I have made three small tweaks to the tool:

  • The code to detect the start of the RTF text worked only if Visual Studio's font size was set to 8pt. That's what I use, but it's not the default, so this would cause problems for most people who tried the tool. The relevant code no longer looks for a specific font-size.
  • Tab characters in the RTF text were ignored, causing layout problems for code with tabs (vs. spaces). Tabs are now auto-expanded to the mostly-standard value of 4 spaces.
  • The use of the private Color class was unnecessary because it added nothing over System.Drawing.Color. System.Drawing.Color is now used to save a few lines of code.

The sample code and tool in the previous post have been updated with these changes, so please go there to get the latest version.