The .NET framework is huge, but not so huge that it does everything for everyone; there are things that they in Redmond miss or don’t do for whatever reason but is still generally applicable to many developers. So, dear reader, I present to you a series of posts on stuff I find missing in .NET, typically where even the Google fails to find the answer. It could be a useful class, a technique, a good practice or documentation that should be in the framework but isn’t.
A regular reader would know I’m quite enamoured with the Cue Banner as UI tool. Once again, in WPF, Microsoft missed something fairly obvious for inclusion; and really easy to implement, as we’ll see. We’ve been through 2 revs of WPF since its release, and the number of new controls or new features on old controls is disappointing. In case you missed it, .NET 3.5 SP1 Beta was released a few weeks ago. The grand total of new controls in WPF? One; admittedly, a useful one – a WebBrowser control.
When I showed how to use the Cue Banner in Windows Forms, I mentioned that the Cue Banner is no app saver. It’s no laser show, fireworks or smoke machine. But it does add significantly to the usability of the app. Since we’re seeing it more frequently on the web now as well, I’d say the effectiveness of the Cue Banner is obvious. When I mention usability, I mean the ease with which a human can use a tool, like software, not "it looks cool." I notice that people often confuse usability with prettiness in the software realm. But you’re smarter than that aren’t you, dear reader?
There are already two implementations of Cue Banners that I’ve seen in WPF, both have some cool features, but both inherit from TextBox which I find so limiting that I can’t really recommend them. The coolest one is the InfoTextBox in Kevin Moore‘s WPF Bag-O-Tricks which has been around since before WPF got its horrible name. The InfoTextBox is actually a great example of extending a Control in WPF. There is almost no code, except for the declared Cue Banner property, called, rather yuckily, TextBoxInfo. It truly shines in the overriding style (in generic\InfoTextBox.generic.xaml). It’s a collection of triggers and ControlTemplate; I still don’t understand how it’s working completely. If you want to see some WPF code written by someone who knows what they are doing, you should download the source and have a look. Anyway, the cool thing about the InfoTextBox is the effect Kevin created when you focus on the TextBox, but before you enter some text. You have to see it to believe it, so go download it!
The second implementation is not from WPF but from its younger, more attractive sibling, Silverlight 2. Poor WPF will probably be the Charlie to Silverlight’s Eddie Murphy, because of the Web’s ubiquity and importance. Silverlight 2 is still in beta, but it has had in its controls collection since the start, the unfortunately named WatermarkedTextBox.
My first Computer Science class was a mandatory class for my first degree in Physics. I’m not going to lie to you: I didn’t like it. Why I didn’t like it is for another blog post and it’s not why I brought it up. I have on occasion gone back to the code I wrote for my assignments and saw with amusement that all my variables were called x. Amusing, because I’m quite adamant on properly naming the classes, components, methods, variables, parameters in my code now, and have been since I started liking this whole computer thing. It’s too important for clarity. I’ve gone through the SomethingManager naming scheme phase, I pay attention to what FxCop says about naming when I create new classes and none of my variables are now named x. I’m fairly literate, so I know the meaning of the words that I use. The WPF team, on the other hand, doesn’t.
If Wikipedia is any authority, then a watermark, either digital, or …real, is for permanently marking something with a mark to distinguish where the something came from. That little logo on the bottom corners of TV shows now? The ones that wreck plasma TVs because they never go away? Those are watermarks. They are always there. The whole point of a cue banner, on the other hand, is to provide just a little hint of what should go there. Once text is entered, then it goes away.
Here’s a couple more reasons WatermarkedTextBox sucks: Google knows about cue banners. It also knows about watermarks, but the real watermarks. The Silverlight controls has both a WatermarkedTextBox and TextBox. Um, why? Just make Watermark a property on TextBox. If it’s null, it doesn’t show up.
The cool thing about WatermarkedTextBox? Actually, cool is the wrong word for it but I hadn’t thought of it until seeing the WatermarkedTextBox demo page: using images or text as the cue banner. I extrapolated: why not anything as the cue banner?
Then I answered my own question.
Cue banners are useful on ComboBoxes and TextBoxes, so that eliminates the possibility of inheritance if I want to maintain this code and keep my sanity (ComboBox and TextBox don’t share enough code in WPF, namely, their Text properties are different). What else could I use? I know! Attached properties! I love those things.
The code for declaring attached properties, like all dependency properties, is pretty much all boilerplate:
public static class CueBannerService { //there is absolutely no way to write this statement out //to look pretty public static readonly DependencyProperty CueBannerProperty = DependencyProperty.RegisterAttached( "CueBanner", typeof (object), typeof (CueBannerService), new FrameworkPropertyMetadata(null, CueBannerPropertyChanged)); public static object GetCueBanner(Control control) { return control.GetValue(CueBannerProperty); } public static void SetCueBanner(Control control, object value) { control.SetValue(CueBannerProperty, value); } private static void CueBannerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ... } }
This code declares CueBanner as DependencyProperty of type object, owned by the CueBannerService, and whose default value is null. The Get/Set methods are there by convention for the programmer who likes to declare stuff in code, and for the XAML engine to set things at design time. You can’t leave it out, even though it won’t be called at runtime if you set these properties in XAML. This is all standard stuff; the difference between this attached property and the rest, other than the name, is what happens in the PropertyChanged event handler. For a good intro to Dependency Properties, you check out Drew Marsh’s Introduction to DependencyProperties post.
It’s Cue Banner’s nature to be set once. The Cue Banner’s only purpose is to provide a small hint to the user about the control with which it’s associated. So the property changed handler assumes that the value is only going to be set once. However, the control may show the cue banner multiple times during the lifetime of the app. We’ll start simply here and implement it only for TextBox and ComboBox. The behaviour of the Cue Banner for these two controls is to show itself if the Text value of the control is the default value (the empty string for these two controls) and to remove itself if the user focuses on the control. In my property changed handler, I basically want to get a reference to the control instance and listen for some of its events:
private static void CueBannerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Control control = (Control)d; control.Loaded += control_Loaded; if (d is ComboBox || d is TextBox) { control.GotFocus += control_GotFocus; control.LostFocus += control_Loaded; } }
There’s more coming in this method, hence the type check. My first implementation of this, when I just limited it to TextBox, was to set the TextProperty if the TextProperty was the empty string. Once the user focused on the control, I’d check to see if the TextProperty value was the CueBanner value, then set the TextProperty back to the empty string. This had the undesirable side effect of raising the TextChanged event on the TextBox. Since I can’t control when that gets raised, any handler that is listening to that event would have to check the cue banner value as well as the empty value. That’s not pretty at all. For most of the low-level input events there is a PreviewXxxChanged event that can be used to cancel the XxxChanged event, but TextChanged doesn’t have one.
So I needed another way. I could have tweaked styles like I did with the AutoComplete WPF implementation, but it didn’t really seem appropriate. Plus, it’s a proven method that I’ve already found; that’s just downright boring. The whole point of implementing these things is to learn different aspects of WPF. Basically what I needed was a way to display something on the screen in the same spot as the TextBox, but I couldn’t use the Text property for fear of messing with the developer’s expectations of an unobtrusive, useful addition to WPF.
It usually happens that when I’m trying to solve a problem where the solution isn’t obvious, the solution comes all at once and at the oddest moment. In this case, it came when I was bombing around Beatriz Costa’s excellent WPF blog, reading this post on Drag and Drop, after reading Chris Sells and Ian Griffiths’ WPF book, Programming WPF, Chapter 18 to be precise. If you don’t own that book – well, you should – Chapter 18 deals with custom controls. After reading those two articles, I had enough stuff bouncing around in my brain to come up with the answer to my problem: the adorner layer.
I have no doubt that you’ve seen the adorner layer in action: the underwhelming WPF visual designer in Visual Studio uses it to show the size values changing as you resize your window or control. The adorner layer, though, is always around, and sits atop the window of your app; think of it as a sheet of glass. Drag and drop is a good use for it as well: since you’re moving all over the visual tree, implementing drag and drop any other way would cause you to go insane.
For my needs, the AdornerLayer is perfect. A small visual cue above the control (above as in higher in the z-order) that goes away when needed. The AdornerLayer contains adorners, so that’s the next thing I have to write. An Adorner is a UIElement so it has all the power of WPF behind it; I want to take advantage of it. My CueBanner will likely be a string, but it could be something else, an image, say. I want something that will handle all that for me. Using the It’s in the Framework, Dummy principle, I knew that the good folks on the WPF team already came up something that does that, the ContentPresenter. That’s all my CueBannerAdorner does: wrap a ContentPresenter in an Adorner. It’s a blatant, purposeful copy of Bea Costa’s DraggedAdorner from the Drag and Drop sample.
internal class CueBannerAdorner : Adorner { private readonly ContentPresenter contentPresenter; public CueBannerAdorner(UIElement adornedElement, object cueBanner) : base(adornedElement) { this.IsHitTestVisible = false; contentPresenter = new ContentPresenter(); contentPresenter.Content = cueBanner; contentPresenter.Opacity = 0.7; contentPresenter.Margin = new Thickness(Control.Margin.Left + Control.Padding.Left, Control.Margin.Top + Control.Padding.Top, 0, 0); } private Control Control { get { return (Control) this.AdornedElement; } } protected override Visual GetVisualChild(int index) { return contentPresenter; } protected override int VisualChildrenCount { get { return 1; } } protected override Size MeasureOverride(Size constraint) { //here's the secret to getting the adorner //to cover the whole control contentPresenter.Measure(Control.RenderSize); return Control.RenderSize; } protected override Size ArrangeOverride(Size finalSize) { contentPresenter.Arrange(new Rect(finalSize)); return finalSize; } }
By default, WPF sizes everything to the content it creates. The one thing that took me a little time to figure out is the right Size value to pass to the ContentPresenter in MeasureOverride. Amazing how generic that code is, eh? Despite all my griping earlier, WPF is still totally awesome. Classes like ContentPresenter are pure magic.
Great. Now we just need to add our adorner to the AdornerLayer. We do that in the event handlers I subscribed to in the CueBannerPropertyChanged handler:
private static void control_GotFocus(object sender, RoutedEventArgs e) { Control c = (Control)sender; if (ShouldShowCueBanner(c)) { RemoveCueBanner(c); } } private static void control_Loaded(object sender, RoutedEventArgs e) { Control control = (Control)sender; if (ShouldShowCueBanner(control)) { ShowCueBanner(control); } } private static void RemoveCueBanner(UIElement control) { AdornerLayer layer = AdornerLayer.GetAdornerLayer(control); Adorner[] adorners = layer.GetAdorners(control); if (adorners == null) return; foreach (Adorner adorner in adorners) { if (adorner is CueBannerAdorner) { adorner.Visibility = Visibility.Hidden; layer.Remove(adorner); } } } private static void ShowCueBanner(Control control) { AdornerLayer layer = AdornerLayer.GetAdornerLayer(control); layer.Add(new CueBannerAdorner(control, GetCueBanner(control))); } private static bool ShouldShowCueBanner(Control c) { DependencyProperty dp = GetDependencyProperty(c); if (dp == null) return true; return c.GetValue(dp).Equals(""); } private static DependencyProperty GetDependencyProperty (Control control) { if (control is ComboBox) return ComboBox.TextProperty; if (control is TextBoxBase) return TextBox.TextProperty; return null; }
That just about does it. Now you can set a cue banner to just about anything for TextBox or ComboBox. The code is similar to how ToolTip works. Here are a few examples:
<StackPanel> <TextBox Name="textBox1" VerticalAlignment="Top" HorizontalAlignment="Stretch"> <sm:CueBannerService.CueBanner> <Image Source="Forest.jpg"/> </sm:CueBannerService.CueBanner> </TextBox> <ComboBox Height="23" sm:CueBannerService.CueBanner="Pick Something" IsEditable="True" IsDropDownOpen="False"> <System:String>Geek</System:String> <System:String>Cool Guy</System:String> </ComboBox> </StackPanel>
But I’m not done yet!
Why limit ourselves to just text controls? I know I keep reminding you about what a cue banner is, but here it is again: a small visual hint about what you’re supposed to do. That needn’t be limited to edit controls.
I recently started looking into this whole Web 2.0 thing and got a Flickr account. Their Uploadr tool is pretty useful and look what they have in their image list pane thing (it’s written with XUL on Mozilla):
Here’s where all that generic code shines. We can easily add some support for CueBanners on List controls, we just need some way to tell when to display it and when to turn it off. In WPF, when we talk about List Controls, we’re talking about ItemsControl. There is a slight wrinkle in that there are two ways to add Items to an ItemsControl: through the non-dependency property, Items, or through the ItemsSource dependency property using data binding. When we first get a reference to our ItemsControl, we have to account for both:
1: private static void CueBannerPropertyChanged(DependencyObject d,
2: DependencyPropertyChangedEventArgs e)
3: {
4: Control control = (Control)d;
5: control.Loaded += control_Loaded;
6: if (d is ComboBox || d is TextBox)
7: {
8: control.GotFocus += control_GotFocus;
9: control.LostFocus += control_Loaded;
10: }
11: if (d is ItemsControl && !(d is ComboBox))
12: {
13: ItemsControl i = (ItemsControl) d;
14: //for Items property
15: i.ItemContainerGenerator.ItemsChanged += ItemsChanged;
16: itemsControls.Add(i.ItemContainerGenerator, i);
17: //for ItemsSource property
18: DependencyPropertyDescriptor prop =
19: DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, i.GetType());
20: prop.AddValueChanged(i, ItemsSourceChanged);
21: }
22: }
Here’s the complete implementation of CueBannerPropertyChanged. I’m not entirely happy with this implementation, but I couldn’t see a way to handle the Items otherwise. The Items collection isn’t a dependency property so we have to use something else. What I really want is for the ItemsControl to raise an event when the Items collection changes. I had to settle for going through the ItemsControl’s ItemContainerGenerator (the class factory for ItemContainers), which meant I had to track ItemContainerGenerators to ItemsControls because I can’t access the ItemsControl from the ItemContainerGenerator’s ItemsChanged event handler, hence the itemsControls dictionary on line 16. If anyone knows a better way, please leave a comment (if you got down this far :)). The ItemsSource property is a dependency property so we can use the DependencyPropertyDescriptor class to add an EventHandler when the property value changes. I was worried about the sender in that event but it turns out to be what you’d expect it to be, namely the ItemsControl instance. Now you can easily add a CueBanner to a ListBox
<Window x:Class="WpfApplication1.Window3" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sm="clr-namespace:Soundmind.Windows;assembly=Soundmind.Windows" Title="Window3" Height="300" Width="300"> <Grid> <ListBox > <sm:CueBannerService.CueBanner> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Height="23"> <Run Text="Drag some images here, bitches" FontSize="18"/> </TextBlock> <TextBlock TextAlignment="Center"> <Run Text="Or click add to find photos" FontFamily="Arial" /> </TextBlock> </StackPanel> </sm:CueBannerService.CueBanner> </ListBox> </Grid> </Window>
which looks like the following:
Nice post! But I didn’t see a download link anywhere. I was going to play with your CueBanner, but wasn’t sure if I had to cut and paste my way to a file or if there was some place to download some working code. Thanks!
Thanks Scott. Something weird happened between WordPress and Windows Live Writer. I’ve hopefully fixed it. You should see the link at the bottom of the post.
This is brilliant.. no better, amazing! Thanks for the article 🙂
Great code, I didn’t even think about adding a cue like that. I did find a small problem though, if the cue’s are displayed and the visiblity of the control changes, the cues can be left. To fix this I hooked up to the IsVisible property to the control so it would remove the cue if it was visible. Not sure if I fixed in the best manner, but it seems to work.
private static void CueBannerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Control control = (Control) d;
control.Loaded += control_Loaded;
DependencyPropertyDescriptor isVisiblePropertyDescriptor = DependencyPropertyDescriptor.FromProperty(Control.IsVisibleProperty, d.GetType());
isVisiblePropertyDescriptor.AddValueChanged(d, control_IsVisibleChanged);
….
private static void control_IsVisibleChanged(object sender, EventArgs e)
{
Control control = (Control) sender;
if( !ShouldShowCueBanner(control) || !control.IsVisible )
RemoveCueBanner(control);
}
Great! Congratulation!
I like it very much! Easy to use. 🙂
Thanks!
Awesome, thanks for posting this! Saved me a bunch of effort.
I have a question though, how would we have the adorner disappear once the Text property in the TextBox changes?
JP:
If you look at the GotFocus event handler, you can see that we call the RemoveCueBanner() method if there is text in the textbox.
Sorry I didn’t clarify. I mean if the textbox is not in focus but the text within it gets updated via something else (ie. another class method) setting it. The adorner stays on and the updated text is underneath. I’m still a bit green at WPF and I’m trying to figure out how to set an event with something like “OnContentUpdate” for a textbox. I’ll post back if I can figure something out.
I’ve modified CueBannerPropertyChanged to specifically check for a TextBox and add a new event handler for TextChanged.
private static void CueBannerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Control control = (Control)d;
TextBox tb = (TextBox)d;
tb.Loaded += control_Loaded;
control.Loaded += control_Loaded;
if (d is TextBox)
{
tb.GotFocus += control_GotFocus;
tb.LostFocus += control_Loaded;
tb.TextChanged += textbox_Changed;
}
then in my textbox_Changed
private static void textbox_Changed(object sender, RoutedEventArgs e)
{
TextBox c = (TextBox)sender;
if (c.Text != “”)
{
RemoveCueBanner(c);
}
}
Sorry I’m spamming your blog now.
It seems that the above code does compile, however it breaks the Design view in VS2008. The TextBox that I’ve put the CueBannerService in is now highlighted with an error of “Object reference not set to an instance of an object.”
If I remove the line with setting the event of TextChanged the design view works fine.
Any thoughts?
i might be missing something, but how can the Cuebanner text be set programatically.
Mybextbox.CueBanner = “This is my CueText”;
First, nice tool! Microsoft should have this in their toolbox!!!
My problem is similiar to the TextBox problem not showing CueBanner when external events update the bound value…
I cannot get the ComboBox to work. I have two (2) ComboBoxes for a Master-Detail UI. When the SelectedItem in MasterComboBox1 changes, I need DetailComboBox2 to show the CueBanner. I am setting the selectedItem to null, and the TextBox of the ComboBox clears – yet no CueBanner. There seems to be a disconnect between the Dependency Properties ComboBox.TextProperty and ComboBox.SelectedxxxProperty. I either get the value and the CueBanner superimposed or nothing at all.
Any help resolving this would be much appreciated. Thank You.
Very nice!
I found an issue when I was trying to show an animated image in the Adorner. The image would only change when the adorner was hidden and reloaded. The solution I found was here:
http://stackoverflow.com/questions/583808/animation-inside-an-adorner-calling-onrender
Adding:
base.AddVisualChild(contentPresenter);
as the last call in the CueBannerAdorner constructor fixed this.
I really dig the approach here to the problem and will be using it, however the GotFocus, LostFocus and Loaded event handlers are never removed and are set using strong references. Since this is a static class and the class never removes the event handlers, the targeted controls will never be garbaged collected. This is a pretty significant memory leak.
Instead of using += to set event handlers you should use the DependencyPropertyDescriptor method that Brian used for the Visibility issue. Internally the WPF Dependency system uses weak refrences for event handlers thus the memory leak issue is avoided. I will post example code showing my solution derived from yours. Thanks for the good work!
Nice work!
But, these code makes Blend2 crash (there are no problem in VS):
Geek
Cool Guy
for textboxes, you could use triggers (for the TextBox.HasContent) dependency property to show the banner only when there is no text, and use a custom attached property like BannerText to set the text, and then bind to it in the trigger.
This is a nice solution, but I found two issues (even after all the wonderful comments):
1) This code crashes Blend 3.
2) The math seems to be off for where the adorner should be placed on a combobox. Create a combobox, give it a margin of 10, and then apply the CueBanner. The banner is shifted down and to the right. Removing Control.Margin from the calculation makes it better, but still not where text appears.
I tried fixing the issue with the event handlers preventing garbage collection, but alas it was beyond me.