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