Keeping it focused

Well, it’s been a while since I’ve blogged, as I’ve been a busy little bee working on CodeStash. I feel guilty about this, so I’d like to take the chance to pass on the solution to a problem that was posted on Code Project today. The scenario goes like this:

You have a ViewModel with some text that you want to be updated, and you do all the usual binding work with some TextBox elements. The thing is, you want to update your ViewModel properties when the TextBox loses focus. OK, so that’s easily achieved – in fact, it’s the default behaviour of the TextBox. However, you have a button with IsDefault set on it, so pressing Enter in the TextBox triggers the button click – but as the TextBox hasn’t lost focus, the property you are binding to doesn’t get updated. This is, of course, a problem. Fortunately, there’s an easy little trick that you can deploy to update the property, and it’s easily achieved using an attached behavior.

All you need to do is associate the behaviour to the Button you have set the IsDefault property on, and Bob is your mothers brother, the Text property updates. So, what does this behavior look like:

using System.Windows.Interactivity;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;
using System.Windows.Data;
/// <summary>
/// Associate this behaviour with the button that you mark as IsDefault
/// to trigger the ViewModel update when the user clicks enter in a textbox
/// and the property doesn't update because the update source is set to
/// lost focus.
/// </summary>
public class DefaultButtonUpdateTextBoxBindingBehavior : Behavior<Button>
{
    /// <summary>
    /// Hook into the button click event.
    /// </summary>
    protected override void OnAttached()
    {
        AssociatedObject.Click += AssociatedObject_Click;
        base.OnAttached();
    }

    /// <summary>
    /// Unhook the button click event.
    /// </summary>
    protected override void OnDetaching()
    {
        AssociatedObject.Click -= AssociatedObject_Click;
    }

    /// <summary>
    /// The click event handler.
    /// </summary>
    void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        // Get the element with the keyboard focus
        FrameworkElement el = Keyboard.FocusedElement as FrameworkElement;
        if (el != null && el is TextBox)
        {
            // Get the binding expression associated with the text property
            // for this element.
            BindingExpression expression = el.GetBindingExpression(TextBox.TextProperty);
            if (expression != null)
            {
                // Now, trigger the update.
                expression.UpdateSource();
            }
        }
    }
}

As you can see, there’s not that much code needed. Basically, we get the element that has focus, and retrieve the binding expression associated with it. Once you get the binding expression, we trigger the update.

I’ve copied this behaviour into CodeStash – the handy repository that I will be putting future snippets in for your delectation.

Advertisements

The rumours of my demise have been exaggerated.

So, I haven’t been very active on this blog for quite a while and you would be forgiven for thinking that I’d given up on it. This couldn’t be further from the truth – it’s just that I’ve been a busy little bee for the last few months.

So what have I been doing that has kept me away from finding time to blog? Well, I’ve been working with the inestimable Sacha Barber on a project called CodeStash. CodeStash is an online snippet manager which allows you to take snippets directly from Visual Studio via an addin, and save them online. OK, so that doesn’t sound too exciting, but CodeStash goes much further. You can group the snippets together, so that you can have related snippets grouped together (why just have a piece of XAML when you can have the styling and the code behind in there).

We have big plans for CodeStash, and have managed to hook up with those wonderful people at CodeProject to host this service. Go on, hop on over to the site and sign up; download the extension and contribute your snippets for the betterment of mankind; well, those people who will be cool enough to appreciate your awesome logic. Best of all; this service is free.

MoXAML goes Codeplex

One of the commonest questions I’ve been asked about MoXAML was whether or not I would be maintaining it in a repository of some description. While I’ve not been against the idea, I just haven’t got around to doing it. Well, that is I hadn’t got around to doing it. I am pleased to announce that MoXAML is now open source and is available on CodePlex at http://moxaml.codeplex.com/.

By putting MoXAML on CodePlex, it is going to be easier for me to maintain the whole setup/installation package and I’m toying with an idea on how to automatically pull down updates to MoXAML – these are architectural ideas only, no code has been written, it’s just notes and diagrams so far.

