Silverlight Grid/StackPanel/Canvas ActualHeight and AcutalWidth is 0
http://connect.microsoft.com/VisualStudio/feedback/details/522231/silverlight-grid-stackpanel-canvas-actualheight-and-acutalwidth-is-0
and as described in the API document:
FrameworkElement.ActualHeight Property
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.actualheight%28v=VS.95%29.aspx
FrameworkElement.ActualWidth Property
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.actualwidth%28v=VS.95%29.aspx
ActualHeight and ActualWidth are not suitable for data binding. Therefore, as a workaround, Microsoft suggests that SizeChanged event be used if we want to do something when actual size changes. However, all we want are just data-binding-suitable ActualHeight and ActualWidth, and here I show an implementation for that purpose.
The class, SizeChange, whose implementation I show later, defines 3 attached properties named "IsEnabled", "ActualHeight" and "ActualWidth", respectively. If SizeChange.IsEnabled="True" is given as a property to a FrameworkElement, actual height and actual width of the FrameworkElement can be obtained through SizeChange.ActualHeight and SizeChange.ActualWidth.
Before diving into the implementation, let's see a usage example to understand what we want to realize.
Usage Example:
<...
// At an appropriate place, define a namespace prefix for// the namespace under whicn SizeChange class is contained.//// The definition below assumes that SizeChange class exists// under the namespace "MyApp.UI" and specifies "local_ui"// as a prefix for the namespace.xmlns:local_ui="clr-namespace:MyApp.UI"
>
// Give a SizeChange.IsEnabled="True" to a FrameworkElement.// By doing this, actual height and actual width of this// element can be bound through SizeChange.ActualHeight and// SizeChange.ActualWidth.<Grid local_ui:SizeChange.IsEnabled="True" x:Name="grid1">
...
</Grid>
// Bind the data-binding-suitable attached property.//// Here, Text property of the TextBlock instance is bound// to SizeChange.ActualHeight attached-property of the Grid// instance named "grid1".//// As a result, The TextBlock shows the actual height of// the Grid instance, and the shown value is refreshed// whenever the actual size of the Grid instance is changed.//// Note that "Path=(local_ui:SizeChange.ActualHeight)"// is used instead of "Path=ActualHeight".<TextBlock Text="{Binding ElementName=grid1,
Path=(local_ui:SizeChange.ActualHeight)}"/>
And the implementation is here:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace MyApp.UI
{
// 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=Trueif (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);
}
}
}
ActualHeight と ActualWidth をバインドする (Silverlight)
Microsoft Connect に報告されているように、
Silverlight Grid/StackPanel/Canvas ActualHeight and AcutalWidth is 0
http://connect.microsoft.com/VisualStudio/feedback/details/522231/silverlight-grid-stackpanel-canvas-actualheight-and-acutalwidth-is-0
また、API ドキュメントに記述されているように、
FrameworkElement.ActualHeight プロパティ
http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.actualheight%28v=VS.95%29.aspx
FrameworkElement.ActualWidth プロパティ
http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.actualwidth%28v=VS.95%29.aspx
ActualHeight と ActualWidth はデータバインディングに適していない。そのため、回避策として、実際のサイズが変更されたときに何かをしたい場合は SizeChanged イベントを使うようにと、マイクロソフトは言っている。しかし、私たちが欲しいのは、単にデータバインディングに適した ActualHeight と ActualWidth であり、そしてそのための実装をここにあげる。
後で実装を見ることになる SizeChange クラスは、IsEnabled、ActualHeight、ActualWidth という三つのアタッチトプロパティ (attached property) を定義している。SizeChange.IsEnabled="True" がプロパティとして FrameworkElement に与えられると、その FrameworkElement の実際の高さと実際の幅を、SizeChange.ActualHeight と SizeChange.ActualWidth を通じて得ることができるようになる。
実装を見る前に、何を実現しようとしているかを理解するために、使用例を見よう。
使用例:
<...
// 適切な場所で、SizeChange クラスが含まれる名前空間用の// 名前空間プレフィックスを定義する。//// 下記の定義では、SizeChange クラスが名前空間 MyApp.UI// にあるものと仮定し、その名前空間用のプレフィックスと// して local_ui を指定している。xmlns:local_ui="clr-namespace:MyApp.UI"
>
// SizeChange.IsEnabled="True" を FrameworkElement に// 与える。これをすることにより、このエレメントの// 実際の高さと幅を、SizeChange.ActualHeight と// SizeChange.ActualWidth でバインドできるようになる。<Grid local_ui:SizeChange.IsEnabled="True" x:Name="grid1">
...
</Grid>
// データバインディングに適したアタッチトプロパティを// バインドする。//// ここでは、TextBlock インスタンスの Text プロパティを、// grid1 という名前の Grid インスタンスのアタッチト// プロパティ SizeChange.ActualHeight にバインドしている。//// 結果として、TextBlock は Grid インスタンスの実際の// 高さを表示し、Grid インスタンスの実際のサイズが変更// されるたびに表示される値が更新される。//// "Path=ActualHeight" ではなく// "Path=(local_ui:SizeChange.ActualHeight)" が使用されている// ことに注意。<TextBlock Text="{Binding ElementName=grid1,
Path=(local_ui:SizeChange.ActualHeight)}"/>
そして実装は下記のとおり。
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace MyApp.UI
{
// アタッチトプロパティを登録する必要があるので、SizeChange// クラスを DependencyObject のサブクラスとして宣言する。public class SizeChange : DependencyObject
{
#region Attached property "IsEnabled"
// IsEnabled プロパティの名前public const string IsEnabledPropertyName = "IsEnabled";
// IsEnabled という名前のアタッチトプロパティを登録する。// IsEnabled プロパティの値が変更されると OnIsEnabledChanged// メソッドが呼ばれることに注意。public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached(
IsEnabledPropertyName,
typeof(bool),
typeof(SizeChange),
new PropertyMetadata(false, OnIsEnabledChanged));
// IsEnabled プロパティのゲッター。依存関係システムが// 使用するので、このメソッドの名前は変更するべきではない。public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
// IsEnabled プロパティのセッター。依存関係システムが// 使用するので、このメソッドの名前は変更するべきではない。public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
#endregion
#region Attached property "ActualHeight"
// ActualHeight プロパティの名前public const string ActualHeightPropertyName = "ActualHeight";
// ActualHeight という名前のアタッチトプロパティを登録する。// このプロパティの値は、SizeChanged イベントが起こったときに// 変更される。public static readonly DependencyProperty ActualHeightProperty
= DependencyProperty.RegisterAttached(
ActualHeightPropertyName,
typeof(double),
typeof(SizeChange),
null);
// ActualHeight プロパティのゲッター。依存関係システムが// 使用するので、このメソッドの名前は変更するべきではない。public static double GetActualHeight(DependencyObject obj)
{
return (double)obj.GetValue(ActualHeightProperty);
}
// ActualHeight プロパティのセッター。依存関係システムが// 使用するので、このメソッドの名前は変更するべきではない。public static void SetActualHeight(DependencyObject obj, double value)
{
obj.SetValue(ActualHeightProperty, value);
}
#endregion
#region Attached property "ActualWidth"
// ActualWidth プロパティの名前public const string ActualWidthPropertyName = "ActualWidth";
// ActualWidth という名前のアタッチトプロパティを登録する。// このプロパティの値は、SizeChanged イベントが起こったときに// 変更される。public static readonly DependencyProperty ActualWidthProperty
= DependencyProperty.RegisterAttached(
ActualWidthPropertyName,
typeof(double),
typeof(SizeChange),
null);
// ActualWidth プロパティのゲッター。依存関係システムが// 使用するので、このメソッドの名前は変更するべきではない。public static double GetActualWidth(DependencyObject obj)
{
return (double)obj.GetValue(ActualWidthProperty);
}
// ActualWidth プロパティのセッター。依存関係システムが// 使用するので、このメソッドの名前は変更するべきではない。public static void SetActualWidth(DependencyObject obj, double value)
{
obj.SetValue(ActualWidthProperty, value);
}
#endregion
// このメソッドは、IsEnabled プロパティの値が変更されたときに// 呼び出される。新しい値が true の場合、対象のエレメントの// SizeChanged イベントにイベントハンドラーが追加される。private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// SizeChanged イベントにイベントハンドラーを追加するので、// エレメントは FrameworkElement のインスタンスでなければ// ならない。var element = obj as FrameworkElement;
if (element == null)
{
// 与えられたオブジェクトは FrameworkElement のインス// タンスではないので、SizeChanged イベントが無い。// そのため、当オブジェクトに対してできることがない。return;
}
// IsEnabled=True の場合if (args.NewValue != null && (bool)args.NewValue == true)
{
// エレメントにアタッチするAttach(element);
}
else
{
// エレメントからデタッチする。Detach(element);
}
}
private static void Attach(FrameworkElement element)
{
// エレメントの実際のサイズが変更されたときに処理を// おこなえるよう、エレメントの SizeChanged イベントに// イベントハンドラーを追加する。element.SizeChanged += HandleSizeChanged;
}
private static void Detach(FrameworkElement element)
{
// エレメントからイベントハンドラーを取り除く。element.SizeChanged -= HandleSizeChanged;
}
// SizeChanged イベントが起こった時に実行されるイベントハンドラーprivate static void HandleSizeChanged(object sender, SizeChangedEventArgs args)
{
var element = sender as FrameworkElement;
if (element == null)
{
return;
}
// 実際の高さと幅を取得する。var width = args.NewSize.Width;
var height = args.NewSize.Height;
// SizeChange.ActualHeight と SizeChange.ActualWidth の// 値を更新する。SetActualWidth(element, width);
SetActualHeight(element, height);
}
}
}