c# - ¿Cómo descartar una ventana emergente en Silverlight al hacer clic fuera del control?
silverlight-3.0 event-handling (7)
En mi UI de Silverlight, tengo un botón que al hacer clic aparece un control con algunos parámetros de filtrado. Me gustaría que este control se oculte al hacer clic fuera de él. En otras palabras, debe funcionar de manera similar a un cuadro combinado, pero no es un cuadro combinado (no se selecciona un elemento en él). Así es como trato de capturar un clic fuera del control para descartarlo:
public partial class MyPanel : UserControl
{
public MyPanel()
{
InitializeComponent();
}
private void FilterButton_Click(object sender, RoutedEventArgs e)
{
// Toggle the open state of the filter popup
FilterPopup.IsOpen = !FilterPopup.IsOpen;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// Capture all clicks and close the popup
App.Current.RootVisual.MouseLeftButtonDown += delegate {
FilterPopup.IsOpen = false; };
}
}
Desafortunadamente, el controlador de eventos para MouseLeftButtonDown
nunca se dispara. ¿Existe una forma bien establecida de hacer un control emergente que se descarta automáticamente al hacer clic fuera de él? Si no, ¿por qué no está disparando mi controlador MouseLeftButtonDown
?
Solución:
Pensé en publicar mi solución completa en caso de que otros la encuentren útil. En mi visual de nivel superior, declaro un "escudo" para las ventanas emergentes, como este:
<UserControl xmlns:my="clr-namespace:Namespace"
x:Class="Namespace.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
>
<Grid Background="Black" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<my:MyStuff/>
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
x:Name="PopupShield" Background="Transparent" Width="Auto"
Height="Auto" Visibility="Collapsed"/>
</Grid>
</UserControl>
Luego, agregué un método de extensión para la clase Popup
, como este:
public static class PopupUtils
{
public static void MakeAutoDismissing(this Popup popup)
{
var shield = (App.Current.RootVisual as MainPage).PopupShield;
// Whenever the popup opens, deploy the shield
popup.HandlePropertyChanges(
"IsOpen",
(s, e) =>
{
shield.Visibility = (bool)e.NewValue
? Visibility.Visible : Visibility.Collapsed;
}
);
// Whenever the shield is clicked, dismiss the popup
shield.MouseLeftButtonDown += (s, e) => popup.IsOpen = false;
}
}
public static class FrameworkUtils
{
public static void HandlePropertyChanges(
this FrameworkElement element, string propertyName,
PropertyChangedCallback callback)
{
//Bind to a depedency property
Binding b = new Binding(propertyName) { Source = element };
var prop = System.Windows.DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(UserControl),
new System.Windows.PropertyMetadata(callback));
element.SetBinding(prop, b);
}
}
El método de extensión se usa así:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
FilterPopup.MakeAutoDismissing();
}
¿Estableciste un color de fondo en tu RootVisual?
Acabo de crear algo similar y espero que esto pueda ser de alguna ayuda. :)
En mi control PopUp, tengo un borde que tiene una cuadrícula que contiene una cantidad de cuadros de texto. Llamé al borde ''PopUpBorder''.
En el constructor de UserControl, tengo,
this.PopUpBorder.MouseLeave += (s, e) =>
{
Application.Current.RootVisual.MouseLeftButtonDown += (s1, e1) =>
{
this.PopUp.IsOpen = false;
};
};
Y parece que está funcionando como se esperaba. Por favor, avíseme si esto no funciona en su caso.
En el primer clic, llame al método CaptureMouse () en el control. Luego llame a ReleaseMouseCapture () en el segundo clic.
He publicado una solución para esto en mi blog, puede ver la publicación aquí - Silverlight: cierre la ventana emergente al hacer clic afuera . como puede ver, el uso es muy simple: es una propiedad adjunta que agrega en su ventana emergente. no tiene que agregar ningún contenedor, no tiene que tener cuidado si tiene o no un poco de color de fondo ... mi código se encargará de todo.
La solución propuesta utiliza un lienzo de escudo especial para bloquear la entrada a otros controles. Esto está bien, pero intento implementar un control genérico y no puedo depender de la existencia de un lienzo de escudo. La vinculación del evento MouseLeftButtonDown en la visualización de la raíz tampoco me funcionó, porque los botones existentes en mi lienzo aún activarían su propio evento de clic y no el MouseLeftButtonDown en el visual de la raíz. Encontré la solución en este artículo de hielo: Popup.StaysOpen en Silverlight de Kent Boograart. Los principales métodos para esta pregunta son:
private void OnPopupOpened(object sender, EventArgs e)
{
var popupAncestor = FindHighestAncestor(this.popup);
if (popupAncestor == null)
{
return;
}
popupAncestor.AddHandler(Windows.Popup.MouseLeftButtonDownEvent, (MouseButtonEventHandler)OnMouseLeftButtonDown, true);
}
private void OnPopupClosed(object sender, EventArgs e)
{
var popupAncestor = FindHighestAncestor(this.popup);
if (popupAncestor == null)
{
return;
}
popupAncestor.RemoveHandler(Windows.Popup.MouseLeftButtonDownEvent, (MouseButtonEventHandler)OnMouseLeftButtonDown);
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// in lieu of DependencyObject.SetCurrentValue, this is the easiest way to enact a change on the value of the Popup''s IsOpen
// property without overwriting any binding that may exist on it
var storyboard = new Storyboard() { Duration = TimeSpan.Zero };
var objectAnimation = new ObjectAnimationUsingKeyFrames() { Duration = TimeSpan.Zero };
objectAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero), Value = false });
Storyboard.SetTarget(objectAnimation, this.popup);
Storyboard.SetTargetProperty(objectAnimation, new PropertyPath("IsOpen"));
storyboard.Children.Add(objectAnimation);
storyboard.Begin();
}
private static FrameworkElement FindHighestAncestor(Popup popup)
{
var ancestor = (FrameworkElement)popup;
while (true) {
var parent = VisualTreeHelper.GetParent(ancestor) as FrameworkElement;
if (parent == null) {
return ancestor;
}
ancestor = parent;
}
}
Todavía no estoy seguro, por qué esta solución funciona para mí y esto no:
Application.Current.RootVisual.MouseLeftButtonDown += (s1, e1) =>
{
this.PopUp.IsOpen = false;
};
Parece que el método FindHighestAncestor simplemente devolverá el visual de la raíz? Pero entonces la diferencia debe ser el controlador de eventos? Supongo que la principal diferencia es el último parámetro del método AddHandler que es verdadero en el caso:
AddHandler(Windows.Popup.MouseLeftButtonDownEvent, (MouseButtonEventHandler)OnMouseLeftButtonDown, true);
Los documentos de MSDN dicen:
manejadoEventosToo
Tipo: System.Boolean
true para registrar el controlador de forma que se invoque incluso cuando el evento enrutado se marque manejado en sus datos de evento; falso para registrar el controlador con la condición predeterminada de que no se invocará si el evento enrutado ya está marcado como manejado. El valor predeterminado es falso. No solicite rutinariamente volver a manipular un evento enrutado. Para obtener más información, vea las observaciones.
Una alternativa más simple (aunque no es exactamente lo que solicitó) sería cerrar la ventana emergente en MouseLeave. MouseLeave en el objeto emergente en sí no funcionará, pero MouseLeave en el contenedor de nivel más alto dentro de Popup sí lo hace.
Una forma es poner tu control sobre un lienzo transparente que llena toda la superficie de Silverlight. Cuando se haga clic en el lienzo, cierre el lienzo y el control. Es importante asegurarse de que el pincel de fondo del lienzo esté configurado en "Transparente" si desea recibir eventos del mouse.
Un método alternativo con el que no he tenido éxito es usar la captura de mouse en Silverlight y detectar cuándo se hace clic en el mouse fuera de la ventana emergente.