Anyway, sorry for not posting something “witty” or silly – this post’s relatively normal for me. Abnormal service will resume shortly.

Adding to MoXAML.

In my last post, I talked about the enhancements and new architecture I have put into place for the new version of MoXAML Power Toys. I also mentioned that I’d talk about adding a new command. Well, in this post I’m going to cover how I coded the Scrubber command that’s available in the new version.

One of the first things I did was break the Scrubber options from the actual Scrubber command. By doing this, the user no longer needs to set the options every time they need to run Scrubber. So, I created a traditional model for the options which could be used by the Scrubber commands and the Scrubber options dialog. This model looks like this:

using System.ComponentModel;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;

namespace MoXAML.Scrubber.Model
{
    public class ScrubberOptionsModel : INotifyPropertyChanged
    {
        private const string FILENAME = "MoXAMLSettings.dat";

        private int _attributeCountTolerance = 3;
        private bool _reorderAttributes = true;
        private bool _reducePrecision = true;
        private int _precision = 3;
        private bool _removeCommonDefaults = true;
        private bool _forceLineMinimum = true;
        private int _spaceCount = 2;
        private bool _convertTabsToSpaces = true;

        public ScrubberOptionsModel()
        {
            LoadModel();
        }

        public bool ConvertTabsToSpaces
        {
            get
            {
                return _convertTabsToSpaces;
            }
            set
            {
                if (_convertTabsToSpaces == value) return;
                _convertTabsToSpaces = value;
                OnChanged("ConvertTabsToSpaces");
            }
        }

        public int SpaceCount
        {
            get
            {
                return _spaceCount;
            }
            set
            {
                if (_spaceCount == value) return;
                _spaceCount = value;
                OnChanged("SpaceCount");
            }
        }

        public bool ForceLineMinimum
        {
            get
            {
                return _forceLineMinimum;
            }
            set
            {
                if (_forceLineMinimum == value) return;
                _forceLineMinimum = value;
                OnChanged("ForceLineMinimum");
            }
        }

        public bool RemoveCommonDefaults
        {
            get
            {
                return _removeCommonDefaults;
            }
            set
            {
                if (_removeCommonDefaults == value) return;
                _removeCommonDefaults = value;
                OnChanged("RemoveCommonDefaults");
            }
        }

        public int Precision
        {
            get
            {
                return _precision;
            }
            set
            {
                if (_precision == value) return;
                _precision = value;
                OnChanged("Precision");
            }
        }

        public bool ReducePrecision
        {
            get
            {
                return _reducePrecision;
            }
            set
            {
                if (_reducePrecision == value) return;
                _reducePrecision = value;
                OnChanged("ReducePrecision");
            }
        }

        public bool ReorderAttributes
        {
            get
            {
                return _reorderAttributes;
            }
            set
            {
                if (_reorderAttributes == value) return;
                _reorderAttributes = value;
                OnChanged("ReorderAttributes");
            }
        }

        public int AttributeCountTolerance
        {
            get { return _attributeCountTolerance; }
            set
            {
                if (_attributeCountTolerance == value) return;
                _attributeCountTolerance = value;
                OnChanged("AttributeCountTolerance");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler == null) return;
            handler(this, new PropertyChangedEventArgs(propertyName));
        }

