Damon Payne: Hand waving software architect

103db signal to noise ratio at < .03% total harmonic distortion
Solution Architect, software developer, geek
Damon Payne at Blogged
2009 Microsoft MVP - Client App Dev
2007 Microsoft MVP - Solution Architecture
 Tuesday, December 02, 2008

The AGT (Argentum Tela) series of articles is an effort to do two things. Usually an idea is presented only in its finished form. The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches. The second goal, and the example vehicle for the evolution aspect, is an extensible Design Surface for Silverlight similar to what we have in Visual Studio 2008. This type of application has all sorts of interesting uses. My example is a Home Theater layout tool. Read the entire saga: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx

 

Finishing Property Editing

In the last article, I mentioned the need for some more complex test cases to use to help determine when Property Editing was done.  I could see that too much logic was living in EditServiceHelper and that the editing of properties for multiple items at once was going to become very difficult.  It has been far longer than I intended since the last article (user group presentations, etc.) and there's a lot of cool stuff we can get to after we get Property Editing working the rest of the way so let's get to it.

A Better Example

A much better example was required right off the bat.  This required some more up front thought and setup.  Each Property potentially has a Display Visual and an Edit Visual, and in some cases these may be the same instance.  I need to test some reasonable scenarios:

  1. If multiple items are selected that happen to have a Property with the same name and of the same type and with the same Editors we can edit this Property for all selected items with a single Edit Visual even if the items are not of the same Type.  Only the lowest common denominator of Properties should display.
  2. Basic edit scenarios: an Enum Property is editable in a ComboBox, a string Property is Editable in a TextBox, and so forth.
  3. In some cases the Display Visual will be a custom IDesignablePropertyVisualizer Type which should support some kind of Binding so it auto-updates when edited.
  4. In some cases the Edit Visual will be a custom IDesignablePropertyEditor Type which should support binding as well as setting the value to the actual object on loss of focus or Enter key.
  5. Refactor: It seems clear at this point that since I'm heavily using Bindings, IValueConverter will come in handy, so this has been added to DesignablePropertyDescriptor.

