Jun 3

Sometimes you want to tweak the layout of a particular panel without having to subclass the control. You can attach new behaviors to existing panels by using Attached Properties that modify the layout of the children before the children are rendered to the screen. Dan Crevier uses such a technique in his PanelLayoutAnimator where he adds an animation to the children of the panel by using a private attached property to attach the updater class to the original panel setting the attached property.

Here I use the same techique to solve a different problem.

Recently someone in the MSDN WPF forums asked how to display items in a WrapPanel to be aligned to the right when the items are wrapped.

The developer provided this code to illustrate the problem:

which produces this output:

As you can see, the wrapped items are still aligned to the left. The simple solution to this problem is to change the flow direction of the panel like this:

which now produces this output:

This is indeed the desired behavour albeit with one glaring caveat. The items are displayed in reversed order! The green box, which was at the end of the items, is now first. If you’re adding the items in a code-behind you have two options, do an insert at index 0 to push items to the front of the collection so they display correctly or reverse your collection before adding it or binding it to the panel.

This is a horrible idea fraught with risky refactoring of collection population and binding code throughout your code to accomplish something that is a UI layout problem.

Although a XAML only solution would be nice, in this case we have to write code to solve the ordering problem. One solution would be to subclass WrapPanel and override it’s Arrange method. This is a perfectly valid solution.

Let’s say however that your design team is a picky bunch and they don’t want to have to go updating opening and closing tags all over the place where wrap panels are used to change it to your new control. Let’s say they insist this new functionality be able to be added to their existing WrapPanels by setting a property on the existing XAML elements.

Well then, now we’re primed to use an attached property to accomplish what we need.

The solution requires a class that exposes an attached property to allow a designer to enable or disable our behavour in XAML. The default of the property is to be set to false which disables our layout tweak. In addition, the class should have a private attached property which we’ll use to attach an instance of our class to the wrap panel setting the property. Don’t get lost yet, I’ll explain in code further in the post. The last requirement is that our class accept a parameter in its constructor of type WrapPanel which ensures our attached property is limited to working on WrapPanels and allows us to subscribe to the WrapPanel’s layout updated event which occurs after all the children have been laid out but before they’re rendered. This gives us the opportunity to modify them a bit before they get rendered to the screen.

Here’s the portion of our class built to manipulate the children. It’s pretty straightfword:

As you can see, in our constructor we receive our wrap panel and subscribe to it’s LayoutUpdated event. In the LayoutUpdated eventhandler we first reverse the order of our children, then calculate locations and arrange them. Finally we provide a detach method to unsubscribe to the wrap panel’s event and clean up.

Now the question is, how do we receive the wrap panel we manipulate?

With the following attached property added to the class definition:

The interesting bit here is in our handling of the OnIsEnabledChanged event. We receive the WrapPanel that set the static property, if it’s set to true, we create a new instance of the class hosting the property passing in the source WrapPanel into the constructer.

Then we use a private attached property defined in the same class:

…to store our new class instance as an attached property to our source WrapPanel. When the IsEnabled attached property is set to False, we retrieve our class instance from the private attached property and call the Detach method to unsubscribe to the LayoutUpdated event, thus preventing further layout tweaks.

With this in place we can now use the property like so:

…after we declare the “local” namespace pointing to our assembly. This now produces the following desired output:

So now you have a method to use an attached property to modify existing elements without having to subclass the element. This would be even more valuable in cases where we want to attach behavior to any panel type, not just wrap panels.

Source code here.

Have a nice day.

Jun 1
Live Tag Cloud in WPF
icon1 roland | icon2 WPF | icon4 06 1st, 2008| icon3No Comments »

As I mentioned in my previous post, I’ll be writing entries about various components I’m creating for a personal project I’m working on in my spare time called Zimba.

