如何正确地将 Popup 绑定到 ToggleButton?

问题描述 投票:0回答:6

我正在尝试做一些从用户界面级别看来相对简单和逻辑的事情,但我有一个非常烦人的错误。我有一个

ToggleButton
,我试图在按钮切换时显示
Popup
,并在按钮切换时隐藏
Popup
。当用户点击远离它时,
Popup
也会隐藏。

使用以下 XAML 一切都按预期工作,除了当我在显示

Popup
后单击切换按钮时,
Popup
瞬间消失然后重新出现。

我怀疑这里发生的事情是,点击远离

Popup
会导致它关闭按钮,然后在鼠标单击按钮后立即重新打开按钮。我只是不知道如何修复它。

如有任何帮助,我们将不胜感激。谢谢。

    <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>
c# wpf xaml popup togglebutton
6个回答
45
投票

Stephans 的答案有一个缺点,即每当弹出窗口失去焦点时关闭弹出窗口的所需行为也会消失。

我通过在弹出窗口打开时禁用切换按钮解决了这个问题。另一种方法是使用 IsHitTestVisible 属性而不是启用:

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

转换器看起来像这样:

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

30
投票

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

3
投票

我也遇到了同样的问题。这里提供的答案都没有正确工作。

经过一番研究,我可以说问题作者的怀疑是正确的。在鼠标单击期间,第一次单击(向下)将关闭弹出窗口并将切换按钮设置为未选中,第二次单击(向上)会在弹出窗口再次出现时导致观察到的操作。

避免这个问题的第一个方法是通过延迟丢弃第二次点击:

<ToggleButton x:Name="UserPhotoToggleButton"/>

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

看起来很简单,可以解决问题。虽然这不是一个理想的解决方案。最好的方法是通过行为扩展弹出窗口的功能:

添加这些命名空间

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

然后通过 i:Interaction.Behaviors

扩展弹出窗口
<Popup x:Name="UserInfoPopup"
       StaysOpen="False">
      <i:Interaction.Behaviors>
            <behaviors:BindToggleButtonToPopupBehavior
                       DesiredToggleButton="{Binding ElementName=UserPhotoToggleButton}"/>
      </i:Interaction.Behaviors>
            <Border>
            <!--Your template-->
            </Border> 
</Popup>

最后添加行为。以最小的形式,它可能看起来像这样:

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

1
投票

在 ToggleButton 上设置属性

ClickMode="Press"
apixeltoofar


0
投票

怎么样

<Popup StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=oneway}">

绑定

Mode=TwoWay
使得控件可以互相更新状态


-1
投票

为您的

StaysOpen="True"
 设置 
Popup

来自MSDN

获取或设置一个值,该值指示 Popup 控件是否关闭 当控件不再处于焦点时。

[...]

true
如果当
Popup
属性设置为
IsOpen
false
控件关闭;

false
(如果在
Popup
控件外部发生鼠标或键盘事件时
Popup
控件关闭)。

© www.soinside.com 2019 - 2024. All rights reserved.