silverlight - La vinculación a ActualWidth no funciona
(8)
¿Por qué no crear un control de panel simple que hereda de ContentPresenter
y realmente puede proporcionar el tamaño actual?
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;
}
}
Debería utilizarse como contenedor para el contenido real.
<local:SizeNotifyPanel x:Name="Content">
<TextBlock Text="{Binding Size.Height, ElementName=Content}" />
</local:SizeNotifyPanel>
Trabajó para mí como un encanto y se ve limpio.
En una aplicación de Silverlight 3.0, intento crear un rectángulo en un lienzo y hacer que se extienda todo el ancho del lienzo. ActualWidth
hacer esto atando a la propiedad ActualWidth
de un contenedor padre (parece ejemplo a continuación), sin embargo, mientras no veo ningún error vinculante, el valor no está vinculado. El rectángulo no es visible ya que su ancho es cero. Además intenté enlazar con el ActualWidth
del lienzo que contiene mi rectángulo, pero esto no hizo diferencia.
Encontré este error registrado en Microsoft Connect pero no había soluciones alternativas.
¿Alguien ha podido resolver este problema o pueden señalar una solución?
Editar: El ejemplo del código original no era exacto de lo que intento lograr, actualizado para mayor claridad.
<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>
¿Qué estás tratando de hacer que requiere una ActualWidth
datos a la propiedad ActualWidth
? Este es un problema conocido con Silverlight, y no hay una solución simple.
Una cosa que se puede hacer es configurar el árbol visual de tal forma que no necesite establecer el ancho del rectángulo, y simplemente permitir que se extienda hasta el tamaño apropiado. Por lo tanto, en el ejemplo anterior, si quita el lienzo (o cambia el lienzo a otro panel) y deja la Rectangle
HorizontalAlignment
del Rectangle
establecida en Stretch
, ocupará todo el ancho disponible (efectivamente el ancho de la rejilla).
Sin embargo, esto puede no ser posible en su caso particular, y realmente puede ser necesario configurar el enlace de datos. Ya se ha establecido que esto no es posible directamente, pero con la ayuda de un objeto proxy, podemos configurar el enlace requerido. Considera este código:
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"));
}
}
}
Podemos usar esto en xaml de la siguiente manera:
<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>
Así que estamos Vinculando TextBlock.Text al ActualWidthValue en el objeto proxy. El objeto proxy a su vez proporciona el ancho real del elemento, que es proporcionado por otro enlace.
Esta no es una solución simple al problema, pero es lo mejor que se me ocurre para cómo enlazar datos con ActualWidth.
Si explicó su escenario un poco más, puede ser posible encontrar una solución más simple. DataBinding puede no ser necesario en absoluto; ¿sería posible simplemente configurar la propiedad del código en un controlador de eventos SizeChanged?
Basado en la respuesta de , funciona bien en mi aplicación UWP y resuelve mi problema. Sin embargo, no puedo ver mi control en tiempo de diseño porque los valores iniciales de ActualWidthValue y ActualHeightValue no se proporcionan en tiempo de diseño. Aunque funciona bien en tiempo de ejecución, no es conveniente diseñar el diseño de mi control. Con una pequeña modificación, este problema puede ser resuelto.
En su código c # para ambas propiedades, ActualWidthValue y ActualHeightValue , agregue
establecer {;}
para permitirnos proporcionar valores ficticios del código XAML. Aunque no sirve para el tiempo de ejecución, puede usarse para el tiempo de diseño.
En la declaración de Recursos de su código XAML, proporcione los valores adecuados de c: ActualSizePropertyProxy para ActualWidthValue y ActualHeightValue , como
ActualHeightValue = "800" ActualWidthValue = "400"
Luego le mostrará un control de 400x800 en tiempo de diseño.
Demasiado tarde, lo sé, pero he estado luchando con este problema. Mi solución es declarar mi propia DependencyProperty
llamada RealWidth y actualizar su valor en el evento SizeChanged
. A continuación, puede enlazar a RealWidth, que se actualizará, a diferencia de la propiedad 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;
}
Esta es una respuesta aparte que puede ayudar a alguien a unirse al ActualWidth
.
Mi proceso no necesitaba un evento de cambio, necesitaba un resultado final de un valor en su estado actual. Así que creé una propiedad de dependencia llamada Target
en mi control / proceso personalizado como FrameworkElement
y la xaml del consumidor se vincularía al objeto real en cuestión.
Cuando llegó el momento del cálculo, el código pudo extraer el objeto real y extraer su ActualWidth
.
Propiedad de dependencia en el control
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 en el lado del consumidor que muestra un enlace a un rectángulo
<local:ThicknessWrapper Target="{Binding ElementName=thePanel}"/>
<Rectangle x:Name="thePanel" HorizontalAlignment="Stretch" Height="20" Fill="Blue"/>
Código para adquirir
double width;
if (Target != null)
width = Target.ActualWidth; // Gets the current value.
He probado el xaml actualizado que publica utilizando TestConverter para ver qué valor pasa al ancho y funciona para mí (estoy usando VS 2010 B2). Para usar TestConverter, simplemente configure un punto de interrupción en el método 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;
}
}
Se pasó un valor de 150 y el rectángulo tuvo un ancho de 150.
¿Estabas esperando algo diferente?
Según la answer de @darutk, aquí hay una solución adjunta basada en propiedades que hace el trabajo muy elegantemente.
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);
}
}
Úselo así:
<Grid>
<Border x:Name="Border" behaviors:SizeBindings.IsEnabled="True"/>
<Border MinWidth="{Binding (behaviors:SizeBindings.ActualWidth), ElementName=Border}"/>
</Grid>
Usando el mecanismo de las propiedades adjuntas , se pueden definir las propiedades que representan ActualHeight
y ActualWidth
y se actualizan mediante el evento SizeChanged
. Su uso se verá como el siguiente.
<Grid local:SizeChange.IsEnabled="True" x:Name="grid1">...</Grid>
<TextBlock Text="{Binding ElementName=grid1,
Path=(local:SizeChange.ActualHeight)}"/>
Los detalles técnicos se pueden encontrar en lo siguiente:
http://darutk-oboegaki.blogspot.com/2011/07/binding-actualheight-and-actualwidth.html
La ventaja de esta solución en comparación con otras es que las propiedades adjuntas definidas en la solución (SizeChange.ActualHeight y SizeChange.ActualWidth) se pueden usar para cualquier FrameworkElement sin crear ninguna subclase. Esta solución es reutilizable y menos invasiva.
En caso de que el enlace se vuelva obsoleto, aquí está la Clase de cambio de tamaño como se muestra en el enlace:
// 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);
}
}