Привязка к ActualWidth не работает
В приложении 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>
Ответы
Ответ 1
Что вы пытаетесь сделать, это требует от вас привязки данных к свойству ActualWidth
?
Это известная проблема с Silverlight, и нет простого способа обхода.
Одна вещь, которую можно сделать, - настроить визуальное дерево таким образом, чтобы вам не нужно было устанавливать ширину прямоугольника и просто разрешать растягиваться до соответствующего размера. Итак, в приведенном выше примере, если вы удалите Canvas (или измените Canvas на какую-либо другую панель) и оставьте Rectangle
HorizontalAlignment
установленным на Stretch
, он займет всю доступную ширину (фактически ширина Width 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 элемента, который предоставляется другим Binding.
Это не простое решение проблемы, но это лучшее, что я могу придумать для того, как привязать данные к ActualWidth.
Если вы объясните свой сценарий немного больше, возможно, появится более простое решение. DataBinding может не потребоваться вообще; можно ли просто установить свойство из кода в обработчике событий SizeChanged?
Ответ 2
Используя механизм прикрепленных свойств, свойства, которые представляют 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);
}
}
Ответ 3
Слишком поздно я знаю, но просто боролся с этой проблемой. Мое решение - объявить свой собственный 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;
}
Ответ 4
Почему бы не создать простой элемент управления панели, который наследует от 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>
Работал для меня как шарм и выглядел чистым.
Ответ 5
Основываясь на @darutk answer, здесь представлено прилагаемое решение на основе свойств, которое делает работу очень элегантно.
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>
Ответ 6
Я тестировал обновленный 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.
Вы ожидали чего-то другого?
Ответ 7
Это ответ в стороне, который может помочь кому-то привязать к 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.
Ответ 8
На основе KeithMahoney ответ он отлично работает в моем приложении UWP и решает мою проблему. Тем не менее, я не могу видеть свой контроль во время разработки, потому что начальные значения ActualWidthValue и ActualHeightValue не указаны во время разработки. Хотя он отлично работает во время работы, неудобно разрабатывать макет моего элемента управления. С небольшим изменением эта проблема может быть решена.
-
В своем коде С# для обоих свойств ActualWidthValue и ActualHeightValue добавьте
set {;}
чтобы мы могли предоставить фиктивные значения из кода XAML. Хотя это время не подходит для времени работы, его можно использовать для времени разработки.
-
В объявлении Ресурсы его XAML-кода укажите c: ActualSizePropertyProxy подходящие значения для ActualWidthValue и ActualHeightValue, например
ActualHeightValue = "800" ActualWidthValue = "400"
Затем он покажет вам управление размером 400x800 во время разработки.