2009-10-21 3 views
23

В приложении Silverlight 3.0 я пытаюсь создать прямоугольник в холсте и растянуть всю ширину холста. Я попытался сделать это, привязав к свойству ActualWidth родительского контейнера (пример ниже), однако пока я не вижу никаких ошибок привязки, значение не связано. Прямоугольник не отображается, так как его ширина равна нулю. Кроме того, попробовал привязать к ActualWidth холста, который содержит мой прямоугольник, но это не имело никакого значения.Привязка к ActualWidth не работает

Я сделал find this bug logged on 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> 

ответ

31

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

Одна вещь, которую можно сделать, это настроить визуальное дерево таким образом, чтобы вам не нужно было устанавливать ширину прямоугольника и просто разрешать растягиваться до соответствующего размера. Итак, в приведенном выше примере, если вы удалите Canvas (или измените Canvas на какую-либо другую панель) и оставьте , установленный на 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> 

Таким образом, мы Binding TextBlock.Text к ActualWidthValue прокси-объекта. Объект-посредник, в свою очередь, предоставляет ActualWidth элемента, который предоставляется другим Binding.

Это не просто решение проблемы, но это лучшее, что я могу придумать для привязки данных к ActualWidth.

Если вы объяснили свой сценарий немного больше, может возникнуть проблема с более простым решением. DataBinding может не потребоваться вообще; можно ли просто установить свойство из кода в обработчик события SizeChanged?

+0

Я использовал подход, предложенный с помощью обработчика события SizeChanged, и я получаю желаемый эффект.Чтобы объяснить сценарий немного больше, мне нужно было привязать к свойству ActualWidth, поскольку у меня есть бит нечетного дизайна пользовательского интерфейса, который требует, чтобы некоторые элементы отображались вне границ элемента управления. –

+2

Более 5 лет и по-прежнему работает под WinRT ;-) - с одним небольшим изменением: новый SizeEventHandler (Element_SizeChanged) должен быть заменен непосредственно Element_SizeChanged. – TheEye

1

Я проверил обновленный 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

Вы считаете это правильным, начиная с 0, однако, поскольку ActualWidth является свойством зависимости, при его изменении должно быть уведомление. Я не могу привязываться к Шире, поскольку он никогда не устанавливается для элемента управления и в результате возвращает double.NaN –

+0

Вы правы, это свойство зависимости, поэтому оно должно измениться. Однако вы устанавливаете ширину в Xaml выше. – Bryant

+0

Приносим извинения, что это было неряшливо на моем конце, поскольку я только что скопировал фрагмент из песочницы, где я работал, чтобы решить вопрос, который у меня есть. Я исправил ошибку и обновил образец, чтобы лучше описать мою ситуацию. –

8

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

Использование механизма прикрепленных свойств, свойства, которые представляют собой 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/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

Это работает во время выполнения, но дает мне ошибку «значение не входит в ожидаемый диапазон» в дизайнере визуальной студии –

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> 

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

+0

очень хорошо, используя StackPanel в качестве базы, решил мою проблему. Когда вы используете ContentPresenter, вам не удается проникнуть внутрь элементов по имени и установить их значения. –

+0

Очень хорошее и чистое решение, используется в качестве родителя в моем случае – deafsheep

2

Основано на answer @ 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> 
0

Это как в сторону ответ, который может помочь кому-то за связывание с ActualWidth.

Мой процесс не нуждался в событии изменения, ему нужен конечный результат значения в его текущем состоянии. Поэтому я создал свойство зависимостей под названием Target на моем пользовательском элементе управления/процессе как FrameworkElement, и потребительский xaml привязал бы к фактическому объекту, о котором идет речь.

Когда пришло время для вычисления, код мог вытащить фактический объект и извлечь из него ActualWidth.


Dependency Property по контролю

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

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

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

    множество {}

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

  2. В декларации ресурсов его XAML кода, обеспечивают C: ActualSizePropertyProxy подходящие значения для ActualWidthValue и ActualHeightValue, такие как

    ActualHeightValue = "800" ActualWidthValue = "400 "

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

Смежные вопросы