绑定 ActualWidth 不起作用

24
在一个 Silverlight 3.0 应用程序中,我试图在画布中创建一个矩形,并使其占据整个画布的宽度。我尝试通过绑定父容器的 ActualWidth 属性来实现此目标(请参见下面的示例),但是虽然我没有看到任何绑定错误,但值没有被绑定。由于矩形的宽度为零,因此矩形不可见。此外,我还尝试将矩形所在画布的 ActualWidth 绑定到该属性,但这没有任何效果。
我在 Microsoft Connect 上发现了此错误记录,但没有列出解决方法。
有人能够解决此问题或者指出解决方案吗?
编辑:原始代码示例不准确,已更新以增加更多的清晰度。
<UserControl>
    <Border BorderBrush="White"
            BorderThickness="1"
            CornerRadius="4"
            HorizontalAlignment="Center">
        <Grid x:Name="GridContainer">
            <Rectangle Fill="Aqua"
                       Width="150"
                       Height="400" />
            <Canvas>
                <Rectangle Width="{Binding Path=ActualWidth, ElementName=GridContainer}"
                           Height="30"
                           Fill="Red" />
            </Canvas>

            <StackPanel>
                <!-- other elements here -->
            </StackPanel>
        </Grid>
    </Border>
</UserControl>
8个回答

32
你想要做什么需要绑定到ActualWidth属性吗?这是Silverlight的已知问题,没有简单的解决方法。
其中一个解决方法是以这样一种方式设置可视树,使您不需要实际设置矩形的宽度,而只需允许其自动拉伸到适当的大小。因此,在上面的示例中,如果您删除Canvas(或将Canvas更改为其他Panel)并保留RectangleHorizontalAlignment设置为Stretch,它将占用所有可用宽度(有效地是Grid的宽度)。
然而,在您的特定情况下可能无法这样做,确实有必要设置数据绑定。已经确定直接绑定是不可能的,但借助代理对象,我们可以设置所需的绑定。请考虑以下代码:
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public FrameworkElement Element
    {
        get { return (FrameworkElement)GetValue(ElementProperty); }
        set { SetValue(ElementProperty, value); }
    }

    public double ActualHeightValue
    {
        get{ return Element == null? 0: Element.ActualHeight; }
    }

    public double ActualWidthValue
    {
        get { return Element == null ? 0 : Element.ActualWidth; }
    }

    public static readonly DependencyProperty ElementProperty =
        DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(ActualSizePropertyProxy), 
                                    new PropertyMetadata(null,OnElementPropertyChanged));

    private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((ActualSizePropertyProxy)d).OnElementChanged(e);
    }

    private void OnElementChanged(DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement oldElement = (FrameworkElement)e.OldValue;
        FrameworkElement newElement = (FrameworkElement)e.NewValue;

        newElement.SizeChanged += new SizeChangedEventHandler(Element_SizeChanged);
        if (oldElement != null)
        {
            oldElement.SizeChanged -= new SizeChangedEventHandler(Element_SizeChanged);
        }
        NotifyPropChange();
    }

    private void Element_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        NotifyPropChange();
    }

    private void NotifyPropChange()
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
            PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
        }
    }
}
我们可以在XAML中使用以下代码:
<Grid x:Name="LayoutRoot">
    <Grid.Resources>
        <c:ActualSizePropertyProxy Element="{Binding ElementName=LayoutRoot}" x:Name="proxy" />
    </Grid.Resources>
    <TextBlock x:Name="tb1" Text="{Binding ActualWidthValue, ElementName=proxy}"  />
</Grid>

我们将 TextBlock.Text 绑定到代理对象的 ActualWidthValue 上。代理对象反过来提供元素的 ActualWidth,该宽度由另一个绑定提供。

这不是解决问题的简单方法,但这是我能想到的绑定 ActualWidth 的最佳方法。

如果您能更详细地说明您的情况,可能会找到更简单的解决方案。也许根本不需要数据绑定;是否可能只需在 SizeChanged 事件处理程序中从代码设置属性?


我采用了你建议的SizeChanged事件处理程序方法,而且已经达到了预期效果。为了更好地解释情况,我需要绑定到ActualWidth属性,因为我的UI设计有点奇怪,需要一些元素出现在控件边界之外。 - Richard McGuire
2
已经超过5年了,仍然可以在WinRT下运行;-) - 只需进行一个小更改:将new SizeEventHandler(Element_SizeChanged)替换为Element_SizeChanged即可。 - TheEye

