不可见的打开弹窗

5

第二天在解决这个问题。

为了重现问题,请创建一个新的 WPF 应用程序,xaml

<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
    <Button Width="100" Height="100" MouseMove="Button_MouseMove"/>
    <Popup x:Name="popup" StaysOpen="False" AllowsTransparency="True" Placement="Center">
        <TextBlock>Some random text</TextBlock>
    </Popup>
    <CheckBox IsChecked="{Binding (Popup.IsOpen), ElementName=popup}">Popup</CheckBox>
</StackPanel>

以及代码

private void Button_MouseMove(object sender, MouseEventArgs e)
{
    popup.IsOpen = true;
}

鼠标移至按钮以打开弹出窗口,单击其他位置以关闭。点击按钮会导致bug:弹出窗口的IsOpen == true(可以通过复选框或在处理程序中使用断点查看),但它是不可见的。

什么鬼?

而我的问题似乎是,设置 IsOpen 不是即时的。例如,当我尝试在 PopupMouseMove 事件中将其设置为 false 时,我会在此期间立即触发 ButtonMouseEnterMouseMove 事件。

IsOpen = true;

与将其设置为true相同,有2个(!) MouseMove事件发生,请将此行放入事件处理程序中以查看。
System.Diagnostics.Trace.WriteLine("M");

在Visual Studio的输出窗口中会有2个 M,而Popup(当StayOpen=false)应该捕获鼠标事件并且确实能够捕获,但不是立即。请问是否有人可以解释一下这是怎么回事?我希望在设置IsOpen期间(或者稍后?如何检查是否正确?)不会发生任何事件。我已经尝试了许多方法:包括Dispatcher.InvokeAsync、变量、计时器等。

可能是Wpf Popup loses focus but stays opened的重复问题。请参考那里的答案:最有可能的原因是对IsOpen的绑定导致了StaysOpen属性无法正常工作... - qqbenq
@qqbenq,与焦点无关。这里的绑定仅用于演示问题(删除它并使用断点=相同问题)。 - Sinatr
当鼠标指针移动到其父对象上时,弹出窗口不会自动打开。如果您想要自动打开弹出窗口,请使用ToolTip或ToolTipService类。有关更多信息,请参阅ToolTip概述。 - Kcvin
1
@NETscape,是的。它不会自动打开。我正在手动打开它。 - Sinatr
@Sinatr,您希望在按钮按下时发生什么?Popup 应该保持打开状态还是关闭也可以? - icebat
显示剩余4条评论
2个回答

4

我认为你对异步假设是正确的。在失去焦点期间,IsOpen的值被设置为false,但按钮的MouseMove触发器会再次打开它。然后一些奇怪的魔法会破坏代码。

根据您的需求,我找到了两种可能的解决方案:

  1. 在弹出窗口关闭后明确将IsOpen设置为false(Bash Async)
  2. #1 + 在弹出窗口IsOpen == true期间禁用按钮

第一种方法将在单击按钮时隐藏弹出窗口。第二种方法会在快速点击按钮时出现轻微的闪烁,但它会保持弹出窗口打开:

对于第一种方法,请使用以下事件处理程序(您可能不需要先检查属性):

private void Button_MouseMove(object sender, MouseEventArgs e)
{
    popup.IsOpen = true;
}

private void Popup_OnClosed(object sender, EventArgs e)
{
    if (popup.IsOpen)
        popup.IsOpen = false;
}

第二种方法是使用一个BoolInvertConverter,并将其单向绑定到弹出窗口:

IsEnabled="{Binding (Popup.IsOpen), ElementName=popup, Converter={StaticResource BoolInvertConverter}, Mode=OneWay}"

关闭并将 IsOpen=false 设为假是一种欺骗,但效果完美,谢谢!我通过在该按钮顶部(与下面的按钮相同)显示 Popup 解决了点击问题,这样你就无法点击它 =P - Sinatr
@Sinatr 是的,这更像是一种变通/欺骗,而不是一个解决方法。如果你想要解决它,去微软那里挖掘他们的代码吧!:) - Herdo

2

好的,MouseMoveMouseEnter都在按下按钮时被调用(即使是使用Spacebar键),因此这会导致当Popup处于IsOpen为true的状态时,尝试关闭Popup。简单的解决方案可以是将这两个事件分开。

一种可能的方法是坚持使用MouseEnter,并在悬停在给定Button上时只打开一次Popup。仅作为示例:

private Button currentPopupHolder;
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
   var btn = sender as Button;
   if (currentPopupHolder != btn)
   {
      popup.IsOpen = true;
      currentPopupHolder = btn;
   }
}

private void Button_MouseLeave(object sender, MouseEventArgs e)
{
   currentPopupHolder = null;
}

虽然同一个按钮生成了此类事件,但弹出窗口不应该在多次打开(包括按下按钮时)。


这个想法不错,但是@Herdo提出的在OnClosed事件中明确使用IsOpen=true的解决方案更优秀。 - Sinatr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接