        private void LoadModel()
        {
            using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForAssembly())
            {
                if (file.GetFileNames(FILENAME).Count() == 0) return;

                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(FILENAME, System.IO.FileMode.Open, file))
                {
                    using (StreamReader sr = new StreamReader(stream))
                    {
                        _attributeCountTolerance = ConvertInt(sr.ReadLine());
                        _reorderAttributes = ConvertBool(sr.ReadLine());
                        _reducePrecision = ConvertBool(sr.ReadLine());
                        _precision = ConvertInt(sr.ReadLine());
                        _removeCommonDefaults = ConvertBool(sr.ReadLine());
                        _forceLineMinimum = ConvertBool(sr.ReadLine());
                        _spaceCount = ConvertInt(sr.ReadLine());
                        _convertTabsToSpaces = ConvertBool(sr.ReadLine());
                    }
                }
            }
        }

        public void SaveModel()
        {
            using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForAssembly())
            {
                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(FILENAME, System.IO.FileMode.Create, file))
                {
                    using (StreamWriter sr = new StreamWriter(stream))
                    {
                        sr.WriteLine(_attributeCountTolerance);
                        sr.WriteLine(_reorderAttributes);
                        sr.WriteLine(_reducePrecision);
                        sr.WriteLine(_precision);
                        sr.WriteLine(_removeCommonDefaults);
                        sr.WriteLine(_forceLineMinimum);
                        sr.WriteLine(_spaceCount);
                        sr.WriteLine(_convertTabsToSpaces);
                    }
                }
            }
        }

        private int ConvertInt(string value)
        {
            int retVal = 0;
            if (int.TryParse(value, out retVal))
            {
                return retVal;
            }
            return 0;
        }

        private bool ConvertBool(string value)
        {
            bool retVal = false;
            if (bool.TryParse(value, out retVal))
            {
                return retVal;
            }
            return false;
        }
    }
}

As you can see, there’s nothing remarkable about it. It’s a straightforward model implementation, and there’s nothing special about using this inside MoXAML (the point I’m making here is that you can mix and match your MoXAML implementation with standard .NET code).

OK Pete, let’s have a look at actually hooking this into MoXAML. Well, as I mentioned in the MoXAML page, I wanted to have two versions of Scrubber, one for the current file and one for the project files. In order to do this, it made sense to pull the core Scrubber functionality into a base class which the actual commands would hook into:

using MoXAML.Infrastructure;
using MoXAML.Scrubber.Model;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System;
namespace MoXAML.Scrubber
{
    public partial class ScrubberCommandBase : CommandBase
    {
        private ScrubberOptionsModel _model;
        public ScrubberCommandBase() : base()
        {
            _model = new ScrubberOptionsModel();
        }

        protected void ParseFile(string file)
        {
            string text = File.ReadAllText(file);
            text = Perform(text);
            File.WriteAllText(file, text);
        }

        private string Perform(string text)
        {
            text = Indent(text);
            return ReducePrecision(text);
        }

        private string IndentString
        {
            get
            {
                if (_model.ConvertTabsToSpaces)
                {
                    string spaces = string.Empty;
                    spaces = spaces.PadRight(_model.SpaceCount, ' ');

                    return spaces;
                }
                else
                {
                    return "\t";
                }
            }
        }

        private string ReducePrecision(string s)
        {
            string old = s;

            if (_model.ReducePrecision)
            {
                int begin = 0;
                int end = 0;

                while (true)
                {
                    begin = old.IndexOf('.', begin);
                    if (begin == -1) break;

                    // get past the period
                    begin++;

                    for (int i = 0; i &lt; _model.Precision; i++)
                    {
                        if (old[begin] &gt;= '0' && old[begin] &lt;= '9') begin++;
                    }

                    end = begin;

                    while (end &lt; old.Length && old[end] &gt;= '0' && old[end] &lt;= '9') end++;

                    old = old.Substring(0, begin) + old.Substring(end, old.Length - end);

                    begin++;
                }
            }

            return old;
        }

