Solución:
Hice algo simple, pero funciona.
Usé un ToggleButton típico, que rediseñé como un bloque de texto cambiando su plantilla de control. Luego, simplemente vinculé la propiedad IsChecked en el ToggleButton a la propiedad IsOpen en la ventana emergente. Popup tiene algunas propiedades como StaysOpen que le permiten modificar el comportamiento de cierre.
Lo siguiente funciona en XamlPad.
<StackPanel>
<ToggleButton Name="button">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<TextBlock>Click Me Here!!</TextBlock>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
<Popup IsOpen="{Binding IsChecked, ElementName=button}" StaysOpen="False">
<Border Background="LightYellow">
<TextBlock>I'm the popup</TextBlock>
</Border>
</Popup>
</StackPanel>
El siguiente enfoque es el mismo que el de Helge Klein, excepto que la ventana emergente se cierra automáticamente cuando hace clic en cualquier lugar fuera de la ventana emergente (incluido el propio ToggleButton):
<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” se usa en el enlace IsHitTestVisible de modo que cuando hace clic en ToggleButton nuevamente, la ventana emergente se cierra:
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);
}
}
… que muestra la práctica técnica de combinar IValueConverter y MarkupExtension en uno.
Descubrí un problema con esta técnica: WPF tiene errores cuando hay dos ventanas emergentes en la pantalla al mismo tiempo. Específicamente, si su botón de alternancia está en la “ventana emergente de desbordamiento” en una barra de herramientas, se abrirán dos ventanas emergentes después de hacer clic en él. Luego, puede encontrar que la segunda ventana emergente (su ventana emergente) permanecerá abierta cuando haga clic en cualquier otro lugar de su ventana. En ese momento, cerrar la ventana emergente es difícil. El usuario no puede hacer clic en ToggleButton nuevamente para cerrar la ventana emergente porque IsHitTestVisible es falso porque la ventana emergente está abierta. En mi aplicación tuve que usar algunos trucos para mitigar este problema, como la siguiente prueba en la ventana principal, que dice (en la voz de Louis Black) “si la ventana emergente está abierta y el usuario hace clic en algún lugar fuera de la ventana emergente, cierra la maldita ventana emergente. “:
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;
Los siguientes usos EventTrigger
para mostrar el Popup
. Esto significa que no necesitamos un ToggleButton
para vinculación estatal. En este ejemplo el Click
evento de un Button
se utiliza. Puede adaptarlo para usar otra combinación de elemento / evento.
<Button x:Name="OpenPopup">Popup
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="ContextPopup"
Storyboard.TargetProperty="IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Popup x:Name="ContextPopup"
PlacementTarget="{Binding ElementName=OpenPopup}"
StaysOpen="False">
<Label>Popupcontent...</Label>
</Popup>
Tenga en cuenta que el Popup
hace referencia al Button
por nombre y viceversa. Entonces x:Name="..."
se requiere en ambos, el Popup
y el Button
.
En realidad, se puede simplificar aún más reemplazando el Storyboard
cosas con una costumbre SetProperty
Acción EventTrigger descrita en esta respuesta SO