Suppose you have a ToggleButton
for opening a Popup
, same behaviour as all known elements as ComboBox
etc.
... which is this code:
<ToggleButton x:Name="PART_OpenToggleButton"
Focusable="False"
IsChecked="False"
Template="{StaticResource MyToggleButton}">
<Grid>
<Popup x:Name="PART_PopupControl"
Style="{StaticResource MyPopupStyle}"
StaysOpen="False"
VerticalAlignment="Bottom"
IsOpen="False"
PlacementTarget="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ToggleButton, AncestorLevel=1}}" />
</Grid>
</ToggleButton>
Then in code behind you work with .IsOpen
for Popup
and .IsChecked
for ToggleButton
.
Everything works, but the problem arrives when you Open the Popup
and click outside the borders.
The Popup
will be closed but the ToggleButton
stays checked.
You cannot set in the PopupOnClosed
Handler that ToggleButton.IsChecked = false
, because when you Click on the ToggleButton
to close the Popup
, the Popup
closes itself, sets ToggleButton.IsChecked = false
but at the sime time you clicked on the ToggleButton
and it tries to Open the Popup
again. So you cannot close it.
1st ToggleButtonClick:
-> ToggleButton IsChecked = true
2nd ToggleButtonClick:
-> ToggleButton IsChecked = false
-> ToggleButton IsChecked = true
So if you click on the Toggle Button while Popup being open, it blinks but stays open.
How would you solve this problem, please ?
EDITED:
Try this in a MyWindow.XAML and add the dependency property IsDropDownOpen in the code behind, please:
<Grid>
<ToggleButton x:Name="PART_OpenToggleButton"
Focusable="False"
Height="20"
Width="50"
IsChecked="{Binding ElementName=TestWindow, Mode=TwoWay, Path=IsDropDownOpen}">
<Grid>
<Popup x:Name="PART_PopupControl"
Width="100"
Height="100"
StaysOpen="False"
Focusable="False"
VerticalAlignment="Bottom"
IsOpen="{Binding ElementName=TestWindow, Path=IsDropDownOpen}"
PlacementTarget="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ToggleButton, AncestorLevel=1}}">
</Popup>
</Grid>
</ToggleButton>
</Grid>
public bool IsDropDownOpen
{
get { return (bool)GetValue(IsDropDownOpenProperty); }
set { SetValue(IsDropDownOpenProperty, value); }
}
public static readonly DependencyProperty IsDropDownOpenProperty =
DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(Window), new UIPropertyMetadata(false));
Ok, here is some code that works for me (those are copy-pasted from working code with some of the not-interesting parts removed):
Here's the content of a ComboBox-like UserControl:
<ToggleButton x:Name="Button" Height="19">
<Grid>
<Label Name="DisplayList" Content="Whatever" />
<Popup Name="SelectionPopup" MinHeight="100" MinWidth="200"
StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=Button}">
</Popup>
</Grid>
</ToggleButton>
And from a custom template to an actual ComboBox:
<ToggleButton
Name="ToggleButton"
Template="{StaticResource ComboBoxToggleButton}"
Grid.Column="2"
Focusable="false"
IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press">
</ToggleButton>
<Popup
Name="Popup"
Placement="Bottom"
IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide">
I found the solution on this post : https://stackoverflow.com/a/5821819/651161
Using the following class will permit to handle the click before the togglebutton is pressed. The popup is closed because of the click but the click is then handled, so it doesn't trigger the ToggleButton click.
public class MyPopup : Popup {
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
bool isOpen = this.IsOpen;
base.OnPreviewMouseLeftButtonDown(e);
if (isOpen && !this.IsOpen)
e.Handled = true;
}
}
You could just bind the Popups StaysOpen
property to the Buttons IsMouseOver
property. This way, the popup will close whenever you click something outside the popup (IsMouseOver = false = StaysOpen
) and it will close the Popup when clicking the ToggleButton
(IsMouseOver = true = StaysOpen
).
This way, even the clicks outside the Popup will be handled.
<ToggleButton x:Name="Toggle" />
<Popup x:Name="Popup" IsOpen="{Binding ElementName=Toggle, Path=IsChecked, Mode=TwoWay}"
StaysOpen="{Binding ElementName=Toggle, Path=IsMouseOver}" />
It seems to me, there are a two problems - one is to adress the problem, that clicks inside the popup are potentially again handled according to it's the position in the visual tree.
The second problem is - autoclose via click - happens really every time you click outside the popup and can trigger additional events. Even you click the "open button" to close. The problem is - you don't know which value was set before at popup.isOpen - becase it always is false for the click eventhandler of the open button.
I don't use a toggleButton for saving memory, but i think the key problem is the same. The toggleButton already is false in the moment you click on it.
My example popup is defined like that:
<Popup Placement="Bottom" PopupAnimation="Slide" Name="PART_Popup" VerticalOffset="3" AllowsTransparency="True" StaysOpen="False">
If you click on the placement target - which was the "open button" the popup closes and at the same time the click event of the button was handled but the popup.IsOpen property was already 'false' - so it was opened again.
What I did to solve this was to subscribe the popups "Closed" event, saved the time - blocking reopen for a second.
DateTime? LastClose = new DateTime?();
private void Popup_Closed(object sender, EventArgs e)
{ LastClose = DateTime.Now; }
public bool AllowReopen
{
get {
if ((popup == null) || (popup.IsOpen)) return false;
//You cannot open, when the template isn't applied or it is already open
return !LastClose.HasValue || (DateTime.Now - LastClose.Value) > new TimeSpan(0,0,1) /*1s*/;
}
}
public void OpenPopup()
{
if (!AllowReopen) return;
popup.IsOpen = true;
}
I would bind both guys to the same property in the ViewModel. You can find good example in Toolbox default template:
<ToggleButton x:Name="OverflowButton"
FocusVisualStyle="{x:Null}"
IsEnabled="{TemplateBinding HasOverflowItems}"
Style="{StaticResource ToolBarHorizontalOverflowButtonStyle}"
IsChecked="{Binding Path=IsOverflowOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<Popup x:Name="OverflowPopup"
AllowsTransparency="true"
Placement="Bottom"
IsOpen="{Binding Path=IsOverflowOpen,RelativeSource={RelativeSource TemplatedParent}}"
StaysOpen="false"
Focusable="false"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}">
<theme:SystemDropShadowChrome Name="Shdw" Color="Transparent">
<Border Background="{StaticResource ToolBarSubMenuBackground}"
BorderBrush="{StaticResource ToolBarMenuBorder}"
BorderThickness="1">
<ToolBarOverflowPanel x:Name="PART_ToolBarOverflowPanel"
Margin="2"
WrapWidth="200"
Focusable="true"
FocusVisualStyle="{x:Null}"
KeyboardNavigation.TabNavigation="Cycle"
KeyboardNavigation.DirectionalNavigation="Cycle"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</theme:SystemDropShadowChrome>
</Popup>
Hope this helps,
Cheers, Anvaka.
To prevent closing the Popup by clicking its background, insert something that will fill it.
In this example, clicking the unfilled space will close the Popup:
<Popup x:Key="MyPop" Width="200" Height="200" StaysOpen="False">
<CheckBox Content="abc" />
</Popup>
In this example, clicking the unfilled space will NOT close the Popup:
<Popup x:Key="MyPop" Width="200" Height="200" StaysOpen="False">
<StackPanel Background="Red" Width="200" Height="200"> <!--EXTRA PANEL -->
<CheckBox Content="abc" />
</StackPanel>
</Popup>
来源:https://stackoverflow.com/questions/2333905/wpf-popup-hiding-problem