        public string Indent(string s)
        {
            string result;

            s = s.Replace("&", "¬¬");
            using (MemoryStream ms = new MemoryStream(s.Length))
            {
                using (StreamWriter sw = new StreamWriter(ms))
                {
                    sw.Write(s);
                    sw.Flush();

                    ms.Seek(0, SeekOrigin.Begin);

                    using (StreamReader reader = new StreamReader(ms))
                    {
                        XmlReaderSettings settings = new XmlReaderSettings();
                        settings.CheckCharacters = false;
                        settings.ConformanceLevel = ConformanceLevel.Auto;
                        //XmlReader xmlReader = XmlReader.Create(reader.BaseStream, settings);
                        XmlTextReader xmlReader = new XmlTextReader(reader.BaseStream);
                        xmlReader.Normalization = false;
                        xmlReader.Read();
                        xmlReader.Normalization = false;

                        string str = "";

                        while (!xmlReader.EOF)
                        {
                            string xml;
                            int num;
                            int num6;
                            int num7;
                            int num8;

                            switch (xmlReader.NodeType)
                            {
                                case XmlNodeType.Element:
                                    xml = "";
                                    num = 0;
                                    goto Element;

                                case XmlNodeType.Text:
                                    {
                                        string str4 = xmlReader.Value.Replace("&", "&amp;").Replace("&lt;", "&lt;").Replace("&gt;", "&gt;").Replace("\"", "&quot;");
                                        str = str + str4;
                                        xmlReader.Read();
                                        continue;
                                    }
                                case XmlNodeType.ProcessingInstruction:
                                    xml = "";
                                    num7 = 0;
                                    goto ProcessingInstruction;

                                case XmlNodeType.Comment:
                                    xml = "";
                                    num8 = 0;
                                    goto Comment;

                                case XmlNodeType.Whitespace:
                                    {
                                        xmlReader.Read();
                                        continue;
                                    }
                                case XmlNodeType.EndElement:
                                    xml = "";
                                    num6 = 0;
                                    goto EndElement;

                                default:
                                    goto Other;
                            }

                        Label_00C0:
                            xml = xml + IndentString;
                            num++;

                        Element:
                            if (num &lt; xmlReader.Depth)
                            {
                                goto Label_00C0;
                            }

                            string elementName = xmlReader.Name;

                            string str5 = str;
                            str = str5 + "\r\n" + xml + "&lt;" + xmlReader.Name;
                            bool isEmptyElement = xmlReader.IsEmptyElement;

                            if (xmlReader.HasAttributes)
                            {
                                // construct an array of the attributes that we reorder later on
                                List&lt;AttributeValuePair&gt; attributes = new List&lt;AttributeValuePair&gt;(xmlReader.AttributeCount);

                                for (int k = 0; k &lt; xmlReader.AttributeCount; k++)
                                {
                                    xmlReader.MoveToAttribute(k);

                                    string value = xmlReader.Value;

                                    if (_model.RemoveCommonDefaults)
                                    {
                                        if (!AttributeValuePair.IsCommonDefault(elementName, xmlReader.Name, value))
                                        {
                                            attributes.Add(new AttributeValuePair(elementName, xmlReader.Name, value));
                                        }
                                    }
                                    else
                                    {
                                        attributes.Add(new AttributeValuePair(elementName, xmlReader.Name, value));
                                    }
                                }

                                if (_model.ReorderAttributes)
                                {
                                    attributes.Sort();
                                }

                                xml = "";
                                string str3 = "";
                                int depth = xmlReader.Depth;

                                //str3 = str3 + IndentString;

                                for (int j = 0; j &lt; depth; j++)
                                {
                                    xml = xml + IndentString;
                                }

                                foreach (AttributeValuePair a in attributes)
                                {
                                    string str7 = str;

                                    if (attributes.Count &gt; _model.AttributeCountTolerance && !AttributeValuePair.ForceNoLineBreaks(elementName))
                                    {
                                        // break up attributes into different lines
                                        str = str7 + "\r\n" + xml + str3 + a.Name + "=\"" + a.Value + "\"";
                                    }
                                    else
                                    {
                                        // attributes on one line
                                        str = str7 + " " + a.Name + "=\"" + a.Value + "\"";
                                    }
                                }

                            }
                            if (isEmptyElement)
                            {
                                str = str + "/";
                            }
                            str = str + "&gt;";
                            xmlReader.Read();
                            continue;
                        Label_02F4:
                            xml = xml + IndentString;
                            num6++;
                        EndElement:
                            if (num6 &lt; xmlReader.Depth)
                            {
                                goto Label_02F4;
                            }
                            string str8 = str;
                            str = str8 + "\r\n" + xml + "&lt;/" + xmlReader.Name + "&gt;";
                            xmlReader.Read();
                            continue;
                        Label_037A:
                            xml = xml + "    ";
                            num7++;
                        ProcessingInstruction:
                            if (num7 &lt; xmlReader.Depth)
                            {
                                goto Label_037A;
                            }
                            string str9 = str;
                            str = str9 + "\r\n" + xml + "&lt;?Mapping " + xmlReader.Value + " ?&gt;";
                            xmlReader.Read();
                            continue;

                        Comment:

                            if (num8 &lt; xmlReader.Depth)
                            {
                                xml = xml + IndentString;
                                num8++;
                            }
                            str = str + "\r\n" + xml + "&lt;!--" + xmlReader.Value + "--&gt;";

                            xmlReader.Read();
                            continue;

                        Other:
                            xmlReader.Read();
                        }

                        xmlReader.Close();

                        result = str;
                    }
                }
            }
            return result.Replace("¬¬", "&");

        }

