tags:

views:

376

answers:

2

I have been having problems with this for some time now, and have come up with some less-than-desirable solutions. The problem is that when a TreeViewItem's context menu is opened, the TreeViewItem is greyed out. Is it possible for a TreeViewItem to stay highlighted while its ContextMenu is open?

The problem with the TreeViewItem greying out, is that it gives no relation to the context menu and the TreeViewItem, and it looks ugly.

Generally, the code I use for setting a context menu is this. Sometimes the context menu will be generated by the code with a PreviewRightMouseButtonDown EventSetter, but it doesn't make a difference:

    <TreeView>
        <TreeView.Resources>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu>
                            <MenuItem Header="Menu Item 1" />
                            <MenuItem Header="Menu Item 2" />
                        </ContextMenu>
                    </Setter.Value>
                </Setter>
            </Style>
        </TreeView.Resources>
        <TreeViewItem Header="Item 1">
            <TreeViewItem Header="Sub-Item 1"/>
        </TreeViewItem>
        <TreeViewItem Header="Item 2"></TreeViewItem>
    </TreeView>

So far the only solution I have found is to override the "grey" unfocused color with the focused color, but then the TreeView never seems unfocused, such as when another control is clicked on. I have had problems with ListViews as well.

+1  A: 

WPF's default behavior is to change the TreeViewItem to gray when the ContextMenu opens, but like virtually everything else in WPF you can override this:

  1. Create an attached property ContextMenuOpened
  2. In the TreeViewItem Style, bind ContextMenuOpened to "ContextMenu.IsOpen"
  3. Add a trigger that changes the brush when ContextMenuOpened and IsSelected are both true

Here's the attached property:

public class TreeViewCustomizer : DependencyObject
{
  public static bool GetContextMenuOpened(DependencyObject obj) { return (bool)obj.GetValue(ContextMenuOpenedProperty); }
  public static void SetContextMenuOpened(DependencyObject obj, bool value) { obj.SetValue(ContextMenuOpenedProperty, value); }
  public static readonly DependencyProperty ContextMenuOpenedProperty = DependencyProperty.RegisterAttached("ContextMenuOpened", typeof(bool), typeof(TreeViewCustomizer));
}

Here's the setter in the style:

<Setter Property="my:TreeViewCustomizer.ContextMenuOpened"
        Value="{Binding ContextMenu.IsOpen, RelativeSource={RelativeSource Self}}" />

Here's the trigger:

<MultiTrigger>
  <MultiTrigger.Conditions>
    <Condition Property="IsSelected" Value="true"/>
    <Condition Property="my:TreeViewCustomizer.ContextMenuOpened" Value="true"/>
  </MultiTrigger.Conditions>
  <Setter TargetName="Bd"
          Property="Background"
          Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
  <Setter Property="Foreground"
          Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</MultiTrigger>

How it works: Every time the ContextMenu opens its IsOpen property is set. The binding causes your attached property to be set on the TreeViewItem. This, combined with IsSelected, invokes the trigger which changes the foreground and background colors to make the item still appear selected.

Ray Burns
Awesome. Never would have thought of that myself. One thing though: The background isn't being set correctly, but the foreground is. I have a feeling it has something to do with the TargetName="Bd", which doesn't compile.
Snea
My answer assumes your custom TreeViewItem ControlTemplate is an edited copy of the default TreeViewItem ControlTemplate. "Bd" is the name used inside the default TreeViewItem ControlTemplate for the Border. If built a totaly custom ControlTemplate for TreeViewItem instead of copying the system one and editing it, you may not have a Border in your template at all or it may be named something different. In this case, you may need a different TargetName to set the background color on the item.
Ray Burns
I just realized I may be assuming too much: Perhaps you haven't created a custom ControlTemplate for TreeViewItem at all. In that case I don't see any way to affect the color of the Border inside the template other than replacing resources which, as you have noted, has a somewhat global scope. Therefore I think you will need to use a custom ControlTemplate. In Blend, just right-click the control and select Edit Template -> Edit A Copy from the context menu, then add my MultiTrigger to the bottom of the list of triggers in the template.
Ray Burns
Alright, hoped it wouldn't come to that, but it'll have to do (Unfortunately, I don't have Blend, but I found the default template earlier). Thanks again.
Snea
A: 

Hi, I need to change the selected color, but when I make the trigger I got the following error:

'IsSelected' cannot be set as the value of a Trigger's Property attribute because it does not have a public or internal get accessor.

Job Middendorp