One of the features of Zimba are various versions of live tag clouds. If you don’t know what a tag cloud is, read this post from Smashing Magazine to get a definition and see some examples. The difference between most tag clouds and Zimba’s is that our tag cloud is live. You see, Zimba is an internet enabled application allowing other users to execute searches against a pre-configured portfolio of stock photography you’ve imported into the application. Your photography will have been tagged with keywords to make the photos available to the rest of the Zimba network and photo buyers will search for media by specifying keyword tags as the search criterion. When the buyer executes a search, the search tags are broadcast to all photographers on the Zimba network. If you have photos matching the search tags you return results, if not, you ignore the request.

Even though many requests will be ignored doesn’t mean we can’t do something useful with the search terms querying against us. One of the things we do with the request is compile a list of search keywords and the frequency with which they are searched on in order to display a tag cloud of search tags to the photographer. Viewing a live cloud of search terms allows the photographer to see a live picture of the hot tags being searched on at that moment in time. This is what this demo illustrates.

Other tag clouds I may make available to the photographer later is a cloud of tags from their own portfolio to illustrate diversity of their images, a cloud of search tags they were able to fulfill with results and a cloud of tags they were not able to fulfill thus informing the photographer of the types of pictures missing from their portfolio they should consider adding.

What’s It Look Like?

What does a live tag cloud look like in WPF? Here’s one low-res version:

It’s much more fun to watch in full resolution. Download the source code or binary to watch it in action live and play around with it.

What you just watched was a mock live tag cloud. “Mock” because I simulate peer search requests coming into the application every 300 milliseconds which are then added to the list of tags searched on. The number of times a tag is searched on, the “incidence count”, is stored along with the keyword tag itself. If a tag has already been searched on in the past, rather than adding it to the list again, we simply increment the Incidence Count on the existing item in our list, otherwise we add the new non-existing tag to our list with an Incidence Count of 1. The displayed tag is scaled based on the incidence count of the tag. Each time someone searches on a tag already existing in your list of previously searched tags, it’s incidence count is increment and thus it’s scale is larger. In other words, the more times the tag has been searched on, the bigger it is displayed. Also, I’ve created 5 RangeName buckets each tag falls into based on how high it’s incidence count is compared to all the other tags in the list: Very Low, Low, Medium, High, Very High.

  • Very Low tags are in the bottom 15 percentile of all tags searched. They don’t even show up on the screen thanks to a style trigger collapsing Very Low keywords.
  • Low incidence tags show up in between the 15 and 40 percentile of all tags searched on. They are displayed light grey and at 90% opacity.
  • Medium incidence tags lay between the 40 and 70 percentile range of all terms searched and are displayed dark grey at 80% opacity.
  • High incidence tags show up between the 75 and 95 percentile of all tags searched and are displayed orange at full opacity.
  • Finally, Very High incidence tags are in the top 5% of all tags searched based on incidence count and are displayed red at full opacity.

Watch the video again to see this in action.

How is it done?

Honestly, it’s simple. WPF makes things unbelievably simple once you wrap your head around how to use it.

We make use of the following items in this WPF demo:

  • A dependency object we create called KeywordTag containing a couple of dependency properties: IncidenceCount and RangeName
  • A custom observable collection of said KeywordTags disallowing duplicates to be added, but rather incrementing the IncidenceCount of attempted duplicates.
  • A value converter to provide a scale of a displayed KeywordTag which is used in a ScaleTransform
  • A C# 3.0 extension method to allow a KeywordTag item to calculate it’s own RangeName based on the maximum incidence count from the collection

Other than that we use a DispatcherTimer to randomly add search terms from a pool of available tags and we style a listbox of tags with a DataTemplate and custom Style. We scale the KeywordTag items with a slider in the lower right corner of the window whose value is bound to our value converter. See? Nothing scary going on here.

Also, just for added fun we make use of Dan Crevier’s panel layout animator to put things in place. Thanks for the great blog Dan!

Have fun!

The source code is heavily commented. There’s probably more comments than code so have a look through it to see the nitty gritty details. Feel free to drop me a line if you have any questions. As a fun little diversion and a testament to the speed of WPF, try changing the timer interval to something ridiculously fast, saaay 1 millisecond and watch the words fly!

Again here are the pertinent downloads:
Binary
Source Code

« Previous Entries Next Entries »