        private class AttributeValuePair : IComparable
        {
            public string Name = "";
            public string Value = "";
            public AttributeType AttributeType = AttributeType.Other;

            public AttributeValuePair(string elementname, string name, string value)
            {
                Name = name;
                Value = value;

                // compute the AttributeType
                if (name.StartsWith("xmlns"))
                {
                    AttributeType = AttributeType.Namespace;

                }
                else
                {
                    switch (name)
                    {
                        case "Key":
                        case "x:Key":
                            AttributeType = AttributeType.Key;
                            break;

                        case "Name":
                        case "x:Name":
                            AttributeType = AttributeType.Name;
                            break;

                        case "x:Class":
                            AttributeType = AttributeType.Class;
                            break;

                        case "Canvas.Top":
                        case "Canvas.Left":
                        case "Canvas.Bottom":
                        case "Canvas.Right":
                        case "Grid.Row":
                        case "Grid.RowSpan":
                        case "Grid.Column":
                        case "Grid.ColumnSpan":
                            AttributeType = AttributeType.AttachedLayout;
                            break;

                        case "Width":
                        case "Height":
                        case "MaxWidth":
                        case "MinWidth":
                        case "MinHeight":
                        case "MaxHeight":
                            AttributeType = AttributeType.CoreLayout;
                            break;

                        case "Margin":
                        case "VerticalAlignment":
                        case "HorizontalAlignment":
                        case "Panel.ZIndex":
                            AttributeType = AttributeType.StandardLayout;
                            break;

                        case "mc:Ignorable":
                        case "d:IsDataSource":
                        case "d:LayoutOverrides":
                        case "d:IsStaticText":

                            AttributeType = AttributeType.BlendGoo;
                            break;

                        default:
                            AttributeType = AttributeType.Other;
                            break;
                    }
                }
            }

            #region IComparable Members

            public int CompareTo(object obj)
            {
                AttributeValuePair other = obj as AttributeValuePair;

                if (other != null)
                {
                    if (this.AttributeType == other.AttributeType)
                    {
                        // some common special cases where we want things to be out of the normal order

                        if (this.Name.Equals("StartPoint") && other.Name.Equals("EndPoint")) return -1;
                        if (this.Name.Equals("EndPoint") && other.Name.Equals("StartPoint")) return 1;

                        if (this.Name.Equals("Width") && other.Name.Equals("Height")) return -1;
                        if (this.Name.Equals("Height") && other.Name.Equals("Width")) return 1;

                        if (this.Name.Equals("Offset") && other.Name.Equals("Color")) return -1;
                        if (this.Name.Equals("Color") && other.Name.Equals("Offset")) return 1;

                        if (this.Name.Equals("TargetName") && other.Name.Equals("Property")) return -1;
                        if (this.Name.Equals("Property") && other.Name.Equals("TargetName")) return 1;

                        return Name.CompareTo(other.Name);
                    }
                    else
                    {
                        return this.AttributeType.CompareTo(other.AttributeType);
                    }
                }

                return 0;
            }

