History
I recently presented the Pfz.Databasing framework. It was not very successful in its first presentation, but I think this new framework, based on it, will show how useful it is. This framework is able to work with serialized files, so making test projects is easier, and there are some improvements, but that's not the real thing to show.
Introduction
I already said that on the other article, but I must say it again: My idea is to create database interfaces and have everything created for me. If possible, the database, the forms, and even webforms must be done without coding. Well, the Pfz.Databasing does the part of creating the database and making searches and updates very easy. Pfz.Databasing.Controls tries to make creating the Windows Forms very easy.
How?
Well, the principle is simple, to create the database and to create the databound controls. Properties are discovered. The right DataTypeConverter is found to convert values to and from the database and the right Editor is found to display and edit the values.
But what happens with custom created types? Well, both the DataTypeConverters and the Editors are registered. So, if you create a new custom data type, you can create the new custom data type converter to read and write it to the database, and the right editor to show and edit the values. At first, this could look like, "Wow! That's too much work for so little", but after some time, the idea will sound wonderful.
Let's compare. One of the great advantages of Delphi is that you can create a basic database form very easily. You drag and drop the fields, and a basic form is created for you.
Actually, the framework does not have "drag and drop" possibility for all fields, but you can drop a control that represents a full record on the form, set the record type, and all the properties will be shown. Or, you can drag and drop a "PropertyBoundControl
", set the record type and property, and the right thing will be shown.
So, what is really new about thi? It is dynamic. Instead of putting a TextBox
on the form and having it as a TextBox
forever, you put a control that is bound to MyRecord.MyProperty
, which is an int
. If I create a new editor for int
, and don't need to reedit the form to put the new editor, the right one will be used. See how useful this can be in a giant system? Also, if you change the data type of the field to something more specific, but which is still compatible with the database, the editor for such a type will be shown, without the need to find where such properties are referenced. Now it looks better, doesn't it? And, to finish, if you create some type of "skin", create more than a control to show data, and allow the user to select which one to use, you only need to register the right control, and all the system will work with the new control for the user.
The controls
PropertyBoundControl
- The only "database control" you need. Shows exactly one property (or database field, if you prefer). They also know how to show their own DisplayNames, if allowed.
RecordBoundControl
- The control that shows an entire record, using many PropertyBoundControl
s internally. It also allows you to set what "columns" you would like to show and their minimum widths, allowing many of them to show in a single line. Makes creating simple layouts really easy.
RecordBoundGrid
- The control capable of showing and editing many records at once. It creates many lines of PropertyBoundControl
s and also works in a CachedUpdates
manner.
Also, to help create forms really fast, there is the FormEditRecord
, with edits one record at a time, and FormEditRecords
, which already has a grid and the buttons to add/delete records and apply/revert changes. You only need to set the record or records, and it is all working.
That's the easy part. The framework already includes controls for editing string, numbers, enums (showed as combobox), date/time with calendars, and boolean with checkboxes (of three states, if it is nullable), so creating basic forms is only a question of using these types. But, for the special types, there is a more complex part.
IPropertyBoundControlGenerator, PropertyBoundControlGenerator, and IPropertyBoundControl
To have your own editor working, you need to create a control generator and register in the PropertyBoundControlGenerator
static class. Also, it is obvious that the ControlGenerator
will create a control, which must be a Control
and also IPropertyBoundControl
.
What does the IPropertyBoundControlGenerator
really do? It must tell which type of properties it is capable of generating controls for, tell if it is capable of generating an editor for sub-classes of these types (if they can be inherited, of course) and, finally, for creating the control.
For example:
using System;
using System.Collections.ObjectModel;
using System.Reflection;
namespace Pfz.Databasing.Controls.Generators
{
internal sealed class StringControlGenerator:
IPropertyBoundControlGenerator
{
private static readonly ReadOnlyCollection<Type> fForTypes = new ReadOnlyCollection<Type>
(
new Type[]
{
typeof(string)
}
);
public ReadOnlyCollection<Type> ForTypes
{
get
{
return fForTypes;
}
}
public bool CanGenerateForSubTypes
{
get
{
return false;
}
}
public IPropertyBoundControl Generate(Type recordType,
PropertyInfo propertyInfo, string displayName)
{
IPropertyBoundControl result = new TextBoxBoundControl(propertyInfo);
if (displayName != null)
return new LabellerControl(result, displayName);
return result;
}
}
}
The preceding class generates the editor for strings only. Do not generate an editor for its sub-classes (as they don't exist). Also, during the generation of the control, as the control itself does not display its DisplayName, a LabellerControl
is created for it, which will add the DisplayName at the top/left of it. The generator is easy, isn't?
Well, now let's understand the control itself:
using System.Reflection;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
namespace Pfz.Databasing.Controls.Generators
{
internal sealed class TextBoxBoundControl:
TextBox,
IPropertyBoundControl
{
internal TextBoxBoundControl(PropertyInfo propertyInfo)
{
RecordProperty = propertyInfo;
ReadOnly = true;
}
public PropertyInfo RecordProperty { get; private set; }
private IRecord fRecord;
public IRecord Record
{
get
{
return fRecord;
}
set
{
fRecord = value;
if (value == null)
{
ReadOnly = true;
return;
}
ReadRecord();
ReadOnly = value.GetRecordMode() == RecordMode.ReadOnly;
}
}
public void ReadRecord()
{
Text = (string)RecordProperty.GetValue(Record, null);
}
public void WriteRecord()
{
string text = Text;
if (text == "")
text = null;
RecordProperty.SetValue(Record, text, null);
}
string IPropertyBoundControl.DisplayName
{
get
{
return null;
}
}
protected override void OnValidating(CancelEventArgs e)
{
if (!ReadOnly)
WriteRecord();
base.OnValidating(e);
}
}
}
The control inherits from TextBox
, as the TextBox
is already capable of editing strings. The RecordProperty
is stored, as it is part of the interface, and the control begins as readonly, as when it is created and not bound, it must be read-only. Getting the record is simple, but setting it must also update the ReadOnly and call the ReadRecord
, to refresh the value being displayed. The ReadRecord
and WriteRecord
, well, set the text to the value of the property, and set the value of the property to the text, only changing the empty strings to null, as empty strings represent null. The DisplayName is get/set by the LabellerControl
, so we can throw an exception or return null
, we don't really need to implement it. And, to update the record as soon as the focus is gone, we implement OnValidating
to call WriteRecord
.
And that's almost all. The control is ready to be used. We only need one more thing: register the ControlGenerator
. To do this, we call:
PropertyBoundControlGenerator.Add(new StringControlGenerator());
But, of course, this editor is already there, so you don't need to do this.
The future
Of course, I plan to create more controls, correct bugs, and specially make the grid faster, as today, it always recreates all controls when applying records, but my next step is to create a web version of the framework, so the interfaces and the business objects can be reutilized, and the creation of the forms can be as simple on the web as it is on the desktop. I even plan to create a form editor and a "form reader" for the web and the desktop, so you can create just one form that runs in both without any changes, but that's for the future.
I hope this framework is useful to you as it is to me.