首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何不关闭xtk:SplitButton的自定义弹出时,单击其中的一个按钮?

如何不关闭xtk:SplitButton的自定义弹出时,单击其中的一个按钮?
EN

Stack Overflow用户
提问于 2019-09-08 13:00:52
回答 1查看 159关注 0票数 1

我有一个带有OnApplyTemplate覆盖的自定义控件。在它中,我试图访问子模板的子模板,但它们似乎没有加载。我想要的是:当单击Popup of a xtk:SplitButton中的Popup时,Popup不会关闭,而只是让Button对单击做出反应。

CustomIntegerUpDownCustomSplitButton是从Xceed扩展的WPF工具包中派生的。CustomIntegerUpDown没有改变样式、模板或代码隐藏,目前它的唯一目的是执行我前面说过的话,但我只是刚刚开始。以下是所有相关的来源。

我试过这个:

代码语言:javascript
运行
复制
IncrementButton = Utils.FindChild<RepeatButton>(PartPopup, "PART_IncreaseButton")

在此之后,IncrementButton是空的,尽管在直接窗口中:

Utils.FindChild<Popup>(this, "PART_Popup")返回从GetTemplateChild("PART_Popup")获得的Popup

然后

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

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

VisualTreeHelper.GetChildrenCount(PartPopup)返回0

PartPopup.ApplyTemplate()返回false

我也见过,我不确定这样做是否值得。

FindChild是这样的(摘自这里):

代码语言:javascript
运行
复制
/// <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;
}

CustomSplitButton.xaml.cs中,我有以下内容:

代码语言:javascript
运行
复制
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;
    }
}

可视树如下:

CustomSplitButton的样式如下(xmlns:xtkThemes="clr-namespace:Xceed.Wpf.Toolkit.Themes;assembly=Xceed.Wpf.Toolkit"):

代码语言:javascript
运行
复制
<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>

OnApplyTemplate中,我希望能够访问this中模板中的子模板。但我没有办法做到这一点。

矿山这里的相关问题。

更新1

示例的起点是更新(它使用了BionicCode的答案中的TryFindVisualChildElementByName扩展方法):

代码语言:javascript
运行
复制
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;
    }
}

上面使用的ApplyTemplateRecursively扩展方法有两个版本:

不工作版本

有可能使这个版本以某种方式工作吗?我认为它更有效率。

代码语言:javascript
运行
复制
/// <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);
        }
    }
}

工作版本

代码语言:javascript
运行
复制
/// <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);
    }
}

现在我正在努力解决实际问题。

更新2

我已经报告了本期

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-09-08 18:47:03

关键是Popup的内容不是视觉树的直接部分。这就是为什么寻找Popup的可视子程序总是会返回nullPopup的内容是单独呈现的,并分配给Popup.Child属性。在继续遍历Child内部的树之前,需要从Popup属性中提取这些数据。

下面是自定义的可视化树帮助器方法,用于返回与给定名称匹配的第一个子元素。此助手正确地在Popup元素中搜索。此方法是DependencyObject类型的扩展方法,必须放入static class中。

代码语言:javascript
运行
复制
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;
}

这是一种扩展方法,它的使用方式如下:

CustomSplitButton.xaml.cs

代码语言:javascript
运行
复制
// 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;
  }
}

备注

搜索父元素Loaded非常重要。

这对于可视化树中的所有元素都是正确的。由于SplitButton是由默认折叠的下拉列表组成的,所以并不是所有的内容都是最初加载的。一旦打开下拉列表,SplitButton就会使其内容可见,这将将其添加到可视树中。到目前为止,SplitButton.IsLoaded属性将返回false,指示按钮的不完全可视状态。您需要做的是,一旦遇到FrameworkElementFrameworkElement.IsLoaded返回false,您就必须订阅FrameworkElement.Loaded事件。在此处理程序中,可以继续可视树遍历。

类似于Popup的元素或折叠控件增加了可视树遍历的复杂性。

编辑:单击内容时保持Popup打开

既然您已经告诉我,您正在ToolBar中使用ToolBar,我马上就知道了问题的根源:

默认情况下,WPF中属于焦点作用域的类是WindowMenuItemToolBarContextMenu。[Microsoft:逻辑焦点]

只需从ToolBar中删除焦点范围,以防止焦点在点击任何内容(接收到逻辑焦点)后立即从Popup中移除:

代码语言:javascript
运行
复制
<ToolBar FocusManager.IsFocusScope="False"> 
  <CustomSplitButton />
</ToolBar>

编辑:当Popup打开时,在PART_ToggleButton上单击时保持Popup打开

为了防止Popup在PART_ToggleButton打开时关闭和重新打开,您需要自己处理鼠标向下事件(应用程序范围)和弹出窗口的打开。

首先修改PART_Popup,使其保持打开状态,并从IsOpen属性中删除绑定:

CustomSplitButton.xaml

代码语言:javascript
运行
复制
<Popup x:Name="PART_Popup"
       IsOpen="False"
       StaysOpen="True"
       AllowsTransparency="True"
       Focusable="False"
       HorizontalOffset="1"
       Placement="{TemplateBinding DropDownPosition}"
       VerticalOffset="1">

然后在您的CustomSplitButton中,观察鼠标设备中的鼠标向下事件,并确定命中目标。我假设您检索了底层的PART_Popup和PART_ToggleButton元素,并将其存储在一个名为PartPopupPartToggleButton的属性中(请参阅这个答案的第一部分关于如何实现它):

CustomSplitButton.xaml.cs

代码语言:javascript
运行
复制
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 ;
}

扩展方法按类型和名称查找可视父级

代码语言:javascript
运行
复制
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);
    }
  }
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57842168

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档