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