收录日期:2020/06/02 02:34:10 时间:2009-08-07 16:33:13 标签:wpf,xaml,treeview,popup

When using a Popup inside a TreeView in WPF I am running into an issue where the controls inside the popup become unusable. For example, using the following code the ToggleButton inside the popup can only be toggled once and then becomes unable to be toggled back. Is there any way to work around this issue?

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <TreeView>
            <TreeViewItem>
                <TreeViewItem.Header>
                    <StackPanel>
                        <ToggleButton>
                            Button outside popup
                        </ToggleButton>
                        <Popup IsOpen="True">
                            <ToggleButton>
                                Button inside popup
                            </ToggleButton>
                        </Popup>
                    </StackPanel>
                </TreeViewItem.Header>
            </TreeViewItem>
        </TreeView>
    </Grid>   
</Page>

The problem is that you are embedding the Popup inside the TreeViewItem, and somehow that is interfering with its built-in functionality. I don't think Popups are meant to be used that way, anyway. Because you can specify the PlacementTarget of a Popup, you can declare them anywhere and then open them at the location you want. The following mark-up demonstrates this with your example:

<StackPanel>
  <Popup IsOpen="True" PlacementTarget="{Binding ElementName=buttonPanel}"
         Placement="Bottom">
    <ToggleButton>
      Button inside popup
    </ToggleButton>
  </Popup>
  <TreeView>
    <TreeViewItem>
      <TreeViewItem.Header>
        <StackPanel Name="buttonPanel">
          <ToggleButton>
            Button outside popup
          </ToggleButton>
        </StackPanel>
      </TreeViewItem.Header>
    </TreeViewItem>
  </TreeView>
</StackPanel>

As you can see, the Popup can be declared anywhere. You can then use PlacementTarget to put it right where you want. With this change, the ToggleButton works as expected.

Thanks to Charlie finding the issue with using TreeView...

Setting Focusable on the tree view to false also seems to work. But may cause you other issues.

<TreeView Focusable="False">

From what I can tell the issue is which control is taking focus, so it looks like your treeview is taking focus of the mouse click instead of the button.

After discussion with the WPF team I found out that is this is just an issue with WPF.

For my case I was trying to create a drop dialog that is used for each TreeViewItem. I played around with different controls trying to get the looked I needed and I discovered that the Popup portion of a ComboBox worked fine inside a TreeView.

Because of this I ended up using a ComboBox inside the TreeView and forcing the ComboBox to only have one item that is always selected and that item would be my drop dialog. After this I just needed to style the ComboBox to match the look I needed. The code below shows the general idea of what I did.

<TreeView>
    <TreeViewItem>
        <ComboBox HorizontalAlignment="Left" VerticalAlignment="Top">
            <ComboBox.Resources>
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
            </ComboBox.Resources>
            <ComboBoxItem IsSelected="True">
                <StackPanel Orientation="Vertical">
                    <Label>Some stuff on the combobox popup</Label>
                    <RadioButton>Radio Button 1</RadioButton>
                    <RadioButton>Radio Button 2</RadioButton>
                    <CheckBox>Check Box</CheckBox>
                    <Button HorizontalAlignment="Right">Ok</Button>
                </StackPanel>
            </ComboBoxItem>
        </ComboBox>
    </TreeViewItem>
</TreeView>

Well I had the same problem where a treeview shows hierarchical items using data template and multiple user control and one of the user control is a toggle button with a pop up in it.

This would freeze totally and would not respond at all. The problem is related to Focus property but setting Focusable to false does not always work.

By looking at the solutions provided above I liked the last one that uses combo box inside a treeviewitem. But it lead to one more problem. Whenever user select the (single) item of the combobox, it would appear in the combobox as a selectedValue and I just want to behave like a toggle with pop up.

Also I wanted to change the triangle on the combobox to ellipsis. So I tried this solution by changing the control template.

Here is my solution:

... ...

    <Style x:Key="ComboBoxStyle1" TargetType="{x:Type ComboBox}">
        <Setter Property="FocusVisualStyle" Value="{StaticResource ComboBoxFocusVisual}"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
        <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
        <Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
        <Setter Property="Padding" Value="4,3"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ComboBox}">
                    <Grid x:Name="MainGrid" SnapsToDevicePixels="true">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Popup x:Name="PART_Popup" Margin="1" 
                               AllowsTransparency="true" 
                               IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" 
                               Placement="Bottom" 
                               PopupAnimation="Fade">
                            <Microsoft_Windows_Themes:SystemDropShadowChrome x:Name="Shdw" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=MainGrid}" Color="Transparent">
                                <Border x:Name="DropDownBorder" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="2" CornerRadius="0,4,4,4">
                                    <ScrollViewer CanContentScroll="true">
                                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.DirectionalNavigation="Contained"/>
                                    </ScrollViewer>
                                </Border>
                            </Microsoft_Windows_Themes:SystemDropShadowChrome>
                        </Popup>
                        <ToggleButton Style="{StaticResource ComboBoxReadonlyToggleButton}" 
                                      Background="{TemplateBinding Background}" 
                                      BorderBrush="{TemplateBinding BorderBrush}" 
                                      IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
                            <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>
                            <Setter Property="Color" TargetName="Shdw" Value="#71000000"/>
                        </Trigger>
                        <Trigger Property="HasItems" Value="false">
                            <Setter Property="Height" TargetName="DropDownBorder" Value="95"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            <Setter Property="Background" Value="#FFF4F4F4"/>
                        </Trigger>
                        <Trigger Property="IsGrouping" Value="true">
                            <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsEditable" Value="true">
                <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
                <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
                <Setter Property="IsTabStop" Value="false"/>
                <Setter Property="Padding" Value="3"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="ComboBoxItemStyle1" TargetType="{x:Type ComboBoxItem}">
        <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="Padding" Value="3,0,3,0"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ComboBoxItem}">
                    <Border x:Name="Bd" SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsHighlighted" Value="true">
                            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<TreeView >
    <TreeViewItem IsExpanded="True">
        <ComboBox HorizontalAlignment="Left" VerticalAlignment="Top"
                  IsEditable="False"
                  Style="{StaticResource ComboBoxStyle1}">
            <ComboBox.Resources>
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
            </ComboBox.Resources>
            <ComboBoxItem IsSelected="False">
                <StackPanel Orientation="Vertical">
                    <Label>Some stuff on the combobox popup</Label>
                    <RadioButton>Radio Button 1</RadioButton>
                    <RadioButton>Radio Button 2</RadioButton>
                    <CheckBox>Check Box</CheckBox>
                    <Button HorizontalAlignment="Right">Ok</Button>
                </StackPanel>
            </ComboBoxItem>
        </ComboBox>
    </TreeViewItem>
</TreeView>