Friday, December 19, 2008

Haskell for C# Programmers Part 3: Visualizing Monads

Today I'll be explain what monads are, what they're good for, and how to build one of your own. One of the interesting things about monads is that they're relatively new.  They were first used in  programming languages about 20 years ago and are only just now making their way into a languages you have much chance of getting paid for using.

"Why do I need to learn about them?"

There is very little you can do in Haskell without understanding monads.  As I explained last time Haskell uses them for IO, without which programs are frightfully dull.  Unfortunately it is rather hard to learn a new programming language and a completely foreign concept at the same time, which is why learning Haskell can be daunting.  C# programmers have a leg up though.  They use monads all the time.  Surprisingly C# has a built-in syntax for working with Monads: Linq.

"Linq's for querying data.  What does that have to do with monads?"

On the contrary, Linq is just a general syntax for constructing monads.  It was made to look like SQL in order to make it feel more familiar to developers.

"You sir, are blowing my mind."

It's true.  In the previous post I showed that Linq expressions could be used to create and manipulate Haskell's IO monad.  Using Linq statements we composed several procedures that performed IO operations into one large procedure and then ran it.  That's not exactly what you normally think of as a query is it?

"I suppose not.  So what are monads?"

If you've written object-oriented code you've constructed objects with functions.  I've you written functional code like a Linq query then you have manipulating functions with other functions.

Monads are functions that are constructed with functions.*

"But Linq programs manipulate IEnumerable objects, not functions."

Don't be fooled.  The IEnumerable object is just a convenient wrapper for the IEnumerator.MoveNext function which does the work.  Although not strictly necessary, wrapper objects are usually defined to encapsulate monadic functions.  This is useful because it allows us to give the monad a nice readable type name that conveys its purpose (ex. IEnumerables are for enumerating).  Creating wrapper objects also makes it easy to use static typing to prevent attempts to compose two monads not meant to be composed (ex. IEnumerable monads shouldn't be combined with IO monads).

The following animation demonstrates how several IEnumerable monads are composed into one big IEnumerable monad with a Linq query.  After the IEnumerable monad is composed, the animation demonstrates what happens when it is run.

Monads have been likened to onions and matryoshka dolls.  From the animation above you can see why.  When you apply a bind two monadic functions together you get a new one that encapsulates them.  When the monad function is run each function runs its inner functions until the inner-most function is executed.  This function returns a result.  Then each monad applies some transformation to the result of its inner monad and returns that value.  This continues until finally the result is returned from the outer-most monad. 

"Why would I want to build a function this way?"

When you bind two monads of the same type together you get...another instance of the same monad.  Think about it.  If I select from an IEnumerable I get...an IEnumerable.  When you bind two IO monads together you get an IO monad and so on and so forth.  This is a very elegant, predictable way of structuring a program and a great way of controlling the complexity of your code. 

"So how do I know if a function is monadic?"

The first time I read the mathematical criteria for a monad I was awfully confused.  I will attempt to describe the rules for determining if a function is monadic in plain english and provide examples so as not to perpetuate this uncomfortable state.

A function is monadic if*:

1.  There exists a function which can construct it.

IEnumerable<int> numbers = new []{2};

2.  There exists a function which can retrieve a value from it.

int[] number = numbers.ToArray();

3.  There exists a function that can bind two monads together and produce another instance of the monad.

public static IEnumerable<R> SelectMany<T,R>( this IEnumerable<T> that, Func<T, IEnumerable<R>> func) { foreach(var item in that) { foreach(var nestedItem in func(item)) { yield return nestedItem; } } }

The first two should be straight forward because everyone knows how to get data into and out of an IEnumerable.  The last function might strike you as a bit funny.  This SelectMany function is a Linq function and although you are probably unaware of it you use it all the time.  The compiler calls it under the hood when you join two lists.  Turns out that the bind function for two IEnumerables is a join.  After all when you join two IEnumerables you get...well a new IEnumerable.  Sound familiar?