            public static bool IsCommonDefault(string elementname, string name, string value)
            {

                if (
                    (name == "HorizontalAlignment" && value == "Stretch") ||
                    (name == "VerticalAlignment" && value == "Stretch") ||
                    (name == "Margin" && value == "0") ||
                    (name == "Margin" && value == "0,0,0,0") ||
                    (name == "Opacity" && value == "1") ||
                    (name == "FontWeight" && value == "{x:Null}") ||
                    (name == "Background" && value == "{x:Null}") ||
                    (name == "Stroke" && value == "{x:Null}") ||
                    (name == "Fill" && value == "{x:Null}") ||
                    (name == "Visibility" && value == "Visible") ||
                    (name == "Grid.RowSpan" && value == "1") ||
                    (name == "Grid.ColumnSpan" && value == "1") ||
                    (name == "BasedOn" && value == "{x:Null}") ||
                    (elementname != "ColumnDefinition" && elementname != "RowDefinition" && name == "Width" && value == "Auto") ||
                    (elementname != "ColumnDefinition" && elementname != "RowDefinition" && name == "Height" && value == "Auto")

                    )
                {
                    return true;
                }

                return false;
            }

            public static bool ForceNoLineBreaks(string elementname)
            {
                if (
                    (elementname == "RadialGradientBrush") ||
                    (elementname == "GradientStop") ||
                    (elementname == "LinearGradientBrush") ||
                    (elementname == "ScaleTransfom") ||
                    (elementname == "SkewTransform") ||
                    (elementname == "RotateTransform") ||
                    (elementname == "TranslateTransform") ||
                    (elementname == "Trigger") ||
                    (elementname == "Setter")
                    )
                {
                    return true;
                }
                else
                {
                    return false;
                }

            }

            #endregion
        }

        // note that these are declared in priority order for easy sorting
        private enum AttributeType
        {
            Key = 10,
            Name = 20,
            Class = 30,
            Namespace = 40,
            CoreLayout = 50,
            AttachedLayout = 60,
            StandardLayout = 70,
            Other = 1000,
            BlendGoo = 2000
        }
    }
}

I appreciate that this is a long listing, but don’t worry about what’s going on inside. To hook into the MoXAML plugin architecture, only one line in there is really important:to be picked up

public partial class ScrubberCommandBase : CommandBase

By inheriting from CommandBase, MoXAML picks the command up because CommandBase implements ICommandBase. The cunning thing about MoXAML is that it uses MEF which allows you to mark interfaces so that any implementations of the interface will automatically be picked up. Well, we have most of the infrastructure in place – all we actually need to do is write the actual command. You’ll be surprised how little that actually takes:

using MoXAML.Infrastructure;

namespace MoXAML.Scrubber
{
    public class ScrubFileCommand : ScrubberCommandBase
    {
        public ScrubFileCommand()
            : base()
        {
            CommandName = "ScrubFile";
            Caption = "Scrubber";
            ParentCommandBar.Add(CommandBarType.XamlContextMenu);
        }

        public override void Execute()
        {
            base.Execute();
            ParseFile(Application.ActiveDocument.FullName);
        }
    }
}

Let’s break it down. In the constructor, we’re giving the command a unique name that MoXAML uses to track whether or not the command was previously installed. The Caption is what will actually appear in the menu, and the line ParentCommandBar.Add is used to add the command to the appropriate menu.

Finally, we actually need our command to do something, and that’s where the Execute method comes in – MoXAML uses this method to execute the command, so it’s the place to hook our command logic to. In this case, we’re hooking into the ParseFile method in the class we inherit from.

MoXAML goes MefXAML

First of all, I must apologise that it’s taken me so long to release the new version of MoXAML. Unfortunately it’s taken a back burner to other projects that I have going on, so it’s been lower down on my priority list than I would have liked. Saying that, I have been tinkering with a new version and making changes to the internal architecture to make it easier for me to add new functionality to it. MoXAML has now got the MEF mojo underpinning it.

A feature of the original version of MoXAML that I was never happy with was the way I had to register commands so that they will appear in the menus. To be honest, the code was a complete mess and it offended my sensibilities whenever I looked at it. Well, the new version has completely changed that. I’ve added a MoXAML.Infrastructure library that provides a lot of the underpinnings of MoXAML registration; all we need to do is derive from the CommandBase class in this library and with just a couple of commands we are good to go.

First of all, we need to add a unique command name, the menu text and the parent command that we want to show this command in. This just gets added into the constructor likes so:

public MyConstructor()
{
  CommandName = "MyUniqueCommandName";
  Caption = "Menu text";
  ParentCommandBar.Add(CommandBarType.SolutionContextMenu);
}

