2008-12-11 17 views
54

Tengo dos controles, un TextBlock y un PopUp. Cuando el usuario hace clic (MouseDown) en el bloque de texto, quiero mostrar la ventana emergente. Pensaría que podría hacer esto con un EventTrigger en el Popup, pero no puedo usar setters en un EventTrigger, solo puedo comenzar storyboards. Quiero hacer esto estrictamente en XAML, porque los dos controles están en una plantilla y no sé cómo encontraría la ventana emergente en el código.¿Cómo abrir una ventana emergente de WPF cuando se hace clic en otro control, utilizando solo el marcado XAML?

Esto es lo que conceptualmente lo que quiero hacer, pero no puede porque no se puede poner un regulador en un EventTrigger (como se hace con un DataTrigger):

<TextBlock x:Name="CCD">Some text</TextBlock> 

<Popup> 
    <Popup.Style> 
     <Style> 
      <Style.Triggers> 
       <EventTrigger SourceName="CCD" RoutedEvent="MouseDown"> 
        <Setter Property="Popup.IsOpen" Value="True" /> 
       </EventTrigger> 
      </Style.Triggers> 
     </Style> 
    </Popup.Style> 
... 

¿Cuál es la mejor manera de mostrar una ventana emergente estrictamente en XAML cuando ocurre un evento en un control diferente?

Respuesta

77

Hice algo simple, pero funciona.

Utilicé un ToggleButton típico, que rediseñé como un bloque de texto cambiando su plantilla de control. Luego acabo de vincular la propiedad IsChecked en ToggleButton a la propiedad IsOpen en la ventana emergente. Popup tiene algunas propiedades como StaysOpen que te 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> 
+0

enfoque interesante – viggity

+4

ver también @ enfoque de Qwertie - una versión más útil inspirado por esto que se cerrará la ventana emergente automáticamente cuando se alt-tab o haga clic fuera de la ventana emergente –

8

Tuve algunos problemas con la parte de MouseDown de esto, pero aquí hay un código que puede comenzar.

<Window x:Class="WpfApplication1.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="300" Width="300"> 
    <Grid> 
     <Control VerticalAlignment="Top"> 
      <Control.Template> 
       <ControlTemplate> 
        <StackPanel> 
        <TextBox x:Name="MyText"></TextBox> 
        <Popup x:Name="Popup" PopupAnimation="Fade" VerticalAlignment="Top"> 
         <Border Background="Red"> 
          <TextBlock>Test Popup Content</TextBlock> 
         </Border> 
        </Popup> 
        </StackPanel> 
        <ControlTemplate.Triggers> 
         <EventTrigger RoutedEvent="UIElement.MouseEnter" SourceName="MyText"> 
          <BeginStoryboard> 
           <Storyboard> 
            <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup" Storyboard.TargetProperty="(Popup.IsOpen)"> 
             <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/> 
            </BooleanAnimationUsingKeyFrames> 
           </Storyboard> 
          </BeginStoryboard> 
         </EventTrigger> 
         <EventTrigger RoutedEvent="UIElement.MouseLeave" SourceName="MyText"> 
          <BeginStoryboard> 
           <Storyboard> 
            <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup" Storyboard.TargetProperty="(Popup.IsOpen)"> 
             <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False"/> 
            </BooleanAnimationUsingKeyFrames> 
           </Storyboard> 
          </BeginStoryboard> 
         </EventTrigger> 
        </ControlTemplate.Triggers> 
       </ControlTemplate> 
      </Control.Template> 
     </Control> 
    </Grid> 
</Window> 
48

El siguiente enfoque es el mismo que Helge Klein, excepto que la ventana emergente se cierra automáticamente cuando se hace clic en cualquier lugar fuera de la emergente (incluyendo la propia 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 utiliza en IsHitTestVisible la unión de modo que cuando hace clic en el ToggleButton de nuevo, 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 combining IValueConverter and MarkupExtension en uno.

Descubrí un problema con esta técnica: WPF tiene errores cuando aparecen dos ventanas emergentes en la pantalla al mismo tiempo. Específicamente, si su botón de alternar se encuentra en la "ventana emergente de desbordamiento" en una barra de herramientas, se abrirán dos ventanas emergentes después de hacer clic en él. A continuación, puede encontrar que la segunda ventana emergente (su ventana emergente) permanecerá abierta cuando haga clic en cualquier otro lugar en su ventana. En ese punto, cerrar la ventana emergente es difícil. El usuario no puede volver a hacer clic en ToggleButton para cerrar la ventana emergente porque IsHitTestVisible es falso porque la ventana emergente está abierta. En mi aplicación tuve que usar algunos hacks 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 fuera de la ventana emergente, cerrar el popup puto ":.

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; 
    } 
}; 
+1

esto funciona muy bien, incluyendo alt-tabbing a otra aplicación o hacer clic en otro lugar en la ventana –

+1

StaysOpen = "false" es la clave para el comportamiento de cierre automático fuera de componente. ¿Por qué oh, por qué eso es predeterminado? –

+1

@ChrisDolan Me molestan tanto cosas como los valores predeterminados que van en contra de todos los principios de UX que conozco. Entonces, hago un estilo implícito para 'Popup' y establezco los valores predeterminados que me gustan, incluyendo 'StaysOpen = False'. – erodewald

0

otra manera de hacerlo:

<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"> 
        <StackPanel> 
         <Image Source="{Binding ProductImage,RelativeSource={RelativeSource TemplatedParent}}" Stretch="Fill" Width="65" Height="85"/> 
         <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 
         <Button x:Name="myButton" Width="40" Height="10"> 
          <Popup Width="100" Height="70" IsOpen="{Binding ElementName=myButton,Path=IsMouseOver, Mode=OneWay}"> 
           <StackPanel Background="Yellow"> 
            <ItemsControl ItemsSource="{Binding Produkt.SubProducts}"/> 
           </StackPanel> 
          </Popup> 
         </Button> 
        </StackPanel> 
       </Border> 
8

¿Qué tal:

<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 se referecing la Button por nombre y viceversa.Por lo tanto, se requiere x:Name="..." en ambos, el Popup y el Button.

En realidad, puede simplificarse aún más mediante la sustitución de la materia Storyboard con una costumbre SetProperty EventTrigger acción descrita in this SO Answer

Cuestiones relacionadas