WPF鼠标悬停MenuItem后按钮Hover状态失效,窗口焦点无法恢复
问题描述
我有一个包含自定义Menu和若干Button的WPF窗口,已实现鼠标悬停MenuItem时自动展开菜单。每个Button都配置了鼠标悬停(IsMouseOver)状态样式,但在通过悬停打开菜单后,Button不再显示该悬停状态。尝试在Popup关闭时手动将焦点恢复到Window,但未生效。
简化代码如下:
<Window> <Button Cursor="Hand" Width="20" Height="20"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="Background" Value="White" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border x:Name="Border" Background="{TemplateBinding Background}" CornerRadius="30"> <Image Source="../Icons/close.png" Width="9" Height="9" /> </Border> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Gray" /> </Trigger> </Style.Triggers> </Style> </Button.Style> </Button> <ScrollViewer> <Menu ItemsSource="{Binding Path=CommandMenuItems}"> <Menu.ItemContainerStyle> <Style TargetType="{x:Type MenuItem}"> <Setter Property="Focusable" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type MenuItem}"> <Border> <Grid> <ContentPresenter /> <TextBlock /> <Grid /> <Popup IsOpen="{Binding IsSubmenuOpen}"/> </Grid> </Border> <ControlTemplate.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="Role" Value="SubmenuHeader" /> </MultiTrigger.Conditions> <Setter Property="IsSubmenuOpen" Value="True" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="Role" Value="TopLevelHeader" /> </MultiTrigger.Conditions> <Setter Property="IsSubmenuOpen" Value="True" /> </MultiTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Menu.ItemContainerStyle> </ScrollViewer> </Window>
问题分析
WPF的Popup控件在打开时会自动捕获鼠标输入,确保Popup优先响应鼠标操作。当通过悬停打开子菜单Popup后,鼠标捕获被Popup持有;即使Popup关闭,若未正确释放鼠标捕获,窗口内的其他控件(如Button)将无法正常检测到IsMouseOver状态——因为鼠标输入仍被之前的捕获操作占用。手动恢复Window焦点无法解决此问题,因为焦点与鼠标捕获是两个独立的WPF输入概念。
解决方案
需要确保Popup关闭时强制释放鼠标捕获,同时优化MenuItem的触发器逻辑,保证子菜单关闭时正确清理鼠标状态。具体修改如下:
- 给MenuItem模板中的Popup添加
StaysOpen="False",确保鼠标离开Popup区域时自动关闭它; - 添加触发器,当
IsSubmenuOpen变为False时,调用Mouse.Capture(null)释放全局鼠标捕获; - (可选)给Button添加
Focusable="False",避免焦点状态干扰悬停样式的触发。
修改后的完整代码:
<Window> <Button Cursor="Hand" Width="20" Height="20" Focusable="False"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="Background" Value="White" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border x:Name="Border" Background="{TemplateBinding Background}" CornerRadius="30"> <Image Source="../Icons/close.png" Width="9" Height="9" /> </Border> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Gray" /> </Trigger> </Style.Triggers> </Style> </Button.Style> </Button> <ScrollViewer> <Menu ItemsSource="{Binding Path=CommandMenuItems}"> <Menu.ItemContainerStyle> <Style TargetType="{x:Type MenuItem}"> <Setter Property="Focusable" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type MenuItem}"> <Border x:Name="MenuItemBorder"> <Grid> <ContentPresenter /> <TextBlock /> <Grid /> <Popup IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" StaysOpen="False"/> </Grid> </Border> <ControlTemplate.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="Role" Value="SubmenuHeader" /> </MultiTrigger.Conditions> <Setter Property="IsSubmenuOpen" Value="True" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="Role" Value="TopLevelHeader" /> </MultiTrigger.Conditions> <Setter Property="IsSubmenuOpen" Value="True" /> </MultiTrigger> <!-- 当子菜单关闭时释放鼠标捕获 --> <Trigger Property="IsSubmenuOpen" Value="False"> <EventSetter Event="Loaded" Handler="MenuItem_Loaded"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Menu.ItemContainerStyle> </ScrollViewer> </Window>
同时,在窗口的后台代码中添加MenuItem_Loaded事件处理,确保释放鼠标捕获:
private void MenuItem_Loaded(object sender, RoutedEventArgs e) { if (sender is MenuItem menuItem && !menuItem.IsSubmenuOpen) { Mouse.Capture(null); } }
内容的提问来源于stack exchange,提问作者dransom90
相关产品推荐
相关产品推荐

