Click here to Skip to main content
15,879,082 members
Articles / Desktop Programming / Windows Forms

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 14.2K   326   7   14
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)


Written By
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

 
QuestionPersistence Manager Pin
Member 41176074-Mar-22 4:00
Member 41176074-Mar-22 4:00 
QuestionKeyboard Service? Pin
nike1989zhuhai19-May-18 1:03
nike1989zhuhai19-May-18 1:03 
PraiseInformative article Pin
delamaine20-Feb-18 16:15
delamaine20-Feb-18 16:15 
QuestionWhat is the status of the TabControl issue? Pin
Jim Acer12-Aug-17 2:51
Jim Acer12-Aug-17 2:51 
BugBrilliant! .... But ... Pin
aymenbnr5-May-17 4:35
aymenbnr5-May-17 4:35 
GeneralRe: Brilliant! .... But ... Pin
MomciloM25-May-17 4:00
MomciloM25-May-17 4:00 
GeneralMy vote of 5 Pin
MomciloM14-Mar-17 2:04
MomciloM14-Mar-17 2:04 
BugChildren inside TabContol tabs are not being saved. Pin
MomciloM13-Mar-17 1:24
MomciloM13-Mar-17 1:24 
GeneralRe: Children inside TabContol tabs are not being saved. Pin
Paolo Foti17-Mar-17 3:18
Paolo Foti17-Mar-17 3:18 
GeneralRe: Children inside TabContol tabs are not being saved. Pin
MomciloM20-Mar-17 12:42
MomciloM20-Mar-17 12:42 
GeneralRe: Children inside TabContol tabs are not being saved. Pin
MomciloM27-Mar-17 5:16
MomciloM27-Mar-17 5:16 
Hello Paolo,

I have managed to find TabControl, similar to code that you proposed.
Unfortunately Tabs and their content does not load.
Only difference is in TabPageEx.
Yours is using [Designer(typeof(PTabPageDesigner))], and this one is
using [Designer(typeof(System.Windows.Forms.Design.ScrollableControlDesigner))] .
Would you be so kind to shed some light on this problem.

Here are the TabControl classes:


C#
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;

namespace Dotnetrix_Samples
{
    #region TabControlEx Class

    /// <summary>
    /// Summary description for TabControlEx.
    /// </summary>
    [DefaultProperty("TabPages")]
    [DefaultEvent("SelectedIndexChanged")]
    [ToolboxBitmap(typeof(System.Windows.Forms.TabControl)),
    Designer(typeof(Designers.TabControlExDesigner))]
    public class TabControlEx : System.Windows.Forms.TabControl
    {

        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        public TabControlEx() : base()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            // TODO: Add any initialization after the InitializeComponent call

        }


        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }


        public event TabControlExEventHandler SelectedIndexChanging;
        public TabPage HotTab = null;

        #region Component Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }


        #endregion

        #region Properties

        [Editor(typeof(TabpageExCollectionEditor), typeof(UITypeEditor))]
        public new TabPageCollection TabPages
        {
            get
            {
                return base.TabPages;
            }
        }


        #endregion

        #region TabpageExCollectionEditor

        internal class TabpageExCollectionEditor : CollectionEditor
        {
            public TabpageExCollectionEditor(System.Type type) : base(type)
            {
            }

            protected override Type CreateCollectionItemType()
            {
                return typeof(TabPageEx);
            }
        }

        #endregion

        #region Interop for SelectedIndexChanging event 

        [StructLayout(LayoutKind.Sequential)]
        private struct NMHDR
        {
            public IntPtr HWND;
            public uint idFrom;
            public int code;
            public override String ToString()
            {
                return String.Format("Hwnd: {0}, ControlID: {1}, Code: {2}", HWND, idFrom, code);
            }
        }

        private const int TCN_FIRST = 0 - 550;
        private const int TCN_SELCHANGING = (TCN_FIRST - 2);

        private const int WM_USER = 0x400;
        private const int WM_NOTIFY = 0x4E;
        private const int WM_REFLECT = WM_USER + 0x1C00;

        #endregion

        #region SelectedIndexChanging event Implementation

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == (WM_REFLECT + WM_NOTIFY))
            {
                NMHDR hdr = (NMHDR)(Marshal.PtrToStructure(m.LParam, typeof(NMHDR)));
                if (hdr.code == TCN_SELCHANGING)
                {
                    if (HotTab != null)
                    {
                        TabControlExEventArgs e = new TabControlExEventArgs(HotTab, Controls.IndexOf(HotTab));
                        if (SelectedIndexChanging != null)
                            SelectedIndexChanging(this, e);
                        if (e.Cancel || !HotTab.Enabled)
                        {
                            m.Result = new IntPtr(1);
                            return;
                        }
                    }
                }
            }
            base.WndProc(ref m);
        }


        #endregion

        #region HotTab Immplementation

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            HotTab = TestTab(new Point(e.X, e.Y));
        }


        #endregion

        #region Custom Methods

        public void InsertTabPage(TabPage tabpage, int index)
        {
            if (index < 0 || index > TabCount)
                throw new ArgumentException("Index out of Range.");

            TabPages.Add(tabpage);

            if (index < TabCount - 1)
            {
                do
                    SwapTabPages(tabpage, (TabPages[TabPages.IndexOf(tabpage) - 1]));
                while (TabPages.IndexOf(tabpage) != index);
            }

            SelectedTab = tabpage;
        }


        public void SwapTabPages(TabPage tp1, TabPage tp2)
        {
            if (!TabPages.Contains(tp1) || !TabPages.Contains(tp2))
                throw new ArgumentException("TabPages must be in the TabCotrols TabPageCollection.");
            int Index1 = TabPages.IndexOf(tp1);
            int Index2 = TabPages.IndexOf(tp2);
            TabPages[Index1] = tp2;
            TabPages[Index2] = tp1;
        }


        private TabPage TestTab(Point pt)
        {
            for (int index = 0; index <= TabCount - 1; index++)
            {
                if (GetTabRect(index).Contains(pt.X, pt.Y))
                    return TabPages[index];
            }
            return null;
        }


        #endregion

    }

    #region SelectedIndexChanging EventArgs Class/Delegate

    public class TabControlExEventArgs : EventArgs
    {
        private TabPage m_TabPage = null;
        private int m_TabPageIndex = -1;
        public bool Cancel = false;

        public TabPage tabPage
        {
            get
            {
                return m_TabPage;
            }
        }


        public int TabPageIndex
        {
            get
            {
                return m_TabPageIndex;
            }
        }


        public TabControlExEventArgs(TabPage tabPage, int TabPageIndex)
        {
            m_TabPage = tabPage;
            m_TabPageIndex = TabPageIndex;
        }


    }


    public delegate void TabControlExEventHandler(Object sender, TabControlExEventArgs e);

    #endregion

    #endregion

    #region TabPageEx Class

    [Designer(typeof(System.Windows.Forms.Design.ScrollableControlDesigner))]
    [ToolboxItemAttribute(false)]
    public class TabPageEx : TabPage
    {
        #region API Declares

        [DllImport("Comctl32.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int DllGetVersion(ref DLLVERSIONINFO pdvi);

        [DllImport("uxtheme.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern bool IsAppThemed();

        [DllImport("uxtheme.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
        private static extern IntPtr OpenThemeData(IntPtr hwnd, String pszClassList);

        [DllImport("uxtheme.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int GetThemePartSize(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, ref Rectangle prc, THEMESIZE eSize, ref Size psz);

        [DllImport("uxtheme.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int DrawThemeBackground(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, ref Rectangle pRect, IntPtr pClipRect);

        [DllImport("uxtheme.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int CloseThemeData(IntPtr htheme);

        private struct DLLVERSIONINFO
        {
            public int cbSize;
            public int dwMajorVersion;
            public int dwMinorVersion;
            public int dwBuildNumber;
            public int dwPlatformID;
            public DLLVERSIONINFO(Control ctrl)
            {
                cbSize = Marshal.SizeOf(typeof(DLLVERSIONINFO));
                dwMajorVersion = 0;
                dwMinorVersion = 0;
                dwBuildNumber = 0;
                dwPlatformID = 0;
            }
        }


        private enum THEMESIZE
        {
            TS_MIN,
            TS_TRUE,
            TS_DRAW
        }


        private const int TABP_BODY = 10;
        private const int WM_THEMECHANGED = 0x31A;

        #endregion

        #region Properties

        private bool bStyled = true;
        private Brush m_Brush;

        private bool AppIsXPThemed
        {
            //IsAppThemed will return True if the App is not using visual
            //Styles but It's TitleBar is drawn with Visual Style(i.e. a
            //manifest resource has not been supplied). To overcome this
            //problem we must also check which version of ComCtl32.dll is
            //being used. Since ComCtl32.dll version 6 is exclusive to
            //WindowsXP, we do not need to check the OSVersion.
            get
            {
                DLLVERSIONINFO dllVer = new DLLVERSIONINFO(this);
                DllGetVersion(ref dllVer);
                if (dllVer.dwMajorVersion >= 6) return IsAppThemed();
                return false;
            }
        }


        [Category("Appearance")]
        [Description("Enables/Disables Visual Styles on the TabPage. Valid only in WidowsXP.")]
        [DefaultValue(true)]
        public bool EnableVisualStyles
        {
            get
            {
                return bStyled;
            }
            set
            {
                if (bStyled == value) return;
                bStyled = value;
                Invalidate(true);
            }
        }


        #endregion

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        public TabPageEx()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            // TODO: Add any initialization after the InitializeComponent call

        }

        public TabPageEx(String Text) : base()
        {
            base.Text = Text;
        }


        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }


        #region Component Designer generated code

        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
            this.Disposed += new EventHandler(TabpageEx_Disposed);
        }


        #endregion

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            if (EnableVisualStyles && AppIsXPThemed)
            {
                if (m_Brush == null) SetTabBrush();
                //Paint the TabPage with our Brush.
                pevent.Graphics.FillRectangle(m_Brush, ClientRectangle);
            }
            else
                //Call the default Paint Event.
                base.OnPaintBackground(pevent);

        }


        private void SetTabBrush()
        {
            IntPtr hdc;
            IntPtr hTheme;
            Size sz = new Size(0, 0);
            Bitmap bmp;
            int h = Height;

            //Open the theme data for the Tab Class.
            hTheme = OpenThemeData(Handle, "TAB");

            //Get the size of the Active Theme's TabPage Bitmap.
            Rectangle displayrect = DisplayRectangle;
            GetThemePartSize(hTheme, IntPtr.Zero, TABP_BODY, 0, ref displayrect, THEMESIZE.TS_TRUE, ref sz);

            //If the TabPage is taller than the bitmap then we'll get a
            //nasty block efect so we'll check for that and correct.
            if (h > sz.Height) sz.Height = h;
            //Create a new bitmap of the correct size.
            bmp = new Bitmap(sz.Width, sz.Height);
            //Create a Graphics object from our bitmap so we can
            //draw to it.
            Graphics g = Graphics.FromImage(bmp);

            //Get the handle to the Graphics Object's DC for API usage.
            hdc = g.GetHdc(); //Hidden member of Graphics

            Rectangle bmpRect = new Rectangle(0, 0, sz.Width, sz.Height);
            //Draw to the Bitmaps Graphics Object.
            DrawThemeBackground(hTheme, hdc, TABP_BODY, 0, ref bmpRect, IntPtr.Zero);

            //Release the DC to Windows.
            g.ReleaseHdc(hdc); //Hidden member of Graphics

            //Close the theme data for the Tab Class.
            CloseThemeData(hTheme);

            //Create a BitmapBrush.
            m_Brush = new TextureBrush(bmp);

            //Clean Up
            bmp.Dispose();
            g.Dispose();

        }


        private void TabpageEx_Disposed(Object sender, System.EventArgs e)
        {
            //Get rid of the brush if we created one.
            if (m_Brush != null) m_Brush.Dispose();
        }


        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            if (AppIsXPThemed) SetTabBrush();
        }


        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (m.Msg == WM_THEMECHANGED)
                SetTabBrush();
        }


        //Have to take responsibility for drawing TabItems for this method to be useful.
        //Protected Overrides Function ProcessMnemonic(ByVal charCode As Char) As Boolean
        //    If IsMnemonic(charCode, Text) Then
        //        DirectCast(Parent, TabControl).SelectedTab = Me
        //        Return True
        //    End If
        //    Return False
        //End Function

    }

    #endregion

}


namespace Designers
{

    internal class TabControlExDesigner : System.Windows.Forms.Design.ParentControlDesigner
    {

        #region Private Instance Variables

        private DesignerVerbCollection m_verbs = new DesignerVerbCollection();
        private IDesignerHost m_DesignerHost;
        private ISelectionService m_SelectionService;

        #endregion

        public TabControlExDesigner() : base()
        {
            DesignerVerb verb1 = new DesignerVerb("Add Tab", new EventHandler(OnAddPage));
            DesignerVerb verb2 = new DesignerVerb("Insert Tab", new EventHandler(OnInsertPage));
            DesignerVerb verb3 = new DesignerVerb("Remove Tab", new EventHandler(OnRemovePage));
            m_verbs.AddRange(new DesignerVerb[] { verb1, verb2, verb3 });
        }


        #region Properties

        public override DesignerVerbCollection Verbs
        {
            get
            {
                if (m_verbs.Count == 3)
                {
                    Dotnetrix_Samples.TabControlEx MyControl = (Dotnetrix_Samples.TabControlEx)Control;
                    if (MyControl.TabCount > 0)
                    {
                        m_verbs[1].Enabled = true;
                        m_verbs[2].Enabled = true;
                    }
                    else
                    {
                        m_verbs[1].Enabled = false;
                        m_verbs[2].Enabled = false;
                    }
                }
                return m_verbs;
            }
        }


        public IDesignerHost DesignerHost
        {
            get
            {
                if (m_DesignerHost == null)
                    m_DesignerHost = (IDesignerHost)(GetService(typeof(IDesignerHost)));

                return m_DesignerHost;
            }
        }


        public ISelectionService SelectionService
        {
            get
            {
                if (m_SelectionService == null)
                    m_SelectionService = (ISelectionService)(this.GetService(typeof(ISelectionService)));
                return m_SelectionService;
            }
        }


        #endregion

        void OnAddPage(Object sender, EventArgs e)
        {
            Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;
            Control.ControlCollection oldTabs = ParentControl.Controls;

            RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)["TabPages"]);

            Dotnetrix_Samples.TabPageEx P = (Dotnetrix_Samples.TabPageEx)(DesignerHost.CreateComponent(typeof(Dotnetrix_Samples.TabPageEx)));
            P.Text = P.Name;
            ParentControl.TabPages.Add(P);

            RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)["TabPages"], oldTabs, ParentControl.TabPages);
            ParentControl.SelectedTab = P;

            SetVerbs();

        }


        void OnInsertPage(Object sender, EventArgs e)
        {
            Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;
            Control.ControlCollection oldTabs = ParentControl.Controls;
            int Index = ParentControl.SelectedIndex;

            RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)["TabPages"]);

            Dotnetrix_Samples.TabPageEx P = (Dotnetrix_Samples.TabPageEx)(DesignerHost.CreateComponent(typeof(Dotnetrix_Samples.TabPageEx)));
            P.Text = P.Name;

            Dotnetrix_Samples.TabPageEx[] tpc = new Dotnetrix_Samples.TabPageEx[ParentControl.TabCount];
            //Starting at our Insert Position, store and remove all the tabpages.
            for (int i = Index; i <= tpc.Length - 1; i++)
            {
                tpc[i] = (Dotnetrix_Samples.TabPageEx)ParentControl.TabPages[Index];
                ParentControl.TabPages.Remove(ParentControl.TabPages[Index]);
            }
            //add the tabpage to be inserted.
            ParentControl.TabPages.Add(P);
            //then re-add the original tabpages.
            for (int i = Index; i <= tpc.Length - 1; i++)
            {
                ParentControl.TabPages.Add(tpc[i]);
            }

            RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)["TabPages"], oldTabs, ParentControl.TabPages);
            ParentControl.SelectedTab = P;

            SetVerbs();

        }


        void OnRemovePage(Object sender, EventArgs e)
        {
            Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;
            Control.ControlCollection oldTabs = ParentControl.Controls;

            if (ParentControl.SelectedIndex < 0) return;

            RaiseComponentChanging(TypeDescriptor.GetProperties(ParentControl)["TabPages"]);

            DesignerHost.DestroyComponent(ParentControl.TabPages[ParentControl.SelectedIndex]);

            RaiseComponentChanged(TypeDescriptor.GetProperties(ParentControl)["TabPages"], oldTabs, ParentControl.TabPages);

            SelectionService.SetSelectedComponents(new IComponent[] { ParentControl }, SelectionTypes.Auto);

            SetVerbs();

        }


        private void SetVerbs()
        {
            Dotnetrix_Samples.TabControlEx ParentControl = (Dotnetrix_Samples.TabControlEx)Control;

            switch (ParentControl.TabPages.Count)
            {
                case 0:
                    Verbs[1].Enabled = false;
                    Verbs[2].Enabled = false;
                    break;
                case 1:
                    Verbs[1].Enabled = false;
                    Verbs[2].Enabled = true;
                    break;
                default:
                    Verbs[1].Enabled = true;
                    Verbs[2].Enabled = true;
                    break;
            }
        }

        private const int WM_NCHITTEST = 0x84;

        private const int HTTRANSPARENT = -1;
        private const int HTCLIENT = 1;

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (m.Msg == WM_NCHITTEST)
            {
                //select tabcontrol when Tabcontrol clicked outside of TabItem.
                if (m.Result.ToInt32() == HTTRANSPARENT)
                    m.Result = (IntPtr)HTCLIENT;
            }

        }

        private enum TabControlHitTest
        {
            TCHT_NOWHERE = 1,
            TCHT_ONITEMICON = 2,
            TCHT_ONITEMLABEL = 4,
            TCHT_ONITEM = TCHT_ONITEMICON | TCHT_ONITEMLABEL
        }

        private const int TCM_HITTEST = 0x130D;

        private struct TCHITTESTINFO
        {
            public Point pt;
            public TabControlHitTest flags;
        }

        protected override bool GetHitTest(Point point)
        {
            if (this.SelectionService.PrimarySelection == this.Control)
            {
                TCHITTESTINFO hti = new TCHITTESTINFO();

                hti.pt = this.Control.PointToClient(point);
                hti.flags = 0;

                Message m = new Message();
                m.HWnd = this.Control.Handle;
                m.Msg = TCM_HITTEST;

                IntPtr lparam = Marshal.AllocHGlobal(Marshal.SizeOf(hti));
                Marshal.StructureToPtr(hti, lparam, false);
                m.LParam = lparam;

                base.WndProc(ref m);
                Marshal.FreeHGlobal(lparam);

                if (m.Result.ToInt32() != -1)
                    return hti.flags != TabControlHitTest.TCHT_NOWHERE;

            }

            return false;
        }


        protected override void OnPaintAdornments(PaintEventArgs pe)
        {
            //Don't want DrawGrid dots.
        }


        //Fix the AllSizable selectiorule on DockStyle.Fill
        public override System.Windows.Forms.Design.SelectionRules SelectionRules
        {
            get
            {
                if (Control.Dock == DockStyle.Fill)
                    return System.Windows.Forms.Design.SelectionRules.Visible;
                return base.SelectionRules;
            }
        }


    }

}



And this is how XML look like with two Tabs and one checkBox in each Tab:

XML
<Form AllowDrop="True" BackColor="Control" ClientSize="392, 293" Location="15, 15" Name="Form_0" Text="Form_0 (design mode)" xmlns="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" xmlns:d="clr-namespace:Dotnetrix_Samples;assembly=DemoConsoleForpDesigner" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Form.Controls>
    <d:TabControlEx AllowDrop="True" Location="63, 46" Name="TabControlEx1" SelectedIndex="1" Size="272, 189" TabIndex="0">
      <d:TabControlEx.Controls>
        <x:Reference>__ReferenceID0</x:Reference>
        <x:Reference>__ReferenceID1</x:Reference>
      </d:TabControlEx.Controls>
      <d:TabControlEx.TabPages>
        <d:TabPageEx x:Name="__ReferenceID0" AllowDrop="True" Enabled="True" Location="4, 22" Name="TabPageEx1" Size="264, 163" TabIndex="0" TabStop="False" Text="TabPageEx1" Visible="False">
          <d:TabPageEx.Controls>
            <CheckBox Location="61, 58" Name="CheckBox1" TabIndex="0" Text="CheckBox1" UseCompatibleTextRendering="True" UseVisualStyleBackColor="True" />
          </d:TabPageEx.Controls>
        </d:TabPageEx>
        <d:TabPageEx x:Name="__ReferenceID1" AllowDrop="True" Enabled="True" Location="4, 22" Name="TabPageEx2" Size="264, 163" TabIndex="1" TabStop="False" Text="TabPageEx2" Visible="True">
          <d:TabPageEx.Controls>
            <CheckBox Location="96, 64" Name="CheckBox2" TabIndex="0" Text="CheckBox2" UseCompatibleTextRendering="True" UseVisualStyleBackColor="True" />
          </d:TabPageEx.Controls>
        </d:TabPageEx>
      </d:TabControlEx.TabPages>
    </d:TabControlEx>
  </Form.Controls>
</Form>


I have added necessary properties in allowedProperties in DeployControlsOnDesignSurface method, but still no luck Frown | :( .

Thank you in advance,
Moma.

modified 27-Mar-17 11:39am.

GeneralRe: Children inside TabContol tabs are not being saved. Pin
aymenbnr5-May-17 4:37
aymenbnr5-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.