21

通过使用附加属性机制,可以定义代表ActualHeightActualWidth的属性,并通过SizeChanged事件进行更新。其用法如下所示。

<Grid local:SizeChange.IsEnabled="True" x:Name="grid1">...</Grid>

<TextBlock Text="{Binding ElementName=grid1,
                         Path=(local:SizeChange.ActualHeight)}"/>

以下是技术细节:

http://darutk-oboegaki.blogspot.com/2011/07/binding-actualheight-and-actualwidth.html

与其他解决方案相比,此解决方案的优点在于所定义的附加属性(SizeChange.ActualHeight 和 SizeChange.ActualWidth)可用于任何 FrameworkElement,而无需创建任何子类。 这个解决方案可重复使用且影响较小。


如果链接失效,请参考以下链接中的 SizeChange 类:

// Declare SizeChange class as a sub class of DependencyObject

// because we need to register attached properties.
public class SizeChange : DependencyObject
 {
     #region Attached property "IsEnabled"

    // The name of IsEnabled property.
    public const string IsEnabledPropertyName = "IsEnabled";

    // Register an attached property named "IsEnabled".
    // Note that OnIsEnabledChanged method is called when
    // the value of IsEnabled property is changed.
    public static readonly DependencyProperty IsEnabledProperty
         = DependencyProperty.RegisterAttached(
             IsEnabledPropertyName,
             typeof(bool),
             typeof(SizeChange),
             new PropertyMetadata(false, OnIsEnabledChanged));

    // Getter of IsEnabled property. The name of this method
    // should not be changed because the dependency system
    // uses it.
    public static bool GetIsEnabled(DependencyObject obj)
     {
         return (bool)obj.GetValue(IsEnabledProperty);
     }

    // Setter of IsEnabled property. The name of this method
    // should not be changed because the dependency system
    // uses it.
    public static void SetIsEnabled(DependencyObject obj, bool value)
     {
         obj.SetValue(IsEnabledProperty, value);
     }

     #endregion

     #region Attached property "ActualHeight"

    // The name of ActualHeight property.
    public const string ActualHeightPropertyName = "ActualHeight";

    // Register an attached property named "ActualHeight".
    // The value of this property is updated When SizeChanged
    // event is raised.
    public static readonly DependencyProperty ActualHeightProperty
         = DependencyProperty.RegisterAttached(
             ActualHeightPropertyName,
             typeof(double),
             typeof(SizeChange),
             null);

    // Getter of ActualHeight property. The name of this method
    // should not be changed because the dependency system
    // uses it.
    public static double GetActualHeight(DependencyObject obj)
     {
         return (double)obj.GetValue(ActualHeightProperty);
     }

    // Setter of ActualHeight property. The name of this method
    // should not be changed because the dependency system
    // uses it.
    public static void SetActualHeight(DependencyObject obj, double value)
     {
         obj.SetValue(ActualHeightProperty, value);
     }

     #endregion

     #region Attached property "ActualWidth"

    // The name of ActualWidth property.
    public const string ActualWidthPropertyName = "ActualWidth";

    // Register an attached property named "ActualWidth".
    // The value of this property is updated When SizeChanged
    // event is raised.
    public static readonly DependencyProperty ActualWidthProperty
         = DependencyProperty.RegisterAttached(
             ActualWidthPropertyName,
             typeof(double),
             typeof(SizeChange),
             null);

    // Getter of ActualWidth property. The name of this method
    // should not be changed because the dependency system
    // uses it.
    public static double GetActualWidth(DependencyObject obj)
     {
         return (double)obj.GetValue(ActualWidthProperty);
     }

    // Setter of ActualWidth property. The name of this method
    // should not be changed because the dependency system
    // uses it.
    public static void SetActualWidth(DependencyObject obj, double value)
     {
         obj.SetValue(ActualWidthProperty, value);
     }

     #endregion

    // This method is called when the value of IsEnabled property
    // is changed. If the new value is true, an event handler is
    // added to SizeChanged event of the target element.
    private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
     {
        // The given object must be a FrameworkElement instance,
        // because we add an event handler to SizeChanged event
        // of it.
        var element = obj as FrameworkElement;

         if (element == null)
         {
            // The given object is not an instance of FrameworkElement,
            // meaning SizeChanged event is not available. So, nothing
            // can be done for the object.
            return;
         }

        // If IsEnabled=True
        if (args.NewValue != null && (bool)args.NewValue == true)
         {
             // Attach to the element.
             Attach(element);
         }
         else
         {
             // Detach from the element.
             Detach(element);
         }
     }

     private static void Attach(FrameworkElement element)
     {
        // Add an event handler to SizeChanged event of the element

        // to take action when actual size of the element changes.
        element.SizeChanged += HandleSizeChanged;
     }

     private static void Detach(FrameworkElement element)
     {
        // Remove the event handler from the element.
        element.SizeChanged -= HandleSizeChanged;
     }

    // An event handler invoked when SizeChanged event is raised.
    private static void HandleSizeChanged(object sender, SizeChangedEventArgs args)
     {
         var element = sender as FrameworkElement;

         if (element == null)
         {
             return;
         }

        // Get the new actual height and width.
        var width  = args.NewSize.Width;
         var height = args.NewSize.Height;

        // Update values of SizeChange.ActualHeight and

        // SizeChange.ActualWidth.
        SetActualWidth(element, width);
         SetActualHeight(element, height);
     }
 }

