Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to open a WPF Popup when another control is clicked, using XAML markup only?

I've got two controls, a TextBlock and a PopUp. When the user clicks (MouseDown) on the textblock, I want to display the popup. I would think that I could do this with an EventTrigger on the Popup, but I can't use setters in an EventTrigger, I can only start storyboards. I want to do this strictly in XAML, because the two controls are in a template and I don't know how I'd find the popup in code.

This is what conceptually I want to do, but can't because you can't put a setter in an EventTrigger (like you can with a 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>
...

What is the best way to show a popup strictly in XAML when an event happens on a different control?

like image 244
viggity Avatar asked Dec 11 '08 22:12

viggity


3 Answers

I did something simple, but it works.

I used a typical ToggleButton, which I restyled as a textblock by changing its control template. Then I just bound the IsChecked property on the ToggleButton to the IsOpen property on the popup. Popup has some properties like StaysOpen that let you modify the closing behavior.

The following works in 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>
like image 159
John Melville Avatar answered Oct 18 '22 22:10

John Melville


The following approach is the same as Helge Klein's, except that the popup closes automatically when you click anywhere outside the Popup (including the ToggleButton itself):

<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" is used in the IsHitTestVisible binding so that when you click the ToggleButton again, the popup closes:

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);
    }
}

...which shows the handy technique of combining IValueConverter and MarkupExtension in one.

I did discover one problem with this technique: WPF is buggy when two popups are on the screen at the same time. Specifically, if your toggle button is on the "overflow popup" in a toolbar, then there will be two popups open after you click it. You may then find that the second popup (your popup) will stay open when you click anywhere else on your window. At that point, closing the popup is difficult. The user cannot click the ToggleButton again to close the popup because IsHitTestVisible is false because the popup is open! In my app I had to use a few hacks to mitigate this problem, such as the following test on the main window, which says (in the voice of Louis Black) "if the popup is open and the user clicks somewhere outside the popup, close the friggin' popup.":

PreviewMouseDown += (s, e) =>
{
    // Workaround for popup not closing automatically when 
    // two popups are on-screen at once.
    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;
like image 57
Qwertie Avatar answered Oct 18 '22 21:10

Qwertie


The following uses EventTrigger to show the Popup. This means we don't need a ToggleButton for state binding. In this example the Click event of a Button is used. You can adapt it to use another element/event combination.

<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>

Please note that the Popup is referencing the Button by name and vice versa. So x:Name="..." is required on both, the Popup and the Button.

It can actually be further simplified by replacing the Storyboard stuff with a custom SetProperty EventTrigger Action described in this SO Answer

like image 17
BatteryBackupUnit Avatar answered Oct 18 '22 23:10

BatteryBackupUnit