Abstract
The Article shows how to implement a persistence manager of a designed form.
Background
It is necessary to be familiar with C# programming. It's useful to know concepts used by the .NET Design services and to read my previously codeproject articles: DesignSurfaceExt and DesignSurfaceManagerExt
Introduction
First of all a very short introduction about what the hell IS this damned "DesignTime" ... I know, it's a bit strange that I write about this now ... at the third chapter of the series! :O But I've received more than one emails asking me to explain what exactly it is ... and, ok here we are.
Look at the picture:
During RunTime your "compiled source code" is alive; let’s say that when you, or anyone in your stead, do execute the program from the CLI (Command Line Interface) or from the GUI (Graphical User Interface), that event really brings to life the program, because, from now on, the program is able to read / to write / to handle events and whatever it is instructed to do.
During CompileTime it is transformed from source code to binary code therefore, in some sense, we could say that it doesn’t exist.
During LatencyTime it’s really dead because it does nothing but wait to be executed.
Good… and during DesignTime? Well, the code is not alive yet because it is "under construction”, nor it is really dead, because it is able to respond to some sort of events: the "design events”, therefore we could say that it is half way alive (or half way dead, if you prefer).
Persistence into FileSystem with XAML
With the introduction of the WPF inside the .NET 3.0, the framework exposes the possibility to persistence the designed controls using an XML dialects: XAML. Two classes are very useful to manage XAML: XAMLReader and XAMLWriter.
Now some of you are about to complain: "Hey, Paolo. we have some good news for you: out there there is .NET 4.6! Wow! So, what are you talking about? .NET 3 is old , too old!"
I know, I know ... the beginning of this article has been written so many years ago, the original code was written using VisualStudio 2010! :( ...and only today I found the time to finish it (nowadays there is Visualstudio2017 RC, but for the article I used the 2013 version)! Do you believe? Years passed! Gosh! :\ So Don't panic: the article describes a persistence Manager based on .NET 4.5 Framework.
The controls will be serialized and deserialized using XAML file.
The story
Well, let's say you passed the whole afternoon to design a beautiful form with lots of coloured controls on its surface; now it's dinner-time and you decide to stop to work on your form. You want to continue to work tomorrow and... wait a minute! Suddenly you realize that you missing something that can save your work already done and restore it in a future time! What you basically need is a mechanism that can save your work into a persistence storage (like the File System, a DataBase, a Remote Repository accessible via web and so on) and that can restore your work from the persistence storage itself.
The Persistence Manager under the hood
The code is developed using my previous pico Form designer as codebase, adding it the capability to persist the design work made by the (designer)user. Inside the posted VisualStudio solution you will find also the code about the DesignSurfaceExt
, DesignSurfaceManagerExt
and p(ico)Designer UserControl with the accompanying demo code.
As usual my recommendation is: feel free to use the code and to modify it as you like for building your own form designer (and let me know if you appreciate my work ;) ).
For those of you that are in hurry, the core are this two snippets of code:
public void Serialize( string storageFolderFullPath ) {
foreach( IDesignSurfaceExt surf in this.DesignSurfaceManager.DesignSurfaces ) {
IDesignerHost idh = (IDesignerHost) surf.GetIDesignerHost();
string xmlfileName = idh.RootComponent.Site.Name;
xmlfileName = Path.ChangeExtension( xmlfileName, this.ExtensionOfSerializedFile );
string sFullPathFileName = Path.Combine( storageFolderFullPath, xmlfileName );
if( idh != null && idh.RootComponent != null ) {
IComponent root = idh.RootComponent;
List<string> propertiesSetToNull = new List<string>();
Control[] ctrlArray = GetChildControls( root as Form );
foreach( Control c in ctrlArray ) {
propertiesSetToNull.Clear();
if( false == IsProperlySet( c, ref propertiesSetToNull ) )
SetToBlank( c, propertiesSetToNull );
}
try {
using( TextWriter writer = File.CreateText( sFullPathFileName ) ) {
XamlServices.Save( writer, root );
}
}
catch( Exception ex ) {
throw;
}
}
}
}</string>
You see, I use this one:
using( TextWriter writer = File.CreateText( sFullPathFileName ) ) {
XamlServices.Save( writer, root );
}
to create the xaml file!
To deserialize from the XAML file:
public void Deserialize( string storageFolderFullPath ) {
string lastFileUsed = string.Empty;
try {
DirectoryInfo di = new DirectoryInfo( storageFolderFullPath );
IEnumerable<FileInfo> linqQuery = from f in di.GetFiles()
where f.Name.EndsWith( this.ExtensionOfSerializedFile )
select f;
foreach( FileInfo fi in linqQuery ) {
lastFileUsed = fi.FullName;
try {
using( TextReader reader = File.OpenText( lastFileUsed ) ) {
Object obj = XamlServices.Load( reader );
if( !(obj is Form) )
return;
Form fx = obj as Form;
foreach( Control c in fx.Controls ) {
c.Enabled = true;
c.Visible = true;
}
DesignSurfaceExt2 surface = (this as IpDesigner).AddDesignSurface<Form>(fx.Size.Width, fx.Size.Height, AlignmentModeEnum.SnapLines, new Size( 1, 1 ));
Form rootComponent = surface.GetIDesignerHost().RootComponent as Form;
(rootComponent as Control).Name = fx.Name;
Point newLoccation = (fx as Control).Location;
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties( rootComponent );
PropertyDescriptor pdS = pdc.Find( "Location", false );
if( null != pdS )
pdS.SetValue( rootComponent, newLoccation );
(rootComponent as Control).Enabled = true;
(rootComponent as Control).Visible = true;
(rootComponent as Control).BackColor = fx.BackColor;
(rootComponent as Control).Text = fx.Text;
DeployControlsOnDesignSurface( surface, fx );
}
}
catch( Exception ex ) {
Debug.WriteLine( ex.Message );
throw;
}
}
}
catch( Exception ex ) {
throw new Exception( _Name_ + "::Deserialize() - Exception on deserializing file: " + lastFileUsed + "(see Inner Exception)", ex );
}
}
Here I call:
using( TextReader reader = File.OpenText( lastFileUsed ) ) {
Object obj = XamlServices.Load( reader );
if( !(obj is Form) )
return;
Form fx = obj as Form;
...
}
to read from the XAML file and get my form. Yep! XAML has done it's dirty work and exits from the scene.
The remaining work is done by the method DeployControlsOnDesignSurface()
private void DeployControlsOnDesignSurface( DesignSurfaceExt2 surface, Form fx ) {
Control[] ctrlArray = GetChildControls( (fx as Control) );
foreach( Control cc in ctrlArray ) {
Control ctrlCreated = surface.CreateControl( cc.GetType(), cc.Size, cc.Location );
Debug.Assert( !string.IsNullOrEmpty( cc.Name ) );
ctrlCreated.Name = cc.Name;
ctrlCreated.Site.Name = cc.Name;
string[] allowedProperties = { "Text", "BackColor" };
Fill( ctrlCreated, cc, allowedProperties );
}
foreach( IComponent comp in surface.GetIDesignerHost().Container.Components ) {
if( comp is Form || comp.GetType().IsSubclassOf( typeof( Form ) ) )
continue;
string componentName = comp.Site.Name;
foreach( Control c in ctrlArray )
if( c.Name == componentName ) {
if( c.Parent is Form || c.Parent.GetType().IsSubclassOf( typeof( Form ) ) )
break;
Control ctrParent = GetControlInsideDesignSurface( surface, c.Parent.Name );
(comp as Control).Parent = ctrParent;
break;
}
}
}
It merely cycle through every control hosted by the Form and, in turn, call the Fill()
method which copy by Reflection some useful Properties: from the control "at RunTime" to the control at "DesignTime".
Using the code
Please refer to project "DemoConsoleForpDesigner" to see a "Form Designer", based on pDesigner, with a persistence layer in action. The code inside is heavily commented.
That's all folks!
Bye! (^_^)
History
04 March 2017 - First submission of the article/code.