This blog has, IMO, some great resources. Unfortunately, some of those resources are becoming less relevant. I'm still blogging, learning tech and helping others...please find me at my new home on http://www.jameschambers.com/.

Thursday, June 11, 2009

IsFocused Never True on ComboBox in WPF

I recently responded to a thread on MSDN forums where a person was trying to get something to happen when the IsFocused property of a ComboBox was set to true in WPF.

Because of some of the work I had previously done on retemplating my own ComboBox, I quickly recognized that this was an issue with the way the control is templated. Largely, what is going on is that the ComboBox has a complex visual tree and when the ComboBox is in edit mode the control that has focus is actually a textbox inside the control template.

Thus, the ComboBox in WPF will never have its IsFocused property set to true.

I realized, though, that this has an effect on other things, such as trying to template a control through styles and triggers without replacing the entire visual tree.

Take the following style for a WPF Button:

<Style TargetType="Button" x:Key="ButtonStyle">
<
Style.Triggers>
<
Trigger Property="IsEnabled" Value="True">
<
Setter Property="Effect">
<
Setter.Value>
<
DropShadowEffect/>
</
Setter.Value>
</
Setter>
</
Trigger>
</
Style.Triggers>
</
Style>


If you put that in the Windows.Resources section of your window and set up a button to use that style, you’ll see that the buttonimage immediately reflects the DropShadowEffect.  Change the above IsEnabled to IsFocused and run the app.  The DropShadowEffect is only rendered if the button has focus.



Now, try the same thing with a style for the ComboBox:



<Style TargetType="ComboBox" x:Key="ComboBoxStyle">
<
Style.Triggers>
<
Trigger Property="IsEnabled" Value="True">
<
Setter Property="Effect">
<
Setter.Value>
<
DropShadowEffect/>
</
Setter.Value>
</
Setter>
</
Trigger>
</
Style.Triggers>
</
Style>



image This, again, works off the hop, even with IsFocused.  But if you set IsEditable to true, things change. 



As you can see from the screenies to the right, we don’t get our drop shadow rendered imageeven when the control has focus when it’s in  edit mode.









I think the only solution is to work around this through code-behind in the class.  This means that you’ll either need to plop some code into each form where you want to bind to the IsFocused property of the ComboBox, or you’ll need to create your own control library (not a bad idea if you’re trying to do something like this, I suppose).



So, I tried to go down that route.  My first attempt, though close, isn’t what we are looking for.  Indeed, it looks kinda cool and imagesome might find it funky, but it doesn’t put the shadow where we’d like – namely, on the ComboBox itself.



Here’s the code:



this.comboBox1.Loaded += delegate
{
TextBox tb = (TextBox)comboBox1.Template.FindName("PART_EditableTextBox", comboBox1);
if (tb != null)
{
Style tbStyle = new Style(typeof(TextBox), tb.Style);
Trigger effectTrigger = new Trigger();
effectTrigger.Property = TextBox.IsFocusedProperty;
effectTrigger.Value = true;

Setter dsSetter = new Setter(TextBox.EffectProperty, new DropShadowEffect());

effectTrigger.Setters.Add(dsSetter);
tbStyle.Triggers.Add(effectTrigger);
tb.Style = tbStyle;

}
};











Perhaps a simpler approach would be just storing a couple of style references in the window (or the control, if you’re rewriting) as such:



Style _cboDropShadowStyle;
Style _cboCleanStyle;


We build up our styles in the ComboBox.Loaded delegate:



            this.comboBox1.Loaded += delegate
{
// store the clean style
_cboCleanStyle = new Style(typeof(ComboBox), comboBox1.Style);

// build and store the dropshadowed style
_cboDropShadowStyle = new Style(typeof(ComboBox), comboBox1.Style);
Trigger effectTrigger = new Trigger();
effectTrigger.Property = ComboBox.IsEnabledProperty;
effectTrigger.Value = true;

Setter dsSetter = new Setter(ComboBox.EffectProperty, new DropShadowEffect());

effectTrigger.Setters.Add(dsSetter);
_cboDropShadowStyle.Triggers.Add(effectTrigger);
};



Then, finally, we write our event handlers (be sure to mark the XAML up accordingly):



private void comboBox1_GotFocus(object sender, RoutedEventArgs e)
{
comboBox1.Style = _cboDropShadowStyle;
}

private void comboBox1_LostFocus(object sender, RoutedEventArgs e)
{
comboBox1.Style = _cboCleanStyle;
}

I will continue to ponder a cleaner way, but for now this should be an acceptable approach.  You could easily build this into a custom control.  Or, you could extend the TextBox sample above into the custom control and expose a DependencyProperty called IsSeriouslyFocusedForRealYo that you set when the focus on that PART_EditableTextBox changes.

4 comments:

  1. did u ever find cleaner way of doing this?
    Thanks,
    Rey.

    ReplyDelete
  2. Hey Rey,

    I haven't found anything cleaner yet, but I'm not sure there would be as it is clearly a bug with the control (unless an SP covers the hover problem). I logged a bug on the Connect web site for this a while ago.

    I also haven't had too much time in Visual Studio 2010 to be working in WPF; it may already be fixed there. I should have a chance in the next few days to try that out. It won't help you if you're targeting 3.5, of course, but at least it would be resolved for future projects.

    Cheers,
    -James

    ReplyDelete
  3. You could use the IsKeyboardFocusWithin property instead of IsFocused.

    ReplyDelete
  4. http://spunkyvt.wordpress.com/2008/11/05/change-background-of-combobox-using-a-trigger-if-iseditable/

    ReplyDelete