Tuesday, November 11, 2008

Auto-sizing Canvas for Silverlight and WPF

One of the first issues I ran into when I was learning WPF was that the Canvas didn't size to its contents.  I was attempting to render a graph and I wanted to stick a Canvas in a ScrollViewer and have the scroll area grow as more content was added outside the current bounds of the Canvas.  While searching for a solution I noticed on various forums that several other developers were looking for a solution to this problem.  At the time I didn't know enough about writing custom layout containers to write my own Canvas and I soon found myself diverted. 

Today I work for Microsoft and am partly responsible for Silverlight Charts.  Now that I'm rather comfortable writing layout containers I figured I'd spend a few minutes and bang out a Canvas control that provides all the flexibility that I'd always wanted.  Ladies and gents I present DynamicCanvas. 

In addition to restoring the WPF Bottom/Right properties that are missing from Silverlight's Canvas the DynamicCanvas adds CenterLeft, CenterTop, CenterRight, and CenterBottom properties.  In WPF these properties are convenient but not strictly necessary as you can use a multi-binding and a converter to  center your objects declaratively.   However Silverlight 2 does not support multi-bindings which makes it rather more difficult to ensure that objects will remain centered if their dimensions change after they've been positioned. 

The following XAML...

<Window x:Class="testDynCanvas.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Unfold.Windows.Controls" Title="Window1" Height="300" Width="300"> <Grid> <local:DynamicCanvas SizeWidthToContent="True" Background="Pink"> <Rectangle Width="30" Height="50" local:DynamicCanvas.CenterBottom="50" local:DynamicCanvas.CenterRight="30" Fill="Purple" /> </local:DynamicCanvas> </Grid> </Window>

....yields the following result:

output

The DynamicCanvas is a good example of a custom layout control if you're trying to learn how to write one.  It also takes advantage of one of the most exciting attributes of Siverlight 2: the ability to cross-compile with WPF.  Silverlight 2 has now matured to the point where some simple controls can be compiled in Silverlight with little or no modification.

