简体   繁体   English

单击内部的按钮时,如何不关闭xtk:SplitButton的自定义弹出窗口?

[英]How to not close a xtk:SplitButton's custom Popup when a click is made on a button inside it?

I have a custom control with an override for OnApplyTemplate. 我有一个自定义控件,其中包含OnApplyTemplate的重写。 In it I tried to access children of children templates but they seem to not be loaded. 在其中,我尝试访问子模板的子模板,但似乎未加载它们。 What I wish: when the PART_IncreaseButton inside the Popup of a xtk:SplitButton is clicked, the Popup will not close but just let the Button react to the click. 我想什么:当该PART_IncreaseButton内侧Popup一个的xtk:SplitButton被点击, Popup不会接近,但只是让Button反应的点击。

CustomIntegerUpDown and CustomSplitButton are derived from the Xceed Extended WPF Toolkit. CustomIntegerUpDownCustomSplitButton源自Xceed Extended WPF Toolkit。 CustomIntegerUpDown has no changed style or template or code-behind and currently its only purpose is to do what I said above but I am just at the beginning of it. CustomIntegerUpDown的样式,模板或代码均未更改,目前,其唯一目的是执行我在上面所说的内容,但我只是刚开始。 Below is all the relevant source. 以下是所有相关来源。

I tried this: 我尝试了这个:

IncrementButton = Utils.FindChild<RepeatButton>(PartPopup, "PART_IncreaseButton")

and IncrementButton is null after that, although in the Immediate Window: 之后,IncrementButton为null,尽管在立即窗口中:

Utils.FindChild<Popup>(this, "PART_Popup") returns the Popup as obtained from GetTemplateChild("PART_Popup") . Utils.FindChild<Popup>(this, "PART_Popup")返回从GetTemplateChild("PART_Popup")获得的Popup GetTemplateChild("PART_Popup")

then 然后

Utils.FindChild<ButtonSpinner>(PartPopup, "PART_Spinner") returns null . Utils.FindChild<ButtonSpinner>(PartPopup, "PART_Spinner")返回null

Utils.FindChild<CustomIntegerUpDown>(PartPopup, "MyCustomIntegerUpDown") return null . Utils.FindChild<CustomIntegerUpDown>(PartPopup, "MyCustomIntegerUpDown")返回null

VisualTreeHelper.GetChildrenCount(PartPopup) returns 0 . VisualTreeHelper.GetChildrenCount(PartPopup)返回0

PartPopup.ApplyTemplate() returns false . PartPopup.ApplyTemplate()返回false

I've also seen this and I am not sure if it is worth trying that way. 我也看到了这一点 ,但不确定是否值得尝试这种方式。

FindChild is this (took from here ): FindChild是这个(从这里获取 ):

/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(System.Windows.DependencyObject parent, string childName)
    where T : System.Windows.DependencyObject
{
    // Confirm parent and childName are valid.
    if (parent == null) return null;
    T foundChild = null;
    int childrenCount = System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < childrenCount; i++)
    {
        var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i);
        // If the child is not of the request child type child
        T childType = child as T;
        if (childType == null)
        {
            // recursively drill down the tree
            foundChild = FindChild<T>(child, childName);
            // If the child is found, break so we do not overwrite the found child.
            if (foundChild != null) break;
        }
        else if (!string.IsNullOrEmpty(childName))
        {
            var frameworkElement = child as System.Windows.FrameworkElement;
            // If the child's name is set for search
            if (frameworkElement != null && frameworkElement.Name == childName)
            {
                // if the child's name is of the request name
                foundChild = (T)child;
                break;
            }
        }
        else
        {
            // child element found.
            foundChild = (T)child;
            break;
        }
    }
    return foundChild;
}

In CustomSplitButton.xaml.cs I have this: CustomSplitButton.xaml.cs中,我具有以下内容:

internal Popup PartPopup;
internal Button PartButtonWith1, PartButtonWith5, PartButtonWith10, PartButtonWithCustom;
internal RepeatButton IncrementButton;
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    PartPopup = (Popup)GetTemplateChild("PART_Popup");
    PartButtonWith1 = (Button)GetTemplateChild("PART_ButtonWith1");
    PartButtonWith5 = (Button)GetTemplateChild("PART_ButtonWith5");
    PartButtonWith10 = (Button)GetTemplateChild("PART_ButtonWith10");
    PartButtonWithCustom = (Button)GetTemplateChild("PART_ButtonWithCustom");
    PartPopup.ApplyTemplate();
    IncrementButton = Utils.FindChild<RepeatButton>(PartPopup, "PART_IncreaseButton");
    if (PartPopup != null)
    {
        PartPopup.PreviewMouseDown += PART_Popup_PreviewMouseUp;
        PartPopup.PreviewMouseUp += PART_Popup_PreviewMouseUp;
    }
    if (PartButtonWith1 != null)
    {
        PartButtonWith1.Click += Btns_NewTimer_Click;
    }
    if (PartButtonWith5 != null)
    {
        PartButtonWith5.Click += Btns_NewTimer_Click;
    }
    if (PartButtonWith10 != null)
    {
        PartButtonWith10.Click += Btns_NewTimer_Click;
    }
    if (PartButtonWithCustom != null)
    {
        PartButtonWithCustom.Click += BtnCustom_Click;
    }
}

The visual tree is this: 视觉树是这样的:

实时视觉树的屏幕截图

The style of CustomSplitButton is the following ( xmlns:xtkThemes="clr-namespace:Xceed.Wpf.Toolkit.Themes;assembly=Xceed.Wpf.Toolkit" ): CustomSplitButton的样式如下( xmlns:xtkThemes="clr-namespace:Xceed.Wpf.Toolkit.Themes;assembly=Xceed.Wpf.Toolkit" ):

