diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/CellEditing/CellEditKeyEngine.cs b/CellEditing/CellEditKeyEngine.cs
new file mode 100644
index 0000000..a806d20
--- /dev/null
+++ b/CellEditing/CellEditKeyEngine.cs
@@ -0,0 +1,520 @@
+/*
+ * CellEditKeyEngine - A engine that allows the behaviour of arbitrary keys to be configured
+ *
+ * Author: Phillip Piper
+ * Date: 3-March-2011 10:53 pm
+ *
+ * Change log:
+ * v2.8
+ * 2014-05-30 JPP - When a row is disabled, skip over it when looking for another cell to edit
+ * v2.5
+ * 2012-04-14 JPP - Fixed bug where, on a OLV with only a single editable column, tabbing
+ * to change rows would edit the cell above rather than the cell below
+ * the cell being edited.
+ * 2.5
+ * 2011-03-03 JPP - First version
+ *
+ * Copyright (C) 2011-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Forms;
+using BrightIdeasSoftware;
+
+namespace BrightIdeasSoftware {
+ ///
+ /// Indicates the behavior of a key when a cell "on the edge" is being edited.
+ /// and the normal behavior of that key would exceed the edge. For example,
+ /// for a key that normally moves one column to the left, the "edge" would be
+ /// the left most column, since the normal action of the key cannot be taken
+ /// (since there are no more columns to the left).
+ ///
+ public enum CellEditAtEdgeBehaviour {
+ ///
+ /// The key press will be ignored
+ ///
+ Ignore,
+
+ ///
+ /// The key press will result in the cell editing wrapping to the
+ /// cell on the opposite edge.
+ ///
+ Wrap,
+
+ ///
+ /// The key press will wrap, but the column will be changed to the
+ /// appropiate adjacent column. This only makes sense for keys where
+ /// the normal action is ChangeRow.
+ ///
+ ChangeColumn,
+
+ ///
+ /// The key press will wrap, but the row will be changed to the
+ /// appropiate adjacent row. This only makes sense for keys where
+ /// the normal action is ChangeColumn.
+ ///
+ ChangeRow,
+
+ ///
+ /// The key will result in the current edit operation being ended.
+ ///
+ EndEdit
+ };
+
+ ///
+ /// Indicates the normal behaviour of a key when used during a cell edit
+ /// operation.
+ ///
+ public enum CellEditCharacterBehaviour {
+ ///
+ /// The key press will be ignored
+ ///
+ Ignore,
+
+ ///
+ /// The key press will end the current edit and begin an edit
+ /// operation on the next editable cell to the left.
+ ///
+ ChangeColumnLeft,
+
+ ///
+ /// The key press will end the current edit and begin an edit
+ /// operation on the next editable cell to the right.
+ ///
+ ChangeColumnRight,
+
+ ///
+ /// The key press will end the current edit and begin an edit
+ /// operation on the row above.
+ ///
+ ChangeRowUp,
+
+ ///
+ /// The key press will end the current edit and begin an edit
+ /// operation on the row below
+ ///
+ ChangeRowDown,
+
+ ///
+ /// The key press will cancel the current edit
+ ///
+ CancelEdit,
+
+ ///
+ /// The key press will finish the current edit operation
+ ///
+ EndEdit,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb1,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb2,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb3,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb4,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb5,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb6,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb7,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb8,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb9,
+
+ ///
+ /// Custom verb that can be used for specialized actions.
+ ///
+ CustomVerb10,
+ };
+
+ ///
+ /// Instances of this class handle key presses during a cell edit operation.
+ ///
+ public class CellEditKeyEngine {
+
+ #region Public interface
+
+ ///
+ /// Sets the behaviour of a given key
+ ///
+ ///
+ ///
+ ///
+ public virtual void SetKeyBehaviour(Keys key, CellEditCharacterBehaviour normalBehaviour, CellEditAtEdgeBehaviour atEdgeBehaviour) {
+ this.CellEditKeyMap[key] = normalBehaviour;
+ this.CellEditKeyAtEdgeBehaviourMap[key] = atEdgeBehaviour;
+ }
+
+ ///
+ /// Handle a key press
+ ///
+ ///
+ ///
+ /// True if the key was completely handled.
+ public virtual bool HandleKey(ObjectListView olv, Keys keyData) {
+ if (olv == null) throw new ArgumentNullException("olv");
+
+ CellEditCharacterBehaviour behaviour;
+ if (!CellEditKeyMap.TryGetValue(keyData, out behaviour))
+ return false;
+
+ this.ListView = olv;
+
+ switch (behaviour) {
+ case CellEditCharacterBehaviour.Ignore:
+ break;
+ case CellEditCharacterBehaviour.CancelEdit:
+ this.HandleCancelEdit();
+ break;
+ case CellEditCharacterBehaviour.EndEdit:
+ this.HandleEndEdit();
+ break;
+ case CellEditCharacterBehaviour.ChangeColumnLeft:
+ case CellEditCharacterBehaviour.ChangeColumnRight:
+ this.HandleColumnChange(keyData, behaviour);
+ break;
+ case CellEditCharacterBehaviour.ChangeRowDown:
+ case CellEditCharacterBehaviour.ChangeRowUp:
+ this.HandleRowChange(keyData, behaviour);
+ break;
+ default:
+ return this.HandleCustomVerb(keyData, behaviour);
+ };
+
+ return true;
+ }
+
+ #endregion
+
+ #region Implementation properties
+
+ ///
+ /// Gets or sets the ObjectListView on which the current key is being handled.
+ /// This cannot be null.
+ ///
+ protected ObjectListView ListView {
+ get { return listView; }
+ set { listView = value; }
+ }
+ private ObjectListView listView;
+
+ ///
+ /// Gets the row of the cell that is currently being edited
+ ///
+ protected OLVListItem ItemBeingEdited {
+ get {
+ return this.ListView.CellEditEventArgs.ListViewItem;
+ }
+ }
+
+ ///
+ /// Gets the index of the column of the cell that is being edited
+ ///
+ protected int SubItemIndexBeingEdited {
+ get {
+ return this.ListView.CellEditEventArgs.SubItemIndex;
+ }
+ }
+
+ ///
+ /// Gets or sets the map that remembers the normal behaviour of keys
+ ///
+ protected IDictionary CellEditKeyMap {
+ get {
+ if (cellEditKeyMap == null)
+ this.InitializeCellEditKeyMaps();
+ return cellEditKeyMap;
+ }
+ set {
+ cellEditKeyMap = value;
+ }
+ }
+ private IDictionary cellEditKeyMap;
+
+ ///
+ /// Gets or sets the map that remembers the desired behaviour of keys
+ /// on edge cases.
+ ///
+ protected IDictionary CellEditKeyAtEdgeBehaviourMap {
+ get {
+ if (cellEditKeyAtEdgeBehaviourMap == null)
+ this.InitializeCellEditKeyMaps();
+ return cellEditKeyAtEdgeBehaviourMap;
+ }
+ set {
+ cellEditKeyAtEdgeBehaviourMap = value;
+ }
+ }
+ private IDictionary cellEditKeyAtEdgeBehaviourMap;
+
+ #endregion
+
+ #region Initialization
+
+ ///
+ /// Setup the default key mapping
+ ///
+ protected virtual void InitializeCellEditKeyMaps() {
+ this.cellEditKeyMap = new Dictionary();
+ this.cellEditKeyMap[Keys.Escape] = CellEditCharacterBehaviour.CancelEdit;
+ this.cellEditKeyMap[Keys.Return] = CellEditCharacterBehaviour.EndEdit;
+ this.cellEditKeyMap[Keys.Enter] = CellEditCharacterBehaviour.EndEdit;
+ this.cellEditKeyMap[Keys.Tab] = CellEditCharacterBehaviour.ChangeColumnRight;
+ this.cellEditKeyMap[Keys.Tab | Keys.Shift] = CellEditCharacterBehaviour.ChangeColumnLeft;
+ this.cellEditKeyMap[Keys.Left | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnLeft;
+ this.cellEditKeyMap[Keys.Right | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnRight;
+ this.cellEditKeyMap[Keys.Up | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowUp;
+ this.cellEditKeyMap[Keys.Down | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowDown;
+
+ this.cellEditKeyAtEdgeBehaviourMap = new Dictionary();
+ this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab] = CellEditAtEdgeBehaviour.Wrap;
+ this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab | Keys.Shift] = CellEditAtEdgeBehaviour.Wrap;
+ this.cellEditKeyAtEdgeBehaviourMap[Keys.Left | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
+ this.cellEditKeyAtEdgeBehaviourMap[Keys.Right | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
+ this.cellEditKeyAtEdgeBehaviourMap[Keys.Up | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
+ this.cellEditKeyAtEdgeBehaviourMap[Keys.Down | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
+ }
+
+ #endregion
+
+ #region Command handling
+
+ ///
+ /// Handle the end edit command
+ ///
+ protected virtual void HandleEndEdit() {
+ this.ListView.PossibleFinishCellEditing();
+ }
+
+ ///
+ /// Handle the cancel edit command
+ ///
+ protected virtual void HandleCancelEdit() {
+ this.ListView.CancelCellEdit();
+ }
+
+ ///
+ /// Placeholder that subclasses can override to handle any custom verbs
+ ///
+ ///
+ ///
+ ///
+ protected virtual bool HandleCustomVerb(Keys keyData, CellEditCharacterBehaviour behaviour) {
+ return false;
+ }
+
+ ///
+ /// Handle a change row command
+ ///
+ ///
+ ///
+ protected virtual void HandleRowChange(Keys keyData, CellEditCharacterBehaviour behaviour) {
+ // If we couldn't finish editing the current cell, don't try to move it
+ if (!this.ListView.PossibleFinishCellEditing())
+ return;
+
+ OLVListItem olvi = this.ItemBeingEdited;
+ int subItemIndex = this.SubItemIndexBeingEdited;
+ bool isGoingUp = behaviour == CellEditCharacterBehaviour.ChangeRowUp;
+
+ // Try to find a row above (or below) the currently edited cell
+ // If we find one, start editing it and we're done.
+ OLVListItem adjacentOlvi = this.GetAdjacentItemOrNull(olvi, isGoingUp);
+ if (adjacentOlvi != null) {
+ this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
+ return;
+ }
+
+ // There is no adjacent row in the direction we want, so we must be on an edge.
+ CellEditAtEdgeBehaviour atEdgeBehaviour;
+ if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
+ atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
+ switch (atEdgeBehaviour) {
+ case CellEditAtEdgeBehaviour.Ignore:
+ break;
+ case CellEditAtEdgeBehaviour.EndEdit:
+ this.ListView.PossibleFinishCellEditing();
+ break;
+ case CellEditAtEdgeBehaviour.Wrap:
+ adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
+ this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
+ break;
+ case CellEditAtEdgeBehaviour.ChangeColumn:
+ // Figure out the next editable column
+ List editableColumnsInDisplayOrder = this.EditableColumnsInDisplayOrder;
+ int displayIndex = Math.Max(0, editableColumnsInDisplayOrder.IndexOf(this.ListView.GetColumn(subItemIndex)));
+ if (isGoingUp)
+ displayIndex = (editableColumnsInDisplayOrder.Count + displayIndex - 1) % editableColumnsInDisplayOrder.Count;
+ else
+ displayIndex = (displayIndex + 1) % editableColumnsInDisplayOrder.Count;
+ subItemIndex = editableColumnsInDisplayOrder[displayIndex].Index;
+
+ // Wrap to the next row and start the cell edit
+ adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
+ this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
+ break;
+ }
+ }
+
+ ///
+ /// Handle a change column command
+ ///
+ ///
+ ///
+ protected virtual void HandleColumnChange(Keys keyData, CellEditCharacterBehaviour behaviour)
+ {
+ // If we couldn't finish editing the current cell, don't try to move it
+ if (!this.ListView.PossibleFinishCellEditing())
+ return;
+
+ // Changing columns only works in details mode
+ if (this.ListView.View != View.Details)
+ return;
+
+ List editableColumns = this.EditableColumnsInDisplayOrder;
+ OLVListItem olvi = this.ItemBeingEdited;
+ int displayIndex = Math.Max(0,
+ editableColumns.IndexOf(this.ListView.GetColumn(this.SubItemIndexBeingEdited)));
+ bool isGoingLeft = behaviour == CellEditCharacterBehaviour.ChangeColumnLeft;
+
+ // Are we trying to continue past one of the edges?
+ if ((isGoingLeft && displayIndex == 0) ||
+ (!isGoingLeft && displayIndex == editableColumns.Count - 1))
+ {
+ // Yes, so figure out our at edge behaviour
+ CellEditAtEdgeBehaviour atEdgeBehaviour;
+ if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
+ atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
+ switch (atEdgeBehaviour)
+ {
+ case CellEditAtEdgeBehaviour.Ignore:
+ return;
+ case CellEditAtEdgeBehaviour.EndEdit:
+ this.HandleEndEdit();
+ return;
+ case CellEditAtEdgeBehaviour.ChangeRow:
+ case CellEditAtEdgeBehaviour.Wrap:
+ if (atEdgeBehaviour == CellEditAtEdgeBehaviour.ChangeRow)
+ olvi = GetAdjacentItem(olvi, isGoingLeft && displayIndex == 0);
+ if (isGoingLeft)
+ displayIndex = editableColumns.Count - 1;
+ else
+ displayIndex = 0;
+ break;
+ }
+ }
+ else
+ {
+ if (isGoingLeft)
+ displayIndex -= 1;
+ else
+ displayIndex += 1;
+ }
+
+ int subItemIndex = editableColumns[displayIndex].Index;
+ this.StartCellEditIfDifferent(olvi, subItemIndex);
+ }
+
+ #endregion
+
+ #region Utilities
+
+ ///
+ /// Start editing the indicated cell if that cell is not already being edited
+ ///
+ /// The row to edit
+ /// The cell within that row to edit
+ protected void StartCellEditIfDifferent(OLVListItem olvi, int subItemIndex) {
+ if (this.ItemBeingEdited == olvi && this.SubItemIndexBeingEdited == subItemIndex)
+ return;
+
+ this.ListView.EnsureVisible(olvi.Index);
+ this.ListView.StartCellEdit(olvi, subItemIndex);
+ }
+
+ ///
+ /// Gets the adjacent item to the given item in the given direction.
+ /// If that item is disabled, continue in that direction until an enabled item is found.
+ ///
+ /// The row whose neighbour is sought
+ /// The direction of the adjacentness
+ /// An OLVListView adjacent to the given item, or null if there are no more enabled items in that direction.
+ protected OLVListItem GetAdjacentItemOrNull(OLVListItem olvi, bool up) {
+ OLVListItem item = up ? this.ListView.GetPreviousItem(olvi) : this.ListView.GetNextItem(olvi);
+ while (item != null && !item.Enabled)
+ item = up ? this.ListView.GetPreviousItem(item) : this.ListView.GetNextItem(item);
+ return item;
+ }
+
+ ///
+ /// Gets the adjacent item to the given item in the given direction, wrapping if needed.
+ ///
+ /// The row whose neighbour is sought
+ /// The direction of the adjacentness
+ /// An OLVListView adjacent to the given item, or null if there are no more items in that direction.
+ protected OLVListItem GetAdjacentItem(OLVListItem olvi, bool up) {
+ return this.GetAdjacentItemOrNull(olvi, up) ?? this.GetAdjacentItemOrNull(null, up);
+ }
+
+ ///
+ /// Gets a collection of columns that are editable in the order they are shown to the user
+ ///
+ protected List EditableColumnsInDisplayOrder {
+ get {
+ List editableColumnsInDisplayOrder = new List();
+ foreach (OLVColumn x in this.ListView.ColumnsInDisplayOrder)
+ if (x.IsEditable)
+ editableColumnsInDisplayOrder.Add(x);
+ return editableColumnsInDisplayOrder;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/CellEditing/CellEditors.cs b/CellEditing/CellEditors.cs
new file mode 100644
index 0000000..7ecd9fb
--- /dev/null
+++ b/CellEditing/CellEditors.cs
@@ -0,0 +1,288 @@
+/*
+ * CellEditors - Several slightly modified controls that are used as celleditors within ObjectListView.
+ *
+ * Author: Phillip Piper
+ * Date: 20/10/2008 5:15 PM
+ *
+ * Change log:
+ * v2.6
+ * 2012-08-02 JPP - Make most editors public so they can be reused/subclassed
+ * v2.3
+ * 2009-08-13 JPP - Standardized code formatting
+ * v2.2.1
+ * 2008-01-18 JPP - Added special handling for enums
+ * 2008-01-16 JPP - Added EditorRegistry
+ * v2.0.1
+ * 2008-10-20 JPP - Separated from ObjectListView.cs
+ *
+ * Copyright (C) 2006-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Reflection;
+using System.Windows.Forms;
+
+namespace BrightIdeasSoftware
+{
+ ///
+ /// These items allow combo boxes to remember a value and its description.
+ ///
+ public class ComboBoxItem
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ComboBoxItem(Object key, String description) {
+ this.key = key;
+ this.description = description;
+ }
+ private readonly String description;
+
+ ///
+ ///
+ ///
+ public Object Key {
+ get { return key; }
+ }
+ private readonly Object key;
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ ///
+ /// A string that represents the current object.
+ ///
+ /// 2
+ public override string ToString() {
+ return this.description;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ // Cell editors
+ // These classes are simple cell editors that make it easier to get and set
+ // the value that the control is showing.
+ // In many cases, you can intercept the CellEditStarting event to
+ // change the characteristics of the editor. For example, changing
+ // the acceptable range for a numeric editor or changing the strings
+ // that respresent true and false values for a boolean editor.
+
+ ///
+ /// This editor shows and auto completes values from the given listview column.
+ ///
+ [ToolboxItem(false)]
+ public class AutoCompleteCellEditor : ComboBox
+ {
+ ///
+ /// Create an AutoCompleteCellEditor
+ ///
+ ///
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// This combo box is specialised to allow editing of an enum.
+ ///
+ [ToolboxItem(false)]
+ public class EnumCellEditor : ComboBox
+ {
+ ///
+ ///
+ ///
+ ///
+ public EnumCellEditor(Type type) {
+ this.DropDownStyle = ComboBoxStyle.DropDownList;
+ this.ValueMember = "Key";
+
+ ArrayList values = new ArrayList();
+ foreach (object value in Enum.GetValues(type))
+ values.Add(new ComboBoxItem(value, Enum.GetName(type, value)));
+
+ this.DataSource = values;
+ }
+ }
+
+ ///
+ /// This editor simply shows and edits integer values.
+ ///
+ [ToolboxItem(false)]
+ public class IntUpDown : NumericUpDown
+ {
+ ///
+ ///
+ ///
+ public IntUpDown() {
+ this.DecimalPlaces = 0;
+ this.Minimum = -9999999;
+ this.Maximum = 9999999;
+ }
+
+ ///
+ /// Gets or sets the value shown by this editor
+ ///
+ 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.
+ ///
+ /// This class can't be made public because unsigned int is not a
+ /// CLS-compliant type. If you want to use, just copy the code to this class
+ /// into your project and use it from there.
+ [ToolboxItem(false)]
+ internal 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.
+ ///
+ [ToolboxItem(false)]
+ public class BooleanCellEditor : ComboBox
+ {
+ ///
+ ///
+ ///
+ public BooleanCellEditor() {
+ this.DropDownStyle = ComboBoxStyle.DropDownList;
+ this.ValueMember = "Key";
+
+ ArrayList values = new ArrayList();
+ values.Add(new ComboBoxItem(false, "False"));
+ values.Add(new ComboBoxItem(true, "True"));
+
+ this.DataSource = values;
+ }
+ }
+
+ ///
+ /// This editor simply shows and edits boolean values using a checkbox
+ ///
+ [ToolboxItem(false)]
+ public class BooleanCellEditor2 : CheckBox
+ {
+ ///
+ /// Gets or sets the value shown by this editor
+ ///
+ public bool? Value {
+ get {
+ switch (this.CheckState) {
+ case CheckState.Checked: return true;
+ case CheckState.Indeterminate: return null;
+ case CheckState.Unchecked:
+ default: return false;
+ }
+ }
+ set {
+ if (value.HasValue)
+ this.CheckState = value.Value ? CheckState.Checked : CheckState.Unchecked;
+ else
+ this.CheckState = CheckState.Indeterminate;
+ }
+ }
+
+ ///
+ /// Gets or sets how the checkbox will be aligned
+ ///
+ public new HorizontalAlignment TextAlign {
+ get {
+ switch (this.CheckAlign) {
+ case ContentAlignment.MiddleRight: return HorizontalAlignment.Right;
+ case ContentAlignment.MiddleCenter: return HorizontalAlignment.Center;
+ case ContentAlignment.MiddleLeft:
+ default: return HorizontalAlignment.Left;
+ }
+ }
+ set {
+ switch (value) {
+ case HorizontalAlignment.Left:
+ this.CheckAlign = ContentAlignment.MiddleLeft;
+ break;
+ case HorizontalAlignment.Center:
+ this.CheckAlign = ContentAlignment.MiddleCenter;
+ break;
+ case HorizontalAlignment.Right:
+ this.CheckAlign = ContentAlignment.MiddleRight;
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// 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.
+ [ToolboxItem(false)]
+ public class FloatCellEditor : NumericUpDown
+ {
+ ///
+ ///
+ ///
+ public FloatCellEditor() {
+ this.DecimalPlaces = 2;
+ this.Minimum = -9999999;
+ this.Maximum = 9999999;
+ }
+
+ ///
+ /// Gets or sets the value shown by this editor
+ ///
+ new public double Value {
+ get { return Convert.ToDouble(base.Value); }
+ set { base.Value = Convert.ToDecimal(value); }
+ }
+ }
+}
diff --git a/CellEditing/EditorRegistry.cs b/CellEditing/EditorRegistry.cs
new file mode 100644
index 0000000..c75a4ba
--- /dev/null
+++ b/CellEditing/EditorRegistry.cs
@@ -0,0 +1,203 @@
+/*
+ * EditorRegistry - A registry mapping types to cell editors.
+ *
+ * Author: Phillip Piper
+ * Date: 6-March-2011 7:53 am
+ *
+ * Change log:
+ * 2011-03-31 JPP - Use OLVColumn.DataType if the value to be edited is null
+ * 2011-03-06 JPP - Separated from CellEditors.cs
+ *
+ * Copyright (C) 2011-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Forms;
+using System.Reflection;
+
+namespace BrightIdeasSoftware {
+
+ ///
+ /// A delegate that creates an editor for the given value
+ ///
+ /// The model from which that value came
+ /// The column for which the editor is being created
+ /// A representative value of the type to be edited. This value may not be the exact
+ /// value for the column/model combination. It could be simply representative of
+ /// the appropriate type of value.
+ /// A control which can edit the given value
+ public delegate Control EditorCreatorDelegate(Object model, OLVColumn column, Object value);
+
+ ///
+ /// An editor registry gives a way to decide what cell editor should be used to edit
+ /// the value of a cell. Programmers can register non-standard types and the control that
+ /// should be used to edit instances of that type.
+ ///
+ ///
+ /// All ObjectListViews share the same editor registry.
+ ///
+ public class EditorRegistry {
+ #region Initializing
+
+ ///
+ /// Create an EditorRegistry
+ ///
+ public EditorRegistry() {
+ this.InitializeStandardTypes();
+ }
+
+ private void InitializeStandardTypes() {
+ this.Register(typeof(Boolean), typeof(BooleanCellEditor));
+ this.Register(typeof(Int16), typeof(IntUpDown));
+ this.Register(typeof(Int32), typeof(IntUpDown));
+ this.Register(typeof(Int64), typeof(IntUpDown));
+ this.Register(typeof(UInt16), typeof(UintUpDown));
+ this.Register(typeof(UInt32), typeof(UintUpDown));
+ this.Register(typeof(UInt64), typeof(UintUpDown));
+ this.Register(typeof(Single), typeof(FloatCellEditor));
+ this.Register(typeof(Double), typeof(FloatCellEditor));
+ this.Register(typeof(DateTime), delegate(Object model, OLVColumn column, Object value) {
+ DateTimePicker c = new DateTimePicker();
+ c.Format = DateTimePickerFormat.Short;
+ return c;
+ });
+ this.Register(typeof(Boolean), delegate(Object model, OLVColumn column, Object value) {
+ CheckBox c = new BooleanCellEditor2();
+ c.ThreeState = column.TriStateCheckBoxes;
+ return c;
+ });
+ }
+
+ #endregion
+
+ #region Registering
+
+ ///
+ /// Register that values of 'type' should be edited by instances of 'controlType'.
+ ///
+ /// The type of value to be edited
+ /// The type of the Control that will edit values of 'type'
+ ///
+ /// ObjectListView.EditorRegistry.Register(typeof(Color), typeof(MySpecialColorEditor));
+ ///
+ public void Register(Type type, Type controlType) {
+ this.Register(type, delegate(Object model, OLVColumn column, Object value) {
+ return controlType.InvokeMember("", BindingFlags.CreateInstance, null, null, null) as Control;
+ });
+ }
+
+ ///
+ /// Register the given delegate so that it is called to create editors
+ /// for values of the given type
+ ///
+ /// The type of value to be edited
+ /// The delegate that will create a control that can edit values of 'type'
+ ///
+ /// ObjectListView.EditorRegistry.Register(typeof(Color), CreateColorEditor);
+ /// ...
+ /// public Control CreateColorEditor(Object model, OLVColumn column, Object value)
+ /// {
+ /// return new MySpecialColorEditor();
+ /// }
+ ///
+ public void Register(Type type, EditorCreatorDelegate creator) {
+ this.creatorMap[type] = creator;
+ }
+
+ ///
+ /// Register a delegate that will be called to create an editor for values
+ /// that have not been handled.
+ ///
+ /// The delegate that will create a editor for all other types
+ public void RegisterDefault(EditorCreatorDelegate creator) {
+ this.defaultCreator = creator;
+ }
+
+ ///
+ /// Register a delegate that will be given a chance to create a control
+ /// before any other option is considered.
+ ///
+ /// The delegate that will create a control
+ public void RegisterFirstChance(EditorCreatorDelegate creator) {
+ this.firstChanceCreator = creator;
+ }
+
+ #endregion
+
+ #region Accessing
+
+ ///
+ /// Create and return an editor that is appropriate for the given value.
+ /// Return null if no appropriate editor can be found.
+ ///
+ /// The model involved
+ /// The column to be edited
+ /// The value to be edited. This value may not be the exact
+ /// value for the column/model combination. It could be simply representative of
+ /// the appropriate type of value.
+ /// A Control that can edit the given type of values
+ public Control GetEditor(Object model, OLVColumn column, Object value) {
+ Control editor;
+
+ // Give the first chance delegate a chance to decide
+ if (this.firstChanceCreator != null) {
+ editor = this.firstChanceCreator(model, column, value);
+ if (editor != null)
+ return editor;
+ }
+
+ // Try to find a creator based on the type of the value (or the column)
+ Type type = value == null ? column.DataType : value.GetType();
+ if (type != null && this.creatorMap.ContainsKey(type)) {
+ editor = this.creatorMap[type](model, column, value);
+ if (editor != null)
+ return editor;
+ }
+
+ // Enums without other processing get a special editor
+ if (value != null && value.GetType().IsEnum)
+ return this.CreateEnumEditor(value.GetType());
+
+ // Give any default creator a final chance
+ if (this.defaultCreator != null)
+ return this.defaultCreator(model, column, value);
+
+ return null;
+ }
+
+ ///
+ /// Create and return an editor that will edit values of the given type
+ ///
+ /// A enum type
+ protected Control CreateEnumEditor(Type type) {
+ return new EnumCellEditor(type);
+ }
+
+ #endregion
+
+ #region Private variables
+
+ private EditorCreatorDelegate firstChanceCreator;
+ private EditorCreatorDelegate defaultCreator;
+ private Dictionary creatorMap = new Dictionary();
+
+ #endregion
+ }
+}
diff --git a/CustomDictionary.xml b/CustomDictionary.xml
new file mode 100644
index 0000000..f2cf5b9
--- /dev/null
+++ b/CustomDictionary.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ br
+ Canceled
+ Center
+ Color
+ Colors
+ f
+ fmt
+ g
+ gdi
+ hti
+ i
+ lightbox
+ lv
+ lvi
+ lvsi
+ m
+ multi
+ Munger
+ n
+ olv
+ olvi
+ p
+ parms
+ r
+ Renderer
+ s
+ SubItem
+ Unapply
+ Unpause
+ x
+ y
+
+
+ ComPlus
+
+
+
+
+ OLV
+
+
+
diff --git a/DataListView.cs b/DataListView.cs
new file mode 100644
index 0000000..16f31ce
--- /dev/null
+++ b/DataListView.cs
@@ -0,0 +1,199 @@
+/*
+ * DataListView - A data-bindable listview
+ *
+ * Author: Phillip Piper
+ * Date: 27/09/2008 9:15 AM
+ *
+ * Change log:
+ * 2011-02-27 JPP - Moved most of the logic to DataSourceAdapter (where it
+ * can be used by FastDataListView too)
+ * v2.3
+ * 2009-01-18 JPP - Boolean columns are now handled as checkboxes
+ * - Auto-generated columns would fail if the data source was
+ * reseated, even to the same data source
+ * v2.0.1
+ * 2009-01-07 JPP - Made all public and protected methods virtual
+ * 2008-10-03 JPP - Separated from ObjectListView.cs
+ *
+ * Copyright (C) 2006-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Data;
+using System.Diagnostics;
+using System.Drawing.Design;
+using System.Windows.Forms;
+
+namespace BrightIdeasSoftware
+{
+
+ ///
+ /// 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.
+ /// The DataListView will automatically create columns to show all of the data source's columns/properties, if there is not already
+ /// a column showing that property. This allows you to define one or two columns in the designer and then have the others generated automatically.
+ /// If you don't want any column to be auto generated, set to false.
+ /// These generated columns 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.
+ /// Internally, a CurrencyManager controls keeping the data source in-sync with other users of the data source (as per normal .NET
+ /// behavior). This means that the model objects in the DataListView are DataRowView objects. If you write your own AspectGetters/Setters,
+ /// they will be given DataRowView objects.
+ ///
+ public class DataListView : ObjectListView
+ {
+ ///
+ /// Make a DataListView
+ ///
+ public DataListView()
+ {
+ this.Adapter = new DataSourceAdapter(this);
+ }
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets whether or not columns will be automatically generated to show the
+ /// columns when the DataSource is set.
+ ///
+ /// This must be set before the DataSource is set. It has no effect afterwards.
+ [Category("Data"),
+ Description("Should the control automatically generate columns from the DataSource"),
+ DefaultValue(true)]
+ public bool AutoGenerateColumns {
+ get { return this.Adapter.AutoGenerateColumns; }
+ set { this.Adapter.AutoGenerateColumns = value; }
+ }
+
+ ///
+ /// 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.
+ /// When a DataSource is set, the control will create OLVColumns to show any
+ /// data source columns that are not already shown.
+ /// If the DataSource is changed, you will have to remove any previously
+ /// created columns, since they will be configured for the previous DataSource.
+ /// .
+ ///
+ [Category("Data"),
+ TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
+ public virtual Object DataSource
+ {
+ get { return this.Adapter.DataSource; }
+ set { this.Adapter.DataSource = value; }
+ }
+
+ ///
+ /// 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 virtual string DataMember
+ {
+ get { return this.Adapter.DataMember; }
+ set { this.Adapter.DataMember = value; }
+ }
+
+ #endregion
+
+ #region Implementation properties
+
+ ///
+ /// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
+ /// for data binding.
+ ///
+ protected DataSourceAdapter Adapter {
+ get {
+ Debug.Assert(adapter != null, "Data adapter should not be null");
+ return adapter;
+ }
+ set { adapter = value; }
+ }
+ private DataSourceAdapter adapter;
+
+ #endregion
+
+ #region Object manipulations
+
+ ///
+ /// Add the given collection of model objects to this control.
+ ///
+ /// A collection of model objects
+ /// This is a no-op for data lists, since the data
+ /// is controlled by the VirtualListDataSource. Manipulate the data source
+ /// rather than this view of the data source.
+ public override void AddObjects(ICollection modelObjects)
+ {
+ }
+
+ ///
+ /// Remove the given collection of model objects from this control.
+ ///
+ /// This is a no-op for data lists, since the data
+ /// is controlled by the VirtualListDataSource. Manipulate the data source
+ /// rather than this view of the data source.
+ public override void RemoveObjects(ICollection modelObjects)
+ {
+ }
+
+ #endregion
+
+ #region Event Handlers
+
+ ///
+ /// 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.
+
+ // THINK: Do we need to forward this to the adapter?
+ }
+
+ #endregion
+ }
+}
diff --git a/DataTreeListView.cs b/DataTreeListView.cs
new file mode 100644
index 0000000..b8e61d7
--- /dev/null
+++ b/DataTreeListView.cs
@@ -0,0 +1,226 @@
+/*
+ * DataTreeListView - A data bindable TreeListView
+ *
+ * Author: Phillip Piper
+ * Date: 05/05/2012 3:26 PM
+ *
+ * Change log:
+
+ * 2012-05-05 JPP Initial version
+ *
+ * TO DO:
+
+ *
+ * Copyright (C) 2012 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Data;
+using System.Diagnostics;
+using System.Drawing.Design;
+using System.Windows.Forms;
+
+namespace BrightIdeasSoftware
+{
+ ///
+ /// A DataTreeListView is a TreeListView that calculates its hierarchy based on
+ /// information in the data source.
+ ///
+ ///
+ /// Like a , a DataTreeListView sources all its information
+ /// from a combination of and .
+ /// can be a DataTable, DataSet,
+ /// or anything that implements .
+ ///
+ ///
+ /// To function properly, the DataTreeListView requires:
+ ///
+ /// the table to have a column which holds a unique for the row. The name of this column must be set in .
+ /// the table to have a column which holds id of the hierarchical parent of the row. The name of this column must be set in .
+ /// a value which identifies which rows are the roots of the tree ().
+ ///
+ /// The hierarchy structure is determined finding all the rows where the parent key is equal to . These rows
+ /// become the root objects of the hierarchy.
+ ///
+ /// Like a TreeListView, the hierarchy must not contain cycles. Bad things will happen if the data is cyclic.
+ ///
+ public partial class DataTreeListView : TreeListView
+ {
+ #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 virtual Object DataSource {
+ get { return this.Adapter.DataSource; }
+ set { this.Adapter.DataSource = value; }
+ }
+
+ ///
+ /// 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 virtual string DataMember {
+ get { return this.Adapter.DataMember; }
+ set { this.Adapter.DataMember = value; }
+ }
+
+ ///
+ /// Gets or sets the name of the property/column that uniquely identifies each row.
+ ///
+ ///
+ ///
+ /// The value contained by this column must be unique across all rows
+ /// in the data source. Odd and unpredictable things will happen if two
+ /// rows have the same id.
+ ///
+ /// Null cannot be a valid key value.
+ ///
+ [Category("Data"),
+ Description("The name of the property/column that holds the key of a row"),
+ DefaultValue(null)]
+ public virtual string KeyAspectName {
+ get { return this.Adapter.KeyAspectName; }
+ set { this.Adapter.KeyAspectName = value; }
+ }
+
+ ///
+ /// Gets or sets the name of the property/column that contains the key of
+ /// the parent of a row.
+ ///
+ ///
+ ///
+ /// The test condition for deciding if one row is the parent of another is functionally
+ /// equivilent to this:
+ ///
+ /// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName])
+ ///
+ ///
+ /// Unlike key value, parent keys can be null but a null parent key can only be used
+ /// to identify root objects.
+ ///
+ [Category("Data"),
+ Description("The name of the property/column that holds the key of the parent of a row"),
+ DefaultValue(null)]
+ public virtual string ParentKeyAspectName {
+ get { return this.Adapter.ParentKeyAspectName; }
+ set { this.Adapter.ParentKeyAspectName = value; }
+ }
+
+ ///
+ /// Gets or sets the value that identifies a row as a root object.
+ /// When the ParentKey of a row equals the RootKeyValue, that row will
+ /// be treated as root of the TreeListView.
+ ///
+ ///
+ ///
+ /// The test condition for deciding a root object is functionally
+ /// equivilent to this:
+ ///
+ /// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue)
+ ///
+ ///
+ /// The RootKeyValue can be null. Actually, it can be any value that can
+ /// be compared for equality against a basic type.
+ /// If this is set to the wrong value (i.e. to a value that no row
+ /// has in the parent id column), the list will be empty.
+ ///
+ [Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public virtual object RootKeyValue {
+ get { return this.Adapter.RootKeyValue; }
+ set { this.Adapter.RootKeyValue = value; }
+ }
+
+ ///
+ /// Gets or sets the value that identifies a row as a root object.
+ /// . The RootKeyValue can be of any type,
+ /// but the IDE cannot sensibly represent a value of any type,
+ /// so this is a typed wrapper around that property.
+ ///
+ ///
+ /// If you want the root value to be something other than a string,
+ /// you will have set it yourself.
+ ///
+ [Category("Data"),
+ Description("The parent id value that identifies a row as a root object"),
+ DefaultValue(null)]
+ public virtual string RootKeyValueString {
+ get { return Convert.ToString(this.Adapter.RootKeyValue); }
+ set { this.Adapter.RootKeyValue = value; }
+ }
+
+ ///
+ /// Gets or sets whether or not the key columns (id and parent id) should
+ /// be shown to the user.
+ ///
+ /// This must be set before the DataSource is set. It has no effect
+ /// afterwards.
+ [Category("Data"),
+ Description("Should the keys columns (id and parent id) be shown to the user?"),
+ DefaultValue(true)]
+ public virtual bool ShowKeyColumns {
+ get { return this.Adapter.ShowKeyColumns; }
+ set { this.Adapter.ShowKeyColumns = value; }
+ }
+
+ #endregion
+
+ #region Implementation properties
+
+ ///
+ /// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
+ /// for data binding.
+ ///
+ protected TreeDataSourceAdapter Adapter {
+ get {
+ if (this.adapter == null)
+ this.adapter = new TreeDataSourceAdapter(this);
+ return adapter;
+ }
+ set { adapter = value; }
+ }
+ private TreeDataSourceAdapter adapter;
+
+ #endregion
+ }
+}
diff --git a/DragDrop/DragSource.cs b/DragDrop/DragSource.cs
new file mode 100644
index 0000000..c1a1844
--- /dev/null
+++ b/DragDrop/DragSource.cs
@@ -0,0 +1,219 @@
+/*
+ * DragSource.cs - Add drag source functionality to an ObjectListView
+ *
+ * Author: Phillip Piper
+ * Date: 2009-03-17 5:15 PM
+ *
+ * Change log:
+ * 2011-03-29 JPP - Separate OLVDataObject.cs
+ * v2.3
+ * 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default
+ * (since MS didn't make it part of the 'All' value)
+ * v2.2
+ * 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs
+ * 2009-03-17 JPP - Initial version
+ *
+ * Copyright (C) 2009-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Forms;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+
+namespace BrightIdeasSoftware
+{
+ ///
+ /// An IDragSource controls how drag out from the ObjectListView will behave
+ ///
+ public interface IDragSource
+ {
+ ///
+ /// A drag operation is beginning. Return the data object that will be used
+ /// for data transfer. Return null to prevent the drag from starting. The data
+ /// object will normally include all the selected objects.
+ ///
+ ///
+ /// The returned object is later passed to the GetAllowedEffect() and EndDrag()
+ /// methods.
+ ///
+ /// What ObjectListView is being dragged from.
+ /// Which mouse button is down?
+ /// What item was directly dragged by the user? There may be more than just this
+ /// item selected.
+ /// The data object that will be used for data transfer. This will often be a subclass
+ /// of DataObject, but does not need to be.
+ Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item);
+
+ ///
+ /// What operations are possible for this drag? This controls the icon shown during the drag
+ ///
+ /// The data object returned by StartDrag()
+ /// A combination of DragDropEffects flags
+ DragDropEffects GetAllowedEffects(Object dragObject);
+
+ ///
+ /// The drag operation is complete. Do whatever is necessary to complete the action.
+ ///
+ /// The data object returned by StartDrag()
+ /// The value returned from GetAllowedEffects()
+ void EndDrag(Object dragObject, DragDropEffects effect);
+ }
+
+ ///
+ /// A do-nothing implementation of IDragSource that can be safely subclassed.
+ ///
+ public class AbstractDragSource : IDragSource
+ {
+ #region IDragSource Members
+
+ ///
+ /// See IDragSource documentation
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
+ return null;
+ }
+
+ ///
+ /// See IDragSource documentation
+ ///
+ ///
+ ///
+ public virtual DragDropEffects GetAllowedEffects(Object data) {
+ return DragDropEffects.None;
+ }
+
+ ///
+ /// See IDragSource documentation
+ ///
+ ///
+ ///
+ public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
+ }
+
+ #endregion
+ }
+
+ ///
+ /// A reasonable implementation of IDragSource that provides normal
+ /// drag source functionality. It creates a data object that supports
+ /// inter-application dragging of text and HTML representation of
+ /// the dragged rows. It can optionally force a refresh of all dragged
+ /// rows when the drag is complete.
+ ///
+ /// Subclasses can override GetDataObject() to add new
+ /// data formats to the data transfer object.
+ public class SimpleDragSource : IDragSource
+ {
+ #region Constructors
+
+ ///
+ /// Construct a SimpleDragSource
+ ///
+ public SimpleDragSource() {
+ }
+
+ ///
+ /// Construct a SimpleDragSource that refreshes the dragged rows when
+ /// the drag is complete
+ ///
+ ///
+ public SimpleDragSource(bool refreshAfterDrop) {
+ this.RefreshAfterDrop = refreshAfterDrop;
+ }
+
+ #endregion
+
+ #region Public properties
+
+ ///
+ /// Gets or sets whether the dragged rows should be refreshed when the
+ /// drag operation is complete.
+ ///
+ public bool RefreshAfterDrop {
+ get { return refreshAfterDrop; }
+ set { refreshAfterDrop = value; }
+ }
+ private bool refreshAfterDrop;
+
+ #endregion
+
+ #region IDragSource Members
+
+ ///
+ /// Create a DataObject when the user does a left mouse drag operation.
+ /// See IDragSource for further information.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
+ // We only drag on left mouse
+ if (button != MouseButtons.Left)
+ return null;
+
+ return this.CreateDataObject(olv);
+ }
+
+ ///
+ /// Which operations are allowed in the operation? By default, all operations are supported.
+ ///
+ ///
+ /// All opertions are supported
+ public virtual DragDropEffects GetAllowedEffects(Object data) {
+ return DragDropEffects.All | DragDropEffects.Link; // why didn't MS include 'Link' in 'All'??
+ }
+
+ ///
+ /// The drag operation is finished. Refreshe the dragged rows if so configured.
+ ///
+ ///
+ ///
+ public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
+ OLVDataObject data = dragObject as OLVDataObject;
+ if (data == null)
+ return;
+
+ if (this.RefreshAfterDrop)
+ data.ListView.RefreshObjects(data.ModelObjects);
+ }
+
+ ///
+ /// Create a data object that will be used to as the data object
+ /// for the drag operation.
+ ///
+ ///
+ /// Subclasses can override this method add new formats to the data object.
+ ///
+ /// The ObjectListView that is the source of the drag
+ /// A data object for the drag
+ protected virtual object CreateDataObject(ObjectListView olv) {
+ return new OLVDataObject(olv);
+ }
+
+ #endregion
+ }
+}
diff --git a/DragDrop/DropSink.cs b/DragDrop/DropSink.cs
new file mode 100644
index 0000000..f509425
--- /dev/null
+++ b/DragDrop/DropSink.cs
@@ -0,0 +1,1435 @@
+/*
+ * DropSink.cs - Add drop sink ability to an ObjectListView
+ *
+ * Author: Phillip Piper
+ * Date: 2009-03-17 5:15 PM
+ *
+ * Change log:
+ * 2011-04-20 JPP - Rewrote how ModelDropEventArgs.RefreshObjects() works on TreeListViews
+ * v2.4.1
+ * 2010-08-24 JPP - Moved AcceptExternal property up to SimpleDragSource.
+ * v2.3
+ * 2009-09-01 JPP - Correctly handle case where RefreshObjects() is called for
+ * objects that were children but are now roots.
+ * 2009-08-27 JPP - Added ModelDropEventArgs.RefreshObjects() to simplify updating after
+ * a drag-drop operation
+ * 2009-08-19 JPP - Changed to use OlvHitTest()
+ * v2.2.1
+ * 2007-07-06 JPP - Added StandardDropActionFromKeys property to OlvDropEventArgs
+ * v2.2
+ * 2009-05-17 JPP - Added a Handled flag to OlvDropEventArgs
+ * - Tweaked the appearance of the drop-on-background feedback
+ * 2009-04-15 JPP - Separated DragDrop.cs into DropSink.cs
+ * 2009-03-17 JPP - Initial version
+ *
+ * Copyright (C) 2009-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Windows.Forms;
+
+namespace BrightIdeasSoftware
+{
+ ///
+ /// Objects that implement this interface can acts as the receiver for drop
+ /// operation for an ObjectListView.
+ ///
+ public interface IDropSink
+ {
+ ///
+ /// Gets or sets the ObjectListView that is the drop sink
+ ///
+ ObjectListView ListView { get; set; }
+
+ ///
+ /// Draw any feedback that is appropriate to the current drop state.
+ ///
+ ///
+ /// Any drawing is done over the top of the ListView. This operation should disturb
+ /// the Graphic as little as possible. Specifically, do not erase the area into which
+ /// you draw.
+ ///
+ /// A Graphic for drawing
+ /// The contents bounds of the ListView (not including any header)
+ void DrawFeedback(Graphics g, Rectangle bounds);
+
+ ///
+ /// The user has released the drop over this control
+ ///
+ ///
+ /// Implementators should set args.Effect to the appropriate DragDropEffects. This value is returned
+ /// to the originator of the drag.
+ ///
+ ///
+ void Drop(DragEventArgs args);
+
+ ///
+ /// A drag has entered this control.
+ ///
+ /// Implementators should set args.Effect to the appropriate DragDropEffects.
+ ///
+ void Enter(DragEventArgs args);
+
+ ///
+ /// Change the cursor to reflect the current drag operation.
+ ///
+ ///
+ void GiveFeedback(GiveFeedbackEventArgs args);
+
+ ///
+ /// The drag has left the bounds of this control
+ ///
+ void Leave();
+
+ ///
+ /// The drag is moving over this control.
+ ///
+ /// This is where any drop target should be calculated.
+ /// Implementators should set args.Effect to the appropriate DragDropEffects.
+ ///
+ ///
+ void Over(DragEventArgs args);
+
+ ///
+ /// Should the drag be allowed to continue?
+ ///
+ ///
+ void QueryContinue(QueryContinueDragEventArgs args);
+ }
+
+ ///
+ /// This is a do-nothing implementation of IDropSink that is a useful
+ /// base class for more sophisticated implementations.
+ ///
+ public class AbstractDropSink : IDropSink
+ {
+ #region IDropSink Members
+
+ ///
+ /// Gets or sets the ObjectListView that is the drop sink
+ ///
+ public virtual ObjectListView ListView {
+ get { return listView; }
+ set { this.listView = value; }
+ }
+ private ObjectListView listView;
+
+ ///
+ /// Draw any feedback that is appropriate to the current drop state.
+ ///
+ ///
+ /// Any drawing is done over the top of the ListView. This operation should disturb
+ /// the Graphic as little as possible. Specifically, do not erase the area into which
+ /// you draw.
+ ///
+ /// A Graphic for drawing
+ /// The contents bounds of the ListView (not including any header)
+ public virtual void DrawFeedback(Graphics g, Rectangle bounds) {
+ }
+
+ ///
+ /// The user has released the drop over this control
+ ///
+ ///
+ /// Implementators should set args.Effect to the appropriate DragDropEffects. This value is returned
+ /// to the originator of the drag.
+ ///
+ ///
+ public virtual void Drop(DragEventArgs args) {
+ this.Cleanup();
+ }
+
+ ///
+ /// A drag has entered this control.
+ ///
+ /// Implementators should set args.Effect to the appropriate DragDropEffects.
+ ///
+ public virtual void Enter(DragEventArgs args) {
+ }
+
+ ///
+ /// The drag has left the bounds of this control
+ ///
+ public virtual void Leave() {
+ this.Cleanup();
+ }
+
+ ///
+ /// The drag is moving over this control.
+ ///
+ /// This is where any drop target should be calculated.
+ /// Implementators should set args.Effect to the appropriate DragDropEffects.
+ ///
+ ///
+ public virtual void Over(DragEventArgs args) {
+ }
+
+ ///
+ /// Change the cursor to reflect the current drag operation.
+ ///
+ /// You only need to override this if you want non-standard cursors.
+ /// The standard cursors are supplied automatically.
+ ///
+ public virtual void GiveFeedback(GiveFeedbackEventArgs args) {
+ args.UseDefaultCursors = true;
+ }
+
+ ///
+ /// Should the drag be allowed to continue?
+ ///
+ ///
+ /// You only need to override this if you want the user to be able
+ /// to end the drop in some non-standard way, e.g. dragging to a
+ /// certain point even without releasing the mouse, or going outside
+ /// the bounds of the application.
+ ///
+ ///
+ public virtual void QueryContinue(QueryContinueDragEventArgs args) {
+ }
+
+
+ #endregion
+
+ #region Commands
+
+ ///
+ /// This is called when the mouse leaves the drop region and after the
+ /// drop has completed.
+ ///
+ protected virtual void Cleanup() {
+ }
+
+ #endregion
+ }
+
+ ///
+ /// The enum indicates which target has been found for a drop operation
+ ///
+ [Flags]
+ public enum DropTargetLocation
+ {
+ ///
+ /// No applicable target has been found
+ ///
+ None = 0,
+
+ ///
+ /// The list itself is the target of the drop
+ ///
+ Background = 0x01,
+
+ ///
+ /// An item is the target
+ ///
+ Item = 0x02,
+
+ ///
+ /// Between two items (or above the top item or below the bottom item)
+ /// can be the target. This is not actually ever a target, only a value indicate
+ /// that it is valid to drop between items
+ ///
+ BetweenItems = 0x04,
+
+ ///
+ /// Above an item is the target
+ ///
+ AboveItem = 0x08,
+
+ ///
+ /// Below an item is the target
+ ///
+ BelowItem = 0x10,
+
+ ///
+ /// A subitem is the target of the drop
+ ///
+ SubItem = 0x20,
+
+ ///
+ /// On the right of an item is the target (not currently used)
+ ///
+ RightOfItem = 0x40,
+
+ ///
+ /// On the left of an item is the target (not currently used)
+ ///
+ LeftOfItem = 0x80
+ }
+
+ ///
+ /// This class represents a simple implementation of a drop sink.
+ ///
+ ///
+ /// Actually, it's far from simple and can do quite a lot in its own right.
+ ///
+ public class SimpleDropSink : AbstractDropSink
+ {
+ #region Life and death
+
+ ///
+ /// Make a new drop sink
+ ///
+ public SimpleDropSink() {
+ this.timer = new Timer();
+ this.timer.Interval = 250;
+ this.timer.Tick += new EventHandler(this.timer_Tick);
+
+ this.CanDropOnItem = true;
+ //this.CanDropOnSubItem = true;
+ //this.CanDropOnBackground = true;
+ //this.CanDropBetween = true;
+
+ this.FeedbackColor = Color.FromArgb(180, Color.MediumBlue);
+ this.billboard = new BillboardOverlay();
+ }
+
+ #endregion
+
+ #region Public properties
+
+ ///
+ /// Get or set the locations where a drop is allowed to occur (OR-ed together)
+ ///
+ public DropTargetLocation AcceptableLocations {
+ get { return this.acceptableLocations; }
+ set { this.acceptableLocations = value; }
+ }
+ private DropTargetLocation acceptableLocations;
+
+ ///
+ /// Gets or sets whether this sink allows model objects to be dragged from other lists
+ ///
+ public bool AcceptExternal {
+ get { return this.acceptExternal; }
+ set { this.acceptExternal = value; }
+ }
+ private bool acceptExternal = true;
+
+ ///
+ /// Gets or sets whether the ObjectListView should scroll when the user drags
+ /// something near to the top or bottom rows.
+ ///
+ public bool AutoScroll {
+ get { return this.autoScroll; }
+ set { this.autoScroll = value; }
+ }
+ private bool autoScroll = true;
+
+ ///
+ /// Gets the billboard overlay that will be used to display feedback
+ /// messages during a drag operation.
+ ///
+ /// Set this to null to stop the feedback.
+ public BillboardOverlay Billboard {
+ get { return this.billboard; }
+ set { this.billboard = value; }
+ }
+ private BillboardOverlay billboard;
+
+ ///
+ /// Get or set whether a drop can occur between items of the list
+ ///
+ public bool CanDropBetween {
+ get { return (this.AcceptableLocations & DropTargetLocation.BetweenItems) == DropTargetLocation.BetweenItems; }
+ set {
+ if (value)
+ this.AcceptableLocations |= DropTargetLocation.BetweenItems;
+ else
+ this.AcceptableLocations &= ~DropTargetLocation.BetweenItems;
+ }
+ }
+
+ ///
+ /// Get or set whether a drop can occur on the listview itself
+ ///
+ public bool CanDropOnBackground {
+ get { return (this.AcceptableLocations & DropTargetLocation.Background) == DropTargetLocation.Background; }
+ set {
+ if (value)
+ this.AcceptableLocations |= DropTargetLocation.Background;
+ else
+ this.AcceptableLocations &= ~DropTargetLocation.Background;
+ }
+ }
+
+ ///
+ /// Get or set whether a drop can occur on items in the list
+ ///
+ public bool CanDropOnItem {
+ get { return (this.AcceptableLocations & DropTargetLocation.Item) == DropTargetLocation.Item; }
+ set {
+ if (value)
+ this.AcceptableLocations |= DropTargetLocation.Item;
+ else
+ this.AcceptableLocations &= ~DropTargetLocation.Item;
+ }
+ }
+
+ ///
+ /// Get or set whether a drop can occur on a subitem in the list
+ ///
+ public bool CanDropOnSubItem {
+ get { return (this.AcceptableLocations & DropTargetLocation.SubItem) == DropTargetLocation.SubItem; }
+ set {
+ if (value)
+ this.AcceptableLocations |= DropTargetLocation.SubItem;
+ else
+ this.AcceptableLocations &= ~DropTargetLocation.SubItem;
+ }
+ }
+
+ ///
+ /// Get or set the index of the item that is the target of the drop
+ ///
+ public int DropTargetIndex {
+ get { return dropTargetIndex; }
+ set {
+ if (this.dropTargetIndex != value) {
+ this.dropTargetIndex = value;
+ this.ListView.Invalidate();
+ }
+ }
+ }
+ private int dropTargetIndex = -1;
+
+ ///
+ /// Get the item that is the target of the drop
+ ///
+ public OLVListItem DropTargetItem {
+ get {
+ return this.ListView.GetItem(this.DropTargetIndex);
+ }
+ }
+
+ ///
+ /// Get or set the location of the target of the drop
+ ///
+ public DropTargetLocation DropTargetLocation {
+ get { return dropTargetLocation; }
+ set {
+ if (this.dropTargetLocation != value) {
+ this.dropTargetLocation = value;
+ this.ListView.Invalidate();
+ }
+ }
+ }
+ private DropTargetLocation dropTargetLocation;
+
+ ///
+ /// Get or set the index of the subitem that is the target of the drop
+ ///
+ public int DropTargetSubItemIndex {
+ get { return dropTargetSubItemIndex; }
+ set {
+ if (this.dropTargetSubItemIndex != value) {
+ this.dropTargetSubItemIndex = value;
+ this.ListView.Invalidate();
+ }
+ }
+ }
+ private int dropTargetSubItemIndex = -1;
+
+ ///
+ /// Get or set the color that will be used to provide drop feedback
+ ///
+ public Color FeedbackColor {
+ get { return this.feedbackColor; }
+ set { this.feedbackColor = value; }
+ }
+ private Color feedbackColor;
+
+ ///
+ /// Get whether the alt key was down during this drop event
+ ///
+ public bool IsAltDown {
+ get { return (this.KeyState & 32) == 32; }
+ }
+
+ ///
+ /// Get whether any modifier key was down during this drop event
+ ///
+ public bool IsAnyModifierDown {
+ get { return (this.KeyState & (4 + 8 + 32)) != 0; }
+ }
+
+ ///
+ /// Get whether the control key was down during this drop event
+ ///
+ public bool IsControlDown {
+ get { return (this.KeyState & 8) == 8; }
+ }
+
+ ///
+ /// Get whether the left mouse button was down during this drop event
+ ///
+ public bool IsLeftMouseButtonDown {
+ get { return (this.KeyState & 1) == 1; }
+ }
+
+ ///
+ /// Get whether the right mouse button was down during this drop event
+ ///
+ public bool IsMiddleMouseButtonDown {
+ get { return (this.KeyState & 16) == 16; }
+ }
+
+ ///
+ /// Get whether the right mouse button was down during this drop event
+ ///
+ public bool IsRightMouseButtonDown {
+ get { return (this.KeyState & 2) == 2; }
+ }
+
+ ///
+ /// Get whether the shift key was down during this drop event
+ ///
+ public bool IsShiftDown {
+ get { return (this.KeyState & 4) == 4; }
+ }
+
+ ///
+ /// Get or set the state of the keys during this drop event
+ ///
+ public int KeyState {
+ get { return this.keyState; }
+ set { this.keyState = value; }
+ }
+ private int keyState;
+
+ ///
+ /// Gets or sets whether the drop sink will automatically use cursors
+ /// based on the drop effect. By default, this is true. If this is
+ /// set to false, you must set the Cursor yourself.
+ ///
+ public bool UseDefaultCursors {
+ get { return useDefaultCursors; }
+ set { useDefaultCursors = value; }
+ }
+ private bool useDefaultCursors = true;
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Triggered when the sink needs to know if a drop can occur.
+ ///
+ ///
+ /// Handlers should set Effect to indicate what is possible.
+ /// Handlers can change any of the DropTarget* setttings to change
+ /// the target of the drop.
+ ///
+ public event EventHandler CanDrop;
+
+ ///
+ /// Triggered when the drop is made.
+ ///
+ public event EventHandler Dropped;
+
+ ///
+ /// Triggered when the sink needs to know if a drop can occur
+ /// AND the source is an ObjectListView
+ ///
+ ///
+ /// Handlers should set Effect to indicate what is possible.
+ /// Handlers can change any of the DropTarget* setttings to change
+ /// the target of the drop.
+ ///
+ public event EventHandler ModelCanDrop;
+
+ ///
+ /// Triggered when the drop is made.
+ /// AND the source is an ObjectListView
+ ///
+ public event EventHandler ModelDropped;
+
+ #endregion
+
+ #region DropSink Interface
+
+ ///
+ /// Cleanup the drop sink when the mouse has left the control or
+ /// the drag has finished.
+ ///
+ protected override void Cleanup() {
+ this.DropTargetLocation = DropTargetLocation.None;
+ this.ListView.FullRowSelect = this.originalFullRowSelect;
+ this.Billboard.Text = null;
+ }
+
+ ///
+ /// Draw any feedback that is appropriate to the current drop state.
+ ///
+ ///
+ /// Any drawing is done over the top of the ListView. This operation should disturb
+ /// the Graphic as little as possible. Specifically, do not erase the area into which
+ /// you draw.
+ ///
+ /// A Graphic for drawing
+ /// The contents bounds of the ListView (not including any header)
+ public override void DrawFeedback(Graphics g, Rectangle bounds) {
+ g.SmoothingMode = ObjectListView.SmoothingMode;
+
+ switch (this.DropTargetLocation) {
+ case DropTargetLocation.Background:
+ this.DrawFeedbackBackgroundTarget(g, bounds);
+ break;
+ case DropTargetLocation.Item:
+ this.DrawFeedbackItemTarget(g, bounds);
+ break;
+ case DropTargetLocation.AboveItem:
+ this.DrawFeedbackAboveItemTarget(g, bounds);
+ break;
+ case DropTargetLocation.BelowItem:
+ this.DrawFeedbackBelowItemTarget(g, bounds);
+ break;
+ }
+
+ if (this.Billboard != null) {
+ this.Billboard.Draw(this.ListView, g, bounds);
+ }
+ }
+
+ ///
+ /// The user has released the drop over this control
+ ///
+ ///
+ public override void Drop(DragEventArgs args) {
+ this.dropEventArgs.DragEventArgs = args;
+ this.TriggerDroppedEvent(args);
+ this.timer.Stop();
+ this.Cleanup();
+ }
+
+ ///
+ /// A drag has entered this control.
+ ///
+ /// Implementators should set args.Effect to the appropriate DragDropEffects.
+ ///
+ public override void Enter(DragEventArgs args) {
+ //System.Diagnostics.Debug.WriteLine("Enter");
+
+ /*
+ * When FullRowSelect is true, we have two problems:
+ * 1) GetItemRect(ItemOnly) returns the whole row rather than just the icon/text, which messes
+ * up our calculation of the drop rectangle.
+ * 2) during the drag, the Timer events will not fire! This is the major problem, since without
+ * those events we can't autoscroll.
+ *
+ * The first problem we can solve through coding, but the second is more difficult.
+ * We avoid both problems by turning off FullRowSelect during the drop operation.
+ */
+ this.originalFullRowSelect = this.ListView.FullRowSelect;
+ this.ListView.FullRowSelect = false;
+
+ // Setup our drop event args block
+ this.dropEventArgs = new ModelDropEventArgs();
+ this.dropEventArgs.DropSink = this;
+ this.dropEventArgs.ListView = this.ListView;
+ this.dropEventArgs.DragEventArgs = args;
+ this.dropEventArgs.DataObject = args.Data;
+ OLVDataObject olvData = args.Data as OLVDataObject;
+ if (olvData != null) {
+ this.dropEventArgs.SourceListView = olvData.ListView;
+ this.dropEventArgs.SourceModels = olvData.ModelObjects;
+ }
+
+ this.Over(args);
+ }
+
+ ///
+ /// Change the cursor to reflect the current drag operation.
+ ///
+ ///
+ public override void GiveFeedback(GiveFeedbackEventArgs args) {
+ args.UseDefaultCursors = this.UseDefaultCursors;
+ }
+
+ ///
+ /// The drag is moving over this control.
+ ///
+ ///
+ public override void Over(DragEventArgs args) {
+ //System.Diagnostics.Debug.WriteLine("Over");
+ this.dropEventArgs.DragEventArgs = args;
+ this.KeyState = args.KeyState;
+ Point pt = this.ListView.PointToClient(new Point(args.X, args.Y));
+ args.Effect = this.CalculateDropAction(args, pt);
+ this.CheckScrolling(pt);
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Trigger the Dropped events
+ ///
+ ///
+ protected virtual void TriggerDroppedEvent(DragEventArgs args) {
+ this.dropEventArgs.Handled = false;
+
+ // If the source is an ObjectListView, trigger the ModelDropped event
+ if (this.dropEventArgs.SourceListView != null)
+ this.OnModelDropped(this.dropEventArgs);
+
+ if (!this.dropEventArgs.Handled)
+ this.OnDropped(this.dropEventArgs);
+ }
+
+ ///
+ /// Trigger CanDrop
+ ///
+ ///
+ protected virtual void OnCanDrop(OlvDropEventArgs args) {
+ if (this.CanDrop != null)
+ this.CanDrop(this, args);
+ }
+
+ ///
+ /// Trigger Dropped
+ ///
+ ///
+ protected virtual void OnDropped(OlvDropEventArgs args) {
+ if (this.Dropped != null)
+ this.Dropped(this, args);
+ }
+
+ ///
+ /// Trigger ModelCanDrop
+ ///
+ ///
+ protected virtual void OnModelCanDrop(ModelDropEventArgs args) {
+
+ // Don't allow drops from other list, if that's what's configured
+ if (!this.AcceptExternal && args.SourceListView != null && args.SourceListView != this.ListView) {
+ args.Effect = DragDropEffects.None;
+ args.DropTargetLocation = DropTargetLocation.None;
+ args.InfoMessage = "This list doesn't accept drops from other lists";
+ return;
+ }
+
+ if (this.ModelCanDrop != null)
+ this.ModelCanDrop(this, args);
+ }
+
+ ///
+ /// Trigger ModelDropped
+ ///
+ ///
+ protected virtual void OnModelDropped(ModelDropEventArgs args) {
+ if (this.ModelDropped != null)
+ this.ModelDropped(this, args);
+ }
+
+ #endregion
+
+ #region Implementation
+
+ private void timer_Tick(object sender, EventArgs e) {
+ this.HandleTimerTick();
+ }
+
+ ///
+ /// Handle the timer tick event, which is sent when the listview should
+ /// scroll
+ ///
+ protected virtual void HandleTimerTick() {
+
+ // If the mouse has been released, stop scrolling.
+ // This is only necessary if the mouse is released outside of the control.
+ // If the mouse is released inside the control, we would receive a Drop event.
+ if ((this.IsLeftMouseButtonDown && (Control.MouseButtons & MouseButtons.Left) != MouseButtons.Left) ||
+ (this.IsMiddleMouseButtonDown && (Control.MouseButtons & MouseButtons.Middle) != MouseButtons.Middle) ||
+ (this.IsRightMouseButtonDown && (Control.MouseButtons & MouseButtons.Right) != MouseButtons.Right)) {
+ this.timer.Stop();
+ this.Cleanup();
+ return;
+ }
+
+ // Auto scrolling will continune while the mouse is close to the ListView
+ const int GRACE_PERIMETER = 30;
+
+ Point pt = this.ListView.PointToClient(Cursor.Position);
+ Rectangle r2 = this.ListView.ClientRectangle;
+ r2.Inflate(GRACE_PERIMETER, GRACE_PERIMETER);
+ if (r2.Contains(pt)) {
+ this.ListView.LowLevelScroll(0, this.scrollAmount);
+ }
+ }
+
+ ///
+ /// When the mouse is at the given point, what should the target of the drop be?
+ ///
+ /// This method should update the DropTarget* members of the given arg block
+ ///
+ /// The mouse point, in client co-ordinates
+ protected virtual void CalculateDropTarget(OlvDropEventArgs args, Point pt) {
+ const int SMALL_VALUE = 3;
+ DropTargetLocation location = DropTargetLocation.None;
+ int targetIndex = -1;
+ int targetSubIndex = 0;
+
+ if (this.CanDropOnBackground)
+ location = DropTargetLocation.Background;
+
+ // Which item is the mouse over?
+ // If it is not over any item, it's over the background.
+ //ListViewHitTestInfo info = this.ListView.HitTest(pt.X, pt.Y);
+ OlvListViewHitTestInfo info = this.ListView.OlvHitTest(pt.X, pt.Y);
+ if (info.Item != null && this.CanDropOnItem) {
+ location = DropTargetLocation.Item;
+ targetIndex = info.Item.Index;
+ if (info.SubItem != null && this.CanDropOnSubItem)
+ targetSubIndex = info.Item.SubItems.IndexOf(info.SubItem);
+ }
+
+ // Check to see if the mouse is "between" rows.
+ // ("between" is somewhat loosely defined)
+ if (this.CanDropBetween && this.ListView.GetItemCount() > 0) {
+
+ // If the mouse is over an item, check to see if it is near the top or bottom
+ if (location == DropTargetLocation.Item) {
+ if (pt.Y - SMALL_VALUE <= info.Item.Bounds.Top)
+ location = DropTargetLocation.AboveItem;
+ if (pt.Y + SMALL_VALUE >= info.Item.Bounds.Bottom)
+ location = DropTargetLocation.BelowItem;
+ } else {
+ // Is there an item a little below the mouse?
+ // If so, we say the drop point is above that row
+ info = this.ListView.OlvHitTest(pt.X, pt.Y + SMALL_VALUE);
+ if (info.Item != null) {
+ targetIndex = info.Item.Index;
+ location = DropTargetLocation.AboveItem;
+ } else {
+ // Is there an item a little above the mouse?
+ info = this.ListView.OlvHitTest(pt.X, pt.Y - SMALL_VALUE);
+ if (info.Item != null) {
+ targetIndex = info.Item.Index;
+ location = DropTargetLocation.BelowItem;
+ }
+ }
+ }
+ }
+
+ args.DropTargetLocation = location;
+ args.DropTargetIndex = targetIndex;
+ args.DropTargetSubItemIndex = targetSubIndex;
+ }
+
+ ///
+ /// What sort of action is possible when the mouse is at the given point?
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual DragDropEffects CalculateDropAction(DragEventArgs args, Point pt) {
+
+ this.CalculateDropTarget(this.dropEventArgs, pt);
+
+ this.dropEventArgs.MouseLocation = pt;
+ this.dropEventArgs.InfoMessage = null;
+ this.dropEventArgs.Handled = false;
+
+ if (this.dropEventArgs.SourceListView != null) {
+ this.dropEventArgs.TargetModel = this.ListView.GetModelObject(this.dropEventArgs.DropTargetIndex);
+ this.OnModelCanDrop(this.dropEventArgs);
+ }
+
+ if (!this.dropEventArgs.Handled)
+ this.OnCanDrop(this.dropEventArgs);
+
+ this.UpdateAfterCanDropEvent(this.dropEventArgs);
+
+ return this.dropEventArgs.Effect;
+ }
+
+ ///
+ /// Based solely on the state of the modifier keys, what drop operation should
+ /// be used?
+ ///
+ /// The drop operation that matches the state of the keys
+ public DragDropEffects CalculateStandardDropActionFromKeys() {
+ if (this.IsControlDown) {
+ if (this.IsShiftDown)
+ return DragDropEffects.Link;
+ else
+ return DragDropEffects.Copy;
+ } else {
+ return DragDropEffects.Move;
+ }
+ }
+
+ ///
+ /// Should the listview be made to scroll when the mouse is at the given point?
+ ///
+ ///
+ protected virtual void CheckScrolling(Point pt) {
+ if (!this.AutoScroll)
+ return;
+
+ Rectangle r = this.ListView.ContentRectangle;
+ int rowHeight = this.ListView.RowHeightEffective;
+ int close = rowHeight;
+
+ // In Tile view, using the whole row height is too much
+ if (this.ListView.View == View.Tile)
+ close /= 2;
+
+ if (pt.Y <= (r.Top + close)) {
+ // Scroll faster if the mouse is closer to the top
+ this.timer.Interval = ((pt.Y <= (r.Top + close / 2)) ? 100 : 350);
+ this.timer.Start();
+ this.scrollAmount = -rowHeight;
+ } else {
+ if (pt.Y >= (r.Bottom - close)) {
+ this.timer.Interval = ((pt.Y >= (r.Bottom - close / 2)) ? 100 : 350);
+ this.timer.Start();
+ this.scrollAmount = rowHeight;
+ } else {
+ this.timer.Stop();
+ }
+ }
+ }
+
+ ///
+ /// Update the state of our sink to reflect the information that
+ /// may have been written into the drop event args.
+ ///
+ ///
+ protected virtual void UpdateAfterCanDropEvent(OlvDropEventArgs args) {
+ this.DropTargetIndex = args.DropTargetIndex;
+ this.DropTargetLocation = args.DropTargetLocation;
+ this.DropTargetSubItemIndex = args.DropTargetSubItemIndex;
+
+ if (this.Billboard != null) {
+ Point pt = args.MouseLocation;
+ pt.Offset(5, 5);
+ if (this.Billboard.Text != this.dropEventArgs.InfoMessage || this.Billboard.Location != pt) {
+ this.Billboard.Text = this.dropEventArgs.InfoMessage;
+ this.Billboard.Location = pt;
+ this.ListView.Invalidate();
+ }
+ }
+ }
+
+ #endregion
+
+ #region Rendering
+
+ ///
+ /// Draw the feedback that shows that the background is the target
+ ///
+ ///
+ ///
+ protected virtual void DrawFeedbackBackgroundTarget(Graphics g, Rectangle bounds) {
+ float penWidth = 12.0f;
+ Rectangle r = bounds;
+ r.Inflate((int)-penWidth / 2, (int)-penWidth / 2);
+ using (Pen p = new Pen(Color.FromArgb(128, this.FeedbackColor), penWidth)) {
+ using (GraphicsPath path = this.GetRoundedRect(r, 30.0f)) {
+ g.DrawPath(p, path);
+ }
+ }
+ }
+
+ ///
+ /// Draw the feedback that shows that an item (or a subitem) is the target
+ ///
+ ///
+ ///
+ ///
+ /// DropTargetItem and DropTargetSubItemIndex tells what is the target
+ ///
+ protected virtual void DrawFeedbackItemTarget(Graphics g, Rectangle bounds) {
+ if (this.DropTargetItem == null)
+ return;
+ Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex);
+ r.Inflate(1, 1);
+ float diameter = r.Height / 3;
+ using (GraphicsPath path = this.GetRoundedRect(r, diameter)) {
+ using (SolidBrush b = new SolidBrush(Color.FromArgb(48, this.FeedbackColor))) {
+ g.FillPath(b, path);
+ }
+ using (Pen p = new Pen(this.FeedbackColor, 3.0f)) {
+ g.DrawPath(p, path);
+ }
+ }
+ }
+
+ ///
+ /// Draw the feedback that shows the drop will occur before target
+ ///
+ ///
+ ///
+ protected virtual void DrawFeedbackAboveItemTarget(Graphics g, Rectangle bounds) {
+ if (this.DropTargetItem == null)
+ return;
+
+ Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex);
+ this.DrawBetweenLine(g, r.Left, r.Top, r.Right, r.Top);
+ }
+
+ ///
+ /// Draw the feedback that shows the drop will occur after target
+ ///
+ ///
+ ///
+ protected virtual void DrawFeedbackBelowItemTarget(Graphics g, Rectangle bounds) {
+ if (this.DropTargetItem == null)
+ return;
+
+ Rectangle r = this.CalculateDropTargetRectangle(this.DropTargetItem, this.DropTargetSubItemIndex);
+ this.DrawBetweenLine(g, r.Left, r.Bottom, r.Right, r.Bottom);
+ }
+
+ ///
+ /// Return a GraphicPath that is round corner rectangle.
+ ///
+ ///
+ ///
+ ///
+ protected GraphicsPath GetRoundedRect(Rectangle rect, float diameter) {
+ GraphicsPath path = new GraphicsPath();
+
+ RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
+ path.AddArc(arc, 180, 90);
+ arc.X = rect.Right - diameter;
+ path.AddArc(arc, 270, 90);
+ arc.Y = rect.Bottom - diameter;
+ path.AddArc(arc, 0, 90);
+ arc.X = rect.Left;
+ path.AddArc(arc, 90, 90);
+ path.CloseFigure();
+
+ return path;
+ }
+
+ ///
+ /// Calculate the target rectangle when the given item (and possible subitem)
+ /// is the target of the drop.
+ ///
+ ///
+ ///
+ ///
+ protected virtual Rectangle CalculateDropTargetRectangle(OLVListItem item, int subItem) {
+ if (subItem > 0)
+ return item.SubItems[subItem].Bounds;
+
+ Rectangle r = this.ListView.CalculateCellTextBounds(item, subItem);
+
+ // Allow for indent
+ if (item.IndentCount > 0) {
+ int indentWidth = this.ListView.SmallImageSize.Width;
+ r.X += (indentWidth * item.IndentCount);
+ r.Width -= (indentWidth * item.IndentCount);
+ }
+
+ return r;
+ }
+
+ ///
+ /// Draw a "between items" line at the given co-ordinates
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected virtual void DrawBetweenLine(Graphics g, int x1, int y1, int x2, int y2) {
+ using (Brush b = new SolidBrush(this.FeedbackColor)) {
+ int x = x1;
+ int y = y1;
+ using (GraphicsPath gp = new GraphicsPath()) {
+ gp.AddLine(
+ x, y + 5,
+ x, y - 5);
+ gp.AddBezier(
+ x, y - 6,
+ x + 3, y - 2,
+ x + 6, y - 1,
+ x + 11, y);
+ gp.AddBezier(
+ x + 11, y,
+ x + 6, y + 1,
+ x + 3, y + 2,
+ x, y + 6);
+ gp.CloseFigure();
+ g.FillPath(b, gp);
+ }
+ x = x2;
+ y = y2;
+ using (GraphicsPath gp = new GraphicsPath()) {
+ gp.AddLine(
+ x, y + 6,
+ x, y - 6);
+ gp.AddBezier(
+ x, y - 7,
+ x - 3, y - 2,
+ x - 6, y - 1,
+ x - 11, y);
+ gp.AddBezier(
+ x - 11, y,
+ x - 6, y + 1,
+ x - 3, y + 2,
+ x, y + 7);
+ gp.CloseFigure();
+ g.FillPath(b, gp);
+ }
+ }
+ using (Pen p = new Pen(this.FeedbackColor, 3.0f)) {
+ g.DrawLine(p, x1, y1, x2, y2);
+ }
+ }
+
+ #endregion
+
+ private Timer timer;
+ private int scrollAmount;
+ private bool originalFullRowSelect;
+ private ModelDropEventArgs dropEventArgs;
+ }
+
+ ///
+ /// This drop sink allows items within the same list to be rearranged,
+ /// as well as allowing items to be dropped from other lists.
+ ///
+ ///
+ ///
+ /// This class can only be used on plain ObjectListViews and FastObjectListViews.
+ /// The other flavours have no way to implement the insert operation that is required.
+ ///
+ ///
+ /// This class does not work with grouping.
+ ///
+ ///
+ /// This class works when the OLV is sorted, but it is up to the programmer
+ /// to decide what rearranging such lists "means". Example: if the control is sorting
+ /// students by academic grade, and the user drags a "Fail" grade student up amonst the "A+"
+ /// students, it is the responsibility of the programmer to makes the appropriate changes
+ /// to the model and redraw/rebuild the control so that the users action makes sense.
+ ///
+ ///
+ /// Users of this class should listen for the CanDrop event to decide
+ /// if models from another OLV can be moved to OLV under this sink.
+ ///
+ ///
+ public class RearrangingDropSink : SimpleDropSink
+ {
+ ///
+ /// Create a RearrangingDropSink
+ ///
+ public RearrangingDropSink() {
+ this.CanDropBetween = true;
+ this.CanDropOnBackground = true;
+ this.CanDropOnItem = false;
+ }
+
+ ///
+ /// Create a RearrangingDropSink
+ ///
+ ///
+ public RearrangingDropSink(bool acceptDropsFromOtherLists)
+ : this() {
+ this.AcceptExternal = acceptDropsFromOtherLists;
+ }
+
+ ///
+ /// Trigger OnModelCanDrop
+ ///
+ ///
+ protected override void OnModelCanDrop(ModelDropEventArgs args) {
+ base.OnModelCanDrop(args);
+
+ if (args.Handled)
+ return;
+
+ args.Effect = DragDropEffects.Move;
+
+ // Don't allow drops from other list, if that's what's configured
+ if (!this.AcceptExternal && args.SourceListView != this.ListView) {
+ args.Effect = DragDropEffects.None;
+ args.DropTargetLocation = DropTargetLocation.None;
+ args.InfoMessage = "This list doesn't accept drops from other lists";
+ }
+
+ // If we are rearranging a list, don't allow drops on the background
+ if (args.DropTargetLocation == DropTargetLocation.Background && args.SourceListView == this.ListView) {
+ args.Effect = DragDropEffects.None;
+ args.DropTargetLocation = DropTargetLocation.None;
+ }
+ }
+
+ ///
+ /// Trigger OnModelDropped
+ ///
+ ///
+ protected override void OnModelDropped(ModelDropEventArgs args) {
+ base.OnModelDropped(args);
+
+ if (!args.Handled)
+ this.RearrangeModels(args);
+ }
+
+ ///
+ /// Do the work of processing the dropped items
+ ///
+ ///
+ public virtual void RearrangeModels(ModelDropEventArgs args) {
+ switch (args.DropTargetLocation) {
+ case DropTargetLocation.AboveItem:
+ this.ListView.MoveObjects(args.DropTargetIndex, args.SourceModels);
+ break;
+ case DropTargetLocation.BelowItem:
+ this.ListView.MoveObjects(args.DropTargetIndex + 1, args.SourceModels);
+ break;
+ case DropTargetLocation.Background:
+ this.ListView.AddObjects(args.SourceModels);
+ break;
+ default:
+ return;
+ }
+
+ if (args.SourceListView != this.ListView) {
+ args.SourceListView.RemoveObjects(args.SourceModels);
+ }
+ }
+ }
+
+ ///
+ /// When a drop sink needs to know if something can be dropped, or
+ /// to notify that a drop has occured, it uses an instance of this class.
+ ///
+ public class OlvDropEventArgs : EventArgs
+ {
+ ///
+ /// Create a OlvDropEventArgs
+ ///
+ public OlvDropEventArgs() {
+ }
+
+ #region Data Properties
+
+ ///
+ /// Get the original drag-drop event args
+ ///
+ public DragEventArgs DragEventArgs
+ {
+ get { return this.dragEventArgs; }
+ internal set { this.dragEventArgs = value; }
+ }
+ private DragEventArgs dragEventArgs;
+
+ ///
+ /// Get the data object that is being dragged
+ ///
+ public object DataObject
+ {
+ get { return this.dataObject; }
+ internal set { this.dataObject = value; }
+ }
+ private object dataObject;
+
+ ///
+ /// Get the drop sink that originated this event
+ ///
+ public SimpleDropSink DropSink {
+ get { return this.dropSink; }
+ internal set { this.dropSink = value; }
+ }
+ private SimpleDropSink dropSink;
+
+ ///
+ /// Get or set the index of the item that is the target of the drop
+ ///
+ public int DropTargetIndex {
+ get { return dropTargetIndex; }
+ set { this.dropTargetIndex = value; }
+ }
+ private int dropTargetIndex = -1;
+
+ ///
+ /// Get or set the location of the target of the drop
+ ///
+ public DropTargetLocation DropTargetLocation {
+ get { return dropTargetLocation; }
+ set { this.dropTargetLocation = value; }
+ }
+ private DropTargetLocation dropTargetLocation;
+
+ ///
+ /// Get or set the index of the subitem that is the target of the drop
+ ///
+ public int DropTargetSubItemIndex {
+ get { return dropTargetSubItemIndex; }
+ set { this.dropTargetSubItemIndex = value; }
+ }
+ private int dropTargetSubItemIndex = -1;
+
+ ///
+ /// Get the item that is the target of the drop
+ ///
+ public OLVListItem DropTargetItem {
+ get {
+ return this.ListView.GetItem(this.DropTargetIndex);
+ }
+ set {
+ if (value == null)
+ this.DropTargetIndex = -1;
+ else
+ this.DropTargetIndex = value.Index;
+ }
+ }
+
+ ///
+ /// Get or set the drag effect that should be used for this operation
+ ///
+ public DragDropEffects Effect {
+ get { return this.effect; }
+ set { this.effect = value; }
+ }
+ private DragDropEffects effect;
+
+ ///
+ /// Get or set if this event was handled. No further processing will be done for a handled event.
+ ///
+ public bool Handled {
+ get { return this.handled; }
+ set { this.handled = value; }
+ }
+ private bool handled;
+
+ ///
+ /// Get or set the feedback message for this operation
+ ///
+ ///
+ /// If this is not null, it will be displayed as a feedback message
+ /// during the drag.
+ ///
+ public string InfoMessage {
+ get { return this.infoMessage; }
+ set { this.infoMessage = value; }
+ }
+ private string infoMessage;
+
+ ///
+ /// Get the ObjectListView that is being dropped on
+ ///
+ public ObjectListView ListView {
+ get { return this.listView; }
+ internal set { this.listView = value; }
+ }
+ private ObjectListView listView;
+
+ ///
+ /// Get the location of the mouse (in target ListView co-ords)
+ ///
+ public Point MouseLocation {
+ get { return this.mouseLocation; }
+ internal set { this.mouseLocation = value; }
+ }
+ private Point mouseLocation;
+
+ ///
+ /// Get the drop action indicated solely by the state of the modifier keys
+ ///
+ public DragDropEffects StandardDropActionFromKeys {
+ get {
+ return this.DropSink.CalculateStandardDropActionFromKeys();
+ }
+ }
+
+ #endregion
+ }
+
+ ///
+ /// These events are triggered when the drag source is an ObjectListView.
+ ///
+ public class ModelDropEventArgs : OlvDropEventArgs
+ {
+ ///
+ /// Create a ModelDropEventArgs
+ ///
+ public ModelDropEventArgs()
+ {
+ }
+
+ ///
+ /// Gets the model objects that are being dragged.
+ ///
+ public IList SourceModels {
+ get { return this.dragModels; }
+ internal set {
+ this.dragModels = value;
+ TreeListView tlv = this.SourceListView as TreeListView;
+ if (tlv != null) {
+ foreach (object model in this.SourceModels) {
+ object parent = tlv.GetParent(model);
+ if (!toBeRefreshed.Contains(parent))
+ toBeRefreshed.Add(parent);
+ }
+ }
+ }
+ }
+ private IList dragModels;
+ private ArrayList toBeRefreshed = new ArrayList();
+
+ ///
+ /// Gets the ObjectListView that is the source of the dragged objects.
+ ///
+ public ObjectListView SourceListView {
+ get { return this.sourceListView; }
+ internal set { this.sourceListView = value; }
+ }
+ private ObjectListView sourceListView;
+
+ ///
+ /// Get the model object that is being dropped upon.
+ ///
+ /// This is only value for TargetLocation == Item
+ public object TargetModel {
+ get { return this.targetModel; }
+ internal set { this.targetModel = value; }
+ }
+ private object targetModel;
+
+ ///
+ /// Refresh all the objects involved in the operation
+ ///
+ public void RefreshObjects() {
+
+ toBeRefreshed.AddRange(this.SourceModels);
+ TreeListView tlv = this.SourceListView as TreeListView;
+ if (tlv == null)
+ this.SourceListView.RefreshObjects(toBeRefreshed);
+ else
+ tlv.RebuildAll(true);
+
+ TreeListView tlv2 = this.ListView as TreeListView;
+ if (tlv2 == null)
+ this.ListView.RefreshObject(this.TargetModel);
+ else
+ tlv2.RebuildAll(true);
+ }
+ }
+}
diff --git a/DragDrop/OLVDataObject.cs b/DragDrop/OLVDataObject.cs
new file mode 100644
index 0000000..8047554
--- /dev/null
+++ b/DragDrop/OLVDataObject.cs
@@ -0,0 +1,185 @@
+/*
+ * OLVDataObject.cs - An OLE DataObject that knows how to convert rows of an OLV to text and HTML
+ *
+ * Author: Phillip Piper
+ * Date: 2011-03-29 3:34PM
+ *
+ * Change log:
+ * v2.8
+ * 2014-05-02 JPP - When the listview is completely empty, don't try to set CSV text in the clipboard.
+ * v2.6
+ * 2012-08-08 JPP - Changed to use OLVExporter.
+ * - Added CSV to formats exported to Clipboard
+ * v2.4
+ * 2011-03-29 JPP - Initial version
+ *
+ * Copyright (C) 2011-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.Windows.Forms;
+
+namespace BrightIdeasSoftware {
+
+ ///
+ /// A data transfer object that knows how to transform a list of model
+ /// objects into a text and HTML representation.
+ ///
+ public class OLVDataObject : DataObject {
+ #region Life and death
+
+ ///
+ /// Create a data object from the selected objects in the given ObjectListView
+ ///
+ /// The source of the data object
+ public OLVDataObject(ObjectListView olv)
+ : this(olv, olv.SelectedObjects) {
+ }
+
+ ///
+ /// Create a data object which operates on the given model objects
+ /// in the given ObjectListView
+ ///
+ /// The source of the data object
+ /// The model objects to be put into the data object
+ public OLVDataObject(ObjectListView olv, IList modelObjects) {
+ this.objectListView = olv;
+ this.modelObjects = modelObjects;
+ this.includeHiddenColumns = olv.IncludeHiddenColumnsInDataTransfer;
+ this.includeColumnHeaders = olv.IncludeColumnHeadersInCopy;
+ this.CreateTextFormats();
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets whether hidden columns will also be included in the text
+ /// and HTML representation. If this is false, only visible columns will
+ /// be included.
+ ///
+ public bool IncludeHiddenColumns {
+ get { return includeHiddenColumns; }
+ }
+ private readonly bool includeHiddenColumns;
+
+ ///
+ /// Gets or sets whether column headers will also be included in the text
+ /// and HTML representation.
+ ///
+ public bool IncludeColumnHeaders {
+ get { return includeColumnHeaders; }
+ }
+ private readonly bool includeColumnHeaders;
+
+ ///
+ /// Gets the ObjectListView that is being used as the source of the data
+ ///
+ public ObjectListView ListView {
+ get { return objectListView; }
+ }
+ private readonly ObjectListView objectListView;
+
+ ///
+ /// Gets the model objects that are to be placed in the data object
+ ///
+ public IList ModelObjects {
+ get { return modelObjects; }
+ }
+ private readonly IList modelObjects;
+
+ #endregion
+
+ ///
+ /// Put a text and HTML representation of our model objects
+ /// into the data object.
+ ///
+ public void CreateTextFormats() {
+
+ OLVExporter exporter = this.CreateExporter();
+
+ // Put both the text and html versions onto the clipboard.
+ // For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format,
+ // but using SetData() does.
+ //this.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
+ this.SetData(exporter.ExportTo(OLVExporter.ExportFormat.TabSeparated));
+ string exportTo = exporter.ExportTo(OLVExporter.ExportFormat.CSV);
+ if (!String.IsNullOrEmpty(exportTo))
+ this.SetText(exportTo, TextDataFormat.CommaSeparatedValue);
+ this.SetText(ConvertToHtmlFragment(exporter.ExportTo(OLVExporter.ExportFormat.HTML)), TextDataFormat.Html);
+ }
+
+ ///
+ /// Create an exporter for the data contained in this object
+ ///
+ ///
+ protected OLVExporter CreateExporter() {
+ OLVExporter exporter = new OLVExporter(this.ListView);
+ exporter.IncludeColumnHeaders = this.IncludeColumnHeaders;
+ exporter.IncludeHiddenColumns = this.IncludeHiddenColumns;
+ exporter.ModelObjects = this.ModelObjects;
+ return exporter;
+ }
+
+ ///
+ /// Make a HTML representation of our model objects
+ ///
+ [Obsolete("Use OLVExporter directly instead", false)]
+ public string CreateHtml() {
+ OLVExporter exporter = this.CreateExporter();
+ return exporter.ExportTo(OLVExporter.ExportFormat.HTML);
+ }
+
+ ///
+ /// 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
+ const string SOURCE = "http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView";
+
+ 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, StringComparison.Ordinal);
+ int endFragment = startFragment + fragment.Length;
+
+ return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, SOURCE, html);
+ }
+ }
+}
diff --git a/FastDataListView.cs b/FastDataListView.cs
new file mode 100644
index 0000000..785dcfb
--- /dev/null
+++ b/FastDataListView.cs
@@ -0,0 +1,140 @@
+/*
+ * FastDataListView - A data bindable listview that has the speed of a virtual list
+ *
+ * Author: Phillip Piper
+ * Date: 22/09/2010 8:11 AM
+ *
+ * Change log:
+ * 2010-09-22 JPP - Initial version
+ *
+ * Copyright (C) 2006-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Data;
+using System.ComponentModel;
+using System.Windows.Forms;
+using System.Drawing.Design;
+
+namespace BrightIdeasSoftware
+{
+ ///
+ /// A FastDataListView virtualizes the display of data from a DataSource. It operates on
+ /// DataSets and DataTables in the same way as a DataListView, but does so much more efficiently.
+ ///
+ ///
+ ///
+ /// A FastDataListView still has to load all its data from the DataSource. If you have SQL statement
+ /// that returns 1 million rows, all 1 million rows will still need to read from the database.
+ /// However, once the rows are loaded, the FastDataListView will only build rows as they are displayed.
+ ///
+ ///
+ public class FastDataListView : FastObjectListView
+ {
+ #region Public Properties
+
+ ///
+ /// Gets or sets whether or not columns will be automatically generated to show the
+ /// columns when the DataSource is set.
+ ///
+ /// This must be set before the DataSource is set. It has no effect afterwards.
+ [Category("Data"),
+ Description("Should the control automatically generate columns from the DataSource"),
+ DefaultValue(true)]
+ public bool AutoGenerateColumns
+ {
+ get { return this.Adapter.AutoGenerateColumns; }
+ set { this.Adapter.AutoGenerateColumns = value; }
+ }
+
+ ///
+ /// Get or set the VirtualListDataSource that will be displayed in this list view.
+ ///
+ /// The VirtualListDataSource 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
+ /// VirtualListDataSource 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 virtual Object DataSource {
+ get { return this.Adapter.DataSource; }
+ set { this.Adapter.DataSource = value; }
+ }
+
+ ///
+ /// 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 virtual string DataMember {
+ get { return this.Adapter.DataMember; }
+ set { this.Adapter.DataMember = value; }
+ }
+
+ #endregion
+
+ #region Implementation properties
+
+ ///
+ /// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
+ /// for data binding.
+ ///
+ protected DataSourceAdapter Adapter {
+ get {
+ if (adapter == null)
+ adapter = this.CreateDataSourceAdapter();
+ return adapter;
+ }
+ set { adapter = value; }
+ }
+ private DataSourceAdapter adapter;
+
+ #endregion
+
+ #region Implementation
+
+ ///
+ /// Create the DataSourceAdapter that this control will use.
+ ///
+ /// A DataSourceAdapter configured for this list
+ /// Subclasses should overrride this to create their
+ /// own specialized adapters
+ protected virtual DataSourceAdapter CreateDataSourceAdapter() {
+ return new DataSourceAdapter(this);
+ }
+
+ #endregion
+ }
+}
diff --git a/FastObjectListView.cs b/FastObjectListView.cs
new file mode 100644
index 0000000..eba2102
--- /dev/null
+++ b/FastObjectListView.cs
@@ -0,0 +1,360 @@
+/*
+ * FastObjectListView - A listview that behaves like an ObjectListView but has the speed of a virtual list
+ *
+ * Author: Phillip Piper
+ * Date: 27/09/2008 9:15 AM
+ *
+ * Change log:
+ * 2012-06-11 JPP - Added more efficient version of FilteredObjects
+ * v2.5.1
+ * 2011-04-25 JPP - Fixed problem with removing objects from filtered or sorted list
+ * v2.4
+ * 2010-04-05 JPP - Added filtering
+ * v2.3
+ * 2009-08-27 JPP - Added GroupingStrategy
+ * - Added optimized Objects property
+ * v2.2.1
+ * 2009-01-07 JPP - Made all public and protected methods virtual
+ * 2008-09-27 JPP - Separated from ObjectListView.cs
+ *
+ * Copyright (C) 2006-2014 Phillip Piper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Windows.Forms;
+
+namespace BrightIdeasSoftware
+{
+ ///
+ /// 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 many 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:
+ ///
+ /// use Tile view
+ /// show groups on XP
+ ///
+ ///
+ ///
+ public class FastObjectListView : VirtualObjectListView
+ {
+ ///
+ /// Make a FastObjectListView
+ ///
+ public FastObjectListView() {
+ this.VirtualListDataSource = new FastObjectListDataSource(this);
+ this.GroupingStrategy = new FastListGroupingStrategy();
+ }
+
+ ///
+ /// Gets the collection of objects that survive any filtering that may be in place.
+ ///
+ [Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public override IEnumerable FilteredObjects {
+ get {
+ // This is much faster than the base method
+ return ((FastObjectListDataSource)this.VirtualListDataSource).FilteredObjectList;
+ }
+ }
+
+ ///
+ /// Get/set the collection of objects that this list will show
+ ///
+ ///
+ ///
+ /// The contents of the control will be updated immediately after setting this property.
+ ///
+ /// 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.
+ /// This method is not thread safe.
+ ///
+ [Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public override IEnumerable Objects {
+ get {
+ // This is much faster than the base method
+ return ((FastObjectListDataSource)this.VirtualListDataSource).ObjectList;
+ }
+ set { base.Objects = value; }
+ }
+
+ ///
+ /// Remove any sorting and revert to the given order of the model objects
+ ///
+ /// To be really honest, Unsort() doesn't work on FastObjectListViews since
+ /// the original ordering of model objects is lost when Sort() is called. So this method
+ /// effectively just turns off sorting.
+ public override void Unsort() {
+ this.ShowGroups = false;
+ this.PrimarySortColumn = null;
+ this.PrimarySortOrder = SortOrder.None;
+ this.SetObjects(this.Objects);
+ }
+ }
+
+ ///
+ /// Provide a data source for a FastObjectListView
+ ///
+ ///
+ /// This class isn't intended to be used directly, but it is left as a public
+ /// class just in case someone wants to subclass it.
+ ///
+ public class FastObjectListDataSource : AbstractVirtualListDataSource
+ {
+ ///
+ /// Create a FastObjectListDataSource
+ ///
+ ///
+ public FastObjectListDataSource(FastObjectListView listView)
+ : base(listView) {
+ }
+
+ #region IVirtualListDataSource Members
+
+ ///
+ /// Get n'th object
+ ///
+ ///
+ ///
+ public override object GetNthObject(int n) {
+ if (n >= 0 && n < this.filteredObjectList.Count)
+ return this.filteredObjectList[n];
+
+ return null;
+ }
+
+ ///
+ /// How many items are in the data source
+ ///
+ ///
+ public override int GetObjectCount() {
+ return this.filteredObjectList.Count;
+ }
+
+ ///
+ /// Get the index of the given model
+ ///
+ ///
+ ///
+ public override int GetObjectIndex(object model) {
+ int index;
+
+ if (model != null && this.objectsToIndexMap.TryGetValue(model, out index))
+ return index;
+
+ return -1;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override int SearchText(string text, int first, int last, OLVColumn column) {
+ if (first <= last) {
+ for (int i = first; i <= last; i++) {
+ string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
+ if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
+ return i;
+ }
+ } else {
+ for (int i = first; i >= last; i--) {
+ string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
+ if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override void Sort(OLVColumn column, SortOrder sortOrder) {
+ if (sortOrder != SortOrder.None) {
+ ModelObjectComparer comparer = new ModelObjectComparer(column, sortOrder, this.listView.SecondarySortColumn, this.listView.SecondarySortOrder);
+ this.fullObjectList.Sort(comparer);
+ this.filteredObjectList.Sort(comparer);
+ }
+ this.RebuildIndexMap();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override void AddObjects(ICollection modelObjects) {
+ foreach (object modelObject in modelObjects) {
+ if (modelObject != null)
+ this.fullObjectList.Add(modelObject);
+ }
+ this.FilterObjects();
+ this.RebuildIndexMap();
+ }
+
+ ///
+ /// Remove the given collection of models from this source.
+ ///
+ ///
+ public override void RemoveObjects(ICollection modelObjects) {
+ List indicesToRemove = new List();
+ foreach (object modelObject in modelObjects) {
+ int i = this.GetObjectIndex(modelObject);
+ if (i >= 0)
+ indicesToRemove.Add(i);
+
+ // Remove the objects from the unfiltered list
+ this.fullObjectList.Remove(modelObject);
+ }
+
+ // Sort the indices from highest to lowest so that we
+ // remove latter ones before earlier ones. In this way, the
+ // indices of the rows doesn't change after the deletes.
+ indicesToRemove.Sort();
+ indicesToRemove.Reverse();
+
+ foreach (int i in indicesToRemove)
+ this.listView.SelectedIndices.Remove(i);
+
+ this.FilterObjects();
+ this.RebuildIndexMap();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override void SetObjects(IEnumerable collection) {
+ ArrayList newObjects = ObjectListView.EnumerableToArray(collection, true);
+
+ this.fullObjectList = newObjects;
+ this.FilterObjects();
+ this.RebuildIndexMap();
+ }
+
+ ///
+ /// Update/replace the nth object with the given object
+ ///
+ ///
+ ///
+ public override void UpdateObject(int index, object modelObject) {
+ if (index < 0 || index >= this.filteredObjectList.Count)
+ return;
+
+ int i = this.fullObjectList.IndexOf(this.filteredObjectList[index]);
+ if (i < 0)
+ return;
+
+ this.fullObjectList[i] = modelObject;
+ this.filteredObjectList[index] = modelObject;
+ this.objectsToIndexMap[modelObject] = index;
+ }
+
+ private ArrayList fullObjectList = new ArrayList();
+ private ArrayList filteredObjectList = new ArrayList();
+ private IModelFilter modelFilter;
+ private IListFilter listFilter;
+
+ #endregion
+
+ #region IFilterableDataSource Members
+
+ ///
+ /// Apply the given filters to this data source. One or both may be null.
+ ///
+ ///
+ ///
+ public override void ApplyFilters(IModelFilter iModelFilter, IListFilter iListFilter) {
+ this.modelFilter = iModelFilter;
+ this.listFilter = iListFilter;
+ this.SetObjects(this.fullObjectList);
+ }
+
+ #endregion
+
+ #region Implementation
+
+ ///
+ /// Gets the full list of objects being used for this fast list.
+ /// This list is unfiltered.
+ ///
+ public ArrayList ObjectList {
+ get { return fullObjectList; }
+ }
+
+ ///
+ /// Gets the list of objects from ObjectList which survive any installed filters.
+ ///
+ public ArrayList FilteredObjectList {
+ get { return filteredObjectList; }
+ }
+
+ ///
+ /// Rebuild the map that remembers which model object is displayed at which line
+ ///
+ protected void RebuildIndexMap() {
+ this.objectsToIndexMap.Clear();
+ for (int i = 0; i < this.filteredObjectList.Count; i++)
+ this.objectsToIndexMap[this.filteredObjectList[i]] = i;
+ }
+ readonly Dictionary