Suppose you have a ToggleButton
for opening a Popup
, same behaviour as all known elements as ComboBox
etc.
... which is this c
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}" />
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;
}
}
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">
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>