我正在尝试创建一个类似于组合框的自定义下拉控件,这样当您单击鼠标向下(而不是向上)时,弹出窗口将打开,而当您单击控件外部时,弹出窗口将关闭。
问题是,只有当我将 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>
如果您希望它按预期与
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" ...
您已经有了可行的答案。但是,找到父级
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);
}
}
我遇到了同样的问题,并找到了另一个(更轻的)解决方案: 只需在弹出窗口的构造函数中添加以下代码行:
Mouse.Capture(null);
这会释放当前的鼠标捕获,该捕获仍然属于您在构建弹出窗口时仍未释放的按钮。