From 7c97b9e539c6157c2f47e09c0a6de167986fafec Mon Sep 17 00:00:00 2001
From: Daniel Brunner <0xFEEDC0DE64@gmail.com>
Date: Sat, 4 Feb 2017 13:47:58 +0100
Subject: [PATCH] Imported existing sources
---
.gitattributes | 63 +
CellEditing/CellEditKeyEngine.cs | 520 +
CellEditing/CellEditors.cs | 288 +
CellEditing/EditorRegistry.cs | 203 +
CustomDictionary.xml | 46 +
DataListView.cs | 199 +
DataTreeListView.cs | 226 +
DragDrop/DragSource.cs | 219 +
DragDrop/DropSink.cs | 1435 +++
DragDrop/OLVDataObject.cs | 185 +
FastDataListView.cs | 140 +
FastObjectListView.cs | 360 +
Filtering/Cluster.cs | 125 +
Filtering/ClusteringStrategy.cs | 189 +
Filtering/ClustersFromGroupsStrategy.cs | 70 +
Filtering/DateTimeClusteringStrategy.cs | 187 +
Filtering/FilterMenuBuilder.cs | 369 +
Filtering/Filters.cs | 476 +
Filtering/FlagClusteringStrategy.cs | 160 +
Filtering/ICluster.cs | 56 +
Filtering/IClusteringStrategy.cs | 80 +
Filtering/TextMatchFilter.cs | 623 ++
FullClassDiagram.cd | 1261 +++
Implementation/Attributes.cs | 335 +
Implementation/Comparers.cs | 291 +
Implementation/DataSourceAdapter.cs | 613 ++
Implementation/Delegates.cs | 151 +
Implementation/DragSource.cs | 407 +
Implementation/DropSink.cs | 1402 +++
Implementation/Enums.cs | 99 +
Implementation/Events.cs | 2422 +++++
Implementation/GroupingParameters.cs | 175 +
Implementation/Groups.cs | 747 ++
Implementation/Munger.cs | 568 ++
Implementation/NativeMethods.cs | 1226 +++
Implementation/NullableDictionary.cs | 87 +
Implementation/OLVListItem.cs | 272 +
Implementation/OLVListSubItem.cs | 162 +
Implementation/OlvListViewHitTestInfo.cs | 383 +
Implementation/TreeDataSourceAdapter.cs | 262 +
Implementation/VirtualGroups.cs | 355 +
Implementation/VirtualListDataSource.cs | 334 +
LICENSE | 674 ++
OLVColumn.cs | 1672 ++++
ObjectListView.DesignTime.cs | 550 +
ObjectListView.FxCop | 3521 +++++++
ObjectListView.cs | 10482 ++++++++++++++++++++
ObjectListView.shfb | 47 +
ObjectListView2005.csproj | 178 +
ObjectListView2008.csproj | 188 +
ObjectListView2008.ncrunchproject | 16 +
ObjectListView2010.csproj | 188 +
ObjectListView2010.ncrunchproject | 27 +
ObjectListView2012.csproj | 198 +
ObjectListView2012.ncrunchproject | 22 +
ObjectListView2012.nuspec | 19 +
ObjectListView2012.v2.ncrunchproject | 22 +
Properties/AssemblyInfo.cs | 35 +
Properties/Resources.Designer.cs | 98 +
Properties/Resources.resx | 137 +
README.md | 2 +
Rendering/Adornments.cs | 743 ++
Rendering/Decorations.cs | 820 ++
Rendering/Overlays.cs | 302 +
Rendering/Renderers.cs | 3413 +++++++
Rendering/Styles.cs | 400 +
Rendering/TreeRenderer.cs | 248 +
Resources/clear-filter.png | Bin 0 -> 1381 bytes
Resources/coffee.jpg | Bin 0 -> 73464 bytes
Resources/filter-icons3.png | Bin 0 -> 1305 bytes
Resources/filter.png | Bin 0 -> 1331 bytes
Resources/sort-ascending.png | Bin 0 -> 1364 bytes
Resources/sort-descending.png | Bin 0 -> 1371 bytes
SubControls/GlassPanelForm.cs | 459 +
SubControls/HeaderControl.cs | 1218 +++
SubControls/ToolStripCheckedListBox.cs | 189 +
SubControls/ToolTipControl.cs | 699 ++
TreeListView.cs | 2128 ++++
Utilities/ColumnSelectionForm.Designer.cs | 190 +
Utilities/ColumnSelectionForm.cs | 263 +
Utilities/ColumnSelectionForm.resx | 120 +
Utilities/Generator.cs | 561 ++
Utilities/OLVExporter.cs | 277 +
Utilities/TypedObjectListView.cs | 561 ++
VirtualObjectListView.cs | 1227 +++
olv-keyfile.snk | Bin 0 -> 596 bytes
86 files changed, 49665 insertions(+)
create mode 100644 .gitattributes
create mode 100644 CellEditing/CellEditKeyEngine.cs
create mode 100644 CellEditing/CellEditors.cs
create mode 100644 CellEditing/EditorRegistry.cs
create mode 100644 CustomDictionary.xml
create mode 100644 DataListView.cs
create mode 100644 DataTreeListView.cs
create mode 100644 DragDrop/DragSource.cs
create mode 100644 DragDrop/DropSink.cs
create mode 100644 DragDrop/OLVDataObject.cs
create mode 100644 FastDataListView.cs
create mode 100644 FastObjectListView.cs
create mode 100644 Filtering/Cluster.cs
create mode 100644 Filtering/ClusteringStrategy.cs
create mode 100644 Filtering/ClustersFromGroupsStrategy.cs
create mode 100644 Filtering/DateTimeClusteringStrategy.cs
create mode 100644 Filtering/FilterMenuBuilder.cs
create mode 100644 Filtering/Filters.cs
create mode 100644 Filtering/FlagClusteringStrategy.cs
create mode 100644 Filtering/ICluster.cs
create mode 100644 Filtering/IClusteringStrategy.cs
create mode 100644 Filtering/TextMatchFilter.cs
create mode 100644 FullClassDiagram.cd
create mode 100644 Implementation/Attributes.cs
create mode 100644 Implementation/Comparers.cs
create mode 100644 Implementation/DataSourceAdapter.cs
create mode 100644 Implementation/Delegates.cs
create mode 100644 Implementation/DragSource.cs
create mode 100644 Implementation/DropSink.cs
create mode 100644 Implementation/Enums.cs
create mode 100644 Implementation/Events.cs
create mode 100644 Implementation/GroupingParameters.cs
create mode 100644 Implementation/Groups.cs
create mode 100644 Implementation/Munger.cs
create mode 100644 Implementation/NativeMethods.cs
create mode 100644 Implementation/NullableDictionary.cs
create mode 100644 Implementation/OLVListItem.cs
create mode 100644 Implementation/OLVListSubItem.cs
create mode 100644 Implementation/OlvListViewHitTestInfo.cs
create mode 100644 Implementation/TreeDataSourceAdapter.cs
create mode 100644 Implementation/VirtualGroups.cs
create mode 100644 Implementation/VirtualListDataSource.cs
create mode 100644 LICENSE
create mode 100644 OLVColumn.cs
create mode 100644 ObjectListView.DesignTime.cs
create mode 100644 ObjectListView.FxCop
create mode 100644 ObjectListView.cs
create mode 100644 ObjectListView.shfb
create mode 100644 ObjectListView2005.csproj
create mode 100644 ObjectListView2008.csproj
create mode 100644 ObjectListView2008.ncrunchproject
create mode 100644 ObjectListView2010.csproj
create mode 100644 ObjectListView2010.ncrunchproject
create mode 100644 ObjectListView2012.csproj
create mode 100644 ObjectListView2012.ncrunchproject
create mode 100644 ObjectListView2012.nuspec
create mode 100644 ObjectListView2012.v2.ncrunchproject
create mode 100644 Properties/AssemblyInfo.cs
create mode 100644 Properties/Resources.Designer.cs
create mode 100644 Properties/Resources.resx
create mode 100644 Rendering/Adornments.cs
create mode 100644 Rendering/Decorations.cs
create mode 100644 Rendering/Overlays.cs
create mode 100644 Rendering/Renderers.cs
create mode 100644 Rendering/Styles.cs
create mode 100644 Rendering/TreeRenderer.cs
create mode 100644 Resources/clear-filter.png
create mode 100644 Resources/coffee.jpg
create mode 100644 Resources/filter-icons3.png
create mode 100644 Resources/filter.png
create mode 100644 Resources/sort-ascending.png
create mode 100644 Resources/sort-descending.png
create mode 100644 SubControls/GlassPanelForm.cs
create mode 100644 SubControls/HeaderControl.cs
create mode 100644 SubControls/ToolStripCheckedListBox.cs
create mode 100644 SubControls/ToolTipControl.cs
create mode 100644 TreeListView.cs
create mode 100644 Utilities/ColumnSelectionForm.Designer.cs
create mode 100644 Utilities/ColumnSelectionForm.cs
create mode 100644 Utilities/ColumnSelectionForm.resx
create mode 100644 Utilities/Generator.cs
create mode 100644 Utilities/OLVExporter.cs
create mode 100644 Utilities/TypedObjectListView.cs
create mode 100644 VirtualObjectListView.cs
create mode 100644 olv-keyfile.snk
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