It's important to remember that you shouldn't use DynamicCanvas (or Canvas) to do layout.  You should only use Canvas for tasks where coordinate-based plotting is appropriate such as writing a custom series for Silverlight Charts (something I'll be blogging about after our next release).  This control is not an MS supported control and you should use it at your own risk.  That said if I get positive feedback on it I may try and get it into the Silverlight Toolkit.

Without further ado here are the two files to add to your WPF/Silverlight project...

DynamicCanvas.cs

using System; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace Unfold.Windows.Controls { /// <summary> /// A canvas that can size to its contents. /// </summary> public class DynamicCanvas : Panel { #region public bool SizeWidthToContent /// <summary> /// Gets or sets a value indicating whether the dynamic canvas should /// size its width to its content. /// </summary> public bool SizeWidthToContent { get { return (bool)GetValue(SizeWidthToContentProperty); } set { SetValue(SizeWidthToContentProperty, value); } } /// <summary> /// Identifies the SizeWidthToContent dependency property. /// </summary> public static readonly DependencyProperty SizeWidthToContentProperty = DependencyProperty.Register( "SizeWidthToContent", typeof(bool), typeof(DynamicCanvas), new PropertyMetadata(false, OnSizeWidthToContentPropertyChanged)); /// <summary> /// SizeWidthToContentProperty property changed handler. /// </summary> /// <param name="d">DynamicCanvas that changed its SizeWidthToContent.</param> /// <param name="e">Event arguments.</param> private static void OnSizeWidthToContentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DynamicCanvas source = (DynamicCanvas)d; bool oldValue = (bool)e.OldValue; bool newValue = (bool)e.NewValue; source.OnSizeWidthToContentPropertyChanged(oldValue, newValue); } /// <summary> /// SizeWidthToContentProperty property changed handler. /// </summary> /// <param name="oldValue">Old value.</param> /// <param name="newValue">New value.</param> protected virtual void OnSizeWidthToContentPropertyChanged(bool oldValue, bool newValue) { Invalidate(); } #endregion public bool SizeWidthToContent #region public bool SizeHeightToContent /// <summary> /// Gets or sets a value indicating whether the canvas should size its /// height to its content. /// </summary> public bool SizeHeightToContent { get { return (bool)GetValue(SizeHeightToContentProperty); } set { SetValue(SizeHeightToContentProperty, value); } } /// <summary> /// Identifies the SizeHeightToContent dependency property. /// </summary> public static readonly DependencyProperty SizeHeightToContentProperty = DependencyProperty.Register( "SizeHeightToContent", typeof(bool), typeof(DynamicCanvas), new PropertyMetadata(false, OnSizeHeightToContentPropertyChanged)); /// <summary> /// SizeHeightToContentProperty property changed handler. /// </summary> /// <param name="d">DynamicCanvas that changed its SizeHeightToContent.</param> /// <param name="e">Event arguments.</param> private static void OnSizeHeightToContentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DynamicCanvas source = (DynamicCanvas)d; bool oldValue = (bool)e.OldValue; bool newValue = (bool)e.NewValue; source.OnSizeHeightToContentPropertyChanged(oldValue, newValue); } /// <summary> /// SizeHeightToContentProperty property changed handler. /// </summary> /// <param name="oldValue">Old value.</param> /// <param name="newValue">New value.</param> protected virtual void OnSizeHeightToContentPropertyChanged(bool oldValue, bool newValue) { Invalidate(); } #endregion public bool SizeHeightToContent #region public attached double Bottom /// <summary> /// Gets the value of the Bottom attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The Bottom property value for the UIElement.</returns> public static double GetBottom(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(BottomProperty); } /// <summary> /// Sets the value of the Bottom attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed Bottom value.</param> public static void SetBottom(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(BottomProperty, value); } /// <summary> /// Identifies the Bottom dependency property. /// </summary> public static readonly DependencyProperty BottomProperty = DependencyProperty.RegisterAttached( "Bottom", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnBottomPropertyChanged)); /// <summary> /// BottomProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its Bottom.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnBottomPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double Bottom #region public attached double Left /// <summary> /// Gets the value of the Left attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The Left property value for the UIElement.</returns> public static double GetLeft(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(LeftProperty); } /// <summary> /// Sets the value of the Left attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed Left value.</param> public static void SetLeft(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(LeftProperty, value); } /// <summary> /// Identifies the Left dependency property. /// </summary> public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached( "Left", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnLeftPropertyChanged)); /// <summary> /// LeftProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its Left.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnLeftPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double Left #region public attached double Right /// <summary> /// Gets the value of the Right attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The Right property value for the UIElement.</returns> public static double GetRight(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(RightProperty); } /// <summary> /// Sets the value of the Right attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed Right value.</param> public static void SetRight(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(RightProperty, value); } /// <summary> /// Identifies the Right dependency property. /// </summary> public static readonly DependencyProperty RightProperty = DependencyProperty.RegisterAttached( "Right", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnRightPropertyChanged)); /// <summary> /// RightProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its Right.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnRightPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double Right #region public attached double Top /// <summary> /// Gets the value of the Top attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The Top property value for the UIElement.</returns> public static double GetTop(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(TopProperty); } /// <summary> /// Sets the value of the Top attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed Top value.</param> public static void SetTop(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(TopProperty, value); } /// <summary> /// Identifies the Top dependency property. /// </summary> public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached( "Top", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnTopPropertyChanged)); /// <summary> /// TopProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its Top.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnTopPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double Top #region public attached double CenterBottom /// <summary> /// Gets the value of the CenterBottom attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The CenterBottom property value for the UIElement.</returns> public static double GetCenterBottom(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(CenterBottomProperty); } /// <summary> /// Sets the value of the CenterBottom attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed CenterBottom value.</param> public static void SetCenterBottom(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(CenterBottomProperty, value); } /// <summary> /// Identifies the CenterBottom dependency property. /// </summary> public static readonly DependencyProperty CenterBottomProperty = DependencyProperty.RegisterAttached( "CenterBottom", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnCenterBottomPropertyChanged)); /// <summary> /// CenterBottomProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its CenterBottom.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnCenterBottomPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double CenterBottom #region public attached double CenterLeft /// <summary> /// Gets the value of the CenterLeft attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The CenterLeft property value for the UIElement.</returns> public static double GetCenterLeft(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(CenterLeftProperty); } /// <summary> /// Sets the value of the CenterLeft attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed CenterLeft value.</param> public static void SetCenterLeft(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(CenterLeftProperty, value); } /// <summary> /// Identifies the CenterLeft dependency property. /// </summary> public static readonly DependencyProperty CenterLeftProperty = DependencyProperty.RegisterAttached( "CenterLeft", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnCenterLeftPropertyChanged)); /// <summary> /// CenterLeftProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its CenterLeft.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnCenterLeftPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double CenterLeft #region public attached double CenterRight /// <summary> /// Gets the value of the CenterRight attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The CenterRight property value for the UIElement.</returns> public static double GetCenterRight(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(CenterRightProperty); } /// <summary> /// Sets the value of the CenterRight attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed CenterRight value.</param> public static void SetCenterRight(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(CenterRightProperty, value); } /// <summary> /// Identifies the CenterRight dependency property. /// </summary> public static readonly DependencyProperty CenterRightProperty = DependencyProperty.RegisterAttached( "CenterRight", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnCenterRightPropertyChanged)); /// <summary> /// CenterRightProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its CenterRight.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnCenterRightPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double CenterRight #region public attached double CenterTop /// <summary> /// Gets the value of the CenterTop attached property for a specified UIElement. /// </summary> /// <param name="element">The UIElement from which the property value is read.</param> /// <returns>The CenterTop property value for the UIElement.</returns> public static double GetCenterTop(UIElement element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(CenterTopProperty); } /// <summary> /// Sets the value of the CenterTop attached property to a specified UIElement. /// </summary> /// <param name="element">The UIElement to which the attached property is written.</param> /// <param name="value">The needed CenterTop value.</param> public static void SetCenterTop(UIElement element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(CenterTopProperty, value); } /// <summary> /// Identifies the CenterTop dependency property. /// </summary> public static readonly DependencyProperty CenterTopProperty = DependencyProperty.RegisterAttached( "CenterTop", typeof(double), typeof(DynamicCanvas), new PropertyMetadata(double.NaN, OnCenterTopPropertyChanged)); /// <summary> /// CenterTopProperty property changed handler. /// </summary> /// <param name="dependencyObject">UIElement that changed its CenterTop.</param> /// <param name="eventArgs">Event arguments.</param> public static void OnCenterTopPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { UIElement source = dependencyObject as UIElement; if (source == null) { throw new ArgumentException("dependencyObject"); } DynamicCanvas parent = VisualTreeHelper.GetParent(source) as DynamicCanvas; if (parent != null) { parent.Invalidate(); } } #endregion public attached double CenterTop /// <summary> /// Invalidates the position of child elements. /// </summary> private void Invalidate() { if (this.SizeHeightToContent || this.SizeWidthToContent) { this.InvalidateMeasure(); } else { this.InvalidateArrange(); } } /// <summary> /// Measures all the children and returns their size. /// </summary> /// <param name="constraint">The available size.</param> /// <returns>The desired size.</returns> protected override Size MeasureOverride(Size constraint) { Size availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); if (SizeHeightToContent || SizeWidthToContent) { foreach (UIElement child in Children) { child.Measure(availableSize); } double maxWidth = 0; if (SizeWidthToContent) { maxWidth = Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetLeft(child))) .Select(child => GetLeft(child) + child.DesiredSize.Width) .Concat( Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetCenterLeft(child))) .Select(child => GetCenterLeft(child) + (child.DesiredSize.Width / 2))).MaxOrNullable() ?? 0.0; double maxRightOffset = Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetRight(child))) .Select(child => (maxWidth - GetRight(child)) - child.DesiredSize.Width) .Concat( Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetCenterRight(child))) .Select(child => (maxWidth - GetCenterRight(child)) - (child.DesiredSize.Width / 2))).MinOrNullable() ?? 0.0; if (maxRightOffset < 0.0) { maxWidth += Math.Abs(maxRightOffset); } } double maxHeight = 0; if (SizeHeightToContent) { maxHeight = Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetTop(child))) .Select(child => GetTop(child) + child.DesiredSize.Height) .Concat( Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetCenterTop(child))) .Select(child => GetCenterTop(child) + (child.DesiredSize.Height / 2))).MaxOrNullable() ?? 0.0; double maxBottomOffset = Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetBottom(child))) .Select(child => (maxHeight - GetBottom(child)) - child.DesiredSize.Height) .Concat( Children .Cast<UIElement>() .Where(child => !double.IsNaN(GetCenterBottom(child))) .Select(child => (maxHeight - GetCenterBottom(child)) - (child.DesiredSize.Height / 2))).MinOrNullable() ?? 0.0; if (maxBottomOffset < 0.0) { maxHeight += Math.Abs(maxBottomOffset); } } return new Size(maxWidth, maxHeight); } else { foreach (UIElement element in Children) { if (element != null) { element.Measure(availableSize); } } return Size.Empty; } } /// <summary> /// Arranges all children in the correct position. /// </summary> /// <param name="arrangeSize">The size to arrange element's within. /// </param> /// <returns>The size that element's were arranged in.</returns> protected override Size ArrangeOverride(Size arrangeSize) { foreach (UIElement element in base.Children) { if (element == null) { continue; } double x = 0.0; double y = 0.0; double left = GetLeft(element); double centerLeft = GetCenterLeft(element); double halfWidth = (element.DesiredSize.Width / 2.0); if (!double.IsNaN(left)) { x = left; } else if (!double.IsNaN(centerLeft)) { x = centerLeft - halfWidth; } else { double right = GetRight(element); if (!double.IsNaN(right)) { x = (arrangeSize.Width - element.DesiredSize.Width) - right; } else { double centerRight = GetCenterRight(element); if (!double.IsNaN(centerRight)) { x = (arrangeSize.Width - halfWidth) - centerRight; } } } double top = GetTop(element); double centerTop = GetCenterTop(element); double halfHeight = (element.DesiredSize.Height / 2.0); if (!double.IsNaN(top)) { y = top; } else if (!double.IsNaN(centerTop)) { y = centerTop - halfHeight; } else { double bottom = GetBottom(element); if (!double.IsNaN(bottom)) { y = (arrangeSize.Height - element.DesiredSize.Height) - bottom; } else { double centerBottom = GetCenterBottom(element); if (!double.IsNaN(centerBottom)) { y = (arrangeSize.Height - halfHeight) - centerBottom; } } } element.Arrange(new Rect(new Point(x, y), element.DesiredSize)); } return arrangeSize; } } }

EnumerableFunctions.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Unfold.Windows.Controls { public static class EnumerableFunctions { /// <summary> /// Returns the maximum value or null if sequence is empty. /// </summary> /// <param name="that">The sequence to retrieve the maximum value from. /// </param> /// <returns>The maximum value or null.</returns> public static T? MaxOrNullable<T>(this IEnumerable<T> that) where T : struct, IComparable { if (!that.Any()) { return null; } return that.Max(); } /// <summary> /// Returns the minimum value or null if sequence is empty. /// </summary> /// <param name="that">The sequence to retrieve the minimum value from. /// </param> /// <returns>The minimum value or null.</returns> public static T? MinOrNullable<T>(this IEnumerable<T> that) where T : struct, IComparable { if (!that.Any()) { return null; } return that.Min(); } } }

25 comments:

Anonymous said...

You said,
It's important to remember that you shouldn't use DynamicCanvas (or Canvas) to do layout.

Why not? If you want your design fixed, Canvas is a good choice. It's not as flexible as the other panels, but that doesn't mean it's inferior.

The disdain shown for the Canvas is a bit odd to me, but it seems to be common with a lot of MS employees in the US.

BTW, why don't you have a solution to download with the sample. Copying code is so 80's.

Jafar Husain said...

I can't comment for other MS employees but generally I try and avoid GUI's fixed at pixel location. They don't scale to different screen sizes very well. You don't want to tell Silverlight how to position elements by pixel for the same reason you don't want to tell your programming language program exactly how to filter a list with a predicate: it's too specific and therefore inflexible.

Obviously if you find yourself in a position where you need to fix your UI...then fix it. Just make sure that you can't reproduce the same result with the layout containers in Silverlight/WPF.

As for your constructive criticism about the lack of a solution to download...I have no excuse. I'm in the process of acquiring webspace at which point this will be rectified. In the meantime I hope that those who really need this component wont begrudge me the Ctrl+C-ing.

Anonymous said...

You're forgiven for the lack of a project(lol).

Absolute positioning was never a problem in HTML, simply because I could wrap it so that it fit into a container that always adjusted.

I haven't seen a way to make a UIElement positioned relative to a containing object in SilverLight(preferably a Grid).

That would be nice. Anyway, I still think your concept for this Canvas is BEAUTIFUL. Good work!

David S said...

I have to say I'd love to see this type of auto-sizing canvas make its way into the Silverlight toolkit as you hinted. Great work!

Anonymous said...

I don't get it. Is DynamicCanvas supposed to fit itself to the content drawn inside of it? Under WPF, DynamicCanvas seems to behave the SAME as Canvas. Even your example results image isn't sizing the canvas to fit the rectangle (or scaling up the rectangle to fit the canvas). What am I missing?

Anonymous said...

I can't get this to work in WPF. It doesn't seem to resize to the content when the SizeWidthToContent and SizeHeightToContent are set to true.

Anonymous said...

Great Control!
Very Usefull!

Igor Kondrasovas said...

Hello,


Actually I could also not see any scrolling using you sample under WPF. How can I test it.

Kindly Regards,

Igor

Affordable Luxurious Wedding Dress Blog said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...

Hi Jafar,

great code. The only thing missing (or which I missed) is the license under which it is released. Or is it public domain ?

Thank you,
Markus

Jafar Husain said...

MS-PL. Enjoy.

Nitin Chaudhari said...

This thing works, but only for right and bottom, can you modify this so that it works even when child goes in negative top or left

About Me

My photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.