In order to test all of this I'll make the following changes:

  • Couch and Chair will get a Softness property of type Double which I'll edit with a custom slider.  This should work for single or multi selection. (Tests #1, #4)
  • Softness will be displayed with a custom "volume style" Visualizer (Tests #3)
  • The Color property of Chair & Couch will get a Converter that displays a Color name instead of the hex value as shown in the previous article. (Tests #1, #2, #5)

Beyond this, we'll simply need to think about the various "basic" edit scenarios to support, like editing boolean values with a radio button or checkbox.  I've added "Editors" and "Visualizers" namespaces to the DamonPayne.HTLayout project and created the requisite implementations.

 

PropertyGridModel

The various private fields I had been adding to PropertyGrid for keeping track of various states and such clearly needed to be moved to a single class which I now call PropertyGridModel.  All of the methods are virtual to allow enterprising developers to easily tweak behavior.  The public interface for PropertyGridModel :

/// <summary>
/// What are we editing?
/// </summary>
public virtual List<IDesignableControl> Selection { get; set; }
/// <summary>
/// Properties common across the entire Selection
/// </summary>
/// <param name="props"></param>
public virtual void SetProperties(List<DesignablePropertyDescriptor> props)

 

/// <summary>
/// Only 1 property is editing at a time
/// </summary>
public FrameworkElement CurrentEditElement { get; set; }
public virtual void SetDisplayElement(DesignablePropertyDescriptor d, FrameworkElement fe)...
public virtual FrameworkElement GetDisplayElement(DesignablePropertyDescriptor d)...
public virtual List<FrameworkElement> GetAllDisplayElements()...
public virtual void SetEditElement(DesignablePropertyDescriptor d, FrameworkElement fe) ...
public virtual FrameworkElement GetEditElement(DesignablePropertyDescriptor d)...
public virtual DesignablePropertyDescriptor GetDescriptorForEditElement(FrameworkElement fe)...
public virtual DesignablePropertyDescriptor GetDescriptorForDisplayElement(FrameworkElement fe)...
/// <summary>
/// Remove everything from the Model
/// </summary>
public virtual void Reset()...

 

EditServiceHelper

I continued by overhauling the EditServiceHelper class.  I realized my notion of the TypeDescriptor-like functionality of this class was not fully baked, but sitting down to look at it the second time a much more cohesive purpose coalesced.  I also made sure to pull in the concepts of IDesignablePropertyEditor and IDesignablePropertyVisualizer right away.  I'm only going to show the Display path and no the Edit path here for brevity, they are very similar.

In a static constructor we set up some defaults, this will be expanding as I go.

AddDefaultDisplayor(typeof(string), typeof(TextBlock));
AddDefaultDisplayor(typeof(Color), typeof(TextBlock));
AddDefaultEditor(typeof(string), typeof(TextBox));
AddDefaultEditor(typeof(Color), typeof(ComboBox));
AddBasicBindableDisplayType(typeof(TextBlock), TextBlock.TextProperty);
AddBasicBindableEditType(typeof(TextBox), TextBox.TextProperty);
AddBasicBindableEditType(typeof(ComboBox), ComboBox.SelectedItemProperty);

These are all public static methods in case someone needs to expand this faster than I get to it.

Client code (PropertyGrid) will call GetDisplayInstance to get some type of Visual that can be used to represent the value of a single property

public static FrameworkElement GetDisplayInstance(IDesignableControl instance, DesignablePropertyDescriptor desc)
{
    //if null we try to find a default, otherwise see if we can do a binding anyway using the override
    if (null == desc.DisplayType || _basicBindableDisplayTypes.ContainsKey(desc.DisplayType))
    {
        Type displayType = _displayors[desc.PropertyInfo.PropertyType];
        FrameworkElement displayInstance = null;
        if (null != displayType)
        {
            displayInstance = (FrameworkElement)Activator.CreateInstance(displayType);
            SetupDisplayInstanceBinding(instance, desc, displayInstance);
        }
        return displayInstance;
    }
    else if (desc.DisplayType.ImplementsInterface(typeof(IDesignablePropertyVisualizer)))
    {
        var visualizer = (IDesignablePropertyVisualizer)Activator.CreateInstance(desc.DisplayType);
        visualizer.Initialize(instance, desc);
        return visualizer.Visual;
    }
    return null;
}

First check defaults, then check special overrides, then ultimately give up.  ImplementsInterface is an extension method in the DamonPayne.AGT.IoC project.  SetupDisplayInstanceBinding handles the rest of the work which is fairly simple with the help of Silverlight Binding.

protected static void SetupDisplayInstanceBinding(IDesignableControl instance, 
    DesignablePropertyDescriptor desc, FrameworkElement display)
{
    if (_basicBindableDisplayTypes.ContainsKey(display.GetType()))
    {                
        var dProp = _basicBindableDisplayTypes[display.GetType()];
        Binding b = new Binding(desc.PropertyInfo.Name);
        b.Converter = desc.Converter;
        b.Mode = BindingMode.TwoWay;
        b.Source = instance;
        display.SetBinding(dProp, b);
    }
}

How might a custom Property Visualizer work?  Take a look at part of the code behind for displaying the "softness" property of Couches & Chairs:

public double Softness
{
    get { return (double)GetValue(SoftnessProperty); }
    set { SetValue(SoftnessProperty, value); }
}

// Using a DependencyProperty as the backing store for Softness.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty SoftnessProperty =
    DependencyProperty.Register("Softness", typeof(double), typeof(SoftnessVisualizer), new PropertyMetadata(SoftnessChanged));

public static void SoftnessChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    if(obj is SoftnessVisualizer)
    {
        ((SoftnessVisualizer)obj).ScaleSoftness();
    }
}

protected void ScaleSoftness()
{
    if (Softness > 0.0)
    {
        VisualScale.ScaleX = Softness;
        VisualTranslate.X = 10 * Softness;
    }
    else
    {
        VisualScale.ScaleX = 1.0;
        VisualTranslate.X = 0.0;
    }
}

/// <summary>
/// We know we're just for softness!
/// </summary>
/// <param name="instance"></param>
/// <param name="desc"></param>
public void Initialize(DamonPayne.AGT.Design.IDesignableControl instance, DamonPayne.AGT.Design.Types.DesignablePropertyDescriptor desc)
{
    if (null != instance)
    {
        Binding b = new Binding("Softness");
        b.Converter = desc.Converter;
        b.Mode = BindingMode.TwoWay;
        b.Source = instance;
        SetBinding(SoftnessVisualizer.SoftnessProperty, b);
    }
}

The visual representation of softness here is a triangle that I am stretching based on Softness but that's not important.  In Initialize we set up a binding to the particular Property we care about which handles keeping in sync with the underlying object.  Using a changed callback for the Softness DependencyProperty allows us to tweak the UI if an editor changes this value in some fashion.

You can see here that I am making a heavy dependence on System.Windows.Data.Binding concepts which in turn means DependencyProperties.  This is OK as far as I'm concerned.  The Binding in Silverlight/WPF is the Data Binding you've always wished you had.  It is awesome.  It is likely going to be used frequently and you should learn it, love it, learn where it breaks down, and return to loving it.

 

The PropertyGrid logic is now fairly simple and just swaps an Edit Visual out for a Display Visual when the Display Visual is clicked on.

 

The ColorConverter I wrote may be helpful as well...

public class ColorConverter : IValueConverter
{

    static ColorConverter()
    {
        _colorNameToColor = new Dictionary<string, Color>();
        _colorToName = new Dictionary<Color, string>();
        Type cType = typeof(System.Windows.Media.Colors);
        PropertyInfo[] colors = cType.GetProperties(BindingFlags.Public | BindingFlags.Static);
        foreach (var propInfo in colors)
        {
            var c = (Color)propInfo.GetValue(null,null);
            _colorNameToColor.Add(propInfo.Name, c);
            _colorToName.Add(c, propInfo.Name);
        }
    }

    private static Dictionary<string, Color> _colorNameToColor;
    private static Dictionary<Color, string> _colorToName;


    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is Color)
        {
            Color src = (Color)value;
            if (targetType == typeof(string))
            {
                if (_colorToName.ContainsKey(src))
                {
                    return _colorToName[src];
                }
                else
                {
                    return src.ToString();
                }
            }
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is string)
        {
            string src = value.ToString();
            if (targetType == typeof(Color))
            {
                if (_colorNameToColor.ContainsKey(src))
                {
                    return _colorNameToColor[src];
                }
            }
        }

        return null;
    }
}

How does it look?

I needed to create some better visuals than the placeholders I had at the end of the previous article.  The default ControlTemplate for PropertyGrid now looks like this with a Chair selected.  Click on any image for the larger version.

Vision14

Note my special visualizer (I am no artist) for Softness.  When I select Softness, I get a custom Editor as well:

 Vision15

With a chair, a couch, and a Dummy Button selected, the only Property they have in common is Name, so only name shows up in the grid:

Vision16

Couches and Chairs have lots in common, so I could edit the Color of them all at once:

Vision17

 

Unit Tests

Jeff Wilcox released a full Silverlight 2 version of his Silverlight Unit Test Framework, so the AGT Solution has been updated to use this.  I have yet to find new Templates, so the Templates still say Beta and the default test project has to be tweaked slightly.  This is extremely handy for things like IValueConverters, and I will be greatly increasing test coverage in the future.

Conclusion

So, we've got Property Editing working now and this is starting to look like a real Design Time Environment.

The source code is checked into Codeplex as downloadable Change Set 8353 here.  I have also created a 0.7.17.2 "Planning" source drop on Codeplex.

 

The Live Demo has been updated at http://www.damonpayne.com/agt/

 

In the next article, we will be going through some refactoring items and some questions that I've been meaning to get to.



Tuesday, December 02, 2008 3:57:51 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Catastrophic Failure is such an excellent error message, but I often have to wonder if it's necessary.  Include my favorite "unknown error" in there as well.  It seems that often enough the implementers of Silverlight 2 ought to know what the error is enough to give me a more meaningful error message?  Suppose you were implementing some generic binding code:

protected static void SetupDisplayInstanceBinding(IDesignableControl instance, 
    DesignablePropertyDescriptor desc, FrameworkElement display)
{
    if (_basicBindableDisplayTypes.ContainsKey(display.GetType()))
    {                
        var dProp = _basicBindableDisplayTypes[display.GetType()];
        Binding b = new Binding(desc.PropertyInfo.Name);
        b.Converter = desc.Converter;
        b.Mode = BindingMode.TwoWay;
        b.Source = instance;
        display.SetBinding(dProp, b);
    }
}

Now, suppose dProp returns TextBox.TextProperty but display is of type TextBlock - I am constantly mixing those two.  The call to display.SetBinding(dProp, b); will cause the Catastrophic Failure.  Why couldn't it say "The DependencyProperty is not an Attached Property and does not belong to type TextBlock" or something like that?



Tuesday, December 02, 2008 10:55:01 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

The Terski got me into Seth Godin maybe a month ago.  Good stuff.  Marketing and Excellence in Business are becoming increasingly relevant to those who wish to get beyond Code Monkey status.  Not that there's anything wrong with being a code monkey.

While discussing one of Seth's recent posts with a co-worker, he made the simple observation that

"There's something wrong with the old school."

There's something wrong with the old school.  No shit.  He was talking about the music industry.  The auto manufacturers come to mind too.  So does the entire Real Estate industry.  So do a lot of things.

It should be pretty obvious to any reader that I'm in favor of laissez faire capitalism, and yet for a while I've been meaning to write something on what is wrong with business in America right now.  No, it's not greed.  It's not that there's not enough regulation.  It's not that there's not enough skilled workers in America.

Our corporate culture is in some ways broken.  I think there is a lack of vision, an out of touch quality, a laziness and a weakness in the leadership of many of America's big businesses.  In the past 20 years, more and more of us look at Dilbert and our first reaction is not to laugh, but to see reflections of our own work day in the pointy haired boss.

Out of Touch to the point of insanity

I'm still driving my Subaru WRX that I got in 2001.  It's a great car.  By the end of 2002, Subaru was importing around 10,000 of these per year.  This car is very Japanese, it's a 2.0 liter turbo-charged all wheel drive four door that's fun to drive and practical enough for me; I get around 27mpg.  Detroit's answer to the WRX and to the Lancer Evolution and the other cars that followed was more or less that Americans don't want cars like this.  Americans want Corvettes and Mustangs and SUVs.  Yet, the WRX, the WRX STi, the Lancer Evolution, the Nissan GT-R, and others show that Americans clearly do want cars like this.  The Ironic Icing on the Cake of Incompetency is that if you look at the overseas offerings from companies like Ford you'll find (wait for it) things like an all wheel drive turbo-charged car that sure looks fun to drive.

Instead of thinking and doing, Detroit spends their time and money lobbying the government to take choices away from you, to making sure the law protects them from having to change with the times and with consumers' wants.  Our engineers and workers are great, our leadership is killing us.

The music industry couldn't be more out of touch.  I don't think I even need to expand this point?

Status Quo vs. Innovation

As Seth pointed out in the already referenced story, a business with a strong brand will ultimately find itself with two choices.  It can use this brand to build the Next Big Thing or it can do its best to keep the Next Big Thing from happening.  Think about how hard the Music and Movie "content" industry has fought digital distribution.  Their price for losing this battle is that Apple Computers, of all people, is considered by many insiders to be "calling the shots in the music industry".

My favorite example in this realm is the Real Estate industry.  If you've ever bought or sold a house you probably have a vague notion that there is this thing called an MLS (Multiple Listing System) and this group called the National Association of Realtors.  Getting your home on the MLS means that if there's a buyer for your home, they'll find it.  You pay a 6% commission for this service.  Some brokers will work harder than others to sell your house, but you're really paying a fee to get into the MLS. 

From my point of view, the National Association of Realtors has spent most of their time in the past 20 years protecting this 6% commission model under the ostensible banner of "protecting consumers".  They even got laws passed in some states making it a legal fact that brokers had to charge a 6% commission.  Then Buyers Agents came along, representing the home buyer in this important transaction and they wanted part of the commission.  Then The Internet came along and companies sprang up who would charge a much smaller fee (less than $1,000) to get your home into the MLS (the most valuable thing a seller can do) but leaving you to do the work of actually showing your house to sellers.  The NAR and many local MLS franchises went to work outlawing the operations of these companies in order to protect the Old Guard and had a degree of success until the DOJ got involved. 

The net worth of most Americans is tied up in the value of their homes.  Suppose you have no debts and no assets but you managed to put 20% down on a $300,000 home.  Your net worth would be $60,000.  Commission on selling the home would be $18,000, or nearly a third of your net worth.  Despite how infrequently you sell your home, why would you want to give a significant portion of your net worth to brokers if there's a good alternative?

The Realtors had a captive and grateful audience.  They could have become the Internet Real Estate solution for brokers.  Instead, they tried to stuff the genie back into the bottle, they tried to hinder the online business models of their members in order to protect the status quo.  Brokers instead had to go outside the MLS, they had to pay technology companies to build their websites, to build the systems they use to communicate with their buyers and sellers, the systems that show homes on Google Base and other content aggregators.  The MLSs left this money on the table, and now they've lost it forever and they can expect to see themselves slowly decline in relevance.

Culture of Fear and Stagnation

There is an underlying current of fear and stagnation in many American businesses, especially the larger ones.  Managers and executives do not take risks.  They don't learn new things.  They spend their days solidifying their own power.  They have to force their employees to be at their desks between 8am and 4pm rather than advocate virtual offices because otherwise they'd have no idea how much work their employees were doing!  Your manager probably does not reward risk taking because their manager probably does not reward their risk taking and all the way up to the top.

Chances are your manager has been in his or her job longer than you, and they've got a career path to protect.  How much of the upper management at your company has been there longer than five years?  Longer than ten?  Is the company you work for providing incentive for and rewarding good behavior or are they rewarding people who Do Their Time and erect walls around their Empire?  When was the last time you saw an announcement go out in your company saying "Damon took a risk and he failed, but ultimately it was a good risk that could have payed off big for this firm" ?  You've probably never heard that at work.  You've probably at some time worked for a business that claims to to desire one kind of behavior but clearly rewards the opposite.

If your manager is new, are they shaking things up, bringing in new ideas and processes?  Or are they tripping over themselves in their hurry to appease the old guard?  How many of the policies you're supposed to be following at work are there because your manager/company is a afraid of being spied on, afraid of competition, afraid of being sued, afraid of having to change.

Does your manager reward smart risk?  Do they do the right thing even if it means putting their empire at risk?  Do they trust you to do your job once you've proven trust-worthy?  Do they try to give you what you need to be successful?  Be sure to tell them you notice.

What's the Answer?

None of this is to say that young people have all the answers, that "thinking outside the box" is always what's called for, or that all of our corporate leadership are frightened little empire builders.  My own manager is almost too future-looking sometimes.

Companies and individuals, big and small, are shaking things up.  Apple is forcing the music industry to change.  Tesla Motors is close to proving Detroit wrong.  Southwest is growing while other airlines flounder.  Despite ever-increasing regulation and taxes, America is still the most free nation in the world and that means opportunity.  What can you do?

  • Don't do business with dinosaurs if you can help it.  Don't reward bad behavior.  When something new comes along that shows that someone "gets it", do business with them instead if you can. 
  • Don't break the law or act unethically.  On some level, I think we all know that intellectual property is still property.  It costs millions of dollars to make a movie, so don't pirate them.  When breaking the law is your protest, the response is all too often more laws and less freedom.
  • Don't work for dinosaurs.  This might be the hardest one to follow, and will in fact not be a real option to many of us.  Still, if you're a top performer, chances are you can work elsewhere even in the worst economy.  Work for someone who gets it, or start your own business.
  • Encourage those who show good leadership when you see it in action.  Some Project Managers are only able to walk around and ask you what the percent complete on a task is.  When you encounter the ones who are really providing guidance and removing barriers so the project gets done make sure they know you get it.

Shake things up.  Do better.  Succeed.  There's something wrong with the old school.



Tuesday, December 02, 2008 8:22:31 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Friday, November 28, 2008

After some hideous CSS work I have deployed a new default theme for the blog.  I couldn't have done it without the help of CJ from CarSpot; I definately owe him a link on here as soon as I find the URL of his freelance site. 

The old design looked horrid in some browsers, mostly IE 6, 7, and 8 and Firefox 2 and 3...

Some older pages/huge images are absolutely breaking the layout still, but anything newer (posted with Live Writer using <pre> tags for code) should be working and scrolling code.  Please leave me a comment if you notice anything broken and I'll continue to tweak.



Friday, November 28, 2008 12:05:56 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [5]  |  Trackback
 Friday, November 21, 2008

Regular readers may observe that my interest in Silverlight is primarily in making business applications and Visual Tools. I am not an artist and I don't stream music or movies.  No, I'd just like to see Silverlight replace HTML, CSS, and Javascript.  There are a few issues with this plan right now (other than wish thinking) - binding ought to be closer to what WPF has.  There are some handy controls missing. 

There's no built in Validation strategy. 

Obviously nothing stops me from doing validation in a completely manual fashion but it's unlike Microsoft to leave holes like this...

Strategy

We have Extension Methods, but not Extension Properties or Extension Events, so simply adding Validating event handlers isn't an option.  What's more, the typical Windows Forms strategy requires an Extender Provider framework - perhaps when I get further on the AGT Project I'll revisit this, but for now something quicker to implement would be better.

For some reason the ASP.Net model was intriguing to me given the current state of Silverlight.

  1. The Validators have a visual representation and function essentially as a Control.
  2. The Validators "point to" the Control they will validate, so they are completely outside the rest of the functionality on the Page so to speak.
  3. Making sure to trigger Validation via code like if(Page.IsValid) { /* do something*/} is a fair enough way to write code.

So, could we create something like this, but in a reasonably Silverlight-y way? Read on.

Test Form and three sample validation scenarios

I have created a very simple Wine Order Form:

Form0

I have some simple data requirements for this order form.

  • For Name, I'd like something similar to the Asp.Net RequiredFieldValidator
  • For Email, I'd like a RegexValidator
  • For Bottles of Wine I'd like a RangeValidator

Design Goals

So, how would I like this to work and what should the programming experience be?

  • I'd like some sort of Control in the Visual Tree representing the Validation state of an associated Control; it should be easy to Template
  • I do not want to require any interface implementation, base classes, or any changes to the Visual Tree besides the addition of the Validation visuals. (ie no <v:ValidationArea>... your XAML here </v:ValidationArea>, although this is a common enough model in Silverlight and WPF)
  • Calling an arbitrary # of Validators on any given Control or Panel should require as little code as possible
  • Adding new Validator types should be as easy as possible.
  • It should feel like the Validators have saved programming time and created consistency versus pure manual validation.

 

Implementation

How did I do on these goals?  Let's see.

Validator Control Class

