自定义弹出控件未关闭

问题描述 投票:0回答:3

我正在尝试创建一个类似于组合框的自定义下拉控件,这样当您单击鼠标向下(而不是向上)时,弹出窗口将打开,而当您单击控件外部时,弹出窗口将关闭。

问题是,只有当我将 ClickMode 设置为“Release”时,它才会起作用。但我真正想要的是 ClickMode="Press",这样弹出窗口会在 MouseDown 而不是 MouseUp 上打开。

但是当我将其设置为 ClickMode="Press" 时,当您在控件外部单击时,弹出窗口不会关闭。

我有什么想法可以实现这一目标吗?

用途:

          <StackPanel>
            <local:CustomDropdown Width="200"
                                  Height="50"
                                  Content="Custom!" />

            <ComboBox Width="200"
                      Margin="20"> 
                <ComboBoxItem>A</ComboBoxItem>
                <ComboBoxItem>B</ComboBoxItem>
                <ComboBoxItem>C</ComboBoxItem>
            </ComboBox>
        </StackPanel>

班级:

internal class CustomDropdown : ContentControl
{
    public bool IsOpen
    {
        get { return (bool)GetValue(IsOpenProperty); }
        set { SetValue(IsOpenProperty, value); }
    }

    public static readonly DependencyProperty IsOpenProperty =
        DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}

Xaml:

<Style TargetType="{x:Type local:CustomDropdown}">
            <Style.Setters>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <Grid>
                                <ToggleButton IsChecked="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" 
                                              ClickMode="Press"/>
                                <ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
                                                  HorizontalAlignment="Center"
                                                  VerticalAlignment="Center"/>
                                <Popup StaysOpen="False"
                                       Placement="Bottom"
                                       IsOpen="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
                                    <Border Background="White"
                                            BorderBrush="Black"
                                            BorderThickness="1"
                                            Padding="50">
                                        <TextBlock Text="Popup!" />
                                    </Border>
                                </Popup>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style.Setters>
        </Style>
wpf button popup controls
3个回答
1
投票

如果您希望它按预期与

ClickMode.Press
一起工作,则每当您想要关闭
IsOpen
时,都应该以编程方式将
false
属性设置为
Popup
。例如,每当您检测到
ToggleButton
之外的点击时。

例如,您可以处理控件中父窗口的

PreviewMouseLeftButtonDown
事件。像这样的东西:

internal class CustomDropdown : ContentControl
{
    private ToggleButton _toggleButton;

    public CustomDropdown()
    {
        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _toggleButton = GetTemplateChild("toggleButton") as ToggleButton;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        Window window = Window.GetWindow(this);
        window.PreviewMouseLeftButtonDown += OnWindowPreviewMouseLeftButtonDown;
    }

    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        Window window = Window.GetWindow(this);
        window.PreviewMouseLeftButtonDown -= OnWindowPreviewMouseLeftButtonDown;
    }

    private void OnWindowPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        ToggleButton toggleButton = FindParent<ToggleButton>(e.OriginalSource as DependencyObject);
        if (toggleButton != _toggleButton)
            IsOpen = false;
    }

    private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
    {
        var parent = VisualTreeHelper.GetParent(dependencyObject);

        if (parent == null)
            return null;

        var parentT = parent as T;
        return parentT ?? FindParent<T>(parent);
    }

    public bool IsOpen
    {
        get { return (bool)GetValue(IsOpenProperty); }
        set { SetValue(IsOpenProperty, value); }
    }

    public static readonly DependencyProperty IsOpenProperty =
        DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}

}

XAML:

<ControlTemplate>
    <Grid>
        <ToggleButton x:Name="toggleButton" ...

0
投票

您已经有了可行的答案。但是,找到父级

Window
和父级
ToggleButton
可能会影响性能(取决于可视化树的深度)。
作为替代解决方案,我建议专注于处理
Popup

有两种情况会阻止

Popup
自行关闭:按钮配置为
ButtonBase.ClickMode
设置为
ClickMode.Pressed
并且用户未单击弹出窗口内任何可聚焦的内容。

如果这两个条件之一的计算结果为 false(=>

ClickMode.Release
user 已将焦点移至
Popup
内),您的代码将按照您预期的方式工作。
请注意,为了允许用户在
Popup
内移动焦点,必须有一个可聚焦的子项(
UIElement.Focusable
设置为
true
- 对于大多数不需要的控件,默认情况下为
false
)用户交互)。例如,默认情况下,
TextBlock
不可聚焦。

因为您希望将按钮配置为在按下鼠标按钮时引发

Click
事件,所以您必须手动移动焦点。但是,当您手动设置时,
Popup
将不会收到鼠标单击来设置自身以观看焦点。因此,您最终将手动关闭
Popup
(从
Popup
中取消相关控制)。

以下示例通过观察

Popup
事件来确定焦点何时离开
Mouse.PreviewMouseDownOutsideCapturedElement
控件(鼠标在
CustomDropdown
外部单击)来关闭
Popup

自定义下拉.cs

internal class CustomDropdown : ContentControl
{
  public bool IsOpen
  {
    get => (bool)GetValue(IsOpenProperty);
    set => SetValue(IsOpenProperty, value);
  }

  public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
    "IsOpen",
    typeof(bool),
    typeof(CustomDropdown),
    new PropertyMetadata(default(bool), OnIsOpenChanged));

  public CustomDropdown()
  {
    Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnPreviewMouseDownOutsideCapturedElement);
  }

  private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    bool isOpen = (bool)e.NewValue;
    if (isOpen)
    {
      _ = Mouse.Capture(d as IInputElement, CaptureMode.SubTree);
    }
    else
    {
      _ = Mouse.Capture(null);
    }
  }

  // Manually close the Popup if click is recorded outside the CustomDropdown/Popup 
  private void OnPreviewMouseDownOutsideCapturedElement(object sender, MouseButtonEventArgs e) 
  {
    SetCurrentValue(IsOpenProperty, false);
  }
}

0
投票

我遇到了同样的问题,并找到了另一个(更轻的)解决方案: 只需在弹出窗口的构造函数中添加以下代码行:

Mouse.Capture(null);

这会释放当前的鼠标捕获,该捕获仍然属于您在构建弹出窗口时仍未释放的按钮。

© www.soinside.com 2019 - 2024. All rights reserved.