<Style x:Key="AddCountSplitButtonStyle" TargetType="{x:Type xtk:SplitButton}">
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Background" Value="{DynamicResource {ComponentResourceKey ResourceId=ButtonNormalBackgroundKey, TypeInTargetAssembly={x:Type xtkThemes:ResourceKeys}}}"/>
    <Setter Property="BorderBrush" Value="{DynamicResource {ComponentResourceKey ResourceId=ButtonNormalOuterBorderKey, TypeInTargetAssembly={x:Type xtkThemes:ResourceKeys}}}"/>
    <Setter Property="DropDownContentBackground">
        <Setter.Value>
            <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                <GradientStop Color="#FFF0F0F0" Offset="0"/>
                <GradientStop Color="#FFE5E5E5" Offset="1"/>
            </LinearGradientBrush>
        </Setter.Value>
    </Setter>
    <Setter Property="Padding" Value="3"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type xtk:SplitButton}">
                <Grid x:Name="MainGrid" SnapsToDevicePixels="True">
                    <xtk:ButtonChrome x:Name="ControlChrome" BorderThickness="0" Background="{TemplateBinding Background}" RenderEnabled="{TemplateBinding IsEnabled}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <Button x:Name="PART_ActionButton" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="0" Padding="{TemplateBinding Padding}" Style="{x:Null}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
                                <Button.Template>
                                    <ControlTemplate TargetType="{x:Type Button}">
                                        <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}"/>
                                    </ControlTemplate>
                                </Button.Template>
                                <Grid>
                                    <xtk:ButtonChrome x:Name="ActionButtonChrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" RenderMouseOver="{Binding IsMouseOver, ElementName=PART_ActionButton}" RenderPressed="{Binding IsPressed, ElementName=PART_ActionButton}" RenderEnabled="{TemplateBinding IsEnabled}">
                                        <xtk:ButtonChrome.BorderThickness>
                                            <Binding ConverterParameter="2" Path="BorderThickness" RelativeSource="{RelativeSource TemplatedParent}">
                                                <Binding.Converter>
                                                    <xtk:ThicknessSideRemovalConverter/>
                                                </Binding.Converter>
                                            </Binding>
                                        </xtk:ButtonChrome.BorderThickness>
                                        <ContentPresenter x:Name="ActionButtonContent" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                                    </xtk:ButtonChrome>
                                </Grid>
                            </Button>
                            <ToggleButton x:Name="PART_ToggleButton" Grid.Column="1" IsChecked="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
                                <ToggleButton.IsHitTestVisible>
                                    <Binding Path="IsOpen" RelativeSource="{RelativeSource TemplatedParent}">
                                        <Binding.Converter>
                                            <xtk:InverseBoolConverter/>
                                        </Binding.Converter>
                                    </Binding>
                                </ToggleButton.IsHitTestVisible>
                                <ToggleButton.Template>
                                    <ControlTemplate TargetType="{x:Type ToggleButton}">
                                        <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}"/>
                                    </ControlTemplate>
                                </ToggleButton.Template>
                                <Grid>
                                    <xtk:ButtonChrome x:Name="ToggleButtonChrome" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1,0" RenderMouseOver="{Binding IsMouseOver, ElementName=PART_ToggleButton}" RenderPressed="{Binding IsPressed, ElementName=PART_ToggleButton}" RenderChecked="{TemplateBinding IsOpen}" RenderEnabled="{TemplateBinding IsEnabled}">
                                        <Grid x:Name="arrowGlyph" IsHitTestVisible="False" Margin="4,3">
                                            <Path x:Name="Arrow" Data="M0,0L3,0 4.5,1.5 6,0 9,0 4.5,4.5z" Fill="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" Height="5" Margin="0,1,0,0" Width="9"/>
                                        </Grid>
                                    </xtk:ButtonChrome>
                                </Grid>
                            </ToggleButton>
                        </Grid>
                    </xtk:ButtonChrome>
                    <Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" HorizontalOffset="1" IsOpen="{Binding IsChecked, ElementName=PART_ToggleButton}" Placement="{TemplateBinding DropDownPosition}" VerticalOffset="1"
                                StaysOpen="False">

                        <Border BorderThickness="{DynamicResource DefaultBorderThickness}" Margin="10,0,10,10" Background="{DynamicResource DarkerBaseBrush}" BorderBrush="{DynamicResource PopupBorderBrush}" CornerRadius="{DynamicResource DefaultCornerRadius}">
                            <Grid MinWidth="100" Name="PART_ContentPresenter">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Button x:Name="PART_ButtonWith1" Grid.Row="0" Grid.ColumnSpan="2">
                                    1
                                </Button>
                                <Button x:Name="PART_ButtonWith5" Grid.Row="1" Grid.ColumnSpan="2">
                                    5
                                </Button>
                                <Button x:Name="PART_ButtonWith10" Grid.Row="2" Grid.ColumnSpan="2">
                                    10
                                </Button>
                                <local:CustomIntegerUpDown Grid.Row="3" Value="1"
                                                            Increment="1" ClipValueToMinMax="True"              
                                                            x:Name="MyCustomIntegerUpDown">
                                </local:CustomIntegerUpDown>
                                <Button x:Name="PART_ButtonWithCustom" Grid.Row="3" Grid.Column="1" Padding="2,2,2,2">
                                    &gt;
                                </Button>
                            </Grid>
                            <Border.Effect>
                                <DropShadowEffect ShadowDepth="0" BlurRadius="10" Color="{DynamicResource Base6Color}" />
                            </Border.Effect>
                        </Border>
                    </Popup>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Fill" TargetName="Arrow" Value="#FFAFAFAF"/>
                        <Setter Property="Foreground" TargetName="ActionButtonChrome" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

In the OnApplyTemplate I would expect to be able to access children of templates inside templates inside this . 我希望在OnApplyTemplate中能够访问this内部模板内的模板子级。 But I did not find a way to do this. 但是我没有找到一种方法。

Related question of mine here . 我的相关问题在这里

Update 1 更新1