As I stated, I've taken the approach of creating a Control which will live in the Silverlight Visual Tree.   Any inheritance involving XAML & User Controls is painful or impossible, but I wanted to see about directly inheriting from Control and using a ControlTemplate instead of working at any kind of visual inheritance.  I'm happy with the results.

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace DamonPayne.AGContrib.Validation
{
    [TemplatePart(Name = "LayoutRoot", Type = typeof(Panel))]
    public class Validator : Control
    {
        public Validator()
        {
            DefaultStyleKey = typeof(Validator);
            Loaded += new RoutedEventHandler(Validator_Loaded);
        }

        private Panel _layoutRoot;

        void Validator_Loaded(object sender, RoutedEventArgs e)
        {
            CheckValidState(this);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _layoutRoot = (Panel)GetTemplateChild("LayoutRoot");
        }

        /// <summary>
        /// A single Framework Element to display when Valid, perhaps an empty TextBlock?
        /// </summary>
        public FrameworkElement ValidContent
        {
            get { return (FrameworkElement)GetValue(ValidContentProperty); }
            set { SetValue(ValidContentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ValidContent.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValidContentProperty =
            DependencyProperty.Register("ValidContent", typeof(FrameworkElement), typeof(Validator), new PropertyMetadata(new TextBlock()));



        public FrameworkElement InValidContent
        {
            get { return (FrameworkElement)GetValue(InValidContentProperty); }
            set { SetValue(InValidContentProperty, value); }
        }

        //Inheritance, does NOT like sharing visual elements, very sneaky
        public static readonly DependencyProperty InValidContentProperty =
            DependencyProperty.Register("InValidContent", typeof(FrameworkElement), 
            typeof(Validator), 
            new PropertyMetadata(new TextBlock { Text="*", Foreground=new SolidColorBrush(Colors.Red) }));



        /// <summary>
        /// Tool tip content, possibly explaining more information about why the value is invalid
        /// </summary>
        public FrameworkElement InvalidToolTip
        {
            get { return (FrameworkElement)GetValue(InvalidToolTipProperty); }
            set { SetValue(InvalidToolTipProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InvalidToolTip.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InvalidToolTipProperty =
            DependencyProperty.Register("InvalidToolTip", typeof(FrameworkElement), typeof(Validator), new PropertyMetadata(null));


        /// <summary>
        /// Is the target Control currently valid
        /// </summary>
        public bool IsValid
        {
            get { return (bool)GetValue(IsValidProperty); }
            set { SetValue(IsValidProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsValid.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsValidProperty =
            DependencyProperty.Register("IsValid", typeof(bool), typeof(Validator), new PropertyMetadata(true, IsValidChanged));


        public static void IsValidChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (obj is Validator)
            {
                var v = (Validator)obj;
                CheckValidState(v);
            }
        }

        /// <summary>
        /// show/hide the valid/invalid FrameworkElement
        /// </summary>
        /// <param name="v"></param>
        private static void CheckValidState(Validator v)
        {
            if (null != v.ValidContent && null != v.InValidContent)
            {
                if (v.IsValid)
                {
                    v.ValidContent.Visibility = Visibility.Visible;
                    v.InValidContent.Visibility = Visibility.Collapsed;
                }
                else
                {
                    v.ValidContent.Visibility = Visibility.Collapsed;
                    v.InValidContent.Visibility = Visibility.Visible;
                }
            }
        }

        /// <summary>
        /// The name of the Control within the visual tree we should try to validate
        /// </summary>
        public string ControlToValidate
        {
            get { return (string)GetValue(ControlToValidateProperty); }
            set { SetValue(ControlToValidateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ControlToValidate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ControlToValidateProperty =
            DependencyProperty.Register("ControlToValidate", typeof(string), typeof(Validator), new PropertyMetadata(null));


        /// <summary>
        /// When overridden in a derrived class, checks for a valid value
        /// in <paramref name="ctv"/>
        /// </summary>
        /// <param name="ctv"></param>
        /// <returns></returns>
        public virtual bool Validate(FrameworkElement ctv)
        {
            return true;
        }

        /// <summary>
        /// Helper method for derived classes
        /// </summary>
        /// <param name="fe"></param>
        /// <returns></returns>
        protected virtual string GetInputValue(FrameworkElement fe)
        {
            string val = string.Empty;
            if (fe is TextBox)
            {
                val = ((TextBox)fe).Text;
            } //else if ComboBox, etc.
            return val;
        }
    }
}

We define three dependency properties for showing content, presumably using a ContentPresenter in the default template for Validator.  ValidContent, InValidContent, and InvalidToolTip.  We leave the Validate function empty, and the work of showing or hiding the correct content happens in CheckValidState.

Default Validator ControlTemplate

We target Validator with this ControlTemplate:

<ControlTemplate TargetType="local:Validator">
    <Canvas x:Name="LayoutRoot">
        <ContentPresenter x:Name="ValidPresenter" Content="{TemplateBinding ValidContent}"></ContentPresenter>
        <ContentPresenter x:Name="InValidPresenter" Content="{TemplateBinding InValidContent}">
            <ToolTipService.ToolTip>
                <ContentPresenter x:Name="InvalidToolTipPresenter" Content="{TemplateBinding InvalidToolTip}"></ContentPresenter>
            </ToolTipService.ToolTip>
        </ContentPresenter>
    </Canvas>
</ControlTemplate>

This should be easy to understand.   Since this is an early experiment I haven't created any Visual States yet.

 

Validator Implementations

Surprisingly, creating classes that extend Validator worked out fairly well.  We can review the main pitfall in the ValidContentProperty Dependency Property registration.  Note the default value inside the PropertyMetadata constructor.  I left this in to make a point - only one instance of this TextBlock will be created, and a single Control cannot appear multiple times in the Visual Tree.  Be sure to provide values in each instance or you will get hard to grok errors.  For the sake of brevity, we can just review the implementation for RegexValidator:

using System.Text.RegularExpressions;
using System.Windows;

namespace DamonPayne.AGContrib.Validation
{
    public class RegexValidator  : Validator
    {
        public RegexValidator()
        {
            DefaultStyleKey = typeof(Validator);
        }

        public string RegularExpression
        {
            get { return (string)GetValue(RegularExpressionProperty); }
            set { SetValue(RegularExpressionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RegularExpression.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RegularExpressionProperty =
            DependencyProperty.Register("RegularExpression", typeof(string), typeof(RegexValidator), new PropertyMetadata(string.Empty));

        public override bool Validate(FrameworkElement ctv)
        {
            if (!string.IsNullOrEmpty(RegularExpression))
            {
                Regex r = new Regex(RegularExpression);
                string text = GetInputValue(ctv);

                return r.IsMatch(text);
            }
            return true;
        }
    }
}

There are a couple of things to note here.  Because this class is simply adding some logic we use DefaultStyleKey = typeof(Validator); in the constructor.  We could conceivably want different Validators to have utterly different visual representations, but in those cases we can create a ControlTemplate for those instances only.  Surprisingly this works just fine.  We've defined a RegularExpression property we can set from XAML and overridden Validate to do the Regex check.

Adding the Validators in XAML

Given all of this, it's easy to add the Validators onto my order form:

<!-- Name Row-->
<TextBlock Grid.Row="1">Name:</TextBlock>
<TextBox x:Name="NameTxt" Grid.Row="1" Grid.Column="1" Height="25" VerticalAlignment="Top"></TextBox>
<v:RequiredFieldValidator x:Name="NameReq" ControlToValidate="NameTxt" Grid.Column="2" IsValid="True">                
</v:RequiredFieldValidator>            

Obviously, the value of ControlToValidate must be a valid Name somewhere in the visual tree.  Because we allow the display of anything for valid vs. invalid Controls, and we can set the initial valid state my order form now looks like this when first loaded, calling out the fact that Wine Bottles needs a real value:

Form1

Firing the Validators

Given the primitive state of the example application so far, a single extension method for type FrameworkElement will cause all of the validators contained within that FrameworkElement to do their thing.  The extension code is worth taking a quick look at.

namespace DamonPayne.AGContrib.Validation
{
    public static class FrameworkElementExtensions
    {
        /// <summary>
        /// Search the control's Visual Tree for Validators, and run them!
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public static bool IsValid(this FrameworkElement c)
        {
            bool valid = true;
            var lv = FindAllValidators(c);

            foreach (var v in lv)
            {
                var ctv = (Control)c.FindName(v.ControlToValidate);
                if (null != ctv)
                {
                    if (!v.Validate(ctv))
                    {
                        valid = false;
                        v.IsValid = false;
                    }
                    else
                    {
                        v.IsValid = true;
                    }
                }
            }

            return valid;
        }

        public static List<Validator> FindAllValidators(FrameworkElement fe)
        {
            return FindAllValidators(fe, new List<Validator>());
        }

        public static List<Validator> FindAllValidators(FrameworkElement fe, List<Validator> lst)
        {
            int count = VisualTreeHelper.GetChildrenCount(fe);
            for (int i = 0; i < count; ++i)
            {
                DependencyObject d = VisualTreeHelper.GetChild(fe, i);
                if (d is Validator)
                {
                    lst.Add((Validator)d);
                }
                if (d is FrameworkElement)
                {
                    FindAllValidators((FrameworkElement)d, lst);
                }
            }
            return lst;
        }

    }    
}

You may note that only certain elements such as Canvas define a "Children" property.  VisualTreeHelper is the only way I know of to walk the Visual Tree in a generic fashion.  This allows us to look for Validators within the target FrameworkElement no matter how nested.  I will revisit my choice of FrameworkElement as the entry point.  With this Extension Method, validating my form when I attempt to submit is easy, "Form" being the name of my top level Grid.

private void Button_Click(object sender, RoutedEventArgs e)
{
    if (Form.IsValid())
    {
        //Do something meaninful
    }
}

So, if we push the Submit button without doing anything, we see this ...

Form2

... but providing valid values ...

Form3

 

Conclusion

So, what do you think?  Is this worth a little more work?  I would probably add some kind of Validation Summary, some Visual States to Validator, potentially support Validation Groups, and add a way to dynamically fire the Validators when a Control loses focus.

Send me a note with your comments.

Source code:

DamonPayne.AGContrib.zip (44.75 KB)


Friday, November 21, 2008 11:03:43 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

http://www.scienceblog.com/cms/red-wine-may-reduce-incidence-alzheimers-17841.html

Red wine, you just keep getting better.  Not only do you cure cancer and prevent cancer, you combat Alzheimer's as well.  Scientists are wasting their time with on this nonsense with stem cells and sequencing genomes and so forth - red wine is the answer to everything.  Now, if you'll excuse me, I'm heading to Thief Wine to get some medicine.

Cheers!



Friday, November 21, 2008 2:50:34 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, November 19, 2008

The talk went very well last night and I got very positive feedback.  Thanks to John from Microsoft for helping with the Projector system, which mysteriously shut down at special pre-programmed times twice during the night; this was a complex topic and the audience was excellent.

Now that I'm done preparing for this, you can expect the next AGT article to happen soon.

Here are the slides and code from the talk:

AGSIGControlTeplatePreso.zip (1.03 MB)


Wednesday, November 19, 2008 12:06:31 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, November 18, 2008

http://www.wi-ineta.org/DesktopDefault.aspx?tabid=192

Hopefully I will see some of you there at 6pm in the Microsoft offices.  I think I'll be visiting the bar at Thunder Bay Grill next door after the show...



Tuesday, November 18, 2008 11:27:16 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, November 14, 2008

This article on Engadget HD today greatly amused me.

Research finds that people still heart physical discs, greatly prefer Blu-ray to streaming

While the "HD streaming rulez!one11!1!" bandwagon was quickly filling up after Netflix announced that it would be bringing such a service to the Xbox 360, the numbers just don't substantiate the claims that physical discs are doomed anytime soon. Sure, for the budding technophile, streaming is just the next great thing, but for the average joe / jane, the tried and true disc still holds a great deal of importance. A recent study by market research firm SmithGeiger found that out of over 2,000 surveyed, "HDTV owners familiar with Blu-ray favor the format over downloading and streaming by a margin of nearly 10-to-1, with about 70% of respondents citing the fact that there's a physical disc to keep as a key factor in their decision to buy Blu-ray." It also found that 96% of BD users were "familiar with downloading and streaming services, but that two-thirds believe watching a movie on Blu-ray is a better overall entertainment experience." Sure, BD has its flaws, but not having to re-rent an HD film after a remarkably short 24-hour window sure is nice, huh?

No kidding.  The cries that physical media was dead reached their highest volume as it became clear that Blu-Ray was going to win over HD-DVD.  The people making these claims were either:

  1. Analysts who must not watch many movies at home (ergo are relatively clueless on these dynamics)
  2. HD-DVD supporters/fanboys trying a little et tu coque to ease the pain of their loss by saying BD's days were numbered as well.
  3. HD-DVD supporters with huge vested interests in media streaming
  4. Random outfits not necessarily associated with HD-DVD, but with huge vested interests in media streaming

Media streaming has such a long way to go it's not even funny.  I am certain that we'll get there, but consider the following facts and anecdotes:

  1. I have yet to see a streaming solution that offers basic DVD features: chapter selection, audio selection, special features, multiple audio streams, multiple subtitle options.  This takes bandwidth and increases technical complexity of this delivery mechanism.
  2. Ditto #1 but for the advanced interactive features of Blu-Ray: online features, PiP, advanced programmability, etc.
  3. Quality.  Last time I checked HD Cable bitrates top out at around 9mb/s for audio plus video.  Right now this is overwhelmingly Dolby Digital and MPEG-2, with some outfits moving to MPEG-4 for better video compression.  While not bad compared to normal TV, this is a far cry from the 40+ mb/s I have on numerous Blu-Ray discs or even the relatively lean 18+ mb/s on say the King Kong HD-DVD encode.  Some people will not be able to tell the difference, and some won't care, but a lot of people do.  If you have a sound system you care.  The larger the TV you have, the more you'll be able to notice the flaws.
  4. Delivery.  While some people have the option of 10mb/s U-Verse, 18mb/s cable, or 150mb/s fiber in their home, that is still a relatively small # of people and not increasing very fast.  A lot more folks are still slumming it with 3mb/s or 5mb/s cable because they either can't get or won't pay for the faster options.  Where I live, 1.5mb/s DSL is my best option.  I could literally get a Blu-Ray from Amazon.com with 2-day shipping before I could download even half of a high quality movie.

The convenience of just instantly grabbing a movie over the PSN or Netflix, or even queuing it up while I make dinner has appeal.  I will not, however, pay money to watch something of less than DVD quality in my home theater on my 106" screen.  Those of you who will, enjoy your mediocre entertainment! 



Friday, November 14, 2008 4:19:51 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Monday, November 10, 2008

I will be speaking next week at the Silverlight Special Interest Group, a subset of the Wisconsin .NET user's group.  The topic is Control Templating, Styling, and the Visual State Manager.  For more details check out:

http://www.wi-ineta.org/DesktopDefault.aspx?tabid=192

 

 



Monday, November 10, 2008 10:46:45 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback