This segment looks at localization of button toolips and the descriptive text that appears in the statusbar when a menu item or button is active. The code for this segment is available at http://www.cavinconsulting.com/Code/Scribble11.zip.
The general idea is that we want to give helpful hints to our user when if comes to deciphering the little icons on the screen. Normally, we attach tooltips to tell the user what each icon does. However, some application go the extra step to make it clear what a command does by placing a brief description of the actions in the status bar. This description appears when the menu item or toolbar button becomes "active" and goes away when the button becomes inactive.
For our purposes, Active will mean the mouse is over a button and or that a menu item is the focus item on in the application (the latter lets us display description information even if the menu becomes active via Alt+f,arrow keys).
To implement the GotFocus and LostFocus event handlers for the MenuItems, I went down a number of paths including style triggers, control templates, and adding event handlers to each MenuItem individually. The implementation I landed on was to apply an EventSetter to the MenuItem style like this:
<Window.Resources> <Style TargetType="{x:Type MenuItem}" x:Key="{x:Type MenuItem}" > <EventSetter Event="GotFocus" Handler="OnGotFocus"/> <EventSetter Event="LostFocus" Handler="OnLostFocus"/> </Style> </Window.Resources>
From there, we change the declaration of each MenuItem to look like this:
<MenuItem Header="{x:Static scribble:ScribbleCommands.FileMenuText}"> <MenuItem Command="{x:Static scribble:ScribbleCommands.New}" /> <MenuItem Command="{x:Static scribble:ScribbleCommands.Open}" /> <MenuItem Command="{x:Static scribble:ScribbleCommands.Save}" /> <MenuItem Command="{x:Static scribble:ScribbleCommands.SaveAs}" /> <Separator /> <MenuItem Command="{x:Static scribble:ScribbleCommands.Exit}" /> </MenuItem>
Now, all we have to do is implement the event handlers:
public void OnGotFocus(object sender, RoutedEventArgs e) { DisplayCommandDescription(sender); } public void OnLostFocus(object sender, RoutedEventArgs e) { ClearCommandDescription(sender); }
Now, the implementation of the Display and Clear functions can look something like this:
private void DisplayCommandDescription(object sender) { MenuItem item = sender as MenuItem; if ((null != item) && (null != item.Command)) { ScribbleCommand command = item.Command as ScribbleCommand; if (null != command) { StatusText = command.Description; } } ScribbleButton button = sender as ScribbleButton; if (null != button) { StatusText = button.Description; } } private void ClearCommandDescription(object sender) { StatusText = ScribbleCommands.StatusbarText; }
Of course, if you compile and run at this point, you'll get an error about StatusText not existing. That's because it doesn't. Of course, you could just change that to _statusBar.Content, but that would be directly manipulating the UI from code. According to the rumor, it's better to set a property somewhere that notifies the observers that it has changed. So, that's what we'll do, by adding a dependency property to our main window:
static MainWindow() { StatusTextProperty = DependencyProperty.Register( "StatusText", typeof(string), typeof(MainWindow), new FrameworkPropertyMetadata(String.Empty, FrameworkPropertyMetadataOptions.AffectsRender)); } public static DependencyProperty StatusTextProperty; public string StatusText { get { return (string)GetValue(StatusTextProperty); } set { SetValue(StatusTextProperty, value); } }
Now, we can databind the status bar's content to the main window's StatusText dependency property (we've added x:Name="main" to the Window tag at the top):
<StatusBar DockPanel.Dock="Bottom" Visibility="{Binding Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=_viewStatusBar}"> <Label x:Name="_statusBarText" Content="{Binding ElementName=main, Path=StatusText}" /> </StatusBar>
Now, a little bit about the ScribbleCommand class. The ScribbleCommands class was getting a little tedious, so I wrote the ScribbleCommand class to encapsulate all of the information we need in order to attach to a menu item or a button. It inherits from RoutedUICommand so it plays nicely with the expectations of WPF. Really, it's just a RoutedUICommand with additional properties for Description and Tooltip.
For the status bar text on the buttons, I tried a different approach. Rather than checking for a command of a particular type in the activate and deactivate handlers, I checked if it was one of my own buttons that had a Description property. This helped in a few ways, one of which was that my inherited class was able to override the Tooltip property with that of the attached command. I'm not sure if this is a good approach or a blatant violation of some of the separation principles we were hoping to instill with the command pattern. I'll lay it out, anyway, and we'll see what we can make of it later.
The ScribbleButton inherits from Button, and has a text dependency property called Description (just like StatusText above). Also, it listens to the OnPropertyChanged event to see when the Command property is changed. If we get attached to a ScribbleCommand, then we set our tooltip and description according to the ScribbleCommand.
static ScribbleButton() { DescriptionProperty = DependencyProperty.Register( "Description", typeof(string), typeof(ScribbleButton), new FrameworkPropertyMetadata(String.Empty, FrameworkPropertyMetadataOptions.AffectsRender)); } public static DependencyProperty DescriptionProperty; public string Description { get { return (string)GetValue(DescriptionProperty); } set { SetValue(DescriptionProperty, value); } } protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); if(0==string.Compare(e.Property.Name,"Command")) { ScribbleCommand newCommand = e.NewValue as ScribbleCommand; if (null!=newCommand) { ToolTip = newCommand.Tooltip; Description = newCommand.Description; } } }
We do something similar to the GotFocus, LostFocus trick to hook up mouse focus:
<Style TargetType="{x:Type scribble:ScribbleButton}" x:Key="{x:Type scribble:ScribbleButton}" > <EventSetter Event="MouseEnter" Handler="OnMouseEnter"/> <EventSetter Event="MouseLeave" Handler="OnMouseLeave"/> </Style>
And that's it. We finally have an application that looks like an application, localizes nicely, and takes advantage of some of the new features of WPF. I think this is probably the end of the Scribble tutorial, as such. I'm still not entirely convinced I've learned the localization model MS is trying to get across. Every example I see uses that LocBaml example and editing values in Excel. I'm not entirely convinced that's the correct route either.
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.