The starting point of the example, updated (it uses the TryFindVisualChildElementByName extension method from BionicCode's answer): 示例的起点已更新(它使用了BionicCode的答案中的TryFindVisualChildElementByName扩展方法):

internal Popup PartPopup;
internal Button PartButtonWith1, PartButtonWith5, PartButtonWith10, PartButtonWithCustom;
internal RepeatButton IncrementButton;

private void SplitButton_Loaded(object sender, RoutedEventArgs e)
{
    PartPopup = (Popup)GetTemplateChild("PART_Popup");
    PartButtonWith1 = (Button)GetTemplateChild("PART_ButtonWith1");
    PartButtonWith5 = (Button)GetTemplateChild("PART_ButtonWith5");
    PartButtonWith10 = (Button)GetTemplateChild("PART_ButtonWith10");
    PartButtonWithCustom = (Button)GetTemplateChild("PART_ButtonWithCustom");

    if (PartPopup != null)
    {
        PartPopup.ApplyTemplateRecursively();

        if (PartPopup.TryFindVisualChildElementByName("PART_IncreaseButton", out FrameworkElement incButton))
        {
            IncrementButton = (RepeatButton)incButton;

            // do something with IncrementButton here
        }

        PartPopup.PreviewMouseDown += PART_Popup_PreviewMouseUp;
        PartPopup.PreviewMouseUp += PART_Popup_PreviewMouseUp;
    }

    if (PartButtonWith1 != null)
    {
        PartButtonWith1.Click += Btns_NewTimer_Click;
    }
    if (PartButtonWith5 != null)
    {
        PartButtonWith5.Click += Btns_NewTimer_Click;
    }
    if (PartButtonWith10 != null)
    {
        PartButtonWith10.Click += Btns_NewTimer_Click;
    }
    if (PartButtonWithCustom != null)
    {
        PartButtonWithCustom.Click += BtnCustom_Click;
    }
}

The ApplyTemplateRecursively extension method used above, in 2 versions: 上面使用的ApplyTemplateRecursively扩展方法有2个版本:

Not working version 不起作用的版本

Is it possible to make this version work somehow? 是否可以使该版本以某种方式工作? I think it is more efficient. 我认为这样更有效。

/// <summary>
/// Not working because the ApplyTemplate affects the VisualTree and when applying
/// templates recursively it does not see the correct updated visual tree to be able
/// to continue.
/// </summary>
/// <param name="root"></param>
internal static void ApplyTemplateRecursively(this System.Windows.DependencyObject root)
{
    if (root is System.Windows.Controls.Primitives.Popup p)
    {
        p.Child.ApplyTemplateRecursively();
        return;
    }

    if (root is FrameworkElement r)
    {
        r.ApplyTemplate();
    }

    foreach (object element in System.Windows.LogicalTreeHelper.GetChildren(root))
    {
        if (element is System.Windows.DependencyObject el)
        {
            ApplyTemplateRecursively(el);
        }
    }
}

Working version 工作版本

/// <summary>
/// I am not sure if this is sufficiently efficient, because it goes through the entire visual tree.
/// </summary>
/// <param name="root"></param>
internal static void ApplyTemplateRecursively(this System.Windows.DependencyObject root)
{
    if (root is System.Windows.Controls.Primitives.Popup p)
    {
        p.Child.ApplyTemplateRecursively();
        return;
    }

    if (root is FrameworkElement r)
    {
        r.ApplyTemplate();
    }

    for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(root); ++i)
    {
        DependencyObject d = VisualTreeHelper.GetChild(root, i);
        ApplyTemplateRecursively(d);
    }
}

Now I am trying to solve the actual problem. 现在,我正在尝试解决实际问题。

Update 2 更新2

I have reported this issue . 我已经报告了这个问题

The point is that the content of a Popup is not directly part of the visual tree. 关键是Popup的内容不直接属于可视化树的一部分。 That's why looking for visual children of a Popup will always return null . 这就是为什么寻找Popup可视子代总是返回null The content of a Popup is rendered separately and is assigned to the Popup.Child property. Popup的内容是单独呈现的,并分配给Popup.Child属性。 You need to extract those from the Child property before continuing tree traversal inside the Popup . 您需要从Child属性中提取那些内容,然后继续在Popup内部进行树遍历。

The following is a custom visual tree helper method to return the first child element that matches a given name. 以下是自定义可视树帮助器方法,用于返回与给定名称匹配的第一个子元素。 This helper properly searches inside a Popup element. 该帮助器可以在Popup元素内正确搜索。 This method is an Extension Method of type DependencyObject and has to be put into a static class : 此方法是DependencyObject类型的扩展方法,必须放入static class

