Monday, February 21, 2011

Tricks & Tips: ListBoxes, TextBoxes and SelectedItems

I ran across this today and thought it was worth sharing. The problem is one that's been found in both Silverlight and WPF, but there doesn't seem to be a nice simple solution for either.

The issue involves a control that can accept focus (e.g. a TextBox) being part of the ItemTemplate of a ListBox control. In this scenario, you can select one ListBoxItem by clicking on the whitespace within the ItemTemplate, but when you click on the focusable control (the TextBox) of another ListBoxItem, then the selection does NOT move along with the focus.

The solution is a dinky little behavior that walks up the Visual tree from the TextBox when it gets focus and selects the parent ListBoxItem.

namespace MyProject.Behaviors

{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;

///
/// Behaviour that allows a control to cause a parent ListBox to become focused.
///

public sealed class ListBoxItemFocusBehaviour : Behavior<Control>
{
///
/// Called after the behaviour is attached to an AssociatedObject.
///

/// Override this to hook up functionality to the AssociatedObject.
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.GotFocus += OnControlFocused;
}

///
/// Called when the behaviour is being detached from its AssociatedObject, but before it has actually occurred.
///

/// Override this to unhook functionality from the AssociatedObject.
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.GotFocus -= OnControlFocused;
}

///
/// Called when [control focused].
///

/// "sender">The sender.
/// "e">The "System.Windows.RoutedEventArgs"/> instance containing the event data.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "By design - p is re-used for multiple control types.")]
private static void OnControlFocused(object sender, RoutedEventArgs e)
{
var control = sender as Control;
DependencyObject p = control;
while (p != null && !(p is ListBoxItem))
{
p = VisualTreeHelper.GetParent(p);
}

if (p == null)
{
return;
}

((ListBoxItem)p).IsSelected = true;
}
}
}

Alll you have to do is attach this behavior to your focusable control and when it receives focus, its associated ListBoxItem gets selected.
                <TextBox x:Name="MyTextBox>

<Interactivity:Interaction.Behaviors>
<Behaviours:ListBoxItemFocusBehaviour />
<Interactivity:Interaction.Behaviors>
TextBox>
Not forgetting of course to register the appropriate namespaces and reference the System.Windows.Interactivity assembly from the Blend SDK.
xmlns:Behaviours="clr-namespace:MyProject.Behaviors;assembly=MyProject" 
xmlns:Interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Nothing too it!

No comments: