基于实际宽度的可见性在网格中表现得很奇怪

本文关键字:网格 于实际 可见性 | 更新日期: 2023-09-27 17:55:02

我试图理解如何将子元素的可见性绑定到父元素的大小。我的策略是构建一个具有int阈值(它将与父控件的宽度相对应)的转换器,并基于该阈值决定子元素是否可见:

public class IntToVisibilityConverter : IValueConverter
{
    public IntToVisibilityConverter()
    {
        FalseVisibility = Visibility.Collapsed;
        Negate = false;
        VisibilityThreshold = 0;
    }
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int iVal;
        bool result = int.TryParse(value.ToString(), out iVal);
        if (!result) return value;
        bool isVisible;
        if (iVal < VisibilityThreshold)
        {
            isVisible = false;
        }
        else
        {
            isVisible = true;
        }
        isVisible = Negate ? !isVisible : isVisible;
        if (isVisible)
        {
            return Visibility.Visible;
        }
        else
        {
            return FalseVisibility;
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Visibility? val = null;
        if (value is Visibility) val = (Visibility)value;
        if (!val.HasValue) return value;
        bool result = val == Visibility.Visible;
        result = Negate ? !result : result;
        if (result)
        {
            return VisibilityThreshold;
        }
        else
        {
            return VisibilityThreshold - 1;
        }
    }
    public bool Negate { get; set; }
    public int VisibilityThreshold { get; set; }
    public Visibility FalseVisibility { get; set; }
}

为了测试它是如何工作的,我构建了一个非常简单的UI,看起来像这样:

<Window.Resources>
    <ResourceDictionary>
        <local:IntToVisibilityConverter x:Key="IntConverter" VisibilityThreshold="600" Negate="True" />
    </ResourceDictionary>
</Window.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="50" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Button Grid.Column="1" x:Name="button1">
        <Ellipse Fill="Orange" Width="100" Height="100" Visibility="{Binding ElementName=button1, Path=ActualWidth, Converter={StaticResource IntConverter}}" />
    </Button>
</Grid>

这工作得很好,没有问题-当我拉伸窗口和按钮达到一定的宽度时,椭圆消失,当我对比窗口回来时重新出现。

然而,当我像这样向网格中添加另一列时,我遇到了奇怪的行为:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="50" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Button Grid.Column="1" x:Name="button1">
        <Ellipse Fill="Orange" Width="100" Height="100" Visibility="{Binding ElementName=button1, Path=ActualWidth, Converter={StaticResource IntConverter}}" />
    </Button>
    <Button Grid.Column="2" x:Name="button2">
        <Ellipse Fill="DarkOrange" Width="100" Height="100" Visibility="{Binding ElementName=button2, Path=ActualWidth, Converter={StaticResource IntConverter}}" />
    </Button>
</Grid>

现在,当我运行这个应用程序时,如果我展开窗口,使按钮的宽度超过阈值,而不是椭圆消失,它们开始闪烁。就像每次改变宽度,它们只是来回改变它们的可见性,而不是永久地折叠。

有谁能解释一下为什么它会这样做,我怎么做才能使它不会出现意外?

谢谢。

基于实际宽度的可见性在网格中表现得很奇怪

主要错误是您的转换器试图使用int进行数学计算,即使WPF中的所有维度指标都是double。即使在所谓的"工作"中;例如,只有当窗口宽度为精确整数时,椭圆才会消失。否则,转换器在TryParse()方法上失败,然后返回Visibility属性的完全无意义的值(即最初传递给它的double)。

我改变了你的转换器,所以它看起来像这样,两个例子现在对我来说都很好:

public class IntToVisibilityConverter : IValueConverter
{
    public IntToVisibilityConverter()
    {
        FalseVisibility = Visibility.Collapsed;
        Negate = false;
        VisibilityThreshold = 0;
    }
    public object Convert(object valueObject, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        double value = (double)valueObject;
        bool isVisible;
        if (value < VisibilityThreshold)
        {
            isVisible = false;
        }
        else
        {
            isVisible = true;
        }
        isVisible = Negate ? !isVisible : isVisible;
        if (isVisible)
        {
            //System.Diagnostics.Debug.WriteLine("isVisible");
            return Visibility.Visible;
        }
        else
        {
            //System.Diagnostics.Debug.WriteLine("NOT isVisible");
            return FalseVisibility;
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Visibility? val = null;
        if (value is Visibility) val = (Visibility)value;
        if (!val.HasValue) return value;
        bool result = val == Visibility.Visible;
        result = Negate ? !result : result;
        if (result)
        {
            return VisibilityThreshold;
        }
        else
        {
            return VisibilityThreshold - 1;
        }
    }
    public bool Negate { get; set; }
    public double VisibilityThreshold { get; set; }
    public Visibility FalseVisibility { get; set; }
}

如果您愿意,您可以对valueObject参数进行额外检查,如果valueObject is double失败,则返回Binding.DoNothing。我认为这是不必要的,但它可以避免转换器在初始化期间抛出异常,如果您出于某种原因将DependencyProperty.UnsetValue之类的值作为值。

我要注意的是,原来的逻辑比它需要的要冗长一些,新的模式匹配和字符串插值特性在编写上面的原始代码时也可以提供帮助。一个更简洁但更有表现力和可读性的转换器方法的版本可能是这样的:

    public object Convert(object valueObject, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        double value = (double)valueObject;
        bool isVisible = (value >= VisibilityThreshold) ^ Negate;
        //System.Diagnostics.Debug.WriteLine($"{(isVisible ? "" : "NOT ")}isVisible");
        return isVisible ? Visibility.Visible : FalseVisibility;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is not Visibility val) return value;
        bool result = (val == Visibility.Visible) ^ Negate;
        return result ? VisibilityThreshold : VisibilityThreshold - 1;
    }