using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; using System.Windows; using System.Windows.Controls; using System.Xml; using Microsoft.Win32; namespace YALV.Common { /// /// Refs: http://www.codeproject.com/Articles/23731/RecentFileList-a-WPF-MRU /// public class RecentFileList : Separator { public interface IPersist { List RecentFiles(int max); void InsertFile(string filepath, int max); void RemoveFile(string filepath, int max); } public IPersist Persister { get; set; } public void UseRegistryPersister() { Persister = new RegistryPersister(); } public void UseRegistryPersister(string key) { Persister = new RegistryPersister(key); } public void UseXmlPersister() { Persister = new XmlPersister(); } public void UseXmlPersister(string filepath) { Persister = new XmlPersister(filepath); } public void UseXmlPersister(Stream stream) { Persister = new XmlPersister(stream); } public int MaxNumberOfFiles { get; set; } public int MaxPathLength { get; set; } public MenuItem FileMenu { get; private set; } /// /// Used in: String.Format( MenuItemFormat, index, filepath, displayPath ); /// Default = "_{0}: {2}" /// public string MenuItemFormatOneToNine { get; set; } /// /// Used in: String.Format( MenuItemFormat, index, filepath, displayPath ); /// Default = "{0}: {2}" /// public string MenuItemFormatTenPlus { get; set; } public delegate string GetMenuItemTextDelegate(int index, string filepath); public GetMenuItemTextDelegate GetMenuItemTextHandler { get; set; } public event EventHandler MenuClick; Separator _Separator = null; List _RecentFiles = null; public RecentFileList() { Persister = new RegistryPersister(); MaxNumberOfFiles = 10; MaxPathLength = 150; MenuItemFormatOneToNine = "_{0}: {2}"; MenuItemFormatTenPlus = "{0}: {2}"; this.Loaded += (s, e) => HookFileMenu(); } void HookFileMenu() { MenuItem parent = Parent as MenuItem; if (parent == null) throw new ApplicationException("Parent must be a MenuItem"); if (FileMenu == parent) return; if (FileMenu != null) FileMenu.SubmenuOpened -= _FileMenu_SubmenuOpened; FileMenu = parent; FileMenu.SubmenuOpened += _FileMenu_SubmenuOpened; } public List RecentFiles { get { return Persister.RecentFiles(MaxNumberOfFiles); } } public void RemoveFile(string filepath) { Persister.RemoveFile(filepath, MaxNumberOfFiles); } public void InsertFile(string filepath) { Persister.InsertFile(filepath, MaxNumberOfFiles); } void _FileMenu_SubmenuOpened(object sender, RoutedEventArgs e) { SetMenuItems(); } void SetMenuItems() { RemoveMenuItems(); LoadRecentFiles(); InsertMenuItems(); } void RemoveMenuItems() { if (_Separator != null) FileMenu.Items.Remove(_Separator); if (_RecentFiles != null) foreach (RecentFile r in _RecentFiles) if (r.MenuItem != null) FileMenu.Items.Remove(r.MenuItem); _Separator = null; _RecentFiles = null; } void InsertMenuItems() { if (_RecentFiles == null) return; if (_RecentFiles.Count == 0) return; int iMenuItem = FileMenu.Items.IndexOf(this); foreach (RecentFile r in _RecentFiles) { string header = GetMenuItemText(r.Number + 1, r.Filepath, r.DisplayPath); r.MenuItem = new MenuItem { Header = header }; r.MenuItem.Click += MenuItem_Click; FileMenu.Items.Insert(++iMenuItem, r.MenuItem); } _Separator = new Separator(); FileMenu.Items.Insert(++iMenuItem, _Separator); } string GetMenuItemText(int index, string filepath, string displaypath) { GetMenuItemTextDelegate delegateGetMenuItemText = GetMenuItemTextHandler; if (delegateGetMenuItemText != null) return delegateGetMenuItemText(index, filepath); string format = (index < 10 ? MenuItemFormatOneToNine : MenuItemFormatTenPlus); string shortPath = ShortenPathname(displaypath, MaxPathLength); return String.Format(format, index, filepath, shortPath); } // This method is taken from Joe Woodbury's article at: http://www.codeproject.com/KB/cs/mrutoolstripmenu.aspx /// /// Shortens a pathname for display purposes. /// /// The pathname to shorten. /// The maximum number of characters to be displayed. /// Shortens a pathname by either removing consecutive components of a path /// and/or by removing characters from the end of the filename and replacing /// then with three elipses (...) /// In all cases, the root of the passed path will be preserved in it's entirety. /// If a UNC path is used or the pathname and maxLength are particularly short, /// the resulting path may be longer than maxLength. /// This method expects fully resolved pathnames to be passed to it. /// (Use Path.GetFullPath() to obtain this.) /// /// static public string ShortenPathname(string pathname, int maxLength) { if (pathname.Length <= maxLength) return pathname; string root = Path.GetPathRoot(pathname); if (root.Length > 3) root += Path.DirectorySeparatorChar; string[] elements = pathname.Substring(root.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); int filenameIndex = elements.GetLength(0) - 1; if (elements.GetLength(0) == 1) // pathname is just a root and filename { if (elements[0].Length > 5) // long enough to shorten { // if path is a UNC path, root may be rather long if (root.Length + 6 >= maxLength) { return root + elements[0].Substring(0, 3) + "..."; } else { return pathname.Substring(0, maxLength - 3) + "..."; } } } else if ((root.Length + 4 + elements[filenameIndex].Length) > maxLength) // pathname is just a root and filename { root += "...\\"; int len = elements[filenameIndex].Length; if (len < 6) return root + elements[filenameIndex]; if ((root.Length + 6) >= maxLength) { len = 3; } else { len = maxLength - root.Length - 3; } return root + elements[filenameIndex].Substring(0, len) + "..."; } else if (elements.GetLength(0) == 2) { return root + "...\\" + elements[1]; } else { int len = 0; int begin = 0; for (int i = 0; i < filenameIndex; i++) { if (elements[i].Length > len) { begin = i; len = elements[i].Length; } } int totalLength = pathname.Length - len + 3; int end = begin + 1; while (totalLength > maxLength) { if (begin > 0) totalLength -= elements[--begin].Length - 1; if (totalLength <= maxLength) break; if (end < filenameIndex) totalLength -= elements[++end].Length - 1; if (begin == 0 && end == filenameIndex) break; } // assemble final string for (int i = 0; i < begin; i++) { root += elements[i] + '\\'; } root += "...\\"; for (int i = end; i < filenameIndex; i++) { root += elements[i] + '\\'; } return root + elements[filenameIndex]; } return pathname; } void LoadRecentFiles() { _RecentFiles = LoadRecentFilesCore(); } List LoadRecentFilesCore() { List list = RecentFiles; List files = new List(list.Count); int i = 0; foreach (string filepath in list) files.Add(new RecentFile(i++, filepath)); return files; } private class RecentFile { public int Number = 0; public string Filepath = ""; public MenuItem MenuItem = null; public string DisplayPath { get { return Path.Combine( Path.GetDirectoryName(Filepath), Path.GetFileNameWithoutExtension(Filepath)); } } public RecentFile(int number, string filepath) { this.Number = number; this.Filepath = filepath; } } public class MenuClickEventArgs : EventArgs { public string Filepath { get; private set; } public MenuClickEventArgs(string filepath) { this.Filepath = filepath; } } void MenuItem_Click(object sender, EventArgs e) { MenuItem menuItem = sender as MenuItem; OnMenuClick(menuItem); } protected virtual void OnMenuClick(MenuItem menuItem) { string filepath = GetFilepath(menuItem); if (String.IsNullOrEmpty(filepath)) return; EventHandler dMenuClick = MenuClick; if (dMenuClick != null) dMenuClick(menuItem, new MenuClickEventArgs(filepath)); } string GetFilepath(MenuItem menuItem) { foreach (RecentFile r in _RecentFiles) if (r.MenuItem == menuItem) return r.Filepath; return String.Empty; } //----------------------------------------------------------------------------------------- static class ApplicationAttributes { static readonly Assembly _Assembly = null; static readonly AssemblyTitleAttribute _Title = null; static readonly AssemblyCompanyAttribute _Company = null; static readonly AssemblyCopyrightAttribute _Copyright = null; static readonly AssemblyProductAttribute _Product = null; public static string Title { get; private set; } public static string CompanyName { get; private set; } public static string Copyright { get; private set; } public static string ProductName { get; private set; } static Version _Version = null; public static string Version { get; private set; } static ApplicationAttributes() { try { Title = String.Empty; CompanyName = String.Empty; Copyright = String.Empty; ProductName = String.Empty; Version = String.Empty; _Assembly = Assembly.GetEntryAssembly(); if (_Assembly != null) { object[] attributes = _Assembly.GetCustomAttributes(false); foreach (object attribute in attributes) { Type type = attribute.GetType(); if (type == typeof(AssemblyTitleAttribute)) _Title = (AssemblyTitleAttribute)attribute; if (type == typeof(AssemblyCompanyAttribute)) _Company = (AssemblyCompanyAttribute)attribute; if (type == typeof(AssemblyCopyrightAttribute)) _Copyright = (AssemblyCopyrightAttribute)attribute; if (type == typeof(AssemblyProductAttribute)) _Product = (AssemblyProductAttribute)attribute; } _Version = _Assembly.GetName().Version; } if (_Title != null) Title = _Title.Title; if (_Company != null) CompanyName = _Company.Company; if (_Copyright != null) Copyright = _Copyright.Copyright; if (_Product != null) ProductName = _Product.Product; if (_Version != null) Version = _Version.ToString(); } catch { } } } //----------------------------------------------------------------------------------------- private class RegistryPersister : IPersist { public string RegistryKey { get; set; } public RegistryPersister() { RegistryKey = "Software\\" + ApplicationAttributes.CompanyName + "\\" + ApplicationAttributes.ProductName + "\\" + "RecentFileList"; } public RegistryPersister(string key) { RegistryKey = key; } string Key(int i) { return i.ToString("00"); } public List RecentFiles(int max) { RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey); if (k == null) k = Registry.CurrentUser.CreateSubKey(RegistryKey); List list = new List(max); for (int i = 0; i < max; i++) { string filename = (string)k.GetValue(Key(i)); if (String.IsNullOrEmpty(filename)) break; list.Add(filename); } return list; } public void InsertFile(string filepath, int max) { RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey); if (k == null) Registry.CurrentUser.CreateSubKey(RegistryKey); k = Registry.CurrentUser.OpenSubKey(RegistryKey, true); RemoveFile(filepath, max); for (int i = max - 2; i >= 0; i--) { string sThis = Key(i); string sNext = Key(i + 1); object oThis = k.GetValue(sThis); if (oThis == null) continue; k.SetValue(sNext, oThis); } k.SetValue(Key(0), filepath); } public void RemoveFile(string filepath, int max) { RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey); if (k == null) return; for (int i = 0; i < max; i++) { again: string s = (string)k.GetValue(Key(i)); if (s != null && s.Equals(filepath, StringComparison.CurrentCultureIgnoreCase)) { RemoveFile(i, max); goto again; } } } void RemoveFile(int index, int max) { RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey, true); if (k == null) return; k.DeleteValue(Key(index), false); for (int i = index; i < max - 1; i++) { string sThis = Key(i); string sNext = Key(i + 1); object oNext = k.GetValue(sNext); if (oNext == null) break; k.SetValue(sThis, oNext); k.DeleteValue(sNext); } } } //----------------------------------------------------------------------------------------- private class XmlPersister : IPersist { public string Filepath { get; set; } public Stream Stream { get; set; } public XmlPersister() { Filepath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ApplicationAttributes.CompanyName + "\\" + ApplicationAttributes.ProductName + "\\" + "RecentFileList.xml"); } public XmlPersister(string filepath) { Filepath = filepath; } public XmlPersister(Stream stream) { Stream = stream; } public List RecentFiles(int max) { return Load(max); } public void InsertFile(string filepath, int max) { Update(filepath, true, max); } public void RemoveFile(string filepath, int max) { Update(filepath, false, max); } void Update(string filepath, bool insert, int max) { List old = Load(max); List list = new List(old.Count + 1); if (insert) list.Add(filepath); CopyExcluding(old, filepath, list, max); Save(list, max); } void CopyExcluding(List source, string exclude, List target, int max) { foreach (string s in source) if (!String.IsNullOrEmpty(s)) if (!s.Equals(exclude, StringComparison.OrdinalIgnoreCase)) if (target.Count < max) target.Add(s); } class SmartStream : IDisposable { bool _IsStreamOwned = true; Stream _Stream = null; public Stream Stream { get { return _Stream; } } public static implicit operator Stream(SmartStream me) { return me.Stream; } public SmartStream(string filepath, FileMode mode) { _IsStreamOwned = true; Directory.CreateDirectory(Path.GetDirectoryName(filepath)); _Stream = File.Open(filepath, mode); } public SmartStream(Stream stream) { _IsStreamOwned = false; _Stream = stream; } public void Dispose() { if (_IsStreamOwned && _Stream != null) _Stream.Dispose(); _Stream = null; } } SmartStream OpenStream(FileMode mode) { if (!String.IsNullOrEmpty(Filepath)) { return new SmartStream(Filepath, mode); } else { return new SmartStream(Stream); } } List Load(int max) { List list = new List(max); using (MemoryStream ms = new MemoryStream()) { using (SmartStream ss = OpenStream(FileMode.OpenOrCreate)) { if (ss.Stream.Length == 0) return list; ss.Stream.Position = 0; byte[] buffer = new byte[1 << 20]; for (; ; ) { int bytes = ss.Stream.Read(buffer, 0, buffer.Length); if (bytes == 0) break; ms.Write(buffer, 0, bytes); } ms.Position = 0; } XmlTextReader x = null; try { x = new XmlTextReader(ms); while (x.Read()) { switch (x.NodeType) { case XmlNodeType.XmlDeclaration: case XmlNodeType.Whitespace: break; case XmlNodeType.Element: switch (x.Name) { case "RecentFiles": break; case "RecentFile": if (list.Count < max) list.Add(x.GetAttribute(0)); break; default: Debug.Assert(false); break; } break; case XmlNodeType.EndElement: switch (x.Name) { case "RecentFiles": return list; default: Debug.Assert(false); break; } break; default: Debug.Assert(false); break; } } } finally { if (x != null) x.Close(); } } return list; } void Save(List list, int max) { using (MemoryStream ms = new MemoryStream()) { XmlTextWriter x = null; try { x = new XmlTextWriter(ms, Encoding.UTF8); if (x == null) { Debug.Assert(false); return; } x.Formatting = Formatting.Indented; x.WriteStartDocument(); x.WriteStartElement("RecentFiles"); foreach (string filepath in list) { x.WriteStartElement("RecentFile"); x.WriteAttributeString("Filepath", filepath); x.WriteEndElement(); } x.WriteEndElement(); x.WriteEndDocument(); x.Flush(); using (SmartStream ss = OpenStream(FileMode.Create)) { ss.Stream.SetLength(0); ms.Position = 0; byte[] buffer = new byte[1 << 20]; for (; ; ) { int bytes = ms.Read(buffer, 0, buffer.Length); if (bytes == 0) break; ss.Stream.Write(buffer, 0, bytes); } } } finally { if (x != null) x.Close(); } } } } //----------------------------------------------------------------------------------------- } }