65.9K
CodeProject 正在变化。 阅读更多。
Home

DesignSurfaceExtended 第三章:如何实现文件系统持久化管理器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2017年3月3日

CPOL

4分钟阅读

viewsIcon

16619

downloadIcon

377

使用基于 XAML 代码的持久化管理器保存和恢复您的设计工作。

摘要

本文展示了如何实现一个设计窗体的持久化管理器。

背景

需要熟悉 C# 编程。了解 .NET 设计服务使用的概念将很有帮助,并且建议阅读我之前的 codeproject 文章:DesignSurfaceExtDesignSurfaceManagerExt

引言

首先,非常简短地介绍一下 **“DesignTime”到底是什么**……我知道,我现在才写这个有点奇怪……已经是系列文章的第三章了!:O 但我收到了不止一封邮件,询问我到底是什么……好吧,我们现在就来解释。

看图

在 **运行时 (RunTime)**,你的“编译后的源代码”是活着的;也就是说,当你,或者任何代表你的人,从 CLI(命令行界面)或 GUI(图形用户界面)执行程序时,这个事件真正赋予了程序生命,因为从现在开始,程序能够读取/写入/处理事件以及任何它被指示要做的事情。

在 **编译时 (CompileTime)**,它被从源代码转换为二进制代码,因此,在某种意义上,我们可以说它不存在。

在 **等待时 (LatencyTime)**,它真的死了,因为它什么都不做,只是等待被执行。

好的……那么在 **设计时 (DesignTime)** 呢?嗯,代码还没有活过来,因为它“还在构建中”,也没有真正死去,因为它能够响应某些类型的事件:“设计事件”,因此我们可以说它半死不活(或者如果你愿意,半死不活)。

使用 XAML 进行文件系统持久化

随着 WPF 在 .NET 3.0 中的引入,该框架公开了使用 XML 方言(XAML)持久化设计控件的可能性。有两个类对于管理 XAML 非常有用:XAMLReader 和 XAMLWriter。

现在有些人要抱怨了:“嘿,Paolo。我们有好消息告诉你:外面有 .NET 4.6 了!哇!你还在说什么?.NET 3 太老了,太老了!”

我知道,我知道……这篇文章的开头写于很多年前,最初的代码是使用 VisualStudio 2010 编写的!:(……今天我才找到时间完成它(现在已经有 Visualstudio2017 RC 了,但文章中我使用了 2013 版本)!你相信吗?几年过去了!天哪!:\ 所以别担心:文章描述了一个基于 .NET 4.5 Framework 的持久化管理器。

控件将使用 XAML 文件进行序列化和反序列化。

故事

好吧,假设你花了一个下午的时间在一个漂亮的窗体上设计了很多彩色控件;现在是晚餐时间了,你决定停止在你的窗体上工作。你想明天继续工作,等等!突然你意识到缺少一些东西,能够保存你已经完成的工作并在将来恢复它!你基本上需要一个机制,可以将你的工作保存到持久化存储(如文件系统、数据库、可通过 Web 访问的远程存储库等),并且能够从持久化存储本身恢复你的工作。

持久化管理器幕后

代码是使用我之前的 pico Form Designer 作为代码库开发的,为其增加了持久化用户(设计器)所做的设计工作的能力。在发布的 VisualStudio 解决方案中,您还可以找到有关 DesignSurfaceExtDesignSurfaceManagerExt 和 p(ico)Designer UserControl 的代码以及配套的演示代码。

一如既往,我的建议是:请随意使用代码并根据您的喜好修改它来构建您自己的窗体设计器(如果您欣赏我的工作,请告诉我 ;))。

对于那些赶时间的人,核心是这两段代码

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>

你看,我用这个

using( TextWriter writer = File.CreateText( sFullPathFileName ) ) {
    XamlServices.Save( writer, root );
}

来创建 xaml 文件!

从 XAML 文件反序列化

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 );
    }
}

这里我调用

using( TextReader reader = File.OpenText( lastFileUsed ) ) {
    Object obj = XamlServices.Load( reader );
    if( !(obj is Form) )
         return;
    Form fx = obj as Form;
...
}

从 XAML 文件读取并获取我的窗体。是的!XAML 已经完成了它的肮脏工作,然后退出场景。

其余工作由 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
}

它仅仅遍历 Form 托管的每个控件,然后依次调用 Fill() 方法,该方法通过反射将一些有用的属性从“运行时”的控件复制到“设计时”的控件。

使用代码

请参考项目“DemoConsoleForpDesigner”来查看一个基于 pDesigner 的“窗体设计器”,其中包含一个正在运行的持久化层。里面的代码有大量的注释。

就这样,各位!
再见!(^_^)

历史

2017 年 3 月 4 日 - 文章/代码首次提交。

© . All rights reserved.