Your software architecture is not more important that producing working software

Designing software is hard, producing internal design for the software (the software “architecture”) that both supports the user requirements and makes it easier for developers to work on the software is a real challenge.

There are a lot of “best practices” (I really hate that term), frameworks, libraries, software design principles and design patterns that are supposed to help with this challenge.

The idea behind those is that some very smart and very experienced people already solved the problem for you, they found the correct way to structure software and indentified all the pitfalls, they then wrote some easy to follow rules – if you follow those rules you will produce quality software.

Software developers are very logical people – people that are used to simple black-and-white rules (because basically that’s all the computer can understand and perform) – so software developers take those rules written by people smarter and more experienced then them and follow them religiously.

And then everything breaks down.

Every software project is different, there’s is almost nothing in common between high end graphic software and normal everyday business software or between word processors and computer games and even sometimes between normal everyday business software built for different industries.

So those software design principles, formulated by very smart and very experienced people, truly describe a near-perfect pitfall free software design – for the specific project that smart and experienced person worked on when he wrote that principle (or, in the best case, for the kind of projects that he usually works on).

And large groups of software developers all around the world, working on a large variety of different software projects, all blindly following the one true software design – and most of them hit some completely obvious limitations of that design, simply because they are doing something that didn’t exist in the project that influenced that design.

But the smart and experienced guru is never wrong, and the failing design principle always looks so logical and right (because it is right, just not for your specific project).

So those poor developers keep religiously following the one true design, building one huge patch after the other working around the limitation of the one true design, doing more and more work, making the software more and more complicated (and adding more and more bugs in the process).

And so, I have one request for my fellow software developers – never value your precious architecture more than actually producing working software that your customers can user.

If you need a global messaging system to do something that should be one method call you’re doing it wrong and should rethink your architecture.

If your design uses some design patterns that is not supported by your platform you’re doing it wrong and should rethink your architecture.

If you’re using a library for something it wasn’t designed to do you’re doing it wrong and should rethink your architecture.

And in general if you are doing something that should be easy but just hit a wall, don’t start coding some clever and complicated solution, first stop and think how to keep the simple things simple – even if you have to change your architecture.

Easy form layout in WPF Part 3 – Adding Groups

This is the third and final post in a series, you may want to start from the beginning:

  1. Easy form layout in WPF Part 1 – Introducing FormPanel.
  2. Easy form layout in WPF Part 2 – How to deal with more complicated scenarios
  3. Easy form layout in WPF Part 3 – Adding Groups (You are here).

Let’s divide the controls from out previous example into groups and produce this dialog box:

Putting multiple FormPanel panels in the window, each in its own GroupBox, is simple – but we need somehow to synchronize the sizes across multiple controls (and we still want to minimize typing).

Let’s look at the XAML for this window (only the form panels part this time)

<l:FormGroupHost>
    <l:FormGroup Header="General Details">
        <TextBlock Text="Title:"/>
        <TextBox/>
        <TextBlock Text="Area:"/>
        <ComboBox/>
        <TextBlock Text="Category:"/>
        <ComboBox/>
        <TextBlock Text="Assigned To:"/>
        <ComboBox/>
        <TextBlock Text="Status:"/>
        <ComboBox/>
        <TextBlock Text="Estimate:"/>
        <TextBox/>
    </l:FormGroup>
    <l:FormGroup Header="User Defined Fields">
        <TextBlock Text="Tags:"/>
        <TextBox/>
        <TextBlock Text="Version:"/>
        <TextBox/>
    </l:FormGroup>
</l:FormGroupHost>

Ok, what’s going on here? there’s a new FormGroup that works like a FormPanel except it has an Header property that probably draws the GroupBox and a FormPanelHost that displays all the groups one after the other and maybe takes care of synchronizing the sizes between panels.

Let’s look at the code for FormGroup:

    public class FormGroup : HeaderedItemsControl
    {
    }

That is one short class, let’s look at it’s XAML style:

<Style TargetType="l:FormGroup">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <l:FormPanel/>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="l:FormGroup">
                <GroupBox Header="{TemplateBinding Header}" Padding="5">
                    <ItemsPresenter/>
                </GroupBox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
 

So, what do we have here? FormGroup is an HeaderedItemsControl and it has a control template that simply put all it’s children inside a GroupBox, it also has a panel template that makes is use a FormTemplate.

FormGroup is just a FormPanel inside a GroupBox, the clever part is that children of an ItemsControl are added to it’s panel so we don’t have to type the group box and panel XAML for every group.

Now let’s look at FormGroupHost:

    public class FormGroupHost : ItemsControl, IFormPanelCoordinator
    {
        protected override Size MeasureOverride(Size constraint)
        {
            Size result = base.MeasureOverride(constraint);
            DoSizing();
            return result;
        }

        private void DoSizing()
        {
            List groups = new List();

            for (int i = 0; i < Items.Count; ++i)
            {
                var group = ItemContainerGenerator.ContainerFromIndex(i) as FormGroup;
                if (group != null && group.Items.Count > 0)
                {
                    var container = group.ItemContainerGenerator.ContainerFromIndex(0);
                    var panel = VisualTreeHelper.GetParent(container) as FormPanel;
                    if (panel != null)
                    {
                        panel.Coordinator = this;
                        groups.Add(panel);
                    }
                }
            }

            double labelMaxWidth = 0;
            double labelMaxHeight = 0;
            double controlMaxWidth = 0;
            double controlMaxHeight = 0;

            foreach (var current in groups)
            {
                labelMaxWidth = Math.Max(labelMaxWidth, current.LabelSize.Width);
                labelMaxHeight = Math.Max(labelMaxHeight, current.LabelSize.Height);
                controlMaxWidth = Math.Max(controlMaxWidth, current.ControlSize.Width);
                controlMaxHeight = Math.Max(controlMaxHeight, current.ControlSize.Height);
            }

            foreach (var current in groups)
            {
                current.LabelSize = new Size(
                    labelMaxWidth, labelMaxHeight);
                current.ControlSize = new Size(
                    controlMaxWidth, controlMaxHeight);
            }
        }

        #region IFormPanelCoordinator Members

        void IFormPanelCoordinator.ControlOrLabelSizeChanged(FormPanel sender)
        {
            DoSizing();
        }

        #endregion
    }

finally some code, now it’s really quite simple:

  1. Iterate over all FormGroups, extract form panel, get label and control size and set myself as the coordinator (finally we find out what the coordinator is for) for the panel.
  2. Iterate over all panels again, set the maximum label and control size

We do this on MeasureOverride (when the system tells us our layout changed) and when one of the panels notifies us (via the IFormPanelCoordinator interface) that its sizing changed.

FormGroupHost is also an ItemsControl but it ahs no style, by default this will put all the children in a StackPanel (we can change that with a style or by setting ItemsPanel if we want).

You can download the code, VS2008 project that targets .net 3.5SP1 here: FormPanelApp sample code.

The project also contains OkCancelFrame another typing saving control for MVVM that I may write about in the future.