WPF自定义MenuButton控件首次点击菜单项无响应问题求助
自定义的WPF MenuButton控件外观如下:
控件功能说明:点击按钮显示菜单项,失去焦点时隐藏,菜单项点击逻辑正常,但首次点击菜单项无响应,第二次及以后点击才生效。以下是控件的原代码、问题分析和修复方案:
原代码
C#代码
public partial class MenuButton : Button, INotifyPropertyChanged { private readonly ContextMenu Menu; private SolidColorBrush arrowColor = Brushes.Transparent; public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(MenuButton), new(string.Empty, new(OnSomeTextPropertyChanged))); public static readonly DependencyProperty MenuPlacementProperty = DependencyProperty.Register(nameof(MenuPlacement), typeof(PlacementMode), typeof(MenuButton), new(PlacementMode.Top, new(OnSomeMenuPlacementPropertyChanged))); public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register(nameof(MenuItems), typeof(ItemCollection), typeof(MenuButton), new(default(ItemCollection), new(OnSomeMenuItemsPropertyChanged))); public SolidColorBrush ArrowColor { get => arrowColor; private set { arrowColor = value; OnPropertyChanged(nameof(arrowColor)); } } public string Text { get => (string)GetValue(TextProperty); set => SetCurrentValue(TextProperty, value); } public PlacementMode MenuPlacement { get => (PlacementMode)GetValue(MenuPlacementProperty); set => SetCurrentValue(MenuPlacementProperty, value); } public ItemCollection MenuItems { get => (ItemCollection)GetValue(MenuItemsProperty); set => SetCurrentValue(MenuItemsProperty, value); } public MenuButton() { Menu = new() { StaysOpen = true, HasDropShadow = false, PlacementTarget = this, Placement = MenuPlacement, ItemsSource = MenuItems, }; SetCurrentValue(MenuItemsProperty, new DataGrid().Items); InitializeComponent(); } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName] string prop = "") { PropertyChanged?.Invoke(this, new(prop)); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); Menu.Width = ActualWidth; Menu.IsOpen = true; } private void MenuButtonControl_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) { if (sender is MenuButton button && bool.TryParse(e.NewValue?.ToString(), out bool enabled)) { button.ArrowColor = enabled ? Brushes.Black : Brushes.DimGray; } } private static void OnSomeTextPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { if (target is MenuButton menuButton) { menuButton.ButtonText.Content = e.NewValue.ToString(); } } private static void OnSomeMenuPlacementPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { if (target is MenuButton menuButton && menuButton.Menu is ContextMenu menu && Enum.TryParse(e.NewValue?.ToString(), true, out PlacementMode placementMode)) { menu.Placement = placementMode; } } private static void OnSomeMenuItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { if (target is MenuButton menuButton && menuButton.Menu is ContextMenu menu && e.NewValue is ItemCollection itemCollection) { menu.ItemsSource = itemCollection; menu.IsOpen = true; menu.IsOpen = false; } } }
XAML代码
<Button x:Name="MenuButtonControl" x:Class="Controls.UserControls.MenuButton" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="20" d:DesignWidth="100" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" IsEnabledChanged="MenuButtonControl_IsEnabledChanged"> <Button.Content> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <Label x:Name="ButtonText" Grid.Row="0" Grid.Column="0" Margin="0" Padding="0" Content="{Binding Text, ElementName=MenuButtonControl}" HorizontalAlignment="Center" VerticalAlignment="Stretch"></Label> <Path Grid.Row="0" Grid.Column="1" Margin="5,0" Fill="{Binding ArrowColor, ElementName=MenuButtonControl}" Data="M 0 0 L 4 5 L 8 0 Z" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </Button.Content> </Button>
问题分析
- ContextMenu状态异常:
OnSomeMenuItemsPropertyChanged中强制切换IsOpen状态(先设为true再设为false),干扰了ContextMenu内部的事件处理逻辑,导致首次点击菜单项时事件无法正常触发。 - MenuItems初始化错误:用
new DataGrid().Items初始化菜单数据源,DataGrid的Items集合是控件内部绑定集合,不适合作为ContextMenu的数据源,还会触发上述错误的状态切换。 - PropertyChanged通知错误:
ArrowColor属性的通知参数用了字段名arrowColor而非属性名ArrowColor,导致箭头颜色绑定无法更新。 - 焦点冲突:
OnMouseLeftButtonDown中调用base方法触发Button的默认点击状态,与ContextMenu的焦点获取逻辑冲突,影响菜单项点击响应。
修复方案
修改后的C#代码
public partial class MenuButton : Button, INotifyPropertyChanged { private readonly ContextMenu Menu; private SolidColorBrush arrowColor = Brushes.Transparent; public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(MenuButton), new(string.Empty, new(OnSomeTextPropertyChanged))); public static readonly DependencyProperty MenuPlacementProperty = DependencyProperty.Register(nameof(MenuPlacement), typeof(PlacementMode), typeof(MenuButton), new(PlacementMode.Top, new(OnSomeMenuPlacementPropertyChanged))); // 改用ObservableCollection作为绑定源,更适合动态数据场景 public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register(nameof(MenuItems), typeof(ObservableCollection<object>), typeof(MenuButton), new(new ObservableCollection<object>(), new(OnSomeMenuItemsPropertyChanged))); public SolidColorBrush ArrowColor { get => arrowColor; private set { arrowColor = value; // 修复PropertyChanged通知的属性名 OnPropertyChanged(nameof(ArrowColor)); } } public string Text { get => (string)GetValue(TextProperty); set => SetCurrentValue(TextProperty, value); } public PlacementMode MenuPlacement { get => (PlacementMode)GetValue(MenuPlacementProperty); set => SetCurrentValue(MenuPlacementProperty, value); } public ObservableCollection<object> MenuItems { get => (ObservableCollection<object>)GetValue(MenuItemsProperty); set => SetCurrentValue(MenuItemsProperty, value); } public MenuButton() { Menu = new() { // 恢复默认关闭行为,点击菜单项或外部区域自动关闭 StaysOpen = false, HasDropShadow = false, PlacementTarget = this, Placement = MenuPlacement, ItemsSource = MenuItems, }; // 初始化空的ObservableCollection,替换原错误的DataGrid.Items SetCurrentValue(MenuItemsProperty, new ObservableCollection<object>()); InitializeComponent(); } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName] string prop = "") { PropertyChanged?.Invoke(this, new(prop)); } protected override void OnClick() { // 改用OnClick事件打开菜单,避免MouseLeftButtonDown的焦点冲突 Menu.Width = ActualWidth; Menu.IsOpen = true; } private void MenuButtonControl_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) { if (sender is MenuButton button) { button.ArrowColor = (bool)e.NewValue ? Brushes.Black : Brushes.DimGray; } } private static void OnSomeTextPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { if (target is MenuButton menuButton) { menuButton.ButtonText.Content = e.NewValue.ToString(); } } private static void OnSomeMenuPlacementPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { if (target is MenuButton menuButton && menuButton.Menu is ContextMenu menu && e.NewValue is PlacementMode placementMode) { menu.Placement = placementMode; } } private static void OnSomeMenuItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { if (target is MenuButton menuButton && menuButton.Menu is ContextMenu menu && e.NewValue is ObservableCollection<object> itemCollection) { menu.ItemsSource = itemCollection; // 移除错误的IsOpen开关逻辑 } } }
关键修改点
- 将
MenuItemsProperty类型改为ObservableCollection<object>,更适合作为数据绑定源。 - 移除
OnSomeMenuItemsPropertyChanged中错误的IsOpen状态切换代码。 - 修复
ArrowColor的PropertyChanged通知参数,使用正确的属性名。 - 将菜单打开逻辑移至
OnClick方法,避免MouseLeftButtonDown的焦点冲突。 - 设置
Menu.StaysOpen = false,恢复ContextMenu默认关闭行为。 - 修正
MenuItems初始化方式,使用空的ObservableCollection<object>。
内容的提问来源于stack exchange,提问作者Vlad i Slav
相关产品推荐
相关产品推荐