Now, to add the code to actually do the work, just override the Execute method and add in the functionality that you want to perform.

In a future post, I’ll cover creating an add-in from scratch and talk about the API that’s now been exposed in MoXAML. To get the latest version of MoXAML, please check it out here.

It’s time to dock with the mothership

It seems that the many headed Hydra that is VS Live is coming round again in October, and it’s bigger and better than ever. If you thought it was good before, you haven’t seen anything yet.

While I can’t promise that attending it will make you more successful with members of whichever sex you prefer, I can tell you that some of my favourite speakers are going to be there. Rachel Appel will be delivering talks on creating data driven web sites with WebMatrix and Razor, a dive into how Orchard CMS works and will finish off with a high dive off Microsoft 1 into a thimble containing an inch of water.

If you think that’s impressive, Pete Brown will be delivering a whole host of XAMLicious goodness and rumour has it that he will do this tied up in chains, dangling upside down in a big box of water, wearing nothing other than a goose-fat thong. You owe it to yourself to get along to the Redmond mothership and catch these outstanding talks, and a whole lot more.

Disclaimer: Some of this shizzle is made up, but the bit about the great speakers and the topics they’ll be talking about is 100% straight up.

You see, you don’t always need code behind.

By now you should be aware that I’m a big fan of attached behaviors. In this post, I’m going to demonstrate a simple technique to add resize and close functionality to window buttons when you want to custom draw your window chrome without having to add code behind the window. This is going to be a quick post, because it’s just so darned easy.

Note: Originally I was using Application.Current.MainWindow to retrieve the window as I only ever apply this trick to the application main window. Mike Strobel suggested using Window.GetWindow instead to retrieve the logical window for the button, just in case it was in a separate window. I’ve adjusted the code sample here to demonstrate this as it makes this more reusable. Thanks Mike.

namespace AttachedTitleButtonsSample
{
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;

    /// <summary>
    /// Attach this behaviour to a button to enable a button to change the window state without
    /// having to write any code behind the view.
    /// </summary>
    public partial class TitleButtonBehavior : Behavior<Button>
    {
        /// <summary>
        /// The tile button action to apply.
        /// </summary>
        public enum TitleButtonAction
        {
            /// <summary>
            /// Close the application
            /// </summary>
            Close,
            /// <summary>
            /// Maximize the application
            /// </summary>
            Maximize,
            /// <summary>
            /// Minimize the application
            /// </summary>
            Minimize,
            /// <summary>
            /// Reset the application to normal
            /// </summary>
            Normal
        }

        /// <summary>
        /// Gets or sets the button behavior.
        /// </summary>
        public TitleButtonAction ButtonBehavior { get; set; }

        /// <summary>
        /// Add the click handler when this is attached.
        /// </summary>
        protected override void OnAttached()
        {
            this.AssociatedObject.Click += AssociatedObject_Click;
            base.OnAttached();
        }

        /// <summary>
        /// Remove the click handler when this is detached.
        /// </summary>
        protected override void OnDetaching()
        {
            this.AssociatedObject.Click -= AssociatedObject_Click;
            base.OnDetaching();
        }

        /// <summary>
        /// Change the window state when the button is clicked.
        /// </summary>
        void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            Window window = Window.GetWindow(AssociatedObject);
            switch (ButtonBehavior)
            {
                case TitleButtonAction.Close:
                    window.Close();
                    break;
                case TitleButtonAction.Maximize:
                    window.WindowState = WindowState.Maximized;
                    break;
                case TitleButtonAction.Minimize:
                    window.WindowState = WindowState.Minimized;
                    break;
                case TitleButtonAction.Normal:
                    window.WindowState = WindowState.Normal;
                    break;
            }
        }
    }
}

Basically, all you need to do is create an attached behavior that hooks up to the Click event of the button and sets the size based on the appropriate value from the enumeration.

Sample application
I’ve attached a sample application that demonstrates this technique in action. As always, when you download the sample, you’ll need to rename it from a doc to a zip file.

AttachedTitleButtonsSampleZip