Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I correctly bind a Popup to a ToggleButton?

I am trying to do something that seems relatively simple and logic from a user interface level but I have one bug that is very annoying. I have a ToggleButton and I am trying to show a Popup when the button is toggled in and hide the Popup when the button is toggled out. The Popup also hides when the user clicks away from it.

Everything is working as expected with the following XAML except when I click the toggle button after the Popup is shown, the Popup disappears for a split second then reappears.

I suspect what's going on here is that clicking away from the Popup is causing it to toggle the button off then immediately after the button is toggled back on as the mouse clicks it. I just don't know how to go about fixing it.

Any help is appreciated. Thanks.

    <ToggleButton x:Name="TogglePopupButton" Content="My Popup Toggle Button" Width="100" />

    <Popup StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=TwoWay}">
        <Border Width="100" Height="200" Background="White" BorderThickness="1" BorderBrush="Black">
            <TextBlock>This is a test</TextBlock>
        </Border>                
    </Popup>
like image 619
craftworkgames Avatar asked Jan 10 '13 06:01

craftworkgames


3 Answers

Stephans answers has the disadvantage, that the desired behaviour of closing the popup whenever it loses focus also disappears.

I solved it by disabling the toggle-button when the popup is open. An alternative would be to use the IsHitTestVisible Property instead of is enabled:

    <ToggleButton x:Name="TogglePopupButton" Content="My Popup Toggle Button" Width="100"  IsEnabled="{Binding ElementName=ToggledPopup, Path=IsOpen, Converter={StaticResource BoolToInvertedBoolConverter}}"/>
    <Popup x:Name="ToggledPopup" StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=TwoWay}">
        <Border Width="100" Height="200" Background="White" BorderThickness="1" BorderBrush="Black">
            <TextBlock>This is a test</TextBlock>
        </Border>                
    </Popup>

The converter looks like this:

public class BoolToInvertedBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is bool)
        {
            bool boolValue = (bool)value;
            return !boolValue;
        }
        else
            return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException("ConvertBack() of BoolToInvertedBoolConverter is not implemented");
    }
}
like image 60
schoola Avatar answered Nov 14 '22 05:11

schoola


Solution without IValueConverter:

<Grid>
    <ToggleButton x:Name="TogglePopupButton" Content="My Popup Toggle Button" Width="100" >
        <ToggleButton.Style>
            <Style TargetType="{x:Type ToggleButton}">
                <Setter Property="IsHitTestVisible" Value="True"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ElementName=Popup, Path=IsOpen}" Value="True">
                        <Setter Property="IsHitTestVisible" Value="False"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ToggleButton.Style>
    </ToggleButton>

    <Popup StaysOpen="false" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=TwoWay}"
               PlacementTarget="{Binding ElementName=TogglePopupButton}" PopupAnimation="Slide" 
           x:Name="Popup">
        <Border Width="100" Height="200" Background="White" BorderThickness="1" BorderBrush="Black">
            <TextBlock>This is a test</TextBlock>
        </Border>
    </Popup>
</Grid>
like image 29
Maxim Prasolov Avatar answered Nov 14 '22 04:11

Maxim Prasolov


I faced the same problem. None of the answers offered here worked correctly.

After a little research, I can say that the suspicions of the author of the question are correct. During a mouse click, the first click (down) closes the popup and set togglebutton as unchecked, the second click (up) causes the observed action when the popup appears again.

The first way to avoid this problem is to discard the second click by delay:

<ToggleButton x:Name="UserPhotoToggleButton"/>

<Popup x:Name="UserInfoPopup"
       IsOpen="{Binding IsChecked, ElementName=UserPhotoToggleButton, Delay=200, Mode=TwoWay}"
       StaysOpen="False">

It looks simple enough to fix problem. Although it is not an ideal solution. The best way would be to extend the functionality of the popup by Behavior:

Add these namespaces

xmlns:behaviors="clr-namespace:SecurityScanner.WpfClient.Resources.Behaviors;assembly=SecurityScanner.WpfClient.Resources"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

then extend your popup by i:Interaction.Behaviors

<Popup x:Name="UserInfoPopup"
       StaysOpen="False">
      <i:Interaction.Behaviors>
            <behaviors:BindIconToggleButtonToPopupBehavior
                       DesiredToggleButton="{Binding ElementName=UserPhotoToggleButton}"/>
      </i:Interaction.Behaviors>
            <Border>
            <!--Your template-->
            </Border> 
</Popup>

Finally add the behavior. In a minimal form, it may look like this:

using Microsoft.Xaml.Behaviors;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace WpfClient.Resources.Behaviors
{
    public class BindToggleButtonToPopupBehavior : Behavior<Popup>
    {
        public ToggleButton DesiredToggleButton
        {
            get { return (ToggleButton)GetValue(DesiredToggleButtonProperty); }
            set { SetValue(DesiredToggleButtonProperty, value); }
        }

        public static readonly DependencyProperty DesiredToggleButtonProperty =
            DependencyProperty.Register(nameof(DesiredToggleButton), typeof(ToggleButton), typeof(BindIconToggleButtonToPopupBehavior), new PropertyMetadata(null));

        protected override void OnAttached()
        {
            base.OnAttached();
            
            DesiredToggleButton.Checked += DesiredToggleButton_Checked;
            DesiredToggleButton.Unchecked += DesiredToggleButton_Unchecked;

            AssociatedObject.Closed += AssociatedObject_Closed;
            AssociatedObject.PreviewMouseUp += AssociatedObject_PreviewMouseUp;
        }

        private void DesiredToggleButton_Unchecked(object sender, RoutedEventArgs e) => AssociatedObject.IsOpen = false;

        private void DesiredToggleButton_Checked(object sender, RoutedEventArgs e) => AssociatedObject.IsOpen = true;

        private void AssociatedObject_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.Source is Button)
                AssociatedObject.IsOpen = false;
        }

        private void AssociatedObject_Closed(object sender, EventArgs e)
        {
            if (DesiredToggleButton != Mouse.DirectlyOver)
                DesiredToggleButton.IsChecked = false;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            DesiredToggleButton.Checked -= DesiredToggleButton_Checked;
            DesiredToggleButton.Unchecked -= DesiredToggleButton_Unchecked;
            
            if (AssociatedObject != null)
            {
                AssociatedObject.Closed -= AssociatedObject_Closed;
                AssociatedObject.PreviewMouseUp -= AssociatedObject_PreviewMouseUp;
            }
        }
    }
}
like image 3
Andrey Dergach Avatar answered Nov 14 '22 04:11

Andrey Dergach