Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF DataForm - Defaults the Ground Floor

0.00/5 (No votes)
26 Jul 2013 1  
Getting started with customizing the WPF DataForm.

Overview

This is a follow on article to WPF DataForm covering at least some of the ways Defaults can be used to customize the form.

WPF DataForm was designed with a good deal of customization. The basis of that customization starts with Defaults. In a nutshell, many of the classes when instantiated need some initial values. Rather than code those values into each of the classes where needed or using a set of constants, the defaults were centralized and accessed by name via an Interface. The result is that the implementation of Defaults can be replaced, it allows both temporal customization of values and extension of values for new controls, and provides set based access to values.

Default Categories

  • Form - This contains defaults for the overall form. The default keys are:
    • TitleHeight - The height of a form title.
    • HeadingHeight - The height of an internal form heading.
    • LineHeight - The height of a form line which is the part that holds the controls.
    • LineSpacing - The height of the space between controls.
    • TitleStyle - The style that can be applied to a title.
    • SeparatorBrush - The brush used to fill in the separator in a CSV layout form.
    • SeparatorThickness - The thickness of the separator in a CSV layout form. Use 0 to not draw a line or a number > 0 to draw a line of that thickness.
    • SeparatorHeight - The overall height of a separator. The separator line if drawn will be vertically centered.
    • MakeForm - A function to create an IMakeForm object. Customize this to change what class is used to create form layout. For example you might create a class that puts prompts under the controls instead of to the left.
    • MakeColumns - A function to create an IMakeColumn object. Customize this to change how columns are selected.
    • MakeHeaders - A function to create an IMakeHeader object. Customize this to change what a form header is. Form headers are the prompts that appear in front of a control.
  • Command - This contains the defaults used for Action, Command, or Router Stack panels. i.e. The buttons on a form. The default keys are:
    • ButtonMaker - The class used to create a button. Note in the next version there will be a breaking change that will change this to a function that creates the ButtonMaker. This will only affect applications that over-ride ButtonMaker.
    • Width - The width of a button.
    • Height - The height of a button.
    • Spacing - The space between buttons.
    • Thickness - The thickness of the button border. Set this to 0 to remove the button border.
    • Orientation - The initial orientation of the button stack panel. The default is Horizontal.
  • Column - This contains the defaults used for the column control factories and by extension the created controls. For example the Boolean defaults control the initial properties of a check box control. Each control may have special defaults, but the common keys are:
    • StyleName - The style applied to the control when created.
    • Height - The height of the control.
    • Width - The width of the control.
    • SuppressHeading - If true headings are not displayed.
    • Stretch - If true the control fills available space.
    • IsRequired - If true the control is required.
    • IsReadonly - If true the control is read-only.
    • MaxLength - The maximum # of characters that may be entered.
    • TextAlignment - The alignment of the text.
  • Factories - In the next version, these defaults will control how a factory is created. These can be changed to define what control factory is used, say to replace a check box with a toggle button, as well as providing another way to control defaults for the control factories.

Library Defaults

You may find that you would always like Defaults different than the ones I've chosen or you may find that you would like additional defaults set either on existing classes or on custom classes that you write. If so, the best place to customize would be the actual values in Controls\Defaults\MemoryDefaults.vb. The advantage of customizing this file is that you don't have to do anything special to use it. You set values, compile, and when you run your app, it just uses the values you set. For example you could change Form.TitleStyle to be the name of your style. Form.SeparatorThickness could be set to 0 to make it a spacer without a line. Form.MakeForm could be switched to use MakeCsvForm, provide additional initial values, or use your custom layout class. Also because all defaults come from this file, merging your customization with the next version of WPF DataForm will be far easier than updating values scattered accross many classes. My recommendation if you choose to customize this, is to put your customizations at the bottom of the New method. Using the previous examples you might have the following:

_item("Form.TitleStyle") = "H1"
_item("Form.SeparatorThickness") = 0.0#
_item("Form.MakeForm") = Function() New Form.MakeCsvForm
_item("Form.NewDefault") = "Bright"

If you follow this methodology, you can simply paste your customizations into the next version with little to no impact. The only thing you'll need to worry about is whether I've changed the name of a particular default.

Application Defaults

Like me, you might prefer not to change the base library, but still find that you would prefer to use Defaults different than the ones I've chosen. Or you might simply want a particular application to use different Defaults. In that case, you can either put the values in Application_Startup or create a method you call from Application_Startup. I prefer the second because I find it a bit easier to move settings between applications. It would look something like this:

Module mCurionDefaults
 Sub SetDefaults()
  Curion.WPF.Controls.mGlobal.Defaults("Form.TitleStyle") = "H1"
  Curion.WPF.Controls.mGlobal.Defaults("Form.SeparatorThickness") = 0.0#
  Curion.WPF.Controls.mGlobal.Defaults("Form.MakeForm") = Function()
    Dim mf = New Curion.WPF.Controls.Form.MakeCsvForm
    mf.AddLines(My.Resources.csvLayout)
    Return mf
   End Function
  Curion.WPF.Controls.mGlobal.Defaults("Form.NewDefault") = "Bright"
 End Sub
End Module
To call this, from Project Properties on the Application tab, click View Application Events and add the following method:
Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
  Call mCurionDefaults.SetDefaults()
End Sub

In the next release you can simplify the Form.MakeForm to the single line function:

Curion.WPF.Controls.mGlobal.Defaults("Form.MakeForm") = _
   Function() New Curion.WPF.Controls.Form.MakeCsvForm With {.LayoutText = My.Resources.csvLayout}

You might be asking what Form.NewDefault will do. The answer is nothing unless you use it. There are several ways in which you might use Form.NewDefault but that will be covered in a later Article.

Setting your defaults by creating static methods facilitates their contextual re-use. Which of course leads us right into Form Defaults. :)

Form Defaults

You may find that you would like specialized Defaults on a single form or group of forms. About this time your probably thinking argh! Set all those defaults and then set them back? Luckily Defaults are implemented as an interface. As such we can simply replace Defaults, do some stuff and then set it back. Still a pain to do manually, so good news, there's a class for that! (ContextDefaults) If you followed with me through the last section and stuck your Default customization in a method, you can do the following:

Sub New()
  With New Curion.WPF.Controls.ContextDefaults
   Call mCurionDefaults.SetDefaults()
   InitializeComponent()
  End With
End Sub

What this does, is quietly swap the implementation of Defaults which starts out as a MemoryContext with ContextDefaults and then sets it back to the MemoryContext when the use block is exited for any reason. How convenient :) Note that it's important to set your defaults before you initialize components otherwise you'll set the values after they've already been used.

This sort of points out that if you are going to use methods to set your defaults, you should choose to give them slightly better names than SetDefaults.

MemoryDefaults

MemoryDefaults is the default implementation of Defaults. It simply manages a case insensitive string dictionary of objects. It's designed to be fairly easy to use. To add or update a value, simply set the value. mc.Item("key") = value. To remove an item, set its key to Nothing: mc.Item("key") = Nothing. The Item property is the default so the previous two statements could simply be: mc("key") = value and mc("key") = Nothing.

ContextDefaults

ContextDefaults is a disposable class that is designed to swap itself with the current implementation of Default when created and swap the original back when disposed. The current implementation copies all the keys and values from the current default. The next implementation will just capture changes and use the existing implementation to get unchanged values, but it's use will remain unchanged. A Using Block is particularly handy to swap it in and out. Previously Form Defaults showed an example of this.

Implementing Custom Defaults

At some point you may find a need for a Default implementation other than MemoryDefaults or ContextDefaults. Creating the shell is particularly easy, simply add a new class and Implement IDefaults. It contains three routines:

  1. Default Property Item(key As String) As Object - This property gets and sets items. By convention if a key is not found, Nothing should be returned. If Nothing is set on a key it should be removed. And finally, when a key is set to a value it should be updated or added as appropriate.
  2. ReadOnly Property Keys As IEnumerable(Of String) - This property returns a collection of all defined keys. This can be used with Linq to select sub-sets of properties.
  3. Function Contains(key As String) As Boolean - This property simply indicates if the key is currently defined.

Some examples of other implementations might include one that allowed any default to be defined as either a static value or the result of a function and would return the current value when requested, or either a global or user specific persisted Defaults in a file, database, or web service.

LINQing Defaults

From time to time you may want to retrieve or update a set of Defaults. For example, perhaps you would like to set the default height for all controls to 23:

Dim q = From d In Curion.WPF.Controls.mGlobal.Defaults.Keys Where d.EndsWith(".Height") Select d
  For Each k In q
  Curion.WPF.Controls.mGlobal.Defaults(k) = 23.0#
Next

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here