1
这在运行时可以工作,但在 Visual Studio 设计器中会给我一个“值不在预期范围内”的错误。 - stefan.s

9
我的解决方案是声明自己的“DependencyProperty”(依赖属性)叫做RealWidth,并在“SizeChanged”事件上更新它的值。然后你可以绑定到RealWidth,它会更新,而ActualWidth属性不会更新。
public MyControl()
{
    InitializeComponent();
    SizeChanged += HandleSizeChanged;
}

public static DependencyProperty RealWidthProperty =
     DependencyProperty.Register("RealWidth", typeof (double),
     typeof (MyControl),
     new PropertyMetadata(500D));

public double RealWidth
{
    get { return (double) GetValue(RealWidthProperty); }
    set { SetValue(RealWidthProperty, value); }
}

private void HandleSizeChanged(object sender, SizeChangedEventArgs e)
{
    RealWidth = e.NewSize.Width;
}

我认为一种变化是重写 OnRenderSizeChanged() 方法;这样可以避免向对象的自身事件添加处理程序。 - StayOnTarget

5
为什么不创建一个简单的面板控件,继承自ContentPresenter并且实际上可以提供当前大小。
public class SizeNotifyPanel : ContentPresenter
{
    public static DependencyProperty SizeProperty =
        DependencyProperty.Register("Size",
                                    typeof (Size),
                                    typeof (SizeNotifyPanel),
                                    null);

    public Size Size
    {
        get { return (Size) GetValue(SizeProperty); }
        set { SetValue(SizeProperty, value); }
    }

    public SizeNotifyPanel()
    {
        SizeChanged += (s, e) => Size = e.NewSize;
    }
}

然后,它应作为实际内容的包装器使用。
<local:SizeNotifyPanel x:Name="Content">
    <TextBlock Text="{Binding Size.Height, ElementName=Content}" />
</local:SizeNotifyPanel>

对我很有效,看起来很干净。


非常好,使用StackPanel作为基础解决了我的问题。当您使用ContentPresenter时,会遇到通过名称访问内部元素并设置其值的困难。 - Davut Gürbüz

2
基于@darutk的答案,这里提供了一种基于附加属性的解决方案,非常优雅地完成了工作。
public static class SizeBindings
{
    public static readonly DependencyProperty ActualHeightProperty =
        DependencyProperty.RegisterAttached("ActualHeight", typeof (double), typeof (SizeBindings),
                                            new PropertyMetadata(0.0));

