L'approche suivante est la même que celle de Helge Klein, sauf que la fenêtre contextuelle se ferme automatiquement lorsque vous cliquez n'importe où à l'extérieur de la fenêtre contextuelle (y compris le ToggleButton lui-même) :
<ToggleButton x:Name="Btn" IsHitTestVisible="{Binding ElementName=Popup, Path=IsOpen, Mode=OneWay, Converter={local:BoolInverter}}">
<TextBlock Text="Click here for popup!"/>
</ToggleButton>
<Popup IsOpen="{Binding IsChecked, ElementName=Btn}" x:Name="Popup" StaysOpen="False">
<Border BorderBrush="Black" BorderThickness="1" Background="LightYellow">
<CheckBox Content="This is a popup"/>
</Border>
</Popup>
"BoolInverter" est utilisé dans la liaison IsHitTestVisible de sorte que lorsque vous cliquez à nouveau sur le ToggleButton, la fenêtre contextuelle se ferme :
public class BoolInverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
return !(bool)value;
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Convert(value, targetType, parameter, culture);
}
}
...qui montre la technique pratique de combinaison de IValueConverter et MarkupExtension en un.
J'ai découvert un problème avec cette technique : WPF est bogué lorsque deux popups sont sur l'écran en même temps. Plus précisément, si votre bouton de basculement se trouve sur le "popup de débordement" d'une barre d'outils, deux popups seront ouverts après que vous aurez cliqué dessus. Il se peut alors que la deuxième fenêtre contextuelle (la vôtre) reste ouverte lorsque vous cliquez sur un autre élément de votre fenêtre. À ce stade, il est difficile de fermer la fenêtre contextuelle. L'utilisateur ne peut pas cliquer à nouveau sur le ToggleButton pour fermer la fenêtre contextuelle car IsHitTestVisible est faux puisque la fenêtre contextuelle est ouverte ! Dans mon application, j'ai dû utiliser quelques astuces pour atténuer ce problème, comme le test suivant sur la fenêtre principale, qui dit (avec la voix de Louis Black) "si la fenêtre pop-up est ouverte et que l'utilisateur clique quelque part en dehors de la fenêtre pop-up, fermez cette foutue fenêtre pop-up" :
PreviewMouseDown += (s, e) =>
{
if (Popup.IsOpen)
{
Point p = e.GetPosition(Popup.Child);
if (!IsInRange(p.X, 0, ((FrameworkElement)Popup.Child).ActualWidth) ||
!IsInRange(p.Y, 0, ((FrameworkElement)Popup.Child).ActualHeight))
Popup.IsOpen = false;
}
};
// Elsewhere...
public static bool IsInRange(int num, int lo, int hi) =>
num >= lo && num <= hi;