Take the following Linq query...

var pairs = from left in Enumerable.Range(0,3) from right in Enumerable.Range(3,6) select new {left, right};

The code above translates to...

Enumerable.Range(0,3).SelectMany( left => Enumerable.Range(3,6).Select( right => new {left, right}));

Take a minute to absorb this.  Nested functions can be very difficult for developers who are accustomed to imperative development.

"Okay...I think I get this but I'd like to see another example."

Okay.  Let's take a look at the code required to build Haskell's IO monad in C#.  First we'll define the monad.  No need for a wrapper class. A delegate type should suffice just fine.

delegate T IO<T>();

Okay that was easy enough.  Since our monad is just a delegate it's easy enough to construct one...

IO<object> helloMonad = () => { Console.WriteLine("Hello monad."); return null; };

Getting a value out (in this case null) is even easier.

helloMonad();

Now comes the hard part.  We need to write the bind function.  The Bind function uses a transformation function to create a new IO monad that encapsulates an existing one IO monad. 

public static IO<R> Bind<T, R>(this IO<T> io, Func<T, IO<R>> func) { return func(io()); }

As you can see, Bind runs the monad, gets the result, passes it to the transformation function, and returns the output, which is the transformed monad.  Unfortunately we're not done.  The C# compiler wants us to define one more overload.  If we want to transform the output of our composed monad with another function the compiler calls the following overload to avoid adding another layer of nesting.

public static IO<V> Bind<T, U, V>( this IO<T> io, Func<T, IO<U>> io0, Func<T, U, V> io1) { return io.Bind( value0 => io0(value0).Bind( value1 => new IO<V>(() => io1(value0, value1)))); }

Once again the function above is entirely redundant and is just a performance optimization.  Any code you can write with the latter can also be written with the former.

"Umm...I'm not sure I follow."

I know.  It's complicated.  The bind operation is perhaps the most difficult part of monads to understand.  Just keep in mind that bind takes two monadic functions, f and g, and composes them together, making a new one h.  The result of h(x) x is the same as calling g(f(x)).

"So how does Linq know how to compose my IO monad?  It doesn't implement IEnumerable."

Good observation.  In fact query comprehensions don't actually rely on the IEnumerable interface.  They rely on method name patterns.  For example the "select" keyword causes the C# compiler to look for a method named Select on the object it is manipulating.  If you attempt to bind two monads together the C# compiler converts that into a call to SelectMany. 

Therefore all we have to do to compose our IO monad with Linq is rename the Bind function to SelectMany!  Then we can construct our IO functions using Linq.

static IO<object> RealMain(string[] args){ return from address in GetAddressFromConsole() from html in GetHtml(address) from _ in WriteToConsole(html) select _; }

Notice that nothing has actually happened yet.  We've just constructed a new function that can be run and will return a value.

"This is really cool and all but when am I gonna learn some Haskell?"

I'm going to suspend this series here for two reasons:

The first is that there are many more good resources on the web for learning Haskell today than there were when I started this series many months ago.  My intent was to take advantage of the fact that C# and Haskell have so many similarities (anonymous types, lambda functions, and Linq which actually comes straight from Haskell) to get C# developers off the ground.  Hopefully you've not only gotten a good introduction to monads and Haskell syntax, but you've also gained a newfound respect for C#. 

The other reason I'm suspending this series is that I believe most .NET developers are more interested in Microsoft's new functional language, F#, which is available today and will be released with the next version of Visual Studio.  F# will sit alongside C# and VB.NET as a fully supported .NET language.  The good news is that everything you've just learned about Haskell and functional programming applies to F#.  The languages have a very similar syntax and share the same idioms.  I look forward to posting about F# in the near future.

*I'm aware that in explaining what a monad is I've managed to be at once too broad and too narrow.  Please know that I'm aware of this and have taken some creative license to make things as clear as possible.

Tuesday, December 2, 2008

Clojure. Wow.