public static bool TryFindVisualChildElementByName(
  this DependencyObject parent,
  string childElementName,
  out FrameworkElement resultElement)
{
  resultElement = null;

  if (parent is Popup popup)
  {
    parent = popup.Child;
    if (parent == null)
    {
      return false;
    }
  }

  for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
  {
    DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);        

    if (childElement is FrameworkElement uiElement && uiElement.Name.Equals(
          childElementName,
          StringComparison.OrdinalIgnoreCase))
    {
      resultElement = uiElement;
      return true;
    }

    if (childElement.TryFindVisualChildElementByName(childElementName, out resultElement))
    {
      return true;
    }
  }

  return false;
}

It's an Extension Method and it is used like this: 这是一种扩展方法,其用法如下:

CustomSplitButton.xaml.cs CustomSplitButton.xaml.cs

// Constructor
public CustomSplitButton()
{
  this.Loaded += GetParts;
}

private void GetParts(object sender, RoutedEventArgs e)
{
  if (this.TryFindVisualChildElementByName("PART_Popup", out FrameworkElement popupPart))
  {
    if (popupPart.TryFindVisualChildElementByName("PART_ContentPresenter", out FrameworkElement contentPresenter))
    {
      if (!contentPresenter.IsLoaded)
      {
        contentPresenter.Loaded += CompleteSearch;
      }
      else 
      {
        CompleteSearch(contentPresenter, null);
      }
    }
  }
}

private void CompleteSearch(object sender, RoutedEventArgs e)
{      
  contentPresenter.Loaded -= CompleteSearch;

  if ((sender as DependencyObject).TryFindVisualChildElementByName("PART_IncreaseButton", out FrameworkElement increaseButton))
  {        
    IncrementButton = (RepeatButton) increaseButton;
  }
}

Remarks 备注

It is very important to search after a parent element was Loaded . Loaded父元素之后进行搜索非常重要。

This is true for all elements in the visual tree. 对于可视树中的所有元素都是如此。 Since the SplitButton consists of a drop down that is collapsed by default, not all contents are loaded initially. 由于SplitButton包含一个默认情况下折叠的下拉菜单,因此并非所有内容最初都会加载。 Once the drop down is opened the SplitButton makes its content visible, which will add them to the visual tree. 打开下拉菜单后, SplitButton使其内容可见,这会将它们添加到可视树中。 Up to this point the SplitButton.IsLoaded property will return false , indicating the button's incomplete visual state. 到目前为止, SplitButton.IsLoaded属性将返回false ,指示按钮的不完整视觉状态。 What you need to do is, once you encountered a FrameworkElement where FrameworkElement.IsLoaded returns false you have to subscribe to the FrameworkElement.Loaded event. 您需要做的是,一旦遇到FrameworkElement.IsLoaded返回falseFrameworkElement ,则必须订阅FrameworkElement.Loaded事件。 In this handler you can continue the visual tree traversal. 在此处理程序中,您可以继续可视树遍历。
Popup like elements or collapsed controls add complexity to the visual tree traversal. Popup式元素或折叠控件会增加可视化树遍历的复杂性。


Edit: Keep Popup open when content was clicked 编辑:单击内容后,保持Popup窗口打开

Now that you have told me that you are using the SplitButton inside a ToolBar I instantly knew the origin of your problem: 既然您已经告诉我您正在使用ToolBar SplitButton ,那么我立即知道问题的根源:

Classes in WPF which are focus scopes by default are Window , MenuItem , ToolBar , and ContextMenu . WPF中默认为焦点范围的类是WindowMenuItemToolBarContextMenu [Microsoft Docs: Logical Focus ] [Microsoft Docs: 逻辑焦点 ]

Simply remove the focus scope from the ToolBar to prevent the focus from being removed from the Popup as soon as any of its content was clicked (received logical focus): 只需从ToolBar上删除焦点范围,以防止在单击其任何内容时将焦点从Popup窗口中删除(收到逻辑焦点):

<ToolBar FocusManager.IsFocusScope="False"> 
  <CustomSplitButton />
