Click here to Skip to main content
14,984,032 members
Articles / Desktop Programming / Windows Forms
Article
Posted 3 Mar 2017

Stats

8.6K views
230 downloads
6 bookmarked

DesignSurfaceExtended the third (chapter), how to implement a FileSystem Persistence Manager

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
3 Mar 2017CPOL4 min read
Save and restore your design work using a persistence manager based on XAML code

Image 1

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:

Image 2

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.

Image 3

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;

            //- check if some  properties is about to be serialized as {x:Null}
            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;
            }
        }//end_if
    }//end_foreach
}</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;
                    }

                    //- re-create the DesignSurfaces with controls inside
                    //-
                    //- Location and Size must be accessed via Control.Location and Control.Size
                    //- because the related  Control are dummy properties not sync to Form properties
                    //- Size is used during DesignSurface creation
                    //- while Location is used to modify the rootcomponent inside the DesignSurface
                    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;
                    //-
                    //-
                    //- this code doesn't reflect the location of the rootcomponent
                    //rootComponent.Location = (fx as IPCCommonProperties).P1ControlLocation;
                    //- instead we have to modify the Location via Reflection
                    Point newLoccation = (fx as Control).Location;
                    PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties( rootComponent );
                    //- Sets a PropertyDescriptor to the specific property
                    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;
                    //-
                    //- 
                    //- now deploy the control on the DesignSurface 
                    DeployControlsOnDesignSurface( surface, fx );
                }//end_using

            }//end_try
            catch( Exception ex ) {
                Debug.WriteLine( ex.Message );
                throw;
            }
        }//end_foreach
    }
    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 ) {
    //- steps:
    //- 1. deploy every control on DesignSurface
    //- 2. reparse the control setting the Parent Property

    //- step.1
    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 ) );
        //- pay attention to this fact:
        //- ctrlCreated.Site.Name is set by INameCreationService
        //- and may be different from the name of control we are cloning
        //- so set it accordingly, 
        //- because it will be used later to match the two controls
        ctrlCreated.Name = cc.Name;
        ctrlCreated.Site.Name = cc.Name;
        //- fill in the property of the 'designed' control using the properties of the 'live' control via Reflection
        string[] allowedProperties = { "Text", "BackColor" };
        Fill( ctrlCreated, cc, allowedProperties );
    }

    //-
    //-
    //- step.2
    //- iterate the components in a linear fashion (not hierarchically)
    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 the parent is the rootcomponent (i.e. the Form)
                //- then do nothing, because the control is already deployed inside the Form
                if( c.Parent is Form || c.Parent.GetType().IsSubclassOf( typeof( Form ) ) )
                    break;

                //- the Parent is another control (different from the form)
                Control ctrParent = GetControlInsideDesignSurface( surface, c.Parent.Name );
                (comp as Control).Parent = ctrParent;
                break;
            }
    }//end_foreach
}

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paolo Foti
Software Developer (Senior)
Italy Italy
I started coding in 1984 on a ZX Spectrum. From the Sinclair Basic I jumped straight to C++ (which I use since 1992). I wrote code in assembly 386, ANSI C++ (I love it) and VisualBasic 6.0 (urgh!!!); nowadays I write code in C# 4.0!

I was born in 1968 in Italy and I live in the north west of my country (near Venice). I work as a Project Leader/Senior Developer.

Computer Science interests: I like coding 3D-GameEngine and OpenGL applications.

Sport and hobbies: Martial Arts, Latino Dance, travels, reading and writing novels.

Comments and Discussions

 
QuestionKeyboard Service? Pin
nike1989zhuhai19-May-18 1:03
Membernike1989zhuhai19-May-18 1:03 
PraiseInformative article Pin
delamaine20-Feb-18 16:15
Memberdelamaine20-Feb-18 16:15 
QuestionWhat is the status of the TabControl issue? Pin
Jim Acer12-Aug-17 2:51
MemberJim Acer12-Aug-17 2:51 
BugBrilliant! .... But ... Pin
aymenbnr5-May-17 4:35
Memberaymenbnr5-May-17 4:35 
GeneralRe: Brilliant! .... But ... Pin
MomciloM25-May-17 4:00
MemberMomciloM25-May-17 4:00 
GeneralMy vote of 5 Pin
MomciloM14-Mar-17 2:04
MemberMomciloM14-Mar-17 2:04 
BugChildren inside TabContol tabs are not being saved. Pin
MomciloM13-Mar-17 1:24
MemberMomciloM13-Mar-17 1:24 
GeneralRe: Children inside TabContol tabs are not being saved. Pin
Paolo Foti17-Mar-17 3:18
MemberPaolo Foti17-Mar-17 3:18 
GeneralRe: Children inside TabContol tabs are not being saved. Pin
MomciloM20-Mar-17 12:42
MemberMomciloM20-Mar-17 12:42 
GeneralRe: Children inside TabContol tabs are not being saved. Pin
MomciloM27-Mar-17 5:16
MemberMomciloM27-Mar-17 5:16 
GeneralRe: Children inside TabContol tabs are not being saved. Pin
aymenbnr5-May-17 4:37
Memberaymenbnr5-May-17 4:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.