Someone finally did it. They created a LISP that I want to use. Too bad it's for the JVM. :-)

Most of my complaints about LISPs are the same as everyone else's. Few libraries, less documentation, and poor interoperability. I never found a LISP that had everything I wanted. I'm repelled by the complexity of Common Lisp and vexed by Scheme's completely avoidable verbosity. It also find that languages like F# and Haskell do a better job of encouraging practices that minimize mutable state. Putting all that aside, even if a free Lisp with standard libraries as extensive as that of .NET or Java existed I just don't have time to relearn how to print "Hello word," nor should I have to.

Enter Clojure. It runs on the JVM and as such can use any Java library. As for syntax it takes a very conservative approach, adding just enough to make a big difference in readability. Clojure has tremendous potential. Its encourages the use of sequences, has lots of handy immutable data structures, and some nice syntax for creating them. It's weird but Clojure programming feels almost as much like F# as it does Scheme.

I have to call one particular syntactical decision out in particular. The choice of lambda syntax is brilliant:

(reduce #(+ %1 %2) (range 100))

Clojure's got macros (of course), optional type annotations, even a working Software Transactional Memory implementation(!). Having done some simple benchmarking I was pleasantly surprised to find that it's no slouch either.

I have a minor complaint which is that I wish Clojure had adopted JScheme's dot notation for referencing Java class members. I don't find Clojure's approach nearly as readable or natural to a Java programmer. Also there's no way to write mutually recursive functions but that's the JVM's fault.

It's too early to predict whether Clojure will get adopted. My concern is that it will lose out to more syntactically approachable dynamic JVM languages like Jython and JRuby. Frankly I'm not sure I can deal with any more disappointment. I got really excited about Arc. Of course you remember Arc. Paul Grahm's LISP flavor which, after years in the making, took the software development industry by storm. Not.

I don't want to pile on Paul Grahm. He's gotten enough flack over the Arc debacle. There is a lesson here though. If your language doesn't target one of the major VM's you might as well just write a paper and submit it to LtU.

There's no good reason that Clojure couldn't be ported more or less faithfully to .NET. Take the DLR and add some of the classes in F#'s standard library and you're 70% there. It's not like Lisp parsers are hard to write after all. Any takers? I'd do it my self if I wasn't consumed by charting :-).

Wednesday, November 26, 2008

Protected Dependency Properties Are Not Really

I discovered an interesting fact yesterday.  I was attempting modify ImplicitStyleManager to support the DefaultStyleKey property.  Previously I was just using the concrete type of the object I was styling to retrieve its style from the resource dictionary.  Unfortunately this prevented types derived from System.Windows.Controls.Button from getting styled as buttons.  This diverged from WPF's behavior and understandably we got flack for it on CodePlex.  When I saw that the property was protected I thought "game over."  Luckily Silverlight Toolkit team member Justin Angel didn't give in so easily.

Justin: "Lemme try something."

Me: "Justin we can't access protected members.  I mean we could add an interface or something..."

Justin: "Just gimme a sec."

After a few moments he IM'ed me something very much like this:

internal class DefaultStyleKeyRetriever : Control { /// <summary> /// Initializes a new instance of the DefaultStyleKeyRetriever class. /// </summary> public DefaultStyleKeyRetriever() { } /// <summary> /// This method retrieves the default style key of a control. /// </summary> /// <param name="control">The control to retrieve the default style key /// from.</param> /// <returns>The default style key of the control.</returns> public object GetDefaultStyleKey(Control control) { return control.GetValue(Control.DefaultStyleKeyProperty); } }

It worked of course.  DefaultStyleKey is a dependency property.  Another class that inherits from Control cannot access a protected CLR property that belongs to another Control instance.  However any class that inherits from Control has access to all of its static members, one of which was the DefaultStyleKeyProperty dependency property object.  Since CLR properties and dependency properties usually come in pairs that means that effectively any protected properties that use a DP as a backing store are not as protected as you may think.

FYI.

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(); } } }

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.