Introduction
In programming, factories are classes or methods used to create instances of other objects. This is useful when the method calling the factory does not know which is the best object to do what it's needed.
For example, when I hand place a control to edit strings, I use a TextBox
. When I hand place a control to edit booleans, I use a checkbox
. But, if I do it dynamically, like getting all properties of an object to create the controls, how do I choose the best control to show and edit the value? The answer is simple, I use a factory.
A factory can be as simple as:
public static Control CreateControl(Type dataType)
{
if (dataType == typeof(string))
return new TextBox();
if (dataType == typeof(bool))
return new CheckBox();
return null;
}
In this case, if I need to support a new data-type, I will add new if
s to the CreateControl
method. This can work for a program, but will not work if the factory is in a library called by a program with custom data-types. In this case, the program will need some way to register its editors. Using a dictionary will work but, again, it will not be complete. I could create a control that's able to edit any sub-class of a given class and, so, using a common dictionary will not work. Ok, I have many options here. I could create events, create a specialized dictionary capable of finding base types or even a combination of these. In fact, my solution is the TypeDictionary
I created, capable of finding the best control given its data-type, finding its base types and even finding controls for generic types. But, to be honest, this is not the real purpose of the article, so I will move forward.
Advantages
Standardization: If the factory is used everywhere, it is guaranteed that a given data-type will always use the right editor.
Future updates: If, in the future, a new control for a given type is created, a single change in the registered controls will be enough to update the entire system.
Dynamic editors: If you simply want to create a control capable of editing all the properties of any given object, it is really easy to do so when you already have the factory. You simply create a control for each one of the properties in the object.
Maybe there are more advantages, but those three, for me, are enough.
But, there is always something that could be lost, so, let's see the disadvantages.
Disadvantages
Lack of customization: When a factory is used, there is always some lack of customization, as the most basic control does not have all the properties of the instantiated controls.
Unalignment: When you first put a control in a window, you know its real size. You can set it to use the most appropriate size needed. But, if in the future a new control, bigger or smaller, is registered, everything can mess up.
So, how do we solve the disadvantages?
The alignment problem is automatically solved by WPF. To be honest, that's why I use WPF. The fact it allow styles and templates is an advantage, but it's not my main goal.
And for the lack of customization, I propose a weak-enforcement and Data-type wrappers.
Weak-enforcement: If a window needs special control placement, special properties set or special events captured, then don't use a factory. The factory is better suited for general purpose content, where it's better to have a fast way to show the content than to have the best way.
Data-type wrappers: In general, people are stuck with basic data-types, many times doing things like this when some validation is needed:
[Range(0, 100)]
public int PercentualValue { get; set; }
[EmailValidation]
public string Email { get; set; }
or, even worse, simple:
public int PercentualValue { get; set; }
public string Email { get; set; }
And then the validation is done on the form that presents the data. But, if instead, a custom type is used, we can have:
public Range0_100 PercentualValue { get; set; }
public EmailString Email { get; set; }
And, not only the Range0_100
and EmailString
can already do their validations in the constructor, but a factory can use a better suited control for editing Range0_100
and EmailString
than the controls used to edit int
s and string
s in general.
After some time working with custom data-types for validations and specialized editors, I think everyone will love them. But, even this has some problems. ORMs in general don't support custom data-types. Regarding this, I can only say that my own ORM framework was, in fact, the base of this article. In future, I will re-publish my Database framework, which will use this WpfControlFactory
as its heart for Desktop controls.
Attached is a sample that uses the Pfz.WpfControls
, which uses the factory model, and a very simple program, without any styles or visual appeal, that simply shows some records, and even has a specialized control for YesNo values, showing a radio-group instead of a combobox
.
The TypeDictionary
I already explained some advantages and disadvantages of factories and presented some possible solutions to the disadvantages. Now, I will explain a little about my factory implementation.
My factory is based in the TypeDictionary generic class. This class is, in fact, composed of 6 dictionaries.
- One to register values (factories) that only work on exact matches;
- One to register values for a class-type or any sub-type;
- One to register values for an interface-type or any class that implements such interface;
- And the same three dictionaries for generic-type definitions (like List<>).
As the type of the value is generic, such type-dictionary can be used for anything that requires some "class hierarchy" identification but, of course, I use it for factories.
The real important method is: TryFindUp
.
The method is a little hard to read, but I will try to explain the steps:
- First, we try an exact match.
- So, if the type is a generic type definition, we look in the ExactGeneric dictionary. If the type is not a generic type definition, we look into the Exact dictionary.
- Obviously, if a match is found we return it. But, if not, we continue.
- If the type is an interface, we try to find a value in the interfaces dictionary.
- Then, if the type is a class, we try to find a value for InheritableTypes, for the actual type and for each one of it's base types.
Finally, we don't care if the given type was a class or an interface. We extract all the interfaces, try to order them (so, if a class has interface C and D, one that inherits from A and one that inherits from B, we will see C and D first [they will not have a guaranteed order] and A and B after).
- And, again, we will try to find a value for each one of the interfaces.
- We will only return that no value is found after finishing those steps unsuccessfully.
That's it
I hope this article helps anyone wanting to create a dynamic or general purpose Windows/object editors.
History
- 16th April, 2010: Initial version
- 20th April, 2010: More detailed article and using routed-events properly