</ToolBar>

Edit: Keep Popup open when clicked on PART_ToggleButton while the Popup is open 编辑:请Popup开放上PART_ToggleButton当点击而Popup是开放的

To prevent the Popup from closing and reopening when the PART_ToggleButton is clicked while the Popup is open you need to handle the mouse down event (application wide) and the opening of the Popup yourself. 为了防止在Popup 窗口打开时单击PART_ToggleButtonPopup窗口关闭并重新打开,您需要自行处理鼠标按下事件(应用程序范围)和打开Popup窗口。

First modify the PART_Popup to make it stay open and remove the binding from the IsOpen property: 首先修改PART_Popup使其保持打开状态,并从IsOpen属性中删除绑定:

CustomSplitButton.xaml CustomSplitButton.xaml

<Popup x:Name="PART_Popup"
       IsOpen="False"
       StaysOpen="True"
       AllowsTransparency="True"
       Focusable="False"
       HorizontalOffset="1"
       Placement="{TemplateBinding DropDownPosition}"
       VerticalOffset="1">

Then in your CustomSplitButton observe the mouse device for mouse down events and determine the hit target. 然后在CustomSplitButton观察鼠标设备的鼠标按下事件并确定命中目标。 I assume that you retrieved the underlying PART_Popup and PART_ToggleButton element and stored it in a property named PartPopup and PartToggleButton (see first part of this answer on how to do it): 我假设您检索了基础PART_PopupPART_ToggleButton元素,并将其存储在名为PartPopupPartToggleButton的属性中(有关此操作的更多信息,请参见本答案的第一部分):

CustomSplitButton.xaml.cs CustomSplitButton.xaml.cs

public CustomSplitButton()
{
  this.Loaded += OnLoaded;
}

private void OnLoaded(object sender, RoutedEventArgs e)
{
  Mouse.AddPreviewMouseDownHandler(Application.Current.MainWindow, KeepPopupOpen);
}

private void KeepPopupOpen(object sender, RoutedEventArgs routedEventArgs)
{
  var mouseClickSourceElement = routedEventArgs.OriginalSource as DependencyObject;
  var isPopupContentClicked = false;
  var isPartToggleButtonClicked = 
    object.ReferenceEquals(routedEventArgs.Source, this) 
      && mouseClickSourceElement.TryFindVisualParentElement(out ButtonBase button) 
      && button.Name.Equals(this.PartToggleButton.Name, StringComparison.OrdinalIgnoreCase);

  if (!isPartToggleButtonClicked)
  {
    isPopupContentClicked = 
      object.ReferenceEquals(routedEventArgs.Source, this) 
        && mouseClickSourceElement.TryFindVisualParentElementByName("PART_ContentPresenter", out FrameworkElement popupContentPresenter));
  }

  this.PartPopup.IsOpen = this.IsOpen = isPartToggleButtonClicked || isPopupContentClicked ;
}

Extension Methods to find the visual parent by type and by name 通过类型和名称查找可视父项的扩展方法

public static class HelperExtensions
{
  public static bool TryFindVisualParentElement<TParent>(this DependencyObject child, out TParent resultElement)
    where TParent : DependencyObject
  {
    resultElement = null;

    if (child == null)
    {
      return false;
    }

    DependencyObject parentElement = VisualTreeHelper.GetParent(child);

    if (parentElement is TParent parent)
    {
      resultElement = parent;
      return true;
    }

    return parentElement.TryFindVisualParentElement(out resultElement);
  }

  public static bool TryFindVisualParentElementByName(
      this DependencyObject child,
      string elementName,
      out FrameworkElement resultElement)
    {
      resultElement = null;

      if (child == null)
      {
        return false;
      }

      DependencyObject parentElement = VisualTreeHelper.GetParent(child);

      if (parentElement is FrameworkElement frameworkElement &&
          frameworkElement.Name.Equals(elementName, StringComparison.OrdinalIgnoreCase))
      {
        resultElement = frameworkElement;
        return true;
      }

      return parentElement.TryFindVisualParentElementByName(elementName, out resultElement);
    }
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM