MDI
XML
TreeView
Børre Stenseth
Moduler>Arkitektur>MDI

MDI-applikasjon

Hva
Image1
MDI, XML, TreeView etc

Dette er en litt mer omfattende applikasjon enn de fleste andre i denne samlingen. Den tar utgangspunkt i .Nets MDI-løsning.

Dokumenttypen er grunnleggende tekst, som kan betraktes på tre måter: som ren editerbar tekst, som HTML i en web-browser eller som et tre med XML-noder. Slik sett er applikasjonen et enkelt skjelett for et program som kunne være nyttig i utvikling av XHTML-dokumenter. Det gjenstår en del før programmet er et nyttig verktøy, både grensesnitt, sikkerhet og funksjonalitet.

Det du kanskje kan ha nytte av er:

  • Holde orden på hvilke dokumenter som er lagret/bør lagres.
  • En enkel måte å ta vare på programmets egenskaper som i dette tilfellet betyr vindusstørrelse, vindusplassering og de sist brukte filene.
  • Lydhørhet for at andre applikasjoner kan endre åpne dokumenter
  • Mapping av XML til en TreeView
  • Bruk av faner, tabs

Hovedstrukturen

Applikasjonen består av en Form som fungerer som ramme for de dokumentene vi skal bearbeide. Denne heter MainForm. Dokumentene er beskrevet i en annen Form, MDIChild. Det er MainForm som administrerer alle menyer og som responderer på menyvalg.

Hver forekomst av MDIChild har en TabControl med tre tabs: en med en TextBox, en med en WebBrowser og en med en TreeView. Vi kan endre framstilling av dokumentet ved å velge mellom disse.

En avveining som må gjøres i en slik MDI-applikasjon er plasseringen av filbehandling. Vi må kunne identifisere mellom dokumenter som er endret og som derfor bør spares, og vi må kunne spare lagrede dokumenter under et annet navn (save as). Vi har i prinsippet to valg når det gjelder organisering av filhandteringen: Vi kan ordne alt i hovedformen eler vi kan desentralisere til dokumentene. I dett eksempelet er mesteparten lagt til dokumentet og hovedformen begrenser seg til å holde styr på hvilken dokument som er aktivt:

this.ActiveMdiChild

HovedFormen

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Configuration;
namespace MDI
{
    public partial class MainForm : Form
    {
        // the settings
        MDI.Properties.Settings MySettings;
        public MainForm()
        {
            InitializeComponent();
            MySettings=new MDI.Properties.Settings();
        }
        private void MainForm_Load(object sender, EventArgs e)
        {
            // set size and position
            this.SetBounds(MySettings.left, 
                MySettings.top, 
                MySettings.width, 
                MySettings.height);
            // append recent files menues
            RecentList.LoadAndSetup(MySettings, 
                this.recentFiles,this.RecentFileMenu);
            UpdateCommands();
        }
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
             // set position and size
            MySettings.top = this.Bounds.Top;
            MySettings.left = this.Bounds.Left;
            MySettings.width = this.Bounds.Width;
            MySettings.height = this.Bounds.Height;
            
            MySettings.Save();
        }

        private void MainForm_MdiChildActivate(object sender, 
            EventArgs e)
        {
            UpdateCommands();
        }

        private void MainForm_Activated(object sender, 
            EventArgs e)
        {
            // form has gained focus and we want to check if
            // any children has been changed by other programs
            if (MdiChildren.Length == 0)
                return;
            for (int ix = 0; ix < MdiChildren.Length; ix++)
            {
                MDIChild child = (MDIChild)MdiChildren[ix];
                child.CheckUpdateStatus();
            }
            UpdateCommands();
        }
        // keep status
        public void UpdateCommands()
        {
            editToolStripMenuItem.Enabled = true;
            windowsToolStripMenuItem.Enabled = true;
            saveAsToolStripMenuItem.Enabled = true;
            toolStripButtonSave.Enabled = true;
            saveToolStripMenuItem.Enabled = true;
            closeToolStripMenuItem.Enabled = true;
            
            if (this.ActiveMdiChild == null)
            {
                editToolStripMenuItem.Enabled = false;
                windowsToolStripMenuItem.Enabled = false;
                saveAsToolStripMenuItem.Enabled = false;
                saveToolStripMenuItem.Enabled = false;
                toolStripButtonSave.Enabled = false;
                closeToolStripMenuItem.Enabled = false;
            }
            else
            {
                MDIChild child = (MDIChild)this.ActiveMdiChild;
                if (!child.Dirty)
                {
                    saveToolStripMenuItem.Enabled = false;
                    toolStripButtonSave.Enabled = false;
                }
                if(!child.Editable)
                    editToolStripMenuItem.Enabled = false;
            }
        }
#region Filemenuhandling
        private void newToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            //establish a new, empty, XMLfile
            MDIChild childForm = new MDIChild(this);
            childForm.Show();
            UpdateCommands();
        }
        private void openToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            DialogResult result = openFileDialog1.ShowDialog(this);
            if (result == DialogResult.OK)
            {
                MDIChild childForm = 
                    new MDIChild(this, openFileDialog1.FileName);
                childForm.Show();
                RecentList.Append(openFileDialog1.FileName);
            }
            UpdateCommands();
        }
        private void saveToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            if (this.ActiveMdiChild == null)
                return;
            MDIChild child = (MDIChild)this.ActiveMdiChild;
            if (child.FileName != null)
                child.DoSave();
            else
                child.DoSaveAs();
            UpdateCommands();
        }
        private void saveAsToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            if (this.ActiveMdiChild == null)
                return;
            MDIChild child = (MDIChild)this.ActiveMdiChild;
            child.DoSaveAs();
            UpdateCommands();
        }
        private void closeToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            if (this.ActiveMdiChild == null)
                return;
            MDIChild child = (MDIChild)this.ActiveMdiChild;
            if (child.Dirty)
                child.WillSave();
            child.Dispose();
            UpdateCommands();
        }
        private void exitToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            // loop all documents and 
            // check if any need a save before closing
            while (MdiChildren.Length > 0)
                closeToolStripMenuItem_Click(sender, e);
            this.Close();
            this.Dispose();
        }

        private void RecentFileMenu(object sender, 
            EventArgs e)
        {
            // menutext is filename
            String fileName = sender.ToString();
            MDIChild childForm = new MDIChild(this, fileName);
            childForm.Show();
            UpdateCommands();
        }
#endregion Filemenuhandling
#region windowmenuhandling
        private void cascadeToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            LayoutMdi(MdiLayout.Cascade);
        }
        private void tileVerticalToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            LayoutMdi(MdiLayout.TileVertical);
        }
        private void tileHorizontalToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            LayoutMdi(MdiLayout.TileHorizontal);
        }
        private void arrangeIconsToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            LayoutMdi(MdiLayout.ArrangeIcons);
        }
        // close all
        private void closeAllToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            // loop all documents and check 
            // if any need a save before closing
            while (MdiChildren.Length > 0)
                closeToolStripMenuItem_Click(sender, e);
        }
#endregion windowmenuhandling
#region editmenuhandling
        private void cutToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            if (this.ActiveMdiChild != null)
                ((MDIChild)this.ActiveMdiChild).Cut();
        }
        private void copyToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            if (this.ActiveMdiChild != null)
                ((MDIChild)this.ActiveMdiChild).Copy();
        }
        private void pasteToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            if (this.ActiveMdiChild != null)
                ((MDIChild)this.ActiveMdiChild).Paste();
        }
        private void selectAllToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            if (this.ActiveMdiChild != null)
                ((MDIChild)this.ActiveMdiChild).SelectAll();
        }
#endregion editmenuhandling
#region viewandhelpmenuhandling
        private void statusbarToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            this.statusStrip1.Visible=!this.statusStrip1.Visible;
        }
        private void toolbarToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            this.toolStrip1.Visible = !this.toolStrip1.Visible;
        }
        private void aboutToolStripMenuItem_Click(object sender, 
            EventArgs e)
        {
            AboutBox1 box = new AboutBox1();
            box.ShowDialog(this);
            box.Dispose();
        }
#endregion viewandhelpmenuhandling

    }
}

Dokumentene

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace MDI
{
    public partial class MDIChild : Form
    {
        // where does it belong
        String filename;
        // and what is the name if iterator not saved yet
        String docname;
        // naming of new documents
        static String DOCNAMEBASE = "Untitled-";
        static int docno=1;
        // when was it last updated by this program
        DateTime lastUpdated;
        // controlling updates, avoid looping
        Boolean timeChecked;
        // need a save ?
        Boolean dirty;
        // presentation mode as source text
        Boolean textmode;
 
        // construct new
        public MDIChild(MainForm p)
        {
            InitializeComponent();
            this.MdiParent = p;
            textBox1.Text = MDI.Properties.Resources.EmptyDocument;
            filename = null;
            timeChecked = false;
            docname = DOCNAMEBASE + Convert.ToString(docno++);
            this.Text = docname;
            Dirty = true;
            textmode = true;
        }
        //construct based on file
        public MDIChild(MainForm p,String fname)
        {
            InitializeComponent();
            this.MdiParent = p;
            filename = fname;
            timeChecked = false;
            docname = Path.GetFileName(fname);
            this.Text = fname;
            Dirty=false;
            textmode = true;
            Load();
       }
        public override string ToString()
        {
            if(filename!=null)
                return filename;
            return docname;
        }
        public Boolean Dirty
        {
            get { return dirty; }
            set { dirty = value;
                String s=this.Text;
                if (s.EndsWith("*"))
                {
                    if (!dirty)
                        this.Text = s.Substring(0, s.Length - 1);
                }
                else
                {
                    if(dirty)
                        this.Text = s+"*";
                }
            }
        }
        public String FileName
        {
            get { return filename; }
            set { filename = value; this.Text = filename; }
        }
        
        public DateTime UpdateTime
        {
            get { return lastUpdated; }
            set { lastUpdated = value;
                  timeChecked = false;
                }
        }
        // only editable when we show source text
        public Boolean Editable
        {
            get { return textmode; }
        }
        
        private void TabPage_Selected(object sender, 
            TabControlEventArgs e)
        {
            textmode = true;
            if (e.TabPage == tabBrowse)
            {
                webBrowser1.DocumentText = textBox1.Text; 
                textmode = false;
            }
            else if (e.TabPage == tabTree){
                TreeMaker.PrepareTreeView(treeView1, textBox1.Text);
                textmode = false;
            }
            // do nothing for e.TabPage == tabSource
            ((MainForm)this.ParentForm).UpdateCommands();
        }

        private void textBox1_TextChanged(object sender, 
            EventArgs e)
        {
            Dirty = true;
        }
        
        private void MDIChild_MdiChildActivate(object sender, 
            EventArgs e)
        {
            CheckUpdateStatus();
        }
        
        public void CheckUpdateStatus()
        {
            if (FileName != null && HasChanged())
            {
                DialogResult result = 
                    MessageBox.Show("File: " + 
                    ToString() + 
                    " May have been changed by another program, reload ?",
                            "Reload",
                            MessageBoxButtons.YesNo,
                            MessageBoxIcon.Question);
                if (result == DialogResult.Yes)
                    Load();
                UpdateTime = DateTime.Now;
            }
        }
        public Boolean HasChanged()
        {
            DateTime dt = File.GetLastWriteTime(filename);
            if ((dt.CompareTo(lastUpdated) > 0) && (!timeChecked))
            {
                timeChecked = true;
                return true;
            }
            return false;
        }
 #region edit       
        public void Cut() 
        {
            if (textBox1.SelectedText.Length > 0)
                Dirty = true;
            textBox1.Cut(); 
        }
        public void Copy() 
        {
            textBox1.Copy(); 
        }
        public void SelectAll() 
        { 
            textBox1.SelectAll(); 
        }
        public void Paste()
        {
            if (Clipboard.ContainsText())
            {
                Dirty = true;
                textBox1.Paste();
            }
        }
#endregion edit
#region filing
        private void MDIChild_FormClosing(object sender, 
            FormClosingEventArgs e)
        {
            if (Dirty)
            {
                WillSave();
            }
        }
       public void WillSave()
        {
            DialogResult result = 
                MessageBox.Show("Save " + 
                ToString() + 
                " before quitting?",
                       "Save",
                       MessageBoxButtons.YesNo,
                       MessageBoxIcon.Question);
            if (result == DialogResult.Yes)
            {
                if (FileName != null)
                    DoSave();
                else
                    DoSaveAs();
            }
        }
        public void DoSave()
        {
            if (FileName != null)
                Save();
            else
                DoSaveAs();
        }

        public void DoSaveAs()
        {
            saveFileDialog1.FileName = ToString();
            DialogResult result = 
                saveFileDialog1.ShowDialog(this.Parent);
            if (result == DialogResult.OK)
            {
                FileName = saveFileDialog1.FileName;
                Save();
                RecentList.Append(FileName);
            }
        }
        public  void Load()
        {
            String inTxt = "";
            FileStream s = null;
            StreamReader r = null;
            try
            {
                s = new FileStream(filename, FileMode.Open);
                r = new StreamReader(s,Encoding.Default,true);
                String line = r.ReadLine();
                while (line != null)
                {
                    inTxt = inTxt + line + "\r\n";
                    line = r.ReadLine();
                }
                r.Close();
                textBox1.Text = inTxt;
                Dirty = false;
                UpdateTime = DateTime.Now;
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message,
                                "Load Error",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Exclamation);
           }
            finally
            {
                if (r != null)
                    r.Close();
            }
        }
        public  void Save()
        {
            String txt = textBox1.Text;
            FileStream s = null;
            StreamWriter w = null;
            try
            {
                s = new FileStream(FileName, FileMode.Create);
                w = new StreamWriter(s);
                String[] lines = txt.Split('\n');
                foreach (String line in lines)
                    w.WriteLine(line.Trim());
                Dirty = false;
                UpdateTime = DateTime.Now;
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message,
                                "Save Error",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Exclamation); 
            }
            finally
            {
                if (w != null)
                    w.Close();
            }
        }       
        #endregion filing
    }
}

