Привязка к ActualWidth не работает - PullRequest
24 голосов
/ 21 октября 2009

В приложении 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 ]

31 голосов
/ 22 октября 2009

Что вы пытаетесь сделать, что требует привязки данных к свойству ActualWidth? Это известная проблема с Silverlight, и не существует простого обходного пути.

Одна вещь, которую можно сделать, это настроить визуальное дерево таким образом, чтобы вам не нужно было фактически устанавливать ширину прямоугольника и просто позволять ему растягиваться до соответствующего размера. Таким образом, в приведенном выше примере, если вы удалите Canvas (или измените Canvas на другую панель) и оставите HorizontalAlignment s *1005* установленным на Stretch, он займет всю доступную ширину (фактически Ширина сетки).

Однако в вашем конкретном случае это может оказаться невозможным, и может потребоваться установить привязку данных. Уже было установлено, что это невозможно напрямую, но с помощью прокси-объекта мы можем установить требуемую привязку. Рассмотрим этот код:

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?

21 голосов
/ 28 июля 2011

Используя механизм прикрепленных свойств , можно определить свойства, которые представляют ActualHeight и ActualWidth и обновляются событием 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 / связывании-ActualHeight-и-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);
     }
 }
8 голосов
/ 15 ноября 2010

Слишком поздно, я знаю, но только боролся с этой проблемой. Мое решение состоит в том, чтобы объявить мой собственный 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;
}
5 голосов
/ 12 октября 2011

Почему бы не создать простой элемент управления панели, который наследуется от 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>

Сработал для меня как шарм и выглядит чисто.

2 голосов
/ 29 марта 2013

Основываясь на ответе @ 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 голос
/ 21 октября 2009

Я протестировал обновленный xaml, который вы публикуете с помощью TestConverter, чтобы увидеть, какое значение передается по ширине и работает ли оно для меня (я использую 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 голосов
/ 14 октября 2016

Основываясь на ответе KeithMahoney , он отлично работает в моем приложении UWP и решает мою проблему. Однако я не вижу свой элемент управления во время разработки, поскольку оба начальных значения ActualWidthValue и ActualHeightValue не предоставляются во время разработки. Хотя он отлично работает во время выполнения, это неудобно для разработки макета моего элемента управления. Небольшая модификация позволяет решить эту проблему.

  1. В своем коде c # для обоих свойств ActualWidthValue и ActualHeightValue , добавьте

    set {;}

    чтобы мы могли предоставить фиктивные значения из кода XAML. Хотя он не используется для времени выполнения, он может использоваться для времени разработки.

  2. В объявлении ресурсов его XAML-кода укажите c: ActualSizePropertyProxy подходящие значения для ActualWidthValue и ActualHeightValue такие как

    ActualHeightValue = "800" ActualWidthValue = "400"

    Тогда он покажет вам элемент управления 400x800 во время разработки.

0 голосов
/ 17 июля 2015

Это в качестве ответа , который может помочь кому-то для привязки к 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"/>

Код для получения

double width;

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