diff --git a/code/PTM.Addin.Charts/Properties/AssemblyInfo.cs b/code/PTM.Addin.Charts/Properties/AssemblyInfo.cs index de5ab3f..c925a91 100644 --- a/code/PTM.Addin.Charts/Properties/AssemblyInfo.cs +++ b/code/PTM.Addin.Charts/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.1")] -[assembly: AssemblyFileVersion("1.4.1")] +[assembly: AssemblyVersion("1.4.0")] +[assembly: AssemblyFileVersion("1.4.0")] diff --git a/code/PTM.Addin.WeekView/AssemblyInfo.cs b/code/PTM.Addin.WeekView/AssemblyInfo.cs index 7cc64a9..df6faed 100644 --- a/code/PTM.Addin.WeekView/AssemblyInfo.cs +++ b/code/PTM.Addin.WeekView/AssemblyInfo.cs @@ -26,7 +26,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.1")] +[assembly: AssemblyVersion("1.4.0")] // // In order to sign your assembly you must specify a key to use. Refer to the diff --git a/code/PTM/Addin/AddinTabPage.cs b/code/PTM/Addin/AddinTabPage.cs index 6111b61..5fd6f4e 100644 --- a/code/PTM/Addin/AddinTabPage.cs +++ b/code/PTM/Addin/AddinTabPage.cs @@ -85,7 +85,7 @@ public string Status } - public delegate void StatusChangedDelegate(object sender, StatusChangedEventArgs e); + public delegate void StatusChangedDelegate(StatusChangedEventArgs e); public event StatusChangedDelegate StatusChanged; @@ -94,7 +94,7 @@ protected void OnStatusChanged() if (this.StatusChanged != null) { StatusChangedDelegate del = new StatusChangedDelegate(StatusChanged); - this.Invoke(del, new object[] {this, new StatusChangedEventArgs(status) }); + this.Invoke(del, new object[] { new StatusChangedEventArgs(status) }); } // StatusChanged(new StatusChangedEventArgs(status)); //Application.DoEvents(); diff --git a/code/PTM/AssemblyInfo.cs b/code/PTM/AssemblyInfo.cs index 521cb27..a3e8280 100644 --- a/code/PTM/AssemblyInfo.cs +++ b/code/PTM/AssemblyInfo.cs @@ -25,7 +25,7 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.1")] +[assembly: AssemblyVersion("1.4.0")] // // In order to sign your assembly you must specify a key to use. Refer to the diff --git a/code/PTM/Framework/ApplicationsLog.cs b/code/PTM/Framework/ApplicationsLog.cs index 9c45112..398839b 100644 --- a/code/PTM/Framework/ApplicationsLog.cs +++ b/code/PTM/Framework/ApplicationsLog.cs @@ -337,11 +337,7 @@ private static void JoinLoggingThread() { if (loggingThread != null && loggingThread.IsAlive) { - try - { - loggingThread.Join(100); - } - catch(ThreadStateException){} //catch exception + loggingThread.Join(100); } //if-else } //JoinLoggingThread diff --git a/code/PTM/Framework/Helpers/ConfigurationHelper.cs b/code/PTM/Framework/Helpers/ConfigurationHelper.cs index 07c23aa..10e37c2 100644 --- a/code/PTM/Framework/Helpers/ConfigurationHelper.cs +++ b/code/PTM/Framework/Helpers/ConfigurationHelper.cs @@ -91,7 +91,7 @@ public static void SaveConfiguration(Configuration configuration) public static string GetVersionString() { - return "v. 1.4 RC2"; + return "v. 1.4 RC1"; } //GetVersionString public static string GetInternalVersionString() diff --git a/code/PTM/Framework/Helpers/DBUpdaterHelper.cs b/code/PTM/Framework/Helpers/DBUpdaterHelper.cs index a479445..f6da35d 100644 --- a/code/PTM/Framework/Helpers/DBUpdaterHelper.cs +++ b/code/PTM/Framework/Helpers/DBUpdaterHelper.cs @@ -46,8 +46,6 @@ public static void UpdateDataBase() if (UpdateFromV098ToV099(oldVersion)) continue; if (UpdateFromV099ToV0910(oldVersion)) - continue; - if (UpdateFromV_0_9_10_ToV_1_0_0(oldVersion)) continue; findNextUpdate = false; RegisterAddins(); @@ -73,27 +71,6 @@ private static void RegisterAddins() } } - private static bool UpdateFromV_0_9_10_ToV_1_0_0(Configuration oldVersion) - { - if (string.Compare(oldVersion.Value.ToString().Trim(), "0.9.10") == 0) - { - try - { - DbHelper.AddColumn("Tasks", "Hidden", "Bit"); - DbHelper.AddColumn("Tasks", "Priority", "Integer"); - DbHelper.AddColumn("Tasks", "Notes", "VarChar(255)"); - ConfigurationHelper.SaveConfiguration(new Configuration(ConfigurationKey.DataBaseVersion, "1.0.0")); - return true; - } - catch (OleDbException ex) - { - Logger.WriteException(ex); - return false; - } - } - return false; - } - private static bool UpdateFromV099ToV0910(Configuration oldVersion) { if (string.Compare(oldVersion.Value.ToString().Trim(), "0.9.9") == 0) diff --git a/code/PTM/Framework/Infos/Task.cs b/code/PTM/Framework/Infos/Task.cs index d77b889..b1fb06a 100644 --- a/code/PTM/Framework/Infos/Task.cs +++ b/code/PTM/Framework/Infos/Task.cs @@ -11,15 +11,12 @@ public class Task : IComparable { private int id; private int parentId; - private string description = String.Empty; + private string description; private int iconId; private bool isActive; private int estimation; - private bool hidden; - private int priority; - private string notes = String.Empty; - public int Id + public int Id { get { return id; } set { id = value; } @@ -55,24 +52,6 @@ public int Estimation set { estimation = value; } } - public bool Hidden - { - get { return hidden; } - set { hidden = value; } - } - - public int Priority - { - get { return priority; } - set { priority = value; } - } - - public string Notes - { - get { return notes; } - set { notes = value; } - } - public Task Clone() { Task task = new Task(); @@ -82,9 +61,6 @@ public Task Clone() task.iconId = this.iconId; task.isActive = this.isActive; task.estimation = this.estimation; - task.hidden = this.hidden; - task.priority = this.priority; - task.notes = this.notes; return task; } diff --git a/code/PTM/Framework/Tasks.cs b/code/PTM/Framework/Tasks.cs index ed8074e..1e0fe05 100644 --- a/code/PTM/Framework/Tasks.cs +++ b/code/PTM/Framework/Tasks.cs @@ -140,17 +140,15 @@ public static void UpdateTask(Task task) } DbHelper.ExecuteNonQuery( - "UPDATE Tasks SET Description = ?, IconId = ?, IsActive = ?, ParentId = ?, Estimation = ?, Hidden = ?, Priority = ?, Notes = ? WHERE (Id = ?)" - , new string[]{"Description", "IconId", "IsActive", "ParentId", "Estimation", "Hidden", "Priority", "Notes", "Id"}, - new object[]{task.Description, task.IconId, task.IsActive, task.ParentId,task.Estimation, task.Hidden, task.Priority, task.Notes, task.Id}); + "UPDATE Tasks SET Description = ?, IconId = ?, IsActive = ?, ParentId = ?, Estimation = ? WHERE (Id = ?)" + , new string[]{"Description", "IconId", "IsActive", "ParentId", "Estimation", "Id"}, + new object[]{task.Description, task.IconId, task.IsActive, task.ParentId,task.Estimation, task.Id}); for(int i = 0;i System.Data - System.Drawing @@ -163,11 +162,6 @@ Form - - True - True - Resources.resx - Code @@ -195,12 +189,6 @@ UserControl - - Component - - - Component - UserControl @@ -261,13 +249,13 @@ Form - + Form Form - + Form @@ -295,11 +283,6 @@ MainForm.cs Designer - - Designer - ResXFileCodeGenerator - Resources.Designer.cs - Designer @@ -315,7 +298,6 @@ SummaryControl.cs - Designer TasksLogControl.cs @@ -348,21 +330,22 @@ SplashForm.cs - - TaskSelectForm.cs - Designer + + TaskLogForm.cs TaskPropertiesForm.cs - - TasksExplorerForm.cs - Designer + + TasksHierarchyForm.cs UpdateForm.cs + + + diff --git a/code/PTM/Properties/Resources.Designer.cs b/code/PTM/Properties/Resources.Designer.cs deleted file mode 100644 index c2d4041..0000000 --- a/code/PTM/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:2.0.50727.832 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace PTM.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PTM.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/code/PTM/Properties/Resources.resx b/code/PTM/Properties/Resources.resx deleted file mode 100644 index 5ea0895..0000000 --- a/code/PTM/Properties/Resources.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/code/PTM/View/Controls/ObjectListViewComponents/ListViewPrinter.cs b/code/PTM/View/Controls/ObjectListViewComponents/ListViewPrinter.cs deleted file mode 100644 index 1d7b9ed..0000000 --- a/code/PTM/View/Controls/ObjectListViewComponents/ListViewPrinter.cs +++ /dev/null @@ -1,1979 +0,0 @@ -/* - * ListViewPrinterBase - A helper class to easily print an ListView - * - * User: Phillip Piper (phillip_piper@bigfoot.com) - * Date: 2007-11-01 11:15 AM - * - * Change log: - * 2008-01-16 JPP - Made all classes public so they can be accessed from a DLL - * - Corrected initial value bugs - * 2007-11-29 JPP - Made list cells able to wrap, rather than always ellipsing. - * - Handle ListViewItems having less sub items than there are columns. - * 2007-11-21 JPP - Cell images are no longer erased by a non-transparent cell backgrounds. - * v1.1 - * 2007-11-10 JPP - Made to work with virtual lists (if using ObjectListView) - * - Make the list view header be able to show on each page - * 2007-11-06 JPP - Changed to use Pens internally in BlockFormat - * - Fixed bug where group + following row would overprint footer - * v1.0 - * 2007-11-05 JPP - Vastly improved integration with IDE - * - Added support for page ranges, and printing images - * 2007-11-03 JPP Added support for groups - * 2007-10-31 JPP Initial version - * - * To Do: - * - * CONDITIONS OF USE - * This code may be freely used for any purpose, providate that this code is kept intact, - * complete with this header and conditions of use. - * - * Copyright (C) 2007 Phillip Piper - */ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; -using System.Windows.Forms; -using System.Drawing.Printing; -using System.Drawing.Drawing2D; - -namespace BrightIdeasSoftware -{ - /// - /// A ListViewPrinterBase prints or print previews an ListView. - /// - /// - /// The format of the page header/footer, list header and list rows can all be customised. - /// This class works best with ObjectListView class, but still works fine with normal ListViews. - /// If you don't have ObjectListView class in your project, you must define WITHOUT_OBJECTLISTVIEW as one - /// of the conditional compilation symbols on your projects properties. - /// - public class ListViewPrinterBase : PrintDocument - { - #region Constructors - - public ListViewPrinterBase() - { - this.ListView = null; - - // Give the report a reasonable set of default values - this.HeaderFormat = BlockFormat.Header(); - this.ListHeaderFormat = BlockFormat.ListHeader(); - this.CellFormat = BlockFormat.DefaultCell(); - this.GroupHeaderFormat = BlockFormat.GroupHeader(); - this.FooterFormat = BlockFormat.Footer(); - } - - public ListViewPrinterBase(ListView lv) - : this() - { - this.ListView = lv; - } - - #endregion - - #region Control Properties - - /// - /// This is the ListView that will be printed - /// - [Category("Behaviour"), - Description("Which listview will be printed by this printer?"), - DefaultValue(null)] - public ListView ListView - { - get { return listView; } - set { listView = value; } - } - private ListView listView; - - /// - /// Should this report use text only? - /// - [Category("Behaviour"), - Description("Should this report use text only? If this is false, images on the primary column will be included."), - DefaultValue(false)] - public bool IsTextOnly - { - get { return isTextOnly; } - set { isTextOnly = value; } - } - private bool isTextOnly = false; - - /// - /// Should this report be shrunk to fit into the width of a page? - /// - [Category("Behaviour"), - Description("Should this report be shrunk to fit into the width of a page?"), - DefaultValue(true)] - public bool IsShrinkToFit - { - get { return isShrinkToFit; } - set { isShrinkToFit = value; } - } - private bool isShrinkToFit = true; - - /// - /// Should this report only include the selected rows in the listview? - /// - [Category("Behaviour"), - Description("Should this report only include the selected rows in the listview?"), - DefaultValue(false)] - public bool IsPrintSelectionOnly - { - get { return isPrintSelectionOnly; } - set { isPrintSelectionOnly = value; } - } - private bool isPrintSelectionOnly = false; - - /// - /// Should this report use the column order as the user sees them? With this enabled, - /// the report will match the order of column as the user has arranged them. - /// - [Category("Behaviour"), - Description("Should this report use the column order as the user sees them? With this enabled, the report will match the order of column as the user has arranged them."), - DefaultValue(true)] - public bool UseColumnDisplayOrder - { - get { return useColumnDisplayOrder; } - set { useColumnDisplayOrder = value; } - } - private bool useColumnDisplayOrder = true; - - /// - /// Should column headings always be centered, even if on the control itself, they are - /// aligned to the left or right? - /// - [Category("Behaviour"), - Description("Should column headings always be centered or should they follow the alignment on the control itself?"), - DefaultValue(true)] - public bool AlwaysCenterListHeader - { - get { return slwaysCenterListHeader; } - set { slwaysCenterListHeader = value; } - } - private bool slwaysCenterListHeader = true; - - /// - /// Should listview headings be printed at the top of each page, or just at the top of the list? - /// - [Category("Behaviour"), - Description("Should listview headings be printed at the top of each page, or just at the top of the list?"), - DefaultValue(true)] - public bool IsListHeaderOnEachPage - { - get { return isListHeaderOnEachPage; } - set { isListHeaderOnEachPage = value; } - } - private bool isListHeaderOnEachPage = true; - - /// - /// Return the first page of the report that should be printed - /// - [Category("Behaviour"), - Description("Return the first page of the report that should be printed"), - DefaultValue(0)] - public int FirstPage - { - get { return firstPage; } - set { firstPage = value; } - } - private int firstPage = 0; - - /// - /// Return the last page of the report that should be printed - /// - [Category("Behaviour"), - Description("Return the last page of the report that should be printed"), - DefaultValue(9999)] - public int LastPage - { - get { return lastPage; } - set { lastPage = value; } - } - private int lastPage = 9999; - - /// - /// Return the number of the page that is currently being printed. - /// - [Browsable(false)] - public int PageNumber - { - get - { - return this.pageNumber; - } - } - - /// - /// Is this report showing groups? - /// - /// Groups can't be shown when we are printing selected rows only. - [Browsable(false)] - public bool IsShowingGroups - { - get - { - return (this.ListView != null && this.ListView.ShowGroups && !this.IsPrintSelectionOnly && this.ListView.Groups.Count > 0); - } - } - - #endregion - - #region Formatting Properties - - /// - /// How should the page header be formatted? null means no page header will be printed - /// - [Category("Appearance - Formatting"), - Description("How will the page header be formatted? "), - DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public BlockFormat HeaderFormat - { - get { return headerFormat; } - set { headerFormat = value; } - } - private BlockFormat headerFormat; - - /// - /// How should the list header be formatted? null means no list header will be printed - /// - [Category("Appearance - Formatting"), - Description("How will the header of the list be formatted? "), - DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public BlockFormat ListHeaderFormat - { - get { return listHeaderFormat; } - set { listHeaderFormat = value; } - } - public BlockFormat listHeaderFormat; - - /// - /// How should the grouping header be formatted? null means revert to reasonable default - /// - [Category("Appearance - Formatting"), - Description("How will the group headers be formatted?"), - DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public BlockFormat GroupHeaderFormat - { - get - { - // The group header format cannot be null - if (groupHeaderFormat == null) - groupHeaderFormat = BlockFormat.GroupHeader(); - - return groupHeaderFormat; - } - set { groupHeaderFormat = value; } - } - public BlockFormat groupHeaderFormat; - - /// - /// How should the list cells be formatted? null means revert to default - /// - [Category("Appearance - Formatting"), - Description("How will the list cells be formatted? "), - DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public BlockFormat CellFormat - { - get - { - // The cell format cannot be null - if (cellFormat == null) - cellFormat = BlockFormat.DefaultCell(); - - return cellFormat; - } - set - { - cellFormat = value; - } - } - private BlockFormat cellFormat; - - /// - /// How should the page footer be formatted? null means no footer will be printed - /// - [Category("Appearance - Formatting"), - Description("How will the page footer be formatted?"), - DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public BlockFormat FooterFormat - { - get { return footerFormat; } - set { footerFormat = value; } - } - private BlockFormat footerFormat; - - /// - /// What font will be used to draw the text of the list? - /// - [Browsable(false)] - public Font ListFont - { - get { return this.CellFormat.Font; } - set { this.CellFormat.Font = value; } - } - - /// - /// What pen will be used to draw the cells within the list? - /// If this is null, no grid will be drawn - /// - /// This is just a conviencence wrapper around CellFormat.SetBorderPen - [Browsable(false)] - public Pen ListGridPen - { - get { return this.CellFormat.GetBorderPen(Sides.Top); } - set { this.CellFormat.SetBorderPen(Sides.All, value); } - } - - /// - /// What color will all the borders be drawn in? - /// - /// This is just a conviencence wrapper around CellFormat.SetBorder - [Browsable(false)] - public Color ListGridColor - { - get - { - Pen p = this.ListGridPen; - if (p == null) - return Color.Empty; - else - return p.Color; - } - set - { - this.ListGridPen = new Pen(new SolidBrush(value), 0.5f); - } - } - - /// - /// What string will be written at the top of each page of the report? - /// - /// The header can be divided into three parts: left aligned, - /// centered, and right aligned. If the given string contains Tab characters, - /// everything before the first tab will be left aligned, everything between - /// the first and second tabs will be centered and everything after the second - /// tab will be right aligned. - /// Within each part, the following substitutions are possible: - /// - /// {0} - The page number - /// {1} - The current date/time - /// - /// - [Category("Appearance"), - Description("The string that will be written at the top of each page. Use '\\t' characters to separate left, centre, and right parts of the header."), - DefaultValue(null)] - public String Header - { - get { return header; } - set - { - header = value; - if (!String.IsNullOrEmpty(header)) - header = header.Replace("\\t", "\t"); - } - } - private String header; - - /// - /// What string will be written at the bottom of each page of the report? - /// - /// The footer, like the header, can have three parts, and behaves - /// in the same way as described as Header. - [Category("Appearance"), - Description("The string that will be written at the bottom of each page. Use '\\t' characters to separate left, centre, and right parts of the footer."), - DefaultValue(null)] - public String Footer - { - get { return footer; } - set - { - footer = value; - if (!String.IsNullOrEmpty(footer)) - footer = footer.Replace("\\t", "\t"); - } - } - private String footer; - - //----------------------------------------------------------------------- - // Watermark - - /// - /// The watermark will be printed translucently over the report itself - /// - [Category("Appearance - Watermark"), - Description("The watermark will be printed translucently over the report itself?"), - DefaultValue(null)] - public String Watermark - { - get { return watermark; } - set { watermark = value; } - } - private String watermark; - - /// - /// What font should be used to print the watermark - /// - [Category("Appearance - Watermark"), - Description("What font should be used to print the watermark?"), - DefaultValue(null)] - public Font WatermarkFont - { - get { return watermarkFont; } - set { watermarkFont = value; } - } - private Font watermarkFont; - - /// - /// Return the watermark font or a reasonable default - /// - [Browsable(false)] - public Font WatermarkFontOrDefault - { - get - { - if (this.WatermarkFont == null) - return new Font("Tahoma", 72); - else - return this.WatermarkFont; - } - } - - /// - /// What color should be used for the watermark? - /// - [Category("Appearance - Watermark"), - Description("What color should be used for the watermark?"), - DefaultValue(typeof(Color), "Empty")] - public Color WatermarkColor - { - get { return watermarkColor; } - set { watermarkColor = value; } - } - private Color watermarkColor = Color.Empty; - - /// - /// Return the color of the watermark or a reasonable default - /// - [Browsable(false)] - public Color WatermarkColorOrDefault - { - get - { - if (this.WatermarkColor == Color.Empty) - return Color.Gray; - else - return this.WatermarkColor; - } - } - - /// - /// How transparent should the watermark be? <=0 is transparent, >=100 is completely opaque. - /// - [Category("Appearance - Watermark"), - Description("How transparent should the watermark be? 0 is transparent, 100 is completely opaque."), - DefaultValue(50)] - public int WatermarkTransparency - { - get { return watermarkTransparency; } - set { watermarkTransparency = Math.Max(0, Math.Min(value, 100)); } - } - private int watermarkTransparency = 50; - - #endregion - - #region Accessing - - /// - /// Return the number of rows that this printer is going to print - /// - /// The listview that is being printed - /// The number of rows that will be displayed - protected int GetRowCount(ListView lv) - { - if (this.IsPrintSelectionOnly) - return lv.SelectedIndices.Count; - else - if (lv.VirtualMode) - return lv.VirtualListSize; - else - return lv.Items.Count; - } - - /// - /// Return the n'th row that will be printed - /// - /// The listview that is being printed - /// The index of the row to be printed - /// A ListViewItem - protected ListViewItem GetRow(ListView lv, int n) - { - if (this.IsPrintSelectionOnly) - if (lv.VirtualMode) - return this.GetVirtualItem(lv, lv.SelectedIndices[n]); - else - return lv.SelectedItems[n]; - - if (!this.IsShowingGroups) - if (lv.VirtualMode) - return this.GetVirtualItem(lv, n); - else - return lv.Items[n]; - - // If we are showing groups, things are more complicated. The n'th - // row of the report doesn't directly correspond to existing list. - // The best we can do is figure out which group the n'th item belongs to - // and then figure out which item it is within the groups items. - int i; - for (i = this.groupStartPositions.Count - 1; i >= 0; i--) - if (n >= this.groupStartPositions[i]) - break; - int indexInList = n - this.groupStartPositions[i]; - return lv.Groups[i].Items[indexInList]; - } - - /// - /// Get the nth item from the given listview, which is in virtual mode. - /// - /// The ListView in virtual mode - /// index of item to get - /// the item - virtual protected ListViewItem GetVirtualItem(ListView lv, int n) - { - throw new ApplicationException("Virtual list items cannot be retrieved. Use an ObjectListView instead."); - } - - /// - /// Return the i'th subitem of the given row, in the order - /// that coumns are presented in the report - /// - /// The row from which a subitem is to be fetched - /// The index of the subitem in display order - /// A SubItem - protected ListViewItem.ListViewSubItem GetSubItem(ListViewItem lvi, int i) - { - if (i < lvi.SubItems.Count) - return lvi.SubItems[this.GetColumn(i).Index]; - else - return new ListViewItem.ListViewSubItem(); - } - - /// - /// Return the number of columns to be printed in the report - /// - /// An int - protected int GetColumnCount() - { - return this.sortedColumns.Count; - } - - /// - /// Return the n'th ColumnHeader (ordered as they should be displayed in the report) - /// - /// Which column - /// A ColumnHeader - protected ColumnHeader GetColumn(int i) - { - return this.sortedColumns[i]; - } - - /// - /// Return the index of group that starts at the given position. - /// Return -1 if no group starts at that position - /// - /// The row position in the list - /// The group index - protected int GetGroupAtPosition(int n) - { - return this.groupStartPositions.IndexOf(n); - } - - #endregion - - #region Commands - - /// - /// Show a Page Setup dialog to customize the printing of this document - /// - public void PageSetup() - { - PageSetupDialog dlg = new PageSetupDialog(); - dlg.Document = this; - dlg.EnableMetric = true; - dlg.ShowDialog(); - } - - /// - /// Show a Print Preview of this document - /// - public void PrintPreview() - { - PrintPreviewDialog dlg = new PrintPreviewDialog(); - dlg.UseAntiAlias = true; - dlg.Document = this; - dlg.ShowDialog(); - } - - /// - /// Print this document after showing a confirmation dialog - /// - public void PrintWithDialog() - { - PrintDialog dlg = new PrintDialog(); - dlg.Document = this; - dlg.AllowSelection = this.ListView.SelectedIndices.Count > 0; - dlg.AllowSomePages = true; - - // Show the standard print dialog box, that lets the user select a printer - // and change the settings for that printer. - if (dlg.ShowDialog() == DialogResult.OK) { - this.IsPrintSelectionOnly = (dlg.PrinterSettings.PrintRange == PrintRange.Selection); - if (dlg.PrinterSettings.PrintRange == PrintRange.SomePages) { - this.FirstPage = dlg.PrinterSettings.FromPage; - this.LastPage = dlg.PrinterSettings.ToPage; - } else { - this.FirstPage = 1; - this.LastPage = 999999; - } - this.Print(); - } - } - - #endregion - - #region Event handlers - - override protected void OnBeginPrint(PrintEventArgs e) - { - base.OnBeginPrint(e); - - // Initialize our state information - this.rowIndex = -1; - this.indexLeftColumn = -1; - this.indexRightColumn = -1; - this.pageNumber = 0; - - // Initialize our caches - this.sortedColumns = new SortedList(); - this.groupStartPositions = new List(); - - this.PreparePrint(); - } - - override protected void OnPrintPage(PrintPageEventArgs e) - { - if (this.ListView == null || this.ListView.View != View.Details) - return; - - base.OnPrintPage(e); - - this.pageNumber++; - - // Ignore all pages before the first requested page - // Have to allow for weird cases where the last page is before the first page - // and where we run out of things to print before reaching the first requested page. - int pageToStop = Math.Min(this.FirstPage, this.LastPage + 1); - if (this.pageNumber < pageToStop) { - e.HasMorePages = true; - while (this.pageNumber < pageToStop && e.HasMorePages) { - e.HasMorePages = this.PrintOnePage(e); - this.pageNumber++; - } - - // Remove anything drawn - e.Graphics.Clear(Color.White); - - // If we ran out of pages before reaching the first page, simply return - if (!e.HasMorePages) - return; - } - - // If we haven't reached the end of the requested pages, print one. - if (this.pageNumber <= this.LastPage) { - e.HasMorePages = this.PrintOnePage(e); - e.HasMorePages = e.HasMorePages && (this.pageNumber < this.LastPage); - } else - e.HasMorePages = false; - } - - #endregion - - #region List printing - - /// - /// Prepare some precalculated fields used when printing - /// - protected void PreparePrint() - { - if (this.ListView == null) - return; - - // Build sortedColumn so it holds the column in the order they should be printed - foreach (ColumnHeader column in this.ListView.Columns) { - if (this.UseColumnDisplayOrder) - this.sortedColumns.Add(column.DisplayIndex, column); - else - this.sortedColumns.Add(column.Index, column); - } - - // If the listview is grouped, build an array to holds the start - // position of each group. The way to understand this array is that - // the index of the first member of group n is found at groupStartPositions[n]. - int itemCount = 0; - foreach (ListViewGroup lvg in this.ListView.Groups) { - this.groupStartPositions.Add(itemCount); - itemCount += lvg.Items.Count; - } - } - - /// - /// Do the actual work of printing on page - /// - /// - protected bool PrintOnePage(PrintPageEventArgs e) - { - this.CalculateBounds(e); - this.CalculatePrintParameters(this.ListView); - this.PrintHeaderFooter(e.Graphics); - this.ApplyScaling(e.Graphics); - bool continuePrinting = this.PrintList(e.Graphics, this.ListView); - this.PrintWatermark(e.Graphics); - return continuePrinting; - } - - /// - /// Figure out the page bounds and the boundaries for the list - /// - /// - protected void CalculateBounds(PrintPageEventArgs e) - { - // Printing to a real printer doesn't take the printers hard margins into account - if (this.PrintController.IsPreview) - this.pageBounds = (RectangleF)e.MarginBounds; - else - this.pageBounds = new RectangleF(e.MarginBounds.X - e.PageSettings.HardMarginX, - e.MarginBounds.Y - e.PageSettings.HardMarginY, e.MarginBounds.Width, e.MarginBounds.Height); - - this.listBounds = this.pageBounds; - } - - /// - /// Figure out the boundaries for various aspects of the report - /// - /// The listview to be printed - protected void CalculatePrintParameters(ListView lv) - { - // If we are in the middle of printing a listview, don't change the parameters - if (this.rowIndex >= 0 && this.rowIndex < this.GetRowCount(lv)) - return; - - this.rowIndex = 0; - - // If we are shrinking the report to fit on the page... - if (this.IsShrinkToFit) { - - // ...we print all the columns, but we need to figure how much to shrink - // them so that they will fit onto the page - this.indexLeftColumn = 0; - this.indexRightColumn = this.GetColumnCount() - 1; - - int totalWidth = 0; - for (int i = 0; i < this.GetColumnCount(); i++) { - totalWidth += this.GetColumn(i).Width; - } - this.scaleFactor = Math.Min(this.listBounds.Width / totalWidth, 1.0f); - } else { - // ...otherwise, we print unscaled but have to figure out which columns - // will fit on the current page - this.scaleFactor = 1.0f; - this.indexLeftColumn = ++this.indexRightColumn; - - // Iterate the columns until we find a column that won't fit on the page - int width = 0; - for (int i = this.indexLeftColumn; i < this.GetColumnCount() && (width += this.GetColumn(i).Width) < this.listBounds.Width; i++) - this.indexRightColumn = i; - } - } - - /// - /// Apply any scaling that is required to the report - /// - /// - protected void ApplyScaling(Graphics g) - { - if (this.scaleFactor >= 1.0f) - return; - - g.ScaleTransform(this.scaleFactor, this.scaleFactor); - - float inverse = 1.0f / this.scaleFactor; - this.listBounds = new RectangleF(this.listBounds.X * inverse, this.listBounds.Y * inverse, this.listBounds.Width * inverse, this.listBounds.Height * inverse); - } - - /// - /// Print our watermark on the given Graphic - /// - /// - protected void PrintWatermark(Graphics g) - { - if (String.IsNullOrEmpty(this.Watermark)) - return; - - StringFormat strFormat = new StringFormat(); - strFormat.LineAlignment = StringAlignment.Center; - strFormat.Alignment = StringAlignment.Center; - - // THINK: Do we want this to be a property? - int watermarkRotation = -30; - - // Setup a rotation transform on the Graphic so we can write the watermark at an angle - g.ResetTransform(); - Matrix m = new Matrix(); - m.RotateAt(watermarkRotation, new PointF(this.pageBounds.X + this.pageBounds.Width / 2, this.pageBounds.Y + this.pageBounds.Height / 2)); - g.Transform = m; - - // Calculate the semi-transparent pen required to print the watermark - int alpha = (int)(255.0f * (float)this.WatermarkTransparency / 100.0f); - Brush brush = new SolidBrush(Color.FromArgb(alpha, this.WatermarkColorOrDefault)); - - // Finally draw the watermark - g.DrawString(this.Watermark, this.WatermarkFontOrDefault, brush, this.pageBounds, strFormat); - g.ResetTransform(); - } - - /// - /// Do the work of printing the list into 'listBounds' - /// - /// The graphic used for drawing - /// The listview to be printed - /// Return true if there are still more pages in the report - protected bool PrintList(Graphics g, ListView lv) - { - this.currentOrigin = this.listBounds.Location; - - if (this.rowIndex == 0 || this.IsListHeaderOnEachPage) - this.PrintListHeader(g, lv); - - this.PrintRows(g, lv); - - // We continue to print pages when we have more rows or more columns remaining - return (this.rowIndex < this.GetRowCount(lv) || this.indexRightColumn + 1 < this.GetColumnCount()); - } - - /// - /// Print the header of the listview - /// - /// The graphic used for drawing - /// The listview to be printed - protected void PrintListHeader(Graphics g, ListView lv) - { - // If there is no format for the header, we don't draw it - BlockFormat fmt = this.ListHeaderFormat; - if (fmt == null) - return; - - // Calculate the height of the list header - float height = 0; - for (int i = 0; i < this.GetColumnCount(); i++) { - ColumnHeader col = this.GetColumn(i); - height = Math.Max(height, fmt.CalculateHeight(g, col.Text, col.Width)); - } - - // Draw the header one cell at a time - RectangleF cell = new RectangleF(this.currentOrigin.X, this.currentOrigin.Y, 0, height); - for (int i = this.indexLeftColumn; i <= this.indexRightColumn; i++) { - ColumnHeader col = this.GetColumn(i); - cell.Width = col.Width; - fmt.Draw(g, cell, col.Text, (this.AlwaysCenterListHeader ? HorizontalAlignment.Center : col.TextAlign)); - cell.Offset(cell.Width, 0); - } - - this.currentOrigin.Y += cell.Height; - } - - /// - /// Print the rows of the listview - /// - /// The graphic used for drawing - /// The listview to be printed - protected void PrintRows(Graphics g, ListView lv) - { - while (this.rowIndex < this.GetRowCount(lv)) { - - // Will this row fit before the end of page? - float rowHeight = this.CalculateRowHeight(g, lv, this.rowIndex); - if (this.currentOrigin.Y + rowHeight > this.listBounds.Bottom) - break; - - // If we are printing group and there is a group begining at the current position, - // print it so long as the group header and at least one following row will fit on the page - if (this.IsShowingGroups) { - int groupIndex = this.GetGroupAtPosition(this.rowIndex); - if (groupIndex != -1) { - float groupHeaderHeight = this.GroupHeaderFormat.CalculateHeight(g); - if (this.currentOrigin.Y + groupHeaderHeight + rowHeight < this.listBounds.Bottom) { - this.PrintGroupHeader(g, lv, groupIndex); - } else { - this.currentOrigin.Y = this.listBounds.Bottom; - break; - } - } - } - this.PrintRow(g, lv, this.rowIndex, rowHeight); - this.rowIndex++; - } - } - - /// - /// Calculate how high the given row of the report should be. - /// - /// The graphic used for drawing - /// The listview to be printed - /// The index of the row whose height is to be calculated - /// The height of one row in pixels - virtual protected float CalculateRowHeight(Graphics g, ListView lv, int n) - { - // If we're including graphics in the report, we need to allow for the height of a small image - if (!this.IsTextOnly && lv.SmallImageList != null) - this.CellFormat.MinimumTextHeight = lv.SmallImageList.ImageSize.Height; - - // If the cell lines can't wrap, calculate the generic height of the row - if (!this.CellFormat.CanWrap) - return this.CellFormat.CalculateHeight(g); - - // If the cell lines can wrap, calculate the height of the tallest cell - float height = 0f; - ListViewItem lvi = this.GetRow(lv, n); - for (int i = 0; i < this.GetColumnCount(); i++) { - ColumnHeader column = this.GetColumn(i); - int colWidth = column.Width; - if (!this.IsTextOnly && column.Index == 0 && lv.SmallImageList != null && lvi.ImageIndex != -1) - colWidth -= lv.SmallImageList.ImageSize.Width; - height = Math.Max(height, this.CellFormat.CalculateHeight(g, this.GetSubItem(lvi, i).Text, colWidth)); - } - return height; - } - - /// - /// Print a group header - /// - /// The graphic used for drawing - /// The listview to be printed - /// The index of the group header to be printed - protected void PrintGroupHeader(Graphics g, ListView lv, int groupIndex) - { - ListViewGroup lvg = lv.Groups[groupIndex]; - BlockFormat fmt = this.GroupHeaderFormat; - float height = fmt.CalculateHeight(g); - RectangleF r = new RectangleF(this.currentOrigin.X, this.currentOrigin.Y, this.listBounds.Width, height); - fmt.Draw(g, r, lvg.Header, lvg.HeaderAlignment); - this.currentOrigin.Y += height; - } - - /// - /// Print one row of the listview - /// - /// - /// - /// - /// - /// - protected void PrintRow(Graphics g, ListView lv, int row, float rowHeight) - { - ListViewItem lvi = this.GetRow(lv, row); - - // Print the row cell by cell. We only print the cells that are in the range - // of columns that are chosen for this page - RectangleF cell = new RectangleF(this.currentOrigin, new SizeF(0, rowHeight)); - for (int i = this.indexLeftColumn; i <= this.indexRightColumn; i++) { - ColumnHeader col = this.GetColumn(i); - cell.Width = col.Width; - this.PrintCell(g, lv, lvi, row, i, cell); - cell.Offset(cell.Width, 0); - } - this.currentOrigin.Y += rowHeight; - } - - /// - /// Print one cell of the listview - /// - /// - /// - /// - /// - /// - /// - /// - virtual protected void PrintCell(Graphics g, ListView lv, ListViewItem lvi, int row, int column, RectangleF cell) - { - BlockFormat fmt = this.CellFormat; - ColumnHeader ch = this.GetColumn(column); - - // Are we going to print an icon in this cell? We print an image if it - // isn't a text only report AND it is a primary column AND the cell has an image and a image list. - if (!this.IsTextOnly && ch.Index == 0 && lvi.ImageIndex != -1 && lv.SmallImageList != null) { - // Trick the block format into indenting the text so it doesn't write the text into where the image is going to be drawn - const int gapBetweenImageAndText = 3; - float textInsetCorrection = lv.SmallImageList.ImageSize.Width + gapBetweenImageAndText; - fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) + textInsetCorrection); - fmt.Draw(g, cell, this.GetSubItem(lvi, column).Text, ch.TextAlign); - fmt.SetTextInset(Sides.Left, fmt.GetTextInset(Sides.Left) - textInsetCorrection); - - // Now draw the image into the area reserved for it - RectangleF r = fmt.CalculatePaddedTextBox(cell); - if (lv.SmallImageList.ImageSize.Height < r.Height) - r.Y += (r.Height - lv.SmallImageList.ImageSize.Height) / 2; - g.DrawImage(lv.SmallImageList.Images[lvi.ImageIndex], r.Location); - } else { - // No image to draw. SImply draw the text - fmt.Draw(g, cell, this.GetSubItem(lvi, column).Text, ch.TextAlign); - } - } - - /// - /// Print the page header and page footer - /// - /// - protected void PrintHeaderFooter(Graphics g) - { - if (!String.IsNullOrEmpty(this.Header)) - PrintPageHeader(g); - - if (!String.IsNullOrEmpty(this.Footer)) - PrintPageFooter(g); - } - - /// - /// Print the page header - /// - /// - protected void PrintPageHeader(Graphics g) - { - BlockFormat fmt = this.HeaderFormat; - if (fmt == null) - return; - - float height = fmt.CalculateHeight(g); - RectangleF headerRect = new RectangleF(this.listBounds.X, this.listBounds.Y, this.listBounds.Width, height); - fmt.Draw(g, headerRect, this.SplitAndFormat(this.Header)); - - // Move down the top of the area available for the list - this.listBounds.Y += height; - this.listBounds.Height -= height; - } - - /// - /// Print the page footer - /// - /// - protected void PrintPageFooter(Graphics g) - { - BlockFormat fmt = this.FooterFormat; - if (fmt == null) - return; - - float height = fmt.CalculateHeight(g); - RectangleF r = new RectangleF(this.listBounds.X, this.listBounds.Bottom - height, this.listBounds.Width, height); - fmt.Draw(g, r, this.SplitAndFormat(this.Footer)); - - // Decrease the area available for the list - this.listBounds.Height -= height; - } - - /// - /// Split the given string into at most three parts, using Tab as the divider. - /// Perform any substitutions required - /// - /// - /// - private String[] SplitAndFormat(String text) - { - String s = String.Format(text, this.pageNumber, DateTime.Now); - return s.Split(new Char[] { '\x09' }, 3); - } - - #endregion - - #region Private variables - - // These are our state variables. - private int rowIndex; - private int indexLeftColumn; - private int indexRightColumn; - private int pageNumber; - - // Cached values - private SortedList sortedColumns; - private List groupStartPositions; - - // Per-page variables - private RectangleF pageBounds; - private RectangleF listBounds; - private PointF currentOrigin; - private float scaleFactor; - - #endregion - } - - /// - /// This ListViewPrinterBase handles only normal ListViews, while this class knows about the specifics of ObjectListViews - /// - public class ListViewPrinter : ListViewPrinterBase - { - public ListViewPrinter() - { - } - -#if !WITHOUT_OBJECTLISTVIEW - /// - /// Get the nth item from the given listview, which is in virtual mode. - /// - /// The ListView in virtual mode - /// index of item to get - /// the item - override protected ListViewItem GetVirtualItem(ListView lv, int n) - { - return ((VirtualObjectListView)lv).MakeListViewItem(n); - } - - /// - /// Calculate how high each row of the report should be. - /// - /// The graphic used for drawing - /// The listview to be printed - /// The font used for the list - /// The height of one row in pixels - override protected float CalculateRowHeight(Graphics g, ListView lv, int n) - { - float height = base.CalculateRowHeight(g, lv, n); - if (lv is ObjectListView) - height = Math.Max(height, ((ObjectListView)lv).RowHeight); - return height; - } - - /// - /// If the given BlockFormat doesn't specify a background, take it from the SubItem or the ListItem. - /// - protected bool ApplyCellSpecificBackground(BlockFormat fmt, ListViewItem lvi, ListViewItem.ListViewSubItem lvsi) - { - if (fmt.BackgroundBrush != null) - return false; - - if (lvi.UseItemStyleForSubItems) - fmt.BackgroundColor = lvi.BackColor; - else - fmt.BackgroundColor = lvsi.BackColor; - - return true; - } - - protected override void PrintCell(Graphics g, ListView lv, ListViewItem lvi, int row, int column, RectangleF cell) - { - if (this.IsTextOnly || !(lv is ObjectListView)) { - base.PrintCell(g, lv, lvi, row, column, cell); - return; - } - - OLVColumn olvc = (OLVColumn)this.GetColumn(column); - - BaseRenderer renderer = null; - if (olvc.Renderer == null) - renderer = new BaseRenderer(); - else { - renderer = olvc.Renderer; - - // Nasty hack. MS themed ProgressBarRenderer will not work on printer graphic contexts. - if (renderer is BarRenderer) - ((BarRenderer)renderer).UseStandardBar = false; - } - - renderer.IsDrawBackground = false; - renderer.Aspect = null; - renderer.Column = olvc; - renderer.IsItemSelected = false; - renderer.Font = this.CellFormat.Font; - renderer.TextBrush = this.CellFormat.TextBrush; - renderer.ListItem = (OLVListItem)lvi; - renderer.ListView = (ObjectListView)lv; - renderer.RowObject = ((OLVListItem)lvi).RowObject; - renderer.SubItem = this.GetSubItem(lvi, column); - renderer.CanWrap = this.CellFormat.CanWrap; - - // Use the cell block format to draw the background and border of the cell - bool bkChanged = this.ApplyCellSpecificBackground(this.CellFormat, renderer.ListItem, renderer.SubItem); - this.CellFormat.Draw(g, cell, "", "", ""); - if (bkChanged) - this.CellFormat.BackgroundBrush = null; - - // The renderer draws into the text area of the block. Unfortunately, the renderer uses Rectangle's - // rather than RectangleF's, so we have to convert, trying to prevent rounding errors - RectangleF r = this.CellFormat.CalculatePaddedTextBox(cell); - Rectangle r2 = new Rectangle((int)r.X + 1, (int)r.Y + 1, (int)r.Width - 1, (int)r.Height - 1); - renderer.Render(g, r2); - - // TODO: Put back the previous value rather than just assuming it was true - if (renderer is BarRenderer) - ((BarRenderer)renderer).UseStandardBar = true; - } -#endif - } - - /// - /// Specify which sides of a block will be operated on - /// - public enum Sides - { - Left = 0, - Top = 1, - Right = 2, - Bottom = 3, - All = 4 - } - - public class BlockFormat : System.ComponentModel.Component - { - public BlockFormat() - { - - } - - #region Public properties - - /// - /// In what font should the text of the block be drawn? If this is null, the font from the listview will be used - /// - [Category("Appearance"), - Description("What font should this block be drawn in?"), - DefaultValue(null)] - public Font Font - { - get { return font; } - set { font = value; } - } - private Font font; - - /// - /// Return the font that should be used for the text of this block or a reasonable default - /// - [Browsable(false)] - public Font FontOrDefault - { - get - { - if (this.Font == null) - return new Font("Ms Sans Serif", 12); - else - return this.Font; - } - } - - /// - /// What brush will be used to draw the text? - /// - /// - /// If this format is used for cells and this is null AND an ObjectListView is being printed, - /// then the text color from the listview will be used. - /// This is useful when you have setup specific colors on a RowFormatter delegate, for example. - /// - /// - [Browsable(false)] - public Brush TextBrush = null; - - /// - /// Return the brush that will be used to draw the text or a reasonable default - /// - [Browsable(false)] - public Brush TextBrushOrDefault - { - get - { - if (this.TextBrush == null) - return Brushes.Black; - else - return this.TextBrush; - } - } - - /// - /// What color will be used to draw the text? - /// This is a convience method used by the IDE. Programmers should call TextBrush directly. - /// - [Category("Appearance"), - Description("What color should text in this block be drawn in?"), - DefaultValue(typeof(Color), "Empty")] - public Color TextColor - { - get - { - if (this.TextBrush == null || !(this.TextBrush is SolidBrush)) - return Color.Empty; - else - return ((SolidBrush)this.TextBrush).Color; - } - set - { - if (value.IsEmpty) - this.TextBrush = null; - else - this.TextBrush = new SolidBrush(value); - } - } - - /// - /// What brush will be used to paint the background? - /// - [Browsable(false)] - public Brush BackgroundBrush = null; - - /// - /// What color will be used to draw the background? - /// This is a convience method used by the IDE. - /// - [Category("Appearance"), - Description("What color should be used to paint the background of this block?"), - DefaultValue(typeof(Color), "Empty")] - public Color BackgroundColor - { - get - { - if (this.BackgroundBrush == null || !(this.BackgroundBrush is SolidBrush)) - return Color.Empty; - else - return ((SolidBrush)this.BackgroundBrush).Color; - } - set - { - this.BackgroundBrush = new SolidBrush(value); - } - } - - /// - /// When laying out our header can the text be wrapped? - /// - [Category("Appearance"), - Description("When laying out our header can the text be wrapped?"), - DefaultValue(false)] - public bool CanWrap - { - get { return canWrap; } - set { canWrap = value; } - } - private bool canWrap = false; - - /// - /// If this is set, at least this much vertical space will be reserved for the text, - /// even if the text is smaller. - /// - [Browsable(false)] - public float MinimumTextHeight - { - get { return minimumTextHeight; } - set { minimumTextHeight = value; } - } - private float minimumTextHeight = 0; - - //---------------------------------------------------------------------------------- - // All of these attributes are solely to make them appear in the IDE - // When programming by hand, use Get/SetBorderPen() - // rather than these methods. - - [Category("Appearance"), Description("Width of the top border"), DefaultValue(0.0f)] - public float TopBorderWidth - { - get { return this.GetBorderWidth(Sides.Top); } - set { this.SetBorder(Sides.Top, value, this.GetBorderBrush(Sides.Top)); } - } - [Category("Appearance"), Description("Width of the Left border"), DefaultValue(0.0f)] - public float LeftBorderWidth - { - get { return this.GetBorderWidth(Sides.Left); } - set { this.SetBorder(Sides.Left, value, this.GetBorderBrush(Sides.Left)); } - } - [Category("Appearance"), Description("Width of the Bottom border"), DefaultValue(0.0f)] - public float BottomBorderWidth - { - get { return this.GetBorderWidth(Sides.Bottom); } - set { this.SetBorder(Sides.Bottom, value, this.GetBorderBrush(Sides.Bottom)); } - } - [Category("Appearance"), Description("Width of the Right border"), DefaultValue(0.0f)] - public float RightBorderWidth - { - get { return this.GetBorderWidth(Sides.Right); } - set { this.SetBorder(Sides.Right, value, this.GetBorderBrush(Sides.Right)); } - } - [Category("Appearance"), Description("Color of the top border"), DefaultValue(typeof(Color), "Empty")] - public Color TopBorderColor - { - get { return this.GetSolidBorderColor(Sides.Top); } - set { this.SetBorder(Sides.Top, this.GetBorderWidth(Sides.Top), new SolidBrush(value)); } - } - [Category("Appearance"), Description("Color of the Left border"), DefaultValue(typeof(Color), "Empty")] - public Color LeftBorderColor - { - get { return this.GetSolidBorderColor(Sides.Left); } - set { this.SetBorder(Sides.Left, this.GetBorderWidth(Sides.Left), new SolidBrush(value)); } - } - [Category("Appearance"), Description("Color of the Bottom border"), DefaultValue(typeof(Color), "Empty")] - public Color BottomBorderColor - { - get { return this.GetSolidBorderColor(Sides.Bottom); } - set { this.SetBorder(Sides.Bottom, this.GetBorderWidth(Sides.Bottom), new SolidBrush(value)); } - } - [Category("Appearance"), Description("Color of the Right border"), DefaultValue(typeof(Color), "Empty")] - public Color RightBorderColor - { - get { return this.GetSolidBorderColor(Sides.Right); } - set { this.SetBorder(Sides.Right, this.GetBorderWidth(Sides.Right), new SolidBrush(value)); } - } - - private Color GetSolidBorderColor(Sides side) - { - Brush b = this.GetBorderBrush(side); - if (b != null && b is SolidBrush) - return ((SolidBrush)b).Color; - else - return Color.Empty; - } - - #endregion - - #region Accessing - - /// - /// Get the padding for a particular side. 0 means no padding on that side. - /// Padding appears before the border does. - /// - /// Which side - /// The width of the padding - public float GetPadding(Sides side) - { - if (this.Padding.ContainsKey(side)) - return this.Padding[side]; - else - return 0.0f; - } - - /// - /// Set the padding for a particular side. 0 means no padding on that side. - /// - /// Which side - /// How much padding - public void SetPadding(Sides side, float value) - { - if (side == Sides.All) { - this.Padding[Sides.Left] = value; - this.Padding[Sides.Top] = value; - this.Padding[Sides.Right] = value; - this.Padding[Sides.Bottom] = value; - } else - this.Padding[side] = value; - } - - /// - /// Get the pen of the border on a particular side. - /// - /// Which side - /// The pen of the border - public Pen GetBorderPen(Sides side) - { - if (this.BorderPens.ContainsKey(side)) - return this.BorderPens[side]; - else - return null; - } - - /// - /// Get the width of the border on a particular side. 0 means no border on that side. - /// - /// Which side - /// The width of the border - public float GetBorderWidth(Sides side) - { - Pen p = this.GetBorderPen(side); - if (p == null) - return 0; - else - return p.Width; - } - - /// - /// Get the width of the border on a particular side. 0 means no border on that side. - /// - /// Which side - /// The width of the border - public Brush GetBorderBrush(Sides side) - { - Pen p = this.GetBorderPen(side); - if (p == null) - return null; - else - return p.Brush; - } - - /// - /// Change the brush and width of the border on a particular side. 0 means no border on that side. - /// - /// Which side - /// How wide should it be? - /// What brush should be used to paint it - public void SetBorder(Sides side, float width, Brush brush) - { - this.SetBorderPen(side, new Pen(brush, width)); - } - - /// - /// Change the pen of the border on a particular side. - /// - /// Which side - /// How wide should it be? - /// What pen should be used to draw it - public void SetBorderPen(Sides side, Pen p) - { - if (side == Sides.All) { - this.areSideBorderEqual = true; - this.BorderPens[Sides.Left] = p; - this.BorderPens[Sides.Top] = p; - this.BorderPens[Sides.Right] = p; - this.BorderPens[Sides.Bottom] = p; - } else { - this.areSideBorderEqual = false; - this.BorderPens[side] = p; - } - } - private bool areSideBorderEqual = false; - - /// - /// Get the distance that the text should be inset from the border on a given side - /// - /// Which side - /// Distance of text inset - public float GetTextInset(Sides side) - { - return GetKeyOrDefault(this.TextInsets, side, 0f); - } - - /// - /// Set the distance that the text should be inset from the border on a given side - /// - /// Which side - /// Distance of text inset - public void SetTextInset(Sides side, float value) - { - if (side == Sides.All) { - this.TextInsets[Sides.Left] = value; - this.TextInsets[Sides.Top] = value; - this.TextInsets[Sides.Right] = value; - this.TextInsets[Sides.Bottom] = value; - } else - this.TextInsets[side] = value; - } - - // I hate the fact that Dictionary doesn't have a method like this! - private ValueT GetKeyOrDefault(Dictionary map, KeyT key, ValueT defaultValue) - { - if (map.ContainsKey(key)) - return map[key]; - else - return defaultValue; - } - - private Dictionary BorderPens = new Dictionary(); - private Dictionary TextInsets = new Dictionary(); - private Dictionary Padding = new Dictionary(); - - #endregion - - #region Calculating - - /// - /// Calculate how height this block will be when its printed on one line - /// - /// The Graphic to use for renderering - /// - public float CalculateHeight(Graphics g) - { - return this.CalculateHeight(g, "Wy", 9999999); - } - - /// - /// Calculate how height this block will be when it prints the given string - /// to a maximum of the given width - /// - /// The Graphic to use for renderering - /// The string to be considered - /// The max width for the rendering - /// The height that will be used - public float CalculateHeight(Graphics g, String s, int width) - { - width -= (int)(this.GetTextInset(Sides.Left) + this.GetTextInset(Sides.Right) + 0.5f); - StringFormat fmt = new StringFormat(); - fmt.Trimming = StringTrimming.EllipsisCharacter; - if (!this.CanWrap) - fmt.FormatFlags = StringFormatFlags.NoWrap; - float height = g.MeasureString(s, this.FontOrDefault, width, fmt).Height; - height = Math.Max(height, this.MinimumTextHeight); - height += this.GetPadding(Sides.Top); - height += this.GetPadding(Sides.Bottom); - height += this.GetBorderWidth(Sides.Top); - height += this.GetBorderWidth(Sides.Bottom); - height += this.GetTextInset(Sides.Top); - height += this.GetTextInset(Sides.Bottom); - return height; - } - - private RectangleF ApplyInsets(RectangleF cell, float left, float top, float right, float bottom) - { - return new RectangleF(cell.X + left, - cell.Y + top, - cell.Width - (left + right), - cell.Height - (top + bottom)); - } - - /// - /// Given a bounding box return the box after applying the padding factors - /// - /// - /// - public RectangleF CalculatePaddedBox(RectangleF cell) - { - return this.ApplyInsets(cell, - this.GetPadding(Sides.Left), - this.GetPadding(Sides.Top), - this.GetPadding(Sides.Right), - this.GetPadding(Sides.Bottom)); - } - - /// - /// Given an already padded box, return the box into which the text will be drawn. - /// - /// - /// - public RectangleF CalculateBorderedBox(RectangleF cell) - { - return this.ApplyInsets(cell, - this.GetBorderWidth(Sides.Left), - this.GetBorderWidth(Sides.Top), - this.GetBorderWidth(Sides.Right), - this.GetBorderWidth(Sides.Bottom)); - } - - /// - /// Given an already padded and bordered box, return the box into which the text will be drawn. - /// - /// - /// - public RectangleF CalculateTextBox(RectangleF cell) - { - return this.ApplyInsets(cell, - this.GetTextInset(Sides.Left), - this.GetTextInset(Sides.Top), - this.GetTextInset(Sides.Right), - this.GetTextInset(Sides.Bottom)); - } - - /// - /// Apply paddeding and text insets to the given rectangle - /// - /// - /// - public RectangleF CalculatePaddedTextBox(RectangleF cell) - { - return this.CalculateTextBox(this.CalculateBorderedBox(this.CalculatePaddedBox(cell))); - } - - #endregion - - #region Rendering - - /// - /// Draw the given string aligned within the given cell - /// - /// Graphics to draw on - /// Cell into which the text is to be drawn - /// The string to be drawn - /// How should the string be aligned - public void Draw(Graphics g, RectangleF r, String s, HorizontalAlignment align) - { - switch (align) { - case HorizontalAlignment.Center: - this.Draw(g, r, null, s, null); - break; - case HorizontalAlignment.Left: - this.Draw(g, r, s, null, null); - break; - case HorizontalAlignment.Right: - this.Draw(g, r, null, null, s); - break; - default: - break; - } - } - - /// - /// Draw the array of strings so that the first string is left aligned, - /// the second is centered and the third is right aligned. All strings - /// are optional. Extra strings are ignored. - /// - /// Graphics to draw on - /// Cell into which the text is to be drawn - /// Array of strings - public void Draw(Graphics g, RectangleF r, String[] strings) - { - String left = null, centre = null, right = null; - - if (strings.Length >= 1) - left = strings[0]; - if (strings.Length >= 2) - centre = strings[1]; - if (strings.Length >= 3) - right = strings[2]; - - this.Draw(g, r, left, centre, right); - } - - public void Draw(Graphics g, RectangleF r, String left, String centre, String right) - { - RectangleF paddedRect = this.CalculatePaddedBox(r); - RectangleF paddedBorderedRect = this.CalculateBorderedBox(paddedRect); - this.DrawBackground(g, paddedBorderedRect); - this.DrawText(g, paddedBorderedRect, left, centre, right); - this.DrawBorder(g, paddedRect); - //g.DrawRectangle(new Pen(Color.Red, 0.5f), r.X, r.Y, r.Width, r.Height); - } - - private void DrawBackground(Graphics g, RectangleF r) - { - if (this.BackgroundBrush != null) { - // Enlarge the background area by half the border widths on each side - RectangleF r2 = this.ApplyInsets(r, - this.GetBorderWidth(Sides.Left) / -2, - this.GetBorderWidth(Sides.Top) / -2, - this.GetBorderWidth(Sides.Right) / -2, - this.GetBorderWidth(Sides.Bottom) / -2); - this.DrawFilledRectangle(g, this.BackgroundBrush, r2); - } - } - - private void DrawBorder(Graphics g, RectangleF r) - { - if (this.areSideBorderEqual && this.GetBorderPen(Sides.Top) != null) { - Pen p = this.GetBorderPen(Sides.Top); - this.DrawOneBorder(g, Sides.Top, r.X, r.Y, r.Width, r.Height, true); - } else { - this.DrawOneBorder(g, Sides.Top, r.X, r.Y, r.Right, r.Y, false); - this.DrawOneBorder(g, Sides.Bottom, r.X, r.Bottom, r.Right, r.Bottom, false); - this.DrawOneBorder(g, Sides.Left, r.X, r.Y, r.X, r.Bottom, false); - this.DrawOneBorder(g, Sides.Right, r.Right, r.Y, r.Right, r.Bottom, false); - } - } - - private void DrawOneBorder(Graphics g, Sides side, float x1, float y1, float x2, float y2, bool isRectangle) - { - Pen p = this.GetBorderPen(side); - - if (p == null) - return; - - if (p.Brush is LinearGradientBrush) { - LinearGradientBrush lgr = (LinearGradientBrush)p.Brush; - LinearGradientBrush lgr2 = new LinearGradientBrush(new PointF(x1, y1), new PointF(x2, y2), lgr.LinearColors[0], lgr.LinearColors[1]); - lgr2.Blend = lgr.Blend; - lgr2.WrapMode = WrapMode.TileFlipXY; - p.Brush = lgr2; - } - - if (isRectangle) - g.DrawRectangle(p, x1, y1, x2, y2); - else - g.DrawLine(p, x1, y1, x2, y2); - } - - private void DrawFilledRectangle(Graphics g, Brush brush, RectangleF r) - { - if (brush is LinearGradientBrush) { - LinearGradientBrush lgr = (LinearGradientBrush)brush; - LinearGradientBrush lgr2 = new LinearGradientBrush(r, lgr.LinearColors[0], lgr.LinearColors[1], 0f); - lgr2.Blend = lgr.Blend; - lgr2.WrapMode = WrapMode.TileFlipXY; - g.FillRectangle(lgr2, r); - } else - g.FillRectangle(brush, r); - } - - private void DrawText(Graphics g, RectangleF r, string left, string centre, string right) - { - RectangleF textRect = this.CalculateTextBox(r); - Font font = this.FontOrDefault; - Brush textBrush = this.TextBrushOrDefault; - - StringFormat fmt = new StringFormat(); - if (!this.CanWrap) - fmt.FormatFlags = StringFormatFlags.NoWrap; - fmt.LineAlignment = StringAlignment.Center; - fmt.Trimming = StringTrimming.EllipsisCharacter; - - if (!String.IsNullOrEmpty(left)) { - fmt.Alignment = StringAlignment.Near; - g.DrawString(left, font, textBrush, textRect, fmt); - } - - if (!String.IsNullOrEmpty(centre)) { - fmt.Alignment = StringAlignment.Center; - g.DrawString(centre, font, textBrush, textRect, fmt); - } - - if (!String.IsNullOrEmpty(right)) { - fmt.Alignment = StringAlignment.Far; - g.DrawString(right, font, textBrush, textRect, fmt); - } - //g.DrawRectangle(new Pen(Color.Red, 0.5f), textRect.X, textRect.Y, textRect.Width, textRect.Height); - //g.FillRectangle(Brushes.Red, r); - } - - #endregion - - #region Standard formatting styles - - /// - /// Return the default style for cells - /// - static public BlockFormat DefaultCell() - { - BlockFormat fmt = new BlockFormat(); - - fmt.Font = new Font("MS Sans Serif", 9); - //fmt.TextBrush = Brushes.Black; - fmt.SetBorderPen(Sides.All, new Pen(Color.Blue, 0.5f)); - fmt.SetTextInset(Sides.All, 2); - fmt.CanWrap = true; - - return fmt; - } - - /// - /// Return a minimal set of formatting values. - /// - static public BlockFormat Minimal() - { - return BlockFormat.Minimal(new Font("Times New Roman", 12)); - } - static public BlockFormat Minimal(Font f) - { - BlockFormat fmt = new BlockFormat(); - - fmt.Font = f; - fmt.TextBrush = Brushes.Black; - fmt.SetBorderPen(Sides.All, new Pen(Color.Gray, 0.5f)); - fmt.SetTextInset(Sides.All, 3.0f); - - return fmt; - } - - /// - /// Return a set of formatting values that draws boxes - /// - static public BlockFormat Box() - { - return BlockFormat.Box(new Font("Verdana", 24)); - } - static public BlockFormat Box(Font f) - { - BlockFormat fmt = new BlockFormat(); - - fmt.Font = f; - fmt.TextBrush = Brushes.Black; - fmt.SetBorderPen(Sides.All, new Pen(Color.Black, 0.5f)); - fmt.BackgroundBrush = Brushes.LightBlue; - fmt.SetTextInset(Sides.All, 3.0f); - - return fmt; - } - - /// - /// Return a format that will nicely print headers. - /// - static public BlockFormat Header() - { - return BlockFormat.Header(new Font("Verdana", 24)); - } - static public BlockFormat Header(Font f) - { - BlockFormat fmt = new BlockFormat(); - - fmt.Font = f; - fmt.TextBrush = Brushes.WhiteSmoke; - fmt.BackgroundBrush = new LinearGradientBrush(new Point(1, 1), new Point(2, 2), Color.DarkBlue, Color.WhiteSmoke); - fmt.SetTextInset(Sides.All, 3.0f); - fmt.SetPadding(Sides.Bottom, 10); - - return fmt; - } - - /// - /// Return a format that will nicely print report footers. - /// - static public BlockFormat Footer() - { - return BlockFormat.Footer(new Font("Verdana", 10, FontStyle.Italic)); - } - static public BlockFormat Footer(Font f) - { - BlockFormat fmt = new BlockFormat(); - - fmt.Font = f; - fmt.TextBrush = Brushes.Black; - fmt.SetPadding(Sides.Top, 10); - fmt.SetBorderPen(Sides.Top, new Pen(Color.Gray, 0.5f)); - fmt.SetTextInset(Sides.All, 3.0f); - - return fmt; - } - - /// - /// Return a format that will nicely print list headers. - /// - static public BlockFormat ListHeader() - { - return BlockFormat.ListHeader(new Font("Verdana", 12)); - } - static public BlockFormat ListHeader(Font f) - { - BlockFormat fmt = new BlockFormat(); - - fmt.Font = f; - fmt.TextBrush = Brushes.Black; - fmt.BackgroundBrush = Brushes.LightGray; - fmt.SetBorderPen(Sides.All, new Pen(Color.DarkGray, 1.5f)); - fmt.SetTextInset(Sides.All, 1.0f); - - fmt.CanWrap = true; - - return fmt; - } - - /// - /// Return a format that will nicely print group headers. - /// - static public BlockFormat GroupHeader() - { - return BlockFormat.GroupHeader(new Font("Verdana", 10, FontStyle.Bold)); - } - static public BlockFormat GroupHeader(Font f) - { - BlockFormat fmt = new BlockFormat(); - - fmt.Font = f; - fmt.TextBrush = Brushes.Black; - fmt.SetPadding(Sides.Top, f.Height / 2); - fmt.SetPadding(Sides.Bottom, f.Height / 2); - fmt.SetBorder(Sides.Bottom, 3f, new LinearGradientBrush(new Point(1, 1), new Point(2, 2), Color.DarkBlue, Color.White)); - fmt.SetTextInset(Sides.All, 1.0f); - - return fmt; - } - - #endregion - } -} diff --git a/code/PTM/View/Controls/ObjectListViewComponents/ObjectListView.cs b/code/PTM/View/Controls/ObjectListViewComponents/ObjectListView.cs deleted file mode 100644 index 421c6ec..0000000 --- a/code/PTM/View/Controls/ObjectListViewComponents/ObjectListView.cs +++ /dev/null @@ -1,6485 +0,0 @@ -/* - * ObjectListView - A listview to show various aspects of a collection of objects - * - * Author: Phillip Piper - * Date: 9/10/2006 11:15 AM - * - * Change log: - * 2008-01-29 JPP - Fixed bug that caused owner-drawn virtual lists to use 100% CPU - * - Added FlagRenderer to help draw bitwise-OR'ed flag values - * 2008-01-23 JPP - Fixed bug (introduced in v1.9) that made alternate row colour with groups not quite right - * - Ensure that DesignerSerializationVisibility.Hidden is set on all non-browsable properties - * - Make sure that sort indicators are shown after changing which columns are visible - * 2008-01-21 JPP - Added FastObjectListView - * v1.9 - * 2008-01-18 JPP - Added IncrementalUpdate() - * 2008-01-16 JPP - Right clicking on column header will allow the user to choose which columns are visible. - * Set SelectColumnsOnRightClick to false to prevent this behaviour. - * - Added ImagesRenderer to draw more than one images in a column - * - Changed the positioning of the empty list msg to use all the client area. Thanks to Matze. - * 2007-12-13 JPP - Added CopySelectionToClipboard(). Ctrl-C invokes this method. Supports text - * and HTML formats. - * 2007-12-12 JPP - Added support for checkboxes via CheckStateGetter and CheckStatePutter properties. - * - Made ObjectListView and OLVColumn into partial classes so that others can extend them. - * 2007-12-09 JPP - Added ability to have hidden columns, i.e. columns that the ObjectListView knows - * about but that are not visible to the user. Controlled by OLVColumn.IsVisible. - * Added ColumnSelectionForm to the project to show how it could be used in an application. - * - * v1.8 - * 2007-11-26 JPP - Cell editing fully functional - * 2007-11-21 JPP - Added SelectionChanged event. This event is triggered once when the - * selection changes, no matter how many items are selected or deselected (in - * contrast to SelectedIndexChanged which is called once for every row that - * is selected or deselected). Thanks to lupokehl42 (Daniel) for his suggestions and - * improvements on this idea. - * 2007-11-19 JPP - First take at cell editing - * 2007-11-17 JPP - Changed so that items within a group are not sorted if lastSortOrder == None - * - Only call MakeSortIndicatorImages() if we haven't already made the sort indicators - * (Corrected misspelling in the name of the method too) - * 2007-11-06 JPP - Added ability to have secondary sort criteria when sorting - * (SecondarySortColumn and SecondarySortOrder properties) - * - Added SortGroupItemsByPrimaryColumn to allow group items to be sorted by the - * primary column. Previous default was to sort by the grouping column. - * v1.7 - * No big changes to this version but made to work with ListViewPrinter and released with it. - * - * 2007-11-05 JPP - Changed BaseRenderer to use DrawString() rather than TextRenderer, since TextRenderer - * does not work when printing. - * v1.6 - * 2007-11-03 JPP - Fixed some bugs in the rebuilding of DataListView. - * 2007-10-31 JPP - Changed to use builtin sort indicators on XP and later. This also avoids alignment - * problems on Vista. (thanks to gravybod for the suggestion and example implementation) - * 2007-10-21 JPP - Added MinimumWidth and MaximumWidth properties to OLVColumn. - * - Added ability for BuildList() to preserve selection. Calling BuildList() directly - * tries to preserve selection; calling SetObjects() does not. - * - Added SelectAll() and DeselectAll() methods. Useful for working with large lists. - * 2007-10-08 JPP - Added GetNextItem() and GetPreviousItem(), which walk sequentially through the - * listview items, even when the view is grouped. - * - Added SelectedItem property - * 2007-09-28 JPP - Optimized aspect-to-string conversion. BuildList() 15% faster. - * - Added empty implementation of RefreshObjects() to VirtualObjectListView since - * RefreshObjects() cannot work on virtual lists. - * 2007-09-13 JPP - Corrected bug with custom sorter in VirtualObjectListView (thanks for mpgjunky) - * 2007-09-07 JPP - Corrected image scaling bug in DrawAlignedImage() (thanks to krita970) - * 2007-08-29 JPP - Allow item count labels on groups to be set per column (thanks to cmarlow for idea) - * 2007-08-14 JPP - Major rework of DataListView based on Ian Griffiths's great work - * 2007-08-11 JPP - When empty, the control can now draw a "List Empty" message - * - Added GetColumn() and GetItem() methods - * v1.5 - * 2007-08-03 JPP - Support animated GIFs in ImageRenderer - * - Allow height of rows to be specified - EXPERIMENTAL! - * 2007-07-26 JPP - Optimised redrawing of owner-drawn lists by remembering the update rect - * - Allow sort indicators to be turned off - * 2007-06-30 JPP - Added RowFormatter delegate - * - Allow a different label when there is only one item in a group (thanks to cmarlow) - * v1.4 - * 2007-04-12 JPP - Allow owner drawn on steriods! - * - Column headers now display sort indicators - * - ImageGetter delegates can now return ints, strings or Images - * (Images are only visible if the list is owner drawn) - * - Added OLVColumn.MakeGroupies to help with group partitioning - * - All normal listview views are now supported - * - Allow dotted aspect names, e.g. Owner.Workgroup.Name (thanks to OlafD) - * - Added SelectedObject and SelectedObjects properties - * v1.3 - * 2007-03-01 JPP - Added DataListView - * - Added VirtualObjectListView - * - Added Freeze/Unfreeze capabilities - * - Allowed sort handler to be installed - * - Simplified sort comparisons: handles 95% of cases with only 6 lines of code! - * - Fixed bug with alternative line colors on unsorted lists (thanks to cmarlow) - * 2007-01-13 JPP - Fixed bug with lastSortOrder (thanks to Kwan Fu Sit) - * - Non-OLVColumns are no longer allowed - * 2007-01-04 JPP - Clear sorter before rebuilding list. 10x faster! (thanks to aaberg) - * - Include GetField in GetAspectByName() so field values can be Invoked too. - * - Fixed subtle bug in RefreshItem() that erased background colors. - * 2006-11-01 JPP - Added alternate line colouring - * 2006-10-20 JPP - Refactored all sorting comparisons and made it extendable. See ComparerManager. - * - Improved IDE integration - * - Made control DoubleBuffered - * - Added object selection methods - * 2006-10-13 JPP Implemented grouping and column sorting - * 2006-10-09 JPP Initial version - * - * CONDITIONS OF USE - * This code may be freely used for non-commercial purposes. Commerical use requires a one-time - * license fee obtainable from phillip_piper@bigfoot.com. In all cases, this code must be kept intact, - * complete with this header and conditions of use. - * - * Copyright (C) 2006-2008 Phillip Piper - * - * TO DO: - * - Incremental update. List has changed slightly (a couple of new items, a couple of items removed) - * Do this efficiently. Maintain sort order, scroll position, everything. - * - Extend MappedImageRender to be able to draw more than image if its Aspect returns an ICollection. - */ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Diagnostics; -using System.Drawing; -using System.Drawing.Design; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Windows.Forms; - -namespace BrightIdeasSoftware -{ - /// - /// An object list displays 'aspects' of a collection of objects in a listview control. - /// - /// - /// - /// The intelligence for this control is in the columns. OLVColumns are - /// extended so they understand how to fetch an 'aspect' from each row - /// object. They also understand how to sort by their aspect, and - /// how to group them. - /// - /// - /// Aspects are extracted by giving the name of a method to be called or a - /// property to be fetched. These names can be simple names or they can be dotted - /// to chain property access e.g. "Owner.Address.Postcode". - /// Aspects can also be extracted by installing a delegate. - /// - /// - /// Sorting by column clicking and grouping by column are handled automatically. - /// - /// - /// This list puts sort indicators in the column headers to show the column sorting direction. - /// If you wish to replace the standard images with your own images, put entries in the small image list - /// with the key values "sort-indicator-up" and "sort-indicator-down". - /// - /// - /// For these classes to build correctly, the project must have references to these assemblies: - /// - /// System.Data - /// System.Design - /// System.Drawing - /// System.Windows.Forms (obviously) - /// - /// - /// - public partial class ObjectListView : ListView, ISupportInitialize - { - /// - /// Create an ObjectListView - /// - public ObjectListView() - : base() - { - this.ColumnClick += new ColumnClickEventHandler(this.HandleColumnClick); - this.ItemCheck += new ItemCheckEventHandler(this.HandleItemCheck); - - base.View = View.Details; - this.DoubleBuffered = true; // kill nasty flickers. hiss... me hates 'em - this.AlternateRowBackColor = Color.Empty; - this.ShowSortIndicators = true; - } - - #region Public properties - - /// - /// Get/set the collection of objects that this list will show - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public IEnumerable Objects - { - get { return this.objects; } - set { this.SetObjects(value); } - } - - /// - /// Get or set all the columns that this control knows about. - /// Only those columns where IsVisible is true will be seen by the user. - /// - /// If you want to add new columns programmatically, add them to - /// AllColumns and then call RebuildColumns(). Normally, you do not have to - /// deal with this property directly. Just use the IDE. - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] - public List AllColumns - { - get - { - // If someone has wiped out the columns, put the list back - if (allColumns == null) - allColumns = new List(); - - // If we don't know the columns, use the columns from the control. - // This handles legacy cases - if (allColumns.Count == 0 && this.Columns.Count > 0) { - for (int i = 0; i < this.Columns.Count; i++) { - this.allColumns.Add(this.GetColumn(i)); - } - } - return allColumns; - } - set { allColumns = value; } - } - private List allColumns = new List(); - - /// - /// Return the visible columns in the order they are displayed to the user - /// - [Browsable(false)] - public List ColumnsInDisplayOrder - { - get - { - List columnsInDisplayOrder = new List(this.Columns.Count); - for (int i = 0; i < this.Columns.Count; i++) - columnsInDisplayOrder.Add(null); - for (int i = 0; i < this.Columns.Count; i++) { - OLVColumn col = this.GetColumn(i); - columnsInDisplayOrder[col.DisplayIndex] = col; - } - return columnsInDisplayOrder; - } - } - - /// - /// If there are no items in this list view, what message should be drawn onto the control? - /// - [Category("Appearance"), - Description("When the list has no items, show this message in the control"), - DefaultValue("")] - public String EmptyListMsg - { - get { return emptyListMsg; } - set { - if (emptyListMsg != value) { - emptyListMsg = value; - this.Invalidate(); - } - } - } - private String emptyListMsg = ""; - - /// - /// What font should the 'list empty' message be drawn in? - /// - [Category("Appearance"), - Description("What font should the 'list empty' message be drawn in?"), - DefaultValue(null)] - public Font EmptyListMsgFont - { - get { return emptyListMsgFont; } - set { emptyListMsgFont = value; } - } - private Font emptyListMsgFont; - - /// - /// Return the font for the 'list empty' message or a default - /// - [Browsable(false)] - public Font EmptyListMsgFontOrDefault - { - get { - if (this.EmptyListMsgFont == null) - return new Font("Tahoma", 14); - else - return this.EmptyListMsgFont; - } - } - - /// - /// Does this listview have a message that should be drawn when the list is empty? - /// - [Browsable(false)] - public bool HasEmptyListMsg - { - get { return !String.IsNullOrEmpty(this.EmptyListMsg); } - } - - /// - /// Should the list view show images on subitems? - /// - /// - /// Under Windows, this works by sending messages to the underlying - /// Windows control. To make this work under Mono, we would have to owner drawing the items :-( - [Category("Behavior"), - Description("Should the list view show images on subitems?"), - DefaultValue(false)] - public bool ShowImagesOnSubItems - { - get { return showImagesOnSubItems; } - set { showImagesOnSubItems = value; } - } - - /// - /// This property controls whether group labels will be suffixed with a count of items. - /// - /// - /// The format of the suffix is controlled by GroupWithItemCountFormat/GroupWithItemCountSingularFormat properties - /// - [Category("Behavior"), - Description("Will group titles be suffixed with a count of the items in the group?"), - DefaultValue(false)] - public bool ShowItemCountOnGroups { - get { return showItemCountOnGroups; } - set { showItemCountOnGroups = value; } - } - - /// - /// When a group title has an item count, how should the lable be formatted? - /// - /// - /// The given format string can/should have two placeholders: - /// - /// {0} - the original group title - /// {1} - the number of items in the group - /// - /// - /// "{0} [{1} items]" - [Category("Behavior"), - Description("The format to use when suffixing item counts to group titles"), - DefaultValue(null)] - public string GroupWithItemCountFormat - { - get { return groupWithItemCountFormat; } - set { groupWithItemCountFormat = value; } - } - - /// - /// Return this.GroupWithItemCountFormat or a reasonable default - /// - [Browsable(false)] - public string GroupWithItemCountFormatOrDefault - { - get { - if (String.IsNullOrEmpty(this.GroupWithItemCountFormat)) - return "{0} [{1} items]"; - else - return this.GroupWithItemCountFormat; - } - } - - /// - /// When a group title has an item count, how should the lable be formatted if - /// there is only one item in the group? - /// - /// - /// The given format string can/should have two placeholders: - /// - /// {0} - the original group title - /// {1} - the number of items in the group (always 1) - /// - /// - /// "{0} [{1} item]" - [Category("Behavior"), - Description("The format to use when suffixing item counts to group titles"), - DefaultValue(null)] - public string GroupWithItemCountSingularFormat - { - get { return groupWithItemCountSingularFormat; } - set { groupWithItemCountSingularFormat = value; } - } - - /// - /// Return this.GroupWithItemCountSingularFormat or a reasonable default - /// - [Browsable(false)] - public string GroupWithItemCountSingularFormatOrDefault - { - get { - if (String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat)) - return "{0} [{1} item]"; - else - return this.GroupWithItemCountSingularFormat; - } - } - - /// - /// Should the list give a different background color to every second row? - /// - /// The color of the alternate rows is given by AlternateRowBackColor. - /// There is a "feature" in .NET for listviews in non-full-row-select mode, where - /// selected rows are not drawn with their correct background color. - [Category("Appearance"), - Description("Should the list view use a different backcolor to alternate rows?"), - DefaultValue(false)] - public bool UseAlternatingBackColors { - get { return useAlternatingBackColors; } - set { useAlternatingBackColors = value; } - } - - /// - /// Should the list view show a bitmap in the column header to show the sort direction? - /// - [Category("Behavior"), - Description("Should the list view show sort indicators in the column headers?"), - DefaultValue(true)] - public bool ShowSortIndicators - { - get { return showSortIndicators; } - set { showSortIndicators = value; } - } - - /// - /// If every second row has a background different to the control, what color should it be? - /// - [Category("Appearance"), - Description("If using alternate colors, what color should alterate rows be?"), - DefaultValue(typeof(Color), "Empty")] - public Color AlternateRowBackColor { - get { return alternateRowBackColor; } - set { alternateRowBackColor = value; } - } - - /// - /// Return the alternate row background color that has been set, or the default color - /// - [Browsable(false)] - public Color AlternateRowBackColorOrDefault { - get { - if (alternateRowBackColor == Color.Empty) - return Color.LemonChiffon; - else - return alternateRowBackColor; - } - } - - /// - /// Get or set whether or not the listview is frozen. When the listview is - /// frozen, it will not update itself. - /// - /// The Frozen property is similar to the methods Freeze()/Unfreeze() - /// except that changes to the Frozen property do not nest. - /// objectListView1.Frozen = false; // unfreeze the control regardless of the number of Freeze() calls - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public bool Frozen - { - get { return freezeCount > 0; } - set { - if (value) - Freeze(); - else if (freezeCount > 0) { - freezeCount = 1; - Unfreeze(); - } - } - } - private int freezeCount = 0; - - /// - /// Get/set the list of columns that should be used when the list switches to tile view. - /// - /// If no list of columns has been installed, this value will default to the - /// first column plus any column where IsTileViewColumn is true. - [Browsable(false), - Obsolete("Use GetFilteredColumns() and OLVColumn.IsTileViewColumn instead"), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public List ColumnsForTileView - { - get { return this.GetFilteredColumns(View.Tile); } - } - - /// - /// Get the ListViewItem that is currently selected . If no row is selected, or more than one is selected, return null. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public ListViewItem SelectedItem - { - get { - if (this.SelectedIndices.Count == 1) - return this.GetItem(this.SelectedIndices[0]); - else - return null; - } - set { - this.SelectedIndices.Clear(); - if (value != null) - this.SelectedIndices.Add(value.Index); - } - } - - /// - /// Get the model object from the currently selected row. If no row is selected, or more than one is selected, return null. - /// Select the row that is displaying the given model object. All other rows are deselected. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public Object SelectedObject - { - get { return this.GetSelectedObject(); } - set { this.SelectObject(value); } - } - - /// - /// Get the model objects from the currently selected rows. If no row is selected, the returned List will be empty. - /// When setting this value, select the rows that is displaying the given model objects. All other rows are deselected. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public ArrayList SelectedObjects - { - get { return this.GetSelectedObjects(); } - set { this.SelectObjects(value); } - } - - /// - /// Get/set the style of view that this listview is using - /// - /// Switching to tile or details view installs the columns appropriate to that view. - /// Confusingly, in tile view, every column is shown as a row of information. - new public View View - { - get { return base.View; } - set { - if (base.View == value) - return; - - if (this.Frozen) { - base.View = value; - return; - } - - this.Freeze(); - - // If we are switching to a Detail or Tile view, setup the columns needed for that view - if (value == View.Details || value == View.Tile) { - this.ChangeToFilteredColumns(value); - - if (value == View.Tile) - this.CalculateReasonableTileSize(); - } - - base.View = value; - this.Unfreeze(); - } - } - - /// - /// Specify the height of each row in the control in pixels. - /// - /// The row height in a listview is normally determined by the font size and the small image list size. - /// This setting allows that calculation to be overridden (within reason: you still cannot set the line height to be - /// less than the line height of the font used in the control). - /// Setting it to -1 means use the normal calculation method. - /// This feature is experiemental! Strange things may happen to your program, your spouse or your pet if you use it. - /// - [Category("Appearance"), - DefaultValue(-1)] - public int RowHeight - { - get { return rowHeight; } - set { - if (value < 1) - rowHeight = -1; - else - rowHeight = value; - this.SetupExternalImageList(); - } - } - private int rowHeight = -1; - - /// - /// Override the SmallImageList property so we can correctly shadow its operations. - /// - /// If you use the RowHeight property to specify the row height, the SmallImageList - /// must be fully initialised before setting/changing the RowHeight. If you add new images to the image - /// list after setting the RowHeight, you must assign the imagelist to the control again. Something as simple - /// as this will work: - /// listView1.SmallImageList = listView1.SmallImageList; - /// - new public ImageList SmallImageList - { - get { return this.shadowedImageList; } - set { - this.shadowedImageList = value; - this.SetupExternalImageList(); - } - } - private ImageList shadowedImageList = null; - - /// - /// Give access to the image list that is actually being used by the control - /// - [Browsable(false)] - public ImageList BaseSmallImageList - { - get { return base.SmallImageList; } - } - - /// - /// Get/set the column that will be used to resolve comparisons that are equal when sorting. - /// - /// There is no user interface for this setting. It must be set programmatically. - /// The default is the first column. - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public OLVColumn SecondarySortColumn - { - get { - if (this.secondarySortColumn == null) { - if (this.Columns.Count > 0) - return this.GetColumn(0); - else - return null; - } else - return this.secondarySortColumn; - } - set { - this.secondarySortColumn = value; - } - } - private OLVColumn secondarySortColumn; - - /// - /// When the SecondarySortColumn is used, in what order will it compare results? - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public SortOrder SecondarySortOrder - { - get { return this.secondarySortOrder; } - set { this.secondarySortOrder = value; } - } - private SortOrder secondarySortOrder = SortOrder.Ascending; - - /// - /// When the listview is grouped, should the items be sorted by the primary column? - /// If this is false, the items will be sorted by the same column as they are grouped. - /// - [Category("Behavior"), - Description("When the listview is grouped, should the items be sorted by the primary column? If this is false, the items will be sorted by the same column as they are grouped."), - DefaultValue(true)] - public bool SortGroupItemsByPrimaryColumn - { - get { return this.sortGroupItemsByPrimaryColumn; } - set { this.sortGroupItemsByPrimaryColumn = value; } - } - private bool sortGroupItemsByPrimaryColumn = true; - - /// - /// How does a user indicate that they want to edit cells? - /// - public enum CellEditActivateMode - { - /// - /// This list cannot be edited. F2 does nothing. - /// - None = 0, - - /// - /// A single click on a subitem will edit the value. Single clicking the primary column, - /// selects the row just like normal. The user must press F2 to edit the primary column. - /// - SingleClick = 1, - - /// - /// Double clicking a subitem or the primary column will edit that cell. - /// F2 will edit the primary column. - /// - DoubleClick = 2, - - /// - /// Pressing F2 is the only way to edit the cells. Once the primary column is being edited, - /// the other cells in the row can be edited by pressing Tab. - /// - F2Only = 3 - } - - /// - /// How does the user indicate that they want to edit a cell? - /// None means that the listview cannot be edited. - /// - /// Columns can also be marked as editable. - [Category("Behavior"), - Description("How does the user indicate that they want to edit a cell?"), - DefaultValue(CellEditActivateMode.None)] - public CellEditActivateMode CellEditActivation - { - get { return cellEditActivation; } - set { cellEditActivation = value; } - } - private CellEditActivateMode cellEditActivation = CellEditActivateMode.None; - - /// - /// Return true if a cell edit operation is currently happening - /// - [Browsable(false)] - public bool IsCellEditing - { - get { return this.cellEditor != null; } - } - - /// - /// When the user right clicks on the column headers, should a menu be presented which will allow - /// them to choose which columns will be shown in the view? - /// - [Category("Behavior"), - Description("When the user right clicks on the column headers, should a menu be presented which will allow them to choose which columns will be shown in the view?"), - DefaultValue(true)] - public bool SelectColumnsOnRightClick - { - get { return selectColumnsOnRightClick; } - set { selectColumnsOnRightClick = value; } - } - private bool selectColumnsOnRightClick = true; - - /// - /// When the column select menu is open, should it stay open after an item is selected? - /// Staying open allows the user to turn more than one column on or off at a time. - /// - [Category("Behavior"), - Description("When the column select menu is open, should it stay open after an item is selected?"), - DefaultValue(true)] - public bool SelectColumnsMenuStaysOpen - { - get { return selectColumnsMenuStaysOpen; } - set { selectColumnsMenuStaysOpen = value; } - } - private bool selectColumnsMenuStaysOpen = true; - - #endregion - - #region Callbacks - - /// - /// This delegate can be used to sort the table in a custom fasion. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public SortDelegate CustomSorter - { - get { return customSorter; } - set { customSorter = value; } - } - - /// - /// This delegate can be used to format a OLVListItem before it is added to the control. - /// - /// - /// The model object for the row can be found through the RowObject property of the OLVListItem object. - /// All subitems normally have the same style as list item, so setting the forecolor on one - /// subitem changes the forecolor of all subitems. - /// To allow subitems to have different attributes, ListViewItem.UseItemStyleForSubItems - /// must be set to false. - /// - /// If UseAlternatingBackColors is true, the backcolor of the listitem will be calculated - /// by the control and cannot be controlled by the RowFormatter delegate. In general, trying to use a RowFormatter - /// when UseAlternatingBackColors is true does not work well. - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public RowFormatterDelegate RowFormatter - { - get { return rowFormatter; } - set { rowFormatter = value; } - } - private RowFormatterDelegate rowFormatter; - - /// - /// This delegate will be called whenever the ObjectListView needs to know the check state - /// of the row associated with a given model object - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public CheckStateGetterDelegate CheckStateGetter - { - get { return checkStateGetter; } - set { checkStateGetter = value; } - } - private CheckStateGetterDelegate checkStateGetter; - - /// - /// This delegate will be called whenever the user tries to change the check state - /// of a row. The delegate should return the value that the listview should actuall - /// use, which may be different to the one given to the delegate. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public CheckStatePutterDelegate CheckStatePutter - { - get { return checkStatePutter; } - set { checkStatePutter = value; } - } - private CheckStatePutterDelegate checkStatePutter; - - #endregion - - #region List commands - - private delegate void SetObjectsInvoker(IEnumerable collection); - - /// - /// Set the collection of objects that will be shown in this list view. - /// - /// The list is updated immediately - /// The objects to be displayed - virtual public void SetObjects (IEnumerable collection) - { - if (this.InvokeRequired) { - this.Invoke(new SetObjectsInvoker(this.SetObjects), new object [] {collection}); - return; - } - this.objects = collection; - this.BuildList(false); - } - - /// - /// Update the list to reflect the contents of the given collection, without affecting - /// the scrolling position, selection or sort order. - /// - /// The objects to be displayed - /// - /// This method is about twice as slow as SetObjects(). - /// This method is experimental -- it may disappear in later versions of the code. - /// There has to be a better way to do this! JPP 15/1/2008 - /// In most situations, if you need this functionality, use a FastObjectListView instead. JPP 2/2/2008 - /// - virtual public void IncrementalUpdate(IEnumerable collection) - { - if (this.InvokeRequired) { - this.Invoke(new SetObjectsInvoker(this.IncrementalUpdate), new object[] { collection }); - return; - } - - this.BeginUpdate(); - - this.ListViewItemSorter = null; - ArrayList previousSelection = this.SelectedObjects; - - // Replace existing rows, creating new listviewitems if we get to the end of the list - List newItems = new List(); - int rowIndex = 0; - int itemCount = this.Items.Count; - foreach (object model in collection) { - if (rowIndex < itemCount) { - OLVListItem lvi = this.GetItem(rowIndex); - lvi.RowObject = model; - this.RefreshItem(lvi); - } else { - OLVListItem lvi = new OLVListItem(model); - this.FillInValues(lvi, model); - newItems.Add(lvi); - } - rowIndex++; - } - - // Delete any excess rows - int numRowsToDelete = itemCount - rowIndex; - for (int i = 0; i < numRowsToDelete; i++) - this.Items.RemoveAt(rowIndex); - - this.Items.AddRange(newItems.ToArray()); - this.Sort(this.lastSortColumn); - - SetAllSubItemImages(); - - this.SelectedObjects = previousSelection; - - this.EndUpdate(); - - this.objects = collection; - } - - /// - /// Build/rebuild all the list view items in the list - /// - virtual public void BuildList() - { - this.BuildList(true); - } - - /// - /// Build/rebuild all the list view items in the list - /// - /// If this is true, the control will try to preserve the selection, - /// i.e. all objects that were selected before the call will be selected after the rebuild - /// Use this method in situations were the contents of the list is basically the same - /// as previously. - virtual public void BuildList(bool shouldPreserveSelection) - { - if (this.Frozen) - return; - - ArrayList previousSelection = new ArrayList(); - if (shouldPreserveSelection && this.objects != null) { - previousSelection = this.SelectedObjects; - } - - this.BeginUpdate(); - this.Items.Clear(); - this.ListViewItemSorter = null; - - if (this.objects != null) { - // Build a list of all our items and then display them. (Building - // a list and then doing one AddRange is about 10-15% faster than individual adds) - List l = new List(); - foreach (object rowObject in this.objects) { - OLVListItem lvi = new OLVListItem(rowObject); - this.FillInValues(lvi, rowObject); - l.Add(lvi); - } - this.Items.AddRange(l.ToArray()); - this.SetAllSubItemImages(); - this.Sort(this.lastSortColumn); - - if (shouldPreserveSelection) { - this.SelectedObjects = previousSelection; - } - } - - this.EndUpdate(); - } - - /// - /// Sort the items by the last sort column - /// - new public void Sort() - { - this.Sort(this.lastSortColumn); - } - - /// - /// Organise the view items into groups, based on the last sort column or the first column - /// if there is no last sort column - /// - public void BuildGroups() - { - this.BuildGroups(this.lastSortColumn); - } - - /// - /// Organise the view items into groups, based on the given column - /// - /// The column whose values should be used for sorting. - virtual public void BuildGroups(OLVColumn column) - { - if (column == null) - column = this.GetColumn(0); - - this.Groups.Clear(); - - // Getting the Count forces any internal cache of the ListView to be flushed. Without - // this, iterating over the Items will not work correctly if the ListView handle - // has not yet been created. - int dummy = this.Items.Count; - - // Separate the list view items into groups, using the group key as the descrimanent - Dictionary> map = new Dictionary>(); - foreach (OLVListItem olvi in this.Items) { - object key = column.GetGroupKey(olvi.RowObject); - if (key == null) - key = "{null}"; // null can't be used as the key for a dictionary - if (!map.ContainsKey(key)) - map[key] = new List(); - map[key].Add(olvi); - } - - // Make a list of the required groups - List groups = new List(); - foreach (object key in map.Keys) { - ListViewGroup lvg = new ListViewGroup(column.ConvertGroupKeyToTitle(key)); - lvg.Tag = key; - groups.Add(lvg); - } - - // Sort the groups - groups.Sort(new ListViewGroupComparer(this.lastSortOrder)); - - // Put each group into the list view, and give each group its member items. - // The order of statements is important here: - // - the header must be calculate before the group is added to the list view, - // otherwise changing the header causes a nasty redraw (even in the middle of a BeginUpdate...EndUpdate pair) - // - the group must be added before it is given items, otherwise an exception is thrown (is this documented?) - string fmt = column.GroupWithItemCountFormatOrDefault; - string singularFmt = column.GroupWithItemCountSingularFormatOrDefault; - ColumnComparer itemSorter = new ColumnComparer((this.SortGroupItemsByPrimaryColumn ? this.GetColumn(0) : column), - this.lastSortOrder, this.SecondarySortColumn, this.SecondarySortOrder); - foreach (ListViewGroup group in groups) { - if (this.ShowItemCountOnGroups) { - int count = map[group.Tag].Count; - group.Header = String.Format((count == 1 ? singularFmt : fmt), group.Header, count); - } - this.Groups.Add(group); - // If there is no sort order, don't sort since the sort isn't stable - if (this.lastSortOrder != SortOrder.None) - map[group.Tag].Sort(itemSorter); - group.Items.AddRange(map[group.Tag].ToArray()); - } - } - - /// - /// Pause (or unpause) all animations in the list - /// - /// true to pause, false to unpause - public void PauseAnimations(bool isPause) - { - for (int i = 0; i < this.Columns.Count; i++) { - OLVColumn col = this.GetColumn(i); - if (col.Renderer is ImageRenderer) - ((ImageRenderer)col.Renderer).Paused = isPause; - } - } - - /// - /// Give the listview a reasonable size of its tiles, based on the number of lines of - /// information that each tile is going to display. - /// - public void CalculateReasonableTileSize() - { - if (this.Columns.Count <= 0) - return; - - int imageHeight = (this.LargeImageList == null ? 16 : this.LargeImageList.ImageSize.Height); - int dataHeight = (this.Font.Height + 1) * this.Columns.Count; - int tileWidth = (this.TileSize.Width == 0 ? 200 : this.TileSize.Width); - int tileHeight = Math.Max(this.TileSize.Height, Math.Max(imageHeight, dataHeight)); - this.TileSize = new Size(tileWidth, tileHeight); - } - - /// - /// Rebuild this list for the given view - /// - /// - internal void ChangeToFilteredColumns(View view) - { - this.Freeze(); - this.Clear(); - List cols = this.GetFilteredColumns(view); - this.Columns.AddRange(cols.ToArray()); - if (view == View.Details) { - foreach (OLVColumn x in cols) { - if (x.LastDisplayIndex == -1 || x.LastDisplayIndex > cols.Count - 1) - x.DisplayIndex = cols.Count - 1; - else - x.DisplayIndex = x.LastDisplayIndex; - } - this.ShowSortIndicator(); - } - this.BuildList(); - this.Unfreeze(); - } - - /// - /// Rebuild the columns based upon its current view and column visibility settings - /// - public void RebuildColumns() - { - this.ChangeToFilteredColumns(this.View); - } - - /// - /// Copy a text and html representation of the selected rows onto the clipboard. - /// - /// Be careful when using this with virtual lists. If the user has selected - /// 10,000,000 rows, this method will faithfully try to copy all of them to the clipboard. - /// From the user's point of view, your program will appear to have hung. - public void CopySelectionToClipboard() - { - if (this.SelectedIndices.Count == 0) - return; - - //THINK: Do we want to include something like this? - //if (this.SelectedIndices.Count > 10000) - // return; - - // Build text and html versions of the selection - StringBuilder sbText = new StringBuilder(); - StringBuilder sbHtml = new StringBuilder(""); - - foreach (int i in this.SelectedIndices) { - OLVListItem row = this.GetItem(i); - sbHtml.Append(""); - } - sbHtml.AppendLine("
"); - for (int j = 0; j < row.SubItems.Count; j++) { - if (j > 0) { - sbText.Append("\t"); - sbHtml.Append(""); - } - sbText.Append(row.SubItems[j].Text); - sbHtml.Append(row.SubItems[j].Text); - } - sbText.AppendLine(); - sbHtml.AppendLine("
"); - - // Put both the text and html versions onto the clipboard - DataObject dataObject = new DataObject(); - dataObject.SetText(sbText.ToString(), TextDataFormat.UnicodeText); - dataObject.SetText(ConvertToHtmlFragment(sbHtml.ToString()), TextDataFormat.Html); - Clipboard.SetDataObject(dataObject); - } - - /// - /// Convert the fragment of HTML into the Clipboards HTML format. - /// - /// The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx - /// - /// The HTML to put onto the clipboard. It must be valid HTML! - /// A string that can be put onto the clipboard and will be recognized as HTML - private string ConvertToHtmlFragment(string fragment) - { - // Minimal implementation of HTML clipboard format - string source = "http://www.codeproject.com/KB/list/ObjectListView.aspx"; - - const String MARKER_BLOCK = - "Version:1.0\r\n" + - "StartHTML:{0,8}\r\n" + - "EndHTML:{1,8}\r\n" + - "StartFragment:{2,8}\r\n" + - "EndFragment:{3,8}\r\n" + - "StartSelection:{2,8}\r\n" + - "EndSelection:{3,8}\r\n" + - "SourceURL:{4}\r\n" + - "{5}"; - - int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, source, "").Length; - - const String DEFAULT_HTML_BODY = - "" + - "{0}"; - - string html = String.Format(DEFAULT_HTML_BODY, fragment); - int startFragment = prefixLength + html.IndexOf(fragment); - int endFragment = startFragment + fragment.Length; - - return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, source, html); - } - - #endregion - - #region Event handlers - - /// - /// Event handler for the column click event - /// - virtual protected void HandleColumnClick(object sender, ColumnClickEventArgs e) - { - // Toggle the sorting direction on successive clicks on the same column - if (this.lastSortColumn != null && e.Column == this.lastSortColumn.Index) - this.lastSortOrder = (this.lastSortOrder == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending); - else - this.lastSortOrder = SortOrder.Ascending; - - this.BeginUpdate(); - this.Sort(e.Column); - this.EndUpdate(); - } - - /// - /// Handle when a user checks/unchecks a row - /// - protected void HandleItemCheck(object sender, ItemCheckEventArgs e) - { - if (this.CheckStatePutter != null) - e.NewValue = this.CheckStatePutter(this.GetModelObject(e.Index), e.NewValue); - } - - #endregion - - #region Low level Windows message handling - - /// - /// Override the basic message pump for this control - /// - /// - protected override void WndProc(ref Message m) - { - switch (m.Msg) { - case 0x0F: // WM_PAINT - this.HandlePrePaint(); - base.WndProc(ref m); - this.HandlePostPaint(); - break; - case 0x4E: // WM_NOTIFY - if (!this.HandleNotify(ref m)) - base.WndProc(ref m); - break; - case 0x05: // WM_SIZE - case 0x114: // WM_HSCROLL: - case 0x115: // WM_VSCROLL: - this.PossibleFinishCellEditing(); - base.WndProc(ref m); - break; - default: - base.WndProc(ref m); - break; - } - } - - #endregion - - #region Empty List Msg handling - - protected void HandlePrePaint() - { - // When we get a WM_PAINT msg, remember the rectangle that is being updated. - // We can't get this information later, since the BeginPaint call wipes it out. - this.lastUpdateRectangle = NativeMethods.GetUpdateRect(this); - - // When the list is empty, we want to handle the drawing of the control by ourselves. - // Unfortunately, there is no easy way to tell our superclass that we want to do this. - // So we resort to guile and deception. We validate the list area of the control, which - // effectively tells our superclass that this area does not need to be painted. - // Our superclass will then not paint the control, leaving us free to do so ourselves. - // Without doing this trickery, the superclass will draw the - // list as empty, and then moments later, we will draw the empty msg, giving a nasty flicker - if (this.GetItemCount() == 0 && this.HasEmptyListMsg) - NativeMethods.ValidateRect(this, this.ClientRectangle); - } - - protected void HandlePostPaint() - { - // If the list isn't empty or there isn't an emptyList msg, do nothing - if (this.GetItemCount() != 0 || !this.HasEmptyListMsg) - return; - - // Draw the empty list msg centered in the client area of the control - using (BufferedGraphics buffered = BufferedGraphicsManager.Current.Allocate(this.CreateGraphics(), this.ClientRectangle)) { - Graphics g = buffered.Graphics; - g.Clear(this.BackColor); - StringFormat sf = new StringFormat(); - sf.Alignment = StringAlignment.Center; - sf.LineAlignment = StringAlignment.Center; - sf.Trimming = StringTrimming.EllipsisCharacter; - g.DrawString(this.EmptyListMsg, this.EmptyListMsgFontOrDefault, SystemBrushes.ControlDark, this.ClientRectangle, sf); - buffered.Render(); - } - } - - #endregion - - #region Column hiding and resizing handling - - /// - /// When the control is created capture the messages for the header. - /// - protected override void OnCreateControl() - { - base.OnCreateControl(); - hdrCtrl = new HeaderControl(this); - } - private HeaderControl hdrCtrl = null; - - /// - /// Class used to capture window messages for the header of the list view - /// control. - /// - /// We only need this class in order to not change the cursor - /// when the cursor is over the divider of a fixed width column. It - /// really is a little too perfectionist even for me. - private class HeaderControl : NativeWindow - { - private ObjectListView parentListView = null; - - public HeaderControl(ObjectListView olv) - { - parentListView = olv; - this.AssignHandle(NativeMethods.GetHeaderControl(olv)); - } - - protected override void WndProc(ref Message message) - { - const int WM_SETCURSOR = 0x0020; - - switch (message.Msg) { - case WM_SETCURSOR: - if (IsCursorOverLockedDivider()) { - message.Result = (IntPtr)1; // Don't change the cursor - return; - } - break; - } - - base.WndProc(ref message); - } - - private bool IsCursorOverLockedDivider() - { - Point pt = this.parentListView.PointToClient(Cursor.Position); - int dividerIndex = NativeMethods.GetDividerUnderPoint(this.Handle, pt); - if (dividerIndex >= 0 && dividerIndex < this.parentListView.Columns.Count) { - OLVColumn column = this.parentListView.GetColumn(dividerIndex); - return column.IsFixedWidth; - } else - return false; - } - } - - /// - /// In the notification messages, we handle attempts to change the width of our columns - /// - /// The msg to be processed - /// bool to indicate if the msg has been handled - protected bool HandleNotify(ref Message m) - { - bool isMsgHandled = false; - - const int NM_RCLICK = -5; - const int HDN_FIRST = (0 - 300); - const int HDN_DIVIDERDBLCLICKA = (HDN_FIRST - 5); - const int HDN_DIVIDERDBLCLICKW = (HDN_FIRST - 25); - const int HDN_BEGINTRACKA = (HDN_FIRST - 6); - const int HDN_BEGINTRACKW = (HDN_FIRST - 26); - //const int HDN_ENDTRACKA = (HDN_FIRST - 7); - //const int HDN_ENDTRACKW = (HDN_FIRST - 27); - const int HDN_TRACKA = (HDN_FIRST - 8); - const int HDN_TRACKW = (HDN_FIRST - 28); - const int HDN_ITEMCHANGINGA = (HDN_FIRST - 0); - const int HDN_ITEMCHANGINGW = (HDN_FIRST - 20); - - // Handle the notification, remembering to handle both ANSI and Unicode versions - NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR)); - //if (nmhdr.code < HDN_FIRST) - // System.Diagnostics.Debug.WriteLine(nmhdr.code); - - // In KB Article #183258, MS states that when a header control has the HDS_FULLDRAG style, it will receive - // ITEMCHANGING events rather than TRACK events. Under XP SP2 (at least) this is not always true, which may be - // why MS has withdrawn that particular KB article. It is true that the header is always given the HDS_FULLDRAG - // style. But even while window style set, the control doesn't always received ITEMCHANGING events. - // The controlling setting seems to be the Explorer option "Show Window Contents While Dragging"! - // In the category of "truly bizarre side effects", if the this option is turned on, we will receive - // ITEMCHANGING events instead of TRACK events. But if it is turned off, we receive lots of TRACK events and - // only one ITEMCHANGING event at the very end of the process. - // If we receive HDN_TRACK messages, it's harder to control the resizing process. If we return a result of 1, we - // cancel the whole drag operation, not just that particular track event, which is clearly not what we want. - // If we are willing to compile with unsafe code enabled, we can modify the size of the column in place, using the - // commented out code below. But without unsafe code, the best we can do is allow the user to drag the column to - // any width, and then spring it back to within bounds once they release the mouse button. UI-wise it's very ugly. - NativeMethods.NMHEADER nmheader; - switch (nmhdr.code) { - - case NM_RCLICK: - this.PossibleFinishCellEditing(); - isMsgHandled = this.HandleHeaderRightClick(); - break; - - case HDN_DIVIDERDBLCLICKA: - case HDN_DIVIDERDBLCLICKW: - case HDN_BEGINTRACKA: - case HDN_BEGINTRACKW: - this.PossibleFinishCellEditing(); - break; - - case HDN_TRACKA: - case HDN_TRACKW: - nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER)); - if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) { - // unsafe { - // NativeMethods.HDITEM *hditem = (NativeMethods.HDITEM*)nmheader.pHDITEM; - // OLVColumn column = this.GetColumn(nmheader.iItem); - // if (hditem->cxy < column.MiniumWidth) - // hditem->cxy = column.MiniumWidth; - // else if (column.MaxiumWidth != -1 && hditem->cxy > column.MaxiumWidth) - // hditem->cxy = column.MaxiumWidth; - // } - } - break; - - case HDN_ITEMCHANGINGA: - case HDN_ITEMCHANGINGW: - this.PossibleFinishCellEditing(); - nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER)); - if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) { - NativeMethods.HDITEM hditem = (NativeMethods.HDITEM)Marshal.PtrToStructure(nmheader.pHDITEM, typeof(NativeMethods.HDITEM)); - OLVColumn column = this.GetColumn(nmheader.iItem); - // Check the mask to see if the width field is valid, and if it is, make sure it's within range - if ((hditem.mask & 1) == 1 && (hditem.cxy < column.MinimumWidth || (column.MaximumWidth != -1 && hditem.cxy > column.MaximumWidth))) { - m.Result = (IntPtr)1; // prevent the change from happening - isMsgHandled = true; - } - } - break; - - default: - break; - } - - return isMsgHandled; - } - - /// - /// The user has right clicked on the column headers. Do whatever is required - /// - /// Return true if this event has been handle - virtual protected bool HandleHeaderRightClick() - { - if (this.SelectColumnsOnRightClick) { - this.ShowColumnSelectMenu(Cursor.Position); - return true; - } else - return false; - } - - /// - /// Show a popup menu at the given point which will allow the user to choose which columns - /// are visible on this listview - /// - /// Where should the menu be placed - protected void ShowColumnSelectMenu(Point pt) - { - ContextMenuStrip m = new ContextMenuStrip(); - - m.ItemClicked += new ToolStripItemClickedEventHandler(ColumnSelectMenu_ItemClicked); - m.Closing += new ToolStripDropDownClosingEventHandler(ColumnSelectMenu_Closing); - - List columns = new List(this.AllColumns); - columns.Sort(delegate(OLVColumn x, OLVColumn y) { return String.Compare(x.Text, y.Text, true); }); - foreach (OLVColumn col in columns) { - ToolStripMenuItem mi = new ToolStripMenuItem(col.Text); - mi.Checked = col.IsVisible; - mi.Tag = col; - - // The 'Index' property returns -1 when the column is not visible, so if the - // column isn't visible we have to enable the item. Also the first column can't be turned off - mi.Enabled = !col.IsVisible || (col.Index > 0); - m.Items.Add(mi); - } - m.Show(pt); - } - - private void ColumnSelectMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) - { - ToolStripMenuItem mi = (ToolStripMenuItem)e.ClickedItem; - OLVColumn col = (OLVColumn)mi.Tag; - mi.Checked = !mi.Checked; - col.IsVisible = mi.Checked; - this.BeginInvoke(new MethodInvoker(this.RebuildColumns)); - } - - private void ColumnSelectMenu_Closing(object sender, ToolStripDropDownClosingEventArgs e) - { - e.Cancel = (this.SelectColumnsMenuStaysOpen && - e.CloseReason == ToolStripDropDownCloseReason.ItemClicked); - } - - protected override void OnColumnReordered(ColumnReorderedEventArgs e) - { - base.OnColumnReordered(e); - - // The internal logic of the .NET code behind a ENDDRAG event means that, - // at this point, the DisplayIndex's of the columns are not yet as they are - // going to be. So we have to invoke a method to run later that will remember - // what the real DisplayIndex's are. - this.BeginInvoke(new MethodInvoker(this.RememberDisplayIndicies)); - } - - private void RememberDisplayIndicies() - { - // Remember the display indexes so we can put them back at a later date - foreach (OLVColumn x in this.AllColumns) - x.LastDisplayIndex = x.DisplayIndex; - } - - #endregion - - #region OLV accessing - - /// - /// Return the column at the given index - /// - /// Index of the column to be returned - /// An OLVColumn - public OLVColumn GetColumn(int index) - { - return (OLVColumn)this.Columns[index]; - } - - /// - /// Return the column at the given title. - /// - /// Name of the column to be returned - /// An OLVColumn - public OLVColumn GetColumn(string name) - { - foreach (ColumnHeader column in this.Columns) { - if (column.Text == name) - return (OLVColumn)column; - } - return null; - } - - /// - /// Return the number of items in the list - /// - /// the number of items in the list - virtual public int GetItemCount() - { - return this.Items.Count; - } - - /// - /// Return the item at the given index - /// - /// Index of the item to be returned - /// An OLVListItem - virtual public OLVListItem GetItem(int index) - { - return (OLVListItem)this.Items[index]; - } - - /// - /// Return the model object at the given index - /// - /// Index of the model object to be returned - /// A model object - virtual public object GetModelObject(int index) - { - return this.GetItem(index).RowObject; - } - - #endregion - - #region Object manipulation - - /// - /// Select all rows in the listview - /// - public void SelectAll() - { - NativeMethods.SelectAllItems(this); - } - - /// - /// Deselect all rows in the listview - /// - public void DeselectAll() - { - NativeMethods.DeselectAllItems(this); - } - - /// - /// Return the model object of the row that is selected or null if there is no selection or more than one selection - /// - /// Model object or null - virtual public object GetSelectedObject() - { - if (this.SelectedIndices.Count == 1) - return this.GetModelObject(this.SelectedIndices[0]); - else - return null; - } - - /// - /// Return the model objects of the rows that are selected or an empty collection if there is no selection - /// - /// ArrayList - virtual public ArrayList GetSelectedObjects() - { - ArrayList objects = new ArrayList(this.SelectedIndices.Count); - foreach (int index in this.SelectedIndices) - objects.Add(this.GetModelObject(index)); - - return objects; - } - - /// - /// Select the row that is displaying the given model object. All other rows are deselected. - /// - /// The object to be selected or null to deselect all - virtual public void SelectObject(object modelObject) - { - if (this.SelectedItems.Count == 1 && ((OLVListItem)this.SelectedItems[0]).RowObject == modelObject) - return; - - this.SelectedItems.Clear(); - - //TODO: If this is too slow, we could keep a map of model object to ListViewItems - foreach (ListViewItem lvi in this.Items) { - if (((OLVListItem)lvi).RowObject == modelObject) { - lvi.Selected = true; - break; - } - } - } - - /// - /// Select the rows that is displaying any of the given model object. All other rows are deselected. - /// - /// A collection of model objects - virtual public void SelectObjects(IList modelObjects) - { - this.SelectedItems.Clear(); - - //TODO: If this is too slow, we could keep a map of model object to ListViewItems - foreach (ListViewItem lvi in this.Items) { - if (modelObjects.Contains(((OLVListItem)lvi).RowObject)) - lvi.Selected = true; - } - } - - /// - /// Update the ListViewItem with the data from its associated model. - /// - /// This method does not resort or regroup the view. It simply updates - /// the displayed data of the given item - virtual public void RefreshItem(OLVListItem olvi) - { - // For some reason, clearing the subitems also wipes out the back color, - // so we need to store it and then put it back again later - Color c = olvi.BackColor; - olvi.SubItems.Clear(); - this.FillInValues(olvi, olvi.RowObject); - this.SetSubItemImages(olvi.Index, olvi, true); - olvi.BackColor = c; - } - - /// - /// Update the rows that are showing the given objects - /// - /// This method does not resort or regroup the view. - virtual public void RefreshObject(object modelObject) - { - this.RefreshObjects(new object[] {modelObject}); - } - - private delegate void RefreshObjectsInvoker(IList modelObjects); - - /// - /// Update the rows that are showing the given objects - /// - /// This method does not resort or regroup the view. - virtual public void RefreshObjects(IList modelObjects) - { - if (this.InvokeRequired) { - this.Invoke(new RefreshObjectsInvoker(this.RefreshObjects), new object[] { modelObjects }); - return; - } - foreach (ListViewItem lvi in this.Items) { - OLVListItem olvi = (OLVListItem)lvi; - if (modelObjects.Contains(olvi.RowObject)) - this.RefreshItem(olvi); - } - } - - /// - /// Update the rows that are selected - /// - /// This method does not resort or regroup the view. - public void RefreshSelectedObjects() - { - foreach (ListViewItem lvi in this.SelectedItems) - this.RefreshItem((OLVListItem)lvi); - } - - /// - /// Find the given model object within the listview and return its index - /// - /// Technically, this method will work with virtual lists, but it will - /// probably be very slow. - /// The model object to be found - /// The index of the object. -1 means the object was not present - public int IndexOf(Object modelObject) - { - for (int i = 0; i < this.GetItemCount(); i++) { - if (this.GetModelObject(i) == modelObject) - return i; - } - return -1; - } - - /// - /// Return the ListViewItem that appears immediately after the given item. - /// If the given item is null, the first item in the list will be returned. - /// Return null if the given item is the last item. - /// - /// The item that is before the item that is returned, or null - /// A ListViewItem - public ListViewItem GetNextItem(ListViewItem itemToFind) - { - if (this.ShowGroups) { - bool isFound = (itemToFind == null); - foreach (ListViewGroup group in this.Groups) { - foreach (ListViewItem lvi in group.Items) { - if (isFound) - return lvi; - isFound = (lvi == itemToFind); - } - } - return null; - } else { - if (this.GetItemCount() == 0) - return null; - if (itemToFind == null) - return this.GetItem(0); - if (itemToFind.Index == this.GetItemCount() - 1) - return null; - return this.GetItem(itemToFind.Index + 1); - } - } - - /// - /// Return the ListViewItem that appears immediately before the given item. - /// If the given item is null, the last item in the list will be returned. - /// Return null if the given item is the first item. - /// - /// The item that is before the item that is returned - /// A ListViewItem - public ListViewItem GetPreviousItem(ListViewItem itemToFind) - { - if (this.ShowGroups) { - ListViewItem previousItem = null; - foreach (ListViewGroup group in this.Groups) { - foreach (ListViewItem lvi in group.Items) { - if (lvi == itemToFind) - return previousItem; - else - previousItem = lvi; - } - } - if (itemToFind == null) - return previousItem; - else - return null; - } else { - if (this.GetItemCount() == 0) - return null; - if (itemToFind == null) - return this.GetItem(this.GetItemCount() - 1); - if (itemToFind.Index == 0) - return null; - return this.GetItem(itemToFind.Index - 1); - } - } - - #endregion - - #region Freezing - - /// - /// Freeze the listview so that it no longer updates itself. - /// - /// Freeze()/Unfreeze() calls nest correctly - public void Freeze() - { - freezeCount++; - } - - /// - /// Unfreeze the listview. If this call is the outermost Unfreeze(), - /// the contents of the listview will be rebuilt. - /// - /// Freeze()/Unfreeze() calls nest correctly - public void Unfreeze() - { - if (freezeCount <= 0) - return; - - freezeCount--; - if (freezeCount == 0) - DoUnfreeze(); - } - - /// - /// Do the actual work required when the listview is unfrozen - /// - virtual protected void DoUnfreeze() - { - BuildList(); - } - - #endregion - - #region Column Sorting - - /// - /// Sort the items in the list view by the values in the given column. - /// If ShowGroups is true, the rows will be grouped by the given column, - /// otherwise, it will be a straight sort. - /// - /// The name of the column whose values will be used for the sorting - public void Sort(string columnToSortName) - { - this.Sort(this.GetColumn(columnToSortName)); - } - - /// - /// Sort the items in the list view by the values in the given column. - /// If ShowGroups is true, the rows will be grouped by the given column, - /// otherwise, it will be a straight sort. - /// - /// The index of the column whose values will be used for the sorting - public void Sort(int columnToSortIndex) - { - if (columnToSortIndex >= 0 && columnToSortIndex < this.Columns.Count) - this.Sort(this.GetColumn(columnToSortIndex)); - } - - protected delegate void SortInvoker(OLVColumn columnToSort); - - /// - /// Sort the items in the list view by the values in the given column. - /// If ShowGroups is true, the rows will be grouped by the given column, - /// otherwise, it will be a straight sort. - /// - /// The column whose values will be used for the sorting - virtual public void Sort(OLVColumn columnToSort) - { - if (this.InvokeRequired) { - this.Invoke(new SortInvoker(this.Sort), new object [] {columnToSort}); - return; - } - - if (this.Columns.Count < 1) - return; - - if (columnToSort == null) - columnToSort = this.GetColumn(0); - - if (lastSortOrder == SortOrder.None) - lastSortOrder = this.Sorting; - - if (this.ShowGroups) - this.BuildGroups(columnToSort); - else if (this.CustomSorter != null) - this.CustomSorter(columnToSort, lastSortOrder); - else - this.ListViewItemSorter = new ColumnComparer(columnToSort, lastSortOrder, this.SecondarySortColumn, this.SecondarySortOrder); - - if (this.ShowSortIndicators) - this.ShowSortIndicator(columnToSort, lastSortOrder); - - if (this.UseAlternatingBackColors && this.View == View.Details) - PrepareAlternateBackColors(); - - this.lastSortColumn = columnToSort; - } - - - /// - /// Put a sort indicator next to the text of the sort column - /// - public void ShowSortIndicator() - { - if (this.ShowSortIndicators && this.lastSortOrder != SortOrder.None) - this.ShowSortIndicator(this.lastSortColumn, this.lastSortOrder); - } - - - /// - /// Put a sort indicator next to the text of the given given column - /// - /// The column to be marked - /// The sort order in effect on that column - protected void ShowSortIndicator(OLVColumn columnToSort, SortOrder sortOrder) - { - int imageIndex = -1; - - if (!NativeMethods.HasBuiltinSortIndicators()) { - // If we can't use builtin image, we have to make and then locate the index of the - // sort indicator we want to use. SortOrder.None doesn't show an image. - if (this.SmallImageList == null || !this.SmallImageList.Images.ContainsKey(SORT_INDICATOR_UP_KEY)) - MakeSortIndicatorImages(); - - if (sortOrder == SortOrder.Ascending) - imageIndex = this.SmallImageList.Images.IndexOfKey(SORT_INDICATOR_UP_KEY); - else if (sortOrder == SortOrder.Descending) - imageIndex = this.SmallImageList.Images.IndexOfKey(SORT_INDICATOR_DOWN_KEY); - } - - // Set the image for each column - for (int i = 0; i < this.Columns.Count; i++) { - if (i == columnToSort.Index) - NativeMethods.SetColumnImage(this, i, sortOrder, imageIndex); - else - NativeMethods.SetColumnImage(this, i, SortOrder.None, -1); - } - } - - private const string SORT_INDICATOR_UP_KEY = "sort-indicator-up"; - private const string SORT_INDICATOR_DOWN_KEY = "sort-indicator-down"; - - /// - /// If the sort indicator images don't already exist, this method will make and install them - /// - protected void MakeSortIndicatorImages() - { - ImageList il = this.SmallImageList; - if (il == null) { - il = new ImageList(); - il.ImageSize = new Size(16, 16); - } - - // This arrangement of points works well with (16,16) images, and OK with others - int midX = il.ImageSize.Width / 2; - int midY = (il.ImageSize.Height / 2) - 1; - int deltaX = midX - 2; - int deltaY = deltaX / 2; - - if (il.Images.IndexOfKey(SORT_INDICATOR_UP_KEY) == -1) { - Point pt1 = new Point(midX - deltaX, midY + deltaY); - Point pt2 = new Point(midX, midY - deltaY - 1); - Point pt3 = new Point(midX + deltaX, midY + deltaY); - il.Images.Add(SORT_INDICATOR_UP_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 })); - } - - if (il.Images.IndexOfKey(SORT_INDICATOR_DOWN_KEY) == -1) { - Point pt1 = new Point(midX - deltaX, midY - deltaY); - Point pt2 = new Point(midX, midY + deltaY); - Point pt3 = new Point(midX + deltaX, midY - deltaY); - il.Images.Add(SORT_INDICATOR_DOWN_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 })); - } - - this.SmallImageList = il; - } - - private Bitmap MakeTriangleBitmap(Size sz, Point[] pts) - { - Bitmap bm = new Bitmap(sz.Width, sz.Height); - Graphics g = Graphics.FromImage(bm); - g.FillPolygon(new SolidBrush(Color.Gray), pts); - return bm; - } - - #endregion - - #region Utilities - - /// - /// Fill in the given OLVListItem with values of the given row - /// - /// the OLVListItem that is to be stuff with values - /// the model object from which values will be taken - protected void FillInValues(OLVListItem lvi, object rowObject) - { - if (this.Columns.Count == 0) - return; - - OLVColumn column = this.GetColumn(0); - lvi.Text = column.GetStringValue(rowObject); - lvi.ImageSelector = column.GetImage(rowObject); - - for (int i = 1; i < this.Columns.Count; i++) { - column = this.GetColumn(i); - lvi.SubItems.Add(new OLVListSubItem(column.GetStringValue(rowObject), - column.GetImage(rowObject))); - } - - // Give the row formatter a chance to mess with the item - if (this.RowFormatter != null) - this.RowFormatter(lvi); - - // Get the check state of the row, if we are showing check boxes - if (this.CheckBoxes && this.CheckStateGetter != null) - lvi.Checked = this.CheckStateGetter(lvi.RowObject); - } - - /// - /// Setup all subitem images on all rows - /// - protected void SetAllSubItemImages() - { - if (!this.ShowImagesOnSubItems) - return; - - this.ForceSubItemImagesExStyle(); - - for (int rowIndex = 0; rowIndex < this.GetItemCount(); rowIndex++) - SetSubItemImages(rowIndex, this.GetItem(rowIndex)); - } - - /// - /// Tell the underlying list control which images to show against the subitems - /// - /// the index at which the item occurs - /// the item whose subitems are to be set - protected void SetSubItemImages(int rowIndex, OLVListItem item) - { - this.SetSubItemImages(rowIndex, item, false); - } - - /// - /// Tell the underlying list control which images to show against the subitems - /// - /// the index at which the item occurs - /// the item whose subitems are to be set - /// will existing images be cleared if no new image is provided? - protected void SetSubItemImages(int rowIndex, OLVListItem item, bool shouldClearImages) - { - if (!this.ShowImagesOnSubItems) - return; - - for (int i=1; i - /// Prepare the listview to show alternate row backcolors - /// - /// We cannot rely on lvi.Index in this method. - /// In a straight list, lvi.Index is the display index, and can be used to determine - /// whether the row should be colored. But when organised by groups, lvi.Index is not - /// useable because it still refers to the position in the overall list, not the display order. - /// - virtual protected void PrepareAlternateBackColors () - { - Color rowBackColor = this.AlternateRowBackColorOrDefault; - int i = 0; - - if (this.ShowGroups) { - foreach (ListViewGroup group in this.Groups) { - foreach (ListViewItem lvi in group.Items) { - if (i % 2 == 0) - lvi.BackColor = this.BackColor; - else - lvi.BackColor = rowBackColor; - i++; - } - } - } else { - foreach (ListViewItem lvi in this.Items) { - if (i % 2 == 0) - lvi.BackColor = this.BackColor; - else - lvi.BackColor = rowBackColor; - i++; - } - } - } - - /// - /// Convert the given image selector to an index into our image list. - /// Return -1 if that's not possible - /// - /// - /// Index of the image in the imageList, or -1 - public int GetActualImageIndex(Object imageSelector) - { - if (imageSelector == null) - return -1; - - if (imageSelector is Int32) - return (int)imageSelector; - - if (imageSelector is String && this.SmallImageList != null) - return this.SmallImageList.Images.IndexOfKey((String)imageSelector); - - return -1; - } - - /// - /// Make sure the ListView has the extended style that says to display subitem images. - /// - /// This method must be called after any .NET call that update the extended styles - /// since they seem to erase this setting. - protected void ForceSubItemImagesExStyle () - { - NativeMethods.ForceSubItemImagesExStyle(this); - } - - /// - /// For the given item and subitem, make it display the given image - /// - /// row number (0 based) - /// subitem (0 is the item itself) - /// index into the image list - protected void SetSubItemImage(int itemIndex, int subItemIndex, int imageIndex) - { - NativeMethods.SetSubItemImage(this, itemIndex, subItemIndex, imageIndex); - } - - #endregion - - #region ISupportInitialize Members - - void ISupportInitialize.BeginInit() - { - this.Frozen = true; - } - - void ISupportInitialize.EndInit() - { - this.Frozen = false; - } - - #endregion - - #region Image list manipulation - - /// - /// Update our externally visible image list so it holds the same images as our shadow list, but sized correctly - /// - private void SetupExternalImageList() - { - // If a row height hasn't been set, or an image list has been give which is the required size, just assign it - if (rowHeight == -1 || (this.shadowedImageList != null && this.shadowedImageList.ImageSize.Height == rowHeight)) - base.SmallImageList = this.shadowedImageList; - else - base.SmallImageList = this.MakeResizedImageList(rowHeight, shadowedImageList); - } - - /// - /// Return a copy of the given source image list, where each image has been resized to be height x height in size. - /// If source is null, an empty image list of the given size is returned - /// - /// Height and width of the new images - /// Source of the images (can be null) - /// A new image list - private ImageList MakeResizedImageList(int height, ImageList source) - { - // Return a copy of the source image list, where each image has been resized to the given width and height - ImageList il = new ImageList(); - il.ImageSize = new Size(height, height); - - // If there's nothing to copy, just return the new list - if (source == null) - return il; - - il.TransparentColor = source.TransparentColor; - il.ColorDepth = source.ColorDepth; - - // Fill the imagelist with resized copies from the source - for (int i = 0; i < source.Images.Count; i++) { - Bitmap bm = this.MakeResizedImage(height, source.Images[i], source.TransparentColor); - il.Images.Add(bm); - } - - // Give each image the same key it has in the original - foreach (String key in source.Images.Keys) { - il.Images.SetKeyName(source.Images.IndexOfKey(key), key); - } - - return il; - } - - /// - /// Return a bitmap of the given height x height, which shows the given image, centred. - /// - /// Height and width of new bitmap - /// Image to be centred - /// The background color - /// A new bitmap - private Bitmap MakeResizedImage(int height, Image image, Color transparent) - { - Bitmap bm = new Bitmap(height, height); - Graphics g = Graphics.FromImage(bm); - g.Clear(transparent); - int x = Math.Max(0, (bm.Size.Width - image.Size.Width) / 2); - int y = Math.Max(0, (bm.Size.Height - image.Size.Height) / 2); - g.DrawImage(image, x, y, image.Size.Width, image.Size.Height); - return bm; - } - - #endregion - - #region Owner drawing - - /// - /// Owner draw the column header - /// - /// - protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e) - { - e.DrawDefault = true; - base.OnDrawColumnHeader(e); - } - - /// - /// Owner draw the item - /// - /// - protected override void OnDrawItem(DrawListViewItemEventArgs e) - { - e.DrawDefault = (this.View != View.Details); - base.OnDrawItem(e); - } - - int[] columnRightEdge = new int[128]; // will anyone ever want more than 128 columns?? - - /// - /// Owner draw a single subitem - /// - /// - protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e) - { - // Get the special renderer for this column. - // If there isn't one, use the default draw mechanism. - OLVColumn column = this.GetColumn(e.ColumnIndex); - if (column.RendererDelegate == null) { - e.DrawDefault = true; - return; - } - - // Calculate where the subitem should be drawn - // It should be as simple as 'e.Bounds', but it isn't :-( - - // There seems to be a bug in .NET where the left edge of the bounds of subitem 0 - // is always 0. This is normally what is required, but it's wrong when - // someone drags column 0 to somewhere else in the listview. We could - // drop down into Windows-ville and use LVM_GETSUBITEMRECT, but just to be different - // we keep track of the right edge of all columns, and when subitem 0 - // isn't being shown at column 0, we make its left edge to be the right - // edge of the previous column plus a little bit. - //TODO: Replace with LVM_GETSUBITEMRECT - Rectangle r = e.Bounds; - if (e.ColumnIndex == 0 && e.Header.DisplayIndex != 0) { - r.X = this.columnRightEdge[e.Header.DisplayIndex - 1] + 1; - } else - //TODO: Check the size of columnRightEdge and dynamically reallocate? - this.columnRightEdge[e.Header.DisplayIndex] = e.Bounds.Right; - - // Optimize drawing by only redrawing subitems that touch the area that was damaged - if (!r.IntersectsWith(this.lastUpdateRectangle)) { - return; - } - - // Get a graphics context for the renderer to use. - // But we have more complications. Virtual lists have a nasty habit of drawing column 0 - // whenever there is any mouse move events over a row, and doing it in an un-double buffered manner, - // which results in nasty flickers! There are also some unbuffered draw when a mouse is first - // hovered over column 0 of a normal row. So, to avoid all complications, - // we always manually double-buffer the drawing. - Graphics g = e.Graphics; - BufferedGraphics buffer = null; - bool avoidFlickerMode = true; // set this to false to see the probems with flicker - if (avoidFlickerMode) { - buffer = BufferedGraphicsManager.Current.Allocate(e.Graphics, r); - g = buffer.Graphics; - } - - // Finally, give the renderer a chance to draw something - Object row = ((OLVListItem)e.Item).RowObject; - column.RendererDelegate(e, g, r, row); - - if (buffer != null) { - buffer.Render(); - buffer.Dispose(); - } - } - - #endregion - - #region Selection Event Handling - - /// - /// This method is called every time a row is selected or deselected. This can be - /// a pain if the user shift-clicks 100 rows. We override this method so we can - /// trigger one event for any number of select/deselects that come from one user action - /// - /// - protected override void OnSelectedIndexChanged(EventArgs e) - { - base.OnSelectedIndexChanged(e); - - // If we haven't already scheduled an event, schedule it to be triggered - // By using idle time, we will wait until all select events for the same - // user action have finished before triggering the event. - if (!this.hasIdleHandler) { - this.hasIdleHandler = true; - Application.Idle += new EventHandler(Application_Idle); - } - } - private bool hasIdleHandler = false; - - /// - /// The application is idle. Trigger a SelectionChanged event. - /// - /// - /// - protected void Application_Idle(object sender, EventArgs e) - { - // Remove the handler before triggering the event - Application.Idle -= new EventHandler(Application_Idle); - this.hasIdleHandler = false; - - this.OnSelectionChanged(new EventArgs()); - } - - /// - /// This event is triggered once per user action that changes the selection state - /// of one or more rows. - /// - [Category("Behavior"), - Description("This event is triggered once per user action that changes the selection state of one or more rows.")] - public event EventHandler SelectionChanged; - - /// - /// Trigger the SelectionChanged event - /// - /// - virtual protected void OnSelectionChanged(EventArgs e) - { - if (this.SelectionChanged != null) - this.SelectionChanged(this, e); - } - - #endregion - - #region Cell editing - - // NOTES: - // - // - What event should we listen to? - // - // We can't use OnMouseClick, OnMouseDoubleClick, OnClick, or OnDoubleClick - // since they are not triggered for clicks on subitems without Full Row Select. - // - // We could use OnMouseDown, but selecting rows is done in OnMouseUp. This means - // that if we start the editing during OnMouseDown, the editor will automatically - // lose focus when mouse up happens. - // - - // We need the click count in the mouse up event, but that is always 1. - // So we have to remember the click count from the preceding mouse down event. - protected override void OnMouseDown(MouseEventArgs e) - { - base.OnMouseDown(e); - this.lastMouseDownClickCount = e.Clicks; - } - private int lastMouseDownClickCount = 0; - - /// - /// Check to see if we need to start editing a cell - /// - /// - protected override void OnMouseUp(MouseEventArgs e) - { - base.OnMouseUp(e); - - // If it was an unmodified left click, check to see if we should start editing - if (this.ShouldStartCellEdit(e)) { - ListViewHitTestInfo info = this.HitTest(e.Location); - if (info.Item != null && info.SubItem != null) { - int subItemIndex = info.Item.SubItems.IndexOf(info.SubItem); - - // We don't edit the primary column by single clicks -- only subitems. - if (this.CellEditActivation != CellEditActivateMode.SingleClick || subItemIndex > 0) - this.EditSubItem((OLVListItem)info.Item, subItemIndex); - } - } - } - - /// - /// Should we start editing the cell? - /// - /// - /// - protected bool ShouldStartCellEdit(MouseEventArgs e) - { - if (e.Button != MouseButtons.Left) - return false; - - if ((Control.ModifierKeys & (Keys.Shift | Keys.Control | Keys.Alt)) != 0) - return false; - - if (this.lastMouseDownClickCount == 1 && this.CellEditActivation == CellEditActivateMode.SingleClick) - return true; - - return (this.lastMouseDownClickCount == 2 && this.CellEditActivation == CellEditActivateMode.DoubleClick); - } - - /// - /// Handle a key press on this control. We specifically look for F2 which edits the primary column, - /// or a Tab character during an edit operation, which tries to start editing on the next (or previous) cell. - /// - /// - /// - protected override bool ProcessDialogKey(Keys keyData) - { - bool isSimpleTabKey = ((keyData & Keys.KeyCode) == Keys.Tab) && ((keyData & (Keys.Alt | Keys.Control)) == Keys.None); - - if (isSimpleTabKey && this.IsCellEditing) { // Tab key while editing - this.FinishCellEdit(); - - OLVListItem olvi = this.cellEditEventArgs.ListViewItem; - int subItemIndex = this.cellEditEventArgs.SubItemIndex; - int displayIndex = this.GetColumn(subItemIndex).DisplayIndex; - - // Move to the next or previous editable subitem, depending on Shift key. Wrap at the edges - List columnsInDisplayOrder = this.ColumnsInDisplayOrder; - do { - if ((keyData & Keys.Shift) == Keys.Shift) - displayIndex = (olvi.SubItems.Count + displayIndex - 1) % olvi.SubItems.Count; - else - displayIndex = (displayIndex + 1) % olvi.SubItems.Count; - } while (!columnsInDisplayOrder[displayIndex].IsEditable); - - // If we found a different editable cell, start editing it - subItemIndex = columnsInDisplayOrder[displayIndex].Index; - if (this.cellEditEventArgs.SubItemIndex != subItemIndex) { - this.StartCellEdit(olvi, subItemIndex); - return true; - } - } - - // Treat F2 as a request to edit the primary column - if (keyData == Keys.F2 && !this.IsCellEditing) { - this.EditSubItem((OLVListItem)this.FocusedItem, 0); - return true; - } - - // We have to catch Return/Enter/Escape here since some types of controls - // (e.g. ComboBox, UserControl) don't trigger key events that we can listen for. - // Treat Return or Enter as committing the current edit operation - if ((keyData == Keys.Return || keyData == Keys.Enter) && this.IsCellEditing) { - this.FinishCellEdit(); - return true; - } - - // Treat Escaoe as cancel the current edit operation - if (keyData == Keys.Escape && this.IsCellEditing) { - this.CancelCellEdit(); - return true; - } - - // Treat Ctrl-C as Copy To Clipboard. We still allow the default processing - if ((keyData & Keys.Control) == Keys.Control && (keyData & Keys.KeyCode) == Keys.C) - this.CopySelectionToClipboard(); - - return base.ProcessDialogKey(keyData); - } - - /// - /// Begin an edit operation on the given cell. - /// - /// This performs various sanity checks and passes off the real work to StartCellEdit(). - /// The row to be edited - /// The index of the cell to be edited - public void EditSubItem(OLVListItem item, int subItemIndex) - { - if (item == null) - return; - - if (subItemIndex < 0 && subItemIndex >= item.SubItems.Count) - return; - - if (this.CellEditActivation == CellEditActivateMode.None) - return; - - if (!this.GetColumn(subItemIndex).IsEditable) - return; - - this.StartCellEdit(item, subItemIndex); - } - - /// - /// Really start an edit operation on a given cell. The parameters are assumed to be sane. - /// - /// The row to be edited - /// The index of the cell to be edited - protected void StartCellEdit(OLVListItem item, int subItemIndex) - { - OLVColumn column = this.GetColumn(subItemIndex); - Rectangle r = this.CalculateCellBounds(item, subItemIndex); - Control c = this.GetCellEditor(item, subItemIndex); - c.Bounds = r; - - // Try to align the control as the column is aligned. Not all controls support this property - PropertyInfo pinfo = c.GetType().GetProperty("TextAlign"); - if (pinfo != null) - pinfo.SetValue(c, column.TextAlign, null); - - // Give the control the value from the model - this.SetControlValue(c, column.GetValue(item.RowObject), column.GetStringValue(item.RowObject)); - - // Give the outside world the chance to munge with the process - this.cellEditEventArgs = new CellEditEventArgs(column, c, r, item, subItemIndex); - this.OnCellEditStarting(this.cellEditEventArgs); - if (this.cellEditEventArgs.Cancel) - return; - - // The event handler may have completely changed the control, so we need to remember it - this.cellEditor = this.cellEditEventArgs.Control; - - // If the control isn't the height of the cell, centre it vertically. We don't - // need to do this when in Tile view. - if (this.View != View.Tile && this.cellEditor.Height != r.Height) - this.cellEditor.Top += (r.Height - this.cellEditor.Height) / 2; - - this.Controls.Add(this.cellEditor); - this.ConfigureControl(); - this.PauseAnimations(true); - } - private Control cellEditor = null; - private CellEditEventArgs cellEditEventArgs = null; - - /// - /// Try to give the given value to the provided control. Fall back to assigning a string - /// if the value assignment fails. - /// - /// A control - /// The value to be given to the control - /// The string to be given if the value doesn't work - protected void SetControlValue(Control c, Object value, String stringValue) - { - // Look for a property called "Value". We have to look twice, the first time might get an ambiguous - PropertyInfo pinfo = null; - try { - pinfo = c.GetType().GetProperty("Value"); - } catch (AmbiguousMatchException) { - // The lowest level class of the control must have overridden the "Value" property. - // We now have to specifically look for only public instance properties declared in the lowest level class. - pinfo = c.GetType().GetProperty("Value", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public); - } - - // If we found it, use it to assign a value, otherwise simply set the text - if (pinfo == null) - c.Text = stringValue; - else { - try { - pinfo.SetValue(c, value, null); - } catch (TargetInvocationException) { - // Not a lot we can do about this one. Something went wrong in the bowels - // of the method. Let's take the ostrich approach and just ignore it :-) - } catch (ArgumentException) { - c.Text = stringValue; - } - } - } - - /// - /// Setup the given control to be a cell editor - /// - protected void ConfigureControl() - { - this.cellEditor.Leave += new EventHandler(CellEditor_Leave); - this.cellEditor.Select(); - } - - /// - /// Return the value that the given control is showing - /// - /// - /// - protected Object GetControlValue(Control c) - { - try { - return c.GetType().InvokeMember("Value", BindingFlags.GetProperty, null, c, null); - } catch (MissingMethodException) { - return c.Text; - } - } - - /// - /// Called when the cell editor loses focus. Time to commit the change - /// - /// - /// - private void CellEditor_Leave(object sender, EventArgs e) - { - FinishCellEdit(); - } - - /// - /// Return the bounds of the given cell - /// - /// The row to be edited - /// The index of the cell to be edited - /// A Rectangle - protected Rectangle CalculateCellBounds(OLVListItem item, int subItemIndex) - { - // Item 0 is special. Its bounds include all subitems. To get just the bounds - // of cell for item 0, we have to use GetItemRect(). - if (subItemIndex == 0) { - return this.GetItemRect(item.Index, ItemBoundsPortion.Label); - } else - return item.SubItems[subItemIndex].Bounds; - } - - /// - /// Return a control that can be used to edit the value of the given cell. - /// - /// The row to be edited - /// The index of the cell to be edited - /// - protected Control GetCellEditor(OLVListItem item, int subItemIndex) - { - OLVColumn column = this.GetColumn(subItemIndex); - Object value = column.GetValue(item.RowObject); - - //THINK: Do we want to use a Registry instead of this cascade? - if (value is Boolean) - return new BooleanCellEditor(); - - if (value is DateTime) { - DateTimePicker c = new DateTimePicker(); - c.Format = DateTimePickerFormat.Short; - return c; - } - if (value is Int32 || value is Int16 || value is Int64) - return new IntUpDown(); - - if (value is UInt16 || value is UInt32 || value is UInt64) - return new UintUpDown(); - - if (value is Single || value is Double) - return new FloatCellEditor(); - - return this.MakeDefaultCellEditor(column); - } - - /// - /// Return a TextBox that can be used as a default cell editor. - /// - /// What column does the cell belong to? - /// - private Control MakeDefaultCellEditor(OLVColumn column) - { - TextBox tb = new TextBox(); - - // Build a list of unique values, to be used as autocomplete on the editor - Dictionary alreadySeen = new Dictionary(); - for (int i = 0; i < Math.Min(this.GetItemCount(), 1000); i++) { - String str = column.GetStringValue(this.GetModelObject(i)); - if (!alreadySeen.ContainsKey(str)) { - tb.AutoCompleteCustomSource.Add(str); - alreadySeen[str] = true; - } - } - - tb.AutoCompleteSource = AutoCompleteSource.CustomSource; - tb.AutoCompleteMode = AutoCompleteMode.Append; - - return tb; - } - - /// - /// Stop editing a cell and throw away any changes. - /// - protected void CancelCellEdit() - { - // Let the world know that the user has cancelled the edit operation - this.cellEditEventArgs.Cancel = true; - this.OnCellEditFinishing(this.cellEditEventArgs); - - // Now cleanup the editing process - this.CleanupCellEdit(); - } - - /// - /// If a cell edit is in progress, finish the edit - /// - protected void PossibleFinishCellEditing() - { - if (this.IsCellEditing) - FinishCellEdit(); - } - - /// - /// Finish the cell edit operation, writing changed data back to the model object - /// - protected void FinishCellEdit() - { - this.OnCellEditFinishing(this.cellEditEventArgs); - - // If someone doesn't cancel the editing process, write the value back into the model - if (!this.cellEditEventArgs.Cancel) { - Object value = this.GetControlValue(this.cellEditor); - this.cellEditEventArgs.Column.PutValue(this.cellEditEventArgs.RowObject, value); - this.RefreshItem(this.cellEditEventArgs.ListViewItem); - } - - this.CleanupCellEdit(); - } - - /// - /// Remove all trace of any existing cell edit operation - /// - protected void CleanupCellEdit() - { - this.cellEditor.Leave -= new EventHandler(CellEditor_Leave); - this.Controls.Remove(this.cellEditor); - this.cellEditor.Dispose(); //THINK: do we need to call this? - this.cellEditor = null; - this.Select(); - this.PauseAnimations(false); - } - - /// - /// The callbacks for CellEditing events - /// - public delegate void CellEditEventHandler(object sender, CellEditEventArgs e); - - /// - /// Triggered when a cell is about to be edited. - /// - /// Set Cancel to true to prevent the cell being edited. - /// You can change the the Control to be something completely different. - [Category("Behavior")] - public event CellEditEventHandler CellEditStarting; - - /// - /// Triggered when a cell is about to finish being edited. - /// - /// If Cancel is already true, the user is cancelling the edit operation. - /// Set Cancel to true to prevent the value from the cell being written into the model. - /// You cannot prevent the editing from finishing. - [Category("Behavior")] - public event CellEditEventHandler CellEditFinishing; - - /// - /// Tell the world when a cell is about to be edited. - /// - protected virtual void OnCellEditStarting(CellEditEventArgs e) - { - if (this.CellEditStarting != null) - this.CellEditStarting(this, e); - } - - /// - /// Tell the world when a cell is about to finish being edited. - /// - protected virtual void OnCellEditFinishing(CellEditEventArgs e) - { - if (this.CellEditFinishing != null) - this.CellEditFinishing(this, e); - } - - /// - /// Let the world know that a cell edit operation is beginning or ending - /// - public class CellEditEventArgs : EventArgs - { - public CellEditEventArgs(OLVColumn column, Control c, Rectangle r, OLVListItem item, int subItemIndex) - { - this.Cancel = false; - this.Control = c; - this.column = column; - this.cellBounds = r; - this.listViewItem = item; - this.rowObject = item.RowObject; - this.subItemIndex = subItemIndex; - this.value = column.GetValue(item.RowObject); - } - - /// - /// Change this to true to cancel the cell editing operation. - /// - /// - /// During the CellEditStarting event, setting this to true will prevent the cell from being edited. - /// During the CellEditFinishing event, if this value is already true, this indicates that the user has - /// cancelled the edit operation and that the handler should perform cleanup only. Setting this to true, - /// will prevent the ObjectListView from trying to write the new value into the model object. - /// - public bool Cancel = false; - - /// - /// During the CellEditStarting event, this can be modified to be the control that you want - /// to edit the value. You must fully configure the control before returning from the event, - /// including its bounds and the value it is showing. - /// During the CellEditFinishing event, you can use this to get the value that the user - /// entered and commit that value to the model. Changing the control during the finishing - /// event has no effect. - /// - public Control Control = null; - - /// - /// The column of the cell that is going to be or has been edited. - /// - public OLVColumn Column - { - get { return this.column; } - } - private OLVColumn column; - - /// - /// The model object of the row of the cell that is going to be or has been edited. - /// - public Object RowObject - { - get { return this.rowObject; } - } - private Object rowObject; - - /// - /// The listview item of the cell that is going to be or has been edited. - /// - public OLVListItem ListViewItem - { - get { return this.listViewItem; } - } - private OLVListItem listViewItem; - - /// - /// The index of the cell that is going to be or has been edited. - /// - public int SubItemIndex - { - get { return this.subItemIndex; } - } - private int subItemIndex; - - /// - /// The data value of the cell before the edit operation began. - /// - public Object Value - { - get { return this.value; } - } - private Object value; - - /// - /// The bounds of the cell that is going to be or has been edited. - /// - public Rectangle CellBounds - { - get { return this.cellBounds; } - } - private Rectangle cellBounds; - } - - //----------------------------------------------------------------------- - // Cell editors - // These classes are simple cell editors that make it easier to get and set - // the value that the control is showing. - - /// - /// This editor simply shows and edits integer values. - /// - public class IntUpDown : NumericUpDown - { - public IntUpDown() - { - this.DecimalPlaces = 0; - this.Minimum = -9999999; - this.Maximum = 9999999; - } - - new public int Value - { - get { return Decimal.ToInt32(base.Value); } - set { base.Value = new Decimal(value); } - } - } - - /// - /// This editor simply shows and edits unsigned integer values. - /// - public class UintUpDown : NumericUpDown - { - public UintUpDown() - { - this.DecimalPlaces = 0; - this.Minimum = 0; - this.Maximum = 9999999; - } - - new public uint Value - { - get { return Decimal.ToUInt32(base.Value); } - set { base.Value = new Decimal(value); } - } - } - - /// - /// This editor simply shows and edits boolean values. - /// - /// You can intercept the CellEditStarting event if you want - /// to change the characteristics of the editor. For example, by changing - /// the labels to "No" and "Yes". The false value must come first. - public class BooleanCellEditor : ComboBox - { - public BooleanCellEditor() - { - this.DropDownStyle = ComboBoxStyle.DropDownList; - this.Items.AddRange(new String[] { "False", "True" }); // needs to be localizable - } - - public bool Value - { - get { return this.SelectedIndex == 1; } - set { - if (value) - this.SelectedIndex = 1; - else - this.SelectedIndex = 0; - } - } - } - - /// - /// This editor simply shows and edits floating point values. - /// - /// You can intercept the CellEditStarting event if you want - /// to change the characteristics of the editor. For example, by increasing - /// the number of decimal places. - public class FloatCellEditor : NumericUpDown - { - public FloatCellEditor() - { - this.DecimalPlaces = 2; - this.Minimum = -9999999; - this.Maximum = 9999999; - } - - new public double Value - { - get { return Convert.ToDouble(base.Value); } - set { base.Value = Convert.ToDecimal(value); } - } - } - - /// - /// This editor shows and auto completes values from the given listview column. - /// - public class AutoCompleteCellEditor : ComboBox - { - public AutoCompleteCellEditor(ObjectListView lv, OLVColumn column) - { - this.DropDownStyle = ComboBoxStyle.DropDown; - - Dictionary alreadySeen = new Dictionary(); - for (int i = 0; i < Math.Min(lv.GetItemCount(), 1000); i++) { - String str = column.GetStringValue(lv.GetModelObject(i)); - if (!alreadySeen.ContainsKey(str)) { - this.Items.Add(str); - alreadySeen[str] = true; - } - } - - this.Sorted = true; - this.AutoCompleteSource = AutoCompleteSource.ListItems; - this.AutoCompleteMode = AutoCompleteMode.Append; - } - } - - /// - /// Return a collection of columns that are appropriate to the given view. - /// Only Tile and Details have columns; all other views have 0 columns. - /// - /// Which view are the columns being calculate for? - /// A list of columns - public List GetFilteredColumns(View view) - { - // For both detail and tile view, the first column must be included. Normally, we would - // use the ColumnHeader.Index property, but if the header is not currently part of a ListView - // that property returns -1. So, we track the index of - // the column header, and always include the first header. - - int index = 0; - switch (view) { - case View.Details: - return this.AllColumns.FindAll(delegate(OLVColumn x) { return (index++ == 0) || x.IsVisible; }); - case View.Tile: - return this.AllColumns.FindAll(delegate(OLVColumn x) { return (index++ == 0) || x.IsTileViewColumn; }); - default: - return new List(); ; - } - } - - #endregion - - #region Design Time - - /// - /// This class works in conjunction with the OLVColumns property to allow OLVColumns - /// to be added to the ObjectListView. - /// - internal class OLVColumnCollectionEditor : System.ComponentModel.Design.CollectionEditor - { - public OLVColumnCollectionEditor(Type t) - : base(t) - { - } - - protected override Type CreateCollectionItemType() - { - return typeof(OLVColumn); - } - - public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) - { - ListView.ColumnHeaderCollection cols = (ListView.ColumnHeaderCollection)value; - - // Hack to figure out which ObjectListView we are working on - ObjectListView olv; - if (cols.Count == 0) { - cols.Add(new OLVColumn()); - olv = (ObjectListView)cols[0].ListView; - cols.Clear(); - olv.AllColumns.Clear(); - } else - olv = (ObjectListView)cols[0].ListView; - - // Edit all the columns, not just the ones that are visible - base.EditValue(context, provider, olv.AllColumns); - - // Calculate just the visible columns - List newColumns = olv.GetFilteredColumns(View.Details); - olv.Columns.Clear(); - olv.Columns.AddRange(newColumns.ToArray()); - - return olv.Columns; - } - } - - /// - /// Return Columns for this list. We hide the original so we can associate - /// a specialised editor with it. - /// - [Editor(typeof(OLVColumnCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))] - new public ListView.ColumnHeaderCollection Columns { - get { - return base.Columns; - } - } - - #endregion - - private IEnumerable objects; // the collection of objects on show - protected OLVColumn lastSortColumn; // which column did we last sort by - protected SortOrder lastSortOrder; // which direction did we last sort - private bool showImagesOnSubItems; // should we try to show images on subitems? - private bool showSortIndicators; // should we show sort indicators in the column headers? - private bool showItemCountOnGroups; // should we show items count in group labels? - private string groupWithItemCountFormat; // when a group title has an item count, how should the label be formatted? - private string groupWithItemCountSingularFormat; // when a group title has an item count of 1, how should the label be formatted? - private bool useAlternatingBackColors; // should we use different colors for alternate lines? - private Color alternateRowBackColor = Color.Empty; // what color background should alternate lines have? - private SortDelegate customSorter; // callback for handling custom sort by column processing - private Rectangle lastUpdateRectangle; // remember the update rect from the last WM_PAINT msg - } - - /// - /// Wrapper for all native method calls on ListView controls - /// - internal class NativeMethods { - - private const int LVM_FIRST = 0x1000; - private const int LVM_GETHEADER = LVM_FIRST + 31; - private const int LVM_SETITEMSTATE = LVM_FIRST + 43; - private const int LVM_SETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 54; - private const int LVM_SETITEM = LVM_FIRST + 76; - private const int LVM_GETCOLUMN = LVM_FIRST + 95; - private const int LVM_SETCOLUMN = LVM_FIRST + 96; - - private const int LVS_EX_SUBITEMIMAGES = 0x0002; - - private const int LVIF_TEXT = 0x0001; - private const int LVIF_IMAGE = 0x0002; - private const int LVIF_PARAM = 0x0004; - private const int LVIF_STATE = 0x0008; - private const int LVIF_INDENT = 0x0010; - private const int LVIF_NORECOMPUTE = 0x0800; - - private const int LVCF_FMT = 0x0001; - private const int LVCF_WIDTH = 0x0002; - private const int LVCF_TEXT = 0x0004; - private const int LVCF_SUBITEM = 0x0008; - private const int LVCF_IMAGE = 0x0010; - private const int LVCF_ORDER = 0x0020; - private const int LVCFMT_LEFT = 0x0000; - private const int LVCFMT_RIGHT = 0x0001; - private const int LVCFMT_CENTER = 0x0002; - private const int LVCFMT_JUSTIFYMASK = 0x0003; - - private const int LVCFMT_IMAGE = 0x0800; - private const int LVCFMT_BITMAP_ON_RIGHT = 0x1000; - private const int LVCFMT_COL_HAS_IMAGES = 0x8000; - - private const int HDM_FIRST = 0x1200; - private const int HDM_HITTEST = HDM_FIRST + 6; - private const int HDM_GETITEM = HDM_FIRST + 11; - private const int HDM_SETITEM = HDM_FIRST + 12; - - private const int HDI_WIDTH = 0x0001; - private const int HDI_TEXT = 0x0002; - private const int HDI_FORMAT = 0x0004; - private const int HDI_BITMAP = 0x0010; - private const int HDI_IMAGE = 0x0020; - - private const int HDF_LEFT = 0x0000; - private const int HDF_RIGHT = 0x0001; - private const int HDF_CENTER = 0x0002; - private const int HDF_JUSTIFYMASK = 0x0003; - private const int HDF_RTLREADING = 0x0004; - private const int HDF_STRING = 0x4000; - private const int HDF_BITMAP = 0x2000; - private const int HDF_BITMAP_ON_RIGHT = 0x1000; - private const int HDF_IMAGE = 0x0800; - private const int HDF_SORTUP = 0x0400; - private const int HDF_SORTDOWN = 0x0200; - - [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)] - private struct LVITEM - { - public int mask; - public int iItem; - public int iSubItem; - public int state; - public int stateMask; - [MarshalAs(UnmanagedType.LPTStr)] - public string pszText; - public int cchTextMax; - public int iImage; - public int lParam; - // These are available in Common Controls >= 0x0300 - public int iIndent; - // These are available in Common Controls >= 0x056 - public int iGroupId; - public int cColumns; - public IntPtr puColumns; - }; - - [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)] - private struct LVCOLUMN - { - public int mask; - public int fmt; - public int cx; - [MarshalAs(UnmanagedType.LPTStr)] - public string pszText; - public int cchTextMax; - public int iSubItem; - // These are available in Common Controls >= 0x0300 - public int iImage; - public int iOrder; - }; - - /// - /// Notify message header structure. - /// - [StructLayout(LayoutKind.Sequential)] - public struct NMHDR - { - public IntPtr hwndFrom; - public int idFrom; - public int code; - } - - [StructLayout(LayoutKind.Sequential)] - public struct NMHEADER - { - public NMHDR nhdr; - public int iItem; - public int iButton; - public IntPtr pHDITEM; - } - - [StructLayout(LayoutKind.Sequential)] - public struct HDITEM - { - public int mask; - public int cxy; - public IntPtr pszText; - public IntPtr hbm; - public int cchTextMax; - public int fmt; - public IntPtr lParam; - public int iImage; - public int iOrder; - //if (_WIN32_IE >= 0x0500) - public int type; - public IntPtr pvFilter; - } - - [StructLayout(LayoutKind.Sequential)] - public class HDHITTESTINFO - { - public int pt_x; - public int pt_y; - public int flags; - public int iItem; - } - - // Various flavours of SendMessage: plain vanilla, - // others pass references to various structures - [DllImport("user32.dll", CharSet=CharSet.Auto)] - private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam); - [DllImport("user32.dll", EntryPoint="SendMessage", CharSet=CharSet.Auto)] - private static extern IntPtr SendMessageLVItem(IntPtr hWnd, int msg, int wParam, ref LVITEM lvi); - [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] - private static extern IntPtr SendMessageLVColumn(IntPtr hWnd, int msg, int wParam, ref LVCOLUMN lvc); - [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] - private static extern IntPtr SendMessageHDItem(IntPtr hWnd, int msg, int wParam, ref HDITEM hdi); - [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] - public static extern IntPtr SendMessageHDHITTESTINFO(IntPtr hWnd, int Msg, IntPtr wParam, [In, Out] HDHITTESTINFO lParam); - - /// - /// Make sure the ListView has the extended style that says to display subitem images. - /// - /// This method must be called after any .NET call that update the extended styles - /// since they seem to erase this setting. - /// The listview to send a message to - public static void ForceSubItemImagesExStyle (ListView list) - { - SendMessage(list.Handle, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_SUBITEMIMAGES, LVS_EX_SUBITEMIMAGES); - } - - /// - /// For the given item and subitem, make it display the given image - /// - /// The listview to send a message to - /// row number (0 based) - /// subitem (0 is the item itself) - /// index into the image list - public static void SetSubItemImage(ListView list, int itemIndex, int subItemIndex, int imageIndex) - { - LVITEM lvItem = new LVITEM(); - lvItem.mask = LVIF_IMAGE; - lvItem.iItem = itemIndex; - lvItem.iSubItem = subItemIndex; - lvItem.iImage = imageIndex; - SendMessageLVItem(list.Handle, LVM_SETITEM, 0, ref lvItem); - } - - /// - /// Setup the given column of the listview to show the given image to the right of the text. - /// If the image index is -1, any previous image is cleared - /// - /// The listview to send a message to - /// Index of the column to modifiy - /// Index into the small image list - public static void SetColumnImage(ListView list, int columnIndex, SortOrder order, int imageIndex) - { - IntPtr hdrCntl = NativeMethods.GetHeaderControl(list); - if (hdrCntl.ToInt32() == 0) - return; - - HDITEM item = new HDITEM(); - item.mask = HDI_FORMAT; - IntPtr result = SendMessageHDItem(hdrCntl, HDM_GETITEM, columnIndex, ref item); - - item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN | HDF_IMAGE | HDF_BITMAP_ON_RIGHT); - - if (NativeMethods.HasBuiltinSortIndicators()) { - if (order == SortOrder.Ascending) - item.fmt |= HDF_SORTUP; - if (order == SortOrder.Descending) - item.fmt |= HDF_SORTDOWN; - } else { - item.mask |= HDI_IMAGE; - item.fmt |= (HDF_IMAGE | HDF_BITMAP_ON_RIGHT); - item.iImage = imageIndex; - } - - result = SendMessageHDItem(hdrCntl, HDM_SETITEM, columnIndex, ref item); - } - - /// - /// Does this version of the operating system have builtin sort indicators? - /// - /// Are there builtin sort indicators - /// XP and later have these - public static bool HasBuiltinSortIndicators() - { - return OSFeature.Feature.GetVersionPresent(OSFeature.Themes) != null; - } - - [DllImport("user32.dll", EntryPoint = "GetUpdateRect", CharSet = CharSet.Auto)] - private static extern IntPtr GetUpdateRectInternal(IntPtr hWnd, ref Rectangle r, bool eraseBackground); - - /// - /// Return the bounds of the update region on the given control. - /// - /// The BeginPaint() system call validates the update region, effectively wiping out this information. - /// So this call has to be made before the BeginPaint() call. - /// The control whose update region is be calculated - /// A rectangle - public static Rectangle GetUpdateRect(Control cntl) - { - Rectangle r = new Rectangle(); - GetUpdateRectInternal(cntl.Handle, ref r, false); - return r; - } - - [DllImport("user32.dll", EntryPoint = "ValidateRect", CharSet = CharSet.Auto)] - private static extern IntPtr ValidatedRectInternal(IntPtr hWnd, ref Rectangle r); - - /// - /// Validate an area of the given control. A validated area will not be repainted at the next redraw. - /// - /// The control to be validated - /// The area of the control to be validated - public static void ValidateRect(Control cntl, Rectangle r) - { - ValidatedRectInternal(cntl.Handle, ref r); - } - - /// - /// Select all rows on the given listview - /// - /// The listview whose items are to be selected - public static void SelectAllItems(ListView list) - { - NativeMethods.SetItemState(list, -1, 2, 2); - } - - /// - /// Deselect all rows on the given listview - /// - /// The listview whose items are to be deselected - public static void DeselectAllItems(ListView list) - { - NativeMethods.SetItemState(list, -1, 2, 0); - } - - /// - /// Set the item state on the given item - /// - /// The listview whose item's state is to be changed - /// The index of the item to be changed - /// Which bits of the value are to be set? - /// The value to be set - public static void SetItemState(ListView list, int itemIndex, int mask, int value) - { - LVITEM lvItem = new LVITEM(); - lvItem.stateMask = mask; - lvItem.state = value; - SendMessageLVItem(list.Handle, LVM_SETITEMSTATE, itemIndex, ref lvItem); - } - - /// - /// Return the handle to the header control on the given list - /// - /// The listview whose header control is to be returned - /// The handle to the header control - public static IntPtr GetHeaderControl(ListView list) - { - return SendMessage(list.Handle, LVM_GETHEADER, 0, 0); - } - - /// - /// Return the index of the divider under the given point. Return -1 if no divider is under the pt - /// - /// The list we are interested in - /// The client co-ords - /// The index of the divider under the point, or -1 if no divider is under that point - public static int GetDividerUnderPoint(IntPtr handle, Point pt) - { - const int HHT_ONDIVIDER = 4; - - HDHITTESTINFO testInfo = new HDHITTESTINFO(); - testInfo.pt_x = pt.X; - testInfo.pt_y = pt.Y; - IntPtr result = NativeMethods.SendMessageHDHITTESTINFO(handle, HDM_HITTEST, IntPtr.Zero, testInfo); - if ((testInfo.flags & HHT_ONDIVIDER) != 0) - return result.ToInt32(); - else - return -1; - } - } - - /// - /// A virtual object list view operates in virtual mode, that is, it only gets model objects for - /// a row when it is needed. This gives it the ability to handle very large numbers of rows with - /// minimal resources. - /// - /// A listview is not a great user interface for a large number of items. But if you've - /// ever wanted to have a list with 10 million items, go ahead, knock yourself out. - /// Virtual lists can never iterate their contents. That would defeat the whole purpose. - /// Given the above, grouping and sorting are not possible on virtual lists. But if the backing data store has - /// a sorting mechanism, a CustomSorter can be installed which will be called when the sorting is required. - /// For the same reason, animate GIFs should not be used in virtual lists. Animated GIFs require some state - /// information to be stored for each animation, but virtual lists specifically do not keep any state information. - /// You really do not want to keep state information for 10 million animations! - /// - public class VirtualObjectListView : ObjectListView - { - /// - /// Create a VirtualObjectListView - /// - public VirtualObjectListView() - : base() - { - this.ShowGroups = false; // virtual lists can never show groups - this.VirtualMode = true; - this.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(this.HandleRetrieveVirtualItem); - - // Install a null custom sorter to turn off sorting. Who wants to fetch and sort 10 million items? - this.CustomSorter = delegate(OLVColumn column, SortOrder sortOrder) { }; - } - - #region Public Properties - - /// - /// This delegate is used to fetch a rowObject, given it's index within the list - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public RowGetterDelegate RowGetter - { - get { return rowGetter; } - set { rowGetter = value; } - } - - #endregion - - #region OLV accessing - - /// - /// Return the number of items in the list - /// - /// the number of items in the list - override public int GetItemCount() - { - return this.VirtualListSize; - } - - /// - /// Return the item at the given index - /// - /// Index of the item to be returned - /// An OLVListItem - override public OLVListItem GetItem(int index) - { - return this.MakeListViewItem(index); - } - - /// - /// Return the model object at the given index - /// - /// Index of the model object to be returned - /// A model object - override public object GetModelObject(int index) - { - return this.GetRowObjectAt(index); - } - - #endregion - - #region Object manipulation - - /// - /// Select the row that is displaying the given model object. - /// This does nothing in virtual lists. - /// - /// This is a no-op for virtual lists, since there is no way to map the model - /// object back to the ListViewItem that represents it. - /// The object that gave data - override public void SelectObject(object modelObject) - { - // do nothing - } - - /// - /// Select the rows that is displaying any of the given model object. - /// This does nothing in virtual lists. - /// - /// This is a no-op for virtual lists, since there is no way to map the model - /// objects back to the ListViewItem that represents them. - /// A collection of model objects - override public void SelectObjects(IList modelObjects) - { - // do nothing - } - - /// - /// Update the rows that are showing the given objects - /// - /// This is a no-op for virtual lists, since there is no way to map the model - /// objects back to the ListViewItem that represents them. - override public void RefreshObjects(IList modelObjects) - { - // do nothing - } - - #endregion - - /// - /// Invalidate any cached information when we rebuild the list. - /// - public override void BuildList(bool shouldPreserveSelection) - { - this.lastRetrieveVirtualItemIndex = -1; - - // This call was in the older code, but I can't think why since virtual lists - // can't be build in that fashion. - //base.BuildList(false); - } - - /// - /// Prepare the listview to show alternate row backcolors - /// - /// Alternate colored backrows can't be handle in the same way as our base class. - /// With virtual lists, they are handled at RetrieveVirtualItem time. - protected override void PrepareAlternateBackColors () - { - // do nothing - } - - #region Event handlers - - /// - /// Handle a RetrieveVirtualItem - /// - /// - /// - protected void HandleRetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) - { - // .NET 2.0 seems to generate a lot of these events. Before drawing *each* sub-item, - // this event is triggered 4-8 times for the same index. So we save lots of CPU time - // by caching the last result. - if (this.lastRetrieveVirtualItemIndex != e.ItemIndex) { - this.lastRetrieveVirtualItemIndex = e.ItemIndex; - this.lastRetrieveVirtualItem = this.MakeListViewItem(e.ItemIndex); - } - e.Item = this.lastRetrieveVirtualItem; - } - - /// - /// Create a OLVListItem for given row index - /// - /// The index of the row that is needed - /// An OLVListItem - public OLVListItem MakeListViewItem(int itemIndex) - { - OLVListItem olvi = new OLVListItem(this.GetRowObjectAt(itemIndex)); - this.FillInValues(olvi, olvi.RowObject); - if (this.UseAlternatingBackColors) { - if (this.View == View.Details && itemIndex % 2 == 1) - olvi.BackColor = this.AlternateRowBackColorOrDefault; - else - olvi.BackColor = this.BackColor; - - // For some reason, UseItemStyleForSubItems doesn't work for the background color - // when owner drawing the list, so we have to specifically give each subitem - // the desired color - if (this.OwnerDraw && olvi.UseItemStyleForSubItems) - foreach (ListViewItem.ListViewSubItem si in olvi.SubItems) - si.BackColor = olvi.BackColor; - } - this.SetSubItemImages(itemIndex, olvi); - return olvi; - } - - /// - /// Return the row object for the given row index - /// - /// index of the row whose object is to be fetched - /// A model object or null if no delegate is installed - virtual protected object GetRowObjectAt(int index) - { - if (this.RowGetter == null) - return null; - else - return this.RowGetter(index); - } - - #endregion - - #region Variable declaractions - - private RowGetterDelegate rowGetter; - protected int lastRetrieveVirtualItemIndex = -1; - private OLVListItem lastRetrieveVirtualItem; - - #endregion - } - - /// - /// A FastObjectListView trades function for speed. - /// - /// - /// On my mid-range laptop, this view builds a list of 10,000 objects in 0.1 seconds, - /// as opposed to a normal ObjectListView which takes 10-15 seconds. Lists of up to 50,000 items should be - /// able to be handled with sub-second response times even on low end machines. - /// - /// A FastObjectListView is implemented as a virtual list with some of the virtual modes limits (e.g. no sorting) - /// fixed through coding. There are some functions that simply cannot be provided. Specifically, a FastObjectListView cannot: - /// - /// shows groups - /// use Tile view - /// display images on subitems - /// - /// - /// You can circumvent the limit on subitem images by making the list owner drawn, and giving the column - /// a Renderer of BaseRenderer, e.g. myColumnWithImage.Renderer = new BaseRenderer(); - /// - public class FastObjectListView : VirtualObjectListView - { - public FastObjectListView() - { - this.SearchForVirtualItem += new SearchForVirtualItemEventHandler(FastObjectListView_SearchForVirtualItem); - - this.CustomSorter = delegate(OLVColumn column, SortOrder sortOrder) { - this.lastRetrieveVirtualItemIndex = -1; - if (sortOrder != SortOrder.None) - this.objectList.Sort(new ModelObjectComparer(column, sortOrder, this.SecondarySortColumn, this.SecondarySortOrder)); - this.objectsToIndexMap.Clear(); - for (int i = 0; i < this.objectList.Count; i++) - this.objectsToIndexMap[this.objectList[i]] = i; - }; - } - Hashtable objectsToIndexMap = new Hashtable(); - - #region Public properties - - /// - /// Get/set the list of objects that are shown by the control. - /// - /// This method preserves selection, if possible. Use SetObjects() if - /// you do not want to preserve the selection. Preserving selection is the slowest part of this - /// code and performance is O(n) where n is the number of selected rows. - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - new public ArrayList Objects - { - get { return this.objectList; } - set { - ArrayList previousSelection = this.SelectedObjects; - this.SetObjects(value); - this.SelectedObjects = previousSelection; - } - } - private ArrayList objectList = new ArrayList(); - - /// - /// When the user types into a list, should the values in the current sort column be searched to find a match? - /// If this is false, the primary column will always be used regardless of the sort column. - /// - /// When this is true, the behavior is like that of ITunes. - [Category("Behavior"), - Description("When the user types into a list, should the values in the current sort column be searched to find a match?"), - DefaultValue(false)] - public bool IsSearchOnSortColumn - { - get { return isSearchOnSortColumn; } - set { isSearchOnSortColumn = value; } - } - private bool isSearchOnSortColumn = false; - - #endregion - - #region Commands - - /// - /// Set the collection of objects that this control will show. - /// - /// - override public void SetObjects(IEnumerable collection) - { - if (collection == null) - this.objectList = new ArrayList(); - else if (collection is ICollection) - this.objectList = new ArrayList((ICollection)collection); - else { - this.objectList = new ArrayList(); - foreach (object x in collection) - this.objectList.Add(x); - } - this.VirtualListSize = this.objectList.Count; - this.Sort(); - } - - #endregion - - #region Event Handlers - - /// - /// Event handler for the column click event - /// - /// - /// This differs from its base version by trying to preserve selection. The base class, - /// being a pure virtual list, cannot maintain selection since it cannot map a - /// model objects to the row that is responsible for displaying it. This class can do that. - /// - override protected void HandleColumnClick(object sender, ColumnClickEventArgs e) - { - // Toggle the sorting direction on successive clicks on the same column - if (this.lastSortColumn != null && e.Column == this.lastSortColumn.Index) - this.lastSortOrder = (this.lastSortOrder == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending); - else - this.lastSortOrder = SortOrder.Ascending; - - this.BeginUpdate(); - ArrayList previousSelection = this.SelectedObjects; - this.Sort(e.Column); - this.SelectedObjects = previousSelection; - this.EndUpdate(); - } - - void FastObjectListView_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e) - { - // The event has e.IsPrefixSearch, but as far as I can tell, this is always false (maybe that's different under Vista) - // So we ignore IsPrefixSearch and IsTextSearch and always to a case insensitve prefix match - - int increment = (e.Direction == SearchDirectionHint.Up ? -1 : 1); - OLVColumn column = this.GetColumn(0); - if (this.IsSearchOnSortColumn && this.View == View.Details && this.lastSortColumn != null) - column = this.lastSortColumn; - - int i; - for (i = e.StartIndex; i >= 0 && i < this.objectList.Count; i += increment) { - string data = column.GetStringValue(this.objectList[i]); - if (data.StartsWith(e.Text, StringComparison.CurrentCultureIgnoreCase)) { - e.Index = i; - return; - } - } - - // Also the LVFINDINFO has a LV_WRAP flag, but the SearchForVirtualItemEventArgs does not. Why? - // We always wrap - i = (increment < 0 ? this.objectList.Count : 0); - while ((increment < 0 && i > e.StartIndex) || (increment > 0 && i < e.StartIndex)) { - string data = column.GetStringValue(this.objectList[i]); - if (data.StartsWith(e.Text, StringComparison.CurrentCultureIgnoreCase)) { - e.Index = i; - return; - } - i += increment; - } - } - - #endregion - - #region Object manipulation - - /// - /// Select the row that is displaying the given model object. - /// - /// The object that gave data - override public void SelectObject(object modelObject) - { - if (!this.objectsToIndexMap.ContainsKey(modelObject)) - return; - - int index = (int)this.objectsToIndexMap[modelObject]; - - // If this object is already selected, we don't need to do anything - if (this.SelectedIndices.Count == 1 && this.SelectedIndices[0] == index) - return; - - this.SelectedIndices.Clear(); - if (index >= 0) - this.SelectedIndices.Add(index); - } - - /// - /// Select the rows that is displaying any of the given model object. - /// - /// A collection of model objects - override public void SelectObjects(IList modelObjects) - { - this.SelectedIndices.Clear(); - - foreach (object model in modelObjects) { - if (this.objectsToIndexMap.ContainsKey(model)) { - int index = (int)this.objectsToIndexMap[model]; - if (index >= 0) - this.SelectedIndices.Add(index); - } - } - } - - /// - /// Update the rows that are showing the given objects - /// - override public void RefreshObjects(IList modelObjects) - { - this.Invalidate(); - } - - #endregion - - #region Implementation - - /// - /// Return the row object for the given row index - /// - /// index of the row whose object is to be fetched - /// A model object or null if no delegate is installed - override protected object GetRowObjectAt(int index) - { - return this.objectList[index]; - } - - - internal class ModelObjectComparer : IComparer - { - public ModelObjectComparer(OLVColumn col, SortOrder order) - { - this.column = col; - this.sortOrder = order; - this.secondComparer = null; - } - - public ModelObjectComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2) - : this(col, order) - { - // There is no point in secondary sorting on the same column - if (col != col2) - this.secondComparer = new ModelObjectComparer(col2, order2); - } - - public int Compare(object x, object y) - { - int result = 0; - object x1 = this.column.GetValue(x); - object y1 = this.column.GetValue(y); - - if (this.sortOrder == SortOrder.None) - return 0; - - // Handle nulls. Null values come last - bool xIsNull = (x1 == null || x1 == System.DBNull.Value); - bool yIsNull = (y1 == null || y1 == System.DBNull.Value); - if (xIsNull || yIsNull) { - if (xIsNull && yIsNull) - result = 0; - else - result = (xIsNull ? -1 : 1); - } else { - result = this.CompareValues(x1, y1); - } - - if (this.sortOrder == SortOrder.Descending) - result = 0 - result; - - // If the result was equality, use the secondary comparer to resolve it - if (result == 0 && this.secondComparer != null) - result = this.secondComparer.Compare(x, y); - - return result; - } - - public int CompareValues(object x, object y) - { - // Force case insensitive compares on strings - if (x is String) - return String.Compare((String)x, (String)y, true); - else { - IComparable comparable = x as IComparable; - if (comparable != null) - return comparable.CompareTo(y); - else - return 0; - } - } - - private OLVColumn column; - private SortOrder sortOrder; - private ModelObjectComparer secondComparer; - } - - #endregion - } - - /// - /// A DataListView is a ListView that can be bound to a datasource (which would normally be a DataTable or DataView). - /// - /// - /// This listview keeps itself in sync with its source datatable by listening for change events. - /// If the listview has no columns when given a data source, it will automatically create columns to show all of the datatables columns. - /// This will be only the simplest view of the world, and would look more interesting with a few delegates installed. - /// This listview will also automatically generate missing aspect getters to fetch the values from the data view. - /// Changing data sources is possible, but error prone. Before changing data sources, the programmer is responsible for modifying/resetting - /// the column collection to be valid for the new data source. - /// - public class DataListView : ObjectListView - { - /// - /// Make a DataListView - /// - public DataListView() : base () - { - } - - #region Public Properties - - /// - /// Get or set the DataSource that will be displayed in this list view. - /// - /// The DataSource should implement either , , - /// or . Some common examples are the following types of objects: - /// - /// - /// - /// - /// - /// - /// - /// When binding to a list container (i.e. one that implements the - /// interface, such as ) - /// you must also set the property in order - /// to identify which particular list you would like to display. You - /// may also set the property even when - /// DataSource refers to a list, since can - /// also be used to navigate relations between lists. - /// - [Category("Data"), - TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")] - public Object DataSource - { - get { return dataSource; } - set { - //THINK: Should we only assign it if it is changed? - //if (dataSource != value) { - dataSource = value; - this.RebindDataSource(true); - //} - } - } - private Object dataSource; - - /// - /// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data. - /// - /// If the data source is not a DataSet or DataViewManager, this property has no effect - [Category("Data"), - Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)), - DefaultValue("")] - public string DataMember - { - get { return dataMember; } - set { - if (dataMember != value) { - dataMember = value; - RebindDataSource(); - } - } - } - private string dataMember = ""; - - #endregion - - #region Initialization - - private CurrencyManager currencyManager = null; - - /// - /// Our data source has changed. Figure out how to handle the new source - /// - protected void RebindDataSource() - { - RebindDataSource(false); - } - - /// - /// Our data source has changed. Figure out how to handle the new source - /// - protected void RebindDataSource(bool forceDataInitialization) - { - if (this.BindingContext == null) - return; - - // Obtain the CurrencyManager for the current data source. - CurrencyManager tempCurrencyManager = null; - - if (this.DataSource != null) { - tempCurrencyManager = (CurrencyManager)this.BindingContext[this.DataSource, this.DataMember]; - } - - // Has our currency manager changed? - if (this.currencyManager != tempCurrencyManager) { - - // Stop listening for events on our old currency manager - if (this.currencyManager != null) { - this.currencyManager.MetaDataChanged -= new EventHandler(currencyManager_MetaDataChanged); - this.currencyManager.PositionChanged -= new EventHandler(currencyManager_PositionChanged); - this.currencyManager.ListChanged -= new ListChangedEventHandler(currencyManager_ListChanged); - } - - this.currencyManager = tempCurrencyManager; - - // Start listening for events on our new currency manager - if (this.currencyManager != null) { - this.currencyManager.MetaDataChanged += new EventHandler(currencyManager_MetaDataChanged); - this.currencyManager.PositionChanged += new EventHandler(currencyManager_PositionChanged); - this.currencyManager.ListChanged += new ListChangedEventHandler(currencyManager_ListChanged); - } - - // Our currency manager has changed so we have to initialize a new data source - forceDataInitialization = true; - } - - if (forceDataInitialization) - InitializeDataSource(); - } - - /// - /// The data source for this control has changed. Reconfigure the control for the new source - /// - protected void InitializeDataSource() - { - if (this.Frozen || this.currencyManager == null) - return; - - this.CreateColumnsFromSource(); - this.CreateMissingAspectGettersAndPutters(); - this.SetObjects(this.currencyManager.List); - - // If we have some data, resize the new columns based on the data available. - if (this.Items.Count > 0) { - foreach (ColumnHeader column in this.Columns) { - if (column.Width == 0) - this.AutoResizeColumn(column.Index, ColumnHeaderAutoResizeStyle.ColumnContent); - } - } - } - - /// - /// Create columns for the listview based on what properties are available in the data source - /// - /// - /// This method will not replace existing columns. - /// - protected void CreateColumnsFromSource() - { - if (this.currencyManager == null || this.Columns.Count != 0) - return; - - PropertyDescriptorCollection properties = this.currencyManager.GetItemProperties(); - if (properties.Count == 0) - return; - - for (int i = 0; i < properties.Count; i++) { - // Make a stack variable to hold the property so it can be used in the AspectGetter delegate - PropertyDescriptor property = properties[i]; - - // Relationships to other tables turn up as IBindibleLists. Don't make columns to show them. - // CHECK: Is this always true? What other things could be here? Constraints? Triggers? - if (property.PropertyType == typeof(IBindingList)) - continue; - - // Create a column - OLVColumn column = new OLVColumn(property.DisplayName, property.Name); - column.Width = 0; // zero-width since we will resize it once we have some data - column.AspectGetter = delegate(object row) { - return property.GetValue(row); - }; - // If our column is a BLOB, it could be an image, so assign a renderer to draw it. - // CONSIDER: Is this a common enough case to warrant this code? - if (property.PropertyType == typeof(System.Byte[])) - column.Renderer = new ImageRenderer(); - - // Add it to our list - this.Columns.Add(column); - } - } - - /// - /// Generate aspect getters and putters for any columns that are missing them (and for which we have - /// enough information to actually generate a getter) - /// - protected void CreateMissingAspectGettersAndPutters() - { - for (int i = 0; i < this.Columns.Count; i++) { - OLVColumn column = this.GetColumn(i); - if (column.AspectGetter == null && !String.IsNullOrEmpty(column.AspectName)) { - column.AspectGetter = delegate(object row) { - try { - // In most cases, rows will be DataRowView objects - return ((DataRowView)row)[column.AspectName]; - } catch { - return column.GetAspectByName(row); - } - }; - } - if (column.IsEditable && column.AspectPutter == null && !String.IsNullOrEmpty(column.AspectName)) { - column.AspectPutter = delegate(object row, object newValue) { - try { - // In most cases, rows will be DataRowView objects - ((DataRowView)row)[column.AspectName] = newValue; - } catch { - column.PutAspectByName(row, newValue); - } - }; - } - } - } - - #endregion - - #region Event Handlers - - /// - /// What should we do when the list is unfrozen - /// - override protected void DoUnfreeze() - { - // Clear any previous currency manager so the rebind will always work from scratch - this.RebindDataSource(true); - } - - /// - /// Handles binding context changes - /// - /// The EventArgs that will be passed to any handlers - /// of the BindingContextChanged event. - protected override void OnBindingContextChanged(EventArgs e) - { - base.OnBindingContextChanged(e); - - // If our binding context changes, we must rebind, since we will - // have a new currency managers, even if we are still bound to the - // same data source. - this.RebindDataSource(false); - } - - - /// - /// Handles parent binding context changes - /// - /// Unused EventArgs. - protected override void OnParentBindingContextChanged(EventArgs e) - { - base.OnParentBindingContextChanged(e); - - // BindingContext is an ambient property - by default it simply picks - // up the parent control's context (unless something has explicitly - // given us our own). So we must respond to changes in our parent's - // binding context in the same way we would changes to our own - // binding context. - this.RebindDataSource(false); - } - - // CurrencyManager ListChanged event handler. - // Deals with fine-grained changes to list items. - // It's actually difficult to deal with these changes in a fine-grained manner. - // If our listview is grouped, then any change may make a new group appear or - // an old group disappear. It is rarely enough to simply update the affected row. - private void currencyManager_ListChanged(object sender, ListChangedEventArgs e) - { - switch (e.ListChangedType) { - - // Well, usually fine-grained... The whole list has changed utterly, so reload it. - case ListChangedType.Reset: - this.InitializeDataSource(); - break; - - // A single item has changed, so just refresh that. - // TODO: Even in this simple case, we should probably rebuild the list. - case ListChangedType.ItemChanged: - Object changedRow = this.currencyManager.List[e.NewIndex]; - this.RefreshObject(changedRow); - break; - - // A new item has appeared, so add that. - // We get this event twice if certain grid controls are used to add a new row to a - // datatable: once when the editing of a new row begins, and once again when that - // editing commits. (If the user cancels the creation of the new row, we never see - // the second creation.) We detect this by seeing if this is a view on a row in a - // DataTable, and if it is, testing to see if it's a new row under creation. - case ListChangedType.ItemAdded: - Object newRow = this.currencyManager.List[e.NewIndex]; - DataRowView drv = newRow as DataRowView; - if (drv == null || !drv.IsNew) { - // Either we're not dealing with a view on a data table, or this is the commit - // notification. Either way, this is the final notification, so we want to - // handle the new row now! - this.InitializeDataSource(); - } - break; - - // An item has gone away. - case ListChangedType.ItemDeleted: - this.InitializeDataSource(); - break; - - // An item has changed its index. - case ListChangedType.ItemMoved: - this.InitializeDataSource(); - break; - - // Something has changed in the metadata. - // CHECK: When are these events actually fired? - case ListChangedType.PropertyDescriptorAdded: - case ListChangedType.PropertyDescriptorChanged: - case ListChangedType.PropertyDescriptorDeleted: - this.InitializeDataSource(); - break; - } - } - - - // The CurrencyManager calls this if the data source looks - // different. We just reload everything. - // CHECK: Do we need this if we are handle ListChanged metadata events? - private void currencyManager_MetaDataChanged(object sender, EventArgs e) - { - this.InitializeDataSource(); - } - - - // Called by the CurrencyManager when the currently selected item - // changes. We update the ListView selection so that we stay in sync - // with any other controls bound to the same source. - private void currencyManager_PositionChanged(object sender, EventArgs e) - { - int index = this.currencyManager.Position; - - // Make sure the index is sane (-1 pops up from time to time) - if (index < 0 || index >= this.Items.Count) - return; - - // Avoid recursion. If we are currently changing the index, don't - // start the process again. - if (this.isChangingIndex) - return; - - try { - this.isChangingIndex = true; - - // We can't use the index directly, since our listview may be sorted - this.SelectedObject = this.currencyManager.List[index]; - - // THINK: Do we always want to bring it into view? - if (this.SelectedItems.Count > 0) - this.SelectedItems[0].EnsureVisible(); - - } finally { - this.isChangingIndex = false; - } - } - private bool isChangingIndex = false; - - - // Called by Windows Forms when the currently selected index of the - // control changes. This usually happens because the user clicked on - // the control. In this case we want to notify the CurrencyManager so - // that any other bound controls will remain in sync. This method will - // also be called when we changed our index as a result of a - // notification that originated from the CurrencyManager, and in that - // case we avoid notifying the CurrencyManager back! - protected override void OnSelectedIndexChanged(EventArgs e) - { - base.OnSelectedIndexChanged(e); - - // Prevent recursion - if (this.isChangingIndex) - return; - - // If we are bound to a datasource, and only one item is selected, - // tell the currency manager which item is selected. - if (this.SelectedIndices.Count == 1 && this.currencyManager != null) { - try { - this.isChangingIndex = true; - - // We can't use the selectedIndex directly, since our listview may be sorted. - // So we have to find the index of the selected object within the original list. - this.currencyManager.Position = this.currencyManager.List.IndexOf(this.SelectedObject); - } finally { - this.isChangingIndex = false; - } - } - } - - #endregion - - } - - #region Delegate declarations - - /// - /// These delegates are used to extract an aspect from a row object - /// - public delegate Object AspectGetterDelegate(Object rowObject); - - /// - /// These delegates are used to put a changed value back into a model object - /// - public delegate void AspectPutterDelegate(Object rowObject, Object newValue); - - /// - /// These delegates can be used to convert an aspect value to a display string, - /// instead of using the default ToString() - /// - public delegate string AspectToStringConverterDelegate(Object value); - - /// - /// These delegates are used to the state of the checkbox for a row object. - /// - /// For reasons known only to someone in Microsoft, we can only set - /// a boolean on the ListViewItem to indicate it's "checked-ness", but when - /// we receive update events, we have to use a tristate CheckState. So we can - /// be told about an indeterminate state, but we can't set it ourselves. - public delegate bool CheckStateGetterDelegate(Object rowObject); - - /// - /// These delegates are used to put a changed check state back into a model object - /// - public delegate CheckState CheckStatePutterDelegate(Object rowObject, CheckState newValue); - - /// - /// These delegates are used to retrieve the object that is the key of the group to which the given row belongs. - /// - public delegate Object GroupKeyGetterDelegate(Object rowObject); - - /// - /// These delegates are used to convert a group key into a title for the group - /// - public delegate string GroupKeyToTitleConverterDelegate(Object groupKey); - - /// - /// These delegates are used to fetch the image selector that should be used - /// to choose an image for this column. - /// - public delegate Object ImageGetterDelegate(Object rowObject); - - /// - /// These delegates are used to draw a cell - /// - public delegate void RenderDelegate(DrawListViewSubItemEventArgs e, Graphics g, Rectangle r, Object rowObject); - - /// - /// These delegates are used to fetch a row object for virtual lists - /// - public delegate Object RowGetterDelegate(int rowIndex); - - /// - /// These delegates are used to format a listviewitem before it is added to the control. - /// - public delegate void RowFormatterDelegate(OLVListItem olvItem); - - /// - /// These delegates are used to sort the listview in some custom fashion - /// - public delegate void SortDelegate(OLVColumn column, SortOrder sortOrder); - - #endregion - - #region Column - - /// - /// An OLVColumn knows which aspect of an object it should present. - /// - /// - /// The column knows how to: - /// - /// extract its aspect from the row object - /// convert an aspect to a string - /// calculate the image for the row object - /// extract a group "key" from the row object - /// convert a group "key" into a title for the group - /// - /// For sorting to work correctly, aspects from the same column - /// must be of the same type, that is, the same aspect cannot sometimes - /// return strings and other times integers. - /// - [Browsable(false)] - public partial class OLVColumn : ColumnHeader - { - /// - /// Create an OLVColumn - /// - public OLVColumn() - : base () - { - } - - /// - /// Initialize a column to have the given title, and show the given aspect - /// - /// The title of the column - /// The aspect to be shown in the column - public OLVColumn(string title, string aspect) - : this () - { - this.Text = title; - this.AspectName = aspect; - } - - #region Public Properties - - /// - /// The name of the property or method that should be called to get the value to display in this column. - /// This is only used if a ValueGetterDelegate has not been given. - /// - /// This name can be dotted to chain references to properties or methods. - /// "DateOfBirth" - /// "Owner.HomeAddress.Postcode" - [Category("Behavior"), - Description("The name of the property or method that should be called to get the aspect to display in this column")] - public string AspectName { - get { return aspectName; } - set { aspectName = value; } - } - - /// - /// This format string will be used to convert an aspect to its string representation. - /// - /// - /// This string is passed as the first parameter to the String.Format() method. - /// This is only used if ToStringDelegate has not been set. - /// "{0:C}" to convert a number to currency - [Category("Behavior"), - Description("The format string that will be used to convert an aspect to its string representation"), - DefaultValue(null)] - public string AspectToStringFormat { - get { return aspectToStringFormat; } - set { aspectToStringFormat = value; } - } - - /// - /// Group objects by the initial letter of the aspect of the column - /// - /// - /// One common pattern is to group column by the initial letter of the value for that group. - /// The aspect must be a string (obviously). - /// - [Category("Behavior"), - Description("The name of the property or method that should be called to get the aspect to display in this column"), - DefaultValue(false)] - public bool UseInitialLetterForGroup - { - get { return useInitialLetterForGroup; } - set { useInitialLetterForGroup = value; } - } - - /// - /// Get/set whether this column should be used when the view is switched to tile view. - /// - /// Column 0 is always included in tileview regardless of this setting. - /// Tile views do not work well with many "columns" of information, 2 or 3 works best. - [Category("Behavior"), - Description("Will this column be used when the view is switched to tile view"), - DefaultValue(false)] - public bool IsTileViewColumn - { - get { return isTileViewColumn; } - set { isTileViewColumn = value; } - } - private bool isTileViewColumn = false; - - /// - /// This delegate will be used to extract a value to be displayed in this column. - /// - /// - /// If this is set, AspectName is ignored. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public AspectGetterDelegate AspectGetter { - get { return aspectGetter; } - set { - aspectGetter = value; - aspectGetterAutoGenerated = false; - } - } - - /// - /// The delegate that will be used to translate the aspect to display in this column into a string. - /// - /// If this value is set, ValueToStringFormat will be ignored. - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public AspectToStringConverterDelegate AspectToStringConverter { - get { return aspectToStringConverter; } - set { aspectToStringConverter = value; } - } - - /// - /// This delegate is called to get the image selector of the image that should be shown in this column. - /// It can return an int, string, Image or null. - /// - /// This delegate can use these return value to identify the image: - /// - /// null or -1 -- indicates no image - /// an int -- the int value will be used as an index into the image list - /// a String -- the string value will be used as a key into the image list - /// an Image -- the Image will be drawn directly (only in OwnerDrawn mode) - /// - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public ImageGetterDelegate ImageGetter { - get { return imageGetter; } - set { imageGetter = value; } - } - - /// - /// This delegate is called to get the object that is the key for the group - /// to which the given row belongs. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public GroupKeyGetterDelegate GroupKeyGetter { - get { return groupKeyGetter; } - set { groupKeyGetter = value; } - } - - /// - /// This delegate is called to convert a group key into a title for that group. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public GroupKeyToTitleConverterDelegate GroupKeyToTitleConverter { - get { return groupKeyToTitleConverter; } - set { groupKeyToTitleConverter = value; } - } - - /// - /// This delegate is called when a cell needs to be drawn in OwnerDrawn mode. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public RenderDelegate RendererDelegate - { - get { return rendererDelegate; } - set { rendererDelegate = value; } - } - - /// - /// Get/set the renderer that will be invoked when a cell needs to be redrawn - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public BaseRenderer Renderer - { - get { return renderer; } - set { - renderer = value; - if (renderer == null) - this.RendererDelegate = null; - else { - renderer.Column = this; - this.RendererDelegate = new RenderDelegate(renderer.HandleRendering); - } - } - } - private BaseRenderer renderer; - - /// - /// Remember if this aspect getter for this column was generated internally, and can therefore - /// be regenerated at will - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public bool AspectGetterAutoGenerated { - get { return aspectGetterAutoGenerated; } - set { aspectGetterAutoGenerated = value; } - } - private bool aspectGetterAutoGenerated; - - /// - /// When the listview is grouped by this column and group title has an item count, - /// how should the lable be formatted? - /// - /// - /// The given format string can/should have two placeholders: - /// - /// {0} - the original group title - /// {1} - the number of items in the group - /// - /// If this value is not set, the values from the list view will be used - /// - /// "{0} [{1} items]" - [Category("Behavior"), - Description("The format to use when suffixing item counts to group titles"), - DefaultValue(null)] - public string GroupWithItemCountFormat - { - get { return groupWithItemCountFormat; } - set { groupWithItemCountFormat = value; } - } - private string groupWithItemCountFormat; - - /// - /// Return this.GroupWithItemCountFormat or a reasonable default - /// - [Browsable(false)] - public string GroupWithItemCountFormatOrDefault - { - get { - if (String.IsNullOrEmpty(this.GroupWithItemCountFormat)) - // There is one rare but pathelogically possible case where the ListView can - // be null, so we have to provide a workable default for that rare case. - if (this.ListView == null) - return "{0} [{1} items]"; - else - return ((ObjectListView)this.ListView).GroupWithItemCountFormatOrDefault; - else - return this.GroupWithItemCountFormat; - } - } - - /// - /// When the listview is grouped by this column and a group title has an item count, - /// how should the lable be formatted if there is only one item in the group? - /// - /// - /// The given format string can/should have two placeholders: - /// - /// {0} - the original group title - /// {1} - the number of items in the group (always 1) - /// - /// If this value is not set, the values from the list view will be used - /// - /// "{0} [{1} item]" - [Category("Behavior"), - Description("The format to use when suffixing item counts to group titles"), - DefaultValue(null)] - public string GroupWithItemCountSingularFormat - { - get { return groupWithItemCountSingularFormat; } - set { groupWithItemCountSingularFormat = value; } - } - private string groupWithItemCountSingularFormat; - - /// - /// Return this.GroupWithItemCountSingularFormat or a reasonable default - /// - [Browsable(false)] - public string GroupWithItemCountSingularFormatOrDefault - { - get { - if (String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat)) - // There is one pathelogically rare but still possible case where the ListView can - // be null, so we have to provide a workable default for that rare case. - if (this.ListView == null) - return "{0} [{1} item]"; - else - return ((ObjectListView)this.ListView).GroupWithItemCountSingularFormatOrDefault; - else - return this.GroupWithItemCountSingularFormat; - } - } - - /// - /// What is the minimum width that the user can give to this column? - /// - /// -1 means there is no minimum width. Give this the same value as MaximumWidth to make a fixed width column. - [Category("Misc"), - Description("What is the minimum width to which the user can resize this column?"), - DefaultValue(-1)] - public int MinimumWidth - { - get { return minWidth; } - set { - minWidth = value; - if (this.Width < minWidth) - this.Width = minWidth; - } - } - private int minWidth = -1; - - /// - /// What is the maximum width that the user can give to this column? - /// - /// -1 means there is no maximum width. Give this the same value as MinimumWidth to make a fixed width column. - [Category("Misc"), - Description("What is the maximum width to which the user can resize this column?"), - DefaultValue(-1)] - public int MaximumWidth - { - get { return maxWidth; } - set { - maxWidth = value; - if (maxWidth != -1 && this.Width > maxWidth) - this.Width = maxWidth; - } - } - private int maxWidth = -1; - - /// - /// Is this column a fixed width column? - /// - [Browsable(false)] - public bool IsFixedWidth - { - get { - return (this.MinimumWidth != -1 && this.MaximumWidth != -1 && this.MinimumWidth >= this.MaximumWidth); - } - } - - /// - /// This delegate will be used to put an edited value back into the model object. - /// - /// - /// This does nothing if IsEditable == false. - /// - [Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public AspectPutterDelegate AspectPutter - { - get { return aspectPutter; } - set { aspectPutter = value; } - } - - /// - /// Can the values shown in this column be edited? - /// - /// This defaults to true, since the primary means to control the editability of a listview - /// is on the listview itself. Once a listview is editable, all the columns are too, unless the - /// programmer explicitly marks them as not editable - [Category("Misc"), - Description("Can the value in this column be edited?"), - DefaultValue(true)] - public bool IsEditable - { - get { return isEditable; } - set { isEditable = value; } - } - private bool isEditable = true; - - /// - /// Return the control that should be used to edit cells in this column - /// - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public Control CellEditor - { - get { return cellEditor; } - set { cellEditor = value; } - } - private Control cellEditor; - - /// - /// Can this column be seen by the user? - /// - /// After changing this value, you must call RebuildColumns() before the changes will be effected. - [Category("Misc"), - Description("Can this column be seen by the user?"), - DefaultValue(true)] - public bool IsVisible - { - get { return isVisible; } - set { isVisible = value; } - } - private bool isVisible = true; - - /// - /// Where was this column last positioned within the Detail view columns - /// - /// DisplayIndex is volatile. Once a column is removed from the control, - /// there is no way to discover where it was in the display order. This property - /// guards that information even when the column is not in the listview's active columns. - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public int LastDisplayIndex = -1; - - #endregion - - /// - /// For a given row object, return the object that is to be displayed in this column. - /// - /// The row object that is being displayed - /// An object, which is the aspect to be displayed - public object GetValue(object rowObject) - { - if (this.aspectGetter == null) - return this.GetAspectByName(rowObject); - else - return this.aspectGetter(rowObject); - } - - /// - /// For a given row object, extract the value indicated by the AspectName property of this column. - /// - /// The row object that is being displayed - /// An object, which is the aspect named by AspectName - public object GetAspectByName(object rowObject) - { - if (string.IsNullOrEmpty(this.aspectName)) - return null; - - //CONSIDER: Should we include NonPublic in this list? - BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | - BindingFlags.InvokeMethod | BindingFlags.GetProperty | BindingFlags.GetField; - object source = rowObject; - foreach (string property in this.aspectName.Split('.')) { - try { - source = source.GetType().InvokeMember(property, flags, null, source, null); - } catch (System.MissingMethodException) { - return String.Format("Cannot invoke '{0}' on a {1}", property, source.GetType()); - } - } - return source; - } - - public void PutValue(Object rowObject, Object newValue) - { - if (this.aspectPutter == null) - this.PutAspectByName(rowObject, newValue); - else - this.aspectPutter(rowObject, newValue); - } - - public void PutAspectByName(Object rowObject, Object newValue) - { - if (string.IsNullOrEmpty(this.aspectName)) - return; - - // Navigated through the dotted path until we reach the target object. - // We then try to set the last property on the dotted path on that target. - // So, if rowObject is a Person, then an aspect named "HomeAddress.Postcode" - // will first fetch the "HomeAddress" property, and then try to set the - // "Postcode" property on the home address object. - - //CONSIDER: Should we include NonPublic in this list? - BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | - BindingFlags.InvokeMethod | BindingFlags.GetProperty | BindingFlags.GetField; - Object target = rowObject; - List parentProperties = new List(this.aspectName.Split('.')); - String lastProperty = parentProperties[parentProperties.Count - 1]; - parentProperties.RemoveAt(parentProperties.Count - 1); - foreach (string property in parentProperties) { - try { - target = target.GetType().InvokeMember(property, flags, null, target, null); - } catch (System.MissingMethodException) { - System.Diagnostics.Debug.WriteLine(String.Format("Cannot invoke '{0}' on a {1}", property, target.GetType())); - return; - } - } - - // Now try to set the value - try { - BindingFlags flags2 = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.SetField; - target.GetType().InvokeMember(lastProperty, flags2, null, target, new Object[] { newValue }); - } catch (System.MissingMethodException ex) { - System.Diagnostics.Debug.WriteLine("Invoke PutAspectByName failed:"); - System.Diagnostics.Debug.WriteLine(ex); - } - } - - /// - /// For a given row object, return the string representation of the value shown in this column. - /// - /// - /// For aspects that are string (e.g. aPerson.Name), the aspect and its string representation are the same. - /// For non-strings (e.g. aPerson.DateOfBirth), the string representation is very different. - /// - /// - /// - public string GetStringValue(object rowObject) - { - return this.ValueToString(this.GetValue(rowObject)); - } - - /// - /// Convert the aspect object to its string representation. - /// - /// - /// If the column has been given a ToStringDelegate, that will be used to do - /// the conversion, otherwise just use ToString(). Nulls are always converted - /// to empty strings. - /// - /// The value of the aspect that should be displayed - /// A string representation of the aspect - public string ValueToString(object value) - { - // CONSIDER: Should we give aspect-to-string converters a chance to work on a null value? - if (value == null) - return ""; - - if (this.aspectToStringConverter != null) - return this.aspectToStringConverter(value); - - string fmt = this.AspectToStringFormat; - if (String.IsNullOrEmpty(fmt)) - return value.ToString(); - else - return String.Format(fmt, value); - } - - /// - /// For a given row object, return the image selector of the image that should displayed in this column. - /// - /// The row object that is being displayed - /// int or string or Image. int or string will be used as index into image list. null or -1 means no image - public Object GetImage(object rowObject) - { - if (this.imageGetter != null) - return this.imageGetter(rowObject); - - if (!String.IsNullOrEmpty(this.ImageKey)) - return this.ImageKey; - - return this.ImageIndex; - } - - /// - /// For a given row object, return the object that is the key of the group that this row belongs to. - /// - /// The row object that is being displayed - /// Group key object - public object GetGroupKey(object rowObject) - { - if (this.groupKeyGetter == null) - { - object key = this.GetValue(rowObject); - if (key is string && this.UseInitialLetterForGroup) { - String keyAsString = (String)key; - if (keyAsString.Length > 0) - key = keyAsString.Substring(0, 1).ToUpper(); - } - return key; - } - else - return this.groupKeyGetter(rowObject); - } - - /// - /// For a given group value, return the string that should be used as the groups title. - /// - /// The group key that is being converted to a title - /// string - public string ConvertGroupKeyToTitle(object value) - { - if (this.groupKeyToTitleConverter == null) - return this.ValueToString(value); - else - return this.groupKeyToTitleConverter(value); - } - - #region Utilities - - /// - /// Install delegates that will group the columns aspects into progressive partitions. - /// If an aspect is less than value[n], it will be grouped with description[n]. - /// If an aspect has a value greater than the last element in "values", it will be grouped - /// with the last element in "descriptions". - /// - /// Array of values. Values must be able to be - /// compared to the aspect (using IComparable) - /// The description for the matching value. The last element is the default description. - /// If there are n values, there must be n+1 descriptions. - /// - /// this.salaryColumn.MakeGroupies( - /// new UInt32[] { 20000, 100000 }, - /// new string[] { "Lowly worker", "Middle management", "Rarified elevation"}); - /// - public void MakeGroupies(T[] values, string[] descriptions) - { - if (values.Length + 1 != descriptions.Length) - throw new ArgumentException("descriptions must have one more element than values."); - - // Install a delegate that returns the index of the description to be shown - this.GroupKeyGetter = delegate(object row) { - Object aspect = this.GetValue(row); - if (aspect == null || aspect == System.DBNull.Value) - return -1; - IComparable comparable = (IComparable)aspect; - for (int i = 0; i < values.Length; i++) { - if (comparable.CompareTo(values[i]) < 0) - return i; - } - - // Display the last element in the array - return descriptions.Length - 1; - }; - - // Install a delegate that simply looks up the given index in the descriptions. - this.GroupKeyToTitleConverter = delegate(object key) { - if ((int)key < 0) - return ""; - - return descriptions[(int)key]; - }; - } - - #endregion - - #region Private Variables - - private string aspectName; - private string aspectToStringFormat; - private bool useInitialLetterForGroup; - private AspectGetterDelegate aspectGetter; - private AspectPutterDelegate aspectPutter; - private AspectToStringConverterDelegate aspectToStringConverter; - private ImageGetterDelegate imageGetter; - private GroupKeyGetterDelegate groupKeyGetter; - private GroupKeyToTitleConverterDelegate groupKeyToTitleConverter; - private RenderDelegate rendererDelegate; - - - #endregion - - } - - #endregion - - #region OLVListItem and OLVListSubItem - - /// - /// OLVListItems are specialized ListViewItems that know which row object they came from, - /// and the row index at which they are displayed, even when in group view mode. They - /// also know the image they should draw against themselves - /// - public class OLVListItem : ListViewItem - { - /// - /// Create a OLVListItem for the given row object - /// - public OLVListItem(object rowObject) - : base() - { - this.rowObject = rowObject; - } - - /// - /// Create a OLVListItem for the given row object, represented by the given string and image - /// - public OLVListItem(object rowObject, string text, Object image) - : base(text, -1) - { - this.rowObject = rowObject; - this.imageSelector = image; - } - - /// - /// RowObject is the model object that is source of the data for this list item. - /// - public object RowObject { - get { return rowObject; } - set { rowObject = value; } - } - private object rowObject; - - /// - /// DisplayIndex is the index of the row where this item is displayed. For flat lists, - /// this is the same as ListViewItem.Index, but for grouped views, it is different. - /// - public int DisplayIndex { - get { return displayIndex; } - set { displayIndex = value; } - } - private int displayIndex; - - /// - /// Get or set the image that should be shown against this item - /// - /// This can be an Image, a string or an int. A string or an int will - /// be used as an index into the small image list. - public Object ImageSelector - { - get { return imageSelector; } - set { - imageSelector = value; - if (value is Int32) - this.ImageIndex = (Int32)value; - else if (value is String) - this.ImageKey = (String)value; - else - this.ImageIndex = -1; - } - } - private Object imageSelector; - } - - /// - /// A ListViewSubItem that knows which image should be drawn against it. - /// - [Browsable(false)] - public class OLVListSubItem : ListViewItem.ListViewSubItem - { - /// - /// Create a OLVListSubItem - /// - public OLVListSubItem() - : base() - { - } - - /// - /// Create a OLVListSubItem that shows the given string and image - /// - public OLVListSubItem(string text, Object image) - : base() - { - this.Text = text; - this.ImageSelector = image; - } - - /// - /// Get or set the image that should be shown against this item - /// - /// This can be an Image, a string or an int. A string or an int will - /// be used as an index into the small image list. - public Object ImageSelector - { - get { return imageSelector; } - set { imageSelector = value;} - } - private Object imageSelector; - - - /// - /// Return the state of the animatation of the image on this subitem. - /// Null means there is either no image, or it is not an animation - /// - public ImageRenderer.AnimationState AnimationState - { - get { return animationState; } - set { animationState = value; } - } - private ImageRenderer.AnimationState animationState; - - } - - #endregion - - #region Comparers - - /// - /// This comparer sort list view groups. - /// It does this on the basis of the values in the Tags, if we can figure out how to compare - /// objects of that type. Failing that, it uses a case insensitive compare on the group header. - /// - internal class ListViewGroupComparer : IComparer - { - public ListViewGroupComparer(SortOrder order) - { - this.sortOrder = order; - } - - public int Compare(ListViewGroup x, ListViewGroup y) - { - // If we know how to compare the tags, do that. - // Otherwise do a case insensitive compare on the group header. - // We have explicitly catch the "almost-null" value of DBNull.Value, - // since comparing to that value always produces a type exception. - int result; - IComparable comparable = x.Tag as IComparable; - if (comparable != null && y.Tag != System.DBNull.Value) - result = comparable.CompareTo(y.Tag); - else - result = String.Compare(x.Header, y.Header, true); - - if (this.sortOrder == SortOrder.Descending) - result = 0 - result; - - return result; - } - - private SortOrder sortOrder; - } - - /// - /// ColumnComparer is the workhorse for all comparison between two values of a particular column. - /// If the column has a specific comparer, use that to compare the values. Otherwise, do - /// a case insensitive string compare of the string representations of the values. - /// - /// This class inherits from both IComparer and its generic counterpart - /// so that it can be used on untyped and typed collections. - internal class ColumnComparer : IComparer, IComparer - { - public ColumnComparer(OLVColumn col, SortOrder order) - { - this.column = col; - this.sortOrder = order; - this.secondComparer = null; - } - - public ColumnComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2) : this(col, order) - { - // There is no point in secondary sorting on the same column - if (col != col2) - this.secondComparer = new ColumnComparer(col2, order2); - } - - public int Compare(object x, object y) - { - return this.Compare((OLVListItem)x, (OLVListItem)y); - } - - public int Compare(OLVListItem x, OLVListItem y) - { - int result = 0; - object x1 = this.column.GetValue(x.RowObject); - object y1 = this.column.GetValue(y.RowObject); - - if (this.sortOrder == SortOrder.None) - return 0; - - // Handle nulls. Null values come last - bool xIsNull = (x1 == null || x1 == System.DBNull.Value); - bool yIsNull = (y1 == null || y1 == System.DBNull.Value); - if (xIsNull || yIsNull) { - if (xIsNull && yIsNull) - result = 0; - else - result = (xIsNull ? -1 : 1); - } else { - result = this.CompareValues(x1, y1); - } - - if (this.sortOrder == SortOrder.Descending) - result = 0 - result; - - // If the result was equality, use the secondary comparer to resolve it - if (result == 0 && this.secondComparer != null) - result = this.secondComparer.Compare(x, y); - - return result; - } - - public int CompareValues(object x, object y) - { - // Force case insensitive compares on strings - if (x is String) - return String.Compare((String)x, (String)y, true); - else { - IComparable comparable = x as IComparable; - if (comparable != null) - return comparable.CompareTo(y); - else - return 0; - } - } - - private OLVColumn column; - private SortOrder sortOrder; - private ColumnComparer secondComparer; - } - - #endregion - - #region Renderers - - /// - /// Renderers are responsible for drawing a single cell within an owner drawn ObjectListView. - /// - /// - /// Methods on this class are called during the DrawSubItem event. - /// Subclasses will normally override the Render method, and use the other - /// methods as helper functions. - /// - [Browsable(false)] - public class BaseRenderer - { - /// - /// Make a simple renderer - /// - public BaseRenderer() - { - } - - #region Properties - - /// - /// Get/set the event that caused this renderer to be called - /// - public DrawListViewSubItemEventArgs Event - { - get { return eventArgs; } - set { eventArgs = value; } - } - private DrawListViewSubItemEventArgs eventArgs; - - /// - /// Get/set the listview for which the drawing is to be done - /// - public ObjectListView ListView - { - get { return objectListView; } - set { objectListView = value; } - } - private ObjectListView objectListView; - - /// - /// Get or set the OLVColumn that this renderer will draw - /// - public OLVColumn Column - { - get { return column; } - set { column = value; } - } - private OLVColumn column; - - /// - /// Get or set the model object that this renderer should draw - /// - public Object RowObject - { - get { return rowObject; } - set { rowObject = value; } - } - private Object rowObject; - - /// - /// Get or set the aspect of the model object that this renderer should draw - /// - public Object Aspect - { - get { - if (aspect == null) - aspect = column.GetValue(this.rowObject); - return aspect; - } - set { aspect = value; } - } - private Object aspect; - - /// - /// Get or set the listitem that this renderer will be drawing - /// - public OLVListItem ListItem - { - get { return listItem; } - set { listItem = value; } - } - private OLVListItem listItem; - - /// - /// Get or set the list subitem that this renderer will be drawing - /// - public ListViewItem.ListViewSubItem SubItem - { - get { return listSubItem; } - set { listSubItem = value; } - } - private ListViewItem.ListViewSubItem listSubItem; - - /// - /// Get the specialized OLVSubItem that this renderer is drawing - /// - /// This returns null for column 0. - public OLVListSubItem OLVSubItem - { - get { return listSubItem as OLVListSubItem; } - } - - /// - /// Cache whether or not our item is selected - /// - public bool IsItemSelected - { - get { return isItemSelected; } - set { isItemSelected = value; } - } - private bool isItemSelected; - - - /// - /// Return the font to be used for text in this cell - /// - /// The font of the subitem - public Font Font - { - get { - if (this.font == null) { - if (this.ListItem.UseItemStyleForSubItems) - return this.ListItem.Font; - else - return this.SubItem.Font; - } else - return this.font; - } - set { - this.font = value; - } - } - private Font font; - - /// - /// The brush that will be used to paint the text - /// - public Brush TextBrush - { - get { - if (textBrush == null) - return new SolidBrush(this.GetForegroundColor()); - else - return this.textBrush; - } - set { textBrush = value; } - } - private Brush textBrush; - - /// - /// Should this renderer fill in the background before drawing? - /// - public bool IsDrawBackground - { - get { return isDrawBackground; } - set { isDrawBackground = value; } - } - private bool isDrawBackground = true; - - /// - /// Can the renderer wrap lines that do not fit completely within the cell? - /// - /// This value is currently only used when printing a list view using ListViewPrinter. - public bool CanWrap - { - get { return canWrap; } - set { canWrap = value; } - } - private bool canWrap = false; - - #endregion - - #region Utilities - - /// - /// Return the image that should be drawn against this subitem - /// - /// An Image or null if no image should be drawn. - public Image GetImage() { - if (this.Column.Index == 0) - return this.GetImage(this.ListItem.ImageSelector); - else - return this.GetImage(this.OLVSubItem.ImageSelector); - } - - /// - /// Return the actual image that should be drawn when keyed by the given image selector. - /// An image selector can be: - /// an int, giving the index into the image list - /// a string, giving the image key into the image list - /// an Image, being the image itself - /// - /// - /// The value that indicates the image to be used - /// An Image or null - public Image GetImage(Object imageSelector) - { - if (imageSelector == null) - return null; - - ImageList il = this.ListView.BaseSmallImageList; - if (il != null) { - if (imageSelector is Int32) { - Int32 index = (Int32)imageSelector; - if (index < 0 || index >= il.Images.Count) - return null; - else - return il.Images[index]; - } - - if (imageSelector is String) { - if (il.Images.ContainsKey((String)imageSelector)) - return il.Images[(String)imageSelector]; - else - return null; - } - } - - return imageSelector as Image; - } - - /// - /// Return the Color that is the background color for this item's cell - /// - /// The background color of the subitem - public Color GetBackgroundColor() - { - if (this.IsItemSelected && this.ListView.FullRowSelect) { - if (this.ListView.Focused) - return SystemColors.Highlight; - else - if (!this.ListView.HideSelection) - return SystemColors.Control; //TODO: What color should this be? - } - if (this.ListItem.UseItemStyleForSubItems) - return this.ListItem.BackColor; - else - return this.SubItem.BackColor; - } - - /// - /// Return the Color that is the background color for this item's text - /// - /// The background color of the subitem's text - protected Color GetTextBackgroundColor() - { - if (this.IsItemSelected && (this.Column.Index == 0 || this.ListView.FullRowSelect)) - return SystemColors.Highlight; - else - if (this.ListItem.UseItemStyleForSubItems) - return this.ListItem.BackColor; - else - return this.SubItem.BackColor; - } - - /// - /// Return the color to be used for text in this cell - /// - /// The text color of the subitem - protected Color GetForegroundColor() - { - if (this.IsItemSelected && (this.Column.Index == 0 || this.ListView.FullRowSelect)) - return SystemColors.HighlightText; - else - if (this.ListItem.UseItemStyleForSubItems) - return this.ListItem.ForeColor; - else - return this.SubItem.ForeColor; - } - - - /// - /// Align the second rectangle with the first rectangle, - /// according to the alignment of the column - /// - /// The cell's bounds - /// The rectangle to be aligned within the bounds - /// An aligned rectangle - protected Rectangle AlignRectangle(Rectangle outer, Rectangle inner) - { - Rectangle r = new Rectangle(outer.Location, inner.Size); - - // Centre horizontally depending on the column alignment - if (inner.Width < outer.Width) { - switch (this.Column.TextAlign) { - case HorizontalAlignment.Left: - r.X = outer.Left; - break; - case HorizontalAlignment.Center: - r.X = outer.Left + ((outer.Width - inner.Width) / 2); - break; - case HorizontalAlignment.Right: - r.X = outer.Right - inner.Width - 1; - break; - } - } - // Centre vertically too - if (inner.Height < outer.Height) - r.Y = outer.Top + ((outer.Height - inner.Height) / 2); - - return r; - } - - /// - /// Draw the given image aligned horizontally within the column. - /// - /// - /// Over tall images are scaled to fit. Over-wide images are - /// truncated. This is by design! - /// - /// Graphics context to use for drawing - /// Bounds of the cell - /// The image to be drawn - protected void DrawAlignedImage(Graphics g, Rectangle r, Image image) - { - if (image == null) - return; - - // By default, the image goes in the top left of the rectangle - Rectangle imageBounds = new Rectangle(r.Location, image.Size); - - // If the image is too tall to be drawn in the space provided, proportionally scale it down. - // Too wide images are not scaled. - if (image.Height > r.Height) { - float scaleRatio = (float)r.Height / (float)image.Height; - imageBounds.Width = (int)((float)image.Width * scaleRatio); - imageBounds.Height = r.Height - 1; - } - - // Align and draw our (possibly scaled) image - g.DrawImage(image, this.AlignRectangle(r, imageBounds)); - } - - /// - /// Fill in the background of this cell - /// - /// Graphics context to use for drawing - /// Bounds of the cell - protected void DrawBackground(Graphics g, Rectangle r) - { - if (this.IsDrawBackground) - g.FillRectangle(new SolidBrush(this.GetBackgroundColor()), r); - } - - #endregion - - - /// - /// The delegate that is called from the list view. This is the main entry point, but - /// subclasses should override Render instead of this method. - /// - /// The event that caused this redraw - /// The context that our drawing should be done using - /// The bounds of the cell within which the renderer can draw - /// The model object for this row - public void HandleRendering(DrawListViewSubItemEventArgs e, Graphics g, Rectangle r, Object rowObject) - { - this.Event = e; - this.ListView = (ObjectListView)this.Column.ListView; - this.RowObject = rowObject; - this.ListItem = e.Item as OLVListItem; - this.SubItem = e.SubItem; - this.Column = this.ListView.GetColumn(e.ColumnIndex); - this.Aspect = null; // uncache previous result - this.IsItemSelected = this.ListItem.Selected; // ((e.ItemState & ListViewItemStates.Selected) == ListViewItemStates.Selected); - this.IsDrawBackground = true; - this.Font = null; - this.TextBrush = null; - this.Render(g, r); - } - - /// - /// Draw our data into the given rectangle using the given graphics context. - /// - /// - /// Subclasses should override this method. - /// The graphics context that should be used for drawing - /// The bounds of the subitem cell - virtual public void Render(Graphics g, Rectangle r) - { - this.DrawBackground(g, r); - - // Adjust the rectangle to match the padding used by the native mode of the ListView - Rectangle r2 = r; - r2.X += 4; - r2.Width -= 4; - this.DrawImageAndText(g, r2); - } - - /// - /// Draw our subitems image and text - /// - /// Graphics context to use for drawing - /// Bounds of the cell - protected void DrawImageAndText(Graphics g, Rectangle r) - { - DrawImageAndText(g, r, this.SubItem.Text, this.GetImage()); - } - - /// - /// Draw the given text and optional image in the "normal" fashion - /// - /// Graphics context to use for drawing - /// Bounds of the cell - /// The string to be drawn - /// The optional image to be drawn - protected void DrawImageAndText(Graphics g, Rectangle r, String txt, Image image) - { - // Draw the image - if (image != null) { - int top = r.Y; - if (image.Size.Height < r.Height) - top += ((r.Height - image.Size.Height) / 2); - - g.DrawImageUnscaled(image, r.X, top); - r.X += image.Width; - r.Width -= image.Width; - } - - StringFormat fmt = new StringFormat(); - fmt.LineAlignment = StringAlignment.Center; - fmt.Trimming = StringTrimming.EllipsisCharacter; - if (!this.CanWrap) - fmt.FormatFlags = StringFormatFlags.NoWrap; - switch (this.Column.TextAlign) { - case HorizontalAlignment.Center: fmt.Alignment = StringAlignment.Center; break; - case HorizontalAlignment.Left: fmt.Alignment = StringAlignment.Near; break; - case HorizontalAlignment.Right: fmt.Alignment = StringAlignment.Far; break; - } - - // Draw the background of the text as selected, if it's the primary column - // and it's selected and it's not in FullRowSelect mode. - if (this.IsDrawBackground && this.IsItemSelected && this.Column.Index == 0 && !this.ListView.FullRowSelect) { - SizeF size = g.MeasureString(txt, this.Font, r.Width, fmt); - Rectangle r2 = this.AlignRectangle(r, new Rectangle(0, 0, (int)(size.Width + 1), (int)(size.Height + 1))); - g.FillRectangle(new SolidBrush(SystemColors.Highlight), r2); - } - - RectangleF rf = r; - g.DrawString(txt, this.Font, this.TextBrush, rf, fmt); - - // We should put a focus rectange around the column 0 text if it's selected -- - // but we don't because: - // - I really dislike this UI convention - // - we are using buffered graphics, so the DrawFocusRecatangle method of the event doesn't work - - //if (this.Column.Index == 0) { - // Size size = TextRenderer.MeasureText(this.SubItem.Text, this.ListView.ListFont); - // if (r.Width > size.Width) - // r.Width = size.Width; - // this.Event.DrawFocusRectangle(r); - //} - } - } - - /// - /// This class maps a data value to an image that should be drawn for that value. - /// - /// It is useful for drawing data that is represented as an enum or boolean. - public class MappedImageRenderer : BaseRenderer - { - /// - /// Return a renderer that draw boolean values using the given images - /// - /// Draw this when our data value is true - /// Draw this when our data value is false - /// A Renderer - static public MappedImageRenderer Boolean(Object trueImage, Object falseImage) - { - return new MappedImageRenderer(true, trueImage, false, falseImage); - } - - /// - /// Return a renderer that draw tristate boolean values using the given images - /// - /// Draw this when our data value is true - /// Draw this when our data value is false - /// Draw this when our data value is null - /// A Renderer - static public MappedImageRenderer TriState(Object trueImage, Object falseImage, Object nullImage) - { - return new MappedImageRenderer(new Object[] { true, trueImage, false, falseImage, null, nullImage }); - } - - /// - /// Make a new empty renderer - /// - public MappedImageRenderer() - : base() - { - map = new System.Collections.Hashtable(); - } - - /// - /// Make a new renderer that will show the given image when the given key is the aspect value - /// - /// The data value to be matched - /// The image to be shown when the key is matched - public MappedImageRenderer(Object key, Object image) - : this() - { - this.Add(key, image); - } - - public MappedImageRenderer(Object key1, Object image1, Object key2, Object image2) - : this() - { - this.Add(key1, image1); - this.Add(key2, image2); - } - - /// - /// Build a renderer from the given array of keys and their matching images - /// - /// An array of key/image pairs - public MappedImageRenderer(Object [] keysAndImages) - : this() - { - if ((keysAndImages.GetLength(0) % 2) != 0) - throw new ArgumentException("Array must have key/image pairs"); - - for (int i = 0; i < keysAndImages.GetLength(0); i += 2) - this.Add(keysAndImages[i], keysAndImages[i + 1]); - } - - /// - /// Register the image that should be drawn when our Aspect has the data value. - /// - /// Value that the Aspect must match - /// An ImageSelector -- an int, string or image - public void Add(Object value, Object image) - { - if (value == null) - this.nullImage = image; - else - map[value] = image; - } - - /// - /// Render our value - /// - /// - /// - public override void Render(Graphics g, Rectangle r) - { - this.DrawBackground(g, r); - - Image image = null; - if (this.Aspect == null) - image = this.GetImage(this.nullImage); - else - if (map.ContainsKey(this.Aspect)) - image = this.GetImage(map[this.Aspect]); - - this.DrawAlignedImage(g, r, image); - } - - #region Private variables - - private Hashtable map; // Track the association between values and images - private Object nullImage; // image to be drawn for null values (since null can't be a key) - - #endregion - } - - /// - /// Render an image that comes from our data source. - /// - /// The image can be sourced from: - /// - /// a byte-array (normally when the image to be shown is - /// stored as a value in a database) - /// an int, which is treated as an index into the image list - /// a string, which is treated first as a file name, and failing that as an index into the image list - /// - /// If an image is an animated GIF, it's state is stored in the SubItem object. - /// By default, the image renderer does not render animations (it begins life with animations paused). - /// To enable animations, you must call Unpause(). - /// - public class ImageRenderer : BaseRenderer - { - /// - /// Make an empty image renderer - /// - public ImageRenderer() - : base() - { - this.tickler = new System.Threading.Timer(new TimerCallback(this.OnTimer), null, Timeout.Infinite, Timeout.Infinite); - this.stopwatch = new Stopwatch(); - } - - /// - /// Make an empty image renderer that begins life ready for animations - /// - public ImageRenderer(bool startAnimations) - : this() - { - this.Paused = !startAnimations; - } - - /// - /// Draw our image - /// - /// - /// - public override void Render(Graphics g, Rectangle r) - { - this.DrawBackground(g, r); - this.DrawAlignedImage(g, r, this.GetImageFromAspect()); - } - - /// - /// Translate our Aspect into an image. - /// - /// The strategy is: - /// If its a byte array, we treat it as an in-memory image - /// If it's an int, we use that as an index into our image list - /// If it's a string, we try to load a file by that name. If we can't, we use the string as an index into our image list. - /// - /// An image - protected Image GetImageFromAspect() - { - if (this.Aspect == null || this.Aspect == System.DBNull.Value) - return null; - - // If we've already figured out the image, don't do it again - if (this.OLVSubItem != null && this.OLVSubItem.ImageSelector is Image) { - if (this.OLVSubItem.AnimationState == null) - return (Image)this.OLVSubItem.ImageSelector; - else - return this.OLVSubItem.AnimationState.image; - } - - // Try to convert our Aspect into an Image - // If its a byte array, we treat it as an in-memory image - // If it's an int, we use that as an index into our image list - // If it's a string, we try to find a file by that name. - // If we can't, we use the string as an index into our image list. - Image image = null; - if (this.Aspect is System.Byte[]) { - using (MemoryStream stream = new MemoryStream((System.Byte[])this.Aspect)) { - try { - image = Image.FromStream(stream); - } catch (ArgumentException) { - // ignore - } - } - } else if (this.Aspect is Int32) { - image = this.GetImage(this.Aspect); - } else if (this.Aspect is String && ((String)this.Aspect) != "") { - try { - image = Image.FromFile((String)this.Aspect); - } catch (FileNotFoundException) { - image = this.GetImage(this.Aspect); - } catch (OutOfMemoryException) { - image = this.GetImage(this.Aspect); - } - } - - // If this image is an animation, initialize the animation process - if (this.OLVSubItem != null && AnimationState.IsAnimation(image)) { - this.OLVSubItem.AnimationState = new AnimationState(image); - } - - // Cache the image so we don't repeat this dreary process - if (this.OLVSubItem != null) - this.OLVSubItem.ImageSelector = image; - - return image; - } - - /// - /// Should the animations in this renderer be paused? - /// - public bool Paused - { - get { return isPaused; } - set { - if (isPaused != value) { - isPaused = value; - if (isPaused) { - this.tickler.Change(Timeout.Infinite, Timeout.Infinite); - this.stopwatch.Stop(); - } else { - this.tickler.Change(1, Timeout.Infinite); - this.stopwatch.Start(); - } - } - } - } - private bool isPaused = true; - - /// - /// Pause any animations - /// - public void Pause() - { - this.Paused = true; - } - - /// - /// Unpause any animations - /// - public void Unpause() - { - this.Paused = false; - } - - protected delegate void OnTimerCallback(Object state); - - /// - /// This is the method that is invoked by the timer. It basically switches control to the listview thread. - /// - /// not used - public void OnTimer(Object state) - { - if (this.ListView == null || this.Paused) - this.tickler.Change(1000, Timeout.Infinite); - else { - if (this.ListView.InvokeRequired) - this.ListView.Invoke(new OnTimerCallback(this.OnTimer), new object[] { state }); - else - this.OnTimerInThread(); - } - } - - /// - /// This is the OnTimer callback, but invoked in the same thread as the creator of the ListView. - /// This method can use all of ListViews methods without creating a CrossThread exception. - /// - protected void OnTimerInThread() - { - // MAINTAINER NOTE: This method must renew the tickler. If it doesn't the animations will stop. - - // If this listview has been destroyed, we can't do anything, so we return without - // renewing the tickler, effectively killing all animations on this renderer - if (this.ListView.IsDisposed) - return; - - // If we're not in Detail view or our column has been removed from the list, - // we can't do anything at the moment, but we still renew the tickler because the view may change later. - if (this.ListView.View != System.Windows.Forms.View.Details || this.Column.Index < 0) { - this.tickler.Change(1000, Timeout.Infinite); - return; - } - - long elapsedMilliseconds = this.stopwatch.ElapsedMilliseconds; - int subItemIndex = this.Column.Index; - long nextCheckAt = elapsedMilliseconds + 1000; // wait at most one second before checking again - Rectangle updateRect = new Rectangle(); // what part of the view must be updated to draw the changed gifs? - - // Run through all the subitems in the view for our column, and for each one that - // has an animation attached to it, see if the frame needs updating. - foreach (ListViewItem lvi in this.ListView.Items) { - // Get the gif state from the subitem. If there isn't an animation state, skip this row. - OLVListSubItem lvsi = (OLVListSubItem)lvi.SubItems[subItemIndex]; - AnimationState state = lvsi.AnimationState; - if (state == null || !state.IsValid) - continue; - - // Has this frame of the animation expired? - if (elapsedMilliseconds >= state.currentFrameExpiresAt) { - state.AdvanceFrame(elapsedMilliseconds); - - // Track the area of the view that needs to be redrawn to show the changed images - if (updateRect.IsEmpty) - updateRect = lvsi.Bounds; - else - updateRect = Rectangle.Union(updateRect, lvsi.Bounds); - } - - // Remember the minimum time at which a frame is next due to change - nextCheckAt = Math.Min(nextCheckAt, state.currentFrameExpiresAt); - } - - // Update the part of the listview where frames have changed - if (!updateRect.IsEmpty) - this.ListView.Invalidate(updateRect); - - // Renew the tickler in time for the next frame change - this.tickler.Change(nextCheckAt - elapsedMilliseconds, Timeout.Infinite); - } - - /// - /// Instances of this class kept track of the animation state of a single image. - /// - public class AnimationState - { - const int PropertyTagTypeShort = 3; - const int PropertyTagTypeLong = 4; - const int PropertyTagFrameDelay = 0x5100; - const int PropertyTagLoopCount = 0x5101; - - /// - /// Is the given image an animation - /// - /// The image to be tested - /// Is the image an animation? - static public bool IsAnimation(Image image) - { - if (image == null) - return false; - else - return (new List(image.FrameDimensionsList)).Contains(FrameDimension.Time.Guid); - } - - /// - /// Create an AnimationState in a quiet state - /// - public AnimationState() - { - this.currentFrame = 0; - this.frameCount = 0; - this.imageDuration = new List(); - this.image = null; - } - - /// - /// Create an animation state for the given image, which may or may not - /// be an animation - /// - /// The image to be rendered - public AnimationState(Image image) - : this() - { - if (!AnimationState.IsAnimation(image)) - return; - - // How many frames in the animation? - this.image = image; - this.frameCount = this.image.GetFrameCount(FrameDimension.Time); - - // Find the delay between each frame. - // The delays are stored an array of 4-byte ints. Each int is the - // number of 1/100th of a second that should elapsed before the frame expires - foreach (PropertyItem pi in this.image.PropertyItems) { - if (pi.Id == PropertyTagFrameDelay) { - for (int i = 0; i < pi.Len; i += 4) { - //TODO: There must be a better way to convert 4-bytes to an int - int delay = (pi.Value[i + 3] << 24) + (pi.Value[i + 2] << 16) + (pi.Value[i + 1] << 8) + pi.Value[i]; - this.imageDuration.Add(delay * 10); // store delays as milliseconds - } - break; - } - } - - // There should be as many frame durations as frames - Debug.Assert(this.imageDuration.Count == this.frameCount, "There should be as many frame durations as there are frames."); - } - - /// - /// Does this state represent a valid animation - /// - public bool IsValid { - get { - return (this.image != null && this.frameCount > 0); - } - } - - /// - /// Advance our images current frame and calculate when it will expire - /// - public void AdvanceFrame(long millisecondsNow) - { - this.currentFrame = (this.currentFrame + 1) % this.frameCount; - this.currentFrameExpiresAt = millisecondsNow + this.imageDuration[this.currentFrame]; - this.image.SelectActiveFrame(FrameDimension.Time, this.currentFrame); - } - - public int currentFrame; - public long currentFrameExpiresAt; - public Image image; - public List imageDuration; - public int frameCount; - } - - #region Private variables - - private System.Threading.Timer tickler; // timer used to tickle the animations - private Stopwatch stopwatch; // clock used to time the animation frame changes - - #endregion - } - - /// - /// Render our Aspect as a progress bar - /// - public class BarRenderer : BaseRenderer - { - #region Constructors - - /// - /// Make a BarRenderer - /// - public BarRenderer() - : base() - { - this.Pen = new Pen(Color.Blue, 1); - this.Brush = Brushes.Aquamarine; - this.BackgroundBrush = Brushes.White; - this.StartColor = Color.Empty; - } - - /// - /// Make a BarRenderer for the given range of data values - /// - public BarRenderer(int minimum, int maximum) - : this() - { - this.MinimumValue = minimum; - this.MaximumValue = maximum; - } - - /// - /// Make a BarRenderer using a custom bar scheme - /// - public BarRenderer(Pen aPen, Brush aBrush) - : this() - { - this.Pen = aPen; - this.Brush = aBrush; - this.UseStandardBar = false; - } - - /// - /// Make a BarRenderer using a custom bar scheme - /// - public BarRenderer(int minimum, int maximum, Pen aPen, Brush aBrush) - : this(minimum, maximum) - { - this.Pen = aPen; - this.Brush = aBrush; - this.UseStandardBar = false; - } - - /// - /// Make a BarRenderer that uses a horizontal gradient - /// - public BarRenderer(Pen aPen, Color start, Color end) - : this() - { - this.Pen = aPen; - this.SetGradient(start, end); - } - - /// - /// Make a BarRenderer that uses a horizontal gradient - /// - public BarRenderer(int minimum, int maximum, Pen aPen, Color start, Color end) - : this(minimum, maximum) - { - this.Pen = aPen; - this.SetGradient(start, end); - } - - #endregion - - #region Public variables - - /// - /// Should this bar be drawn in the system style - /// - public bool UseStandardBar = true; - - /// - /// How many pixels in from our cell border will this bar be drawn - /// - public int Padding = 2; - - /// - /// The Pen that will draw the frame surrounding this bar - /// - public Pen Pen; - - /// - /// The brush that will be used to fill the bar - /// - public Brush Brush; - - /// - /// The brush that will be used to fill the background of the bar - /// - public Brush BackgroundBrush; - - /// - /// The first color when a gradient is used to fill the bar - /// - public Color StartColor; - - /// - /// The end color when a gradient is used to fill the bar - /// - public Color EndColor; - - /// - /// Regardless of how wide the column become the progress bar will never be wider than this - /// - public int MaximumWidth = 100; - - /// - /// Regardless of how high the cell is the progress bar will never be taller than this - /// - public int MaximumHeight = 16; - - /// - /// The minimum data value expected. Values less than this will given an empty bar - /// - public int MinimumValue = 0; - - /// - /// The maximum value for the range. Values greater than this will give a full bar - /// - public int MaximumValue = 100; - - #endregion - - /// - /// Draw this progress bar using a gradient - /// - /// - /// - public void SetGradient(Color start, Color end) - { - this.StartColor = start; - this.EndColor = end; - this.UseStandardBar = false; - } - - /// - /// Draw our aspect - /// - /// - /// - public override void Render(Graphics g, Rectangle r) - { - this.DrawBackground(g, r); - - Rectangle frameRect = Rectangle.Inflate(r, 0 - this.Padding, 0 - this.Padding); - frameRect.Width = Math.Min(frameRect.Width, this.MaximumWidth); - frameRect.Height = Math.Min(frameRect.Width, this.MaximumHeight); - frameRect = this.AlignRectangle(r, frameRect); - - // Convert our aspect to a numeric value - // CONSIDER: Is this the best way to do this? - if (!(this.Aspect is IConvertible)) - return; - double aspectValue = ((IConvertible)this.Aspect).ToDouble(NumberFormatInfo.InvariantInfo); - - Rectangle fillRect = Rectangle.Inflate(frameRect, -1, -1); - if (aspectValue <= this.MinimumValue) - fillRect.Width = 0; - else if (aspectValue < this.MaximumValue) - fillRect.Width = (int)(fillRect.Width * (aspectValue - this.MinimumValue) / this.MaximumValue); - - if (this.UseStandardBar && ProgressBarRenderer.IsSupported) { - ProgressBarRenderer.DrawHorizontalBar(g, frameRect); - ProgressBarRenderer.DrawHorizontalChunks(g, fillRect); - } else { - g.FillRectangle(this.BackgroundBrush, frameRect); - if (fillRect.Width > 0) { - fillRect.Height++; - if (this.StartColor == Color.Empty) - g.FillRectangle(this.Brush, fillRect); - else { - using (LinearGradientBrush gradient = new LinearGradientBrush(frameRect, this.StartColor, this.EndColor, LinearGradientMode.Horizontal)) { - g.FillRectangle(gradient, fillRect); - } - } - } - g.DrawRectangle(this.Pen, frameRect); - } - } - } - - /// - /// An ImagesRenderer draws zero or more images depending on the data returned by its Aspect. - /// - /// This renderer's Aspect must return a ICollection of ints, strings or Images, - /// each of which will be drawn horizontally one after the other. - public class ImagesRenderer : BaseRenderer - { - /// - /// Get or set the number of pixels between each image - /// - public int Spacing = 1; - - /// - /// Draw our data value - /// - /// - /// - public override void Render(Graphics g, Rectangle r) - { - this.DrawBackground(g, r); - - ICollection imageSelectors = this.Aspect as ICollection; - if (imageSelectors == null) - return; - - Point pt = r.Location; - foreach (Object selector in imageSelectors) { - Image image = this.GetImage(selector); - if (image != null) { - g.DrawImage(image, pt); - pt.X += (image.Width + this.Spacing); - } - } - } - } - - /// - /// A MultiImageRenderer draws the same image a number of times based on our data value - /// - /// The stars in the Rating column of iTunes is a good example of this type of renderer. - public class MultiImageRenderer : BaseRenderer - { - /// - /// Make a quiet rendererer - /// - public MultiImageRenderer() - : base() - { - } - - /// - /// Make an image renderer that will draw the indicated image, at most maxImages times. - /// - /// - /// - /// - /// - public MultiImageRenderer(Object imageSelector, int maxImages, int minValue, int maxValue) - : this() - { - this.ImageSelector = imageSelector; - this.MaxNumberImages = maxImages; - this.MinimumValue = minValue; - this.MaximumValue = maxValue; - } - - /// - /// The image selector that will give the image to be drawn - /// - public Object ImageSelector; - - /// - /// Get or set the number of pixels between each image - /// - public int Spacing = 1; - - /// - /// What is the maximum number of images that this renderer should draw? - /// - public int MaxNumberImages = 10; - - /// - /// Values less than or equal to this will have 0 images drawn - /// - public int MinimumValue = 0; - - /// - /// Values greater than or equal to this will have MaxNumberImages images drawn - /// - public int MaximumValue = 100; - - /// - /// Draw our data value - /// - /// - /// - public override void Render(Graphics g, Rectangle r) - { - this.DrawBackground(g, r); - - Image image = this.GetImage(this.ImageSelector); - if (image == null) - return; - - // Convert our aspect to a numeric value - // CONSIDER: Is this the best way to do this? - if (!(this.Aspect is IConvertible)) - return; - double aspectValue = ((IConvertible)this.Aspect).ToDouble(NumberFormatInfo.InvariantInfo); - - // Calculate how many images we need to draw to represent our aspect value - int numberOfImages; - if (aspectValue <= this.MinimumValue) - numberOfImages = 0; - else if (aspectValue < this.MaximumValue) - numberOfImages = 1 + (int)(this.MaxNumberImages * (aspectValue - this.MinimumValue) / this.MaximumValue); - else - numberOfImages = this.MaxNumberImages; - - // If we need to shrink the image, what will its on-screen dimensions be? - int imageScaledWidth = image.Width; - int imageScaledHeight = image.Height; - if (r.Height < image.Height) { - imageScaledWidth = (int)((float)image.Width * (float)r.Height / (float)image.Height); - imageScaledHeight = r.Height; - } - // Calculate where the images should be drawn - Rectangle imageBounds = r; - imageBounds.Width = (this.MaxNumberImages * (imageScaledWidth + this.Spacing)) - this.Spacing; - imageBounds.Height = imageScaledHeight; - imageBounds = this.AlignRectangle(r, imageBounds); - - // Finally, draw the images - for (int i = 0; i < numberOfImages; i++) { - g.DrawImage(image, imageBounds.X, imageBounds.Y, imageScaledWidth, imageScaledHeight); - imageBounds.X += (imageScaledWidth + this.Spacing); - } - } - } - - - /// - /// A class to render a value that contains a bitwise-OR'ed collection of values. - /// - /// The type of value that holds the bit-OR'ed flag - public class FlagRenderer : BaseRenderer where T : IConvertible - { - public FlagRenderer() - { - } - - public void Add(T key, Object imageSelector) - { - UInt32 k2 = ((IConvertible)key).ToUInt32(NumberFormatInfo.InvariantInfo); - - this.imageMap[k2] = imageSelector; - this.keysInOrder.Remove(k2); - this.keysInOrder.Add(k2); - } - - public override void Render(Graphics g, Rectangle r) - { - this.DrawBackground(g, r); - - UInt32 v2 = ((IConvertible)this.Aspect).ToUInt32(NumberFormatInfo.InvariantInfo); - - Point pt = r.Location; - foreach (UInt32 key in this.keysInOrder) { - if ((v2 & key) == key) { - Image image = this.GetImage(this.imageMap[key]); - if (image != null) { - g.DrawImage(image, pt); - pt.X += (image.Width + this.Spacing); - } - } - } - } - - /// - /// Get or set the number of pixels between each image - /// - public int Spacing = 1; - - private List keysInOrder = new List(); - private Dictionary imageMap = new Dictionary(); - } - #endregion - -} diff --git a/code/PTM/View/Controls/StatisticsControl.cs b/code/PTM/View/Controls/StatisticsControl.cs index b0348f8..d7b8a24 100644 --- a/code/PTM/View/Controls/StatisticsControl.cs +++ b/code/PTM/View/Controls/StatisticsControl.cs @@ -363,7 +363,7 @@ private void GetTaskDetails(TaskStatisticsResult result) private void browseButton_Click(object sender, EventArgs e) { - TaskSelectForm tgForm = new TaskSelectForm(); + TasksHierarchyForm tgForm = new TasksHierarchyForm(); if(tgForm.ShowDialog(this)==DialogResult.OK) { if (FindById(tgForm.SelectedTaskId) == null) diff --git a/code/PTM/View/Controls/SummaryControl.cs b/code/PTM/View/Controls/SummaryControl.cs index 0bd2c13..178eedf 100644 --- a/code/PTM/View/Controls/SummaryControl.cs +++ b/code/PTM/View/Controls/SummaryControl.cs @@ -17,22 +17,28 @@ namespace PTM.View.Controls /// Summary description for Summary. /// internal class SummaryControl : AddinTabPage - { + { + private TreeListView taskList; private GroupBox groupBox1; - private GroupBox groupBox3; - private IndicatorControl indicator1; + private GroupBox groupBox3; + private ColumnHeader PercentHeader; + private IndicatorControl indicator1; + private ColumnHeader TaskHeader; private Label label2; private ComboBox parentTaskComboBox; private IContainer components; private Button browseButton; private GroupBox groupBox2; - private IndicatorControl indicator2; + private IndicatorControl indicator2; + private Panel panel1; private ToolBar toolBar; private ToolBarButton toolBarButton1; private ToolBarButton toolBarButton2; private ImageList toolBarImages; - private ArrayList recentParentTasksList = new ArrayList(); + private ArrayList recentParentTasksList = new ArrayList(); + private ColumnHeader PercentGoalHeader; + private ColumnHeader TimeHeader; private DateTimePicker fromDateTimePicker; private DateTimePicker toDateTimePicker; private RadioButton fromRadioButton; @@ -42,23 +48,8 @@ internal class SummaryControl : AddinTabPage private GroupBox groupBox4; private IndicatorControl indicator3; private IndicatorControl indicator4; + private ColumnHeader GoalHeader; private GroupBox groupBox5; - private BrightIdeasSoftware.ObjectListView taskList; - private BrightIdeasSoftware.OLVColumn TaskHeader; - private BrightIdeasSoftware.OLVColumn TimeHeader; - private BrightIdeasSoftware.OLVColumn ActiveTimeHeader; - private BrightIdeasSoftware.OLVColumn InactiveTimeHeader; - private BrightIdeasSoftware.OLVColumn PercentHeader; - private BrightIdeasSoftware.OLVColumn GoalHeader; - private BrightIdeasSoftware.OLVColumn PercentGoalHeader; - private BrightIdeasSoftware.ListViewPrinter listViewPrinter1; - private ToolStrip toolStrip1; - private ToolStripButton saveToolStripButton; - private ToolStripButton printToolStripButton; - private ToolStripSeparator toolStripSeparator; - private ToolStripButton toolStripButton1; - private ToolStripButton toolStripButton2; - private ToolStripLabel toolStripLabel1; private BackgroundWorker worker; internal SummaryControl() @@ -133,29 +124,22 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); + PTM.View.Controls.TreeListViewComponents.TreeListViewItemCollection.TreeListViewItemCollectionComparer treeListViewItemCollectionComparer1 = new PTM.View.Controls.TreeListViewComponents.TreeListViewItemCollection.TreeListViewItemCollectionComparer(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SummaryControl)); + this.taskList = new PTM.View.Controls.TreeListViewComponents.TreeListView(); + this.TaskHeader = new System.Windows.Forms.ColumnHeader(); + this.TimeHeader = new System.Windows.Forms.ColumnHeader(); + this.PercentHeader = new System.Windows.Forms.ColumnHeader(); + this.GoalHeader = new System.Windows.Forms.ColumnHeader(); + this.PercentGoalHeader = new System.Windows.Forms.ColumnHeader(); this.fromDateTimePicker = new System.Windows.Forms.DateTimePicker(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox3 = new System.Windows.Forms.GroupBox(); - this.toolStrip1 = new System.Windows.Forms.ToolStrip(); - this.saveToolStripButton = new System.Windows.Forms.ToolStripButton(); - this.printToolStripButton = new System.Windows.Forms.ToolStripButton(); - this.toolStripSeparator = new System.Windows.Forms.ToolStripSeparator(); - this.toolStripLabel1 = new System.Windows.Forms.ToolStripLabel(); - this.toolStripButton1 = new System.Windows.Forms.ToolStripButton(); - this.toolStripButton2 = new System.Windows.Forms.ToolStripButton(); - this.taskList = new BrightIdeasSoftware.ObjectListView(); - this.TaskHeader = new BrightIdeasSoftware.OLVColumn(); - this.TimeHeader = new BrightIdeasSoftware.OLVColumn(); - this.ActiveTimeHeader = new BrightIdeasSoftware.OLVColumn(); - this.InactiveTimeHeader = new BrightIdeasSoftware.OLVColumn(); - this.PercentHeader = new BrightIdeasSoftware.OLVColumn(); - this.GoalHeader = new BrightIdeasSoftware.OLVColumn(); - this.PercentGoalHeader = new BrightIdeasSoftware.OLVColumn(); this.label2 = new System.Windows.Forms.Label(); this.parentTaskComboBox = new System.Windows.Forms.ComboBox(); this.browseButton = new System.Windows.Forms.Button(); this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.panel1 = new System.Windows.Forms.Panel(); this.toolBar = new System.Windows.Forms.ToolBar(); this.toolBarButton1 = new System.Windows.Forms.ToolBarButton(); this.toolBarButton2 = new System.Windows.Forms.ToolBarButton(); @@ -166,184 +150,95 @@ private void InitializeComponent() this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.groupBox4 = new System.Windows.Forms.GroupBox(); this.groupBox5 = new System.Windows.Forms.GroupBox(); - this.listViewPrinter1 = new BrightIdeasSoftware.ListViewPrinter(); this.groupBox3.SuspendLayout(); - this.toolStrip1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.taskList)).BeginInit(); + this.panel1.SuspendLayout(); this.SuspendLayout(); // - // fromDateTimePicker - // - this.fromDateTimePicker.CustomFormat = ""; - this.fromDateTimePicker.Format = System.Windows.Forms.DateTimePickerFormat.Short; - this.fromDateTimePicker.Location = new System.Drawing.Point(80, 32); - this.fromDateTimePicker.Name = "fromDateTimePicker"; - this.fromDateTimePicker.Size = new System.Drawing.Size(88, 20); - this.fromDateTimePicker.TabIndex = 0; - this.fromDateTimePicker.Value = new System.DateTime(2006, 10, 2, 0, 0, 0, 0); - // - // groupBox1 - // - this.groupBox1.ForeColor = System.Drawing.Color.Blue; - this.groupBox1.Location = new System.Drawing.Point(8, 64); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(72, 80); - this.groupBox1.TabIndex = 4; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Total Time"; - // - // groupBox3 - // - this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.groupBox3.Controls.Add(this.toolStrip1); - this.groupBox3.Controls.Add(this.taskList); - this.groupBox3.ForeColor = System.Drawing.Color.Blue; - this.groupBox3.Location = new System.Drawing.Point(8, 144); - this.groupBox3.Name = "groupBox3"; - this.groupBox3.Size = new System.Drawing.Size(392, 208); - this.groupBox3.TabIndex = 3; - this.groupBox3.TabStop = false; - this.groupBox3.Text = "Tasks"; - // - // toolStrip1 - // - this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.saveToolStripButton, - this.printToolStripButton, - this.toolStripSeparator, - this.toolStripLabel1, - this.toolStripButton1, - this.toolStripButton2}); - this.toolStrip1.Location = new System.Drawing.Point(3, 16); - this.toolStrip1.Name = "toolStrip1"; - this.toolStrip1.Size = new System.Drawing.Size(386, 25); - this.toolStrip1.TabIndex = 1; - this.toolStrip1.Text = "toolStrip1"; - // - // saveToolStripButton - // - this.saveToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.saveToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("saveToolStripButton.Image"))); - this.saveToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; - this.saveToolStripButton.Name = "saveToolStripButton"; - this.saveToolStripButton.Size = new System.Drawing.Size(23, 22); - this.saveToolStripButton.Text = "&Save"; - // - // printToolStripButton - // - this.printToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.printToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("printToolStripButton.Image"))); - this.printToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; - this.printToolStripButton.Name = "printToolStripButton"; - this.printToolStripButton.Size = new System.Drawing.Size(23, 22); - this.printToolStripButton.Text = "&Print"; - // - // toolStripSeparator - // - this.toolStripSeparator.Name = "toolStripSeparator"; - this.toolStripSeparator.Size = new System.Drawing.Size(6, 25); - // - // toolStripLabel1 - // - this.toolStripLabel1.ForeColor = System.Drawing.SystemColors.ControlText; - this.toolStripLabel1.Name = "toolStripLabel1"; - this.toolStripLabel1.Size = new System.Drawing.Size(36, 22); - this.toolStripLabel1.Text = "Level:"; - // - // toolStripButton1 - // - this.toolStripButton1.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - this.toolStripButton1.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton1.Image"))); - this.toolStripButton1.ImageTransparentColor = System.Drawing.Color.Magenta; - this.toolStripButton1.Name = "toolStripButton1"; - this.toolStripButton1.Size = new System.Drawing.Size(24, 22); - this.toolStripButton1.Text = "Up"; - // - // toolStripButton2 - // - this.toolStripButton2.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - this.toolStripButton2.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton2.Image"))); - this.toolStripButton2.ImageTransparentColor = System.Drawing.Color.Magenta; - this.toolStripButton2.Name = "toolStripButton2"; - this.toolStripButton2.Size = new System.Drawing.Size(38, 22); - this.toolStripButton2.Text = "Down"; - // // taskList // - this.taskList.AllColumns.Add(this.TaskHeader); - this.taskList.AllColumns.Add(this.TimeHeader); - this.taskList.AllColumns.Add(this.PercentHeader); - this.taskList.AllColumns.Add(this.GoalHeader); - this.taskList.AllColumns.Add(this.PercentGoalHeader); - this.taskList.AllColumns.Add(this.ActiveTimeHeader); - this.taskList.AllColumns.Add(this.InactiveTimeHeader); + this.taskList.Alignment = System.Windows.Forms.ListViewAlignment.Default; this.taskList.AllowColumnReorder = true; - this.taskList.AlternateRowBackColor = System.Drawing.Color.Empty; + this.taskList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.taskList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { this.TaskHeader, this.TimeHeader, this.PercentHeader, this.GoalHeader, - this.PercentGoalHeader, - this.ActiveTimeHeader, - this.InactiveTimeHeader}); - this.taskList.FullRowSelect = true; - this.taskList.GridLines = true; + this.PercentGoalHeader}); + treeListViewItemCollectionComparer1.Column = 0; + treeListViewItemCollectionComparer1.SortOrder = System.Windows.Forms.SortOrder.None; + this.taskList.Comparer = treeListViewItemCollectionComparer1; this.taskList.HideSelection = false; - this.taskList.Location = new System.Drawing.Point(6, 44); + this.taskList.Location = new System.Drawing.Point(8, 16); this.taskList.MultiSelect = false; this.taskList.Name = "taskList"; - this.taskList.ShowGroups = false; - this.taskList.Size = new System.Drawing.Size(380, 158); + this.taskList.Size = new System.Drawing.Size(376, 184); + this.taskList.Sorting = System.Windows.Forms.SortOrder.None; this.taskList.TabIndex = 0; this.taskList.UseCompatibleStateImageBehavior = false; - this.taskList.View = System.Windows.Forms.View.Details; // // TaskHeader // - this.TaskHeader.AspectName = null; this.TaskHeader.Text = "Description"; this.TaskHeader.Width = 150; // // TimeHeader // - this.TimeHeader.AspectName = null; this.TimeHeader.Text = "Time Elapsed"; this.TimeHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // PercentHeader // - this.PercentHeader.AspectName = null; this.PercentHeader.Text = "%"; this.PercentHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; this.PercentHeader.Width = 50; // // GoalHeader // - this.GoalHeader.AspectName = null; this.GoalHeader.Text = "Estimated"; this.GoalHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // PercentGoalHeader // - this.PercentGoalHeader.AspectName = null; this.PercentGoalHeader.Text = "% Elapsed"; this.PercentGoalHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; this.PercentGoalHeader.Width = 50; // - // ActiveTimeHeader + // fromDateTimePicker + // + this.fromDateTimePicker.CustomFormat = ""; + this.fromDateTimePicker.Format = System.Windows.Forms.DateTimePickerFormat.Short; + this.fromDateTimePicker.Location = new System.Drawing.Point(80, 32); + this.fromDateTimePicker.Name = "fromDateTimePicker"; + this.fromDateTimePicker.Size = new System.Drawing.Size(88, 20); + this.fromDateTimePicker.TabIndex = 0; + this.fromDateTimePicker.Value = new System.DateTime(2006, 10, 2, 0, 0, 0, 0); // - this.ActiveTimeHeader.AspectName = null; - this.ActiveTimeHeader.Text = "Active Time"; - this.ActiveTimeHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // groupBox1 // - // InactiveTimeHeader + this.groupBox1.ForeColor = System.Drawing.Color.Blue; + this.groupBox1.Location = new System.Drawing.Point(8, 64); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(72, 80); + this.groupBox1.TabIndex = 4; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Total Time"; // - this.InactiveTimeHeader.AspectName = null; - this.InactiveTimeHeader.Text = "Inactive Time"; - this.InactiveTimeHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + // groupBox3 + // + this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox3.Controls.Add(this.taskList); + this.groupBox3.ForeColor = System.Drawing.Color.Blue; + this.groupBox3.Location = new System.Drawing.Point(8, 144); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(392, 208); + this.groupBox3.TabIndex = 3; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Tasks"; // // label2 // @@ -382,16 +277,25 @@ private void InitializeComponent() this.groupBox2.TabStop = false; this.groupBox2.Text = "% Active"; // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.panel1.Controls.Add(this.toolBar); + this.panel1.Location = new System.Drawing.Point(352, 120); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(48, 24); + this.panel1.TabIndex = 14; + // // toolBar // this.toolBar.Buttons.AddRange(new System.Windows.Forms.ToolBarButton[] { this.toolBarButton1, this.toolBarButton2}); this.toolBar.Divider = false; - this.toolBar.Dock = System.Windows.Forms.DockStyle.None; + this.toolBar.Dock = System.Windows.Forms.DockStyle.Fill; this.toolBar.DropDownArrows = true; this.toolBar.ImageList = this.toolBarImages; - this.toolBar.Location = new System.Drawing.Point(352, 53); + this.toolBar.Location = new System.Drawing.Point(0, 0); this.toolBar.Name = "toolBar"; this.toolBar.ShowToolTips = true; this.toolBar.Size = new System.Drawing.Size(48, 26); @@ -473,90 +377,12 @@ private void InitializeComponent() this.groupBox5.TabStop = false; this.groupBox5.Text = "% Elapsed"; // - // listViewPrinter1 - // - // - // - // - this.listViewPrinter1.CellFormat.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.CellFormat.BottomBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(255))))); - this.listViewPrinter1.CellFormat.BottomBorderWidth = 0.5F; - this.listViewPrinter1.CellFormat.CanWrap = true; - this.listViewPrinter1.CellFormat.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F); - this.listViewPrinter1.CellFormat.LeftBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(255))))); - this.listViewPrinter1.CellFormat.LeftBorderWidth = 0.5F; - this.listViewPrinter1.CellFormat.MinimumTextHeight = 0F; - this.listViewPrinter1.CellFormat.RightBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(255))))); - this.listViewPrinter1.CellFormat.RightBorderWidth = 0.5F; - this.listViewPrinter1.CellFormat.TextColor = System.Drawing.Color.Empty; - this.listViewPrinter1.CellFormat.TopBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(255))))); - this.listViewPrinter1.CellFormat.TopBorderWidth = 0.5F; - this.listViewPrinter1.DocumentName = "Tasks Summary Report"; - this.listViewPrinter1.Footer = "{1:F}\t\tPage: {0}"; - // - // - // - this.listViewPrinter1.FooterFormat.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.FooterFormat.BottomBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.FooterFormat.Font = new System.Drawing.Font("Verdana", 10F, System.Drawing.FontStyle.Italic); - this.listViewPrinter1.FooterFormat.LeftBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.FooterFormat.MinimumTextHeight = 0F; - this.listViewPrinter1.FooterFormat.RightBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.FooterFormat.TextColor = System.Drawing.Color.Black; - this.listViewPrinter1.FooterFormat.TopBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(128)))), ((int)(((byte)(128)))), ((int)(((byte)(128))))); - this.listViewPrinter1.FooterFormat.TopBorderWidth = 0.5F; - // - // - // - this.listViewPrinter1.GroupHeaderFormat.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.GroupHeaderFormat.BottomBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.GroupHeaderFormat.BottomBorderWidth = 3F; - this.listViewPrinter1.GroupHeaderFormat.Font = new System.Drawing.Font("Verdana", 10F, System.Drawing.FontStyle.Bold); - this.listViewPrinter1.GroupHeaderFormat.LeftBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.GroupHeaderFormat.MinimumTextHeight = 0F; - this.listViewPrinter1.GroupHeaderFormat.RightBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.GroupHeaderFormat.TextColor = System.Drawing.Color.Black; - this.listViewPrinter1.GroupHeaderFormat.TopBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.Header = "Tasks Summary Report"; - // - // - // - this.listViewPrinter1.HeaderFormat.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.HeaderFormat.BottomBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.HeaderFormat.Font = new System.Drawing.Font("Verdana", 24F); - this.listViewPrinter1.HeaderFormat.LeftBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.HeaderFormat.MinimumTextHeight = 0F; - this.listViewPrinter1.HeaderFormat.RightBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.HeaderFormat.TextColor = System.Drawing.Color.WhiteSmoke; - this.listViewPrinter1.HeaderFormat.TopBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.listViewPrinter1.IsListHeaderOnEachPage = false; - this.listViewPrinter1.ListFont = new System.Drawing.Font("Microsoft Sans Serif", 9F); - this.listViewPrinter1.ListGridColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(255))))); - // - // - // - this.listViewPrinter1.ListHeaderFormat.BackgroundColor = System.Drawing.Color.LightGray; - this.listViewPrinter1.ListHeaderFormat.BottomBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(169)))), ((int)(((byte)(169)))), ((int)(((byte)(169))))); - this.listViewPrinter1.ListHeaderFormat.BottomBorderWidth = 1.5F; - this.listViewPrinter1.ListHeaderFormat.CanWrap = true; - this.listViewPrinter1.ListHeaderFormat.Font = new System.Drawing.Font("Verdana", 12F); - this.listViewPrinter1.ListHeaderFormat.LeftBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(169)))), ((int)(((byte)(169)))), ((int)(((byte)(169))))); - this.listViewPrinter1.ListHeaderFormat.LeftBorderWidth = 1.5F; - this.listViewPrinter1.ListHeaderFormat.MinimumTextHeight = 0F; - this.listViewPrinter1.ListHeaderFormat.RightBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(169)))), ((int)(((byte)(169)))), ((int)(((byte)(169))))); - this.listViewPrinter1.ListHeaderFormat.RightBorderWidth = 1.5F; - this.listViewPrinter1.ListHeaderFormat.TextColor = System.Drawing.Color.Black; - this.listViewPrinter1.ListHeaderFormat.TopBorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(169)))), ((int)(((byte)(169)))), ((int)(((byte)(169))))); - this.listViewPrinter1.ListHeaderFormat.TopBorderWidth = 1.5F; - this.listViewPrinter1.ListView = this.taskList; - this.listViewPrinter1.WatermarkColor = System.Drawing.Color.Empty; - // // SummaryControl // - this.Controls.Add(this.toolBar); this.Controls.Add(this.groupBox5); this.Controls.Add(this.fromDateTimePicker); this.Controls.Add(this.groupBox4); + this.Controls.Add(this.panel1); this.Controls.Add(this.groupBox2); this.Controls.Add(this.browseButton); this.Controls.Add(this.label2); @@ -569,12 +395,9 @@ private void InitializeComponent() this.Name = "SummaryControl"; this.Size = new System.Drawing.Size(408, 360); this.groupBox3.ResumeLayout(false); - this.groupBox3.PerformLayout(); - this.toolStrip1.ResumeLayout(false); - this.toolStrip1.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.taskList)).EndInit(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); this.ResumeLayout(false); - this.PerformLayout(); } @@ -650,7 +473,7 @@ public override void OnTabPageSelected() private void browseButton_Click(object sender, EventArgs e) { - TaskSelectForm tgForm = new TaskSelectForm(); + TasksHierarchyForm tgForm = new TasksHierarchyForm(); if (tgForm.ShowDialog(this) == DialogResult.OK) SetParent(tgForm.SelectedTaskId); @@ -748,19 +571,26 @@ private void UpdateTasksSummary(TaskSummaryResult taskSummaryResult) if (summary.IsActive) totalActiveTime += summary.TotalActiveTime; - TreeListViewItem lvi = - new TreeListViewItem(summary.Description, - new string[] - { - String.Empty, - 0.ToString("0.0%", CultureInfo.InvariantCulture), - String.Empty, - 0.ToString("0.0%", CultureInfo.InvariantCulture), - String.Empty, - String.Empty - }); - - lvi.ImageIndex = summary.IconId; + //TimeSpan activeTimeSpan = new TimeSpan(0, 0, Convert.ToInt32(summary.TotalActiveTime)); + //TimeSpan inactiveTimeSpan = new TimeSpan(0, 0, Convert.ToInt32(summary.TotalInactiveTime)); + TimeSpan elapsedTimeSpan = new TimeSpan(0, 0, Convert.ToInt32(summary.TotalActiveTime + summary.TotalInactiveTime)); + TimeSpan estimationTimeSpan = new TimeSpan(0, Convert.ToInt32(summary.TotalEstimation), 0); + string estimation; + if (summary.TotalEstimation == 0) + estimation = "Not estimated"; + else + estimation = ViewHelper.TimeSpanToTimeString(estimationTimeSpan); + TreeListViewItem lvi = + new TreeListViewItem(summary.Description, + new string[] + { + ViewHelper.TimeSpanToTimeString(elapsedTimeSpan), + 0.ToString("0.0%", CultureInfo.InvariantCulture), + estimation, + 0.ToString("0.0%", CultureInfo.InvariantCulture), + }); + + lvi.ImageIndex = summary.IconId; lvi.Tag = summary; this.taskList.Items.Add(lvi); @@ -782,28 +612,14 @@ private void CalculateTasksPercents() double percent = 0; double goalPercent = 0; - TaskSummary summary = (TaskSummary) item.Tag; + TaskSummary sum = (TaskSummary) item.Tag; if (totalTime > 0) - percent = (summary.TotalActiveTime + summary.TotalInactiveTime)/totalTime; - - if (summary.TotalEstimation > 0) - goalPercent = summary.TotalTimeOverEstimation / (summary.TotalEstimation*60); - - TimeSpan activeTimeSpan = new TimeSpan(0, 0, Convert.ToInt32(summary.TotalActiveTime)); - TimeSpan inactiveTimeSpan = new TimeSpan(0, 0, Convert.ToInt32(summary.TotalInactiveTime)); - TimeSpan elapsedTimeSpan = new TimeSpan(0, 0, Convert.ToInt32(summary.TotalActiveTime + summary.TotalInactiveTime)); - TimeSpan estimationTimeSpan = new TimeSpan(0, Convert.ToInt32(summary.TotalEstimation), 0); - string estimation; - if (summary.TotalEstimation == 0) - estimation = "Not estimated"; - else - estimation = ViewHelper.TimeSpanToTimeString(estimationTimeSpan); + percent = (sum.TotalActiveTime + sum.TotalInactiveTime)/totalTime; + + if (sum.TotalEstimation > 0) + goalPercent = sum.TotalTimeOverEstimation / (sum.TotalEstimation*60); - item.SubItems[TimeHeader.Index].Text = ViewHelper.TimeSpanToTimeString(elapsedTimeSpan); - item.SubItems[ActiveTimeHeader.Index].Text = ViewHelper.TimeSpanToTimeString(activeTimeSpan); - item.SubItems[InactiveTimeHeader.Index].Text = ViewHelper.TimeSpanToTimeString(inactiveTimeSpan); - item.SubItems[GoalHeader.Index].Text = estimation; item.SubItems[PercentHeader.Index].Text = percent.ToString("0.0%", CultureInfo.InvariantCulture); item.SubItems[PercentGoalHeader.Index].Text = goalPercent.ToString("0.0%", CultureInfo.InvariantCulture); diff --git a/code/PTM/View/Controls/SummaryControl.resx b/code/PTM/View/Controls/SummaryControl.resx index 283dd49..5168575 100644 --- a/code/PTM/View/Controls/SummaryControl.resx +++ b/code/PTM/View/Controls/SummaryControl.resx @@ -117,72 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 354, 17 - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAixJREFUOE+tk91L - k3EUx/cvdN9N0EW3NTWGa7EaPOUcyqphWBG9PZEv5dJlmqhYmUYtXyBb4dJJy+kknFT4BqZIjaFMJUsz - V7TEoabYRDD49ju/6Pm1Mi+iH5zLz+c855zvo1L9j/fsaRRUvvZltHmX8Ni9gMaGCO47ZlBb8wn22yHc - KJ9CackECgteIy93FBfOB6H0JrC3B6ipXsVGb2V1Dca0XhxOe8JLEXhbF7mgsuLLX3mCIwsr2G1+DrVa - huWQRwjcj+a5oLTk87qCn/D78CLiTD4UXJ7GAXOTEDjrZ7ngku3dH4Jf4ZHJCLZJXlhzxpGa4hSCurth - LsjOGo0R/A4PBsPYrHdDlgMwmRxCUF31kQvkMwFFsB7c4/+ATYkNOHL0BZKSaoXgZuU0urvATgkcP/kK - lmMDfNu0MJqZPps6/4D7cNDSCUmyC8HVskl0+MAyADS5vrG7f0X59Tm+VFoYzZyZEVTg5NR2GAwVQnCl - cByeZuChc40FJwpjek5MmU/YkH6uiHdOTmHwfg/0+jIhsOWNMRiouhPlnUnAQoI4rYSht7MYm5qDnHsN - e41tHNbucUGnKxICiqXjHpTPJgHBZ/Nv4U1oHqGZJVwstiNe72JwI+J3PYA2MV8IMjOG2dzLfOatBg+2 - 7JDQ0tEPX9cguvv8GHg5hH0mC9S6eiQweLumDhqNVQgo06dP9fN4UsIoJHRnOhVtmxZGM1NXKoJ3JmTH - Cv71r/4OTrQ4xWMwWlcAAAAASUVORK5CYII= - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAi1JREFUOE+1k/9P - UlEYxv2nWK2tVlttGmpltrCcEQ1XUjSMaUHJNLIpNcnCragplBvUoC/okJhZLG92ySUpU8RNICdIhAio - EF+e7r1UZMDW1jrb+8t7z/N83vucc8rK/sdyeYIwvpopWYbRaZTk0uIx0o0/V/JbGt7lVTwxT6CKKylt - oLd8xGYihS/hKGz2WaaeWUnoTATsMz7UCztx9Ex7cYN3jkUQU4tb4DR5LZaAcyEAg4VE5YlLMFmJQoNQ - JA61gUA6k4XPH9pCN9s+gZz2oq5Jjlq+DDfUz3Fba86bOGY9jHiUdDF0mvqT7A/F4fKEcE9nZf5d1jOI - B4ZxVJ2U5gyc8z70akegMX3AXb0ND1+8R6/GgvZbeog61OA2K3CA2lxR34JjZ69B2T8EsVyN/Q0XcwY3 - B14iGk8UpE43UukMNqhA6QyC4Q0srcQg7dagsbWHmuDHScj7jDC9nsJTqx0a4xjuaIfRqXoMSXc/hG0q - 8C4owGnqwEGeFOXHxThH9eoEV7G7VpiboE2pK0qnm9H1JLz+NUzOBfHWEcAQsQSuqAuVDa1gVZzKGUgU - jwoMqAzxNZbC3Od1jDvDYPdth+7NCpP8Yf4V7KoR5A1arg8gmQIoGMLxLJYjWSwEMphwb2J4MoZB2yqU - LBZUIxHGYB9HlBfTE4jl9+GmBPTHv6lfo//+GGoaZajmXQabumXl1HHt5TRjz5Hz2HlIgB3Vp7GNzWeo - RcX/+pq/AwHYL0leVl8fAAAAAElFTkSuQmCC - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAgxJREFUOE+lkvtL - U2EYx+0PEbtpFwnBKPGKiJImGP0gYhIYs1E5GF5gIxkpA00JRSmMEF0ohMh+GaRWYlqabMVcNdS2QpaI - VqiDIYhk397vA6fXhCjyhYdzeM/5fp7vczkAdeL2cwho7v/wWzT1zcN+Pwhr51uY2/y41PQaF+wzKKiZ - QvaN58g0jyLd5KEUcQbg+84P/Cm2tncQjW3j68YWIqubCC3FcOJc478BAuGoZM6zvoRnakXEruEIjhc4 - /g5gZop9c+voGAyLbQIfeBZxLL9BA1jzXvuGbWamuKh+GmmVbswE19A59FEBbmoAG7YbsLtm2mZmiml9 - cvabNDwpz6YB7LYBoMXCumkJr7LOmnnHzBQ/9X2Bo2cOibm1GsBREbAQiYmw/8lnuCeWkVzcgnZlnw1j - 3HV/wuNXK6i/9x5Hc6wawDlTXHbLJ+LZUBQPRyKwdQdxutwl1h+NLXHh5Ht1ewBHsiwawCW57HyDAfWR - dvl0uhZQ1eqX8aVc7EKLqrum651ATLf9OJx5XQM4KmY0xPzZ0hFAiQJnXB0WwME0E3IsL5B17ZlADqWb - NYDrOepdlcysmTWWOrxqbceRWtaLk0VO1XW72D5Vckd2gMBfq8zdpmUG62NJvKM4+XyziDk24xmfWoGE - s1c0gHPmbrPTpHNJKOCo2G1mZs20zcwUJ5yp1AB5+8/zEwgF5GMVDxh4AAAAAElFTkSuQmCC - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAgxJREFUOE+lkvtL - U2EYx+0PEbtpFwnBKPGKiJImGP0gYhIYs1E5GF5gIxkpA00JRSmMEF0ohMh+GaRWYlqabMVcNdS2QpaI - VqiDIYhk397vA6fXhCjyhYdzeM/5fp7vczkAdeL2cwho7v/wWzT1zcN+Pwhr51uY2/y41PQaF+wzKKiZ - QvaN58g0jyLd5KEUcQbg+84P/Cm2tncQjW3j68YWIqubCC3FcOJc478BAuGoZM6zvoRnakXEruEIjhc4 - /g5gZop9c+voGAyLbQIfeBZxLL9BA1jzXvuGbWamuKh+GmmVbswE19A59FEBbmoAG7YbsLtm2mZmiml9 - cvabNDwpz6YB7LYBoMXCumkJr7LOmnnHzBQ/9X2Bo2cOibm1GsBREbAQiYmw/8lnuCeWkVzcgnZlnw1j - 3HV/wuNXK6i/9x5Hc6wawDlTXHbLJ+LZUBQPRyKwdQdxutwl1h+NLXHh5Ht1ewBHsiwawCW57HyDAfWR - dvl0uhZQ1eqX8aVc7EKLqrum651ATLf9OJx5XQM4KmY0xPzZ0hFAiQJnXB0WwME0E3IsL5B17ZlADqWb - NYDrOepdlcysmTWWOrxqbceRWtaLk0VO1XW72D5Vckd2gMBfq8zdpmUG62NJvKM4+XyziDk24xmfWoGE - s1c0gHPmbrPTpHNJKOCo2G1mZs20zcwUJ5yp1AB5+8/zEwgF5GMVDxh4AAAAAElFTkSuQmCC - - 17, 17 @@ -236,7 +170,4 @@ 141, 17 - - 226, 17 - \ No newline at end of file diff --git a/code/PTM/View/Controls/TasksLogControl.cs b/code/PTM/View/Controls/TasksLogControl.cs index 64a3a03..363dc64 100644 --- a/code/PTM/View/Controls/TasksLogControl.cs +++ b/code/PTM/View/Controls/TasksLogControl.cs @@ -316,7 +316,7 @@ private void TasksLogControl_Load(object sender, EventArgs e) internal void NewTaskLog(bool mustAddATask) { notifyTimer.Stop(); - TaskSelectForm tasklog = new TaskSelectForm(); + TaskLogForm tasklog = new TaskLogForm(); if (tasklog.ShowDialog(this) == DialogResult.OK) { AddTaskLog(tasklog.SelectedTaskId, @@ -355,13 +355,13 @@ private void EditSelectedTaskLog() return; int taskId = ((Log) taskList.SelectedItems[0].Tag).TaskId; - TaskSelectForm taskSelectForm = new TaskSelectForm(taskId); - if (taskSelectForm.ShowDialog(this.Parent) == DialogResult.OK) + TaskLogForm taskLogForm = new TaskLogForm(taskId); + if (taskLogForm.ShowDialog(this.Parent) == DialogResult.OK) { for (int i = 0; i < taskList.SelectedItems.Count; i++) { int taskLogId = ((Log) taskList.SelectedItems[i].Tag).Id; - Logs.UpdateLogTaskId(taskLogId, taskSelectForm.SelectedTaskId); + Logs.UpdateLogTaskId(taskLogId, taskLogForm.SelectedTaskId); } } } @@ -621,8 +621,6 @@ private void CreateRigthClickMenu() { if (task.Id == Tasks.IdleTask.Id) continue; - if (task.Hidden) continue; - TaskMenuItem menuItem = new TaskMenuItem(task.Id); menuItem.Text = task.Description; menuItem.Pick += new EventHandler(mnuTaskSetTo_Click); @@ -801,8 +799,6 @@ private void CreateNotifyMenu() foreach (Task task in tasks) { if (task.Id == Tasks.IdleTask.Id) - continue; - if(task.Hidden) continue; TaskMenuItem menuItem = new TaskMenuItem(task.Id); menuItem.Text = task.Description; @@ -869,8 +865,6 @@ private void TasksDataTable_TasksRowChanged(Tasks.TaskChangeEventArgs e) } } } - if(e.Task.Id == Tasks.CurrentTask.Id) - UpdateNotifyIcon(); CreateNotifyMenu(); CreateRigthClickMenu(); DisplaySelectedItemStatus(); @@ -946,8 +940,6 @@ private void TasksLog_LogChanged(Logs.LogChangeEventArgs e) taskList.Items.Insert(0, itemA); } } - if(e.Log.Id == Logs.CurrentLog.Id) - UpdateNotifyIcon(); DisplaySelectedItemStatus(); } } @@ -974,6 +966,13 @@ private void Logs_CurrentLogDurationChanged(object sender, ElapsedEventArgs e) } } + if (notifyIcon.Tag == null || (int)notifyIcon.Tag != Tasks.CurrentTask.Id) + { + notifyIcon.Text = Tasks.CurrentTask.Description.Substring(0, Math.Min(Tasks.CurrentTask.Description.Length, 63)); //notifyIcon supports 64 chars + notifyIcon.Icon = (Icon)IconsManager.CommonTaskIconsTable[Tasks.CurrentTask.IconId]; + notifyIcon.Tag = Tasks.CurrentTask.Id; + } + //update cache if (taskExecutedTimeCache.Contains(Tasks.CurrentTask.Id)) taskExecutedTimeCache[Tasks.CurrentTask.Id] = (int) taskExecutedTimeCache[Tasks.CurrentTask.Id]+1; @@ -985,14 +984,7 @@ private void Logs_CurrentLogDurationChanged(object sender, ElapsedEventArgs e) } } - private void UpdateNotifyIcon() - { - notifyIcon.Text = Tasks.CurrentTask.Description.Substring(0, Math.Min(Tasks.CurrentTask.Description.Length, 63)); //notifyIcon supports 64 chars - notifyIcon.Icon = (Icon)IconsManager.CommonTaskIconsTable[Tasks.CurrentTask.IconId]; - notifyIcon.Tag = Tasks.CurrentTask.Id; - } - - private void CheckCurrentDayChanged() + private void CheckCurrentDayChanged() { if (currentDay != DateTime.Today) { diff --git a/code/PTM/View/Controls/TasksTreeViewControl.cs b/code/PTM/View/Controls/TasksTreeViewControl.cs index 9053e96..dafb6f5 100644 --- a/code/PTM/View/Controls/TasksTreeViewControl.cs +++ b/code/PTM/View/Controls/TasksTreeViewControl.cs @@ -6,7 +6,6 @@ using System.Windows.Forms; using PTM.Framework; using PTM.Framework.Infos; -using PTM.View.Controls.TreeListViewComponents; using PTM.View.Forms; namespace PTM.View.Controls @@ -14,6 +13,22 @@ namespace PTM.View.Controls public class TasksTreeViewControl : UserControl { private IContainer components; + + public TasksTreeViewControl() + { + InitializeComponent(); + InitCommonControls(); + this.treeView.ItemDrag += new ItemDragEventHandler(treeView_ItemDrag); + this.treeView.DragDrop += new DragEventHandler(treeView_DragDrop); + this.treeView.DragOver += new DragEventHandler(treeView_DragOver); + this.treeView.DragEnter += new DragEventHandler(treeView_DragEnter); + this.treeView.DragLeave += new EventHandler(treeView_DragLeave); + this.treeView.GiveFeedback += new GiveFeedbackEventHandler(treeView_GiveFeedback); + this.treeView.DoubleClick += new EventHandler(treeView_DoubleClick); + this.timer.Tick += new EventHandler(timer_Tick); + timer.Interval = 200; + } + public event EventHandler SelectedTaskChanged; protected override void Dispose(bool disposing) @@ -34,146 +49,104 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - PTM.View.Controls.TreeListViewComponents.TreeListViewItemCollection.TreeListViewItemCollectionComparer treeListViewItemCollectionComparer1 = new PTM.View.Controls.TreeListViewComponents.TreeListViewItemCollection.TreeListViewItemCollectionComparer(); - this.treeMenu = new System.Windows.Forms.ContextMenu(); - this.mnuProperties = new System.Windows.Forms.MenuItem(); - this.menuItem5 = new System.Windows.Forms.MenuItem(); - this.mnuAdd = new System.Windows.Forms.MenuItem(); - this.mnuRename = new System.Windows.Forms.MenuItem(); - this.mnuDelete = new System.Windows.Forms.MenuItem(); - this.treeView = new PTM.View.Controls.TreeListViewComponents.TreeListView(); - this.tasksColumnHeader = new System.Windows.Forms.ColumnHeader(); - this.priorityColumnHeader = new System.Windows.Forms.ColumnHeader(); - this.SuspendLayout(); - // - // treeMenu - // - this.treeMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { - this.mnuProperties, - this.menuItem5, - this.mnuAdd, - this.mnuRename, - this.mnuDelete}); - // - // mnuProperties - // - this.mnuProperties.DefaultItem = true; - this.mnuProperties.Index = 0; - this.mnuProperties.Shortcut = System.Windows.Forms.Shortcut.CtrlP; - this.mnuProperties.Text = "Properties..."; - this.mnuProperties.Click += new System.EventHandler(this.mnuProperties_Click); - // - // menuItem5 - // - this.menuItem5.Index = 1; - this.menuItem5.Text = "-"; - // - // mnuAdd - // - this.mnuAdd.Index = 2; - this.mnuAdd.Shortcut = System.Windows.Forms.Shortcut.Ins; - this.mnuAdd.Text = "Add New"; - this.mnuAdd.Click += new System.EventHandler(this.mnuAdd_Click); - // - // mnuRename - // - this.mnuRename.Index = 3; - this.mnuRename.Shortcut = System.Windows.Forms.Shortcut.F2; - this.mnuRename.Text = "Rename"; - this.mnuRename.Click += new System.EventHandler(this.mnuRename_Click); - // - // mnuDelete - // - this.mnuDelete.Index = 4; - this.mnuDelete.Shortcut = System.Windows.Forms.Shortcut.Del; - this.mnuDelete.Text = "Delete"; - this.mnuDelete.Click += new System.EventHandler(this.mnuDelete_Click); - // - // treeView - // - this.treeView.AllowColumnReorder = true; - this.treeView.AllowDrop = true; - this.treeView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.tasksColumnHeader, - this.priorityColumnHeader}); - treeListViewItemCollectionComparer1.Column = 0; - treeListViewItemCollectionComparer1.SortOrder = System.Windows.Forms.SortOrder.Ascending; - this.treeView.Comparer = treeListViewItemCollectionComparer1; - this.treeView.ContextMenu = this.treeMenu; - this.treeView.Dock = System.Windows.Forms.DockStyle.Fill; - this.treeView.HideSelection = false; - this.treeView.LabelEdit = true; - this.treeView.Location = new System.Drawing.Point(0, 0); - this.treeView.MultiSelect = false; - this.treeView.Name = "treeView"; - this.treeView.ShowGroups = false; - this.treeView.Size = new System.Drawing.Size(359, 215); - this.treeView.TabIndex = 0; - this.treeView.UseCompatibleStateImageBehavior = false; - // - // tasksColumnHeader - // - this.tasksColumnHeader.Text = "Tasks"; - this.tasksColumnHeader.Width = 294; - // - // priorityColumnHeader - // - this.priorityColumnHeader.Text = "Priority"; - this.priorityColumnHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; - // - // TasksTreeViewControl - // - this.Controls.Add(this.treeView); - this.Name = "TasksTreeViewControl"; - this.Size = new System.Drawing.Size(359, 215); - this.ResumeLayout(false); - + this.components = new System.ComponentModel.Container(); + System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof (TasksTreeViewControl)); + this.treeView = new System.Windows.Forms.TreeView(); + this.treeMenu = new System.Windows.Forms.ContextMenu(); + this.mnuProperties = new System.Windows.Forms.MenuItem(); + this.menuItem5 = new System.Windows.Forms.MenuItem(); + this.mnuDelete = new System.Windows.Forms.MenuItem(); + this.mnuRename = new System.Windows.Forms.MenuItem(); + this.groupsImageList = new System.Windows.Forms.ImageList(this.components); + this.SuspendLayout(); + // + // treeView + // + this.treeView.AllowDrop = true; + this.treeView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.treeView.ContextMenu = this.treeMenu; + this.treeView.Dock = System.Windows.Forms.DockStyle.Fill; + this.treeView.HotTracking = true; + this.treeView.ImageIndex = -1; + this.treeView.Location = new System.Drawing.Point(0, 0); + this.treeView.Name = "treeView"; + this.treeView.SelectedImageIndex = -1; + this.treeView.Size = new System.Drawing.Size(120, 60); + this.treeView.TabIndex = 0; + // + // treeMenu + // + this.treeMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] + { + this.mnuProperties, + this.menuItem5, + this.mnuDelete, + this.mnuRename + }); + // + // mnuProperties + // + this.mnuProperties.Index = 0; + this.mnuProperties.Text = "Properties..."; + this.mnuProperties.Click += new System.EventHandler(this.mnuProperties_Click); + // + // menuItem5 + // + this.menuItem5.Index = 1; + this.menuItem5.Text = "-"; + // + // mnuDelete + // + this.mnuDelete.Index = 2; + this.mnuDelete.Text = "Delete"; + this.mnuDelete.Click += new System.EventHandler(this.mnuDelete_Click); + // + // mnuRename + // + this.mnuRename.Index = 3; + this.mnuRename.Text = "Rename"; + this.mnuRename.Click += new System.EventHandler(this.mnuRename_Click); + // + // groupsImageList + // + this.groupsImageList.ImageSize = new System.Drawing.Size(16, 16); + this.groupsImageList.ImageStream = + ((System.Windows.Forms.ImageListStreamer) (resources.GetObject("groupsImageList.ImageStream"))); + this.groupsImageList.TransparentColor = System.Drawing.Color.Transparent; + // + // TasksTreeViewControl + // + this.Controls.Add(this.treeView); + this.Name = "TasksTreeViewControl"; + this.Size = new System.Drawing.Size(120, 60); + this.ResumeLayout(false); } #endregion - private TreeListView treeView; + private ImageList groupsImageList; + private TreeView treeView; private int currentSelectedTask = -1; private MenuItem menuItem5; private ContextMenu treeMenu; private MenuItem mnuDelete; private MenuItem mnuRename; private MenuItem mnuProperties; - private ColumnHeader tasksColumnHeader; - private ColumnHeader priorityColumnHeader; - private MenuItem mnuAdd; - private bool showHidden; public const string NEW_TASK = "New Task"; + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + treeView.ImageList = this.groupsImageList; + treeView.ImageIndex = 0; + treeView.SelectedImageIndex = 1; + treeView.LabelEdit = false; + treeView.HideSelection = false; + treeView.AfterSelect += new TreeViewEventHandler(treeView_AfterSelect); + treeView.AfterLabelEdit += new NodeLabelEditEventHandler(treeView_AfterLabelEdit); + } - public bool ShowHidden - { - get { return showHidden; } - set { showHidden = value; } - } - - #region Initialization - public TasksTreeViewControl() - { - InitializeComponent(); - InitCommonControls(); - treeView.ItemDrag += new ItemDragEventHandler(treeView_ItemDrag); - treeView.DragDrop += new DragEventHandler(treeView_DragDrop); - treeView.DragOver += new DragEventHandler(treeView_DragOver); - treeView.DragEnter += new DragEventHandler(treeView_DragEnter); - treeView.DragLeave += new EventHandler(treeView_DragLeave); - treeView.GiveFeedback += new GiveFeedbackEventHandler(treeView_GiveFeedback); - treeView.DoubleClick += new EventHandler(treeView_DoubleClick); - timer.Tick += new EventHandler(timer_Tick); - treeView.SmallImageList = IconsManager.IconsList; - treeView.SelectedIndexChanged += new EventHandler(treeView_SelectedIndexChanged); - treeView.AfterLabelEdit += new TreeListViewLabelEditEventHandler(treeView_AfterLabelEdit); - treeView.BeforeLabelEdit += new TreeListViewBeforeLabelEditEventHandler(treeView_BeforeLabelEdit); - timer.Interval = 200; - } - - protected override void OnHandleDestroyed(EventArgs e) + protected override void OnHandleDestroyed(EventArgs e) { base.OnHandleDestroyed(e); Tasks.TaskChanged -= new Tasks.TaskChangeEventHandler(Tasks_TasksRowChanged); @@ -187,28 +160,12 @@ internal void Initialize() Tasks.TaskDeleting += new Tasks.TaskChangeEventHandler(Tasks_TasksRowDeleting); } - #endregion - - public void LoadTree() - { - treeView.Items.Clear(); - TreeListViewItem nodeParent = CreateNode(Tasks.RootTask); - this.treeView.Items.Add(nodeParent); - AddChildNodes(Tasks.RootTask, nodeParent); - nodeParent.Expand(); - } - - internal void AddNewTask() + internal void AddNewTask() { int newId; try { - if(treeView.SelectedItems.Count<=0) - { - MessageBox.Show("Please select a task.", this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - int parentId = (int) treeView.SelectedItems[0].Tag; + int parentId = (int) treeView.SelectedNode.Tag; string newTaskName = GetNewTaskName(parentId); newId = Tasks.AddTask(newTaskName, parentId).Id; } @@ -218,10 +175,10 @@ internal void AddNewTask() return; } Application.DoEvents();//first insert the new node (event fired) - TreeListViewItem node = FindTaskNode(newId); - node.EnsureVisible(); - node.Selected = true; - treeView.Refresh(); + treeView.LabelEdit = true; + TreeNode node = FindTaskNode(newId); + node.EnsureVisible(); + treeView.SelectedNode = node; node.BeginEdit(); } @@ -242,68 +199,113 @@ private static string GetNewTaskName(int parentId) internal void EditSelectedTaskDescription() { - if (treeView.SelectedItems.Count <= 0) - { - MessageBox.Show("Please select a task.", this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - treeView.SelectedItems[0].BeginEdit(); + treeView.LabelEdit = true; + treeView.SelectedNode.BeginEdit(); } - void treeView_BeforeLabelEdit(object sender, TreeListViewBeforeLabelEditEventArgs e) - { - if(this.priorityColumnHeader == this.treeView.Columns[e.ColumnIndex]) - { - ComboBox cbx = new ComboBox(); - cbx.Items.AddRange(new object[]{"(null)",1,2,3,4,5,6,7,8,9}); - e.Editor = cbx; - cbx.Text = e.Label; - } - } - - private void treeView_AfterLabelEdit(object sender, TreeListViewLabelEditEventArgs e) + internal void DeleteSelectedTask() { - Task task = Tasks.FindById(Convert.ToInt32(e.Item.Tag)); - - if (task != null) + if (MessageBox.Show( + "All tasks and sub-tasks assigned to this task will be deleted too. \nAre you sure you want to delete '" + + this.treeView.SelectedNode.Text + "'?", + this.ParentForm.Text, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2, + MessageBoxOptions.DefaultDesktopOnly) + == DialogResult.OK) { - if(this.treeView.Columns[e.ColumnIndex] == this.tasksColumnHeader) - { - if (e.Label == null || e.Label == String.Empty) - { - e.Cancel = true; - return; - } - task.Description = e.Label; - } - if (this.priorityColumnHeader == this.treeView.Columns[e.ColumnIndex]) - { - int priority; - if (e.Label == null || e.Label == String.Empty || e.Label == "(null)") - { - task.Priority = 0; - } - else if (int.TryParse(e.Label, out priority) && priority >= 0 && priority<=9) - { - task.Priority = priority; - } - else - { - e.Cancel = true; - return; - } - } try { - Tasks.UpdateTask(task); + Cursor.Current = Cursors.WaitCursor; + Tasks.DeleteTask((int) treeView.SelectedNode.Tag); } catch (ApplicationException aex) { + Cursor.Current = Cursors.Default; MessageBox.Show(aex.Message, this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } - finally + finally { - e.Cancel = true; //always cancel, the event Tasks.TaskChanged will change the value. + Cursor.Current = Cursors.Default; + } + } + } + + + private void LoadTree() + { + treeView.Nodes.Clear(); + TreeNode nodeParent = CreateNode(Tasks.RootTask); + this.treeView.Nodes.Add(nodeParent); + AddChildNodes(Tasks.RootTask, nodeParent); + } + + private void AddChildNodes(Task parentRow, TreeNode nodeParent) + { + Task[] childsRows = Tasks.GetChildTasks(parentRow.Id); + foreach (Task row in childsRows) + { + if (row.Id == Tasks.IdleTask.Id) + continue; + TreeNode nodeChild = CreateNode(row); + nodeParent.Nodes.Add(nodeChild); + AddChildNodes(row, nodeChild); + } + } + + private TreeNode CreateNode(Task row) + { + TreeNode node = new TreeNode(row.Description, this.treeView.ImageIndex, this.treeView.SelectedImageIndex); + node.Tag = row.Id; + return node; + } + + private TreeNode FindTaskNode(int taskId) + { + return FindNode(taskId, this.treeView.Nodes); + } + + private TreeNode FindNode(int taskId, TreeNodeCollection nodes) + { + foreach (TreeNode node in nodes) + { + if ((int) node.Tag == taskId) + { + return node; + } + else + { + if (node.Nodes.Count > 0) + { + TreeNode childnode = FindNode(taskId, node.Nodes); + if (childnode != null) + return childnode; + } + } + } + return null; + } + + + private void treeView_AfterLabelEdit(object sender, NodeLabelEditEventArgs e) + { + treeView.LabelEdit = false; + Task row = Tasks.FindById(Convert.ToInt32(e.Node.Tag)); + if (row != null) + { + if (e.Label == null || e.Label == String.Empty) + { + e.CancelEdit = true; + return; + } + + row.Description = e.Label; + try + { + Tasks.UpdateTask(row); + } + catch (ApplicationException aex) + { + e.CancelEdit = true; + MessageBox.Show(aex.Message, this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } } else @@ -312,88 +314,29 @@ private void treeView_AfterLabelEdit(object sender, TreeListViewLabelEditEventAr MessageBoxIcon.Information); } } - - internal void DeleteSelectedTask() - { - if (treeView.SelectedItems.Count <= 0) - { - MessageBox.Show("Please select a task.", this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } - if (MessageBox.Show( - "All tasks and sub-tasks assigned to this task will be deleted too. \nAre you sure you want to delete '" + - this.treeView.SelectedItems[0].Text + "'?", - this.ParentForm.Text, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2, - MessageBoxOptions.DefaultDesktopOnly) - == DialogResult.OK) - { - try - { - Cursor.Current = Cursors.WaitCursor; - Tasks.DeleteTask((int)treeView.SelectedItems[0].Tag); - } - catch (ApplicationException aex) - { - Cursor.Current = Cursors.Default; - MessageBox.Show(aex.Message, this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); - } - finally - { - Cursor.Current = Cursors.Default; - } - } - } - private void AddChildNodes(Task parentRow, TreeListViewItem nodeParent) - { - Task[] childTasks = Tasks.GetChildTasks(parentRow.Id); - foreach (Task task in childTasks) - { - if (task.Id == Tasks.IdleTask.Id) - continue; - if(task.Hidden && !this.showHidden) - continue; - TreeListViewItem nodeChild = CreateNode(task); - nodeParent.Items.Add(nodeChild); - AddChildNodes(task, nodeChild); - } - } - - private static TreeListViewItem CreateNode(Task task) - { - TreeListViewItem node; // = new TreeListViewItem(task.Description, task.IconId); - string priority = task.Priority > 0 ? task.Priority.ToString() : String.Empty; - node = new TreeListViewItem(task.Description, new string[] { priority }); - node.ImageIndex = task.IconId; - node.Tag = task.Id; - return node; - } - - private TreeListViewItem FindTaskNode(int taskId) - { - return FindNode(taskId, this.treeView.Items); - } + private void Tasks_TasksRowChanged(Tasks.TaskChangeEventArgs e) + { + if (e.Action == DataRowAction.Add) + { + TreeNode nodeParent = FindTaskNode(e.Task.ParentId); + TreeNode nodeChild = CreateNode(e.Task); + nodeParent.Nodes.Add(nodeChild); + return; + } + else if (e.Action == DataRowAction.Change) + { + TreeNode node = FindTaskNode(e.Task.Id); + node.Text = e.Task.Description; + } + } - private TreeListViewItem FindNode(int taskId, TreeListViewItemCollection nodes) - { - foreach (TreeListViewItem node in nodes) - { - if ((int)node.Tag == taskId) - { - return node; - } - else - { - if (node.Items.Count > 0) - { - TreeListViewItem childnode = FindNode(taskId, node.Items); - if (childnode != null) - return childnode; - } - } - } - return null; - } + private void Tasks_TasksRowDeleting(Tasks.TaskChangeEventArgs e) + { + TreeNode node = FindTaskNode(e.Task.Id); + if (node != null && node.TreeView != null) + node.Remove(); + } internal int SelectedTaskId @@ -403,13 +346,12 @@ internal int SelectedTaskId { if (currentSelectedTask == value) return; - TreeListViewItem node; + TreeNode node; node = FindTaskNode(value); if (node == null) return; currentSelectedTask = value; - node.Selected = true; - + treeView.SelectedNode = node; if (this.SelectedTaskChanged != null) { this.SelectedTaskChanged(this, new EventArgs()); @@ -417,289 +359,267 @@ internal int SelectedTaskId } } - void treeView_SelectedIndexChanged(object sender, EventArgs e) - { - if(this.treeView.SelectedItems.Count<=0) - { - return; - } - if (currentSelectedTask != (int)this.treeView.SelectedItems[0].Tag) - { - currentSelectedTask = (int)this.treeView.SelectedItems[0].Tag; - if (this.SelectedTaskChanged != null) - { - this.SelectedTaskChanged(sender, e); - } - } - } + private void treeView_AfterSelect(object sender, TreeViewEventArgs e) + { + if (currentSelectedTask != (int) e.Node.Tag) + { + currentSelectedTask = (int) e.Node.Tag; + if (this.SelectedTaskChanged != null) + { + this.SelectedTaskChanged(sender, e); + } + } + } - #region Drag And Drop + #region Drag And Drop - private Timer timer = new Timer(); - private ImageList imageListDrag = new ImageList(); - private TreeListViewItem dragNode = null; - private TreeListViewItem tempDropNode = null; + private Timer timer = new Timer(); + private ImageList imageListDrag = new ImageList(); + private TreeNode dragNode = null; + private TreeNode tempDropNode = null; - [DllImport("comctl32.dll")] - internal static extern bool InitCommonControls(); + [DllImport("comctl32.dll")] + internal static extern bool InitCommonControls(); - [DllImport("comctl32.dll", CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ImageList_BeginDrag(IntPtr himlTrack, int iTrack, int dxHotspot, int dyHotspot); + [DllImport("comctl32.dll", CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImageList_BeginDrag(IntPtr himlTrack, int iTrack, int dxHotspot, int dyHotspot); - [DllImport("comctl32.dll", CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ImageList_DragMove(int x, int y); + [DllImport("comctl32.dll", CharSet=CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImageList_DragMove(int x, int y); - [DllImport("comctl32.dll", CharSet = CharSet.Auto)] - internal static extern void ImageList_EndDrag(); + [DllImport("comctl32.dll", CharSet=CharSet.Auto)] + internal static extern void ImageList_EndDrag(); - [DllImport("comctl32.dll", CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ImageList_DragEnter(IntPtr hwndLock, int x, int y); + [DllImport("comctl32.dll", CharSet=CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImageList_DragEnter(IntPtr hwndLock, int x, int y); - [DllImport("comctl32.dll", CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ImageList_DragLeave(IntPtr hwndLock); + [DllImport("comctl32.dll", CharSet=CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImageList_DragLeave(IntPtr hwndLock); - [DllImport("comctl32.dll", CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool ImageList_DragShowNolock([MarshalAs(UnmanagedType.Bool)]bool fShow); + [DllImport("comctl32.dll", CharSet=CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImageList_DragShowNolock([MarshalAs(UnmanagedType.Bool)]bool fShow); - private const int TREE_VIEW_INDENT = 19; + private void treeView_ItemDrag(object sender, ItemDragEventArgs e) + { + // Get drag node and select it + this.dragNode = (TreeNode) e.Item; + this.treeView.SelectedNode = this.dragNode; - private void treeView_ItemDrag(object sender, ItemDragEventArgs e) - { - // Get drag node and select it - this.dragNode = (TreeListViewItem)e.Item; - if((int)dragNode.Tag == Tasks.RootTask.Id) - return; - // Reset image list used for drag image - this.imageListDrag.Images.Clear(); - this.imageListDrag.ImageSize = - new Size(Math.Min(this.dragNode.Bounds.Size.Width + TREE_VIEW_INDENT, 256), this.dragNode.Bounds.Height); + // Reset image list used for drag image + this.imageListDrag.Images.Clear(); + this.imageListDrag.ImageSize = + new Size(Math.Min(this.dragNode.Bounds.Size.Width + this.treeView.Indent, 256), this.dragNode.Bounds.Height); - // Create new bitmap - // This bitmap will contain the tree node image to be dragged - Bitmap bmp = new Bitmap(Math.Min(this.dragNode.Bounds.Width + TREE_VIEW_INDENT, 256), this.dragNode.Bounds.Height); + // Create new bitmap + // This bitmap will contain the tree node image to be dragged + Bitmap bmp = new Bitmap(Math.Min(this.dragNode.Bounds.Width + this.treeView.Indent, 256), this.dragNode.Bounds.Height); - // Get graphics from bitmap - using (Graphics gfx = Graphics.FromImage(bmp)) - { - // Draw node icon into the bitmap - gfx.DrawImage(IconsManager.IconsList.Images[dragNode.ImageIndex], 0, 0); - - // Draw node label into bitmap - gfx.DrawString(this.dragNode.Text, - this.treeView.Font, - new SolidBrush(this.treeView.ForeColor), - TREE_VIEW_INDENT, 1.0f); - } - - // Add bitmap to imagelist - this.imageListDrag.Images.Add(bmp); - - // Get mouse position in client coordinates - Point p = this.treeView.PointToClient(MousePosition); - // Compute delta between mouse position and node bounds - // int dx = p.X + this.treeView.Indent - this.dragNode.Bounds.Left; - // int dy = p.Y - this.dragNode.Bounds.Top; - - int dx = p.X - this.dragNode.GetBounds(ItemBoundsPortion.Label).Left - this.treeView.Location.X; - int dy = p.Y - this.dragNode.Bounds.Top - this.treeView.Location.Y; - - // Begin dragging image - if (ImageList_BeginDrag(this.imageListDrag.Handle, 0, dx, dy)) - { - // Begin dragging - this.treeView.DoDragDrop(bmp, DragDropEffects.Move); - // End dragging image - ImageList_EndDrag(); - } - } + // Get graphics from bitmap + using (Graphics gfx = Graphics.FromImage(bmp)) + { + // Draw node icon into the bitmap + gfx.DrawImage(this.groupsImageList.Images[0], 0, 0); + + // Draw node label into bitmap + gfx.DrawString(this.dragNode.Text, + this.treeView.Font, + new SolidBrush(this.treeView.ForeColor), + this.treeView.Indent, 1.0f); + } - private void treeView_DragOver(object sender, DragEventArgs e) - { - // Compute drag position and move image - Point formP = this.PointToClient(new Point(e.X, e.Y)); - ImageList_DragMove(formP.X - this.treeView.Left, formP.Y - this.treeView.Top); - - // Get actual drop node - TreeListViewItem dropNode = this.treeView.GetItemAt(this.treeView.PointToClient(new Point(e.X, e.Y))); - if (dropNode == null) - { - e.Effect = DragDropEffects.None; - return; - } + // Add bitmap to imagelist + this.imageListDrag.Images.Add(bmp); - e.Effect = DragDropEffects.Move; + // Get mouse position in client coordinates + Point p = this.treeView.PointToClient(MousePosition); - // if mouse is on a new node select it - if (this.tempDropNode != dropNode) - { - ImageList_DragShowNolock(false); + // Compute delta between mouse position and node bounds + // int dx = p.X + this.treeView.Indent - this.dragNode.Bounds.Left; + // int dy = p.Y - this.dragNode.Bounds.Top; + int dx = p.X + this.treeView.Indent - this.dragNode.Bounds.Left - this.treeView.Location.X; + int dy = p.Y - this.dragNode.Bounds.Top - this.treeView.Location.Y; - if (tempDropNode != null) - { - this.treeView.Refresh(); - } - dropNode.DrawInsertionLine(); + // Begin dragging image + if (ImageList_BeginDrag(this.imageListDrag.Handle, 0, dx, dy)) + { + // Begin dragging + this.treeView.DoDragDrop(bmp, DragDropEffects.Move); + // End dragging image + ImageList_EndDrag(); + } + } - ImageList_DragShowNolock(true); - tempDropNode = dropNode; - } + private void treeView_DragOver(object sender, DragEventArgs e) + { + // Compute drag position and move image + Point formP = this.PointToClient(new Point(e.X, e.Y)); + ImageList_DragMove(formP.X - this.treeView.Left, formP.Y - this.treeView.Top); - // Avoid that drop node is child of drag node - TreeListViewItem tmpNode = dropNode; - while (tmpNode.Parent != null) - { - if (tmpNode.Parent == this.dragNode) e.Effect = DragDropEffects.None; - tmpNode = tmpNode.Parent; - } - } + // Get actual drop node + TreeNode dropNode = this.treeView.GetNodeAt(this.treeView.PointToClient(new Point(e.X, e.Y))); + if (dropNode == null) + { + e.Effect = DragDropEffects.None; + return; + } - private void treeView_DragDrop(object sender, DragEventArgs e) - { - // Unlock updates - ImageList_DragLeave(this.treeView.Handle); + e.Effect = DragDropEffects.Move; - // Get drop node - TreeListViewItem dropNode = this.treeView.GetItemAt(this.treeView.PointToClient(new Point(e.X, e.Y))); + // if mouse is on a new node select it + if (this.tempDropNode != dropNode) + { + ImageList_DragShowNolock(false); + this.treeView.SelectedNode = dropNode; + ImageList_DragShowNolock(true); + tempDropNode = dropNode; + } - // If drop node isn't equal to drag node, add drag node as child of drop node - if (this.dragNode != dropNode) - { - // Remove drag node from parent - if (this.dragNode.Parent == null) - { - this.treeView.Items.Remove(this.dragNode); - } - else - { - this.dragNode.Parent.Items.Remove(this.dragNode); - } - - // Add drag node to drop node - dropNode.Items.Add(this.dragNode); - dropNode.Expand(); - - try - { - Cursor.Current = Cursors.WaitCursor; - Tasks.UpdateParentTask((int)this.dragNode.Tag, (int)dropNode.Tag); - } - catch (ApplicationException aex) - { - Cursor.Current = Cursors.Default; - MessageBox.Show(aex.Message, this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); - } - finally - { - Cursor.Current = Cursors.Default; - } - - // Set drag node to null - this.dragNode = null; - - // Disable scroll timer - this.timer.Enabled = false; - } - this.treeView.Refresh(); - } + // Avoid that drop node is child of drag node + TreeNode tmpNode = dropNode; + while (tmpNode.Parent != null) + { + if (tmpNode.Parent == this.dragNode) e.Effect = DragDropEffects.None; + tmpNode = tmpNode.Parent; + } + } - private void treeView_DragEnter(object sender, DragEventArgs e) - { - ImageList_DragEnter(this.treeView.Handle, e.X - this.treeView.Left, - e.Y - this.treeView.Top); + private void treeView_DragDrop(object sender, DragEventArgs e) + { + // Unlock updates + ImageList_DragLeave(this.treeView.Handle); - // Enable timer for scrolling dragged item - this.timer.Enabled = true; - } + // Get drop node + TreeNode dropNode = this.treeView.GetNodeAt(this.treeView.PointToClient(new Point(e.X, e.Y))); - private void treeView_DragLeave(object sender, EventArgs e) - { - ImageList_DragLeave(this.treeView.Handle); - this.treeView.Refresh(); - // Disable timer for scrolling dragged item - this.timer.Enabled = false; - } + // If drop node isn't equal to drag node, add drag node as child of drop node + if (this.dragNode != dropNode) + { + // Remove drag node from parent + if (this.dragNode.Parent == null) + { + this.treeView.Nodes.Remove(this.dragNode); + } + else + { + this.dragNode.Parent.Nodes.Remove(this.dragNode); + } - private void treeView_GiveFeedback(object sender, GiveFeedbackEventArgs e) - { - if (e.Effect == DragDropEffects.Move) - { - // Show pointer cursor while dragging - e.UseDefaultCursors = false; - this.treeView.Cursor = Cursors.Default; - } - else e.UseDefaultCursors = true; - } + // Add drag node to drop node + dropNode.Nodes.Add(this.dragNode); + dropNode.Expand(); - private void timer_Tick(object sender, EventArgs e) - { - // get node at mouse position - Point pt = treeView.PointToClient(MousePosition); - TreeListViewItem node = this.treeView.GetItemAt(pt); - if (node == null) return; + try + { + Cursor.Current = Cursors.WaitCursor; + Tasks.UpdateParentTask((int) this.dragNode.Tag, (int) dropNode.Tag); + } + catch (ApplicationException aex) + { + Cursor.Current = Cursors.Default; + MessageBox.Show(aex.Message, this.ParentForm.Text, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + finally + { + Cursor.Current = Cursors.Default; + } - // if mouse is near to the top, scroll up - if (pt.Y < 30) - { - // set actual node to the upper one - if (node.PrevVisibleItem != null) - { - node = node.PrevVisibleItem; - - // hide drag image - ImageList_DragShowNolock(false); - // scroll and refresh - node.EnsureVisible(); - this.treeView.Refresh(); - // show drag image - ImageList_DragShowNolock(true); - } - } - // if mouse is near to the bottom, scroll down - else if (pt.Y > this.treeView.Size.Height - 30) - { - if (node.NextVisibleItem != null) - { - node = node.NextVisibleItem; - - ImageList_DragShowNolock(false); - node.EnsureVisible(); - this.treeView.Refresh(); - ImageList_DragShowNolock(true); - } - } - } + // Set drag node to null + this.dragNode = null; - #endregion + // Disable scroll timer + this.timer.Enabled = false; + } + } - private void mnuAdd_Click(object sender, EventArgs e) - { - AddNewTask(); - } + private void treeView_DragEnter(object sender, DragEventArgs e) + { + ImageList_DragEnter(this.treeView.Handle, e.X - this.treeView.Left, + e.Y - this.treeView.Top); - private void mnuRename_Click(object sender, EventArgs e) - { - this.EditSelectedTaskDescription(); - } + // Enable timer for scrolling dragged item + this.timer.Enabled = true; + } + + private void treeView_DragLeave(object sender, EventArgs e) + { + ImageList_DragLeave(this.treeView.Handle); + + // Disable timer for scrolling dragged item + this.timer.Enabled = false; + } + + private void treeView_GiveFeedback(object sender, GiveFeedbackEventArgs e) + { + if (e.Effect == DragDropEffects.Move) + { + // Show pointer cursor while dragging + e.UseDefaultCursors = false; + this.treeView.Cursor = Cursors.Default; + } + else e.UseDefaultCursors = true; + } + + private void timer_Tick(object sender, EventArgs e) + { + // get node at mouse position + Point pt = treeView.PointToClient(MousePosition); + TreeNode node = this.treeView.GetNodeAt(pt); + if(node == null) return; + + // if mouse is near to the top, scroll up + if (pt.Y < 30) + { + // set actual node to the upper one + if (node.PrevVisibleNode != null) + { + node = node.PrevVisibleNode; + + // hide drag image + ImageList_DragShowNolock(false); + // scroll and refresh + node.EnsureVisible(); + this.treeView.Refresh(); + // show drag image + ImageList_DragShowNolock(true); + } + } + // if mouse is near to the bottom, scroll down + else if (pt.Y > this.treeView.Size.Height - 30) + { + if (node.NextVisibleNode != null) + { + node = node.NextVisibleNode; + + ImageList_DragShowNolock(false); + node.EnsureVisible(); + this.treeView.Refresh(); + ImageList_DragShowNolock(true); + } + } + } + + #endregion private void mnuDelete_Click(object sender, EventArgs e) { this.DeleteSelectedTask(); } + private void mnuRename_Click(object sender, EventArgs e) + { + this.EditSelectedTaskDescription(); + } + public void ShowPropertiesSelectedTask() { - if(treeView.SelectedItems.Count==0) - return; - if(Tasks.RootTask.Id == (int) treeView.SelectedItems[0].Tag) - return; - TaskPropertiesForm pf; - pf = new TaskPropertiesForm((int) treeView.SelectedItems[0].Tag); + TaskPropertiesForm pf; + pf = new TaskPropertiesForm((int) treeView.SelectedNode.Tag); pf.ShowDialog(this); } @@ -711,39 +631,6 @@ private void mnuProperties_Click(object sender, EventArgs e) private void treeView_DoubleClick(object sender, EventArgs e) { this.OnDoubleClick(e); - } - - - #region Framework Events - - private void Tasks_TasksRowChanged(Tasks.TaskChangeEventArgs e) - { - if (e.Action == DataRowAction.Add) - { - TreeListViewItem nodeParent = FindTaskNode(e.Task.ParentId); - TreeListViewItem nodeChild = CreateNode(e.Task); - nodeParent.Items.Add(nodeChild); - return; - } - else if (e.Action == DataRowAction.Change) - { - TreeListViewItem node = FindTaskNode(e.Task.Id); - node.Text = e.Task.Description; - node.ImageIndex = e.Task.IconId; - string priority = e.Task.Priority == 0 ? String.Empty : e.Task.Priority.ToString(); - node.SubItems[priorityColumnHeader.Index].Text = priority; - } - } - - private void Tasks_TasksRowDeleting(Tasks.TaskChangeEventArgs e) - { - TreeListViewItem node = FindTaskNode(e.Task.Id); - if (node != null && node.ListView != null) - node.Remove(); - } - #endregion - - - - } + } + } } \ No newline at end of file diff --git a/code/PTM/View/Controls/TasksTreeViewControl.resx b/code/PTM/View/Controls/TasksTreeViewControl.resx index a6fc672..b6dcbed 100644 --- a/code/PTM/View/Controls/TasksTreeViewControl.resx +++ b/code/PTM/View/Controls/TasksTreeViewControl.resx @@ -3,7 +3,7 @@ - - - - - - - - - - - - - - - - - - - + - @@ -109,15 +89,139 @@ text/microsoft-resx - 2.0 + 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + Private + + + Private + + + False + + + Private + + 17, 17 - + + + Private + + + Private + + + Private + + + Private + + + Private + + + Private + + + Private + + + Private + + + Private + + + Private + + + Private + + + 115, 17 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFpTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj0xLjAuNTAw + MC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZT + eXN0ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMA + AADqCAAAAk1TRnQBSQFMAgEBAgEAAQQBAAEEAQABEAEAARABAAT/AQkBEAj/AUIBTQE2AQQGAAE2AQQC + AAEoAwABQAMAARADAAEBAQABCAYAAQQYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB + 3AHAAQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IB + AAM5AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8B + MwMAAWYDAAGZAwABzAIAATMDAAIzAgABMwFmAgABMwGZAgABMwHMAgABMwH/AgABZgMAAWYBMwIAAmYC + AAFmAZkCAAFmAcwCAAFmAf8CAAGZAwABmQEzAgABmQFmAgACmQIAAZkBzAIAAZkB/wIAAcwDAAHMATMC + AAHMAWYCAAHMAZkCAALMAgABzAH/AgAB/wFmAgAB/wGZAgAB/wHMAQABMwH/AgAB/wEAATMBAAEzAQAB + ZgEAATMBAAGZAQABMwEAAcwBAAEzAQAB/wEAAf8BMwIAAzMBAAIzAWYBAAIzAZkBAAIzAcwBAAIzAf8B + AAEzAWYCAAEzAWYBMwEAATMCZgEAATMBZgGZAQABMwFmAcwBAAEzAWYB/wEAATMBmQIAATMBmQEzAQAB + MwGZAWYBAAEzApkBAAEzAZkBzAEAATMBmQH/AQABMwHMAgABMwHMATMBAAEzAcwBZgEAATMBzAGZAQAB + MwLMAQABMwHMAf8BAAEzAf8BMwEAATMB/wFmAQABMwH/AZkBAAEzAf8BzAEAATMC/wEAAWYDAAFmAQAB + MwEAAWYBAAFmAQABZgEAAZkBAAFmAQABzAEAAWYBAAH/AQABZgEzAgABZgIzAQABZgEzAWYBAAFmATMB + mQEAAWYBMwHMAQABZgEzAf8BAAJmAgACZgEzAQADZgEAAmYBmQEAAmYBzAEAAWYBmQIAAWYBmQEzAQAB + ZgGZAWYBAAFmApkBAAFmAZkBzAEAAWYBmQH/AQABZgHMAgABZgHMATMBAAFmAcwBmQEAAWYCzAEAAWYB + zAH/AQABZgH/AgABZgH/ATMBAAFmAf8BmQEAAWYB/wHMAQABzAEAAf8BAAH/AQABzAEAApkCAAGZATMB + mQEAAZkBAAGZAQABmQEAAcwBAAGZAwABmQIzAQABmQEAAWYBAAGZATMBzAEAAZkBAAH/AQABmQFmAgAB + mQFmATMBAAGZATMBZgEAAZkBZgGZAQABmQFmAcwBAAGZATMB/wEAApkBMwEAApkBZgEAA5kBAAKZAcwB + AAKZAf8BAAGZAcwCAAGZAcwBMwEAAWYBzAFmAQABmQHMAZkBAAGZAswBAAGZAcwB/wEAAZkB/wIAAZkB + /wEzAQABmQHMAWYBAAGZAf8BmQEAAZkB/wHMAQABmQL/AQABzAMAAZkBAAEzAQABzAEAAWYBAAHMAQAB + mQEAAcwBAAHMAQABmQEzAgABzAIzAQABzAEzAWYBAAHMATMBmQEAAcwBMwHMAQABzAEzAf8BAAHMAWYC + AAHMAWYBMwEAAZkCZgEAAcwBZgGZAQABzAFmAcwBAAGZAWYB/wEAAcwBmQIAAcwBmQEzAQABzAGZAWYB + AAHMApkBAAHMAZkBzAEAAcwBmQH/AQACzAIAAswBMwEAAswBZgEAAswBmQEAA8wBAALMAf8BAAHMAf8C + AAHMAf8BMwEAAZkB/wFmAQABzAH/AZkBAAHMAf8BzAEAAcwC/wEAAcwBAAEzAQAB/wEAAWYBAAH/AQAB + mQEAAcwBMwIAAf8CMwEAAf8BMwFmAQAB/wEzAZkBAAH/ATMBzAEAAf8BMwH/AQAB/wFmAgAB/wFmATMB + AAHMAmYBAAH/AWYBmQEAAf8BZgHMAQABzAFmAf8BAAH/AZkCAAH/AZkBMwEAAf8BmQFmAQAB/wKZAQAB + /wGZAcwBAAH/AZkB/wEAAf8BzAIAAf8BzAEzAQAB/wHMAWYBAAH/AcwBmQEAAf8CzAEAAf8BzAH/AQAC + /wEzAQABzAH/AWYBAAL/AZkBAAL/AcwBAAJmAf8BAAFmAf8BZgEAAWYC/wEAAf8CZgEAAf8BZgH/AQAC + /wFmAQABIQEAAaUBAANfAQADdwEAA4YBAAOWAQADywEAA7IBAAPXAQAD3QEAA+MBAAPqAQAD8QEAA/gB + AAHwAfsB/wEAAaQCoAEAA4ADAAH/AgAB/wMAAv8BAAH/AwAB/wEAAf8BAAL/AgAD/0QAAf8/AAH7AQAB + +z0AAf8BAAH/AQcB+w4AAf8B+ysAAfsBAAH7AQcB/wEHAfsKAAH/AQAB+wH/AfsB/ykAAf8BAAL/AfsB + BwH/AQcB+wcAAfsCAAH/AfsB/wH7Af8B+ycAAfsBAAH7Av8BBwH7AQcB/wYAAf8BAAH/AQAB+wH/AfsB + /wH7Af8B+wH/JQAB/wEAAv8B+wEHAf8BBwH7BQAB+wEAAv8BAAH/AfsB/wH7Af8B+wH/AfsB/yQAAfsB + AAH7A/8B+wEHAf8GAAP/AgAB/wH7Af8B+wH/AfsB/wH7JAAB/wEAAv8B+wL/AQcB+wgAA/8CAAH7Af8B + +wH/AfsB/yQAAfsB/wIAAv8B+wEHAf8GAAH7Af8CAAL/AQAB/wH7Af8B+wH/AfslAAH7Af8B+wIAAf8B + BwH7BwAB+wH/AfsBAAL/AgAB+wH/AfsB/ycAAf8B+wH/AgAB/wkAAf8B+wEAAv8DAAH/AfsnAAH7Af8B + +wH/AfsPAAH/AfsrAAH7Af93AAFCAU0BPgcAAT4DAAEoAwABQAMAARADAAEBAQABAQUAAYAXAAP/AQAB + 5wP/BAABwwP/BAABwAH/AfgB/wQAAcABPwHwAT8EAAHAAQ8B4AEPBAABwAEHAcABAwQAAcABBwGABQAB + wAEHBgABwAEHBgABwAEHAYAFAAHAAQcBgAUAAcABBwHABQAB8AEHAeAFAAH4AQcB+QUAAfgBBwH/Ac8E + AAH+AX8C/xoACw== + + + + False + + + False + + + True + + + True + + + 80 + + + (Default) + + + False + + + Private + + + TasksTreeViewControl + + + 8, 8 + \ No newline at end of file diff --git a/code/PTM/View/Controls/TreeListViewComponents/TreeListView.cs b/code/PTM/View/Controls/TreeListViewComponents/TreeListView.cs index afa9e95..246200f 100644 --- a/code/PTM/View/Controls/TreeListViewComponents/TreeListView.cs +++ b/code/PTM/View/Controls/TreeListViewComponents/TreeListView.cs @@ -118,46 +118,6 @@ protected virtual void OnAfterCollapse(TreeListViewEventArgs e) { if(AfterCollapse != null) AfterCollapse(this, e); } - - private bool _dragging = false; - protected override void OnItemDrag(ItemDragEventArgs e) - { - if(InEdit) return; - if(_skipMouseDownEvent) return; - _dragging = true; - base.OnItemDrag(e); - } - - protected override void OnDragOver(DragEventArgs drgevent) - { - if (InEdit) return; - base.OnDragOver(drgevent); - } - - protected override void OnDragDrop(DragEventArgs drgevent) - { - _dragging = false; - base.OnDragDrop(drgevent); - } - - protected override void OnDragEnter(DragEventArgs drgevent) - { - _dragging = true; - base.OnDragEnter(drgevent); - } - - protected override void OnDragLeave(EventArgs e) - { - _dragging = false; - base.OnDragLeave(e); - } - - protected override void OnMouseUp(MouseEventArgs e) - { - _dragging = false; - base.OnMouseUp(e); - } - #endregion #region Internal calls internal void RaiseBeforeExpand(TreeListViewCancelEventArgs e) @@ -703,8 +663,6 @@ protected override void WndProc(ref Message m) cancel = true; } #endregion - if (_dragging) - cancel=true; if(cancel) { m.Result = (IntPtr)1; @@ -1303,10 +1261,11 @@ internal void ExitEdit(bool Cancel, string Text) _skipMouseDownEvent = false; if(!Cancel) { - TreeListViewLabelEditEventArgs e = new TreeListViewLabelEditEventArgs(editedItem.Item, editedItem.ColumnIndex, Text); + TreeListViewLabelEditEventArgs e = new TreeListViewLabelEditEventArgs(EditedItem.Item, EditedItem.ColumnIndex, Text); OnAfterLabelEdit(e); if(!e.Cancel) - editedItem.Item.SubItems[editedItem.ColumnIndex].Text = Text; + editedItem.Item.SubItems[ + editedItem.ColumnIndex].Text = Text; } _inedit = false; _editeditem = new EditItemInformations(null, 0, ""); @@ -2116,4 +2075,4 @@ public enum CheckBoxesTypes Recursive, } #endregion -} +} \ No newline at end of file diff --git a/code/PTM/View/Controls/TreeListViewComponents/TreeListViewItem.cs b/code/PTM/View/Controls/TreeListViewComponents/TreeListViewItem.cs index 6cbf23b..61a7976 100644 --- a/code/PTM/View/Controls/TreeListViewComponents/TreeListViewItem.cs +++ b/code/PTM/View/Controls/TreeListViewComponents/TreeListViewItem.cs @@ -1016,34 +1016,6 @@ internal void DrawFocusCues() } g.Dispose(); } - - public void DrawInsertionLine() - { - Graphics g = Graphics.FromHwnd(TreeListView.Handle); - if (Visible) - { - Rectangle entireitemrect = GetBounds(ItemBoundsPortion.Entire); - int X1 = entireitemrect.Left; - int X2 = entireitemrect.Right; - int Y = entireitemrect.Bottom; - g.DrawLine(Pens.Red, X1, Y, X2 - 1, Y); - - Point[] leftTriangle = new Point[3] { - new Point(X1, Y-4), - new Point(X1 + 7, Y), - new Point(X1, Y+4) - }; - Point[] rightTriangle = new Point[3] { - new Point(X2, Y-4), - new Point(X2 - 8, Y), - new Point(X2, Y+4) - }; - g.FillPolygon(Brushes.Red, leftTriangle); - g.FillPolygon(Brushes.Red, rightTriangle); - } - - } - #endregion #region DrawIntermediateState internal void DrawIntermediateState() diff --git a/code/PTM/View/Controls/TreeListViewComponents/TreeListViewSubItemEdit.cs b/code/PTM/View/Controls/TreeListViewComponents/TreeListViewSubItemEdit.cs index cf4493b..9f73d60 100644 --- a/code/PTM/View/Controls/TreeListViewComponents/TreeListViewSubItemEdit.cs +++ b/code/PTM/View/Controls/TreeListViewComponents/TreeListViewSubItemEdit.cs @@ -139,8 +139,7 @@ protected override void WndProc(ref Message m) case (int) APIsEnums.WindowMessages.KILLFOCUS: if(OnKillFocus(m)) { - //EndEdit(!(_control is ComboBox && _treelistview.EditedItem.Label != _control.Text)); - EndEdit(_treelistview.EditedItem.Label == _control.Text); + EndEdit(!(_control is ComboBox && _treelistview.EditedItem.Label != _control.Text)); return; } break; diff --git a/code/PTM/View/Forms/TaskSelectForm.cs b/code/PTM/View/Forms/TaskLogForm.cs similarity index 80% rename from code/PTM/View/Forms/TaskSelectForm.cs rename to code/PTM/View/Forms/TaskLogForm.cs index 07b49b1..54c53aa 100644 --- a/code/PTM/View/Forms/TaskSelectForm.cs +++ b/code/PTM/View/Forms/TaskLogForm.cs @@ -10,7 +10,7 @@ namespace PTM.View.Forms /// /// Summary description for TaskNotificationForm. /// - internal class TaskSelectForm : Form + internal class TaskLogForm : Form { private Button okButton; private Button cancelButton; @@ -20,14 +20,13 @@ internal class TaskSelectForm : Form private Button newButton; private GroupBox groupBox1; private Button propertiesButton; - private CheckBox showHiddenCheckBox; /// /// Required designer variable. /// private Container components = null; - internal TaskSelectForm() + internal TaskLogForm() { InitializeComponent(); @@ -43,10 +42,9 @@ internal TaskSelectForm() tasksTree.SelectedTaskId = Tasks.RootTask.Id; } tasksTree.DoubleClick += new EventHandler(tasksTree_DoubleClick); - this.showHiddenCheckBox.Checked = tasksTree.ShowHidden; } - internal TaskSelectForm(int editTaskId) + internal TaskLogForm(int editTaskId) { InitializeComponent(); tasksTree.Initialize(); @@ -56,7 +54,7 @@ internal TaskSelectForm(int editTaskId) task = Tasks.FindById(editTaskId); this.tasksTree.SelectedTaskId = task.Id; - this.showHiddenCheckBox.Checked = tasksTree.ShowHidden; + //SetChildTask(row); } @@ -83,7 +81,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TaskSelectForm)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TaskLogForm)); this.okButton = new System.Windows.Forms.Button(); this.cancelButton = new System.Windows.Forms.Button(); this.tasksTree = new PTM.View.Controls.TasksTreeViewControl(); @@ -92,7 +90,6 @@ private void InitializeComponent() this.newButton = new System.Windows.Forms.Button(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.propertiesButton = new System.Windows.Forms.Button(); - this.showHiddenCheckBox = new System.Windows.Forms.CheckBox(); this.groupBox1.SuspendLayout(); this.SuspendLayout(); // @@ -100,7 +97,7 @@ private void InitializeComponent() // this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; - this.okButton.Location = new System.Drawing.Point(298, 314); + this.okButton.Location = new System.Drawing.Point(256, 252); this.okButton.Name = "okButton"; this.okButton.Size = new System.Drawing.Size(75, 23); this.okButton.TabIndex = 6; @@ -110,7 +107,7 @@ private void InitializeComponent() // this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.cancelButton.Location = new System.Drawing.Point(386, 314); + this.cancelButton.Location = new System.Drawing.Point(344, 252); this.cancelButton.Name = "cancelButton"; this.cancelButton.Size = new System.Drawing.Size(75, 23); this.cancelButton.TabIndex = 7; @@ -123,14 +120,13 @@ private void InitializeComponent() | System.Windows.Forms.AnchorStyles.Right))); this.tasksTree.Location = new System.Drawing.Point(6, 18); this.tasksTree.Name = "tasksTree"; - this.tasksTree.ShowHidden = false; - this.tasksTree.Size = new System.Drawing.Size(360, 278); + this.tasksTree.Size = new System.Drawing.Size(318, 216); this.tasksTree.TabIndex = 0; // // editButton // this.editButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.editButton.Location = new System.Drawing.Point(386, 94); + this.editButton.Location = new System.Drawing.Point(344, 94); this.editButton.Name = "editButton"; this.editButton.Size = new System.Drawing.Size(75, 23); this.editButton.TabIndex = 4; @@ -140,7 +136,7 @@ private void InitializeComponent() // deleteButton // this.deleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.deleteButton.Location = new System.Drawing.Point(386, 134); + this.deleteButton.Location = new System.Drawing.Point(344, 134); this.deleteButton.Name = "deleteButton"; this.deleteButton.Size = new System.Drawing.Size(75, 23); this.deleteButton.TabIndex = 5; @@ -150,7 +146,7 @@ private void InitializeComponent() // newButton // this.newButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.newButton.Location = new System.Drawing.Point(386, 54); + this.newButton.Location = new System.Drawing.Point(344, 54); this.newButton.Name = "newButton"; this.newButton.Size = new System.Drawing.Size(75, 23); this.newButton.TabIndex = 3; @@ -166,7 +162,7 @@ private void InitializeComponent() this.groupBox1.ForeColor = System.Drawing.Color.Blue; this.groupBox1.Location = new System.Drawing.Point(6, 6); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(372, 302); + this.groupBox1.Size = new System.Drawing.Size(330, 240); this.groupBox1.TabIndex = 2; this.groupBox1.TabStop = false; this.groupBox1.Text = "Choose a task"; @@ -174,32 +170,19 @@ private void InitializeComponent() // propertiesButton // this.propertiesButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.propertiesButton.Location = new System.Drawing.Point(386, 174); + this.propertiesButton.Location = new System.Drawing.Point(344, 174); this.propertiesButton.Name = "propertiesButton"; this.propertiesButton.Size = new System.Drawing.Size(75, 23); this.propertiesButton.TabIndex = 9; this.propertiesButton.Text = "Properties"; this.propertiesButton.Click += new System.EventHandler(this.propertiesButton_Click); // - // showHiddenCheckBox - // - this.showHiddenCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.showHiddenCheckBox.AutoSize = true; - this.showHiddenCheckBox.Location = new System.Drawing.Point(386, 214); - this.showHiddenCheckBox.Name = "showHiddenCheckBox"; - this.showHiddenCheckBox.Size = new System.Drawing.Size(88, 17); - this.showHiddenCheckBox.TabIndex = 10; - this.showHiddenCheckBox.Text = "Show Hidden"; - this.showHiddenCheckBox.UseVisualStyleBackColor = true; - this.showHiddenCheckBox.CheckedChanged += new System.EventHandler(this.showHiddenCheckBox_CheckedChanged); - // - // TaskSelectForm + // TaskLogForm // this.AcceptButton = this.okButton; this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.CancelButton = this.cancelButton; - this.ClientSize = new System.Drawing.Size(474, 351); - this.Controls.Add(this.showHiddenCheckBox); + this.ClientSize = new System.Drawing.Size(432, 289); this.Controls.Add(this.propertiesButton); this.Controls.Add(this.editButton); this.Controls.Add(this.deleteButton); @@ -208,16 +191,16 @@ private void InitializeComponent() this.Controls.Add(this.okButton); this.Controls.Add(this.groupBox1); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; this.MinimizeBox = false; - this.MinimumSize = new System.Drawing.Size(471, 381); - this.Name = "TaskSelectForm"; + this.MinimumSize = new System.Drawing.Size(440, 318); + this.Name = "TaskLogForm"; this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Tasks"; this.TopMost = true; this.groupBox1.ResumeLayout(false); this.ResumeLayout(false); - this.PerformLayout(); } @@ -280,11 +263,5 @@ private void tasksTree_DoubleClick(object sender, EventArgs e) this.DialogResult = DialogResult.OK; this.Close(); } - - private void showHiddenCheckBox_CheckedChanged(object sender, EventArgs e) - { - tasksTree.ShowHidden = this.showHiddenCheckBox.Checked; - tasksTree.LoadTree(); - } } } \ No newline at end of file diff --git a/code/PTM/View/Forms/TaskSelectForm.resx b/code/PTM/View/Forms/TaskLogForm.resx similarity index 100% rename from code/PTM/View/Forms/TaskSelectForm.resx rename to code/PTM/View/Forms/TaskLogForm.resx diff --git a/code/PTM/View/Forms/TaskPropertiesForm.cs b/code/PTM/View/Forms/TaskPropertiesForm.cs index f2ba5b5..d742848 100644 --- a/code/PTM/View/Forms/TaskPropertiesForm.cs +++ b/code/PTM/View/Forms/TaskPropertiesForm.cs @@ -30,11 +30,6 @@ public class TaskPropertiesForm : Form private Label label2; private Label label3; private NumericUpDown minsNumericUpDown; - private CheckBox chkHidden; - private NumericUpDown priorityUpDown; - private Label label4; - private Label label5; - private TextBox notesTextBox; private int taskId; @@ -101,20 +96,14 @@ private void InitializeComponent() this.label2 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); this.minsNumericUpDown = new System.Windows.Forms.NumericUpDown(); - this.chkHidden = new System.Windows.Forms.CheckBox(); - this.priorityUpDown = new System.Windows.Forms.NumericUpDown(); - this.label4 = new System.Windows.Forms.Label(); - this.label5 = new System.Windows.Forms.Label(); - this.notesTextBox = new System.Windows.Forms.TextBox(); ((System.ComponentModel.ISupportInitialize)(this.picture)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.hrsNumericUpDown)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.minsNumericUpDown)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.priorityUpDown)).BeginInit(); this.SuspendLayout(); // // chkIsActive // - this.chkIsActive.Location = new System.Drawing.Point(102, 248); + this.chkIsActive.Location = new System.Drawing.Point(72, 70); this.chkIsActive.Name = "chkIsActive"; this.chkIsActive.Size = new System.Drawing.Size(96, 24); this.chkIsActive.TabIndex = 10; @@ -150,19 +139,18 @@ private void InitializeComponent() // // txtDescription // - this.txtDescription.Location = new System.Drawing.Point(102, 36); - this.txtDescription.MaxLength = 80; + this.txtDescription.Location = new System.Drawing.Point(72, 16); this.txtDescription.Name = "txtDescription"; - this.txtDescription.Size = new System.Drawing.Size(279, 20); + this.txtDescription.Size = new System.Drawing.Size(240, 20); this.txtDescription.TabIndex = 11; // // cancelButton // this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.cancelButton.Location = new System.Drawing.Point(306, 278); + this.cancelButton.Location = new System.Drawing.Point(240, 101); this.cancelButton.Name = "cancelButton"; - this.cancelButton.Size = new System.Drawing.Size(75, 31); + this.cancelButton.Size = new System.Drawing.Size(75, 23); this.cancelButton.TabIndex = 13; this.cancelButton.Text = "Cancel"; this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); @@ -171,16 +159,16 @@ private void InitializeComponent() // this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; - this.okButton.Location = new System.Drawing.Point(218, 278); + this.okButton.Location = new System.Drawing.Point(152, 101); this.okButton.Name = "okButton"; - this.okButton.Size = new System.Drawing.Size(75, 31); + this.okButton.Size = new System.Drawing.Size(75, 23); this.okButton.TabIndex = 12; this.okButton.Text = "Ok"; this.okButton.Click += new System.EventHandler(this.okButton_Click); // // hrsNumericUpDown // - this.hrsNumericUpDown.Location = new System.Drawing.Point(102, 79); + this.hrsNumericUpDown.Location = new System.Drawing.Point(158, 44); this.hrsNumericUpDown.Maximum = new decimal(new int[] { 9999, 0, @@ -193,7 +181,7 @@ private void InitializeComponent() // label1 // this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(13, 83); + this.label1.Location = new System.Drawing.Point(69, 49); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(83, 13); this.label1.TabIndex = 15; @@ -202,7 +190,7 @@ private void InitializeComponent() // label2 // this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(153, 83); + this.label2.Location = new System.Drawing.Point(209, 48); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(24, 13); this.label2.TabIndex = 16; @@ -211,7 +199,7 @@ private void InitializeComponent() // label3 // this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(222, 83); + this.label3.Location = new System.Drawing.Point(278, 48); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(31, 13); this.label3.TabIndex = 18; @@ -219,7 +207,7 @@ private void InitializeComponent() // // minsNumericUpDown // - this.minsNumericUpDown.Location = new System.Drawing.Point(183, 79); + this.minsNumericUpDown.Location = new System.Drawing.Point(239, 44); this.minsNumericUpDown.Maximum = new decimal(new int[] { 59, 0, @@ -229,69 +217,12 @@ private void InitializeComponent() this.minsNumericUpDown.Size = new System.Drawing.Size(36, 20); this.minsNumericUpDown.TabIndex = 17; // - // chkHidden - // - this.chkHidden.AutoSize = true; - this.chkHidden.Location = new System.Drawing.Point(225, 252); - this.chkHidden.Name = "chkHidden"; - this.chkHidden.Size = new System.Drawing.Size(58, 17); - this.chkHidden.TabIndex = 19; - this.chkHidden.Text = "Hidden"; - this.chkHidden.UseVisualStyleBackColor = true; - // - // priorityUpDown - // - this.priorityUpDown.Location = new System.Drawing.Point(102, 110); - this.priorityUpDown.Maximum = new decimal(new int[] { - 10, - 0, - 0, - 0}); - this.priorityUpDown.Name = "priorityUpDown"; - this.priorityUpDown.Size = new System.Drawing.Size(36, 20); - this.priorityUpDown.TabIndex = 20; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(12, 112); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(41, 13); - this.label4.TabIndex = 21; - this.label4.Text = "Priority:"; - // - // label5 - // - this.label5.AutoSize = true; - this.label5.Location = new System.Drawing.Point(13, 151); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(38, 13); - this.label5.TabIndex = 22; - this.label5.Text = "Notes:"; - // - // notesTextBox - // - this.notesTextBox.AcceptsReturn = true; - this.notesTextBox.AcceptsTab = true; - this.notesTextBox.Location = new System.Drawing.Point(102, 148); - this.notesTextBox.MaxLength = 255; - this.notesTextBox.Multiline = true; - this.notesTextBox.Name = "notesTextBox"; - this.notesTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.notesTextBox.Size = new System.Drawing.Size(279, 87); - this.notesTextBox.TabIndex = 23; - // // TaskPropertiesForm // this.AcceptButton = this.okButton; this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.CancelButton = this.cancelButton; - this.ClientSize = new System.Drawing.Size(393, 318); - this.Controls.Add(this.notesTextBox); - this.Controls.Add(this.label5); - this.Controls.Add(this.label4); - this.Controls.Add(this.priorityUpDown); - this.Controls.Add(this.chkHidden); + this.ClientSize = new System.Drawing.Size(320, 129); this.Controls.Add(this.label3); this.Controls.Add(this.minsNumericUpDown); this.Controls.Add(this.label2); @@ -316,7 +247,6 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.picture)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.hrsNumericUpDown)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.minsNumericUpDown)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.priorityUpDown)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -333,19 +263,12 @@ private void TaskPropertiesForm_Load(object sender, EventArgs e) this.chkIsActive.Checked = task.IsActive; this.minsNumericUpDown.Value = task.Estimation % 60; this.hrsNumericUpDown.Value = (task.Estimation - task.Estimation % 60)/60; - this.priorityUpDown.Value = task.Priority; - this.notesTextBox.Text = task.Notes; - this.chkHidden.Checked = task.Hidden; - if(task.Id == Tasks.IdleTask.Id) { this.txtDescription.Enabled = false; this.chkIsActive.Enabled = false; this.minsNumericUpDown.Enabled = false; this.hrsNumericUpDown.Enabled = false; - this.priorityUpDown.Enabled = false; - this.notesTextBox.Enabled = false; - this.chkHidden.Enabled = false; this.btnRigth.Enabled = false; this.btnLeft.Enabled = false; this.okButton.Enabled = false; @@ -384,10 +307,6 @@ private void okButton_Click(object sender, EventArgs e) task.IconId = this.IconId; task.Description = this.Description; task.Estimation = (int) (this.minsNumericUpDown.Value + this.hrsNumericUpDown.Value*60); - task.Priority = Convert.ToInt32(this.priorityUpDown.Value); - task.Notes = this.notesTextBox.Text; - task.Hidden = this.chkHidden.Checked; - Tasks.UpdateTask(task); } catch (ApplicationException aex) diff --git a/code/PTM/View/Forms/TasksExplorerForm.cs b/code/PTM/View/Forms/TasksExplorerForm.cs deleted file mode 100644 index b365301..0000000 --- a/code/PTM/View/Forms/TasksExplorerForm.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.ComponentModel; -using System.Windows.Forms; -using PTM.Framework; -using PTM.View.Controls; - -namespace PTM.View.Forms -{ - /// - /// Summary description for TasksGroupsForm. - /// - internal class TasksExplorerForm : Form - { - private TasksTreeViewControl tasksTreeViewControl; - private Button newButton; - private Button okButton; - private Button deleteButton; - private Button editButton; - private IContainer components; - private CheckBox showHiddenCheckBox; - private Button propertiesButton; - - internal TasksExplorerForm() - { - InitializeComponent(); - tasksTreeViewControl.SelectedTaskChanged += new EventHandler(TreeView_AfterSelect); - tasksTreeViewControl.DoubleClick += new EventHandler(tasksTreeViewControl_DoubleClick); - this.tasksTreeViewControl.Initialize(); - this.showHiddenCheckBox.Checked = tasksTreeViewControl.ShowHidden; - } - - void tasksTreeViewControl_DoubleClick(object sender, EventArgs e) - { - this.tasksTreeViewControl.ShowPropertiesSelectedTask(); - } - - /// - /// Clean up any resources being used. - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (components != null) - { - components.Dispose(); - } - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TasksExplorerForm)); - this.newButton = new System.Windows.Forms.Button(); - this.okButton = new System.Windows.Forms.Button(); - this.deleteButton = new System.Windows.Forms.Button(); - this.editButton = new System.Windows.Forms.Button(); - this.tasksTreeViewControl = new PTM.View.Controls.TasksTreeViewControl(); - this.propertiesButton = new System.Windows.Forms.Button(); - this.showHiddenCheckBox = new System.Windows.Forms.CheckBox(); - this.SuspendLayout(); - // - // newButton - // - this.newButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.newButton.Location = new System.Drawing.Point(375, 16); - this.newButton.Name = "newButton"; - this.newButton.Size = new System.Drawing.Size(80, 23); - this.newButton.TabIndex = 1; - this.newButton.Text = "New Task"; - this.newButton.Click += new System.EventHandler(this.newButton_Click); - // - // okButton - // - this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; - this.okButton.Location = new System.Drawing.Point(375, 321); - this.okButton.Name = "okButton"; - this.okButton.Size = new System.Drawing.Size(80, 23); - this.okButton.TabIndex = 4; - this.okButton.Text = "Close"; - this.okButton.Click += new System.EventHandler(this.okButton_Click); - // - // deleteButton - // - this.deleteButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.deleteButton.Location = new System.Drawing.Point(375, 96); - this.deleteButton.Name = "deleteButton"; - this.deleteButton.Size = new System.Drawing.Size(80, 23); - this.deleteButton.TabIndex = 3; - this.deleteButton.Text = "Delete"; - this.deleteButton.Click += new System.EventHandler(this.deleteButton_Click); - // - // editButton - // - this.editButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.editButton.Location = new System.Drawing.Point(375, 56); - this.editButton.Name = "editButton"; - this.editButton.Size = new System.Drawing.Size(80, 23); - this.editButton.TabIndex = 2; - this.editButton.Text = "Edit"; - this.editButton.Click += new System.EventHandler(this.editButton_Click); - // - // tasksTreeViewControl - // - this.tasksTreeViewControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.tasksTreeViewControl.Location = new System.Drawing.Point(8, 8); - this.tasksTreeViewControl.Name = "tasksTreeViewControl"; - this.tasksTreeViewControl.ShowHidden = false; - this.tasksTreeViewControl.Size = new System.Drawing.Size(359, 337); - this.tasksTreeViewControl.TabIndex = 0; - // - // propertiesButton - // - this.propertiesButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.propertiesButton.Location = new System.Drawing.Point(375, 136); - this.propertiesButton.Name = "propertiesButton"; - this.propertiesButton.Size = new System.Drawing.Size(80, 23); - this.propertiesButton.TabIndex = 5; - this.propertiesButton.Text = "Properties"; - this.propertiesButton.Click += new System.EventHandler(this.propertiesButton_Click); - // - // showHiddenCheckBox - // - this.showHiddenCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.showHiddenCheckBox.AutoSize = true; - this.showHiddenCheckBox.Location = new System.Drawing.Point(375, 176); - this.showHiddenCheckBox.Name = "showHiddenCheckBox"; - this.showHiddenCheckBox.Size = new System.Drawing.Size(88, 17); - this.showHiddenCheckBox.TabIndex = 11; - this.showHiddenCheckBox.Text = "Show Hidden"; - this.showHiddenCheckBox.UseVisualStyleBackColor = true; - this.showHiddenCheckBox.CheckedChanged += new System.EventHandler(this.showHiddenCheckBox_CheckedChanged); - // - // TasksExplorerForm - // - this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); - this.ClientSize = new System.Drawing.Size(463, 351); - this.Controls.Add(this.showHiddenCheckBox); - this.Controls.Add(this.propertiesButton); - this.Controls.Add(this.tasksTreeViewControl); - this.Controls.Add(this.editButton); - this.Controls.Add(this.deleteButton); - this.Controls.Add(this.okButton); - this.Controls.Add(this.newButton); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MinimizeBox = false; - this.MinimumSize = new System.Drawing.Size(471, 381); - this.Name = "TasksExplorerForm"; - this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Tasks Explorer"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private void newButton_Click(object sender, EventArgs e) - { - tasksTreeViewControl.AddNewTask(); - } - - private void editButton_Click(object sender, EventArgs e) - { - tasksTreeViewControl.EditSelectedTaskDescription(); - } - - private void deleteButton_Click(object sender, EventArgs e) - { - tasksTreeViewControl.DeleteSelectedTask(); - } - - private void TreeView_AfterSelect(object sender, EventArgs e) - { - if (tasksTreeViewControl.SelectedTaskId == Tasks.RootTask.Id) - { - this.editButton.Enabled = false; - this.deleteButton.Enabled = false; - this.propertiesButton.Enabled = false; - } - else - { - this.editButton.Enabled = true; - this.deleteButton.Enabled = true; - this.propertiesButton.Enabled = true; - } - } - - private void okButton_Click(object sender, EventArgs e) - { - this.Close(); - } - - private void propertiesButton_Click(object sender, EventArgs e) - { - this.tasksTreeViewControl.ShowPropertiesSelectedTask(); - } - - private void showHiddenCheckBox_CheckedChanged(object sender, EventArgs e) - { - tasksTreeViewControl.ShowHidden = this.showHiddenCheckBox.Checked; - tasksTreeViewControl.LoadTree(); - } - - } -} \ No newline at end of file diff --git a/code/PTM/View/Forms/TasksExplorerForm.resx b/code/PTM/View/Forms/TasksExplorerForm.resx deleted file mode 100644 index ec5160d..0000000 --- a/code/PTM/View/Forms/TasksExplorerForm.resx +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAIAICAQAAAAAADoAgAAJgAAABAQEAAAAAAAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC - AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ - AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAqqqgmZkAAAAAAAAAAAAAAKqqoJmZAAAAAAAAAAAA - AAAKAAmZkAAAAAAAAAAAAAAAAP/wAAAAAAAAd3d3d3d3cAD/AI/wd3AAD4iIiIiIiIAA//8PAIiHAA+I - ///////wAP8Aj/8PhwAPhwAAAAAAAACP8I8A/4cAD4cAIiIiIiIAAAAP/w+HAA+HACACACACAAAAAA8P - hwAPhwAgAgAgAgAAAgAAD4cAD4cAIiIiIiIiIiIgAA+HAA+HACACACACACACACAPhwAPhwqqAgAgAgqq - AgAgD4cAD4cAIqIiIiKiIqIiIA+HAA+HACAKACAKACAKACAPhwAPhwAgCgAgCgAgCgAgD4cAD4cAIiKi - IqIiIiKiIA+HAA+HACACCqoCACACCqoPhwAPhwAgAgAgAgAgAgAgD4cAD4cAIiIiIiIiIiIiIA+HAA+H - ACACACACACACACAPhwAPhwAgAgAgAgAgAgAgD4cAD4cAIiIiIiIiIiIiIA+HAA+HAAAAAAAAAAAAAAAP - hwAPiHd3d3d3d3d3d3d3eIcAD4iIiIiIiIiIiIiIiIiHAAD/////////////////8AAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//4AH//+ - AB///wA/wAAAB4AAAAMAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAA - AAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABgAAAA8AAAAf///////////// - //8oAAAAEAAAACAAAAABAAQAAAAAAIAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA - AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAqqqgAAAAAACq - qqAAiIiIiAoAAA8AAAAAAP/wDwIiIiIA/wAPAgAgAgD/8A8KACACAP8ADwKiIiogD/APAqAgoqAAAA8C - CqoCCiAADwIAIAIAoIAPAiIiIiIggA8AAAAAAACAD////////wAAAAAAAAAAAAAAAAAAAAAA/4AAAIAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA//8AAA== - - - \ No newline at end of file diff --git a/code/PTM/View/Forms/TasksHierarchyForm.cs b/code/PTM/View/Forms/TasksHierarchyForm.cs new file mode 100644 index 0000000..4fe44d9 --- /dev/null +++ b/code/PTM/View/Forms/TasksHierarchyForm.cs @@ -0,0 +1,217 @@ +using System; +using System.ComponentModel; +using System.Windows.Forms; +using PTM.Framework; +using PTM.View.Controls; + +namespace PTM.View.Forms +{ + /// + /// Summary description for TasksGroupsForm. + /// + internal class TasksHierarchyForm : Form + { + private TasksTreeViewControl tasksTreeViewControl; + private Button newButton; + private Button okButton; + private Button deleteButton; + private Button editButton; + private IContainer components; + private Button propertiesButton; + + + private int selectedTaskId; + + internal int SelectedTaskId + { + get { return selectedTaskId; } + } + + internal TasksHierarchyForm() + { + InitializeComponent(); + tasksTreeViewControl.SelectedTaskChanged += new EventHandler(TreeView_AfterSelect); + tasksTreeViewControl.DoubleClick+=new EventHandler(tasksTreeViewControl_DoubleClick); + this.tasksTreeViewControl.Initialize(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + } + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof (TasksHierarchyForm)); + this.newButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.deleteButton = new System.Windows.Forms.Button(); + this.editButton = new System.Windows.Forms.Button(); + this.tasksTreeViewControl = new PTM.View.Controls.TasksTreeViewControl(); + this.propertiesButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // newButton + // + this.newButton.Anchor = + ((System.Windows.Forms.AnchorStyles) + ((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.newButton.Location = new System.Drawing.Point(264, 16); + this.newButton.Name = "newButton"; + this.newButton.Size = new System.Drawing.Size(80, 23); + this.newButton.TabIndex = 1; + this.newButton.Text = "New Task..."; + this.newButton.Click += new System.EventHandler(this.newButton_Click); + // + // okButton + // + this.okButton.Anchor = + ((System.Windows.Forms.AnchorStyles) + ((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(264, 288); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(80, 23); + this.okButton.TabIndex = 4; + this.okButton.Text = "Close"; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // deleteButton + // + this.deleteButton.Anchor = + ((System.Windows.Forms.AnchorStyles) + ((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.deleteButton.Location = new System.Drawing.Point(264, 96); + this.deleteButton.Name = "deleteButton"; + this.deleteButton.Size = new System.Drawing.Size(80, 23); + this.deleteButton.TabIndex = 3; + this.deleteButton.Text = "Delete"; + this.deleteButton.Click += new System.EventHandler(this.deleteButton_Click); + // + // editButton + // + this.editButton.Anchor = + ((System.Windows.Forms.AnchorStyles) + ((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.editButton.Location = new System.Drawing.Point(264, 56); + this.editButton.Name = "editButton"; + this.editButton.TabIndex = 2; + this.editButton.Text = "Edit"; + this.editButton.Click += new System.EventHandler(this.editButton_Click); + // + // tasksTreeViewControl + // + this.tasksTreeViewControl.Anchor = + ((System.Windows.Forms.AnchorStyles) + ((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tasksTreeViewControl.Location = new System.Drawing.Point(8, 8); + this.tasksTreeViewControl.Name = "tasksTreeViewControl"; + this.tasksTreeViewControl.Size = new System.Drawing.Size(248, 304); + this.tasksTreeViewControl.TabIndex = 0; + // + // propertiesButton + // + this.propertiesButton.Anchor = + ((System.Windows.Forms.AnchorStyles) + ((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.propertiesButton.Location = new System.Drawing.Point(264, 136); + this.propertiesButton.Name = "propertiesButton"; + this.propertiesButton.Size = new System.Drawing.Size(80, 23); + this.propertiesButton.TabIndex = 5; + this.propertiesButton.Text = "Properties"; + this.propertiesButton.Click += new System.EventHandler(this.propertiesButton_Click); + // + // TasksHierarchyForm + // + this.AcceptButton = this.okButton; + this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); + this.ClientSize = new System.Drawing.Size(352, 318); + this.Controls.Add(this.propertiesButton); + this.Controls.Add(this.tasksTreeViewControl); + this.Controls.Add(this.editButton); + this.Controls.Add(this.deleteButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.newButton); + this.Icon = ((System.Drawing.Icon) (resources.GetObject("$this.Icon"))); + this.MinimizeBox = false; + this.Name = "TasksHierarchyForm"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Tasks Explorer"; + this.ResumeLayout(false); + } + + #endregion + + private void newButton_Click(object sender, EventArgs e) + { + tasksTreeViewControl.AddNewTask(); + } + + private void editButton_Click(object sender, EventArgs e) + { + tasksTreeViewControl.EditSelectedTaskDescription(); + } + + private void deleteButton_Click(object sender, EventArgs e) + { + tasksTreeViewControl.DeleteSelectedTask(); + } + + private void TreeView_AfterSelect(object sender, EventArgs e) + { + if (tasksTreeViewControl.SelectedTaskId == Tasks.RootTask.Id) + { + this.editButton.Enabled = false; + this.deleteButton.Enabled = false; + this.propertiesButton.Enabled = false; + } + else + { + this.editButton.Enabled = true; + this.deleteButton.Enabled = true; + this.propertiesButton.Enabled = true; + } + this.selectedTaskId = tasksTreeViewControl.SelectedTaskId; + } + + private void okButton_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void propertiesButton_Click(object sender, EventArgs e) + { + this.tasksTreeViewControl.ShowPropertiesSelectedTask(); + } + + private void tasksTreeViewControl_DoubleClick(object sender, EventArgs e) + { + this.selectedTaskId = this.tasksTreeViewControl.SelectedTaskId; + if(this.Modal) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + } + } +} \ No newline at end of file diff --git a/code/PTM/View/Forms/TasksHierarchyForm.resx b/code/PTM/View/Forms/TasksHierarchyForm.resx new file mode 100644 index 0000000..750eac4 --- /dev/null +++ b/code/PTM/View/Forms/TasksHierarchyForm.resx @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + Private + + + Private + + + False + + + (Default) + + + False + + + False + + + 8, 8 + + + True + + + 80 + + + True + + + TasksHierarchyForm + + + Private + + + + AAABAAIAICAQAAAAAADoAgAAJgAAABAQEAAAAAAAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAqqqgmZkAAAAAAAAAAAAAAKqqoJmZAAAAAAAAAAAA + AAAKAAmZkAAAAAAAAAAAAAAAAP/wAAAAAAAAd3d3d3d3cAD/AI/wd3AAD4iIiIiIiIAA//8PAIiHAA+I + ///////wAP8Aj/8PhwAPhwAAAAAAAACP8I8A/4cAD4cAIiIiIiIAAAAP/w+HAA+HACACACACAAAAAA8P + hwAPhwAgAgAgAgAAAgAAD4cAD4cAIiIiIiIiIiIgAA+HAA+HACACACACACACACAPhwAPhwqqAgAgAgqq + AgAgD4cAD4cAIqIiIiKiIqIiIA+HAA+HACAKACAKACAKACAPhwAPhwAgCgAgCgAgCgAgD4cAD4cAIiKi + IqIiIiKiIA+HAA+HACACCqoCACACCqoPhwAPhwAgAgAgAgAgAgAgD4cAD4cAIiIiIiIiIiIiIA+HAA+H + ACACACACACACACAPhwAPhwAgAgAgAgAgAgAgD4cAD4cAIiIiIiIiIiIiIA+HAA+HAAAAAAAAAAAAAAAP + hwAPiHd3d3d3d3d3d3d3eIcAD4iIiIiIiIiIiIiIiIiHAAD/////////////////8AAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//4AH//+ + AB///wA/wAAAB4AAAAMAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAA + AAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABgAAAA8AAAAf///////////// + //8oAAAAEAAAACAAAAABAAQAAAAAAIAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAqqqgAAAAAACq + qqAAiIiIiAoAAA8AAAAAAP/wDwIiIiIA/wAPAgAgAgD/8A8KACACAP8ADwKiIiogD/APAqAgoqAAAA8C + CqoCCiAADwIAIAIAoIAPAiIiIiIggA8AAAAAAACAD////////wAAAAAAAAAAAAAAAAAAAAAA/4AAAIAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA//8AAA== + + + \ No newline at end of file diff --git a/database/ptm.mdb b/database/ptm.mdb index 39fb122..0dfd3e2 100644 Binary files a/database/ptm.mdb and b/database/ptm.mdb differ