Reputation: 21969
Second day fighting this issue.
To reproduce, create new WPF application, 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>
and code
private void Button_MouseMove(object sender, MouseEventArgs e)
{
popup.IsOpen = true;
}
Mouseover button to open popup, click elsewhere to close. Click button to have bug: popup is IsOpen == true (can be seen on checkbox or with breakpoint in handler), while it is invisible.
WTF?
And my original problem is, it seems, what setting IsOpen
is not instant. To example, when I try to set it to false
in Popup
's MouseMove
event, then I get MouseEnter
and MouseMove
events of Button
fired right during that
IsOpen = true;
Same with setting it to true
, there are 2 (!) MouseMove
events occurs, put this line into event handler to see it
System.Diagnostics.Trace.WriteLine("M");
There will be 2 M in the Output window of VS, while Popup
(when StayOpen=false
) suppose to capture mouse events and it does, but not immediately.
Can someone explain me what it going on? I want no events to occurs during (or shortly after? how to check if this is true?) setting IsOpen
. Tried already dozens of things: Dispatcher.InvokeAsync
, variables, timers, etc.
Upvotes: 5
Views: 1401
Reputation: 4774
Well, MouseMove
and MouseEnter
are both called when you press the button (even if with Spacebar key), so this leads to situation when Popup
is trying to close while having IsOpen
set to true at the same time. The simple solution could be to divide this two events.
One possible way can be to stick to MouseEnter
and open Popup
just once while hovering over given Button
. Just as example:
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;
}
While the same Button
generates this events Popup
should not be opened more than once (including the time when the button is pressed).
Upvotes: 2
Reputation: 6046
I think you are right with the asynchronous assumption. During the focus loss, the value of IsOpen
is set to false, but the MouseMove
of the button triggers to set it open again. Some strange magic inside then breaks the code.
2 possible solutions I found, depending on your needs:
IsOpen
to false
, after the popup closed (bash async)IsOpen == true
The first approach will hide the popup during clicking on the button. The second approach will come along with a slight flickering - when rapidly clicking the button - but it keeps the popup open:
For the first approach, use the following event handlers (you might not check the property first):
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;
}
For the second approach, use a BoolInvertConverter
and bind it one way to the popup:
IsEnabled="{Binding (Popup.IsOpen), ElementName=popup, Converter={StaticResource BoolInvertConverter}, Mode=OneWay}"
Upvotes: 4