// Project -- DocToolkit
// DocManager.cs
#region Using directives
using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Globalization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Win32;
using System.Security;
#endregion
// Using: in the end of this file.
namespace DocToolkit
{
#region Class DocManager
/// <summary>
/// Document manager. Makes file-related operations:
/// open, new, save, updating of the form title,
/// registering of file type for Windows Shell.
/// Built using the article:
/// Creating Document-Centric Applications in Windows Forms
/// by Chris Sells
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms09182003.asp
/// </summary>
public class DocManager
{
#region Events
public event SaveEventHandler SaveEvent;
public event LoadEventHandler LoadEvent;
public event OpenFileEventHandler OpenEvent;
public event EventHandler ClearEvent;
public event EventHandler DocChangedEvent;
#endregion
#region Members
private string fileName = "";
private bool dirty = false;
private Form frmOwner;
private string newDocName;
private string fileDlgFilter;
private string registryPath;
private bool updateTitle;
private const string registryValue = "Path";
private string fileDlgInitDir = ""; // file dialog initial directory
#endregion
#region Enum
/// <summary>
/// Enumeration used for Save function
/// </summary>
public enum SaveType
{
Save,
SaveAs
}
#endregion
#region Constructor
/// <summary>
/// Initialization
/// </summary>
/// <param name="data"></param>
public DocManager(DocManagerData data)
{
frmOwner = data.FormOwner;
frmOwner.Closing += OnClosing;
updateTitle = data.UpdateTitle;
newDocName = data.NewDocName;
fileDlgFilter = data.FileDialogFilter;
registryPath = data.RegistryPath;
if (!registryPath.EndsWith("\\"))
registryPath += "\\";
registryPath += "FileDir";
// attempt to read initial directory from registry
RegistryKey key = Registry.CurrentUser.OpenSubKey(registryPath);
if (key != null)
{
string s = (string)key.GetValue(registryValue);
if (!Empty(s))
fileDlgInitDir = s;
}
}
#endregion
#region Public functions and Properties
/// <summary>
/// Dirty property (true when document has unsaved changes).
/// </summary>
public bool Dirty
{
get
{
return dirty;
}
set
{
dirty = value;
SetCaption();
}
}
/// <summary>
/// Open new document
/// </summary>
/// <returns></returns>
public bool NewDocument()
{
if (!CloseDocument())
return false;
SetFileName("");
if (ClearEvent != null)
{
// raise event to clear document contents in memory
// (this class has no idea how to do this)
ClearEvent(this, new EventArgs());
}
Dirty = false;
return true;
}
/// <summary>
/// Close document
/// </summary>
/// <returns></returns>
public bool CloseDocument()
{
if (!this.dirty)
return true;
DialogResult res = MessageBox.Show(
frmOwner,
"Save changes?",
Application.ProductName,
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Exclamation);
switch (res)
{
case DialogResult.Yes: return SaveDocument(SaveType.Save);
case DialogResult.No: return true;
case DialogResult.Cancel: return false;
default: Debug.Assert(false); return false;
}
}
/// <summary>
/// Open document
/// </summary>
/// <param name="newFileName">
/// Document file name. Empty - function shows Open File dialog.
/// </param>
/// <returns></returns>
public bool OpenDocument(string newFileName)
{
// Check if we can close current file
if (!CloseDocument())
return false;
// Get the file to open
if (Empty(newFileName))
{
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.Filter = fileDlgFilter;
openFileDialog1.InitialDirectory = fileDlgInitDir;
DialogResult res = openFileDialog1.ShowDialog(frmOwner);
if (res != DialogResult.OK)
return false;
newFileName = openFileDialog1.FileName;
fileDlgInitDir = new FileInfo(newFileName).DirectoryName;
}
// Read the data
try
{
using (Stream stream = new FileStream(
newFileName, FileMode.Open, FileAccess.Read))
{
// Deserialize object from text format
IFormatter formatter = new BinaryFormatter();
if (LoadEvent != null) // if caller subscribed to this event
{
SerializationEventArgs args = new SerializationEventArgs(
formatter, stream, newFileName);
// raise event to load document from file
LoadEvent(this, args);
if (args.Error)
{
// report failure
if (OpenEvent != null)
{
OpenEvent(this,
new OpenFileEventArgs(newFileName, false));
}
return false;
}
// raise event to show document in the window
if (DocChangedEvent != null)
{
DocChangedEvent(this, new EventArgs());
}
}
}
}
// Catch all exceptions which may be raised from this code.
// Caller is responsible to handle all other exceptions
// in the functions invoked by LoadEvent and DocChangedEvent.
catch (ArgumentNullException ex) { return HandleOpenException(ex, newFileName); }
catch (ArgumentOutOfRangeException ex) { return HandleOpenException(ex, newFileName); }
catch (ArgumentException ex) { return HandleOpenException(ex, newFileName); }
catch (SecurityException ex) { return HandleOpenException(ex, newFileName); }
catch (FileNotFoundException ex) { return HandleOpenException(ex, newFileName); }
catch (DirectoryNotFoundException ex) { return HandleOpenException(ex, newFileName); }
catch (PathTooLongException ex) { return HandleOpenException(ex, newFileName); }
catch (IOException ex) { return HandleOpenException(ex, newFileName); }
// Clear dirty bit, cache the file name and set the caption
Dirty = false;
SetFileName(newFileName);
if (OpenEvent != null)
{
// report success
OpenEvent(this, new OpenFileEventArgs(newFileName, true));
}
// Success
return true;
}
/// <summary>
/// Save file.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public bool SaveDocument(SaveType type)
{
// Get the file name
string newFileName = this.fileName;
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.Filter = fileDlgFilter;
if ((type == SaveType.SaveAs) ||
Empty(newFileName))
{
if (!Empty(newFileName))
{
saveFileDialog1.InitialDirectory = Path.GetDirectoryName(newFileName);
saveFileDialog1.FileName = Path.GetFileName(newFileName);
}
else
{
saveFileDialog1.InitialDirectory = fileDlgInitDir;
saveFileDialog1.FileName = newDocName;
}
DialogResult res = saveFileDialog1.ShowDialog(frmOwner);
if (res != DialogResult.OK)
return false;
newFileName = saveFileDialog1.FileName;
fileDlgInitDir = new FileInfo(newFileName).DirectoryName;
}
// Write the data
try
{
using (Stream stream = new FileStream(
newFileName, FileMode.Create, FileAccess.Write))
{
// Serialize object to text format
IFormatter formatter = new BinaryFormatter();
if (SaveEvent != null) // if caller subscribed to this event
{
SerializationEventArgs args = new SerializationEventArgs(
formatter, stream, newFileName);
// raise event
SaveEvent(this, args);
if (args.Error)
return false;
}
}
}
catch (ArgumentNullException ex) { return HandleSaveException(ex, newFileName); }
catch (ArgumentOutOfRangeException ex) { return HandleSaveException(ex, newFileName); }
catch (ArgumentException ex) { return HandleSaveException(ex, newFileName); }
catch (SecurityException ex) { return HandleSaveException(ex, newFileName); }
catch (FileNotFoundException ex) { return HandleSaveException(ex, newFileName); }
catch (DirectoryNotFoundException ex) { return HandleSaveException(ex, newFileName); }
catch (PathTooLongException ex) { return HandleSaveException(ex, newFileName); }
catch (IOException ex) { return HandleSaveException(ex, newFileName); }
// Clear the dirty bit, cache the new file name
// and the caption is set automatically
Dirty = false;
SetFileName(newFileName);
// Success
return true;
}
/// <summary>
/// Assosciate file type with this program in the Registry
/// </summary>
/// <param name="data"></param>
/// <returns>true - OK, false - failed</returns>
public bool RegisterFileType(
string fileExtension,
string progId,
string typeDisplayName)
{
try
{
string s = String.Format(CultureInfo.InvariantCulture, ".{0}", fileExtension);
// Register custom extension with the shell
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(s))
{
// Map custom extension to a ProgID
key.SetValue(null, progId);
}
// create ProgID key with display name
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(progId))
{
key.SetValue(null, typeDisplayName);
}
// register icon
using (RegistryKey key =
Registry.ClassesRoot.CreateSubKey(progId + @"\DefaultIcon"))
{
key.SetValue(null, Application.ExecutablePath + ",0");
}
// Register open command with the shell
string cmdkey = progId + @"\shell\open\command";
using (RegistryKey key =
Registry.ClassesRoot.CreateSubKey(cmdkey))
{
// Map ProgID to an Open action for the shell
key.SetValue(null, Application.ExecutablePath + " \"%1\"");
}
// Register application for "Open With" dialog
string appkey = "Applications\\" +
new FileInfo(Application.ExecutablePath).Name +
"\\shell";
using (RegistryKey key =
Registry.ClassesRoot.CreateSubKey(appkey))
{
key.SetValue("FriendlyCache", Application.ProductName);
}
}
catch (ArgumentNullException ex)
{
return HandleRegistryException(ex);
}
catch (SecurityException ex)
{
return HandleRegistryException(ex);
}
catch (ArgumentException ex)
{
return HandleRegistryException(ex);
}
catch (ObjectDisposedException ex)
{
return HandleRegistryException(ex);
}
catch (UnauthorizedAccessException ex)
{
return HandleRegistryException(ex);
}
return true;
}
#endregion
#region Other Functions
/// <summary>
/// Hanfle exception from RegisterFileType function
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private bool HandleRegistryException(Exception ex)
{
Trace.WriteLine("Registry operation failed: " + ex.Message);
return false;
}
/// <summary>
/// Save initial directory to the Registry
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(registryPath);
key.SetValue(registryValue, fileDlgInitDir);
}
/// <summary>
/// Set file name and change owner's caption
/// </summary>
/// <param name="fileName"></param>
private void SetFileName(string fileName)
{
this.fileName = fileName;
SetCaption();
}
/// <summary>
/// Set owner form caption
/// </summary>
private void SetCaption()
{
if (!updateTitle)
return;
frmOwner.Text = string.Format(
CultureInfo.InvariantCulture,
"{0} - {1}{2}",
Application.ProductName,
Empty(this.fileName) ? newDocName : Path.GetFileName(this.fileName),
this.dirty ? "*" : "");
}
/// <summary>
/// Handle exception in OpenDocument function
/// </summary>
/// <param name="ex"></param>
/// <param name="fileName"></param>
/// <returns></returns>
private bool HandleOpenException(Exception ex, string fileName)
{
MessageBox.Show(frmOwner,
"Open File operation failed. File name: " + fileName + "\n" +
"Reason: " + ex.Message,
Application.ProductName);
if (OpenEvent != null)
{
// report failure
OpenEvent(this, new OpenFileEventArgs(fileName, false));
}
return false;
}
/// <summary>
/// Handle exception in SaveDocument function
/// </summary>
/// <param name="ex"></param>
/// <param name="fileName"></param>
/// <returns></returns>
private bool HandleSaveException(Exception ex, string fileName)
{
MessageBox.Show(frmOwner,
"Save File operation failed. File name: " + fileName + "\n" +
"Reason: " + ex.Message,
Application.ProductName);
return false;
}
/// <summary>
/// Helper function - test if string is empty
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
static bool Empty(string s)
{
return s == null || s.Length == 0;
}
#endregion
}
#endregion
#region Delegates
public delegate void SaveEventHandler(object sender, SerializationEventArgs e);
public delegate void LoadEventHandler(object sender, SerializationEventArgs e);
public delegate void OpenFileEventHandler(object sender, OpenFileEventArgs e);
#endregion
#region Class SerializationEventArgs
/// <summary>
/// Serialization event arguments.
/// Used in events raised from DocManager class.
/// Class contains information required to load/save file.
/// </summary>
public class SerializationEventArgs : System.EventArgs
{
private IFormatter formatter;
private Stream stream;
private string fileName;
private bool errorFlag;
public SerializationEventArgs(IFormatter formatter, Stream stream,
string fileName)
{
this.formatter = formatter;
this.stream = stream;
this.fileName = fileName;
errorFlag = false;
}
public bool Error
{
get
{
return errorFlag;
}
set
{
errorFlag = value;
}
}
public IFormatter Formatter
{
get
{
return formatter;
}
}
public Stream SerializationStream
{
get
{
return stream;
}
}
public string FileName
{
get
{
return fileName;
}
}
}
#endregion
#region Class OpenFileEventArgs
/// <summary>
/// Open file event arguments.
/// Used in events raised from DocManager class.
/// Class contains name of file and result of Open operation.
/// </summary>
public class OpenFileEventArgs : System.EventArgs
{
private string fileName;
private bool success;
public OpenFileEventArgs(string fileName, bool success)
{
this.fileName = fileName;
this.success = success;
}
public string FileName
{
get
{
return fileName;
}
}
public bool Succeeded
{
get
{
return success;
}
}
}
#endregion
#region class DocManagerData
/// <summary>
/// Class used for DocManager class initialization
/// </summary>
public class DocManagerData
{
public DocManagerData()
{
frmOwner = null;
updateTitle = true;
newDocName = "Untitled";
fileDlgFilter = "All Files (*.*)|*.*";
registryPath = "Software\\Unknown";
}
private Form frmOwner;
private bool updateTitle;
private string newDocName;
private string fileDlgFilter;
private string registryPath;
public Form FormOwner
{
get
{
return frmOwner;
}
set
{
frmOwner = value;
}
}
public bool UpdateTitle
{
get
{
return updateTitle;
}
set
{
updateTitle = value;
}
}
public string NewDocName
{
get
{
return newDocName;
}
set
{
newDocName = value;
}
}
public string FileDialogFilter
{
get
{
return fileDlgFilter;
}
set
{
fileDlgFilter = value;
}
}
public string RegistryPath
{
get
{
return registryPath;
}
set
{
registryPath = value;
}
}
};
#endregion
}
#region Using
/*
Using:
1. Write class which implements program-specific tasks. This class keeps some data,
knows to draw itself in the form window, and implements ISerializable interface.
Example:
[Serializable]
public class MyTask : ISerializable
{
// class members
private int myData;
// ...
public MyTask()
{
// ...
}
public void Draw(Graphics g, Rectangle r)
{
// ...
}
// other functions
// ...
// Serialization
// This function is called when file is loaded
protected GraphicsList(SerializationInfo info, StreamingContext context)
{
myData = info.GetInt32("myData");
// ...
}
// Serialization
// This function is called when file is saved
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("myData", myData);
// ...
}
}
Add member of this class to the form:
private MyClass myClass;
2. Add the DocManager member to the owner form:
private DocManager docManager;
3. Add DocManager message handlers to the owner form:
private void docManager_ClearEvent(object sender, EventArgs e)
{
// DocManager executed New command
// Clear here myClass or create new empty instance:
myClass = new MyClass();
Refresh();
}
private void docManager_DocChangedEvent(object sender, EventArgs e)
{
// DocManager reports that document was changed (loaded from file)
Refresh();
}
private void docManager_OpenEvent(object sender, OpenFileEventArgs e)
{
// DocManager reports about successful/unsuccessful Open File operation
// For example:
if ( e.Succeeded )
// add e.FileName to MRU list
else
// remove e.FileName from MRU list
}
private void docManager_LoadEvent(object sender, SerializationEventArgs e)
{
// DocManager asks to load document from supplied stream
try
{
myClass = (MyClass)e.Formatter.Deserialize(e.SerializationStream);
}
catch ( catch possible exceptions here )
{
// report error
e.Error = true;
}
}
private void docManager_SaveEvent(object sender, SerializationEventArgs e)
{
// DocManager asks to save document to supplied stream
try
{
e.Formatter.Serialize(e.SerializationStream, myClass);
}
catch ( catch possible exceptions here )
{
// report error
e.Error = true;
}
}
4. Initialize docManager member in the form initialization code:
DocManagerData data = new DocManagerData();
data.FormOwner = this;
data.UpdateTitle = true;
data.FileDialogFilter = "MyProgram files (*.mpf)|*.mpf|All Files (*.*)|*.*";
data.NewDocName = "Untitled.mpf";
data.RegistryPath = "Software\\MyCompany\\MyProgram";
docManager = new DocManager(data);
docManager.SaveEvent += docManager_SaveEvent;
docManager.LoadEvent += docManager_LoadEvent;
docManager.OpenEvent += docManager_OpenEvent;
docManager.DocChangedEvent += docManager_DocChangedEvent;
docManager.ClearEvent += docManager_ClearEvent;
docManager.NewDocument();
// Optionally - register file type for Windows Shell
bool result = docManager.RegisterFileType("mpf", "mpffile", "MyProgram File");
5. Call docManager functions when necessary. For example:
// File is dropped into the window;
// Command line parameter is handled.
public void OpenDocument(string file)
{
docManager.OpenDocument(file);
}
// User Selected File - Open command.
private void CommandOpen()
{
docManager.OpenDocument("");
}
// User selected File - Save command
private void CommandSave()
{
docManager.SaveDocument(DocManager.SaveType.Save);
}
// User selected File - Save As command
private void CommandSaveAs()
{
docManager.SaveDocument(DocManager.SaveType.SaveAs);
}
// User selected File - New command
private void CommandNew()
{
docManager.NewDocument();
}
6. Optionally: test for unsaved data in the form Closing event:
private void MainForm_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if ( ! docManager.CloseDocument() )
e.Cancel = true;
}
7. Optionally: handle command-line parameters in the main function:
[STAThread]
static void Main(string[] args)
{
// Check command line
if( args.Length > 1 )
{
MessageBox.Show("Incorrect number of arguments. Usage: MyProgram.exe [file]", "MyProgram");
return;
}
// Load main form, taking command line into account
MainForm form = new MainForm();
if ( args.Length == 1 )
form.OpenDocument(args[0]); // OpenDocument calls docManager.OpenDocument
Application.Run(form);
}
*/
#endregion
// DragDropManager.cs
#region Using directives
using System;
using System.Windows.Forms;
using System.Diagnostics;
#endregion
namespace DocToolkit
{
/// <summary>
/// DragDropManager class allows to open files dropped from
/// Windows Explorer in Windows Form application.
///
/// Using:
/// 1) Write function which opens file selected from MRU:
///
/// private void dragDropManager_FileDroppedEvent(object sender, FileDroppedEventArgs e)
/// {
/// // open file(s) from e.FileArray:
/// // e.FileArray.GetValue(0).ToString() ...
/// }
///
/// 2) Add member of this class to the parent form:
///
/// private DragDropManager dragDropManager;
///
/// 3) Create class instance in parent form initialization code:
///
/// dragDropManager = new DragDropManager(this);
/// dragDropManager.FileDroppedEvent += dragDropManager_FileDroppedEvent;
///
/// </summary>
public class DragDropManager
{
private Form frmOwner; // reference to owner form
// Event raised when drops file(s) to the form
public event FileDroppedEventHandler FileDroppedEvent;
public DragDropManager(Form owner)
{
frmOwner = owner;
// ensure that parent form allows dropping
frmOwner.AllowDrop = true;
// subscribe to parent form's drag-drop events
frmOwner.DragEnter += OnDragEnter;
frmOwner.DragDrop += OnDragDrop;
}
/// <summary>
/// Handle parent form DragEnter event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnDragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
// If file is dragged, show cursor "Drop allowed"
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.None;
}
/// <summary>
/// Handle parent form DragDrop event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnDragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
// When file(s) are dragged from Explorer to the form, IDataObject
// contains array of file names. If one file is dragged,
// array contains one element.
Array a = (Array)e.Data.GetData(DataFormats.FileDrop);
if (a != null)
{
if (FileDroppedEvent != null)
{
// Raise event asynchronously.
// Explorer instance from which file is dropped is not responding
// all the time when DragDrop handler is active, so we need to return
// immidiately (especially if OpenFiles shows MessageBox).
FileDroppedEvent.BeginInvoke(this, new FileDroppedEventArgs(a), null, null);
frmOwner.Activate(); // in the case Explorer overlaps parent form
}
}
// NOTE: exception handling is not used here.
// Caller responsibility is to handle exceptions
// in the function invoked by FileDroppedEvent.
}
}
public delegate void FileDroppedEventHandler(object sender, FileDroppedEventArgs e);
public class FileDroppedEventArgs : System.EventArgs
{
private Array fileArray;
public FileDroppedEventArgs(Array array)
{
this.fileArray = array;
}
public Array FileArray
{
get
{
return fileArray;
}
}
}
}
// MruManager.cs
#region Using directives
using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Win32;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Security;
#endregion
namespace DocToolkit
{
using StringList = List<String>;
using StringEnumerator = IEnumerator<String>;
/// <summary>
/// MRU manager - manages Most Recently Used Files list
/// for Windows Form application.
/// </summary>
public class MruManager
{
#region Members
// Event raised when user selects file from MRU list
public event MruFileOpenEventHandler MruOpenEvent;
private Form ownerForm; // owner form
private ToolStripMenuItem menuItemMRU; // Recent Files menu item
private ToolStripMenuItem menuItemParent; // Recent Files menu item parent
private string registryPath; // Registry path to keep MRU list
private int maxNumberOfFiles = 10; // maximum number of files in MRU list
private int maxDisplayLength = 40; // maximum length of file name for display
private string currentDirectory; // current directory
private StringList mruList; // MRU list (file names)
private const string regEntryName = "file"; // entry name to keep MRU (file0, file1...)
#endregion
#region Windows API
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
private static extern bool PathCompactPathEx(
StringBuilder pszOut,
string pszPath,
int cchMax,
int reserved);
#endregion
#region Constructor
public MruManager()
{
mruList = new StringList();
}
#endregion
#region Public Properties
/// <summary>
/// Maximum length of displayed file name in menu (default is 40).
///
/// Set this property to change default value (optional).
/// </summary>
public int MaxDisplayNameLength
{
set
{
maxDisplayLength = value;
if (maxDisplayLength < 10)
maxDisplayLength = 10;
}
get
{
return maxDisplayLength;
}
}
/// <summary>
/// Maximum length of MRU list (default is 10).
///
/// Set this property to change default value (optional).
/// </summary>
public int MaxMruLength
{
set
{
maxNumberOfFiles = value;
if (maxNumberOfFiles < 1)
maxNumberOfFiles = 1;
if (mruList.Count > maxNumberOfFiles)
mruList.RemoveRange(maxNumberOfFiles - 1, mruList.Count - maxNumberOfFiles);
}
get
{
return maxNumberOfFiles;
}
}
/// <summary>
/// Set current directory.
///
/// Default value is program current directory which is set when
/// Initialize function is called.
///
/// Set this property to change default value (optional)
/// after call to Initialize.
/// </summary>
public string CurrentDir
{
set
{
currentDirectory = value;
}
get
{
return currentDirectory;
}
}
#endregion
#region Public Functions
/// <summary>
/// Initialization. Call this function in form Load handler.
/// </summary>
/// <param name="owner">Owner form</param>
/// <param name="mruItem">Recent Files menu item</param>
/// <param name="regPath">Registry Path to keep MRU list</param>
public void Initialize(Form owner, ToolStripMenuItem mruItem, ToolStripMenuItem mruItemParent, string regPath)
{
// keep reference to owner form
ownerForm = owner;
// keep reference to MRU menu item
menuItemMRU = mruItem;
// keep reference to MRU menu item parent
menuItemParent = mruItemParent;
// keep Registry path adding MRU key to it
registryPath = regPath;
if (registryPath.EndsWith("\\"))
registryPath += "MRU";
else
registryPath += "\\MRU";
// keep current directory in the time of initialization
currentDirectory = Directory.GetCurrentDirectory();
// subscribe to MRU parent Popup event
menuItemParent.DropDownOpening += new EventHandler(this.OnMRUParentPopup);
// subscribe to owner form Closing event
ownerForm.Closing += OnOwnerClosing;
// load MRU list from Registry
LoadMRU();
}
/// <summary>
/// Add file name to MRU list.
/// Call this function when file is opened successfully.
/// If file already exists in the list, it is moved to the first place.
/// </summary>
/// <param name="file">File Name</param>
public void Add(string file)
{
Remove(file);
// if array has maximum length, remove last element
if (mruList.Count == maxNumberOfFiles)
mruList.RemoveAt(maxNumberOfFiles - 1);
// add new file name to the start of array
mruList.Insert(0, file);
}
/// <summary>
/// Remove file name from MRU list.
/// Call this function when File - Open operation failed.
/// </summary>
/// <param name="file">File Name</param>
public void Remove(string file)
{
int i = 0;
StringEnumerator myEnumerator = mruList.GetEnumerator();
while (myEnumerator.MoveNext())
{
if ((string)myEnumerator.Current == file)
{
mruList.RemoveAt(i);
return;
}
i++;
}
}
#endregion
#region Event Handlers
/// <summary>
/// Update MRU list when MRU menu item parent is opened
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMRUParentPopup(object sender, EventArgs e)
{
// remove all childs
//if (menuItemMRU.IsParent)
//{
menuItemMRU.DropDownItems.Clear();
//}
// Disable menu item if MRU list is empty
if (mruList.Count == 0)
{
menuItemMRU.Enabled = false;
return;
}
// enable menu item and add child items
menuItemMRU.Enabled = true;
ToolStripMenuItem item;
StringEnumerator myEnumerator = mruList.GetEnumerator();
int i = 0;
while (myEnumerator.MoveNext())
{
item = new ToolStripMenuItem();
item.Text = GetDisplayName((string)myEnumerator.Current);
item.Tag = i++;
// subscribe to item's Click event
item.Click += OnMRUClicked;
menuItemMRU.DropDownItems.Add(item);
}
}
/// <summary>
/// MRU menu item is clicked - call owner's OpenMRUFile function
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMRUClicked(object sender, EventArgs e)
{
string s;
// cast sender object to MenuItem
ToolStripMenuItem item = (ToolStripMenuItem)sender;
if (item != null)
{
// Get file name from list using item index
s = (string)mruList[(int)item.Tag];
// Raise event to owner and pass file name.
// Owner should handle this event and open file.
if (s.Length > 0)
{
if (MruOpenEvent != null)
{
MruOpenEvent(this, new MruFileOpenEventArgs(s));
}
}
}
}
/// <summary>
/// Save MRU list in Registry when owner form is closing
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnOwnerClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
int i, n;
try
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(registryPath);
if (key != null)
{
n = mruList.Count;
for (i = 0; i < maxNumberOfFiles; i++)
{
key.DeleteValue(regEntryName +
i.ToString(CultureInfo.InvariantCulture), false);
}
for (i = 0; i < n; i++)
{
key.SetValue(regEntryName +
i.ToString(CultureInfo.InvariantCulture), mruList[i]);
}
}
}
catch (ArgumentNullException ex) { HandleWriteError(ex); }
catch (SecurityException ex) { HandleWriteError(ex); }
catch (ArgumentException ex) { HandleWriteError(ex); }
catch (ObjectDisposedException ex) { HandleWriteError(ex); }
catch (UnauthorizedAccessException ex) { HandleWriteError(ex); }
}
#endregion
#region Private Functions
/// <summary>
/// Load MRU list from Registry.
/// Called from Initialize.
/// </summary>
private void LoadMRU()
{
string sKey, s;
try
{
mruList.Clear();
RegistryKey key = Registry.CurrentUser.OpenSubKey(registryPath);
if (key != null)
{
for (int i = 0; i < maxNumberOfFiles; i++)
{
sKey = regEntryName + i.ToString(CultureInfo.InvariantCulture);
s = (string)key.GetValue(sKey, "");
if (s.Length == 0)
break;
mruList.Add(s);
}
}
}
catch (ArgumentNullException ex) { HandleReadError(ex); }
catch (SecurityException ex) { HandleReadError(ex); }
catch (ArgumentException ex) { HandleReadError(ex); }
catch (ObjectDisposedException ex) { HandleReadError(ex); }
catch (UnauthorizedAccessException ex) { HandleReadError(ex); }
}
/// <summary>
/// Handle error from OnOwnerClosing function
/// </summary>
/// <param name="ex"></param>
private void HandleReadError(Exception ex)
{
Trace.WriteLine("Loading MRU from Registry failed: " + ex.Message);
}
/// <summary>
/// Handle error from OnOwnerClosing function
/// </summary>
/// <param name="ex"></param>
private void HandleWriteError(Exception ex)
{
Trace.WriteLine("Saving MRU to Registry failed: " + ex.Message);
}
/// <summary>
/// Get display file name from full name.
/// </summary>
/// <param name="fullName">Full file name</param>
/// <returns>Short display name</returns>
private string GetDisplayName(string fullName)
{
// if file is in current directory, show only file name
FileInfo fileInfo = new FileInfo(fullName);
if (fileInfo.DirectoryName == currentDirectory)
return GetShortDisplayName(fileInfo.Name, maxDisplayLength);
return GetShortDisplayName(fullName, maxDisplayLength);
}
/// <summary>
/// Truncate a path to fit within a certain number of characters
/// by replacing path components with ellipses.
///
/// This solution is provided by CodeProject and GotDotNet C# expert
/// Richard Deeming.
///
/// </summary>
/// <param name="longName">Long file name</param>
/// <param name="maxLen">Maximum length</param>
/// <returns>Truncated file name</returns>
private string GetShortDisplayName(string longName, int maxLen)
{
StringBuilder pszOut = new StringBuilder(maxLen + maxLen + 2); // for safety
if (PathCompactPathEx(pszOut, longName, maxLen, 0))
{
return pszOut.ToString();
}
else
{
return longName;
}
}
#endregion
}
public delegate void MruFileOpenEventHandler(object sender, MruFileOpenEventArgs e);
public class MruFileOpenEventArgs : System.EventArgs
{
private string fileName;
public MruFileOpenEventArgs(string fileName)
{
this.fileName = fileName;
}
public string FileName
{
get
{
return fileName;
}
}
}
}
#region Using
/*******************************************************************************
NOTE: This class works with new Visual Studio 2005 MenuStrip class.
If owner form has old-style MainMenu, use previous MruManager version.
Using:
1) Add menu item Recent Files (or any name you want) to main application menu.
This item is used by MruManager as popup menu for MRU list.
2) Write function which opens file selected from MRU:
private void mruManager_MruOpenEvent(object sender, MruFileOpenEventArgs e)
{
// open file e.FileName
}
3) Add MruManager member to the form class and initialize it:
private MruManager mruManager;
Initialize it in the form initialization code:
mruManager = new MruManager();
mruManager.Initialize(
this, // owner form
mnuFileMRU, // Recent Files menu item (ToolStripMenuItem class)
mruItemParent, // Recent Files menu item parent (ToolStripMenuItem class)
"Software\\MyCompany\\MyProgram"); // Registry path to keep MRU list
mruManager.MruOpenEvent += this.mruManager_MruOpenEvent;
// Optional. Call these functions to change default values:
mruManager.CurrentDir = "....."; // default is current directory
mruManager.MaxMruLength = ...; // default is 10
mruMamager.MaxDisplayNameLength = ...; // default is 40
NOTES:
- If Registry path is, for example, "Software\MyCompany\MyProgram",
MRU list is kept in
HKEY_CURRENT_USER\Software\MyCompany\MyProgram\MRU Registry entry.
- CurrentDir is used to show file names in the menu. If file is in
this directory, only file name is shown.
4) Call MruManager Add and Remove functions when necessary:
mruManager.Add(fileName); // when file is successfully opened
mruManager.Remove(fileName); // when Open File operation failed
*******************************************************************************/
// Implementation details:
//
// MruManager loads MRU list from Registry in Initialize function.
// List is saved in Registry when owner form is closed.
//
// MRU list in the menu is updated when parent menu is poped-up.
//
// Owner form OnMRUFileOpen function is called when user selects file
// from MRU list.
#endregion
// PersistWindowState.cs
#region Using directives
using System;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Drawing;
#endregion
namespace DocToolkit
{
/// <summary>
/// Class allows to keep last window state in Registry
/// and restore it when form is loaded.
///
/// Source: Saving and Restoring the Location, Size and
/// Windows State of a .NET Form
/// By Joel Matthias
///
/// Downloaded from http://www.codeproject.com
///
/// Using:
/// 1. Add class member to the owner form:
///
/// private PersistWindowState persistState;
///
/// 2. Create it in the form constructor:
///
/// persistState = new PersistWindowState("Software\\MyCompany\\MyProgram", this);
///
/// </summary>
public class PersistWindowState
{
#region Members
private Form ownerForm; // reference to owner form
private string registryPath; // path in Registry where state information is kept
// Form state parameters:
private int normalLeft;
private int normalTop;
private int normalWidth;
private int normalHeight;
// FormWindowState is enumeration from System.Windows.Forms Namespace
// Contains 3 members: Maximized, Minimized and Normal.
private FormWindowState windowState = FormWindowState.Normal;
// if allowSaveMinimized is true, form closed in minimal state
// is loaded next time in minimal state.
private bool allowSaveMinimized = false;
#endregion
#region Constructor
/// <summary>
/// Initialization
/// </summary>
/// <param name="sRegPath"></param>
/// <param name="owner"></param>
public PersistWindowState(string path, Form owner)
{
if (path == null ||
path.Length == 0)
{
registryPath = "Software\\Unknown";
}
else
{
registryPath = path;
}
if (!registryPath.EndsWith("\\"))
registryPath += "\\";
registryPath += "MainForm";
ownerForm = owner;
// subscribe to parent form's events
ownerForm.Closing += OnClosing;
ownerForm.Resize += OnResize;
ownerForm.Move += OnMove;
ownerForm.Load += OnLoad;
// get initial width and height in case form is never resized
normalWidth = ownerForm.Width;
normalHeight = ownerForm.Height;
}
#endregion
#region Properties
/// <summary>
/// AllowSaveMinimized property (default value false)
/// </summary>
public bool AllowSaveMinimized
{
get
{
return allowSaveMinimized;
}
set
{
allowSaveMinimized = value;
}
}
#endregion
#region Event Handlers
/// <summary>
/// Parent form is resized.
/// Keep current size.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnResize(object sender, System.EventArgs e)
{
// save width and height
if (ownerForm.WindowState == FormWindowState.Normal)
{
normalWidth = ownerForm.Width;
normalHeight = ownerForm.Height;
}
}
/// <summary>
/// Parent form is moved.
/// Keep current window position.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMove(object sender, System.EventArgs e)
{
// save position
if (ownerForm.WindowState == FormWindowState.Normal)
{
normalLeft = ownerForm.Left;
normalTop = ownerForm.Top;
}
// save state
windowState = ownerForm.WindowState;
}
/// <summary>
/// Parent form is closing.
/// Keep last state in Registry.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
// save position, size and state
RegistryKey key = Registry.CurrentUser.CreateSubKey(registryPath);
key.SetValue("Left", normalLeft);
key.SetValue("Top", normalTop);
key.SetValue("Width", normalWidth);
key.SetValue("Height", normalHeight);
// check if we are allowed to save the state as minimized (not normally)
if (!allowSaveMinimized)
{
if (windowState == FormWindowState.Minimized)
windowState = FormWindowState.Normal;
}
key.SetValue("WindowState", (int)windowState);
}
/// <summary>
/// Parent form is loaded.
/// Read last state from Registry and set it to form.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnLoad(object sender, System.EventArgs e)
{
// attempt to read state from registry
RegistryKey key = Registry.CurrentUser.OpenSubKey(registryPath);
if (key != null)
{
int left = (int)key.GetValue("Left", ownerForm.Left);
int top = (int)key.GetValue("Top", ownerForm.Top);
int width = (int)key.GetValue("Width", ownerForm.Width);
int height = (int)key.GetValue("Height", ownerForm.Height);
FormWindowState windowState = (FormWindowState)key.GetValue("WindowState", (int)ownerForm.WindowState);
ownerForm.Location = new Point(left, top);
ownerForm.Size = new Size(width, height);
ownerForm.WindowState = windowState;
}
}
#endregion
}
}
// Project -- DrawTools
// DrawObject.cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
namespace DrawTools
{
/// <summary>
/// Base class for all draw objects
/// </summary>
abstract class DrawObject
{
#region Members
// Object properties
private bool selected;
private Color color;
private int penWidth;
// Allows to write Undo - Redo functions and don't care about
// objects order in the list.
int id;
// Last used property values (may be kept in the Registry)
private static Color lastUsedColor = Color.Black;
private static int lastUsedPenWidth = 1;
// Entry names for serialization
private const string entryColor = "Color";
private const string entryPenWidth = "PenWidth";
#endregion
public DrawObject()
{
id = this.GetHashCode();
}
#region Properties
/// <summary>
/// Selection flag
/// </summary>
public bool Selected
{
get
{
return selected;
}
set
{
selected = value;
}
}
/// <summary>
/// Color
/// </summary>
public Color Color
{
get
{
return color;
}
set
{
color = value;
}
}
/// <summary>
/// Pen width
/// </summary>
public int PenWidth
{
get
{
return penWidth;
}
set
{
penWidth = value;
}
}
/// <summary>
/// Number of handles
/// </summary>
public virtual int HandleCount
{
get
{
return 0;
}
}
/// <summary>
/// Object ID
/// </summary>
public int ID
{
get { return id; }
set { id = value; }
}
/// <summary>
/// Last used color
/// </summary>
public static Color LastUsedColor
{
get
{
return lastUsedColor;
}
set
{
lastUsedColor = value;
}
}
/// <summary>
/// Last used pen width
/// </summary>
public static int LastUsedPenWidth
{
get
{
return lastUsedPenWidth;
}
set
{
lastUsedPenWidth = value;
}
}
#endregion
#region Virtual Functions
/// <summary>
/// Clone this instance.
/// </summary>
public abstract DrawObject Clone();
/// <summary>
/// Draw object
/// </summary>
/// <param name="g"></param>
public virtual void Draw(Graphics g)
{
}
/// <summary>
/// Get handle point by 1-based number
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public virtual Point GetHandle(int handleNumber)
{
return new Point(0, 0);
}
/// <summary>
/// Get handle rectangle by 1-based number
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public virtual Rectangle GetHandleRectangle(int handleNumber)
{
Point point = GetHandle(handleNumber);
return new Rectangle(point.X - 3, point.Y - 3, 7, 7);
}
/// <summary>
/// Draw tracker for selected object
/// </summary>
/// <param name="g"></param>
public virtual void DrawTracker(Graphics g)
{
if ( ! Selected )
return;
SolidBrush brush = new SolidBrush(Color.Black);
for ( int i = 1; i <= HandleCount; i++ )
{
g.FillRectangle(brush, GetHandleRectangle(i));
}
brush.Dispose();
}
/// <summary>
/// Hit test.
/// Return value: -1 - no hit
/// 0 - hit anywhere
/// > 1 - handle number
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public virtual int HitTest(Point point)
{
return -1;
}
/// <summary>
/// Test whether point is inside of the object
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
protected virtual bool PointInObject(Point point)
{
return false;
}
/// <summary>
/// Get cursor for the handle
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public virtual Cursor GetHandleCursor(int handleNumber)
{
return Cursors.Default;
}
/// <summary>
/// Test whether object intersects with rectangle
/// </summary>
/// <param name="rectangle"></param>
/// <returns></returns>
public virtual bool IntersectsWith(Rectangle rectangle)
{
return false;
}
/// <summary>
/// Move object
/// </summary>
/// <param name="deltaX"></param>
/// <param name="deltaY"></param>
public virtual void Move(int deltaX, int deltaY)
{
}
/// <summary>
/// Move handle to the point
/// </summary>
/// <param name="point"></param>
/// <param name="handleNumber"></param>
public virtual void MoveHandleTo(Point point, int handleNumber)
{
}
/// <summary>
/// Dump (for debugging)
/// </summary>
public virtual void Dump()
{
Trace.WriteLine(this.GetType().Name);
Trace.WriteLine("Selected = " +
selected.ToString(CultureInfo.InvariantCulture)
+ " ID = " + id.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Normalize object.
/// Call this function in the end of object resizing.
/// </summary>
public virtual void Normalize()
{
}
/// <summary>
/// Save object to serialization stream
/// </summary>
/// <param name="info"></param>
/// <param name="orderNumber"></param>
public virtual void SaveToStream(SerializationInfo info, int orderNumber)
{
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryColor, orderNumber),
Color.ToArgb());
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryPenWidth, orderNumber),
PenWidth);
}
/// <summary>
/// Load object from serialization stream
/// </summary>
/// <param name="info"></param>
/// <param name="orderNumber"></param>
public virtual void LoadFromStream(SerializationInfo info, int orderNumber)
{
int n = info.GetInt32(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryColor, orderNumber));
Color = Color.FromArgb(n);
PenWidth = info.GetInt32(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryPenWidth, orderNumber));
id = this.GetHashCode();
}
#endregion
#region Other functions
/// <summary>
/// Initialization
/// </summary>
protected void Initialize()
{
color = lastUsedColor;
penWidth = LastUsedPenWidth;
}
/// <summary>
/// Copy fields from this instance to cloned instance drawObject.
/// Called from Clone functions of derived classes.
/// </summary>
protected void FillDrawObjectFields(DrawObject drawObject)
{
drawObject.selected = this.selected;
drawObject.color = this.color;
drawObject.penWidth = this.penWidth;
drawObject.ID = this.ID;
}
#endregion
}
}
// DrawEllipse.cs
using System;
using System.Windows.Forms;
using System.Drawing;
namespace DrawTools
{
/// <summary>
/// Ellipse graphic object
/// </summary>
class DrawEllipse : DrawTools.DrawRectangle
{
public DrawEllipse() : this(0, 0, 1, 1)
{
}
public DrawEllipse(int x, int y, int width, int height) : base()
{
Rectangle = new Rectangle(x, y, width, height);
Initialize();
}
/// <summary>
/// Clone this instance
/// </summary>
public override DrawObject Clone()
{
DrawEllipse drawEllipse = new DrawEllipse();
drawEllipse.Rectangle = this.Rectangle;
FillDrawObjectFields(drawEllipse);
return drawEllipse;
}
public override void Draw(Graphics g)
{
Pen pen = new Pen(Color, PenWidth);
g.DrawEllipse(pen, DrawRectangle.GetNormalizedRectangle(Rectangle));
pen.Dispose();
}
}
}
// DrawLine.cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.Globalization;
using System.Drawing.Drawing2D;
using System.Runtime.Serialization;
namespace DrawTools
{
/// <summary>
/// Line graphic object
/// </summary>
class DrawLine : DrawTools.DrawObject
{
private Point startPoint;
private Point endPoint;
private const string entryStart = "Start";
private const string entryEnd = "End";
/// <summary>
/// Graphic objects for hit test
/// </summary>
private GraphicsPath areaPath = null;
private Pen areaPen = null;
private Region areaRegion = null;
public DrawLine() : this(0, 0, 1, 0)
{
}
public DrawLine(int x1, int y1, int x2, int y2) : base()
{
startPoint.X = x1;
startPoint.Y = y1;
endPoint.X = x2;
endPoint.Y = y2;
Initialize();
}
/// <summary>
/// Clone this instance
/// </summary>
public override DrawObject Clone()
{
DrawLine drawLine = new DrawLine();
drawLine.startPoint = this.startPoint;
drawLine.endPoint = this.endPoint;
FillDrawObjectFields(drawLine);
return drawLine;
}
public override void Draw(Graphics g)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
Pen pen = new Pen(Color, PenWidth);
g.DrawLine(pen, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
pen.Dispose();
}
public override int HandleCount
{
get
{
return 2;
}
}
/// <summary>
/// Get handle point by 1-based number
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public override Point GetHandle(int handleNumber)
{
if ( handleNumber == 1 )
return startPoint;
else
return endPoint;
}
/// <summary>
/// Hit test.
/// Return value: -1 - no hit
/// 0 - hit anywhere
/// > 1 - handle number
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public override int HitTest(Point point)
{
if ( Selected )
{
for ( int i = 1; i <= HandleCount; i++ )
{
if ( GetHandleRectangle(i).Contains(point) )
return i;
}
}
if ( PointInObject(point) )
return 0;
return -1;
}
protected override bool PointInObject(Point point)
{
CreateObjects();
return AreaRegion.IsVisible(point);
}
public override bool IntersectsWith(Rectangle rectangle)
{
CreateObjects();
return AreaRegion.IsVisible(rectangle);
}
public override Cursor GetHandleCursor(int handleNumber)
{
switch ( handleNumber )
{
case 1:
case 2:
return Cursors.SizeAll;
default:
return Cursors.Default;
}
}
public override void MoveHandleTo(Point point, int handleNumber)
{
if ( handleNumber == 1 )
startPoint = point;
else
endPoint = point;
Invalidate();
}
public override void Move(int deltaX, int deltaY)
{
startPoint.X += deltaX;
startPoint.Y += deltaY;
endPoint.X += deltaX;
endPoint.Y += deltaY;
Invalidate();
}
public override void SaveToStream(System.Runtime.Serialization.SerializationInfo info, int orderNumber)
{
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryStart, orderNumber),
startPoint);
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryEnd, orderNumber),
endPoint);
base.SaveToStream (info, orderNumber);
}
public override void LoadFromStream(SerializationInfo info, int orderNumber)
{
startPoint = (Point)info.GetValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryStart, orderNumber),
typeof(Point));
endPoint = (Point)info.GetValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryEnd, orderNumber),
typeof(Point));
base.LoadFromStream (info, orderNumber);
}
/// <summary>
/// Invalidate object.
/// When object is invalidated, path used for hit test
/// is released and should be created again.
/// </summary>
protected void Invalidate()
{
if ( AreaPath != null )
{
AreaPath.Dispose();
AreaPath = null;
}
if ( AreaPen != null )
{
AreaPen.Dispose();
AreaPen = null;
}
if ( AreaRegion != null )
{
AreaRegion.Dispose();
AreaRegion = null;
}
}
/// <summary>
/// Create graphic objects used from hit test.
/// </summary>
protected virtual void CreateObjects()
{
if ( AreaPath != null )
return;
// Create path which contains wide line
// for easy mouse selection
AreaPath = new GraphicsPath();
AreaPen = new Pen(Color.Black, 7);
AreaPath.AddLine(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
AreaPath.Widen(AreaPen);
// Create region from the path
AreaRegion = new Region(AreaPath);
}
protected GraphicsPath AreaPath
{
get
{
return areaPath;
}
set
{
areaPath = value;
}
}
protected Pen AreaPen
{
get
{
return areaPen;
}
set
{
areaPen = value;
}
}
protected Region AreaRegion
{
get
{
return areaRegion;
}
set
{
areaRegion = value;
}
}
}
}
// DrawPolygon.cs
#region Using directives
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.Globalization;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
#endregion
namespace DrawTools
{
using PointList = List<Point>;
using PointEnumerator = IEnumerator<Point>;
/// <summary>
/// Polygon graphic object
/// </summary>
class DrawPolygon : DrawTools.DrawLine
{
private PointList pointArray; // list of points
private static Cursor handleCursor = new Cursor(typeof(DrawPolygon), "PolyHandle.cur");
private const string entryLength = "Length";
private const string entryPoint = "Point";
public DrawPolygon() : base()
{
pointArray = new PointList();
Initialize();
}
public DrawPolygon(int x1, int y1, int x2, int y2) : base()
{
pointArray = new PointList();
pointArray.Add(new Point(x1, y1));
pointArray.Add(new Point(x2, y2));
Initialize();
}
/// <summary>
/// Clone this instance
/// </summary>
public override DrawObject Clone()
{
DrawPolygon drawPolygon = new DrawPolygon();
foreach(Point p in this.pointArray)
{
drawPolygon.pointArray.Add(p);
}
FillDrawObjectFields(drawPolygon);
return drawPolygon;
}
public override void Draw(Graphics g)
{
int x1 = 0, y1 = 0; // previous point
int x2, y2; // current point
g.SmoothingMode = SmoothingMode.AntiAlias;
Pen pen = new Pen(Color, PenWidth);
PointEnumerator enumerator = pointArray.GetEnumerator();
if (enumerator.MoveNext())
{
x1 = ((Point)enumerator.Current).X;
y1 = ((Point)enumerator.Current).Y;
}
while (enumerator.MoveNext())
{
x2 = ((Point)enumerator.Current).X;
y2 = ((Point)enumerator.Current).Y;
g.DrawLine(pen, x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
pen.Dispose();
}
public void AddPoint(Point point)
{
pointArray.Add(point);
}
public override int HandleCount
{
get
{
return pointArray.Count;
}
}
/// <summary>
/// Get handle point by 1-based number
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public override Point GetHandle(int handleNumber)
{
if (handleNumber < 1)
handleNumber = 1;
if (handleNumber > pointArray.Count)
handleNumber = pointArray.Count;
return ((Point)pointArray[handleNumber - 1]);
}
public override Cursor GetHandleCursor(int handleNumber)
{
return handleCursor;
}
public override void MoveHandleTo(Point point, int handleNumber)
{
if (handleNumber < 1)
handleNumber = 1;
if (handleNumber > pointArray.Count)
handleNumber = pointArray.Count;
pointArray[handleNumber - 1] = point;
Invalidate();
}
public override void Move(int deltaX, int deltaY)
{
int n = pointArray.Count;
Point point;
for (int i = 0; i < n; i++)
{
point = new Point(((Point)pointArray[i]).X + deltaX, ((Point)pointArray[i]).Y + deltaY);
pointArray[i] = point;
}
Invalidate();
}
public override void SaveToStream(System.Runtime.Serialization.SerializationInfo info, int orderNumber)
{
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryLength, orderNumber),
pointArray.Count);
int i = 0;
foreach (Point p in pointArray)
{
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}-{2}",
entryPoint, orderNumber, i++),
p);
}
base.SaveToStream(info, orderNumber); // ??
}
public override void LoadFromStream(System.Runtime.Serialization.SerializationInfo info, int orderNumber)
{
Point point;
int n = info.GetInt32(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryLength, orderNumber));
for (int i = 0; i < n; i++)
{
point = (Point)info.GetValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}-{2}",
entryPoint, orderNumber, i),
typeof(Point));
pointArray.Add(point);
}
base.LoadFromStream(info, orderNumber);
}
/// <summary>
/// Create graphic object used for hit test
/// </summary>
protected override void CreateObjects()
{
if (AreaPath != null)
return;
// Create closed path which contains all polygon vertexes
AreaPath = new GraphicsPath();
int x1 = 0, y1 = 0; // previous point
int x2, y2; // current point
PointEnumerator enumerator = pointArray.GetEnumerator();
if (enumerator.MoveNext())
{
x1 = ((Point)enumerator.Current).X;
y1 = ((Point)enumerator.Current).Y;
}
while (enumerator.MoveNext())
{
x2 = ((Point)enumerator.Current).X;
y2 = ((Point)enumerator.Current).Y;
AreaPath.AddLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
AreaPath.CloseFigure();
// Create region from the path
AreaRegion = new Region(AreaPath);
}
}
}
// DrawRectangle.cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
namespace DrawTools
{
/// <summary>
/// Rectangle graphic object
/// </summary>
class DrawRectangle : DrawTools.DrawObject
{
private Rectangle rectangle;
private const string entryRectangle = "Rect";
protected Rectangle Rectangle
{
get
{
return rectangle;
}
set
{
rectangle = value;
}
}
public DrawRectangle() : this(0, 0, 1, 1)
{
}
public DrawRectangle(int x, int y, int width, int height) : base()
{
rectangle.X = x;
rectangle.Y = y;
rectangle.Width = width;
rectangle.Height = height;
Initialize();
}
/// <summary>
/// Clone this instance
/// </summary>
public override DrawObject Clone()
{
DrawRectangle drawRectangle = new DrawRectangle();
drawRectangle.rectangle = this.rectangle;
FillDrawObjectFields(drawRectangle);
return drawRectangle;
}
/// <summary>
/// Draw rectangle
/// </summary>
/// <param name="g"></param>
public override void Draw(Graphics g)
{
Pen pen = new Pen(Color, PenWidth);
g.DrawRectangle(pen, DrawRectangle.GetNormalizedRectangle(Rectangle));
pen.Dispose();
}
protected void SetRectangle(int x, int y, int width, int height)
{
rectangle.X = x;
rectangle.Y = y;
rectangle.Width = width;
rectangle.Height = height;
}
/// <summary>
/// Get number of handles
/// </summary>
public override int HandleCount
{
get
{
return 8;
}
}
/// <summary>
/// Get handle point by 1-based number
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public override Point GetHandle(int handleNumber)
{
int x, y, xCenter, yCenter;
xCenter = rectangle.X + rectangle.Width/2;
yCenter = rectangle.Y + rectangle.Height/2;
x = rectangle.X;
y = rectangle.Y;
switch ( handleNumber )
{
case 1:
x = rectangle.X;
y = rectangle.Y;
break;
case 2:
x = xCenter;
y = rectangle.Y;
break;
case 3:
x = rectangle.Right;
y = rectangle.Y;
break;
case 4:
x = rectangle.Right;
y = yCenter;
break;
case 5:
x = rectangle.Right;
y = rectangle.Bottom;
break;
case 6:
x = xCenter;
y = rectangle.Bottom;
break;
case 7:
x = rectangle.X;
y = rectangle.Bottom;
break;
case 8:
x = rectangle.X;
y = yCenter;
break;
}
return new Point(x, y);
}
/// <summary>
/// Hit test.
/// Return value: -1 - no hit
/// 0 - hit anywhere
/// > 1 - handle number
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public override int HitTest(Point point)
{
if ( Selected )
{
for ( int i = 1; i <= HandleCount; i++ )
{
if ( GetHandleRectangle(i).Contains(point) )
return i;
}
}
if ( PointInObject(point) )
return 0;
return -1;
}
protected override bool PointInObject(Point point)
{
return rectangle.Contains(point);
}
/// <summary>
/// Get cursor for the handle
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public override Cursor GetHandleCursor(int handleNumber)
{
switch ( handleNumber )
{
case 1:
return Cursors.SizeNWSE;
case 2:
return Cursors.SizeNS;
case 3:
return Cursors.SizeNESW;
case 4:
return Cursors.SizeWE;
case 5:
return Cursors.SizeNWSE;
case 6:
return Cursors.SizeNS;
case 7:
return Cursors.SizeNESW;
case 8:
return Cursors.SizeWE;
default:
return Cursors.Default;
}
}
/// <summary>
/// Move handle to new point (resizing)
/// </summary>
/// <param name="point"></param>
/// <param name="handleNumber"></param>
public override void MoveHandleTo(Point point, int handleNumber)
{
int left = Rectangle.Left;
int top = Rectangle.Top;
int right = Rectangle.Right;
int bottom = Rectangle.Bottom;
switch ( handleNumber )
{
case 1:
left = point.X;
top = point.Y;
break;
case 2:
top = point.Y;
break;
case 3:
right = point.X;
top = point.Y;
break;
case 4:
right = point.X;
break;
case 5:
right = point.X;
bottom = point.Y;
break;
case 6:
bottom = point.Y;
break;
case 7:
left = point.X;
bottom = point.Y;
break;
case 8:
left = point.X;
break;
}
SetRectangle(left, top, right - left, bottom - top);
}
public override bool IntersectsWith(Rectangle rectangle)
{
return Rectangle.IntersectsWith(rectangle);
}
/// <summary>
/// Move object
/// </summary>
/// <param name="deltaX"></param>
/// <param name="deltaY"></param>
public override void Move(int deltaX, int deltaY)
{
rectangle.X += deltaX;
rectangle.Y += deltaY;
}
public override void Dump()
{
base.Dump ();
Trace.WriteLine("rectangle.X = " + rectangle.X.ToString(CultureInfo.InvariantCulture));
Trace.WriteLine("rectangle.Y = " + rectangle.Y.ToString(CultureInfo.InvariantCulture));
Trace.WriteLine("rectangle.Width = " + rectangle.Width.ToString(CultureInfo.InvariantCulture));
Trace.WriteLine("rectangle.Height = " + rectangle.Height.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Normalize rectangle
/// </summary>
public override void Normalize()
{
rectangle = DrawRectangle.GetNormalizedRectangle(rectangle);
}
/// <summary>
/// Save objevt to serialization stream
/// </summary>
/// <param name="info"></param>
/// <param name="orderNumber"></param>
public override void SaveToStream(System.Runtime.Serialization.SerializationInfo info, int orderNumber)
{
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryRectangle, orderNumber),
rectangle);
base.SaveToStream (info, orderNumber);
}
/// <summary>
/// LOad object from serialization stream
/// </summary>
/// <param name="info"></param>
/// <param name="orderNumber"></param>
public override void LoadFromStream(SerializationInfo info, int orderNumber)
{
rectangle = (Rectangle)info.GetValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryRectangle, orderNumber),
typeof(Rectangle));
base.LoadFromStream (info, orderNumber);
}
#region Helper Functions
public static Rectangle GetNormalizedRectangle(int x1, int y1, int x2, int y2)
{
if ( x2 < x1 )
{
int tmp = x2;
x2 = x1;
x1 = tmp;
}
if ( y2 < y1 )
{
int tmp = y2;
y2 = y1;
y1 = tmp;
}
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
public static Rectangle GetNormalizedRectangle(Point p1, Point p2)
{
return GetNormalizedRectangle(p1.X, p1.Y, p2.X, p2.Y);
}
public static Rectangle GetNormalizedRectangle(Rectangle r)
{
return GetNormalizedRectangle(r.X, r.Y, r.X + r.Width, r.Y + r.Height);
}
#endregion
}
}
..................
To be continue...
浙公网安备 33010602011771号