Programegenskaper

Programmets "hukommelse" fra en sesjon til en annen er ivaretatt med den standardløsningen som Visual studio inviterer til. Det vil si at det lages en fil: Properties.Setting.settings med en klasse Settings.cs og Settings.Designer.cs.

Oversikten over sist brukte filer er implementert i en egenprodusert klasse : RecentList.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Collections;
namespace MDI
{
    class RecentList
    {
        static ArrayList List;
        static int MAXRECENTFILELIST = 5;
        static ToolStripMenuItem RecentFiles;
        static MDI.Properties.Settings Settings;
        static EventHandler DoEvent;
        public static void LoadAndSetup(MDI.Properties.Settings settings, 
                                        ToolStripMenuItem recentFiles,
                                        EventHandler doEvent)
        {
            List = new ArrayList(MAXRECENTFILELIST+1);
            RecentFiles = recentFiles;
            Settings = settings;
            DoEvent = doEvent;
            String S = Settings.files;
            if (S != null && S.Length > 3)
            {
                String[] L = S.Split(',');
                for (int ix = 0; ix < L.Length; ix++)
                    List.Add(L[ix]);
                ControlFiles();
            }
            BuildMenu();
            Pack();
        }
        public static void Pack()
        {
            String S="";
            for (int ix = 0; ix < List.Count; ix++)
                S += (String)List[ix] + ",";
            if(S.EndsWith(","))
                S = S.Substring(0, S.Length - 1);
            Settings.files = S;
        }
        public static void Append(String S)
        {
            if(List.Contains(S))
                return;
            if (List.Count >= MAXRECENTFILELIST)
            {
                List.Insert(0, S);
                List.RemoveAt(List.Count-1);
            }
            else
                List.Add(S);                       
            BuildMenu();
            Pack();
        }
        private static void BuildMenu()
        {
            RecentFiles.DropDownItems.Clear();
            for (int ix = 0; ix < List.Count; ix++)
            {
                ToolStripMenuItem m=
                    new ToolStripMenuItem((String)List[ix]);
                RecentFiles.DropDownItems.Add(m);
                m.Click += new System.EventHandler(DoEvent);
            }
            RecentFiles.Enabled = RecentFiles.DropDownItems.Count > 0;
        }
        static void ControlFiles()
        {
            // walk all files and see if they exist
            for (int ix = 0; ix < List.Count; ix++)
            {
                if (!File.Exists((String)List[ix]))
                    List.RemoveAt(ix);
            }
        }
    }
}

XML i et tre

Funksjonaliteten som legger ut en XML-struktur i et TreeView er pakket inn som statiske funksjoner i en klasse TreeMaker.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.IO;
namespace MDI
{
    class TreeMaker
    {
        static public void PrepareTreeView(TreeView TV, String T)
        {
            // want to prepare TV from the DOM-tree generated from T
            XmlDocument doc = null;
            try
            {
                doc = new XmlDocument();
                doc.Load(new StringReader(T));
            }
            catch (Exception e)
            {
                // could not make DOM, make a error-reporting one
                doc = new XmlDocument();
                doc.Load(new StringReader(
                    "<?xml version=\"1.0\" encoding=\"utf8\"?><Could_not_parse/>"));
            }
            // ok we have the DOM
            // Suppress repainting until all the objects have been created.
            TV.BeginUpdate();
            TV.Nodes.Clear();
            // fill the treeView. We traverse the DOM-tree recursively
            XmlElement root = doc.DocumentElement;
            TreeNode new_tn = new TreeNode(root.Name);
            TV.Nodes.Add(new_tn);
            foreach (XmlNode n in root.ChildNodes)
                DoNode(n, new_tn);
            // Begin repainting the TreeView.
            TV.EndUpdate();
        }
        static protected void DoNode(XmlNode n, TreeNode t)
        {
            TreeNode new_tn = null;
            if (n.NodeType == XmlNodeType.Text)
                new_tn = new TreeNode(n.InnerText);
            else
                new_tn = new TreeNode(n.Name);
            t.Nodes.Add(new_tn);
            foreach (XmlNode xn in n.ChildNodes)
                DoNode(xn, new_tn);
        }
    }
}
Referanser
Prosjekt:
https://svn.hiof.no/svn/psource/Csharpspikes/mdi
Vedlikehold

B.Stenseth, september 2005

(Velkommen) Moduler>Arkitektur>MDI (Dialogbokser)