    public static readonly DependencyProperty ActualWidthProperty =
        DependencyProperty.RegisterAttached("ActualWidth", typeof (Double), typeof (SizeBindings),
                                            new PropertyMetadata(0.0));

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof (bool), typeof (SizeBindings),
                                            new PropertyMetadata(false, HandlePropertyChanged));

    private static void HandlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = d as FrameworkElement;
        if (element == null)
        {
            return;
        }

        if ((bool) e.NewValue == false)
        {
            element.SizeChanged -= HandleSizeChanged;
        }
        else
        {
            element.SizeChanged += HandleSizeChanged;
        }
    }

    private static void HandleSizeChanged(object sender, SizeChangedEventArgs e)
    {
        var element = sender as FrameworkElement;

        SetActualHeight(element, e.NewSize.Height);
        SetActualWidth(element, e.NewSize.Width);
    }

    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }

    public static Double GetActualWidth(DependencyObject obj)
    {
        return (Double) obj.GetValue(ActualWidthProperty);
    }

    public static void SetActualWidth(DependencyObject obj, Double value)
    {
        obj.SetValue(ActualWidthProperty, value);
    }

    public static double GetActualHeight(DependencyObject obj)
    {
        return (double)obj.GetValue(ActualHeightProperty);
    }

    public static void SetActualHeight(DependencyObject obj, double value)
    {
        obj.SetValue(ActualHeightProperty, value);
    }
}

使用方法如下:
    <Grid>
        <Border x:Name="Border" behaviors:SizeBindings.IsEnabled="True"/>
        <Border MinWidth="{Binding (behaviors:SizeBindings.ActualWidth), ElementName=Border}"/>
    </Grid>

1

我已经使用TestConverter测试了您发布的更新XAML,以查看传递给宽度的值,并且对我有效(我正在使用VS 2010 B2)。要使用TestConverter,只需在Convert方法中设置断点。

    public class TestConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return value;
        }

    }

传入了一个值为150,而矩形的宽度也是150。

您是否期望得到不同的结果?


你关于它从0开始是正确的,但由于ActualWidth是一个依赖属性,所以当它改变时应该有通知。我无法绑定到Width,因为它从未为控件设置,结果返回double.NaN。 - Richard McGuire
你是对的,它是一个依赖属性,所以它应该会发生变化。然而,你在上面的Xaml中设置了宽度。 - Bryant
抱歉,我这边有些粗心了,因为我只是从沙盒中复制了片段来解决我遇到的问题。我已经修复了错误,并更新了示例以更好地描述我的情况。 - Richard McGuire
我已经按照你的建议做了,我的断点只被触发了一次,在转换器中给出的值为0。 - Richard McGuire

0

根据KeithMahoney的回答,它在我的UWP应用程序上运行良好并解决了我的问题。但是,在设计时间中我无法看到我的控件,因为ActualWidthValueActualHeightValue的初始值在设计时间中未提供。虽然它在运行时正常工作,但对于设计控件布局来说不太方便。通过一些修改,可以解决这个问题。

  1. 在他的C#代码中,对于属性ActualWidthValueActualHeightValue,添加

    set {;}

    以便我们可以从XAML代码提供虚拟值。虽然在运行时没有用处,但可以在设计时使用。

  2. 在他的XAML代码的Resources声明中,为ActualWidthValueActualHeightValue提供适当的值,例如

    ActualHeightValue="800" ActualWidthValue="400"

    然后它将在设计时显示一个400x800的控件。


0

这是一个旁注式的答案,可能会帮助某些人绑定到ActualWidth

我的过程不需要更改事件,它需要当前状态下的值的最终结果。因此,我在自定义控件/过程上创建了一个名为Target的依赖属性,作为FrameworkElement,消费者xaml将绑定到实际的对象。

当计算时间到达时,代码可以提取实际对象并从中提取其ActualWidth


控件上的依赖属性
public FrameworkElement Target
{
    get { return (FrameworkElement)GetValue(TargetProperty);}
    set { SetValue(TargetProperty, value);}
}

// Using a DependencyProperty as the backing store for Target.
// This enables animation, styling, binding, general access etc...
public static readonly DependencyProperty TargetProperty =
    DependencyProperty.Register("Target", typeof(FrameworkElement), 
                                typeof(ThicknessWrapper), 
                                new PropertyMetadata(null, OnTargetChanged));

消费者端的XAML显示了对矩形的绑定。
<local:ThicknessWrapper Target="{Binding ElementName=thePanel}"/>

<Rectangle x:Name="thePanel" HorizontalAlignment="Stretch" Height="20"  Fill="Blue"/>

Code to Acquire

double width;

if (Target != null)
   width = Target.ActualWidth;  // Gets the current value.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接