Monday, May 24, 2010

Backlighting a ListBox

This little Silverlight sample is purely to satisfy my curiosity. I was styling a list box the other day and wondered if it would be possible to simulate backlighting on a selected list box item. The kind of thing you see on a game-show scoreboard where the current dollar value is lit up and casts reflected light over its surroundings. The bit I wondered about was getting a list box item to escape it's boundaries to light up the area around it.

It's completely unrelated to anything I am currently working on (or ever likely to for that matter), but it was something I wanted to do and that is as good a reason for doing it as any!

Here is the finished result and I'll explain how it's done below (try it full screen with the button in the top right corner):


You can grab the source here.

The challenging bit was getting the selected item in the list box to cast a glow on the things around it. The glow seems to escape the list box itself. The reason this wasn't straight forward is that the ListBox control clips its content to keep everything inside the scrollable area. My first instinct was just to set negative margins on something inside the ListBoxItem's template (ItemContainerStyle), but that's when it became obvious that it was being clipped.

So how did I do it? By diving into the ListBox's control template, and then into the ScrollViewer control's template (inside the ListBox control's template) and setting negative margins on the ScrollContentPresenter there. This effectively increases the area the ListBox requires. I then adjusted the ListBoxItem's control template, by setting the margins of the parent Grid to adjust to the extra room in the margin. Here's the steps.

Step 1
I'm assuming you have created a list box. Select it, right click it and select "Edit Template" and "Edit a Copy...". Expand the object tree and find the ScrollViewer element. Right click the ScrollViewer and use the same menu items to edit a copy of the ScrollViewer's control template.

Locate the ScrollContentPresenter element in the object tree view and select it. We need to change its margins, but first use the Advanced Property Options menu (the little yellow box next to the margin boxes) to "Reset" the existing margins, which are bound to the parent style. Once they are reset, change them to -ve values. In my example, I have set all the margins -10.

Step 2
Use the breadcrumb toolbar to exit editing the templates, back out to the main page again. Right click the ListBox and select "Edit Additional Templates" and "Edit Generated Item Container (ItemContainerStyle)" and "Edit a Copy...". This is the control template for the container that will hold each list box item.

At this point you create whatever elements you want in order to create the look you are after. You will have to work out which parts of the template you don't want encroaching into the extra space created by the -ve margins, and increase their margins by the appropriate amount.

In the example above, I have set the top margin of the top level Grid to -10, and the bottom margin set to 10. This counters the -ve top and bottom margins we set on the ScrollContentPresenter and makes the list box items drop back down into the list box area.

Then I've created a child Grid that contains all the elements that are visible when the list box item is not selected. I've set the left and right margins to 10 for those items so they don't poke out the side of the list box.

I've also created a child Grid for the elements that are only visible in the Selected state. That grid has -ve top and bottom margins to counter the values I set on the parent Grid. The items in this child grid will use up the extra 10px all around the list box item. The image to the left show the boundaries of this child grid (the items themselves are hidden).

Step 3
You may have noticed that the list box item elements that extend out of the ListBox now appear over the top of the ListBox border. To fix this, go back into the ListBox control template and drag the ScrollViewer control above it's parent Border. This should make them siblings, with the border just below the ScrollViewer (items lower in the object tree are rendered above items higher up in the object tree). Now the border should render over the top of the list box items.

Other Points of Interest
  • When you are adding elements that use the extra space from the -ve margins, it will look like every list box item is overlapping, but you should only be allowing the overlap for the items that will show in the Selected state so only one will end up showing at a time (assuming you leave the SelectionMode at "Single"). It may help to select the Selected state and turn off recording by clicking the red light in the top left corner of the design area. Don't forget to turn it back on when you are ready to record again.
  • The style is really only for a list box that can show all it's items without scrolling. I completely turned off the scroll bars for the list box since it would just get in the way. I wanted to achieve something very specific and was not interested in creating a style that I could reuse over and over again for any number of items (of any size) in the list.
  • I used a PNG image for the opacity mask for the glow that escapes the ListBox. The linear and radial gradients couldn't give the right shape I was after for the feathered edges.
  • The rest of the visual content is just bling really, and is only there to make the whole thing make sense. Without it I think the example would be a lot less interesting.
So grab the source and have a play. Leave a comment if you like it!

5 comments:

  1. Tip a shorter fullscreen code:

    "
    Button_Click(...)
    {
    Application.Current.Host.Content.IsFullScreen = !Application.Current.Host.Content.IsFullScreen;"

    }


    but since this is a more visual project, good work!

    ReplyDelete
  2. This looks great but I am having a hard time building your sample code in VS2010.
    Great job!

    ReplyDelete
  3. thanks for the feedback guys. Yes, you caught me being lazy - the code for going full screen was just copied and pasted from someone else.

    This project was build in Blend 3, so if you are opening it in VS2010 the conversion wizard will appear. When it asks you if you want to re-target for Silverlight 4 just say no. You will also be prompted to change the config file for debugging - it's safe to click yes.

    ReplyDelete