PageRenderTime 54ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 2ms

/ObjectListView/ObjectListView.cs

https://bitbucket.org/BlueRaja/starbank
C# | 9075 lines | 4805 code | 980 blank | 3290 comment | 1146 complexity | e2e03e0bad28347f094bdfcc5b9c1412 MD5 | raw file
  1. /*
  2. * ObjectListView - A listview to show various aspects of a collection of objects
  3. *
  4. * Author: Phillip Piper
  5. * Date: 9/10/2006 11:15 AM
  6. *
  7. * Change log
  8. * 2011-05-31 JPP - SelectObject() and SelectObjects() no longer deselect all other rows.
  9. Set the SelectedObject or SelectedObjects property to do that.
  10. * - Added CheckedObjectsEnumerable
  11. * - Made setting CheckedObjects more efficient on large collections
  12. * - Deprecated GetSelectedObject() and GetSelectedObjects()
  13. * 2011-04-25 JPP - Added SubItemChecking event
  14. * - Fixed bug in handling of NewValue on CellEditFinishing event
  15. * 2011-04-12 JPP - Added UseFilterIndicator
  16. * - Added some more localizable messages
  17. * 2011-04-10 JPP - FormatCellEventArgs now has a CellValue property, which is the model value displayed
  18. * by the cell. For example, for the Birthday column, the CellValue might be
  19. * DateTime(1980, 12, 31), whereas the cell's text might be 'Dec 31, 1980'.
  20. * 2011-04-04 JPP - Tweaked UseTranslucentSelection and UseTranslucentHotItem to look (a little) more
  21. * like Vista/Win7.
  22. * - Alternate colours are now only applied in Details view (as they always should have been)
  23. * - Alternate colours are now correctly recalculated after removing objects
  24. * 2011-03-29 JPP - Added SelectColumnsOnRightClickBehaviour to allow the selecting of columns mechanism
  25. * to be changed. Can now be InlineMenu (the default), SubMenu, or ModelDialog.
  26. * - ColumnSelectionForm was moved from the demo into the ObjectListView project itself.
  27. * - Ctrl-C copying is now able to use the DragSource to create the data transfer object.
  28. * 2011-03-19 JPP - All model object comparisons now use Equals rather than == (thanks to vulkanino)
  29. * - [Small Break] GetNextItem() and GetPreviousItem() now accept and return OLVListView
  30. * rather than ListViewItems.
  31. * 2011-03-07 JPP - [Big] Added Excel-style filtering. Right click on a header to show a Filtering menu.
  32. * - Added CellEditKeyEngine to allow key handling when cell editing to be completely customised.
  33. * Add CellEditTabChangesRows and CellEditEnterChangesRows to show some of these abilities.
  34. * 2011-03-06 JPP - Added OLVColumn.AutoCompleteEditorMode in preference to AutoCompleteEditor
  35. * (which is now just a wrapper). Thanks to Clive Haskins
  36. * - Added lots of docs to new classes
  37. * 2011-02-25 JPP - Preserve word wrap settings on TreeListView
  38. * - Resize last group to keep it on screen (thanks to ?)
  39. * 2010-11-16 JPP - Fixed (once and for all) DisplayIndex problem with Generator
  40. * - Changed the serializer used in SaveState()/RestoreState() so that it resolves on
  41. * class name alone.
  42. * - Fixed bug in GroupWithItemCountSingularFormatOrDefault
  43. * - Fixed strange flickering in grouped, owner drawn OLV's using RefreshObject()
  44. * v2.4.1
  45. * 2010-08-25 JPP - Fixed bug where setting OLVColumn.CheckBoxes to false gave it a renderer
  46. * specialized for checkboxes. Oddly, this made Generator created owner drawn
  47. * lists appear to be completely empty.
  48. * - In IDE, all ObjectListView properties are now in a single "ObjectListView" category,
  49. * rather than splitting them between "Appearance" and "Behavior" categories.
  50. * - Added GroupingParameters.GroupComparer to allow groups to be sorted in a customizable fashion.
  51. * - Sorting of items within a group can be disabled by setting
  52. * GroupingParameters.PrimarySortOrder to None.
  53. * 2010-08-24 JPP - Added OLVColumn.IsHeaderVertical to make a column draw its header vertical.
  54. * - Added OLVColumn.HeaderTextAlign to control the alignment of a column's header text.
  55. * - Added HeaderMaximumHeight to limit how tall the header section can become
  56. * 2010-08-18 JPP - Fixed long standing bug where having 0 columns caused a InvalidCast exception.
  57. * - Added IncludeAllColumnsInDataObject property
  58. * - Improved BuildList(bool) so that it preserves scroll position even when
  59. * the listview is grouped.
  60. * 2010-08-08 JPP - Added OLVColumn.HeaderImageKey to allow column headers to have an image.
  61. * - CellEdit validation and finish events now have NewValue property.
  62. * 2010-08-03 JPP - Subitem checkboxes improvments: obey IsEditable, can be hot, can be disabled.
  63. * - No more flickering of selection when tabbing between cells
  64. * - Added EditingCellBorderDecoration to make it clearer which cell is being edited.
  65. * 2010-08-01 JPP - Added ObjectListView.SmoothingMode to control the smoothing of all graphics
  66. * operations
  67. * - Columns now cache their group item format strings so that they still work as
  68. * grouping columns after they have been removed from the listview. This cached
  69. * value is only used when the column is not part of the listview.
  70. * 2010-07-25 JPP - Correctly trigger a Click event when the mouse is clicked.
  71. * 2010-07-16 JPP - Invalidate the control before and after cell editing to make sure it looks right
  72. * 2010-06-23 JPP - Right mouse clicks on checkboxes no longer confuse them
  73. * 2010-06-21 JPP - Avoid bug in underlying ListView control where virtual lists in SmallIcon view
  74. * generate GETTOOLINFO msgs with invalid item indicies.
  75. * - Fixed bug where FastObjectListView would throw an exception when showing hyperlinks
  76. * in any view except Details.
  77. * 2010-06-15 JPP - Fixed bug in ChangeToFilteredColumns() that resulted in column display order
  78. * being lost when a column was hidden.
  79. * - Renamed IsVista property to IsVistaOrLater which more accurately describes its function.
  80. * v2.4
  81. * 2010-04-14 JPP - Prevent object disposed errors when mouse event handlers cause the
  82. * ObjectListView to be destroyed (e.g. closing a form during a
  83. * double click event).
  84. * - Avoid checkbox munging bug in standard ListView when shift clicking on non-primary
  85. * columns when FullRowSelect is true.
  86. * 2010-04-12 JPP - Fixed bug in group sorting (thanks Mike).
  87. * 2010-04-07 JPP - Prevent hyperlink processing from triggering spurious MouseUp events.
  88. * This showed itself by launching the same url multiple times.
  89. * 2010-04-06 JPP - Space filling columns correctly resize upon initial display
  90. * - ShowHeaderInAllViews is better but still not working reliably.
  91. * See comments on property for more details.
  92. * 2010-03-23 JPP - Added ObjectListView.HeaderFormatStyle and OLVColumn.HeaderFormatStyle.
  93. * This makes HeaderFont and HeaderForeColor properties unnecessary --
  94. * they will be marked obsolete in the next version and removed after that.
  95. * 2010-03-16 JPP - Changed object checking so that objects can be pre-checked before they
  96. * are added to the list. Normal ObjectListViews managed "checkedness" in
  97. * the ListViewItem, so this won't work for them, unless check state getters
  98. * and putters have been installed. It will work on on virtual lists (thus fast lists and
  99. * tree views) since they manage their own check state.
  100. * 2010-03-06 JPP - Hide "Items" and "Groups" from the IDE properties grid since they shouldn't be set like that.
  101. * They can still be accessed through "Custom Commands" and there's nothing we can do
  102. * about that.
  103. * 2010-03-05 JPP - Added filtering
  104. * 2010-01-18 JPP - Overlays can be turned off. They also only work on 32-bit displays
  105. * v2.3
  106. * 2009-10-30 JPP - Plugged possible resource leak by using using() with CreateGraphics()
  107. * 2009-10-28 JPP - Fix bug when right clicking in the empty area of the header
  108. * 2009-10-20 JPP - Redraw the control after setting EmptyListMsg property
  109. * v2.3
  110. * 2009-09-30 JPP - Added Dispose() method to properly release resources
  111. * 2009-09-16 JPP - Added OwnerDrawnHeader, which you can set to true if you want to owner draw
  112. * the header yourself.
  113. * 2009-09-15 JPP - Added UseExplorerTheme, which allow complete visual compliance with Vista explorer.
  114. * But see property documentation for its many limitations.
  115. * - Added ShowHeaderInAllViews. To make this work, Columns are no longer
  116. * changed when switching to/from Tile view.
  117. * 2009-09-11 JPP - Added OLVColumn.AutoCompleteEditor to allow the autocomplete of cell editors
  118. * to be disabled.
  119. * 2009-09-01 JPP - Added ObjectListView.TextRenderingHint property which controls the
  120. * text rendering hint of all drawn text.
  121. * 2009-08-28 JPP - [BIG] Added group formatting to supercharge what is possible with groups
  122. * - [BIG] Virtual groups now work
  123. * - Extended MakeGroupies() to handle more aspects of group creation
  124. * 2009-08-19 JPP - Added ability to show basic column commands when header is right clicked
  125. * - Added SelectedRowDecoration, UseTranslucentSelection and UseTranslucentHotItem.
  126. * - Added PrimarySortColumn and PrimarySortOrder
  127. * 2009-08-15 JPP - Correct problems with standard hit test and subitems
  128. * 2009-08-14 JPP - [BIG] Support Decorations
  129. * - [BIG] Added header formatting capabilities: font, color, word wrap
  130. * - Gave ObjectListView its own designer to hide unwanted properties
  131. * - Separated design time stuff into separate file
  132. * - Added FormatRow and FormatCell events
  133. * 2009-08-09 JPP - Get around bug in HitTest when not FullRowSelect
  134. * - Added OLVListItem.GetSubItemBounds() method which works correctly
  135. * for all columns including column 0
  136. * 2009-08-07 JPP - Added Hot* properties that track where the mouse is
  137. * - Added HotItemChanged event
  138. * - Overrode TextAlign on columns so that column 0 can have something other
  139. * than just left alignment. This is only honored when owner drawn.
  140. * v2.2.1
  141. * 2009-08-03 JPP - Subitem edit rectangles always allowed for an image in the cell, even if there was none.
  142. * Now they only allow for an image when there actually is one.
  143. * - Added Bounds property to OLVListItem which handles items being part of collapsed groups.
  144. * 2009-07-29 JPP - Added GetSubItem() methods to ObjectListView and OLVListItem
  145. * 2009-07-26 JPP - Avoided bug in .NET framework involving column 0 of owner drawn listviews not being
  146. * redrawn when the listview was scrolled horizontally (this was a LOT of work to track
  147. * down and fix!)
  148. * - The cell edit rectangle is now correctly calculated when the listview is scrolled
  149. * horizontally.
  150. * 2009-07-14 JPP - If the user clicks/double clicks on a tree list cell, an edit operation will no longer begin
  151. * if the click was to the left of the expander. This is implemented in such a way that
  152. * other renderers can have similar "dead" zones.
  153. * 2009-07-11 JPP - CalculateCellBounds() messed with the FullRowSelect property, which confused the
  154. * tooltip handling on the underlying control. It no longer does this.
  155. * - The cell edit rectangle is now correctly calculated for owner-drawn, non-Details views.
  156. * 2009-07-08 JPP - Added Cell events (CellClicked, CellOver, CellRightClicked)
  157. * - Made BuildList(), AddObject() and RemoveObject() thread-safe
  158. * 2009-07-04 JPP - Space bar now properly toggles checkedness of selected rows
  159. * 2009-07-02 JPP - Fixed bug with tooltips when the underlying Windows control was destroyed.
  160. * - CellToolTipShowing events are now triggered in all views.
  161. * v2.2
  162. * 2009-06-02 JPP - BeforeSortingEventArgs now has a Handled property to let event handlers do
  163. * the item sorting themselves.
  164. * - AlwaysGroupByColumn works again, as does SortGroupItemsByPrimaryColumn and all their
  165. * various permutations.
  166. * - SecondarySortOrder and SecondarySortColumn are now "null" by default
  167. * 2009-05-15 JPP - Fixed bug so that KeyPress events are again triggered
  168. * 2009-05-10 JPP - Removed all unsafe code
  169. * 2009-05-07 JPP - Don't use glass panel for overlays when in design mode. It's too confusing.
  170. * 2009-05-05 JPP - Added Scroll event (thanks to Christophe Hosten for the complete patch to implement this)
  171. * - Added Unfocused foreground and background colors (also thanks to Christophe Hosten)
  172. * 2009-04-29 JPP - Added SelectedColumn property, which puts a slight tint on that column. Combine
  173. * this with TintSortColumn property and the sort column is automatically tinted.
  174. * - Use an overlay to implement "empty list" msg. Default empty list msg is now prettier.
  175. * 2009-04-28 JPP - Fixed bug where DoubleClick events were not triggered when CheckBoxes was true
  176. * 2009-04-23 JPP - Fixed various bugs under Vista.
  177. * - Made groups collapsible - Vista only. Thanks to Crustyapplesniffer.
  178. * - Forward events from DropSink to the control itself. This allows handlers to be defined
  179. * within the IDE for drop events
  180. * 2009-04-16 JPP - Made several properties localizable.
  181. * 2009-04-11 JPP - Correctly renderer checkboxes when RowHeight is non-standard
  182. * 2009-04-11 JPP - Implemented overlay architecture, based on CustomDraw scheme.
  183. * This unified drag drop feedback, empty list msgs and overlay images.
  184. * - Added OverlayImage and friends, which allows an image to be drawn
  185. * transparently over the listview
  186. * 2009-04-10 JPP - Fixed long-standing annoying flicker on owner drawn virtual lists!
  187. * This means, amongst other things, that grid lines no longer get confused,
  188. * and drag-select no longer flickers.
  189. * 2009-04-07 JPP - Calculate edit rectangles more accurately
  190. * 2009-04-06 JPP - Double-clicking no longer toggles the checkbox
  191. * - Double-clicking on a checkbox no longer confuses the checkbox
  192. * 2009-03-16 JPP - Optimized the build of autocomplete lists
  193. * v2.1
  194. * 2009-02-24 JPP - Fix bug where double-clicking VERY quickly on two different cells
  195. * could give two editors
  196. * - Maintain focused item when rebuilding list (SF #2547060)
  197. * 2009-02-22 JPP - Reworked checkboxes so that events are triggered for virtual lists
  198. * 2009-02-15 JPP - Added ObjectListView.ConfigureAutoComplete utility method
  199. * 2009-02-02 JPP - Fixed bug with AlwaysGroupByColumn where column header clicks would not resort groups.
  200. * 2009-02-01 JPP - OLVColumn.CheckBoxes and TriStateCheckBoxes now work.
  201. * 2009-01-28 JPP - Complete overhaul of renderers!
  202. * - Use IRenderer
  203. * - Added ObjectListView.ItemRenderer to draw whole items
  204. * 2009-01-23 JPP - Simple Checkboxes now work properly
  205. * - Added TriStateCheckBoxes property to control whether the user can
  206. * set the row checkbox to have the Indeterminate value
  207. * - CheckState property is now just a wrapper around the StateImageIndex property
  208. * 2009-01-20 JPP - Changed to always draw columns when owner drawn, rather than falling back on DrawDefault.
  209. * This simplified several owner drawn problems
  210. * - Added DefaultRenderer property to help with the above
  211. * - HotItem background color is applied to all cells even when FullRowSelect is false
  212. * - Allow grouping by CheckedAspectName columns
  213. * - Commented out experimental animations. Still needs work.
  214. * 2009-01-17 JPP - Added HotItemStyle and UseHotItem to highlight the row under the cursor
  215. * - Added UseCustomSelectionColors property
  216. * - Owner draw mode now honors ForeColor and BackColor settings on the list
  217. * 2009-01-16 JPP - Changed to use EditorRegistry rather than hard coding cell editors
  218. * 2009-01-10 JPP - Changed to use Equals() method rather than == to compare model objects.
  219. * v2.0.1
  220. * 2009-01-08 JPP - Fixed long-standing "multiple columns generated" problem.
  221. * Thanks to pinkjones for his help with solving this one!
  222. * - Added EnsureGroupVisible()
  223. * 2009-01-07 JPP - Made all public and protected methods virtual
  224. * - FinishCellEditing, PossibleFinishCellEditing and CancelCellEditing are now public
  225. * 2008-12-20 JPP - Fixed bug with group comparisons when a group key was null (SF#2445761)
  226. * 2008-12-19 JPP - Fixed bug with space filling columns and layout events
  227. * - Fixed RowHeight so that it only changes the row height, not the width of the images.
  228. * v2.0
  229. * 2008-12-10 JPP - Handle Backspace key. Resets the seach-by-typing state without delay
  230. * - Made some changes to the column collection editor to try and avoid
  231. * the multiple column generation problem.
  232. * - Updated some documentation
  233. * 2008-12-07 JPP - Search-by-typing now works when showing groups
  234. * - Added BeforeSearching and AfterSearching events which are triggered when the user types
  235. * into the list.
  236. * - Added secondary sort information to Before/AfterSorting events
  237. * - Reorganized group sorting code. Now triggers Sorting events.
  238. * - Added GetItemIndexInDisplayOrder()
  239. * - Tweaked in the interaction of the column editor with the IDE so that we (normally)
  240. * don't rely on a hack to find the owning ObjectListView
  241. * - Changed all 'DefaultValue(typeof(Color), "Empty")' to 'DefaultValue(typeof(Color), "")'
  242. * since the first does not given Color.Empty as I thought, but the second does.
  243. * 2008-11-28 JPP - Fixed long standing bug with horizontal scrollbar when shrinking the window.
  244. * (thanks to Bartosz Borowik)
  245. * 2008-11-25 JPP - Added support for dynamic tooltips
  246. * - Split out comparers and header controls stuff into their own files
  247. * 2008-11-21 JPP - Fixed bug where enabling grouping when there was not a sort column would not
  248. * produce a grouped list. Grouping column now defaults to column 0.
  249. * - Preserve selection on virtual lists when sorting
  250. * 2008-11-20 JPP - Added ability to search by sort column to ObjectListView. Unified this with
  251. * ability that was already in VirtualObjectListView
  252. * 2008-11-19 JPP - Fixed bug in ChangeToFilteredColumns() where DisplayOrder was not always restored correctly.
  253. * 2008-10-29 JPP - Event argument blocks moved to directly within the namespace, rather than being
  254. * nested inside ObjectListView class.
  255. * - Removed OLVColumn.CellEditor since it was never used.
  256. * - Marked OLVColumn.AspectGetterAutoGenerated as obsolete (it has not been used for
  257. * several versions now).
  258. * 2008-10-28 JPP - SelectedObjects is now an IList, rather than an ArrayList. This allows
  259. * it to accept generic list (eg List<File>).
  260. * 2008-10-09 JPP - Support indeterminate checkbox values.
  261. * [BREAKING CHANGE] CheckStateGetter/CheckStatePutter now use CheckState types only.
  262. * BooleanCheckStateGetter and BooleanCheckStatePutter added to ease transition.
  263. * 2008-10-08 JPP - Added setFocus parameter to SelectObject(), which allows focus to be set
  264. * at the same time as selecting.
  265. * 2008-09-27 JPP - BIG CHANGE: Fissioned this file into separate files for each component
  266. * 2008-09-24 JPP - Corrected bug with owner drawn lists where a column 0 with a renderer
  267. * would draw at column 0 even if column 0 was dragged to another position.
  268. * - Correctly handle space filling columns when columns are added/removed
  269. * 2008-09-16 JPP - Consistently use try..finally for BeginUpdate()/EndUpdate() pairs
  270. * 2008-08-24 JPP - If LastSortOrder is None when adding objects, don't force a resort.
  271. * 2008-08-22 JPP - Catch and ignore some problems with setting TopIndex on FastObjectListViews.
  272. * 2008-08-05 JPP - In the right-click column select menu, columns are now sorted by display order, rather than alphabetically
  273. * v1.13
  274. * 2008-07-23 JPP - Consistently use copy-on-write semantics with Add/RemoveObject methods
  275. * 2008-07-10 JPP - Enable validation on cell editors through a CellEditValidating event.
  276. * (thanks to Artiom Chilaru for the initial suggestion and implementation).
  277. * 2008-07-09 JPP - Added HeaderControl.Handle to allow OLV to be used within UserControls.
  278. * (thanks to Michael Coffey for tracking this down).
  279. * 2008-06-23 JPP - Split the more generally useful CopyObjectsToClipboard() method
  280. * out of CopySelectionToClipboard()
  281. * 2008-06-22 JPP - Added AlwaysGroupByColumn and AlwaysGroupBySortOrder, which
  282. * force the list view to always be grouped by a particular column.
  283. * 2008-05-31 JPP - Allow check boxes on FastObjectListViews
  284. * - Added CheckedObject and CheckedObjects properties
  285. * 2008-05-11 JPP - Allow selection foreground and background colors to be changed.
  286. * Windows doesn't allow this, so we can only make it happen when owner
  287. * drawing. Set the HighlightForegroundColor and HighlightBackgroundColor
  288. * properties and then call EnableCustomSelectionColors().
  289. * v1.12
  290. * 2008-05-08 JPP - Fixed bug where the column select menu would not appear if the
  291. * ObjectListView has a context menu installed.
  292. * 2008-05-05 JPP - Non detail views can now be owner drawn. The renderer installed for
  293. * primary column is given the chance to render the whole item.
  294. * See BusinessCardRenderer in the demo for an example.
  295. * - BREAKING CHANGE: RenderDelegate now returns a bool to indicate if default
  296. * rendering should be done. Previously returned void. Only important if your
  297. * code used RendererDelegate directly. Renderers derived from BaseRenderer
  298. * are unchanged.
  299. * 2008-05-03 JPP - Changed cell editing to use values directly when the values are Strings.
  300. * Previously, values were always handed to the AspectToStringConverter.
  301. * - When editing a cell, tabbing no longer tries to edit the next subitem
  302. * when not in details view!
  303. * 2008-05-02 JPP - MappedImageRenderer can now handle a Aspects that return a collection
  304. * of values. Each value will be drawn as its own image.
  305. * - Made AddObjects() and RemoveObjects() work for all flavours (or at least not crash)
  306. * - Fixed bug with clearing virtual lists that has been scrolled vertically
  307. * - Made TopItemIndex work with virtual lists.
  308. * 2008-05-01 JPP - Added AddObjects() and RemoveObjects() to allow faster mods to the list
  309. * - Reorganised public properties. Now alphabetical.
  310. * - Made the class ObjectListViewState internal, as it always should have been.
  311. * v1.11
  312. * 2008-04-29 JPP - Preserve scroll position when building the list or changing columns.
  313. * - Added TopItemIndex property. Due to problems with the underlying control, this
  314. * property is not always reliable. See property docs for info.
  315. * 2008-04-27 JPP - Added SelectedIndex property.
  316. * - Use a different, more general strategy to handle Invoke(). Removed all delegates
  317. * that were only declared to support Invoke().
  318. * - Check all native structures for 64-bit correctness.
  319. * 2008-04-25 JPP - Released on SourceForge.
  320. * 2008-04-13 JPP - Added ColumnRightClick event.
  321. * - Made the assembly CLS-compliant. To do this, our cell editors were made internal, and
  322. * the constraint on FlagRenderer template parameter was removed (the type must still
  323. * be an IConvertible, but if it isn't, the error will be caught at runtime, not compile time).
  324. * 2008-04-12 JPP - Changed HandleHeaderRightClick() to have a columnIndex parameter, which tells
  325. * exactly which column was right-clicked.
  326. * 2008-03-31 JPP - Added SaveState() and RestoreState()
  327. * - When cell editing, scrolling with a mouse wheel now ends the edit operation.
  328. * v1.10
  329. * 2008-03-25 JPP - Added space filling columns. See OLVColumn.FreeSpaceProportion property for details.
  330. * A space filling columns fills all (or a portion) of the width unoccupied by other columns.
  331. * 2008-03-23 JPP - Finished tinkering with support for Mono. Compile with conditional compilation symbol 'MONO'
  332. * to enable. On Windows, current problems with Mono:
  333. * - grid lines on virtual lists crashes
  334. * - when grouped, items sometimes are not drawn when any item is scrolled out of view
  335. * - i can't seem to get owner drawing to work
  336. * - when editing cell values, the editing controls always appear behind the listview,
  337. * where they function fine -- the user just can't see them :-)
  338. * 2008-03-16 JPP - Added some methods suggested by Chris Marlowe (thanks for the suggestions Chris)
  339. * - ClearObjects()
  340. * - GetCheckedObject(), GetCheckedObjects()
  341. * - GetItemAt() variation that gets both the item and the column under a point
  342. * 2008-02-28 JPP - Fixed bug with subitem colors when using OwnerDrawn lists and a RowFormatter.
  343. * v1.9.1
  344. * 2008-01-29 JPP - Fixed bug that caused owner-drawn virtual lists to use 100% CPU
  345. * - Added FlagRenderer to help draw bitwise-OR'ed flag values
  346. * 2008-01-23 JPP - Fixed bug (introduced in v1.9) that made alternate row colour with groups not quite right
  347. * - Ensure that DesignerSerializationVisibility.Hidden is set on all non-browsable properties
  348. * - Make sure that sort indicators are shown after changing which columns are visible
  349. * 2008-01-21 JPP - Added FastObjectListView
  350. * v1.9
  351. * 2008-01-18 JPP - Added IncrementalUpdate()
  352. * 2008-01-16 JPP - Right clicking on column header will allow the user to choose which columns are visible.
  353. * Set SelectColumnsOnRightClick to false to prevent this behaviour.
  354. * - Added ImagesRenderer to draw more than one images in a column
  355. * - Changed the positioning of the empty list m to use all the client area. Thanks to Matze.
  356. * 2007-12-13 JPP - Added CopySelectionToClipboard(). Ctrl-C invokes this method. Supports text
  357. * and HTML formats.
  358. * 2007-12-12 JPP - Added support for checkboxes via CheckStateGetter and CheckStatePutter properties.
  359. * - Made ObjectListView and OLVColumn into partial classes so that others can extend them.
  360. * 2007-12-09 JPP - Added ability to have hidden columns, i.e. columns that the ObjectListView knows
  361. * about but that are not visible to the user. Controlled by OLVColumn.IsVisible.
  362. * Added ColumnSelectionForm to the project to show how it could be used in an application.
  363. *
  364. * v1.8
  365. * 2007-11-26 JPP - Cell editing fully functional
  366. * 2007-11-21 JPP - Added SelectionChanged event. This event is triggered once when the
  367. * selection changes, no matter how many items are selected or deselected (in
  368. * contrast to SelectedIndexChanged which is called once for every row that
  369. * is selected or deselected). Thanks to lupokehl42 (Daniel) for his suggestions and
  370. * improvements on this idea.
  371. * 2007-11-19 JPP - First take at cell editing
  372. * 2007-11-17 JPP - Changed so that items within a group are not sorted if lastSortOrder == None
  373. * - Only call MakeSortIndicatorImages() if we haven't already made the sort indicators
  374. * (Corrected misspelling in the name of the method too)
  375. * 2007-11-06 JPP - Added ability to have secondary sort criteria when sorting
  376. * (SecondarySortColumn and SecondarySortOrder properties)
  377. * - Added SortGroupItemsByPrimaryColumn to allow group items to be sorted by the
  378. * primary column. Previous default was to sort by the grouping column.
  379. * v1.7
  380. * No big changes to this version but made to work with ListViewPrinter and released with it.
  381. *
  382. * 2007-11-05 JPP - Changed BaseRenderer to use DrawString() rather than TextRenderer, since TextRenderer
  383. * does not work when printing.
  384. * v1.6
  385. * 2007-11-03 JPP - Fixed some bugs in the rebuilding of DataListView.
  386. * 2007-10-31 JPP - Changed to use builtin sort indicators on XP and later. This also avoids alignment
  387. * problems on Vista. (thanks to gravybod for the suggestion and example implementation)
  388. * 2007-10-21 JPP - Added MinimumWidth and MaximumWidth properties to OLVColumn.
  389. * - Added ability for BuildList() to preserve selection. Calling BuildList() directly
  390. * tries to preserve selection; calling SetObjects() does not.
  391. * - Added SelectAll() and DeselectAll() methods. Useful for working with large lists.
  392. * 2007-10-08 JPP - Added GetNextItem() and GetPreviousItem(), which walk sequentially through the
  393. * listview items, even when the view is grouped.
  394. * - Added SelectedItem property
  395. * 2007-09-28 JPP - Optimized aspect-to-string conversion. BuildList() 15% faster.
  396. * - Added empty implementation of RefreshObjects() to VirtualObjectListView since
  397. * RefreshObjects() cannot work on virtual lists.
  398. * 2007-09-13 JPP - Corrected bug with custom sorter in VirtualObjectListView (thanks for mpgjunky)
  399. * 2007-09-07 JPP - Corrected image scaling bug in DrawAlignedImage() (thanks to krita970)
  400. * 2007-08-29 JPP - Allow item count labels on groups to be set per column (thanks to cmarlow for idea)
  401. * 2007-08-14 JPP - Major rework of DataListView based on Ian Griffiths's great work
  402. * 2007-08-11 JPP - When empty, the control can now draw a "List Empty" m
  403. * - Added GetColumn() and GetItem() methods
  404. * v1.5
  405. * 2007-08-03 JPP - Support animated GIFs in ImageRenderer
  406. * - Allow height of rows to be specified - EXPERIMENTAL!
  407. * 2007-07-26 JPP - Optimised redrawing of owner-drawn lists by remembering the update rect
  408. * - Allow sort indicators to be turned off
  409. * 2007-06-30 JPP - Added RowFormatter delegate
  410. * - Allow a different label when there is only one item in a group (thanks to cmarlow)
  411. * v1.4
  412. * 2007-04-12 JPP - Allow owner drawn on steriods!
  413. * - Column headers now display sort indicators
  414. * - ImageGetter delegates can now return ints, strings or Images
  415. * (Images are only visible if the list is owner drawn)
  416. * - Added OLVColumn.MakeGroupies to help with group partitioning
  417. * - All normal listview views are now supported
  418. * - Allow dotted aspect names, e.g. Owner.Workgroup.Name (thanks to OlafD)
  419. * - Added SelectedObject and SelectedObjects properties
  420. * v1.3
  421. * 2007-03-01 JPP - Added DataListView
  422. * - Added VirtualObjectListView
  423. * - Added Freeze/Unfreeze capabilities
  424. * - Allowed sort handler to be installed
  425. * - Simplified sort comparisons: handles 95% of cases with only 6 lines of code!
  426. * - Fixed bug with alternative line colors on unsorted lists (thanks to cmarlow)
  427. * 2007-01-13 JPP - Fixed bug with lastSortOrder (thanks to Kwan Fu Sit)
  428. * - Non-OLVColumns are no longer allowed
  429. * 2007-01-04 JPP - Clear sorter before rebuilding list. 10x faster! (thanks to aaberg)
  430. * - Include GetField in GetAspectByName() so field values can be Invoked too.
  431. * - Fixed subtle bug in RefreshItem() that erased background colors.
  432. * 2006-11-01 JPP - Added alternate line colouring
  433. * 2006-10-20 JPP - Refactored all sorting comparisons and made it extendable. See ComparerManager.
  434. * - Improved IDE integration
  435. * - Made control DoubleBuffered
  436. * - Added object selection methods
  437. * 2006-10-13 JPP Implemented grouping and column sorting
  438. * 2006-10-09 JPP Initial version
  439. *
  440. * TO DO:
  441. * - Support undocumented group features: subseted groups, group footer items
  442. * - Complete custom designer
  443. *
  444. * Copyright (C) 2006-2011 Phillip Piper
  445. *
  446. * This program is free software: you can redistribute it and/or modify
  447. * it under the terms of the GNU General Public License as published by
  448. * the Free Software Foundation, either version 3 of the License, or
  449. * (at your option) any later version.
  450. *
  451. * This program is distributed in the hope that it will be useful,
  452. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  453. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  454. * GNU General Public License for more details.
  455. *
  456. * You should have received a copy of the GNU General Public License
  457. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  458. *
  459. * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
  460. */
  461. using System;
  462. using System.Collections;
  463. using System.Collections.Generic;
  464. using System.ComponentModel;
  465. using System.Drawing;
  466. using System.IO;
  467. using System.Reflection;
  468. using System.Runtime.InteropServices;
  469. using System.Runtime.Serialization.Formatters.Binary;
  470. using System.Windows.Forms;
  471. using System.Windows.Forms.VisualStyles;
  472. using System.Runtime.Serialization.Formatters;
  473. namespace BrightIdeasSoftware
  474. {
  475. /// <summary>
  476. /// An ObjectListView is a much easier to use, and much more powerful, version of the ListView.
  477. /// </summary>
  478. /// <remarks>
  479. /// <para>
  480. /// An ObjectListView automatically populates a ListView control with information taken
  481. /// from a given collection of objects. It can do this because each column is configured
  482. /// to know which bit of the model object (the "aspect") it should be displaying. Columns similarly
  483. /// understand how to sort the list based on their aspect, and how to construct groups
  484. /// using their aspect.
  485. /// </para>
  486. /// <para>
  487. /// Aspects are extracted by giving the name of a method to be called or a
  488. /// property to be fetched. These names can be simple names or they can be dotted
  489. /// to chain property access e.g. "Owner.Address.Postcode".
  490. /// Aspects can also be extracted by installing a delegate.
  491. /// </para>
  492. /// <para>
  493. /// An ObjectListView can show a "this list is empty" message when there is nothing to show in the list,
  494. /// so that the user knows the control is supposed to be empty.
  495. /// </para>
  496. /// <para>
  497. /// Right clicking on a column header should present a menu which can contain:
  498. /// commands (sort, group, ungroup); filtering; and column selection. Whether these
  499. /// parts of the menu appear is controlled by ShowCommandMenuOnRightClick,
  500. /// ShowFilterMenuOnRightClick and SelectColumnsOnRightClick respectively.
  501. /// </para>
  502. /// <para>
  503. /// The groups created by an ObjectListView can be configured to include other formatting
  504. /// information, including a group icon, subtitle and task button. Using some undocumented
  505. /// interfaces, these groups can even on virtual lists.
  506. /// </para>
  507. /// <para>
  508. /// ObjectListView supports dragging rows to other places, including other application.
  509. /// Special support is provide for drops from other ObjectListViews in the same application.
  510. /// In many cases, an ObjectListView becomes a full drag source by setting <see cref="IsSimpleDragSource"/> to
  511. /// true. Similarly, to accept drops, it is usually enough to set <see cref="IsSimpleDropSink"/> to true,
  512. /// and then handle the <see cref="CanDrop"/> and <see cref="Dropped"/> events (or the <see cref="ModelCanDrop"/> and
  513. /// <see cref="ModelDropped"/> events, if you only want to handle drops from other ObjectListViews in your application).
  514. /// </para>
  515. /// <para>
  516. /// For these classes to build correctly, the project must have references to these assemblies:
  517. /// </para>
  518. /// <list type="bullet">
  519. /// <item><description>System</description></item>
  520. /// <item><description>System.Data</description></item>
  521. /// <item><description>System.Design</description></item>
  522. /// <item><description>System.Drawing</description></item>
  523. /// <item><description>System.Windows.Forms (obviously)</description></item>
  524. /// </list>
  525. /// </remarks>
  526. //[Designer("BrightIdeasSoftware.Design.ObjectListViewDesigner")] // Uncomment this if you are feeling brave :)
  527. public partial class ObjectListView : ListView, ISupportInitialize {
  528. #region Life and death
  529. /// <summary>
  530. /// Create an ObjectListView
  531. /// </summary>
  532. public ObjectListView() {
  533. this.ColumnClick += new ColumnClickEventHandler(this.HandleColumnClick);
  534. this.Layout += new LayoutEventHandler(this.HandleLayout);
  535. this.ColumnWidthChanging += new ColumnWidthChangingEventHandler(this.HandleColumnWidthChanging);
  536. this.ColumnWidthChanged += new ColumnWidthChangedEventHandler(this.HandleColumnWidthChanged);
  537. base.View = View.Details;
  538. this.DoubleBuffered = true; // kill nasty flickers. hiss... me hates 'em
  539. this.ShowSortIndicators = true;
  540. // Setup the overlays that will be controlled by the IDE settings
  541. this.InitializeStandardOverlays();
  542. this.InitializeEmptyListMsgOverlay();
  543. }
  544. /// <summary>
  545. /// Dispose of any resources this instance has been using
  546. /// </summary>
  547. /// <param name="disposing"></param>
  548. protected override void Dispose(bool disposing) {
  549. base.Dispose(disposing);
  550. if (disposing) {
  551. foreach (GlassPanelForm glassPanel in this.glassPanels) {
  552. glassPanel.Unbind();
  553. glassPanel.Dispose();
  554. }
  555. this.glassPanels.Clear();
  556. }
  557. }
  558. #endregion
  559. #region Static properties
  560. /// <summary>
  561. /// Gets whether the program running on Vista or later?
  562. /// </summary>
  563. static public bool IsVistaOrLater {
  564. get {
  565. if (!ObjectListView.isVistaOrLater.HasValue)
  566. ObjectListView.isVistaOrLater = Environment.OSVersion.Version.Major >= 6;
  567. return ObjectListView.isVistaOrLater.Value;
  568. }
  569. }
  570. static private bool? isVistaOrLater;
  571. /// <summary>
  572. /// Gets whether the program running on Win7 or later?
  573. /// </summary>
  574. static public bool IsWin7OrLater {
  575. get {
  576. if (!ObjectListView.isWin7OrLater.HasValue) {
  577. // For some reason, Win7 is v6.1, not v7.0
  578. Version version = Environment.OSVersion.Version;
  579. ObjectListView.isWin7OrLater = version.Major > 6 || (version.Major == 6 && version.Minor > 0);
  580. }
  581. return ObjectListView.isWin7OrLater.Value;
  582. }
  583. }
  584. static private bool? isWin7OrLater;
  585. /// <summary>
  586. /// Gets or sets how what smoothing mode will be applied to graphic operations.
  587. /// </summary>
  588. static public System.Drawing.Drawing2D.SmoothingMode SmoothingMode {
  589. get { return ObjectListView.smoothingMode; }
  590. set { ObjectListView.smoothingMode = value; }
  591. }
  592. static private System.Drawing.Drawing2D.SmoothingMode smoothingMode =
  593. System.Drawing.Drawing2D.SmoothingMode.HighQuality;
  594. /// <summary>
  595. /// Gets or sets how should text be renderered.
  596. /// </summary>
  597. static public System.Drawing.Text.TextRenderingHint TextRenderingHint {
  598. get { return ObjectListView.textRendereringHint; }
  599. set { ObjectListView.textRendereringHint = value; }
  600. }
  601. static private System.Drawing.Text.TextRenderingHint textRendereringHint =
  602. System.Drawing.Text.TextRenderingHint.SystemDefault;
  603. /// <summary>
  604. /// Convert the given enumerable into an ArrayList as efficiently as possible
  605. /// </summary>
  606. /// <param name="collection">The source collection</param>
  607. /// <param name="alwaysCreate">If true, this method will always create a new
  608. /// collection.</param>
  609. /// <returns>An ArrayList with the same contents as the given collection.</returns>
  610. /// <remarks>
  611. /// <para>When we move to .NET 3.5, we can use LINQ and not need this method.</para>
  612. /// </remarks>
  613. public static ArrayList EnumerableToArray(IEnumerable collection, bool alwaysCreate) {
  614. if (collection == null)
  615. return new ArrayList();
  616. if (!alwaysCreate) {
  617. ArrayList array = collection as ArrayList;
  618. if (array != null)
  619. return array;
  620. IList iList = collection as IList;
  621. if (iList != null)
  622. return ArrayList.Adapter(iList);
  623. }
  624. ICollection iCollection = collection as ICollection;
  625. if (iCollection != null)
  626. return new ArrayList(iCollection);
  627. ArrayList newObjects = new ArrayList();
  628. foreach (object x in collection)
  629. newObjects.Add(x);
  630. return newObjects;
  631. }
  632. ///// <summary>
  633. ///// Decide if the given enumerable is empty
  634. ///// </summary>
  635. ///// <param name="collection">The source collection</param>
  636. ///// <returns>True if the given enumerable is empty</returns>
  637. ///// <remarks>
  638. ///// <para>When we move to .NET 3.5, we can use LINQ and not need this method.</para>
  639. ///// </remarks>
  640. //public static bool IsEnumerableEmpty(IEnumerable collection) {
  641. // if (collection == null)
  642. // return true;
  643. // IEnumerator enumerator = collection.GetEnumerator();
  644. // return !enumerator.MoveNext();
  645. //}
  646. #endregion
  647. #region Public properties
  648. /// <summary>
  649. /// Get or set all the columns that this control knows about.
  650. /// Only those columns where IsVisible is true will be seen by the user.
  651. /// </summary>
  652. /// <remarks>
  653. /// <para>
  654. /// If you want to add new columns programmatically, add them to
  655. /// AllColumns and then call RebuildColumns(). Normally, you do not have to
  656. /// deal with this property directly. Just use the IDE.
  657. /// </para>
  658. /// <para>If you do add or remove columns from the AllColumns collection,
  659. /// you have to call RebuildColumns() to make those changes take effect.</para>
  660. /// </remarks>
  661. [Browsable(false),
  662. DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  663. public virtual List<OLVColumn> AllColumns {
  664. get { return this.allColumns; }
  665. set { this.allColumns = value ?? new List<OLVColumn>(); }
  666. }
  667. private List<OLVColumn> allColumns = new List<OLVColumn>();
  668. /// <summary>
  669. /// Gets or sets the background color of every second row
  670. /// </summary>
  671. [Category("ObjectListView"),
  672. Description("If using alternate colors, what color should the background of alterate rows be?"),
  673. DefaultValue(typeof(Color), "")]
  674. public Color AlternateRowBackColor {
  675. get { return alternateRowBackColor; }
  676. set { alternateRowBackColor = value; }
  677. }
  678. private Color alternateRowBackColor = Color.Empty;
  679. /// <summary>
  680. /// Gets the alternate row background color that has been set, or the default color
  681. /// </summary>
  682. [Browsable(false)]
  683. public virtual Color AlternateRowBackColorOrDefault {
  684. get {
  685. return this.alternateRowBackColor == Color.Empty ? Color.LemonChiffon : this.alternateRowBackColor;
  686. }
  687. }
  688. /// <summary>
  689. /// This property forces the ObjectListView to always group items by the given column.
  690. /// </summary>
  691. [Browsable(false),
  692. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  693. public virtual OLVColumn AlwaysGroupByColumn {
  694. get { return alwaysGroupByColumn; }
  695. set { alwaysGroupByColumn = value; }
  696. }
  697. private OLVColumn alwaysGroupByColumn;
  698. /// <summary>
  699. /// If AlwaysGroupByColumn is not null, this property will be used to decide how
  700. /// those groups are sorted. If this property has the value SortOrder.None, then
  701. /// the sort order will toggle according to the users last header click.
  702. /// </summary>
  703. [Browsable(false),
  704. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  705. public virtual SortOrder AlwaysGroupBySortOrder {
  706. get { return alwaysGroupBySortOrder; }
  707. set { alwaysGroupBySortOrder = value; }
  708. }
  709. private SortOrder alwaysGroupBySortOrder = SortOrder.None;
  710. /// <summary>
  711. /// Give access to the image list that is actually being used by the control
  712. /// </summary>
  713. /// <remarks>
  714. /// Normally, it is preferable to use SmallImageList. Only use this property
  715. /// if you know exactly what you are doing.
  716. /// </remarks>
  717. [Browsable(false),
  718. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  719. public virtual ImageList BaseSmallImageList {
  720. get { return base.SmallImageList; }
  721. set { base.SmallImageList = value; }
  722. }
  723. /// <summary>
  724. /// How does the user indicate that they want to edit a cell?
  725. /// None means that the listview cannot be edited.
  726. /// </summary>
  727. /// <remarks>Columns can also be marked as editable.</remarks>
  728. [Category("ObjectListView"),
  729. Description("How does the user indicate that they want to edit a cell?"),
  730. DefaultValue(CellEditActivateMode.None)]
  731. public virtual CellEditActivateMode CellEditActivation {
  732. get { return cellEditActivation; }
  733. set {
  734. cellEditActivation = value;
  735. if (this.Created)
  736. this.Invalidate();
  737. }
  738. }
  739. private CellEditActivateMode cellEditActivation = CellEditActivateMode.None;
  740. /// <summary>
  741. /// Gets or sets the engine that will handle key presses during a cell edit operation.
  742. /// Settings this to null will reset it to default value.
  743. /// </summary>
  744. [Browsable(false),
  745. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  746. public CellEditKeyEngine CellEditKeyEngine {
  747. get {
  748. if (this.cellEditKeyEngine == null)
  749. this.cellEditKeyEngine = new CellEditKeyEngine();
  750. return this.cellEditKeyEngine;
  751. }
  752. set { this.cellEditKeyEngine = value; }
  753. }
  754. private CellEditKeyEngine cellEditKeyEngine;
  755. /// <summary>
  756. /// Gets the control that is currently being used for editing a cell.
  757. /// </summary>
  758. /// <remarks>This will obviously be null if no cell is being edited.</remarks>
  759. [Browsable(false)]
  760. public Control CellEditor {
  761. get {
  762. return this.cellEditor;
  763. }
  764. }
  765. /// <summary>
  766. /// Gets or sets the behaviour of the Tab key when editing a cell on the left or right
  767. /// edge of the control. If this is false (the default), pressing Tab will wrap to the other side
  768. /// of the same row. If this is true, pressing Tab when editing the right most cell will advance
  769. /// to the next row
  770. /// and Shift-Tab when editing the left-most cell will change to the previous row.
  771. /// </summary>
  772. [Category("ObjectListView"),
  773. Description("Should Tab/Shift-Tab change rows while cell editing?"),
  774. DefaultValue(false)]
  775. public virtual bool CellEditTabChangesRows {
  776. get { return cellEditTabChangesRows; }
  777. set {
  778. cellEditTabChangesRows = value;
  779. if (cellEditTabChangesRows) {
  780. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab, CellEditCharacterBehaviour.ChangeColumnRight, CellEditAtEdgeBehaviour.ChangeRow);
  781. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab|Keys.Shift, CellEditCharacterBehaviour.ChangeColumnLeft, CellEditAtEdgeBehaviour.ChangeRow);
  782. } else {
  783. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab, CellEditCharacterBehaviour.ChangeColumnRight, CellEditAtEdgeBehaviour.Wrap);
  784. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab | Keys.Shift, CellEditCharacterBehaviour.ChangeColumnLeft, CellEditAtEdgeBehaviour.Wrap);
  785. }
  786. }
  787. }
  788. private bool cellEditTabChangesRows = false;
  789. /// <summary>
  790. /// Gets or sets the behaviour of the Enter keys while editing a cell.
  791. /// If this is false (the default), pressing Enter will simply finish the editing operation.
  792. /// If this is true, Enter will finish the edit operation and start a new edit operation
  793. /// on the cell below the current cell, wrapping to the top of the next row when at the bottom cell.
  794. /// </summary>
  795. [Category("ObjectListView"),
  796. Description("Should Enter change rows while cell editing?"),
  797. DefaultValue(false)]
  798. public virtual bool CellEditEnterChangesRows {
  799. get { return cellEditEnterChangesRows; }
  800. set {
  801. cellEditEnterChangesRows = value;
  802. if (cellEditEnterChangesRows) {
  803. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter, CellEditCharacterBehaviour.ChangeRowDown, CellEditAtEdgeBehaviour.ChangeColumn);
  804. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter | Keys.Shift, CellEditCharacterBehaviour.ChangeRowUp, CellEditAtEdgeBehaviour.ChangeColumn);
  805. } else {
  806. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter, CellEditCharacterBehaviour.EndEdit, CellEditAtEdgeBehaviour.EndEdit);
  807. this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter | Keys.Shift, CellEditCharacterBehaviour.EndEdit, CellEditAtEdgeBehaviour.EndEdit);
  808. }
  809. }
  810. }
  811. private bool cellEditEnterChangesRows = false;
  812. /// <summary>
  813. /// Gets the tool tip control that shows tips for the cells
  814. /// </summary>
  815. [Browsable(false)]
  816. public ToolTipControl CellToolTip {
  817. get {
  818. if (this.cellToolTip == null) {
  819. this.CreateCellToolTip();
  820. }
  821. return this.cellToolTip;
  822. }
  823. }
  824. private ToolTipControl cellToolTip;
  825. /// <summary>
  826. /// Should this list show checkboxes?
  827. /// </summary>
  828. public new bool CheckBoxes {
  829. get {
  830. return base.CheckBoxes;
  831. }
  832. set {
  833. base.CheckBoxes = value;
  834. // Initialize the state image list so we can display indetermined values.
  835. this.InitializeStateImageList();
  836. }
  837. }
  838. /// <summary>
  839. /// Return the model object of the row that is checked or null if no row is checked
  840. /// or more than one row is checked
  841. /// </summary>
  842. [Browsable(false),
  843. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  844. public virtual Object CheckedObject {
  845. get {
  846. IList checkedObjects = this.CheckedObjects;
  847. if (checkedObjects.Count == 1)
  848. return checkedObjects[0];
  849. else
  850. return null;
  851. }
  852. set {
  853. this.CheckedObjects = new ArrayList(new Object[] { value });
  854. }
  855. }
  856. /// <summary>
  857. /// Get or set the collection of model objects that are checked.
  858. /// When setting this property, any row whose model object isn't
  859. /// in the given collection will be unchecked. Setting to null is
  860. /// equivilent to unchecking all.
  861. /// </summary>
  862. /// <remarks>
  863. /// <para>
  864. /// This property returns a simple collection. Changes made to the returned
  865. /// collection do NOT affect the list. This is different to the behaviour of
  866. /// CheckedIndicies collection.
  867. /// </para>
  868. /// <para>
  869. /// .NET's CheckedItems property is not helpful. It is just a short-hand for
  870. /// iterating through the list looking for items that are checked.
  871. /// </para>
  872. /// <para>
  873. /// The performance of the get method is O(n), where n is the number of items
  874. /// in the control. The performance of the set method is
  875. /// O(n + m) where m is the number of objects being checked. Be careful on long lists.
  876. /// </para>
  877. /// </remarks>
  878. [Browsable(false),
  879. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  880. public virtual IList CheckedObjects {
  881. get {
  882. ArrayList objects = new ArrayList();
  883. if (this.CheckBoxes) {
  884. for (int i = 0; i < this.GetItemCount(); i++) {
  885. OLVListItem olvi = this.GetItem(i);
  886. if (olvi.CheckState == CheckState.Checked)
  887. objects.Add(olvi.RowObject);
  888. }
  889. }
  890. return objects;
  891. }
  892. set {
  893. if (!this.CheckBoxes)
  894. return;
  895. // Set up an efficient way of testing for the presence of a particular model
  896. Hashtable table = new Hashtable(this.GetItemCount());
  897. if (value != null) {
  898. foreach (object x in value)
  899. table[x] = true;
  900. }
  901. foreach (Object x in this.Objects) {
  902. this.SetObjectCheckedness(x, table.ContainsKey(x) ? CheckState.Checked : CheckState.Unchecked);
  903. }
  904. }
  905. }
  906. /// <summary>
  907. /// Gets or sets the checked objects from an enumerable.
  908. /// </summary>
  909. /// <remarks>
  910. /// Useful for checking all objects in the list.
  911. /// </remarks>
  912. /// <example>
  913. /// this.olv1.CheckedObjectsEnumerable = this.olv1.Objects;
  914. /// </example>
  915. [Browsable(false),
  916. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  917. public virtual IEnumerable CheckedObjectsEnumerable {
  918. get {
  919. return this.CheckedObjects;
  920. }
  921. set {
  922. this.CheckedObjects = ObjectListView.EnumerableToArray(value, true);
  923. }
  924. }
  925. /// <summary>
  926. /// Gets Columns for this list. We hide the original so we can associate
  927. /// a specialised editor with it.
  928. /// </summary>
  929. [Editor("BrightIdeasSoftware.Design.OLVColumnCollectionEditor", "System.Drawing.Design.UITypeEditor")]
  930. new public ListView.ColumnHeaderCollection Columns {
  931. get {
  932. return base.Columns;
  933. }
  934. }
  935. /// <summary>
  936. /// Get/set the list of columns that should be used when the list switches to tile view.
  937. /// </summary>
  938. [Browsable(false),
  939. Obsolete("Use GetFilteredColumns() and OLVColumn.IsTileViewColumn instead"),
  940. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  941. public List<OLVColumn> ColumnsForTileView {
  942. get { return this.GetFilteredColumns(View.Tile); }
  943. }
  944. /// <summary>
  945. /// Return the visible columns in the order they are displayed to the user
  946. /// </summary>
  947. [Browsable(false)]
  948. public virtual List<OLVColumn> ColumnsInDisplayOrder {
  949. get {
  950. OLVColumn[] columnsInDisplayOrder = new OLVColumn[this.Columns.Count];
  951. foreach (OLVColumn col in this.Columns) {
  952. columnsInDisplayOrder[col.DisplayIndex] = col;
  953. }
  954. return new List<OLVColumn>(columnsInDisplayOrder);
  955. }
  956. }
  957. /// <summary>
  958. /// Get the area of the control that shows the list, minus any header control
  959. /// </summary>
  960. [Browsable(false)]
  961. public Rectangle ContentRectangle {
  962. get {
  963. Rectangle r = this.ClientRectangle;
  964. // If the listview has a header control, remove the header from the control area
  965. if (this.View == View.Details && this.HeaderControl != null) {
  966. Rectangle hdrBounds = new Rectangle();
  967. NativeMethods.GetClientRect(this.HeaderControl.Handle, ref hdrBounds);
  968. r.Y = hdrBounds.Height;
  969. r.Height = r.Height - hdrBounds.Height;
  970. }
  971. return r;
  972. }
  973. }
  974. /// <summary>
  975. /// Gets or sets if the selected rows should be copied to the clipboard when the user presses Ctrl-C
  976. /// </summary>
  977. [Category("ObjectListView"),
  978. Description("Should the control copy the selection to the clipboard when the user presses Ctrl-C?"),
  979. DefaultValue(true)]
  980. public virtual bool CopySelectionOnControlC {
  981. get { return copySelectionOnControlC; }
  982. set { copySelectionOnControlC = value; }
  983. }
  984. private bool copySelectionOnControlC = true;
  985. /// <summary>
  986. /// Gets or sets whether the Control-C copy to clipboard functionality should use
  987. /// the installed DragSource to create the data object that is placed onto the clipboard.
  988. /// </summary>
  989. /// <remarks>This is normally what is desired, unless a custom DragSource is installed
  990. /// that does some very specialized drag-drop behaviour.</remarks>
  991. [Category("ObjectListView"),
  992. Description("Should the Ctrl-C copy process use the DragSource to create the Clipboard data object?"),
  993. DefaultValue(true)]
  994. public bool CopySelectionOnControlCUsesDragSource {
  995. get { return this.copySelectionOnControlCUsesDragSource; }
  996. set { this.copySelectionOnControlCUsesDragSource = value; }
  997. }
  998. private bool copySelectionOnControlCUsesDragSource = true;
  999. /// <summary>
  1000. /// Gets the list of decorations that will be drawn the ListView
  1001. /// </summary>
  1002. /// <remarks>
  1003. /// <para>
  1004. /// Do not modify the contents of this list directly. Use the AddDecoration() and RemoveDecoration() methods.
  1005. /// </para>
  1006. /// <para>
  1007. /// A decoration scrolls with the list contents. An overlay is fixed in place.
  1008. /// </para>
  1009. /// </remarks>
  1010. [Browsable(false)]
  1011. protected IList<IDecoration> Decorations {
  1012. get { return this.decorations; }
  1013. }
  1014. private List<IDecoration> decorations = new List<IDecoration>();
  1015. /// <summary>
  1016. /// When owner drawing, this renderer will draw columns that do not have specific renderer
  1017. /// given to them
  1018. /// </summary>
  1019. /// <remarks>If you try to set this to null, it will revert to a BaseRenderer</remarks>
  1020. [Browsable(false),
  1021. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1022. public IRenderer DefaultRenderer {
  1023. get { return this.defaultRenderer; }
  1024. set { this.defaultRenderer = value ?? new BaseRenderer(); }
  1025. }
  1026. private IRenderer defaultRenderer = new BaseRenderer();
  1027. /// <summary>
  1028. /// Gets or sets the object that controls how drags start from this control
  1029. /// </summary>
  1030. [Browsable(false),
  1031. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1032. public IDragSource DragSource {
  1033. get { return this.dragSource; }
  1034. set { this.dragSource = value; }
  1035. }
  1036. private IDragSource dragSource;
  1037. /// <summary>
  1038. /// Gets or sets the object that controls how drops are accepted and processed
  1039. /// by this ListView.
  1040. /// </summary>
  1041. /// <remarks>
  1042. /// <para>
  1043. /// If the given sink is an instance of SimpleDropSink, then events from the drop sink
  1044. /// will be automatically forwarded to the ObjectListView (which means that handlers
  1045. /// for those event can be configured within the IDE).
  1046. /// </para>
  1047. /// <para>If this is set to null, the control will not accept drops.</para>
  1048. /// </remarks>
  1049. [Browsable(false),
  1050. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1051. public IDropSink DropSink {
  1052. get { return this.dropSink; }
  1053. set {
  1054. if (this.dropSink == value)
  1055. return;
  1056. // Stop listening for events on the old sink
  1057. SimpleDropSink oldSink = this.dropSink as SimpleDropSink;
  1058. if (oldSink != null) {
  1059. oldSink.CanDrop -= new EventHandler<OlvDropEventArgs>(dropSink_CanDrop);
  1060. oldSink.Dropped -= new EventHandler<OlvDropEventArgs>(dropSink_Dropped);
  1061. oldSink.ModelCanDrop -= new EventHandler<ModelDropEventArgs>(dropSink_ModelCanDrop);
  1062. oldSink.ModelDropped -= new EventHandler<ModelDropEventArgs>(dropSink_ModelDropped);
  1063. }
  1064. this.dropSink = value;
  1065. this.AllowDrop = (value != null);
  1066. if (this.dropSink != null)
  1067. this.dropSink.ListView = this;
  1068. // Start listening for events on the new sink
  1069. SimpleDropSink newSink = value as SimpleDropSink;
  1070. if (newSink != null) {
  1071. newSink.CanDrop += new EventHandler<OlvDropEventArgs>(dropSink_CanDrop);
  1072. newSink.Dropped += new EventHandler<OlvDropEventArgs>(dropSink_Dropped);
  1073. newSink.ModelCanDrop += new EventHandler<ModelDropEventArgs>(dropSink_ModelCanDrop);
  1074. newSink.ModelDropped += new EventHandler<ModelDropEventArgs>(dropSink_ModelDropped);
  1075. }
  1076. }
  1077. }
  1078. private IDropSink dropSink;
  1079. // Forward events from the drop sink to the control itself
  1080. void dropSink_CanDrop(object sender, OlvDropEventArgs e) { this.OnCanDrop(e); }
  1081. void dropSink_Dropped(object sender, OlvDropEventArgs e) { this.OnDropped(e); }
  1082. void dropSink_ModelCanDrop(object sender, ModelDropEventArgs e) { this.OnModelCanDrop(e); }
  1083. void dropSink_ModelDropped(object sender, ModelDropEventArgs e) { this.OnModelDropped(e); }
  1084. /// <summary>
  1085. /// This registry decides what control should be used to edit what cells, based
  1086. /// on the type of the value in the cell.
  1087. /// </summary>
  1088. /// <see cref="EditorRegistry"/>
  1089. /// <remarks>All instances of ObjectListView share the same editor registry.</remarks>
  1090. static public EditorRegistry EditorRegistry = new EditorRegistry();
  1091. /// <summary>
  1092. /// Gets or sets the text that should be shown when there are no items in this list view.
  1093. /// </summary>
  1094. /// <remarks>If the EmptyListMsgOverlay has been changed to something other than a TextOverlay,
  1095. /// this property does nothing</remarks>
  1096. [Category("ObjectListView"),
  1097. Description("When the list has no items, show this message in the control"),
  1098. DefaultValue(null),
  1099. Localizable(true)]
  1100. public virtual String EmptyListMsg {
  1101. get {
  1102. TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
  1103. if (overlay == null)
  1104. return null;
  1105. else
  1106. return overlay.Text;
  1107. }
  1108. set {
  1109. TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
  1110. if (overlay != null) {
  1111. overlay.Text = value;
  1112. this.Invalidate();
  1113. }
  1114. }
  1115. }
  1116. /// <summary>
  1117. /// Gets or sets the font in which the List Empty message should be drawn
  1118. /// </summary>
  1119. /// <remarks>If the EmptyListMsgOverlay has been changed to something other than a TextOverlay,
  1120. /// this property does nothing</remarks>
  1121. [Category("ObjectListView"),
  1122. Description("What font should the 'list empty' message be drawn in?"),
  1123. DefaultValue(null)]
  1124. public virtual Font EmptyListMsgFont {
  1125. get {
  1126. TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
  1127. if (overlay == null)
  1128. return null;
  1129. else
  1130. return overlay.Font;
  1131. }
  1132. set {
  1133. TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
  1134. if (overlay != null)
  1135. overlay.Font = value;
  1136. }
  1137. }
  1138. /// <summary>
  1139. /// Return the font for the 'list empty' message or a reasonable default
  1140. /// </summary>
  1141. [Browsable(false)]
  1142. public virtual Font EmptyListMsgFontOrDefault {
  1143. get {
  1144. return this.EmptyListMsgFont ?? new Font("Tahoma", 14);
  1145. }
  1146. }
  1147. /// <summary>
  1148. /// Gets or sets the overlay responsible for drawing the List Empty msg.
  1149. /// </summary>
  1150. [Browsable(false),
  1151. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1152. public virtual IOverlay EmptyListMsgOverlay {
  1153. get { return this.emptyListMsgOverlay; }
  1154. set {
  1155. if (this.emptyListMsgOverlay != value) {
  1156. this.emptyListMsgOverlay = value;
  1157. this.Invalidate();
  1158. }
  1159. }
  1160. }
  1161. private IOverlay emptyListMsgOverlay;
  1162. /// <summary>
  1163. /// Gets the collection of objects that survive any filtering that may be in place.
  1164. /// </summary>
  1165. /// <remarks>
  1166. /// <para>
  1167. /// This collection is the result of filtering the current list of objects.
  1168. /// It is not a snapshot of the filtered list that was last used to build the control.
  1169. /// </para>
  1170. /// <para>
  1171. /// Normal warnings apply when using this with virtual lists. It will work, but it
  1172. /// may take a while.
  1173. /// </para>
  1174. /// </remarks>
  1175. [Browsable(false),
  1176. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1177. virtual public IEnumerable FilteredObjects {
  1178. get {
  1179. if (this.IsFiltering)
  1180. return this.FilterObjects(this.Objects, this.ModelFilter, this.ListFilter);
  1181. else
  1182. return this.Objects;
  1183. }
  1184. }
  1185. /// <summary>
  1186. /// Gets or sets the strategy object that will be used to build the Filter menu
  1187. /// </summary>
  1188. /// <remarks>If this is null, no filter menu will be built.</remarks>
  1189. [Browsable(false),
  1190. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1191. public FilterMenuBuilder FilterMenuBuildStrategy {
  1192. get { return filterMenuBuilder; }
  1193. set { filterMenuBuilder = value; }
  1194. }
  1195. private FilterMenuBuilder filterMenuBuilder = new FilterMenuBuilder();
  1196. /// <summary>
  1197. /// Get or set whether or not the listview is frozen. When the listview is
  1198. /// frozen, it will not update itself.
  1199. /// </summary>
  1200. /// <remarks><para>The Frozen property is similar to the methods Freeze()/Unfreeze()
  1201. /// except that changes to the Frozen property do not nest.</para></remarks>
  1202. /// <example>objectListView1.Frozen = false; // unfreeze the control regardless of the number of Freeze() calls
  1203. /// </example>
  1204. [Browsable(false),
  1205. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1206. public virtual bool Frozen {
  1207. get { return freezeCount > 0; }
  1208. set {
  1209. if (value)
  1210. Freeze();
  1211. else if (freezeCount > 0) {
  1212. freezeCount = 1;
  1213. Unfreeze();
  1214. }
  1215. }
  1216. }
  1217. private int freezeCount;
  1218. /// <summary>
  1219. /// Hide the Groups collection so it's not visible in the Properties grid.
  1220. /// </summary>
  1221. [Browsable(false),
  1222. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1223. new public ListViewGroupCollection Groups {
  1224. get { return base.Groups; }
  1225. }
  1226. /// <summary>
  1227. /// Gets or sets the image list from which group header will take their images
  1228. /// </summary>
  1229. /// <remarks>If this is not set, then group headers will not show any images.</remarks>
  1230. [Category("ObjectListView"),
  1231. Description("The image list from which group header will take their images"),
  1232. DefaultValue(null)]
  1233. public ImageList GroupImageList {
  1234. get { return this.groupImageList; }
  1235. set {
  1236. this.groupImageList = value;
  1237. if (this.Created) {
  1238. int x = NativeMethods.SetGroupImageList(this, value);
  1239. }
  1240. }
  1241. }
  1242. private ImageList groupImageList;
  1243. /// <summary>
  1244. /// Gets how the group label should be formatted when a group is empty or
  1245. /// contains more than one item
  1246. /// </summary>
  1247. /// <remarks>
  1248. /// The given format string must have two placeholders:
  1249. /// <list type="bullet">
  1250. /// <item><description>{0} - the original group title</description></item>
  1251. /// <item><description>{1} - the number of items in the group</description></item>
  1252. /// </list>
  1253. /// </remarks>
  1254. /// <example>"{0} [{1} items]"</example>
  1255. [Category("ObjectListView"),
  1256. Description("The format to use when suffixing item counts to group titles"),
  1257. DefaultValue(null),
  1258. Localizable(true)]
  1259. public virtual string GroupWithItemCountFormat {
  1260. get { return groupWithItemCountFormat; }
  1261. set { groupWithItemCountFormat = value; }
  1262. }
  1263. private string groupWithItemCountFormat;
  1264. /// <summary>
  1265. /// Return this.GroupWithItemCountFormat or a reasonable default
  1266. /// </summary>
  1267. [Browsable(false)]
  1268. public virtual string GroupWithItemCountFormatOrDefault {
  1269. get {
  1270. if (String.IsNullOrEmpty(this.GroupWithItemCountFormat))
  1271. return "{0} [{1} items]";
  1272. else
  1273. return this.GroupWithItemCountFormat;
  1274. }
  1275. }
  1276. /// <summary>
  1277. /// Gets how the group label should be formatted when a group contains only a single item
  1278. /// </summary>
  1279. /// <remarks>
  1280. /// The given format string must have two placeholders:
  1281. /// <list type="bullet">
  1282. /// <item><description>{0} - the original group title</description></item>
  1283. /// <item><description>{1} - the number of items in the group (always 1)</description></item>
  1284. /// </list>
  1285. /// </remarks>
  1286. /// <example>"{0} [{1} item]"</example>
  1287. [Category("ObjectListView"),
  1288. Description("The format to use when suffixing item counts to group titles"),
  1289. DefaultValue(null),
  1290. Localizable(true)]
  1291. public virtual string GroupWithItemCountSingularFormat {
  1292. get { return groupWithItemCountSingularFormat; }
  1293. set { groupWithItemCountSingularFormat = value; }
  1294. }
  1295. private string groupWithItemCountSingularFormat;
  1296. /// <summary>
  1297. /// Gets GroupWithItemCountSingularFormat or a reasonable default
  1298. /// </summary>
  1299. [Browsable(false)]
  1300. public virtual string GroupWithItemCountSingularFormatOrDefault {
  1301. get {
  1302. if (String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat))
  1303. return "{0} [{1} item]";
  1304. else
  1305. return this.GroupWithItemCountSingularFormat;
  1306. }
  1307. }
  1308. /// <summary>
  1309. /// Gets or sets whether or not the groups in this ObjectListView should be collapsible.
  1310. /// </summary>
  1311. /// <remarks>
  1312. /// This feature only works under Vista and later.
  1313. /// </remarks>
  1314. [Browsable(true),
  1315. Category("ObjectListView"),
  1316. Description("Should the groups in this control be collapsible (Vista and later only)."),
  1317. DefaultValue(true)]
  1318. public bool HasCollapsibleGroups {
  1319. get { return hasCollapsibleGroups; }
  1320. set { hasCollapsibleGroups = value; }
  1321. }
  1322. private bool hasCollapsibleGroups = true;
  1323. /// <summary>
  1324. /// Does this listview have a message that should be drawn when the list is empty?
  1325. /// </summary>
  1326. [Browsable(false)]
  1327. public virtual bool HasEmptyListMsg {
  1328. get { return !String.IsNullOrEmpty(this.EmptyListMsg); }
  1329. }
  1330. /// <summary>
  1331. /// Get whether there are any overlays to be drawn
  1332. /// </summary>
  1333. [Browsable(false)]
  1334. public bool HasOverlays {
  1335. get {
  1336. return (this.Overlays.Count > 2 ||
  1337. this.imageOverlay.Image != null ||
  1338. !String.IsNullOrEmpty(this.textOverlay.Text));
  1339. }
  1340. }
  1341. /// <summary>
  1342. /// Gets the header control for the ListView
  1343. /// </summary>
  1344. [Browsable(false)]
  1345. public HeaderControl HeaderControl {
  1346. get {
  1347. if (this.headerControl == null)
  1348. this.headerControl = new HeaderControl(this);
  1349. return this.headerControl;
  1350. }
  1351. }
  1352. private HeaderControl headerControl;
  1353. /// <summary>
  1354. /// Gets or sets the font in which the text of the column headers will be drawn
  1355. /// </summary>
  1356. /// <remarks>Individual columns can override this through their HeaderFormatStyle property.</remarks>
  1357. [DefaultValue(null)]
  1358. [Browsable(false)]
  1359. [Obsolete("Use a HeaderFormatStyle instead", false)]
  1360. public Font HeaderFont {
  1361. get { return this.HeaderFormatStyle == null ? null : this.HeaderFormatStyle.Normal.Font; }
  1362. set {
  1363. if (value == null && this.HeaderFormatStyle == null)
  1364. return;
  1365. if (this.HeaderFormatStyle == null)
  1366. this.HeaderFormatStyle = new HeaderFormatStyle();
  1367. this.HeaderFormatStyle.SetFont(value);
  1368. }
  1369. }
  1370. /// <summary>
  1371. /// Gets or sets the style that will be used to draw the columm headers of the listview
  1372. /// </summary>
  1373. /// <remarks>
  1374. /// <para>
  1375. /// This is only used when HeaderUsesThemes is false.
  1376. /// </para>
  1377. /// <para>
  1378. /// Individual columns can override this through their HeaderFormatStyle property.
  1379. /// </para>
  1380. /// </remarks>
  1381. [Category("ObjectListView"),
  1382. Description("What style will be used to draw the control's header"),
  1383. DefaultValue(null)]
  1384. public HeaderFormatStyle HeaderFormatStyle {
  1385. get { return this.headerFormatStyle; }
  1386. set { this.headerFormatStyle = value; }
  1387. }
  1388. private HeaderFormatStyle headerFormatStyle;
  1389. /// <summary>
  1390. /// Gets or sets the maximum height of the header. -1 means no maximum.
  1391. /// </summary>
  1392. [Category("ObjectListView"),
  1393. Description("What is the maximum height of the header? -1 means no maximum"),
  1394. DefaultValue(-1)]
  1395. public int HeaderMaximumHeight {
  1396. get { return headerMaximumHeight; }
  1397. set { headerMaximumHeight = value; }
  1398. }
  1399. private int headerMaximumHeight = -1;
  1400. /// <summary>
  1401. /// Gets or sets whether the header will be drawn strictly according to the OS's theme.
  1402. /// </summary>
  1403. /// <remarks>
  1404. /// <para>
  1405. /// If this is set to true, the header will be rendered completely by the system, without
  1406. /// any of ObjectListViews fancy processing -- no images in header, no filter indicators,
  1407. /// no word wrapping, no header styling.
  1408. /// </para>
  1409. /// <para>If this is set to false, ObjectListView will render the header as it thinks best.
  1410. /// If no special features are required, then ObjectListView will delegate rendering to the OS.
  1411. /// Otherwise, ObjectListView will draw the header according to the configuration settings.
  1412. /// </para>
  1413. /// <para>
  1414. /// The effect of not being themed will be different from OS to OS. At
  1415. /// very least, the sort indicator will not be standard.
  1416. /// </para>
  1417. /// </remarks>
  1418. [Category("ObjectListView"),
  1419. Description("Will the column headers be drawn strictly according to OS theme?"),
  1420. DefaultValue(true)]
  1421. public bool HeaderUsesThemes {
  1422. get { return this.headerUsesThemes; }
  1423. set { this.headerUsesThemes = value; }
  1424. }
  1425. private bool headerUsesThemes = true;
  1426. /// <summary>
  1427. /// Gets or sets the whether the text in the header will be word wrapped.
  1428. /// </summary>
  1429. /// <remarks>
  1430. /// <para>THIS FEATURE IS EXPERIMENTAL (August 2009)</para>
  1431. /// <para>Line breaks will be applied between words. Words that are too long
  1432. /// will still be ellipsed.</para>
  1433. /// <para>
  1434. /// As with all settings that make the header look different, HeaderUsesThemes must be set to false, otherwise
  1435. /// the OS will be responsible for drawing the header, and it does not allow word wrapped text.
  1436. /// </para>
  1437. /// </remarks>
  1438. [Category("ObjectListView"),
  1439. Description("Will the text of the column headers be word wrapped?"),
  1440. DefaultValue(false)]
  1441. public bool HeaderWordWrap {
  1442. get { return this.headerWordWrap; }
  1443. set {
  1444. this.headerWordWrap = value;
  1445. if (this.headerControl != null)
  1446. this.headerControl.WordWrap = value;
  1447. }
  1448. }
  1449. private bool headerWordWrap;
  1450. /// <summary>
  1451. /// Gets the tool tip that shows tips for the column headers
  1452. /// </summary>
  1453. [Browsable(false)]
  1454. public ToolTipControl HeaderToolTip {
  1455. get {
  1456. return this.HeaderControl.ToolTip;
  1457. }
  1458. }
  1459. /// <summary>
  1460. /// Gets the index of the row that the mouse is currently over
  1461. /// </summary>
  1462. [Browsable(false),
  1463. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1464. public virtual int HotRowIndex {
  1465. get { return this.hotRowIndex; }
  1466. protected set { this.hotRowIndex = value; }
  1467. }
  1468. private int hotRowIndex;
  1469. /// <summary>
  1470. /// Gets the index of the subitem that the mouse is currently over
  1471. /// </summary>
  1472. [Browsable(false),
  1473. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1474. public virtual int HotColumnIndex {
  1475. get { return this.hotColumnIndex; }
  1476. protected set { this.hotColumnIndex = value; }
  1477. }
  1478. private int hotColumnIndex;
  1479. /// <summary>
  1480. /// Gets the part of the item/subitem that the mouse is currently over
  1481. /// </summary>
  1482. [Browsable(false),
  1483. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1484. public virtual HitTestLocation HotCellHitLocation {
  1485. get { return this.hotCellHitLocation; }
  1486. protected set { this.hotCellHitLocation = value; }
  1487. }
  1488. private HitTestLocation hotCellHitLocation;
  1489. /// <summary>
  1490. /// The index of the item that is 'hot', i.e. under the cursor. -1 means no item.
  1491. /// </summary>
  1492. [Browsable(false),
  1493. Obsolete("Use HotRowIndex instead", false)]
  1494. public virtual int HotItemIndex {
  1495. get { return this.HotRowIndex; }
  1496. }
  1497. /// <summary>
  1498. /// What sort of formatting should be applied to the row under the cursor?
  1499. /// </summary>
  1500. /// <remarks>This only takes effect when UseHotItem is true.</remarks>
  1501. [Category("ObjectListView"),
  1502. Description("How should the row under the cursor be highlighted"),
  1503. DefaultValue(null)]
  1504. public virtual HotItemStyle HotItemStyle {
  1505. get { return this.hotItemStyle; }
  1506. set {
  1507. if (this.HotItemStyle != null)
  1508. this.RemoveOverlay(this.HotItemStyle.Overlay);
  1509. this.hotItemStyle = value;
  1510. if (this.HotItemStyle != null)
  1511. this.AddOverlay(this.HotItemStyle.Overlay);
  1512. }
  1513. }
  1514. private HotItemStyle hotItemStyle;
  1515. /// <summary>
  1516. /// What sort of formatting should be applied to hyperlinks?
  1517. /// </summary>
  1518. [Category("ObjectListView"),
  1519. Description("How should hyperlinks be drawn"),
  1520. DefaultValue(null)]
  1521. public virtual HyperlinkStyle HyperlinkStyle {
  1522. get { return this.hyperlinkStyle; }
  1523. set { this.hyperlinkStyle = value; }
  1524. }
  1525. private HyperlinkStyle hyperlinkStyle;
  1526. /// <summary>
  1527. /// What color should be used for the background of selected rows?
  1528. /// </summary>
  1529. /// <remarks>Windows does not give the option of changing the selection background.
  1530. /// So the control has to be owner drawn to see the result of this setting.
  1531. /// Setting UseCustomSelectionColors = true will do this for you.</remarks>
  1532. [Category("ObjectListView"),
  1533. Description("The background foregroundColor of selected rows when the control is owner drawn"),
  1534. DefaultValue(typeof(Color), "")]
  1535. public virtual Color HighlightBackgroundColor {
  1536. get { return highlightBackgroundColor; }
  1537. set { highlightBackgroundColor = value; }
  1538. }
  1539. private Color highlightBackgroundColor = Color.Empty;
  1540. /// <summary>
  1541. /// Return the color should be used for the background of selected rows or a reasonable default
  1542. /// </summary>
  1543. [Browsable(false)]
  1544. public virtual Color HighlightBackgroundColorOrDefault {
  1545. get {
  1546. if (this.HighlightBackgroundColor.IsEmpty)
  1547. return SystemColors.Highlight;
  1548. else
  1549. return this.HighlightBackgroundColor;
  1550. }
  1551. }
  1552. /// <summary>
  1553. /// What color should be used for the foreground of selected rows?
  1554. /// </summary>
  1555. /// <remarks>Windows does not give the option of changing the selection foreground (text color).
  1556. /// So the control has to be owner drawn to see the result of this setting.
  1557. /// Setting UseCustomSelectionColors = true will do this for you.</remarks>
  1558. [Category("ObjectListView"),
  1559. Description("The foreground foregroundColor of selected rows when the control is owner drawn"),
  1560. DefaultValue(typeof(Color), "")]
  1561. public virtual Color HighlightForegroundColor {
  1562. get { return highlightForegroundColor; }
  1563. set { highlightForegroundColor = value; }
  1564. }
  1565. private Color highlightForegroundColor = Color.Empty;
  1566. /// <summary>
  1567. /// Return the color should be used for the foreground of selected rows or a reasonable default
  1568. /// </summary>
  1569. [Browsable(false)]
  1570. public virtual Color HighlightForegroundColorOrDefault {
  1571. get {
  1572. if (this.HighlightForegroundColor.IsEmpty)
  1573. return SystemColors.HighlightText;
  1574. else
  1575. return this.HighlightForegroundColor;
  1576. }
  1577. }
  1578. /// <summary>
  1579. /// Gets or sets whether or not hidden columns should be included in the text representation
  1580. /// of rows that are copied or dragged to another application. If this is false (the default),
  1581. /// only visible columns will be included.
  1582. /// </summary>
  1583. [Category("ObjectListView"),
  1584. Description("When rows are copied or dragged, will data in hidden columns be included in the text? If this is false, only visible columns will be included."),
  1585. DefaultValue(false)]
  1586. public virtual bool IncludeHiddenColumnsInDataTransfer {
  1587. get { return includeHiddenColumnsInDataTransfer; }
  1588. set { includeHiddenColumnsInDataTransfer = value; }
  1589. }
  1590. private bool includeHiddenColumnsInDataTransfer = false;
  1591. /// <summary>
  1592. /// Gets or sets whether or not hidden columns should be included in the text representation
  1593. /// of rows that are copied or dragged to another application. If this is false (the default),
  1594. /// only visible columns will be included.
  1595. /// </summary>
  1596. [Category("ObjectListView"),
  1597. Description("When rows are copied, will column headers be in the text?."),
  1598. DefaultValue(false)]
  1599. public virtual bool IncludeColumnHeadersInCopy
  1600. {
  1601. get { return includeColumnHeadersInCopy; }
  1602. set { includeColumnHeadersInCopy = value; }
  1603. }
  1604. private bool includeColumnHeadersInCopy = false;
  1605. /// <summary>
  1606. /// Return true if a cell edit operation is currently happening
  1607. /// </summary>
  1608. [Browsable(false)]
  1609. public virtual bool IsCellEditing {
  1610. get { return this.cellEditor != null; }
  1611. }
  1612. /// <summary>
  1613. /// Return true if the ObjectListView is being used within the development environment.
  1614. /// </summary>
  1615. [Browsable(false)]
  1616. public virtual bool IsDesignMode {
  1617. get { return this.DesignMode; }
  1618. }
  1619. /// <summary>
  1620. /// Gets whether or not the current list is filtering its contents
  1621. /// </summary>
  1622. [Browsable(false),
  1623. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1624. virtual public bool IsFiltering {
  1625. get { return this.UseFiltering && (this.ModelFilter != null || this.ListFilter != null); }
  1626. }
  1627. /// <summary>
  1628. /// When the user types into a list, should the values in the current sort column be searched to find a match?
  1629. /// If this is false, the primary column will always be used regardless of the sort column.
  1630. /// </summary>
  1631. /// <remarks>When this is true, the behavior is like that of ITunes.</remarks>
  1632. [Category("ObjectListView"),
  1633. Description("When the user types into a list, should the values in the current sort column be searched to find a match?"),
  1634. DefaultValue(true)]
  1635. public virtual bool IsSearchOnSortColumn {
  1636. get { return isSearchOnSortColumn; }
  1637. set { isSearchOnSortColumn = value; }
  1638. }
  1639. private bool isSearchOnSortColumn = true;
  1640. /// <summary>
  1641. /// Gets or sets if this control will use a SimpleDropSink to receive drops
  1642. /// </summary>
  1643. /// <remarks>
  1644. /// <para>
  1645. /// Setting this replaces any previous DropSink.
  1646. /// </para>
  1647. /// <para>
  1648. /// After setting this to true, the SimpleDropSink will still need to be configured
  1649. /// to say when it can accept drops and what should happen when something is dropped.
  1650. /// The need to do these things makes this property mostly useless :(
  1651. /// </para>
  1652. /// </remarks>
  1653. [Category("ObjectListView"),
  1654. Description("Should this control will use a SimpleDropSink to receive drops."),
  1655. DefaultValue(false)]
  1656. public virtual bool IsSimpleDropSink {
  1657. get { return this.DropSink != null; }
  1658. set {
  1659. if (value)
  1660. this.DropSink = new SimpleDropSink();
  1661. else
  1662. this.DropSink = null;
  1663. }
  1664. }
  1665. /// <summary>
  1666. /// Gets or sets if this control will use a SimpleDragSource to initiate drags
  1667. /// </summary>
  1668. /// <remarks>Setting this replaces any previous DragSource</remarks>
  1669. [Category("ObjectListView"),
  1670. Description("Should this control use a SimpleDragSource to initiate drags out from this control"),
  1671. DefaultValue(false)]
  1672. public virtual bool IsSimpleDragSource {
  1673. get { return this.DragSource != null; }
  1674. set {
  1675. if (value)
  1676. this.DragSource = new SimpleDragSource();
  1677. else
  1678. this.DragSource = null;
  1679. }
  1680. }
  1681. /// <summary>
  1682. /// Hide the Items collection so it's not visible in the Properties grid.
  1683. /// </summary>
  1684. [Browsable(false),
  1685. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1686. new public ListViewItemCollection Items {
  1687. get { return base.Items; }
  1688. }
  1689. /// <summary>
  1690. /// This renderer draws the items when in the list is in non-details view.
  1691. /// In details view, the renderers for the individuals columns are responsible.
  1692. /// </summary>
  1693. [Category("ObjectListView"),
  1694. Description("The owner drawn renderer that draws items when the list is in non-Details view."),
  1695. DefaultValue(null)]
  1696. public IRenderer ItemRenderer {
  1697. get { return itemRenderer; }
  1698. set { itemRenderer = value; }
  1699. }
  1700. private IRenderer itemRenderer;
  1701. /// <summary>
  1702. /// Which column did we last sort by
  1703. /// </summary>
  1704. /// <remarks>This is an alias for PrimarySortColumn</remarks>
  1705. [Browsable(false),
  1706. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1707. public virtual OLVColumn LastSortColumn {
  1708. get { return this.PrimarySortColumn; }
  1709. set { this.PrimarySortColumn = value; }
  1710. }
  1711. /// <summary>
  1712. /// Which direction did we last sort
  1713. /// </summary>
  1714. /// <remarks>This is an alias for PrimarySortOrder</remarks>
  1715. [Browsable(false),
  1716. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1717. public virtual SortOrder LastSortOrder {
  1718. get { return this.PrimarySortOrder; }
  1719. set { this.PrimarySortOrder = value; }
  1720. }
  1721. /// <summary>
  1722. /// Gets or sets the filter that is applied to our whole list of objects.
  1723. /// </summary>
  1724. /// <remarks>
  1725. /// The list is updated immediately to reflect this filter.
  1726. /// </remarks>
  1727. [Browsable(false),
  1728. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1729. public virtual IListFilter ListFilter {
  1730. get { return listFilter; }
  1731. set {
  1732. listFilter = value;
  1733. if (this.UseFiltering)
  1734. this.UpdateFiltering();
  1735. }
  1736. }
  1737. private IListFilter listFilter;
  1738. /// <summary>
  1739. /// Gets or sets the filter that is applied to each model objects in the list
  1740. /// </summary>
  1741. /// <remarks>
  1742. /// The list is updated immediately to reflect this filter.
  1743. /// </remarks>
  1744. [Browsable(false),
  1745. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1746. public virtual IModelFilter ModelFilter {
  1747. get { return modelFilter; }
  1748. set {
  1749. modelFilter = value;
  1750. if (this.UseFiltering)
  1751. this.UpdateFiltering();
  1752. }
  1753. }
  1754. private IModelFilter modelFilter;
  1755. /// <summary>
  1756. /// Gets the hit test info last time the mouse was moved.
  1757. /// </summary>
  1758. /// <remarks>Useful for hot item processing.</remarks>
  1759. [Browsable(false),
  1760. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1761. public virtual OlvListViewHitTestInfo MouseMoveHitTest {
  1762. get { return mouseMoveHitTest; }
  1763. private set { mouseMoveHitTest = value; }
  1764. }
  1765. private OlvListViewHitTestInfo mouseMoveHitTest;
  1766. /// <summary>
  1767. /// Gets or sets the list of groups shown by the listview.
  1768. /// </summary>
  1769. /// <remarks>
  1770. /// This property does not work like the .NET Groups property. It should
  1771. /// be treated as a read-only property.
  1772. /// Changes made to the list are NOT reflected in the ListView itself -- it is pointless to add
  1773. /// or remove groups to/from this list. Such modifications will do nothing.
  1774. /// To do such things, you must listen for
  1775. /// BeforeCreatingGroups or AboutToCreateGroups events, and change the list of
  1776. /// groups in those events.
  1777. /// </remarks>
  1778. [Browsable(false),
  1779. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1780. public IList<OLVGroup> OLVGroups {
  1781. get { return this.olvGroups; }
  1782. set { this.olvGroups = value; }
  1783. }
  1784. private IList<OLVGroup> olvGroups;
  1785. /// <summary>
  1786. /// Gets or sets whether the user wants to owner draw the header control
  1787. /// themselves. If this is false (the default), ObjectListView will use
  1788. /// custom drawing to render the header, if needed.
  1789. /// </summary>
  1790. /// <remarks>
  1791. /// If you listen for the DrawColumnHeader event, you need to set this to true,
  1792. /// otherwise your event handler will not be called.
  1793. /// </remarks>
  1794. [Category("ObjectListView"),
  1795. Description("Should the DrawColumnHeader event be triggered"),
  1796. DefaultValue(false)]
  1797. public bool OwnerDrawnHeader {
  1798. get { return ownerDrawnHeader; }
  1799. set { ownerDrawnHeader = value; }
  1800. }
  1801. private bool ownerDrawnHeader;
  1802. /// <summary>
  1803. /// Get/set the collection of objects that this list will show
  1804. /// </summary>
  1805. /// <remarks>
  1806. /// <para>
  1807. /// The contents of the control will be updated immediately after setting this property.
  1808. /// </para>
  1809. /// <para>This method preserves selection, if possible. Use SetObjects() if
  1810. /// you do not want to preserve the selection. Preserving selection is the slowest part of this
  1811. /// code and performance is O(n) where n is the number of selected rows.</para>
  1812. /// <para>This method is not thread safe.</para>
  1813. /// <para>The property DOES work on virtual lists: setting is problem-free, but if you try to get it
  1814. /// and the list has 10 million objects, it may take some time to return.</para>
  1815. /// </remarks>
  1816. [Browsable(false),
  1817. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1818. public virtual IEnumerable Objects {
  1819. get {
  1820. return this.objects;
  1821. }
  1822. set {
  1823. this.BeginUpdate();
  1824. try {
  1825. IList previousSelection = this.SelectedObjects;
  1826. this.SetObjects(value);
  1827. this.SelectedObjects = previousSelection;
  1828. } finally {
  1829. this.EndUpdate();
  1830. }
  1831. }
  1832. }
  1833. private IEnumerable objects;
  1834. /// <summary>
  1835. /// Gets or sets the image that will be drawn over the top of the ListView
  1836. /// </summary>
  1837. [Category("ObjectListView"),
  1838. Description("The image that will be drawn over the top of the ListView"),
  1839. DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  1840. public ImageOverlay OverlayImage {
  1841. get { return this.imageOverlay; }
  1842. set {
  1843. if (this.imageOverlay == value)
  1844. return;
  1845. this.RemoveOverlay(this.imageOverlay);
  1846. this.imageOverlay = value;
  1847. this.AddOverlay(this.imageOverlay);
  1848. }
  1849. }
  1850. private ImageOverlay imageOverlay;
  1851. /// <summary>
  1852. /// Gets or sets the text that will be drawn over the top of the ListView
  1853. /// </summary>
  1854. [Category("ObjectListView"),
  1855. Description("The text that will be drawn over the top of the ListView"),
  1856. DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  1857. public TextOverlay OverlayText {
  1858. get { return this.textOverlay; }
  1859. set {
  1860. if (this.textOverlay == value)
  1861. return;
  1862. this.RemoveOverlay(this.textOverlay);
  1863. this.textOverlay = value;
  1864. this.AddOverlay(this.textOverlay);
  1865. }
  1866. }
  1867. private TextOverlay textOverlay;
  1868. /// <summary>
  1869. /// Gets or sets the transparency of all the overlays.
  1870. /// 0 is completely transparent, 255 is completely opaque.
  1871. /// </summary>
  1872. /// <remarks>
  1873. /// This is obsolete. Use Transparency on each overlay.
  1874. /// </remarks>
  1875. [Browsable(false),
  1876. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1877. public int OverlayTransparency {
  1878. get { return this.overlayTransparency; }
  1879. set { this.overlayTransparency = Math.Min(255, Math.Max(0, value)); }
  1880. }
  1881. private int overlayTransparency = 128;
  1882. /// <summary>
  1883. /// Gets the list of overlays that will be drawn on top of the ListView
  1884. /// </summary>
  1885. /// <remarks>
  1886. /// You can add new overlays and remove overlays that you have added, but
  1887. /// don't mess with the overlays that you didn't create.
  1888. /// </remarks>
  1889. [Browsable(false)]
  1890. protected IList<IOverlay> Overlays {
  1891. get { return this.overlays; }
  1892. }
  1893. private List<IOverlay> overlays = new List<IOverlay>();
  1894. /// <summary>
  1895. /// Which column did we last sort by
  1896. /// </summary>
  1897. [Browsable(false),
  1898. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1899. public virtual OLVColumn PrimarySortColumn {
  1900. get { return this.primarySortColumn; }
  1901. set {
  1902. this.primarySortColumn = value;
  1903. if (this.TintSortColumn) {
  1904. this.SelectedColumn = value;
  1905. }
  1906. }
  1907. }
  1908. private OLVColumn primarySortColumn;
  1909. /// <summary>
  1910. /// Which direction did we last sort
  1911. /// </summary>
  1912. [Browsable(false),
  1913. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  1914. public virtual SortOrder PrimarySortOrder {
  1915. get { return primarySortOrder; }
  1916. set { primarySortOrder = value; }
  1917. }
  1918. private SortOrder primarySortOrder;
  1919. /// <summary>
  1920. /// Gets or sets if non-editable checkboxes are drawn as disabled.
  1921. /// </summary>
  1922. /// <remarks>
  1923. /// <para>This only has effect in owner drawn mode.</para>
  1924. /// </remarks>
  1925. [Category("ObjectListView"),
  1926. Description("Should non-editable checkboxes be drawn as disabled?"),
  1927. DefaultValue(false)]
  1928. public virtual bool RenderNonEditableCheckboxesAsDisabled {
  1929. get { return renderNonEditableCheckboxesAsDisabled; }
  1930. set { renderNonEditableCheckboxesAsDisabled = value; }
  1931. }
  1932. private bool renderNonEditableCheckboxesAsDisabled = false;
  1933. /// <summary>
  1934. /// Specify the height of each row in the control in pixels.
  1935. /// </summary>
  1936. /// <remarks><para>The row height in a listview is normally determined by the font size and the small image list size.
  1937. /// This setting allows that calculation to be overridden (within reason: you still cannot set the line height to be
  1938. /// less than the line height of the font used in the control). </para>
  1939. /// <para>Setting it to -1 means use the normal calculation method.</para>
  1940. /// <para><bold>This feature is experiemental!</bold> Strange things may happen to your program,
  1941. /// your spouse or your pet if you use it.</para>
  1942. /// </remarks>
  1943. [Category("ObjectListView"),
  1944. Description("Specify the height of each row in pixels. -1 indicates default height"),
  1945. DefaultValue(-1)]
  1946. public virtual int RowHeight {
  1947. get { return rowHeight; }
  1948. set {
  1949. if (value < 1)
  1950. rowHeight = -1;
  1951. else
  1952. rowHeight = value;
  1953. if (this.DesignMode)
  1954. return;
  1955. this.SetupBaseImageList();
  1956. if (this.CheckBoxes)
  1957. this.InitializeStateImageList();
  1958. }
  1959. }
  1960. private int rowHeight = -1;
  1961. /// <summary>
  1962. /// How many pixels high is each row?
  1963. /// </summary>
  1964. [Browsable(false)]
  1965. public virtual int RowHeightEffective {
  1966. get {
  1967. switch (this.View) {
  1968. case View.List:
  1969. case View.SmallIcon:
  1970. case View.Details:
  1971. return Math.Max(this.SmallImageSize.Height, this.Font.Height);
  1972. case View.Tile:
  1973. return this.TileSize.Height;
  1974. case View.LargeIcon:
  1975. if (this.LargeImageList == null)
  1976. return this.Font.Height;
  1977. else
  1978. return Math.Max(this.LargeImageList.ImageSize.Height, this.Font.Height);
  1979. default:
  1980. // This should never happen
  1981. return 0;
  1982. }
  1983. }
  1984. }
  1985. /// <summary>
  1986. /// How many rows appear on each page of this control
  1987. /// </summary>
  1988. [Browsable(false)]
  1989. public virtual int RowsPerPage {
  1990. get {
  1991. return NativeMethods.GetCountPerPage(this);
  1992. }
  1993. }
  1994. /// <summary>
  1995. /// Get/set the column that will be used to resolve comparisons that are equal when sorting.
  1996. /// </summary>
  1997. /// <remarks>There is no user interface for this setting. It must be set programmatically.</remarks>
  1998. [Browsable(false),
  1999. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2000. public virtual OLVColumn SecondarySortColumn {
  2001. get { return this.secondarySortColumn; }
  2002. set { this.secondarySortColumn = value; }
  2003. }
  2004. private OLVColumn secondarySortColumn;
  2005. /// <summary>
  2006. /// When the SecondarySortColumn is used, in what order will it compare results?
  2007. /// </summary>
  2008. [Browsable(false),
  2009. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2010. public virtual SortOrder SecondarySortOrder {
  2011. get { return this.secondarySortOrder; }
  2012. set { this.secondarySortOrder = value; }
  2013. }
  2014. private SortOrder secondarySortOrder = SortOrder.None;
  2015. /// <summary>
  2016. /// Gets or sets if all rows should be selected when the user presses Ctrl-A
  2017. /// </summary>
  2018. [Category("ObjectListView"),
  2019. Description("Should the control select all rows when the user presses Ctrl-A?"),
  2020. DefaultValue(true)]
  2021. public virtual bool SelectAllOnControlA {
  2022. get { return selectAllOnControlA; }
  2023. set { selectAllOnControlA = value; }
  2024. }
  2025. private bool selectAllOnControlA = true;
  2026. /// <summary>
  2027. /// When the user right clicks on the column headers, should a menu be presented which will allow
  2028. /// them to choose which columns will be shown in the view?
  2029. /// </summary>
  2030. /// <remarks>This is just a compatibility wrapper for the SelectColumnsOnRightClickBehaviour
  2031. /// property.</remarks>
  2032. [Category("ObjectListView"),
  2033. Description("When the user right clicks on the column headers, should a menu be presented which will allow them to choose which columns will be shown in the view?"),
  2034. DefaultValue(true)]
  2035. public virtual bool SelectColumnsOnRightClick {
  2036. get { return this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None; }
  2037. set {
  2038. if (value) {
  2039. if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.None)
  2040. this.SelectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.InlineMenu;
  2041. } else {
  2042. this.SelectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.None;
  2043. }
  2044. }
  2045. }
  2046. /// <summary>
  2047. /// Gets or sets how the user will be able to select columns when the header is right clicked
  2048. /// </summary>
  2049. [Category("ObjectListView"),
  2050. Description("When the user right clicks on the column headers, how will the user be able to select columns?"),
  2051. DefaultValue(ColumnSelectBehaviour.InlineMenu)]
  2052. public virtual ColumnSelectBehaviour SelectColumnsOnRightClickBehaviour {
  2053. get { return selectColumnsOnRightClickBehaviour; }
  2054. set { selectColumnsOnRightClickBehaviour = value; }
  2055. }
  2056. private ColumnSelectBehaviour selectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.InlineMenu;
  2057. /// <summary>
  2058. /// When the column select menu is open, should it stay open after an item is selected?
  2059. /// Staying open allows the user to turn more than one column on or off at a time.
  2060. /// </summary>
  2061. /// <remarks>This only works when SelectColumnsOnRightClickBehaviour is set to InlineMenu.
  2062. /// It has no effect when the behaviour is set to SubMenu.</remarks>
  2063. [Category("ObjectListView"),
  2064. Description("When the column select inline menu is open, should it stay open after an item is selected?"),
  2065. DefaultValue(true)]
  2066. public virtual bool SelectColumnsMenuStaysOpen {
  2067. get { return selectColumnsMenuStaysOpen; }
  2068. set { selectColumnsMenuStaysOpen = value; }
  2069. }
  2070. private bool selectColumnsMenuStaysOpen = true;
  2071. /// <summary>
  2072. /// Gets or sets the column that is drawn with a slight tint.
  2073. /// </summary>
  2074. /// <remarks>
  2075. /// <para>
  2076. /// If TintSortColumn is true, the sort column will automatically
  2077. /// be made the selected column.
  2078. /// </para>
  2079. /// <para>
  2080. /// The colour of the tint is controlled by SelectedColumnTint.
  2081. /// </para>
  2082. /// </remarks>
  2083. [Browsable(false),
  2084. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2085. public OLVColumn SelectedColumn {
  2086. get { return this.selectedColumn; }
  2087. set {
  2088. this.selectedColumn = value;
  2089. if (value == null) {
  2090. this.RemoveDecoration(this.selectedColumnDecoration);
  2091. } else {
  2092. if (!this.HasDecoration(this.selectedColumnDecoration))
  2093. this.AddDecoration(this.selectedColumnDecoration);
  2094. }
  2095. }
  2096. }
  2097. private OLVColumn selectedColumn;
  2098. private TintedColumnDecoration selectedColumnDecoration = new TintedColumnDecoration();
  2099. /// <summary>
  2100. /// Gets or sets the decoration that will be drawn on all selected rows
  2101. /// </summary>
  2102. [Browsable(false),
  2103. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2104. public virtual IDecoration SelectedRowDecoration {
  2105. get { return this.selectedRowDecoration; }
  2106. set { this.selectedRowDecoration = value; }
  2107. }
  2108. private IDecoration selectedRowDecoration;
  2109. /// <summary>
  2110. /// What color should be used to tint the selected column?
  2111. /// </summary>
  2112. /// <remarks>
  2113. /// The tint color must be alpha-blendable, so if the given color is solid
  2114. /// (i.e. alpha = 255), it will be changed to have a reasonable alpha value.
  2115. /// </remarks>
  2116. [Category("ObjectListView"),
  2117. Description("The color that will be used to tint the selected column"),
  2118. DefaultValue(typeof(Color), "")]
  2119. public virtual Color SelectedColumnTint {
  2120. get { return selectedColumnTint; }
  2121. set {
  2122. if (value.A == 255)
  2123. this.selectedColumnTint = Color.FromArgb(15, value);
  2124. else
  2125. this.selectedColumnTint = value;
  2126. this.selectedColumnDecoration.Tint = this.selectedColumnTint;
  2127. }
  2128. }
  2129. private Color selectedColumnTint = Color.Empty;
  2130. /// <summary>
  2131. /// Gets or sets the index of the row that is currently selected.
  2132. /// When getting the index, if no row is selected,or more than one is selected, return -1.
  2133. /// </summary>
  2134. [Browsable(false),
  2135. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2136. public virtual int SelectedIndex {
  2137. get {
  2138. if (this.SelectedIndices.Count == 1)
  2139. return this.SelectedIndices[0];
  2140. else
  2141. return -1;
  2142. }
  2143. set {
  2144. this.SelectedIndices.Clear();
  2145. if (value >= 0 && value < this.Items.Count)
  2146. this.SelectedIndices.Add(value);
  2147. }
  2148. }
  2149. /// <summary>
  2150. /// Gets or sets the ListViewItem that is currently selected . If no row is selected, or more than one is selected, return null.
  2151. /// </summary>
  2152. [Browsable(false),
  2153. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2154. public virtual OLVListItem SelectedItem {
  2155. get {
  2156. if (this.SelectedIndices.Count == 1)
  2157. return this.GetItem(this.SelectedIndices[0]);
  2158. else
  2159. return null;
  2160. }
  2161. set {
  2162. this.SelectedIndices.Clear();
  2163. if (value != null)
  2164. this.SelectedIndices.Add(value.Index);
  2165. }
  2166. }
  2167. /// <summary>
  2168. /// Gets the model object from the currently selected row, if there is only one row selected.
  2169. /// If no row is selected, or more than one is selected, returns null.
  2170. /// When setting, this will select the row that is displaying the given model object and focus on it.
  2171. /// All other rows are deselected.
  2172. /// </summary>
  2173. [Browsable(false),
  2174. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2175. public virtual Object SelectedObject {
  2176. get {
  2177. if (this.SelectedIndices.Count == 1)
  2178. return this.GetModelObject(this.SelectedIndices[0]);
  2179. else
  2180. return null;
  2181. }
  2182. set {
  2183. // If the given model is already selected, don't do anything else (prevents an flicker)
  2184. object selectedObject = this.SelectedObject;
  2185. if (selectedObject != null && selectedObject.Equals(value))
  2186. return;
  2187. this.SelectedIndices.Clear();
  2188. this.SelectObject(value, true);
  2189. }
  2190. }
  2191. /// <summary>
  2192. /// Get the model objects from the currently selected rows. If no row is selected, the returned List will be empty.
  2193. /// When setting this value, select the rows that is displaying the given model objects. All other rows are deselected.
  2194. /// </summary>
  2195. [Browsable(false),
  2196. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2197. public virtual IList SelectedObjects {
  2198. get {
  2199. ArrayList objects = new ArrayList();
  2200. foreach (int index in this.SelectedIndices)
  2201. objects.Add(this.GetModelObject(index));
  2202. return objects;
  2203. }
  2204. set {
  2205. this.SelectedIndices.Clear();
  2206. this.SelectObjects(value);
  2207. }
  2208. }
  2209. /// <summary>
  2210. /// When the user right clicks on the column headers, should a menu be presented which will allow
  2211. /// them to choose common tasks to perform on the listview?
  2212. /// </summary>
  2213. [Category("ObjectListView"),
  2214. Description("When the user right clicks on the column headers, should a menu be presented which will allow them to perform common tasks on the listview?"),
  2215. DefaultValue(false)]
  2216. public virtual bool ShowCommandMenuOnRightClick {
  2217. get { return showCommandMenuOnRightClick; }
  2218. set { showCommandMenuOnRightClick = value; }
  2219. }
  2220. private bool showCommandMenuOnRightClick = false;
  2221. /// <summary>
  2222. /// Gets or sets whether this ObjectListView will show Excel like filtering
  2223. /// menus when the header control is right clicked
  2224. /// </summary>
  2225. [Category("ObjectListView"),
  2226. Description("If this is true, right clicking on a column header will show a Filter menu option"),
  2227. DefaultValue(true)]
  2228. public bool ShowFilterMenuOnRightClick {
  2229. get { return showFilterMenuOnRightClick; }
  2230. set { showFilterMenuOnRightClick = value; }
  2231. }
  2232. private bool showFilterMenuOnRightClick = true;
  2233. /// <summary>
  2234. /// Should this list show its items in groups?
  2235. /// </summary>
  2236. [Category("Appearance"),
  2237. Description("Should the list view show items in groups?"),
  2238. DefaultValue(true)]
  2239. new public virtual bool ShowGroups {
  2240. get { return base.ShowGroups; }
  2241. set {
  2242. this.GroupImageList = this.GroupImageList;
  2243. base.ShowGroups = value;
  2244. }
  2245. }
  2246. /// <summary>
  2247. /// Should the list view show a bitmap in the column header to show the sort direction?
  2248. /// </summary>
  2249. /// <remarks>
  2250. /// The only reason for not wanting to have sort indicators is that, on pre-XP versions of
  2251. /// Windows, having sort indicators required the ListView to have a small image list, and
  2252. /// as soon as you give a ListView a SmallImageList, the text of column 0 is bumped 16
  2253. /// pixels to the right, even if you never used an image.
  2254. /// </remarks>
  2255. [Category("ObjectListView"),
  2256. Description("Should the list view show sort indicators in the column headers?"),
  2257. DefaultValue(true)]
  2258. public virtual bool ShowSortIndicators {
  2259. get { return showSortIndicators; }
  2260. set { showSortIndicators = value; }
  2261. }
  2262. private bool showSortIndicators;
  2263. /// <summary>
  2264. /// Should the list view show images on subitems?
  2265. /// </summary>
  2266. /// <remarks>
  2267. /// <para>Virtual lists have to be owner drawn in order to show images on subitems</para>
  2268. /// </remarks>
  2269. [Category("ObjectListView"),
  2270. Description("Should the list view show images on subitems?"),
  2271. DefaultValue(false)]
  2272. public virtual bool ShowImagesOnSubItems {
  2273. get { return showImagesOnSubItems; }
  2274. set {
  2275. showImagesOnSubItems = value;
  2276. if (this.Created)
  2277. this.ApplyExtendedStyles();
  2278. if (value && this.VirtualMode)
  2279. this.OwnerDraw = true;
  2280. }
  2281. }
  2282. private bool showImagesOnSubItems;
  2283. /// <summary>
  2284. /// This property controls whether group labels will be suffixed with a count of items.
  2285. /// </summary>
  2286. /// <remarks>
  2287. /// The format of the suffix is controlled by GroupWithItemCountFormat/GroupWithItemCountSingularFormat properties
  2288. /// </remarks>
  2289. [Category("ObjectListView"),
  2290. Description("Will group titles be suffixed with a count of the items in the group?"),
  2291. DefaultValue(false)]
  2292. public virtual bool ShowItemCountOnGroups {
  2293. get { return showItemCountOnGroups; }
  2294. set { showItemCountOnGroups = value; }
  2295. }
  2296. private bool showItemCountOnGroups;
  2297. /// <summary>
  2298. /// Gets or sets whether the control will show column headers in all
  2299. /// views (true), or only in Details view (false)
  2300. /// </summary>
  2301. /// <remarks>
  2302. /// <para>
  2303. /// This property is not working correctly. JPP 2010/04/06.
  2304. /// It works fine if it is set before the control is created.
  2305. /// But if it turned off once the control is created, the control
  2306. /// loses its checkboxes (weird!)
  2307. /// </para>
  2308. /// <para>
  2309. /// To changed this setting after the control is created, things
  2310. /// are complicated. If it is off and we want it on, we have
  2311. /// to change the View and the header will appear. If it is currently
  2312. /// on and we want to turn it off, we have to both change the view
  2313. /// AND recreate the handle. Recreating the handle is a problem
  2314. /// since it makes our checkbox style disappear.
  2315. /// </para>
  2316. /// </remarks>
  2317. [Category("ObjectListView"),
  2318. Description("Will the control will show column headers in all views?"),
  2319. DefaultValue(true)]
  2320. public bool ShowHeaderInAllViews {
  2321. get { return showHeaderInAllViews; }
  2322. set {
  2323. if (showHeaderInAllViews == value)
  2324. return;
  2325. showHeaderInAllViews = value;
  2326. // If the control isn't already created, everything is fine.
  2327. if (!this.Created)
  2328. return;
  2329. // If the header is being hidden, we have to recreate the control
  2330. // to remove the style (not sure why this is)
  2331. if (!showHeaderInAllViews)
  2332. this.RecreateHandle();
  2333. // Still more complications. The change doesn't become visible until the View is changed
  2334. if (this.View != View.Details) {
  2335. View temp = this.View;
  2336. this.View = View.Details;
  2337. this.View = temp;
  2338. }
  2339. }
  2340. }
  2341. private bool showHeaderInAllViews = true;
  2342. /// <summary>
  2343. /// Override the SmallImageList property so we can correctly shadow its operations.
  2344. /// </summary>
  2345. /// <remarks><para>If you use the RowHeight property to specify the row height, the SmallImageList
  2346. /// must be fully initialised before setting/changing the RowHeight. If you add new images to the image
  2347. /// list after setting the RowHeight, you must assign the imagelist to the control again. Something as simple
  2348. /// as this will work:
  2349. /// <code>listView1.SmallImageList = listView1.SmallImageList;</code></para>
  2350. /// </remarks>
  2351. new public ImageList SmallImageList {
  2352. get { return this.shadowedImageList; }
  2353. set {
  2354. this.shadowedImageList = value;
  2355. if (this.UseSubItemCheckBoxes)
  2356. this.SetupSubItemCheckBoxes();
  2357. this.SetupBaseImageList();
  2358. }
  2359. }
  2360. private ImageList shadowedImageList;
  2361. /// <summary>
  2362. /// Return the size of the images in the small image list or a reasonable default
  2363. /// </summary>
  2364. [Browsable(false)]
  2365. public virtual Size SmallImageSize {
  2366. get {
  2367. if (this.SmallImageList == null)
  2368. return new Size(16, 16);
  2369. else
  2370. return this.SmallImageList.ImageSize;
  2371. }
  2372. }
  2373. /// <summary>
  2374. /// When the listview is grouped, should the items be sorted by the primary column?
  2375. /// If this is false, the items will be sorted by the same column as they are grouped.
  2376. /// </summary>
  2377. [Category("ObjectListView"),
  2378. Description("When the listview is grouped, should the items be sorted by the primary column? If this is false, the items will be sorted by the same column as they are grouped."),
  2379. DefaultValue(true)]
  2380. public virtual bool SortGroupItemsByPrimaryColumn {
  2381. get { return this.sortGroupItemsByPrimaryColumn; }
  2382. set { this.sortGroupItemsByPrimaryColumn = value; }
  2383. }
  2384. private bool sortGroupItemsByPrimaryColumn = true;
  2385. /// <summary>
  2386. /// When the listview is grouped, how many pixels should exist between the end of one group and the
  2387. /// beginning of the next?
  2388. /// </summary>
  2389. [Category("ObjectListView"),
  2390. Description("How many pixels of space will be between groups"),
  2391. DefaultValue(0)]
  2392. public virtual int SpaceBetweenGroups {
  2393. get { return this.spaceBetweenGroups; }
  2394. set { this.spaceBetweenGroups = value; }
  2395. }
  2396. private int spaceBetweenGroups;
  2397. /// <summary>
  2398. /// Should the sort column show a slight tinge?
  2399. /// </summary>
  2400. [Category("ObjectListView"),
  2401. Description("Should the sort column show a slight tinting?"),
  2402. DefaultValue(false)]
  2403. public virtual bool TintSortColumn {
  2404. get { return this.tintSortColumn; }
  2405. set {
  2406. this.tintSortColumn = value;
  2407. if (value && this.LastSortColumn != null)
  2408. this.SelectedColumn = this.LastSortColumn;
  2409. else
  2410. this.SelectedColumn = null;
  2411. }
  2412. }
  2413. private bool tintSortColumn;
  2414. /// <summary>
  2415. /// Should each row have a tri-state checkbox?
  2416. /// </summary>
  2417. /// <remarks>
  2418. /// If this is true, the user can choose the third state (normally Indeterminate). Otherwise, user clicks
  2419. /// alternate between checked and unchecked. CheckStateGetter can still return Indeterminate when this
  2420. /// setting is false.
  2421. /// </remarks>
  2422. [Category("ObjectListView"),
  2423. Description("Should the primary column have a checkbox that behaves as a tri-state checkbox?"),
  2424. DefaultValue(false)]
  2425. public virtual bool TriStateCheckBoxes {
  2426. get { return triStateCheckBoxes; }
  2427. set {
  2428. triStateCheckBoxes = value;
  2429. if (value && !this.CheckBoxes)
  2430. this.CheckBoxes = true;
  2431. this.InitializeStateImageList();
  2432. }
  2433. }
  2434. private bool triStateCheckBoxes;
  2435. /// <summary>
  2436. /// Get or set the index of the top item of this listview
  2437. /// </summary>
  2438. /// <remarks>
  2439. /// <para>
  2440. /// This property only works when the listview is in Details view and not showing groups.
  2441. /// </para>
  2442. /// <para>
  2443. /// The reason that it does not work when showing groups is that, when groups are enabled,
  2444. /// the Windows msg LVM_GETTOPINDEX always returns 0, regardless of the
  2445. /// scroll position.
  2446. /// </para>
  2447. /// </remarks>
  2448. [Browsable(false),
  2449. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2450. public virtual int TopItemIndex {
  2451. get {
  2452. if (this.View == View.Details && this.TopItem != null)
  2453. return this.TopItem.Index;
  2454. else
  2455. return -1;
  2456. }
  2457. set {
  2458. int newTopIndex = Math.Min(value, this.GetItemCount() - 1);
  2459. if (this.View == View.Details && newTopIndex >= 0) {
  2460. this.TopItem = this.Items[newTopIndex];
  2461. // Setting the TopItem sometimes gives off by one errors,
  2462. // that (bizarrely) are correct on a second attempt
  2463. if (this.TopItem != null && this.TopItem.Index != newTopIndex)
  2464. this.TopItem = this.GetItem(newTopIndex);
  2465. }
  2466. }
  2467. }
  2468. /// <summary>
  2469. /// When resizing a column by dragging its divider, should any space filling columns be
  2470. /// resized at each mouse move? If this is false, the filling columns will be
  2471. /// updated when the mouse is released.
  2472. /// </summary>
  2473. /// <remarks>
  2474. /// <para>
  2475. /// In previous versions, setting this to true produced ugly behaviour, because every
  2476. /// column to the right of the divider being dragged was updated twice: once when
  2477. /// the column be resized changes size (this moves all the columns slightly to the right);
  2478. /// then again when the filling columns are updated - they are shrunk
  2479. /// so that the combined width is not more than the control, so everything jumps slightly back to the left again.
  2480. /// </para>
  2481. /// <para>
  2482. /// But, as of v2.0, the change the Windows messages in place, so there is now only one update,
  2483. /// and everything looks nice and smooth.
  2484. /// </para>
  2485. /// <para>
  2486. /// However, it still looks odd when the space filling column
  2487. /// is in the left of the column that is being resized: the right edge of the column is dragged, but
  2488. /// its <b>left</b> edge moves, since the space filling column is shrinking.
  2489. /// </para>
  2490. /// <para>Given the above behavior is probably best to turn this property off if your space filling
  2491. /// columns aren't the right-most columns.</para>
  2492. /// </remarks>
  2493. [Category("ObjectListView"),
  2494. Description("When resizing a column by dragging its divider, should any space filling columns be resized at each mouse move?"),
  2495. DefaultValue(true)]
  2496. public virtual bool UpdateSpaceFillingColumnsWhenDraggingColumnDivider {
  2497. get { return updateSpaceFillingColumnsWhenDraggingColumnDivider; }
  2498. set { updateSpaceFillingColumnsWhenDraggingColumnDivider = value; }
  2499. }
  2500. private bool updateSpaceFillingColumnsWhenDraggingColumnDivider = true;
  2501. /// <summary>
  2502. /// What color should be used for the background of selected rows when the control doesn't have the focus?
  2503. /// </summary>
  2504. /// <remarks>Windows does not give the option of changing the selection background.
  2505. /// So the control has to be owner drawn to see the result of this setting.
  2506. /// Setting UseCustomSelectionColors = true will do this for you.</remarks>
  2507. [Category("ObjectListView"),
  2508. Description("The background color of selected rows when the control is owner drawn and doesn't have the focus"),
  2509. DefaultValue(typeof(Color), "")]
  2510. public virtual Color UnfocusedHighlightBackgroundColor {
  2511. get { return unfocusedHighlightBackgroundColor; }
  2512. set { unfocusedHighlightBackgroundColor = value; }
  2513. }
  2514. private Color unfocusedHighlightBackgroundColor = Color.Empty;
  2515. /// <summary>
  2516. /// Return the color should be used for the background of selected rows when the control doesn't have the focus or a reasonable default
  2517. /// </summary>
  2518. [Browsable(false)]
  2519. public virtual Color UnfocusedHighlightBackgroundColorOrDefault {
  2520. get {
  2521. if (this.UnfocusedHighlightBackgroundColor.IsEmpty)
  2522. return SystemColors.Control;
  2523. else
  2524. return this.UnfocusedHighlightBackgroundColor;
  2525. }
  2526. }
  2527. /// <summary>
  2528. /// What color should be used for the foreground of selected rows when the control doesn't have the focus?
  2529. /// </summary>
  2530. /// <remarks>Windows does not give the option of changing the selection foreground (text color).
  2531. /// So the control has to be owner drawn to see the result of this setting.
  2532. /// Setting UseCustomSelectionColors = true will do this for you.</remarks>
  2533. [Category("ObjectListView"),
  2534. Description("The foreground color of selected rows when the control is owner drawn and doesn't have the focus"),
  2535. DefaultValue(typeof(Color), "")]
  2536. public virtual Color UnfocusedHighlightForegroundColor {
  2537. get { return unfocusedHighlightForegroundColor; }
  2538. set { unfocusedHighlightForegroundColor = value; }
  2539. }
  2540. private Color unfocusedHighlightForegroundColor = Color.Empty;
  2541. /// <summary>
  2542. /// Return the color should be used for the foreground of selected rows when the control doesn't have the focus or a reasonable default
  2543. /// </summary>
  2544. [Browsable(false)]
  2545. public virtual Color UnfocusedHighlightForegroundColorOrDefault {
  2546. get {
  2547. if (this.UnfocusedHighlightForegroundColor.IsEmpty)
  2548. return SystemColors.ControlText;
  2549. else
  2550. return this.UnfocusedHighlightForegroundColor;
  2551. }
  2552. }
  2553. /// <summary>
  2554. /// Should the list give a different background color to every second row?
  2555. /// </summary>
  2556. /// <remarks><para>The color of the alternate rows is given by AlternateRowBackColor.</para>
  2557. /// <para>There is a "feature" in .NET for listviews in non-full-row-select mode, where
  2558. /// selected rows are not drawn with their correct background color.</para></remarks>
  2559. [Category("ObjectListView"),
  2560. Description("Should the list view use a different backcolor to alternate rows?"),
  2561. DefaultValue(false)]
  2562. public virtual bool UseAlternatingBackColors {
  2563. get { return useAlternatingBackColors; }
  2564. set { useAlternatingBackColors = value; }
  2565. }
  2566. private bool useAlternatingBackColors;
  2567. /// <summary>
  2568. /// Should FormatCell events be called for each cell in the control?
  2569. /// </summary>
  2570. /// <remarks>Individual rows can decide whether to call FormatCell
  2571. /// events. This is simply the default behaviour.</remarks>
  2572. [Category("ObjectListView"),
  2573. Description("Should FormatCell events be triggered to every cell that is built?"),
  2574. DefaultValue(false)]
  2575. public bool UseCellFormatEvents {
  2576. get { return useCellFormatEvents; }
  2577. set { useCellFormatEvents = value; }
  2578. }
  2579. private bool useCellFormatEvents;
  2580. /// <summary>
  2581. /// Should the selected row be drawn with non-standard foreground and background colors?
  2582. /// </summary>
  2583. /// <remarks>
  2584. /// When this is enabled, the control becomes owner drawn.
  2585. /// </remarks>
  2586. [Category("ObjectListView"),
  2587. Description("Should the selected row be drawn with non-standard foreground and background colors?"),
  2588. DefaultValue(false)]
  2589. public bool UseCustomSelectionColors {
  2590. get { return this.useCustomSelectionColors; }
  2591. set {
  2592. this.useCustomSelectionColors = value;
  2593. if (!this.DesignMode && value)
  2594. this.OwnerDraw = true;
  2595. }
  2596. }
  2597. private bool useCustomSelectionColors;
  2598. /// <summary>
  2599. /// Gets or sets whether this ObjectListView will use the same hot item and selection
  2600. /// mechanism that Vista Explorer does.
  2601. /// </summary>
  2602. /// <remarks>This property has many imperfections:
  2603. /// <list type="bullet">
  2604. /// <item><description>This only works on Vista and later</description></item>
  2605. /// <item><description>It does nothing for owner drawn lists.
  2606. /// Owner drawn lists are (naturally) controlled by their renderers.</description></item>
  2607. /// <item><description>It does not work well with AlternateRowBackColors.</description></item>
  2608. /// <item><description>It does not play well with HotItemStyles.</description></item>
  2609. /// <item><description>It looks a little bit silly is FullRowSelect is false.</description></item>
  2610. /// </list>
  2611. /// But if you absolutely have to look like Vista, this is your property.
  2612. /// Do not complain if settings this messes up other things.
  2613. /// </remarks>
  2614. [Category("ObjectListView"),
  2615. Description("Should the list use the same hot item and selection mechanism as Vista?"),
  2616. DefaultValue(false)]
  2617. public bool UseExplorerTheme {
  2618. get { return useExplorerTheme; }
  2619. set {
  2620. useExplorerTheme = value;
  2621. if (this.Created)
  2622. NativeMethods.SetWindowTheme(this.Handle, value ? "explorer" : "", null);
  2623. }
  2624. }
  2625. private bool useExplorerTheme;
  2626. /// <summary>
  2627. /// Gets or sets whether the list should enable filtering
  2628. /// </summary>
  2629. [Category("ObjectListView"),
  2630. Description("Should the list enable filtering?"),
  2631. DefaultValue(false)]
  2632. virtual public bool UseFiltering {
  2633. get { return useFiltering; }
  2634. set {
  2635. useFiltering = value;
  2636. this.UpdateFiltering();
  2637. }
  2638. }
  2639. private bool useFiltering;
  2640. /// <summary>
  2641. /// Gets or sets whether the list should put an indicator into a column's header to show that
  2642. /// it is filtering on that column
  2643. /// </summary>
  2644. [Category("ObjectListView"),
  2645. Description("Should an image be drawn in a column's header when that column is being used for filtering?"),
  2646. DefaultValue(true)]
  2647. virtual public bool UseFilterIndicator {
  2648. get { return useFilterIndicator; }
  2649. set {
  2650. useFilterIndicator = value;
  2651. this.Invalidate();
  2652. }
  2653. }
  2654. private bool useFilterIndicator = true;
  2655. /// <summary>
  2656. /// Should the item under the cursor be formatted in a special way?
  2657. /// </summary>
  2658. [Category("ObjectListView"),
  2659. Description("Should HotTracking be used? Hot tracking applies special formatting to the row under the cursor"),
  2660. DefaultValue(false)]
  2661. public bool UseHotItem {
  2662. get { return this.useHotItem; }
  2663. set {
  2664. this.useHotItem = value;
  2665. if (this.HotItemStyle != null) {
  2666. if (value)
  2667. this.AddOverlay(this.HotItemStyle.Overlay);
  2668. else
  2669. this.RemoveOverlay(this.HotItemStyle.Overlay);
  2670. }
  2671. }
  2672. }
  2673. private bool useHotItem;
  2674. /// <summary>
  2675. /// Gets or sets whether this listview should show hyperlinks in the cells.
  2676. /// </summary>
  2677. [Category("ObjectListView"),
  2678. Description("Should hyperlinks be shown on this control?"),
  2679. DefaultValue(false)]
  2680. public bool UseHyperlinks {
  2681. get { return this.useHyperlinks; }
  2682. set {
  2683. this.useHyperlinks = value;
  2684. if (value && this.HyperlinkStyle == null)
  2685. this.HyperlinkStyle = new HyperlinkStyle();
  2686. }
  2687. }
  2688. private bool useHyperlinks;
  2689. /// <summary>
  2690. /// Should this control show overlays
  2691. /// </summary>
  2692. /// <remarks>Overlays are enabled by default and would only need to be disabled
  2693. /// if they were causing problems in your development environment.</remarks>
  2694. [Category("ObjectListView"),
  2695. Description("Should this control show overlays"),
  2696. DefaultValue(true)]
  2697. public bool UseOverlays {
  2698. get { return this.useOverlays; }
  2699. set { this.useOverlays = value; }
  2700. }
  2701. private bool useOverlays = true;
  2702. /// <summary>
  2703. /// Should this control be configured to show check boxes on subitems?
  2704. /// </summary>
  2705. /// <remarks>If this is set to True, the control will be given a SmallImageList if it
  2706. /// doesn't already have one. Also, if it is a virtual list, it will be set to owner
  2707. /// drawn, since virtual lists can't draw check boxes without being owner drawn.</remarks>
  2708. [Category("ObjectListView"),
  2709. Description("Should this control be configured to show check boxes on subitems."),
  2710. DefaultValue(false)]
  2711. public bool UseSubItemCheckBoxes {
  2712. get { return this.useSubItemCheckBoxes; }
  2713. set {
  2714. this.useSubItemCheckBoxes = value;
  2715. if (value)
  2716. this.SetupSubItemCheckBoxes();
  2717. }
  2718. }
  2719. private bool useSubItemCheckBoxes;
  2720. /// <summary>
  2721. /// Gets or sets if the ObjectListView will use a translucent selection mechanism like Vista.
  2722. /// </summary>
  2723. /// <remarks>
  2724. /// <para>
  2725. /// Unlike UseExplorerTheme, this Vista-like scheme works on XP and for both
  2726. /// owner and non-owner drawn lists.
  2727. /// </para>
  2728. /// <para>
  2729. /// This will replace any SelectedRowDecoration that has been installed.
  2730. /// </para>
  2731. /// </remarks>
  2732. [Category("ObjectListView"),
  2733. Description("Should the list use a translucent selection mechanism (like Vista)"),
  2734. DefaultValue(false)]
  2735. public bool UseTranslucentSelection {
  2736. get { return useTranslucentSelection; }
  2737. set {
  2738. useTranslucentSelection = value;
  2739. if (value) {
  2740. RowBorderDecoration rbd = new RowBorderDecoration();
  2741. rbd.BorderPen = new Pen(Color.FromArgb(154, 223, 251));
  2742. rbd.FillBrush = new SolidBrush(Color.FromArgb(48, 163, 217, 225));
  2743. rbd.BoundsPadding = new Size(0, 0);
  2744. rbd.CornerRounding = 6.0f;
  2745. this.SelectedRowDecoration = rbd;
  2746. } else
  2747. this.SelectedRowDecoration = null;
  2748. }
  2749. }
  2750. private bool useTranslucentSelection;
  2751. /// <summary>
  2752. /// Gets or sets if the ObjectListView will use a translucent hot row highlighting mechanism like Vista.
  2753. /// </summary>
  2754. /// <remarks>Setting this will replace any HotItemStyle that has been </remarks>
  2755. [Category("ObjectListView"),
  2756. Description("Should the list use a translucent hot row highlighting mechanism (like Vista)"),
  2757. DefaultValue(false)]
  2758. public bool UseTranslucentHotItem {
  2759. get { return useTranslucentHotItem; }
  2760. set {
  2761. useTranslucentHotItem = value;
  2762. if (value) {
  2763. this.HotItemStyle = new HotItemStyle();
  2764. RowBorderDecoration rbd = new RowBorderDecoration();
  2765. rbd.BorderPen = new Pen(Color.FromArgb(154, 223, 251));
  2766. rbd.BoundsPadding = new Size(0, 0);
  2767. rbd.CornerRounding = 6.0f;
  2768. rbd.FillGradientFrom = Color.FromArgb(0, 255, 255, 255);
  2769. rbd.FillGradientTo = Color.FromArgb(64, 183, 237, 240);
  2770. this.HotItemStyle.Decoration = rbd;
  2771. } else
  2772. this.HotItemStyle = null;
  2773. this.UseHotItem = value;
  2774. }
  2775. }
  2776. private bool useTranslucentHotItem;
  2777. /// <summary>
  2778. /// Get/set the style of view that this listview is using
  2779. /// </summary>
  2780. /// <remarks>Switching to tile or details view installs the columns appropriate to that view.
  2781. /// Confusingly, in tile view, every column is shown as a row of information.</remarks>
  2782. new public View View {
  2783. get { return base.View; }
  2784. set {
  2785. if (base.View == value)
  2786. return;
  2787. if (this.Frozen) {
  2788. base.View = value;
  2789. this.SetupBaseImageList();
  2790. } else {
  2791. this.Freeze();
  2792. if (value == View.Tile)
  2793. this.CalculateReasonableTileSize();
  2794. base.View = value;
  2795. this.SetupBaseImageList();
  2796. this.Unfreeze();
  2797. }
  2798. }
  2799. }
  2800. #endregion
  2801. #region Callbacks
  2802. /// <summary>
  2803. /// This delegate fetches the checkedness of an object as a boolean only.
  2804. /// </summary>
  2805. /// <remarks>Use this if you never want to worry about the
  2806. /// Indeterminate state (which is fairly common).
  2807. /// <para>
  2808. /// This is a convenience wrapper around the CheckStateGetter property.
  2809. /// </para>
  2810. /// </remarks>
  2811. [Browsable(false),
  2812. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2813. public virtual BooleanCheckStateGetterDelegate BooleanCheckStateGetter {
  2814. set {
  2815. if (value == null)
  2816. this.CheckStateGetter = null;
  2817. else
  2818. this.CheckStateGetter = delegate(Object x) {
  2819. return value(x) ? CheckState.Checked : CheckState.Unchecked;
  2820. };
  2821. }
  2822. }
  2823. /// <summary>
  2824. /// This delegate sets the checkedness of an object as a boolean only. It must return
  2825. /// true or false indicating if the object was checked or not.
  2826. /// </summary>
  2827. /// <remarks>Use this if you never want to worry about the
  2828. /// Indeterminate state (which is fairly common).
  2829. /// <para>
  2830. /// This is a convenience wrapper around the CheckStatePutter property.
  2831. /// </para>
  2832. /// </remarks>
  2833. [Browsable(false),
  2834. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2835. public virtual BooleanCheckStatePutterDelegate BooleanCheckStatePutter {
  2836. set {
  2837. if (value == null)
  2838. this.CheckStatePutter = null;
  2839. else
  2840. this.CheckStatePutter = delegate(Object x, CheckState state) {
  2841. bool isChecked = (state == CheckState.Checked);
  2842. return value(x, isChecked) ? CheckState.Checked : CheckState.Unchecked;
  2843. };
  2844. }
  2845. }
  2846. /// <summary>
  2847. /// Gets whether or not this listview is capabale of showing groups
  2848. /// </summary>
  2849. [Browsable(false)]
  2850. public virtual bool CanShowGroups {
  2851. get {
  2852. return true;
  2853. }
  2854. }
  2855. /// <summary>
  2856. /// This delegate is called when the list wants to show a tooltip for a particular cell.
  2857. /// The delegate should return the text to display, or null to use the default behavior
  2858. /// (which is to show the full text of truncated cell values).
  2859. /// </summary>
  2860. /// <remarks>
  2861. /// Displaying the full text of truncated cell values only work for FullRowSelect listviews.
  2862. /// This is MS's behavior, not mine. Don't complain to me :)
  2863. /// </remarks>
  2864. [Browsable(false),
  2865. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2866. public virtual CellToolTipGetterDelegate CellToolTipGetter {
  2867. get { return cellToolTipGetter; }
  2868. set { cellToolTipGetter = value; }
  2869. }
  2870. private CellToolTipGetterDelegate cellToolTipGetter;
  2871. /// <summary>
  2872. /// The name of the property (or field) that holds whether or not a model is checked.
  2873. /// </summary>
  2874. /// <remarks>
  2875. /// <para>The property be modifiable. It must have a return type of bool (or of bool? if
  2876. /// TriStateCheckBoxes is true).</para>
  2877. /// <para>Setting this property replaces any CheckStateGetter or CheckStatePutter that have been installed.
  2878. /// Conversely, later setting the CheckStateGetter or CheckStatePutter properties will take precedence
  2879. /// over the behavior of this property.</para>
  2880. /// </remarks>
  2881. [Category("ObjectListView"),
  2882. Description("The name of the property or field that holds the 'checkedness' of the model"),
  2883. DefaultValue(null)]
  2884. public virtual string CheckedAspectName {
  2885. get { return checkedAspectName; }
  2886. set {
  2887. checkedAspectName = value;
  2888. if (String.IsNullOrEmpty(checkedAspectName)) {
  2889. this.checkedAspectMunger = null;
  2890. this.CheckStateGetter = null;
  2891. this.CheckStatePutter = null;
  2892. } else {
  2893. this.checkedAspectMunger = new Munger(checkedAspectName);
  2894. this.CheckStateGetter = delegate(Object modelObject) {
  2895. bool? result = this.checkedAspectMunger.GetValue(modelObject) as bool?;
  2896. if (result.HasValue)
  2897. if (result.Value)
  2898. return CheckState.Checked;
  2899. else
  2900. return CheckState.Unchecked;
  2901. else
  2902. if (this.TriStateCheckBoxes)
  2903. return CheckState.Indeterminate;
  2904. else
  2905. return CheckState.Unchecked;
  2906. };
  2907. this.CheckStatePutter = delegate(Object modelObject, CheckState newValue) {
  2908. if (this.TriStateCheckBoxes && newValue == CheckState.Indeterminate)
  2909. this.checkedAspectMunger.PutValue(modelObject, null);
  2910. else
  2911. this.checkedAspectMunger.PutValue(modelObject, newValue == CheckState.Checked);
  2912. return this.CheckStateGetter(modelObject);
  2913. };
  2914. }
  2915. }
  2916. }
  2917. private string checkedAspectName;
  2918. private Munger checkedAspectMunger;
  2919. /// <summary>
  2920. /// This delegate will be called whenever the ObjectListView needs to know the check state
  2921. /// of the row associated with a given model object.
  2922. /// </summary>
  2923. /// <remarks>
  2924. /// <para>.NET has no support for indeterminate values, but as of v2.0, this class allows
  2925. /// indeterminate values.</para>
  2926. /// </remarks>
  2927. [Browsable(false),
  2928. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2929. public virtual CheckStateGetterDelegate CheckStateGetter {
  2930. get { return checkStateGetter; }
  2931. set { checkStateGetter = value; }
  2932. }
  2933. private CheckStateGetterDelegate checkStateGetter;
  2934. /// <summary>
  2935. /// This delegate will be called whenever the user tries to change the check state of a row.
  2936. /// The delegate should return the state that was actually set, which may be different
  2937. /// to the state given.
  2938. /// </summary>
  2939. [Browsable(false),
  2940. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2941. public virtual CheckStatePutterDelegate CheckStatePutter {
  2942. get { return checkStatePutter; }
  2943. set { checkStatePutter = value; }
  2944. }
  2945. private CheckStatePutterDelegate checkStatePutter;
  2946. /// <summary>
  2947. /// This delegate can be used to sort the table in a custom fasion.
  2948. /// </summary>
  2949. /// <remarks>
  2950. /// <para>
  2951. /// The delegate must install a ListViewItemSorter on the ObjectListView.
  2952. /// Installing the ItemSorter does the actual work of sorting the ListViewItems.
  2953. /// See ColumnComparer in the code for an example of what an ItemSorter has to do.
  2954. /// </para>
  2955. /// <para>
  2956. /// Do not install a CustomSorter on a VirtualObjectListView. Override the SortObjects()
  2957. /// method of the IVirtualListDataSource instead.
  2958. /// </para>
  2959. /// </remarks>
  2960. [Browsable(false),
  2961. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2962. public virtual SortDelegate CustomSorter {
  2963. get { return customSorter; }
  2964. set { customSorter = value; }
  2965. }
  2966. private SortDelegate customSorter;
  2967. /// <summary>
  2968. /// This delegate is called when the list wants to show a tooltip for a particular header.
  2969. /// The delegate should return the text to display, or null to use the default behavior
  2970. /// (which is to not show any tooltip).
  2971. /// </summary>
  2972. /// <remarks>
  2973. /// Installing a HeaderToolTipGetter takes precedence over any text in OLVColumn.ToolTipText.
  2974. /// </remarks>
  2975. [Browsable(false),
  2976. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  2977. public virtual HeaderToolTipGetterDelegate HeaderToolTipGetter {
  2978. get { return headerToolTipGetter; }
  2979. set { headerToolTipGetter = value; }
  2980. }
  2981. private HeaderToolTipGetterDelegate headerToolTipGetter;
  2982. /// <summary>
  2983. /// This delegate can be used to format a OLVListItem before it is added to the control.
  2984. /// </summary>
  2985. /// <remarks>
  2986. /// <para>The model object for the row can be found through the RowObject property of the OLVListItem object.</para>
  2987. /// <para>All subitems normally have the same style as list item, so setting the forecolor on one
  2988. /// subitem changes the forecolor of all subitems.
  2989. /// To allow subitems to have different attributes, do this:
  2990. /// <code>myListViewItem.UseItemStyleForSubItems = false;</code>.
  2991. /// </para>
  2992. /// <para>If UseAlternatingBackColors is true, the backcolor of the listitem will be calculated
  2993. /// by the control and cannot be controlled by the RowFormatter delegate.
  2994. /// In general, trying to use a RowFormatter
  2995. /// when UseAlternatingBackColors is true does not work well.</para>
  2996. /// <para>As it says in the summary, this is called <b>before</b> the item is added to the control.
  2997. /// Many properties of the OLVListItem itself are not available at that point, including:
  2998. /// Index, Selected, Focused, Bounds, Checked, DisplayIndex.</para>
  2999. /// </remarks>
  3000. [Browsable(false),
  3001. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  3002. public virtual RowFormatterDelegate RowFormatter {
  3003. get { return rowFormatter; }
  3004. set { rowFormatter = value; }
  3005. }
  3006. private RowFormatterDelegate rowFormatter;
  3007. #endregion
  3008. #region List commands
  3009. /// <summary>
  3010. /// Add the given model object to this control.
  3011. /// </summary>
  3012. /// <param name="modelObject">The model object to be displayed</param>
  3013. /// <remarks>See AddObjects() for more details</remarks>
  3014. public virtual void AddObject(object modelObject) {
  3015. if (this.InvokeRequired)
  3016. this.Invoke((MethodInvoker)delegate() { this.AddObject(modelObject); });
  3017. else
  3018. this.AddObjects(new object[] { modelObject });
  3019. }
  3020. /// <summary>
  3021. /// Add the given collection of model objects to this control.
  3022. /// </summary>
  3023. /// <param name="modelObjects">A collection of model objects</param>
  3024. /// <remarks>
  3025. /// <para>The added objects will appear in their correct sort position, if sorting
  3026. /// is active (i.e. if LastSortColumn is not null). Otherwise, they will appear at the end of the list.</para>
  3027. /// <para>No check is performed to see if any of the objects are already in the ListView.</para>
  3028. /// <para>Null objects are silently ignored.</para>
  3029. /// </remarks>
  3030. public virtual void AddObjects(ICollection modelObjects) {
  3031. if (this.InvokeRequired) {
  3032. this.Invoke((MethodInvoker)delegate() { this.AddObjects(modelObjects); });
  3033. return;
  3034. }
  3035. this.InsertObjects(this.GetItemCount(), modelObjects);
  3036. this.Sort(this.LastSortColumn, this.LastSortOrder);
  3037. }
  3038. /// <summary>
  3039. /// Resize the columns to the maximum of the header width and the data.
  3040. /// </summary>
  3041. public void AutoResizeColumns()
  3042. {
  3043. foreach (OLVColumn c in this.Columns)
  3044. {
  3045. c.Width = -2;
  3046. }
  3047. }
  3048. /// <summary>
  3049. /// Organise the view items into groups, based on the last sort column or the first column
  3050. /// if there is no last sort column
  3051. /// </summary>
  3052. public virtual void BuildGroups() {
  3053. this.BuildGroups(this.LastSortColumn, this.LastSortOrder == SortOrder.None ? SortOrder.Ascending : this.LastSortOrder);
  3054. }
  3055. /// <summary>
  3056. /// Organise the view items into groups, based on the given column
  3057. /// </summary>
  3058. /// <remarks>
  3059. /// <para>
  3060. /// If the AlwaysGroupByColumn property is not null,
  3061. /// the list view items will be organisd by that column,
  3062. /// and the 'column' parameter will be ignored.
  3063. /// </para>
  3064. /// <para>This method triggers sorting events: BeforeSorting and AfterSorting.</para>
  3065. /// </remarks>
  3066. /// <param name="column">The column whose values should be used for sorting.</param>
  3067. /// <param name="order"></param>
  3068. public virtual void BuildGroups(OLVColumn column, SortOrder order) {
  3069. BeforeSortingEventArgs args = this.BuildBeforeSortingEventArgs(column, order);
  3070. this.OnBeforeSorting(args);
  3071. if (args.Canceled)
  3072. return;
  3073. this.BuildGroups(args.ColumnToGroupBy, args.GroupByOrder,
  3074. args.ColumnToSort, args.SortOrder, args.SecondaryColumnToSort, args.SecondarySortOrder);
  3075. this.OnAfterSorting(new AfterSortingEventArgs(args));
  3076. }
  3077. private BeforeSortingEventArgs BuildBeforeSortingEventArgs(OLVColumn column, SortOrder order) {
  3078. OLVColumn groupBy = this.AlwaysGroupByColumn ?? column ?? this.GetColumn(0);
  3079. SortOrder groupByOrder = this.AlwaysGroupBySortOrder;
  3080. if (order == SortOrder.None) {
  3081. order = this.Sorting;
  3082. if (order == SortOrder.None)
  3083. order = SortOrder.Ascending;
  3084. }
  3085. if (groupByOrder == SortOrder.None)
  3086. groupByOrder = order;
  3087. BeforeSortingEventArgs args = new BeforeSortingEventArgs(
  3088. groupBy, groupByOrder,
  3089. column, order,
  3090. this.SecondarySortColumn ?? this.GetColumn(0),
  3091. this.SecondarySortOrder == SortOrder.None ? order : this.SecondarySortOrder);
  3092. if (column != null)
  3093. args.Canceled = !column.Sortable;
  3094. return args;
  3095. }
  3096. /// <summary>
  3097. /// Organise the view items into groups, based on the given columns
  3098. /// </summary>
  3099. /// <param name="groupByColumn">What column will be used for grouping</param>
  3100. /// <param name="groupByOrder">What ordering will be used for groups</param>
  3101. /// <param name="column">The column whose values should be used for sorting. Cannot be null</param>
  3102. /// <param name="order">The order in which the values from column will be sorted</param>
  3103. /// <param name="secondaryColumn">When the values from 'column' are equal, use the values provided by this column</param>
  3104. /// <param name="secondaryOrder">How will the secondary values be sorted</param>
  3105. /// <remarks>This method does not trigger sorting events. Use BuildGroups() to do that</remarks>
  3106. public virtual void BuildGroups(OLVColumn groupByColumn, SortOrder groupByOrder,
  3107. OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder) {
  3108. // Sanity checks
  3109. if (groupByColumn == null)
  3110. return;
  3111. // Getting the Count forces any internal cache of the ListView to be flushed. Without
  3112. // this, iterating over the Items will not work correctly if the ListView handle
  3113. // has not yet been created.
  3114. int dummy = this.Items.Count;
  3115. // Collect all the information that governs the creation of groups
  3116. GroupingParameters parms = this.CollectGroupingParameters(groupByColumn, groupByOrder,
  3117. column, order, secondaryColumn, secondaryOrder);
  3118. // Trigger an event to let the world create groups if they want
  3119. CreateGroupsEventArgs args = new CreateGroupsEventArgs(parms);
  3120. if (parms.GroupByColumn != null)
  3121. args.Canceled = !parms.GroupByColumn.Groupable;
  3122. this.OnBeforeCreatingGroups(args);
  3123. if (args.Canceled)
  3124. return;
  3125. // If the event didn't create them for us, use our default strategy
  3126. if (args.Groups == null)
  3127. args.Groups = this.MakeGroups(parms);
  3128. // Give the world a chance to munge the groups before they are created
  3129. this.OnAboutToCreateGroups(args);
  3130. if (args.Canceled)
  3131. return;
  3132. // Create the groups now
  3133. this.OLVGroups = args.Groups;
  3134. this.CreateGroups(args.Groups);
  3135. // Tell the world that new groups have been created
  3136. this.OnAfterCreatingGroups(args);
  3137. }
  3138. /// <summary>
  3139. /// Collect and return all the variables that influence the creation of groups
  3140. /// </summary>
  3141. /// <returns></returns>
  3142. protected virtual GroupingParameters CollectGroupingParameters(OLVColumn groupByColumn, SortOrder groupByOrder,
  3143. OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder) {
  3144. string titleFormat = this.ShowItemCountOnGroups ? groupByColumn.GroupWithItemCountFormatOrDefault : null;
  3145. string titleSingularFormat = this.ShowItemCountOnGroups ? groupByColumn.GroupWithItemCountSingularFormatOrDefault : null;
  3146. GroupingParameters parms = new GroupingParameters(this, groupByColumn, groupByOrder,
  3147. column, order, secondaryColumn, secondaryOrder,
  3148. titleFormat, titleSingularFormat, this.SortGroupItemsByPrimaryColumn);
  3149. return parms;
  3150. }
  3151. /// <summary>
  3152. /// Make a list of groups that should be shown according to the given parameters
  3153. /// </summary>
  3154. /// <param name="parms"></param>
  3155. /// <returns>The list of groups to be created</returns>
  3156. /// <remarks>This should not change the state of the control. It is possible that the
  3157. /// groups created will not be used. They may simply be discarded.</remarks>
  3158. protected virtual IList<OLVGroup> MakeGroups(GroupingParameters parms) {
  3159. // There is a lot of overlap between this method and FastListGroupingStrategy.MakeGroups()
  3160. // Any changes made here may need to be reflected there
  3161. // Separate the list view items into groups, using the group key as the descrimanent
  3162. NullableDictionary<object, List<OLVListItem>> map = new NullableDictionary<object, List<OLVListItem>>();
  3163. foreach (OLVListItem olvi in parms.ListView.Items) {
  3164. object key = parms.GroupByColumn.GetGroupKey(olvi.RowObject);
  3165. if (!map.ContainsKey(key))
  3166. map[key] = new List<OLVListItem>();
  3167. map[key].Add(olvi);
  3168. }
  3169. // Sort the items within each group (unless specifically turned off)
  3170. OLVColumn primarySortColumn = parms.SortItemsByPrimaryColumn ? parms.ListView.GetColumn(0) : parms.PrimarySort;
  3171. if (primarySortColumn != null && parms.PrimarySortOrder != SortOrder.None) {
  3172. ColumnComparer itemSorter = new ColumnComparer(primarySortColumn, parms.PrimarySortOrder,
  3173. parms.SecondarySort, parms.SecondarySortOrder);
  3174. foreach (object key in map.Keys) {
  3175. map[key].Sort(parms.ItemComparer ?? itemSorter);
  3176. }
  3177. }
  3178. // Make a list of the required groups
  3179. List<OLVGroup> groups = new List<OLVGroup>();
  3180. foreach (object key in map.Keys) {
  3181. string title = parms.GroupByColumn.ConvertGroupKeyToTitle(key);
  3182. if (!String.IsNullOrEmpty(parms.TitleFormat)) {
  3183. int count = map[key].Count;
  3184. string format = (count == 1 ? parms.TitleSingularFormat : parms.TitleFormat);
  3185. try {
  3186. title = String.Format(format, title, count);
  3187. } catch (FormatException) {
  3188. title = "Invalid group format: " + format;
  3189. }
  3190. }
  3191. OLVGroup lvg = new OLVGroup(title);
  3192. lvg.Collapsible = this.HasCollapsibleGroups;
  3193. lvg.Key = key;
  3194. lvg.SortValue = key as IComparable;
  3195. lvg.Items = map[key];
  3196. if (parms.GroupByColumn.GroupFormatter != null)
  3197. parms.GroupByColumn.GroupFormatter(lvg, parms);
  3198. groups.Add(lvg);
  3199. }
  3200. // Sort the groups
  3201. if (parms.GroupByOrder != SortOrder.None)
  3202. groups.Sort(parms.GroupComparer ?? new OLVGroupComparer(parms.GroupByOrder));
  3203. return groups;
  3204. }
  3205. /// <summary>
  3206. /// Build/rebuild all the list view items in the list
  3207. /// </summary>
  3208. public virtual void BuildList() {
  3209. if (this.InvokeRequired)
  3210. this.Invoke(new MethodInvoker(this.BuildList));
  3211. else
  3212. this.BuildList(true);
  3213. }
  3214. /// <summary>
  3215. /// Build/rebuild all the list view items in the list
  3216. /// </summary>
  3217. /// <param name="shouldPreserveState">If this is true, the control will try to preserve the selection,
  3218. /// focused item, and the scroll position (see Remarks)
  3219. /// </param>
  3220. /// <remarks>
  3221. /// <para>
  3222. /// Use this method in situations were the contents of the list is basically the same
  3223. /// as previously.
  3224. /// </para>
  3225. /// <para>
  3226. /// Due to limitations in .NET's ListView, the scroll position is only preserved if
  3227. /// the control is in Details view AND it is not showing groups.
  3228. /// </para>
  3229. /// </remarks>
  3230. public virtual void BuildList(bool shouldPreserveState) {
  3231. if (this.Frozen)
  3232. return;
  3233. this.ApplyExtendedStyles();
  3234. this.ClearHotItem();
  3235. int previousTopIndex = this.TopItemIndex;
  3236. Point currentScrollPosition = this.LowLevelScrollPosition;
  3237. IList previousSelection = new ArrayList();
  3238. Object previousFocus = null;
  3239. if (shouldPreserveState && this.objects != null) {
  3240. previousSelection = this.SelectedObjects;
  3241. OLVListItem focusedItem = this.FocusedItem as OLVListItem;
  3242. if (focusedItem != null)
  3243. previousFocus = focusedItem.RowObject;
  3244. }
  3245. IEnumerable objectsToDisplay = this.FilteredObjects;
  3246. this.BeginUpdate();
  3247. try {
  3248. this.Items.Clear();
  3249. this.ListViewItemSorter = null;
  3250. if (objectsToDisplay != null) {
  3251. // Build a list of all our items and then display them. (Building
  3252. // a list and then doing one AddRange is about 10-15% faster than individual adds)
  3253. List<OLVListItem> itemList = new List<OLVListItem>();
  3254. foreach (object rowObject in objectsToDisplay) {
  3255. OLVListItem lvi = new OLVListItem(rowObject);
  3256. this.FillInValues(lvi, rowObject);
  3257. itemList.Add(lvi);
  3258. }
  3259. this.Items.AddRange(itemList.ToArray());
  3260. this.Sort();
  3261. if (shouldPreserveState) {
  3262. this.SelectedObjects = previousSelection;
  3263. this.FocusedItem = this.ModelToItem(previousFocus);
  3264. }
  3265. this.RefreshHotItem();
  3266. }
  3267. } finally {
  3268. this.EndUpdate();
  3269. }
  3270. // We can only restore the scroll position after the EndUpdate() because
  3271. // of caching that the ListView does internally during a BeginUpdate/EndUpdate pair.
  3272. if (shouldPreserveState) {
  3273. this.RefreshHotItem();
  3274. // Restore the scroll position. TopItemIndex is best, but doesn't work
  3275. // when the control is grouped.
  3276. if (this.ShowGroups)
  3277. this.LowLevelScroll(currentScrollPosition.X, currentScrollPosition.Y);
  3278. else
  3279. this.TopItemIndex = previousTopIndex;
  3280. }
  3281. }
  3282. /// <summary>
  3283. /// Apply all required extended styles to our control.
  3284. /// </summary>
  3285. /// <remarks>
  3286. /// <para>
  3287. /// Whenever .NET code sets an extended style, it erases all other extended styles
  3288. /// that it doesn't use. So, we have to explicit reapply the styles that we have
  3289. /// added.
  3290. /// </para>
  3291. /// <para>
  3292. /// Normally, we would override CreateParms property and update
  3293. /// the ExStyle member, but ListView seems to ignore all ExStyles that
  3294. /// it doesn't already know about. Worse, when we set the LVS_EX_HEADERINALLVIEWS
  3295. /// value, bad things happen (the control crashes!).
  3296. /// </para>
  3297. /// </remarks>
  3298. protected virtual void ApplyExtendedStyles() {
  3299. const int LVS_EX_SUBITEMIMAGES = 0x00000002;
  3300. //const int LVS_EX_TRANSPARENTBKGND = 0x00400000;
  3301. const int LVS_EX_HEADERINALLVIEWS = 0x02000000;
  3302. const int styleMask = LVS_EX_SUBITEMIMAGES | LVS_EX_HEADERINALLVIEWS;
  3303. int style = 0;
  3304. if (this.ShowImagesOnSubItems && !this.VirtualMode)
  3305. style ^= LVS_EX_SUBITEMIMAGES;
  3306. if (this.ShowHeaderInAllViews)
  3307. style ^= LVS_EX_HEADERINALLVIEWS;
  3308. NativeMethods.SetExtendedStyle(this, style, styleMask);
  3309. }
  3310. /// <summary>
  3311. /// Give the listview a reasonable size of its tiles, based on the number of lines of
  3312. /// information that each tile is going to display.
  3313. /// </summary>
  3314. public virtual void CalculateReasonableTileSize() {
  3315. if (this.Columns.Count <= 0)
  3316. return;
  3317. List<OLVColumn> columns = this.AllColumns.FindAll(delegate(OLVColumn x) {
  3318. return (x.Index == 0) || x.IsTileViewColumn;
  3319. });
  3320. int imageHeight = (this.LargeImageList == null ? 16 : this.LargeImageList.ImageSize.Height);
  3321. int dataHeight = (this.Font.Height + 1) * columns.Count;
  3322. int tileWidth = (this.TileSize.Width == 0 ? 200 : this.TileSize.Width);
  3323. int tileHeight = Math.Max(this.TileSize.Height, Math.Max(imageHeight, dataHeight));
  3324. this.TileSize = new Size(tileWidth, tileHeight);
  3325. }
  3326. /// <summary>
  3327. /// Rebuild this list for the given view
  3328. /// </summary>
  3329. /// <param name="view"></param>
  3330. public virtual void ChangeToFilteredColumns(View view) {
  3331. // Store the state
  3332. IList previousSelection = this.SelectedObjects;
  3333. int previousTopIndex = this.TopItemIndex;
  3334. this.Freeze();
  3335. this.Clear();
  3336. List<OLVColumn> columns = this.GetFilteredColumns(view);
  3337. if (view == View.Details) {
  3338. // Make sure all columns have a reasonable LastDisplayIndex
  3339. for (int index = 0; index < columns.Count; index++)
  3340. {
  3341. if (columns[index].LastDisplayIndex == -1)
  3342. columns[index].LastDisplayIndex = index;
  3343. }
  3344. // ListView will ignore DisplayIndex FOR ALL COLUMNS if there are any errors,
  3345. // e.g. duplicates (two columns with the same DisplayIndex) or gaps.
  3346. // LastDisplayIndex isn't guaranteed to be unique, so we just sort the columns by
  3347. // the last position they were displayed and use that to generate a sequence
  3348. // we can use for the DisplayIndex values.
  3349. List<OLVColumn> columnsInDisplayOrder = new List<OLVColumn>(columns);
  3350. columnsInDisplayOrder.Sort(delegate(OLVColumn x, OLVColumn y) { return (x.LastDisplayIndex - y.LastDisplayIndex); });
  3351. int i = 0;
  3352. foreach (OLVColumn col in columnsInDisplayOrder)
  3353. col.DisplayIndex = i++;
  3354. }
  3355. this.Columns.AddRange(columns.ToArray());
  3356. if (view == View.Details)
  3357. this.ShowSortIndicator();
  3358. this.BuildList();
  3359. this.Unfreeze();
  3360. // Restore the state
  3361. this.SelectedObjects = previousSelection;
  3362. this.TopItemIndex = previousTopIndex;
  3363. }
  3364. /// <summary>
  3365. /// Remove all items from this list
  3366. /// </summary>
  3367. /// <remark>This method can safely be called from background threads.</remark>
  3368. public virtual void ClearObjects() {
  3369. if (this.InvokeRequired)
  3370. this.Invoke(new MethodInvoker(this.ClearObjects));
  3371. else
  3372. this.SetObjects(null);
  3373. }
  3374. /// <summary>
  3375. /// Reset the memory of which URLs have been visited
  3376. /// </summary>
  3377. public virtual void ClearUrlVisited() {
  3378. this.visitedUrlMap = new Dictionary<string, bool>();
  3379. }
  3380. /// <summary>
  3381. /// Copy a text and html representation of the selected rows onto the clipboard.
  3382. /// </summary>
  3383. /// <remarks>Be careful when using this with virtual lists. If the user has selected
  3384. /// 10,000,000 rows, this method will faithfully try to copy all of them to the clipboard.
  3385. /// From the user's point of view, your program will appear to have hung.</remarks>
  3386. public virtual void CopySelectionToClipboard() {
  3387. IList selection = this.SelectedObjects;
  3388. if (selection.Count == 0)
  3389. return;
  3390. // Use the DragSource object to create the data object, if so configured.
  3391. // This relies on the assumption that DragSource will handle the selected objects only.
  3392. // It is legal for StartDrag to return null.
  3393. object data = null;
  3394. if (this.CopySelectionOnControlCUsesDragSource && this.DragSource != null)
  3395. data = this.DragSource.StartDrag(this, MouseButtons.Left, this.ModelToItem(selection[0]));
  3396. Clipboard.SetDataObject(data ?? new OLVDataObject(this, selection));
  3397. }
  3398. /// <summary>
  3399. /// Copy a text and html representation of the given objects onto the clipboard.
  3400. /// </summary>
  3401. public virtual void CopyObjectsToClipboard(IList objectsToCopy) {
  3402. if (objectsToCopy.Count == 0)
  3403. return;
  3404. // We don't know where these objects came from, so we can't use the DragSource to create
  3405. // the data object, like we do with CopySelectionToClipboard() above.
  3406. OLVDataObject dataObject = new OLVDataObject(this, objectsToCopy);
  3407. dataObject.CreateTextFormats();
  3408. Clipboard.SetDataObject(dataObject);
  3409. }
  3410. /// <summary>
  3411. /// Return a html representation of the given objects
  3412. /// </summary>
  3413. public virtual string ObjectsToHtml(IList objectsToConvert) {
  3414. if (objectsToConvert.Count == 0)
  3415. return String.Empty;
  3416. OLVDataObject dataObject = new OLVDataObject(this, objectsToConvert);
  3417. return dataObject.CreateHtml();
  3418. }
  3419. /// <summary>
  3420. /// Deselect all rows in the listview
  3421. /// </summary>
  3422. public virtual void DeselectAll() {
  3423. NativeMethods.DeselectAllItems(this);
  3424. }
  3425. /// <summary>
  3426. /// Setup the list so it will draw selected rows using custom colours.
  3427. /// </summary>
  3428. /// <remarks>
  3429. /// This method makes the list owner drawn, and ensures that all columns have at
  3430. /// least a BaseRender installed.
  3431. /// </remarks>
  3432. public virtual void EnableCustomSelectionColors() {
  3433. this.UseCustomSelectionColors = true;
  3434. }
  3435. /// <summary>
  3436. /// Return the ListViewItem that appears immediately after the given item.
  3437. /// If the given item is null, the first item in the list will be returned.
  3438. /// Return null if the given item is the last item.
  3439. /// </summary>
  3440. /// <param name="itemToFind">The item that is before the item that is returned, or null</param>
  3441. /// <returns>A ListViewItem</returns>
  3442. public virtual OLVListItem GetNextItem(OLVListItem itemToFind) {
  3443. if (this.ShowGroups) {
  3444. bool isFound = (itemToFind == null);
  3445. foreach (ListViewGroup group in this.Groups) {
  3446. foreach (OLVListItem olvi in group.Items) {
  3447. if (isFound)
  3448. return olvi;
  3449. isFound = (itemToFind == olvi);
  3450. }
  3451. }
  3452. return null;
  3453. } else {
  3454. if (this.GetItemCount() == 0)
  3455. return null;
  3456. if (itemToFind == null)
  3457. return this.GetItem(0);
  3458. if (itemToFind.Index == this.GetItemCount() - 1)
  3459. return null;
  3460. return this.GetItem(itemToFind.Index + 1);
  3461. }
  3462. }
  3463. /// <summary>
  3464. /// Return the last item in the order they are shown to the user.
  3465. /// If the control is not grouped, the display order is the same as the
  3466. /// sorted list order. But if the list is grouped, the display order is different.
  3467. /// </summary>
  3468. /// <returns></returns>
  3469. public virtual OLVListItem GetLastItemInDisplayOrder() {
  3470. if (!this.ShowGroups)
  3471. return this.GetItem(this.GetItemCount() - 1);
  3472. if (this.Groups.Count > 0) {
  3473. ListViewGroup lastGroup = this.Groups[this.Groups.Count - 1];
  3474. if (lastGroup.Items.Count > 0)
  3475. return (OLVListItem)lastGroup.Items[lastGroup.Items.Count - 1];
  3476. }
  3477. return null;
  3478. }
  3479. /// <summary>
  3480. /// Return the n'th item (0-based) in the order they are shown to the user.
  3481. /// If the control is not grouped, the display order is the same as the
  3482. /// sorted list order. But if the list is grouped, the display order is different.
  3483. /// </summary>
  3484. /// <param name="n"></param>
  3485. /// <returns></returns>
  3486. public virtual OLVListItem GetNthItemInDisplayOrder(int n) {
  3487. if (!this.ShowGroups)
  3488. return this.GetItem(n);
  3489. foreach (ListViewGroup group in this.Groups) {
  3490. if (n < group.Items.Count)
  3491. return (OLVListItem)group.Items[n];
  3492. n -= group.Items.Count;
  3493. }
  3494. return null;
  3495. }
  3496. /// <summary>
  3497. /// Return the index of the given ListViewItem as it currently shown to the user.
  3498. /// If the control is not grouped, the display order is the same as the
  3499. /// sorted list order. But if the list is grouped, the display order is different.
  3500. /// </summary>
  3501. /// <param name="value"></param>
  3502. /// <returns></returns>
  3503. public virtual int GetItemIndexInDisplayOrder(ListViewItem value) {
  3504. if (!this.ShowGroups)
  3505. return value.Index;
  3506. // TODO: This could be optimized
  3507. int i = 0;
  3508. foreach (ListViewGroup lvg in this.Groups) {
  3509. foreach (ListViewItem lvi in lvg.Items) {
  3510. if (lvi == value)
  3511. return i;
  3512. i++;
  3513. }
  3514. }
  3515. return -1;
  3516. }
  3517. /// <summary>
  3518. /// Return the ListViewItem that appears immediately before the given item.
  3519. /// If the given item is null, the last item in the list will be returned.
  3520. /// Return null if the given item is the first item.
  3521. /// </summary>
  3522. /// <param name="itemToFind">The item that is before the item that is returned</param>
  3523. /// <returns>A ListViewItem</returns>
  3524. public virtual OLVListItem GetPreviousItem(OLVListItem itemToFind) {
  3525. if (this.ShowGroups) {
  3526. OLVListItem previousItem = null;
  3527. foreach (ListViewGroup group in this.Groups) {
  3528. foreach (OLVListItem lvi in group.Items) {
  3529. if (lvi == itemToFind)
  3530. return previousItem;
  3531. else
  3532. previousItem = lvi;
  3533. }
  3534. }
  3535. if (itemToFind == null)
  3536. return previousItem;
  3537. else
  3538. return null;
  3539. } else {
  3540. if (this.GetItemCount() == 0)
  3541. return null;
  3542. if (itemToFind == null)
  3543. return this.GetItem(this.GetItemCount() - 1);
  3544. if (itemToFind.Index == 0)
  3545. return null;
  3546. return this.GetItem(itemToFind.Index - 1);
  3547. }
  3548. }
  3549. /// <summary>
  3550. /// Insert the given collection of objects before the given position
  3551. /// </summary>
  3552. /// <param name="index">Where to insert the objects</param>
  3553. /// <param name="modelObjects">The objects to be inserted</param>
  3554. /// <remarks>
  3555. /// <para>
  3556. /// This operation only makes sense of non-sorted, non-grouped
  3557. /// lists, since any subsequent sort/group operation will rearrange
  3558. /// the list.
  3559. /// </para>
  3560. /// <para>This method only works on ObjectListViews and FastObjectListViews.</para>
  3561. ///</remarks>
  3562. public virtual void InsertObjects(int index, ICollection modelObjects) {
  3563. if (this.InvokeRequired) {
  3564. this.Invoke((MethodInvoker)delegate() {
  3565. this.InsertObjects(index, modelObjects);
  3566. });
  3567. return;
  3568. }
  3569. if (modelObjects == null)
  3570. return;
  3571. this.BeginUpdate();
  3572. try {
  3573. // Give the world a chance to cancel or change the added objects
  3574. ItemsAddingEventArgs args = new ItemsAddingEventArgs(modelObjects);
  3575. this.OnItemsAdding(args);
  3576. if (args.Canceled)
  3577. return;
  3578. modelObjects = args.ObjectsToAdd;
  3579. this.TakeOwnershipOfObjects();
  3580. ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false);
  3581. // If we are filtering the list, there is no way to efficiently
  3582. // insert the objects, so just put them into our collection and rebuild.
  3583. if (this.IsFiltering) {
  3584. ourObjects.InsertRange(index, modelObjects);
  3585. this.BuildList(true);
  3586. return;
  3587. }
  3588. this.ListViewItemSorter = null;
  3589. index = Math.Max(0, Math.Min(index, this.GetItemCount()));
  3590. int i = index;
  3591. foreach (object modelObject in modelObjects) {
  3592. if (modelObject != null) {
  3593. ourObjects.Insert(i, modelObject);
  3594. OLVListItem lvi = new OLVListItem(modelObject);
  3595. this.FillInValues(lvi, modelObject);
  3596. this.Items.Insert(i, lvi);
  3597. i++;
  3598. }
  3599. }
  3600. for (i = index; i < this.GetItemCount(); i++) {
  3601. OLVListItem lvi = this.GetItem(i);
  3602. this.SetSubItemImages(lvi.Index, lvi);
  3603. }
  3604. // Tell the world that the list has changed
  3605. this.OnItemsChanged(new ItemsChangedEventArgs());
  3606. } finally {
  3607. this.EndUpdate();
  3608. }
  3609. }
  3610. /// <summary>
  3611. /// Return true if the row representing the given model is selected
  3612. /// </summary>
  3613. /// <param name="model">The model object to look for</param>
  3614. /// <returns>Is the row selected</returns>
  3615. public bool IsSelected(object model) {
  3616. OLVListItem item = this.ModelToItem(model);
  3617. if (item == null)
  3618. return false;
  3619. else
  3620. return item.Selected;
  3621. }
  3622. /// <summary>
  3623. /// Has the given URL been visited?
  3624. /// </summary>
  3625. /// <param name="url">The string to be consider</param>
  3626. /// <returns>Has it been visited</returns>
  3627. public virtual bool IsUrlVisited(string url) {
  3628. return this.visitedUrlMap.ContainsKey(url);
  3629. }
  3630. /// <summary>
  3631. /// Scroll the ListView by the given deltas.
  3632. /// </summary>
  3633. /// <param name="dx">Horizontal delta</param>
  3634. /// <param name="dy">Vertical delta</param>
  3635. internal void LowLevelScroll(int dx, int dy) {
  3636. NativeMethods.Scroll(this, dx, dy);
  3637. }
  3638. internal Point LowLevelScrollPosition {
  3639. get {
  3640. return new Point(NativeMethods.GetScrollPosition(this, true), NativeMethods.GetScrollPosition(this, false));
  3641. }
  3642. }
  3643. /// <summary>
  3644. /// Remember that the given URL has been visited
  3645. /// </summary>
  3646. /// <param name="url">The url to be remembered</param>
  3647. /// <remarks>This does not cause the control be redrawn</remarks>
  3648. public virtual void MarkUrlVisited(string url) {
  3649. this.visitedUrlMap[url] = true;
  3650. }
  3651. /// <summary>
  3652. /// Move the given collection of objects to the given index.
  3653. /// </summary>
  3654. /// <remarks>This operation only makes sense on non-grouped ObjectListViews.</remarks>
  3655. /// <param name="index"></param>
  3656. /// <param name="modelObjects"></param>
  3657. public virtual void MoveObjects(int index, ICollection modelObjects) {
  3658. // We are going to remove all the given objects from our list
  3659. // and then insert them at the given location
  3660. this.TakeOwnershipOfObjects();
  3661. ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false);
  3662. List<int> indicesToRemove = new List<int>();
  3663. foreach (object modelObject in modelObjects) {
  3664. if (modelObject != null) {
  3665. int i = this.IndexOf(modelObject);
  3666. if (i >= 0) {
  3667. indicesToRemove.Add(i);
  3668. ourObjects.Remove(modelObject);
  3669. if (i <= index)
  3670. index--;
  3671. }
  3672. }
  3673. }
  3674. // Remove the objects in reverse order so earlier
  3675. // deletes don't change the index of later ones
  3676. indicesToRemove.Sort();
  3677. indicesToRemove.Reverse();
  3678. try {
  3679. this.BeginUpdate();
  3680. foreach (int i in indicesToRemove) {
  3681. this.Items.RemoveAt(i);
  3682. }
  3683. this.InsertObjects(index, modelObjects);
  3684. } finally {
  3685. this.EndUpdate();
  3686. }
  3687. }
  3688. /// <summary>
  3689. /// Calculate what item is under the given point?
  3690. /// </summary>
  3691. /// <param name="x"></param>
  3692. /// <param name="y"></param>
  3693. /// <returns></returns>
  3694. new public ListViewHitTestInfo HitTest(int x, int y) {
  3695. // Everything costs something. Playing with the layout of the header can cause problems
  3696. // with the hit testing. If the header shrinks
  3697. try {
  3698. return base.HitTest(x, y);
  3699. } catch (ArgumentOutOfRangeException) {
  3700. return new ListViewHitTestInfo(null, null, ListViewHitTestLocations.None);
  3701. }
  3702. }
  3703. /// <summary>
  3704. /// What is under the given point? This takes the various parts of a cell into accout, including
  3705. /// any custom parts that a custom renderer might use
  3706. /// </summary>
  3707. /// <param name="x"></param>
  3708. /// <param name="y"></param>
  3709. /// <returns>An information block about what is under the point</returns>
  3710. public virtual OlvListViewHitTestInfo OlvHitTest(int x, int y) {
  3711. ListViewHitTestInfo hitTestInfo = this.HitTest(x, y);
  3712. OlvListViewHitTestInfo hti = new OlvListViewHitTestInfo(hitTestInfo);
  3713. // There is a bug/"feature" of the ListView concerning hit testing.
  3714. // If FullRowSelect is false and the point is over cell 0 but not on
  3715. // the text or icon, HitTest will not register a hit. We could turn
  3716. // FullRowSelect on, do the HitTest, and then turn it off again, but
  3717. // toggling FullRowSelect in that way messes up the tooltip in the
  3718. // underlying control. So we have to find another way.
  3719. //
  3720. // It's too hard to try to write the hit test from scratch. Grouping (for
  3721. // example) makes it just too complicated. So, we have to use HitTest
  3722. // but try to get around its limits.
  3723. //
  3724. // First step is to determine if the point was within column 0.
  3725. // If it was, then we only have to determine if there is an actual row
  3726. // under the point. If there is, then we know that the point is over cell 0.
  3727. // So we try a Battleship-style approach: is there a subcell to the right
  3728. // of cell 0? This will return a false negative if column 0 is the rightmost column,
  3729. // so we also check for a subcell to the left. But if only column 0 is visible,
  3730. // then that will fail too, so we check for something at the very left of the
  3731. // control.
  3732. //
  3733. // This will still fail under pathological conditions. If column 0 fills
  3734. // the whole listview and no part of the text column 0 is visible
  3735. // (because it is horizontally scrolled offscreen), then the hit test will fail.
  3736. // Are we in the buggy context? Details view, not full row select, and
  3737. // failing to find anything
  3738. if (hitTestInfo.Item == null && !this.FullRowSelect && this.View == View.Details) {
  3739. // Is the point within the column 0? If it is, maybe it should have been a hit.
  3740. // Let's test slightly to the right and then to left of column 0. Hopefully one
  3741. // of those will hit a subitem
  3742. Point sides = NativeMethods.GetScrolledColumnSides(this, 0);
  3743. if (x >= sides.X && x <= sides.Y) {
  3744. // We look for:
  3745. // - any subitem to the right of cell 0?
  3746. // - any subitem to the left of cell 0?
  3747. // - cell 0 at the left edge of the screen
  3748. hitTestInfo = this.HitTest(sides.Y + 4, y);
  3749. if (hitTestInfo.Item == null)
  3750. hitTestInfo = this.HitTest(sides.X - 4, y);
  3751. if (hitTestInfo.Item == null)
  3752. hitTestInfo = this.HitTest(4, y);
  3753. if (hitTestInfo.Item != null) {
  3754. // We hit something! So, the original point must have been in cell 0
  3755. hti.Item = (OLVListItem)hitTestInfo.Item;
  3756. hti.SubItem = hti.Item.GetSubItem(0);
  3757. hti.Location = ListViewHitTestLocations.None;
  3758. hti.HitTestLocation = HitTestLocation.InCell;
  3759. }
  3760. }
  3761. }
  3762. if (this.OwnerDraw)
  3763. this.CalculateOwnerDrawnHitTest(hti, x, y);
  3764. else
  3765. this.CalculateStandardHitTest(hti, x, y);
  3766. return hti;
  3767. }
  3768. /// <summary>
  3769. /// Perform a hit test when the control is not owner drawn
  3770. /// </summary>
  3771. /// <param name="hti"></param>
  3772. /// <param name="x"></param>
  3773. /// <param name="y"></param>
  3774. protected virtual void CalculateStandardHitTest(OlvListViewHitTestInfo hti, int x, int y) {
  3775. // Standard hit test works fine for the primary column
  3776. if (this.View != View.Details || hti.ColumnIndex == 0 ||
  3777. hti.SubItem == null || hti.Column == null)
  3778. return;
  3779. Rectangle cellBounds = hti.SubItem.Bounds;
  3780. bool hasImage = (this.GetActualImageIndex(hti.SubItem.ImageSelector) != -1);
  3781. // Unless we say otherwise, it was an general incell hit
  3782. hti.HitTestLocation = HitTestLocation.InCell;
  3783. // Check if the point is over where an image should be.
  3784. // If there is a checkbox or image there, tag it and exit.
  3785. Rectangle r = cellBounds;
  3786. r.Width = this.SmallImageSize.Width;
  3787. if (r.Contains(x, y)) {
  3788. if (hti.Column.CheckBoxes) {
  3789. hti.HitTestLocation = HitTestLocation.CheckBox;
  3790. return;
  3791. }
  3792. if (hasImage) {
  3793. hti.HitTestLocation = HitTestLocation.Image;
  3794. return;
  3795. }
  3796. }
  3797. // Figure out where the text actually is and if the point is in it
  3798. // The standard HitTest assumes that any point inside a subitem is
  3799. // a hit on Text -- which is clearly not true.
  3800. Rectangle textBounds = cellBounds;
  3801. textBounds.X += 4;
  3802. if (hasImage)
  3803. textBounds.X += this.SmallImageSize.Width;
  3804. Size proposedSize = new Size(textBounds.Width, textBounds.Height);
  3805. Size textSize = TextRenderer.MeasureText(hti.SubItem.Text, this.Font, proposedSize, TextFormatFlags.EndEllipsis | TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix);
  3806. textBounds.Width = textSize.Width;
  3807. switch (hti.Column.TextAlign) {
  3808. case HorizontalAlignment.Center:
  3809. textBounds.X += (cellBounds.Right - cellBounds.Left - textSize.Width) / 2;
  3810. break;
  3811. case HorizontalAlignment.Right:
  3812. textBounds.X = cellBounds.Right - textSize.Width;
  3813. break;
  3814. }
  3815. if (textBounds.Contains(x, y)) {
  3816. hti.HitTestLocation = HitTestLocation.Text;
  3817. }
  3818. }
  3819. /// <summary>
  3820. /// Perform a hit test when the control is owner drawn. This hands off responsibility
  3821. /// to the renderer.
  3822. /// </summary>
  3823. /// <param name="hti"></param>
  3824. /// <param name="x"></param>
  3825. /// <param name="y"></param>
  3826. protected virtual void CalculateOwnerDrawnHitTest(OlvListViewHitTestInfo hti, int x, int y) {
  3827. // If the click wasn't on an item, give up
  3828. if (hti.Item == null)
  3829. return;
  3830. // If the list is showing column, but they clicked outside the columns, also give up
  3831. if (this.View == View.Details && hti.Column == null)
  3832. return;
  3833. // Which renderer was responsible for drawing that point
  3834. IRenderer renderer = null;
  3835. if (this.View == View.Details) {
  3836. renderer = hti.Column.Renderer ?? this.DefaultRenderer;
  3837. } else {
  3838. renderer = this.ItemRenderer;
  3839. }
  3840. // We can't decide who was responsible. Give up
  3841. if (renderer == null)
  3842. return;
  3843. // Ask the responsible renderer what is at that point
  3844. renderer.HitTest(hti, x, y);
  3845. }
  3846. /// <summary>
  3847. /// Pause (or unpause) all animations in the list
  3848. /// </summary>
  3849. /// <param name="isPause">true to pause, false to unpause</param>
  3850. public virtual void PauseAnimations(bool isPause) {
  3851. for (int i = 0; i < this.Columns.Count; i++) {
  3852. OLVColumn col = this.GetColumn(i);
  3853. if (col.Renderer is ImageRenderer)
  3854. ((ImageRenderer)col.Renderer).Paused = isPause;
  3855. }
  3856. }
  3857. /// <summary>
  3858. /// Rebuild the columns based upon its current view and column visibility settings
  3859. /// </summary>
  3860. public virtual void RebuildColumns() {
  3861. this.ChangeToFilteredColumns(this.View);
  3862. }
  3863. /// <summary>
  3864. /// Remove the given model object from the ListView
  3865. /// </summary>
  3866. /// <param name="modelObject">The model to be removed</param>
  3867. /// <remarks>See RemoveObjects() for more details
  3868. /// <para>This method is thread-safe.</para>
  3869. /// </remarks>
  3870. public virtual void RemoveObject(object modelObject) {
  3871. if (this.InvokeRequired)
  3872. this.Invoke((MethodInvoker)delegate() { this.RemoveObject(modelObject); });
  3873. else
  3874. this.RemoveObjects(new object[] { modelObject });
  3875. }
  3876. /// <summary>
  3877. /// Remove all of the given objects from the control.
  3878. /// </summary>
  3879. /// <param name="modelObjects">Collection of objects to be removed</param>
  3880. /// <remarks>
  3881. /// <para>Nulls and model objects that are not in the ListView are silently ignored.</para>
  3882. /// <para>This method is thread-safe.</para>
  3883. /// </remarks>
  3884. public virtual void RemoveObjects(ICollection modelObjects) {
  3885. if (this.InvokeRequired) {
  3886. this.Invoke((MethodInvoker)delegate() { this.RemoveObjects(modelObjects); });
  3887. return;
  3888. }
  3889. if (modelObjects == null)
  3890. return;
  3891. this.BeginUpdate();
  3892. try {
  3893. // Give the world a chance to cancel or change the added objects
  3894. ItemsRemovingEventArgs args = new ItemsRemovingEventArgs(modelObjects);
  3895. this.OnItemsRemoving(args);
  3896. if (args.Canceled)
  3897. return;
  3898. modelObjects = args.ObjectsToRemove;
  3899. this.TakeOwnershipOfObjects();
  3900. ArrayList ourObjects = (ArrayList)this.Objects;
  3901. foreach (object modelObject in modelObjects) {
  3902. if (modelObject != null) {
  3903. ourObjects.Remove(modelObject);
  3904. int i = this.IndexOf(modelObject);
  3905. if (i >= 0)
  3906. this.Items.RemoveAt(i);
  3907. }
  3908. }
  3909. this.PostProcessRows();
  3910. // Tell the world that the list has changed
  3911. this.OnItemsChanged(new ItemsChangedEventArgs());
  3912. } finally {
  3913. this.EndUpdate();
  3914. }
  3915. }
  3916. /// <summary>
  3917. /// Select all rows in the listview
  3918. /// </summary>
  3919. public virtual void SelectAll() {
  3920. NativeMethods.SelectAllItems(this);
  3921. }
  3922. /// <summary>
  3923. /// Set the given image to be fixed in the bottom right of the list view.
  3924. /// This image will not scroll when the list view scrolls.
  3925. /// </summary>
  3926. /// <remarks>
  3927. /// <para>
  3928. /// This method uses ListView's native ability to display a background image.
  3929. /// It has a few limitations:
  3930. /// </para>
  3931. /// <list type="bullet">
  3932. /// <item><description>It doesn't work well with owner drawn mode. In owner drawn mode, each cell draws itself,
  3933. /// including its background, which covers the background image.</description></item>
  3934. /// <item><description>It doesn't look very good when grid lines are enabled, since the grid lines are drawn over the image.</description></item>
  3935. /// <item><description>It does not work at all on XP.</description></item>
  3936. /// <item><description>Obviously, it doesn't look good when alternate row background colors are enabled.</description></item>
  3937. /// </list>
  3938. /// <para>
  3939. /// If you can live with these limitations, native watermarks are quite neat. They are true backgrounds, not
  3940. /// translucent overlays like the OverlayImage uses. They also have the decided advantage over overlays in that
  3941. /// they work correctly even in MDI applications.
  3942. /// </para>
  3943. /// <para>Setting this clears any background image.</para>
  3944. /// </remarks>
  3945. /// <param name="image">The image to be drawn. If null, any existing image will be removed.</param>
  3946. public void SetNativeBackgroundWatermark(Image image) {
  3947. NativeMethods.SetBackgroundImage(this, image, true, false, 0, 0);
  3948. }
  3949. /// <summary>
  3950. /// Set the given image to be background of the ListView so that it appears at the given
  3951. /// percentage offsets within the list.
  3952. /// </summary>
  3953. /// <remarks>
  3954. /// <para>This has the same limitations as described in <see cref="SetNativeBackgroundWatermark"/>. Make sure those limitations
  3955. /// are understood before using the method.</para>
  3956. /// <para>This is very similar to setting the <see cref="System.Windows.Forms.Control.BackgroundImage"/> property of the standard .NET ListView, except that the standard
  3957. /// BackgroundImage does not handle images with transparent areas properly -- it renders transparent areas as black. This
  3958. /// method does not have that problem.</para>
  3959. /// <para>Setting this clears any background watermark.</para>
  3960. /// </remarks>
  3961. /// <param name="image">The image to be drawn. If null, any existing image will be removed.</param>
  3962. /// <param name="xOffset">The horizontal percentage where the image will be placed. 0 is absolute left, 100 is absolute right.</param>
  3963. /// <param name="yOffset">The vertical percentage where the image will be placed.</param>
  3964. public void SetNativeBackgroundImage(Image image, int xOffset, int yOffset) {
  3965. NativeMethods.SetBackgroundImage(this, image, false, false, xOffset, yOffset);
  3966. }
  3967. /// <summary>
  3968. /// Set the given image to be the tiled background of the ListView.
  3969. /// </summary>
  3970. /// <remarks>
  3971. /// <para>This has the same limitations as described in <see cref="SetNativeBackgroundWatermark"/> and <see cref="SetNativeBackgroundImage"/>.
  3972. /// Make sure those limitations
  3973. /// are understood before using the method.</para>
  3974. /// </remarks>
  3975. /// <param name="image">The image to be drawn. If null, any existing image will be removed.</param>
  3976. public void SetNativeBackgroundTiledImage(Image image) {
  3977. NativeMethods.SetBackgroundImage(this, image, false, true, 0, 0);
  3978. }
  3979. /// <summary>
  3980. /// Set the collection of objects that will be shown in this list view.
  3981. /// </summary>
  3982. /// <remark>This method can safely be called from background threads.</remark>
  3983. /// <remarks>The list is updated immediately</remarks>
  3984. /// <param name="collection">The objects to be displayed</param>
  3985. public virtual void SetObjects(IEnumerable collection) {
  3986. this.SetObjects(collection, false);
  3987. }
  3988. /// <summary>
  3989. /// Set the collection of objects that will be shown in this list view.
  3990. /// </summary>
  3991. /// <remark>This method can safely be called from background threads.</remark>
  3992. /// <remarks>The list is updated immediately</remarks>
  3993. /// <param name="collection">The objects to be displayed</param>
  3994. /// <param name="preserveState">Should the state of the list be preserved as far as is possible.</param>
  3995. public virtual void SetObjects(IEnumerable collection, bool preserveState) {
  3996. if (this.InvokeRequired) {
  3997. this.Invoke((MethodInvoker)delegate { this.SetObjects(collection, preserveState); });
  3998. return;
  3999. }
  4000. // Give the world a chance to cancel or change the assigned collection
  4001. ItemsChangingEventArgs args = new ItemsChangingEventArgs(this.objects, collection);
  4002. this.OnItemsChanging(args);
  4003. if (args.Canceled)
  4004. return;
  4005. collection = args.NewObjects;
  4006. // If we own the current list and they change to another list, we don't own it anymore
  4007. if (this.isOwnerOfObjects && this.objects != collection)
  4008. this.isOwnerOfObjects = false;
  4009. this.objects = collection;
  4010. this.BuildList(preserveState);
  4011. // Tell the world that the list has changed
  4012. this.OnItemsChanged(new ItemsChangedEventArgs());
  4013. }
  4014. #endregion
  4015. #region Save/Restore State
  4016. /// <summary>
  4017. /// Return a byte array that represents the current state of the ObjectListView, such
  4018. /// that the state can be restored by RestoreState()
  4019. /// </summary>
  4020. /// <remarks>
  4021. /// <para>The state of an ObjectListView includes the attributes that the user can modify:
  4022. /// <list type="bullet">
  4023. /// <item><description>current view (i.e. Details, Tile, Large Icon...)</description></item>
  4024. /// <item><description>sort column and direction</description></item>
  4025. /// <item><description>column order</description></item>
  4026. /// <item><description>column widths</description></item>
  4027. /// <item><description>column visibility</description></item>
  4028. /// </list>
  4029. /// </para>
  4030. /// <para>
  4031. /// It does not include selection or the scroll position.
  4032. /// </para>
  4033. /// </remarks>
  4034. /// <returns>A byte array representing the state of the ObjectListView</returns>
  4035. public virtual byte[] SaveState() {
  4036. ObjectListViewState olvState = new ObjectListViewState();
  4037. olvState.VersionNumber = 1;
  4038. olvState.NumberOfColumns = this.AllColumns.Count;
  4039. olvState.CurrentView = this.View;
  4040. // If we have a sort column, it is possible that it is not currently being shown, in which
  4041. // case, it's Index will be -1. So we calculate its index directly. Technically, the sort
  4042. // column does not even have to a member of AllColumns, in which case IndexOf will return -1,
  4043. // which is works fine since we have no way of restoring such a column anyway.
  4044. if (this.LastSortColumn != null)
  4045. olvState.SortColumn = this.AllColumns.IndexOf(this.LastSortColumn);
  4046. olvState.LastSortOrder = this.LastSortOrder;
  4047. olvState.IsShowingGroups = this.ShowGroups;
  4048. if (this.AllColumns.Count > 0 && this.AllColumns[0].LastDisplayIndex == -1)
  4049. this.RememberDisplayIndicies();
  4050. foreach (OLVColumn column in this.AllColumns) {
  4051. olvState.ColumnIsVisible.Add(column.IsVisible);
  4052. olvState.ColumnDisplayIndicies.Add(column.LastDisplayIndex);
  4053. olvState.ColumnWidths.Add(column.Width);
  4054. }
  4055. // Now that we have stored our state, convert it to a byte array
  4056. using (MemoryStream ms = new MemoryStream()) {
  4057. BinaryFormatter serializer = new BinaryFormatter();
  4058. serializer.AssemblyFormat = FormatterAssemblyStyle.Simple;
  4059. serializer.Serialize(ms, olvState);
  4060. return ms.ToArray();
  4061. }
  4062. }
  4063. /// <summary>
  4064. /// Restore the state of the control from the given string, which must have been
  4065. /// produced by SaveState()
  4066. /// </summary>
  4067. /// <param name="state">A byte array returned from SaveState()</param>
  4068. /// <returns>Returns true if the state was restored</returns>
  4069. public virtual bool RestoreState(byte[] state) {
  4070. using (MemoryStream ms = new MemoryStream(state)) {
  4071. BinaryFormatter deserializer = new BinaryFormatter();
  4072. ObjectListViewState olvState;
  4073. try {
  4074. olvState = deserializer.Deserialize(ms) as ObjectListViewState;
  4075. } catch (System.Runtime.Serialization.SerializationException) {
  4076. return false;
  4077. }
  4078. // The number of columns has changed. We have no way to match old
  4079. // columns to the new ones, so we just give up.
  4080. if (olvState.NumberOfColumns != this.AllColumns.Count)
  4081. return false;
  4082. if (olvState.SortColumn == -1) {
  4083. this.LastSortColumn = null;
  4084. this.LastSortOrder = SortOrder.None;
  4085. } else {
  4086. this.LastSortColumn = this.AllColumns[olvState.SortColumn];
  4087. this.LastSortOrder = olvState.LastSortOrder;
  4088. }
  4089. for (int i = 0; i < olvState.NumberOfColumns; i++) {
  4090. OLVColumn column = this.AllColumns[i];
  4091. column.Width = (int)olvState.ColumnWidths[i];
  4092. column.IsVisible = (bool)olvState.ColumnIsVisible[i];
  4093. column.LastDisplayIndex = (int)olvState.ColumnDisplayIndicies[i];
  4094. }
  4095. if (olvState.IsShowingGroups != this.ShowGroups)
  4096. this.ShowGroups = olvState.IsShowingGroups;
  4097. if (this.View == olvState.CurrentView)
  4098. this.RebuildColumns();
  4099. else
  4100. this.View = olvState.CurrentView;
  4101. }
  4102. return true;
  4103. }
  4104. /// <summary>
  4105. /// Instances of this class are used to store the state of an ObjectListView.
  4106. /// </summary>
  4107. [Serializable]
  4108. internal class ObjectListViewState
  4109. {
  4110. public int VersionNumber = 1;
  4111. public int NumberOfColumns = 1;
  4112. public View CurrentView;
  4113. public int SortColumn = -1;
  4114. public bool IsShowingGroups;
  4115. public SortOrder LastSortOrder = SortOrder.None;
  4116. public ArrayList ColumnIsVisible = new ArrayList();
  4117. public ArrayList ColumnDisplayIndicies = new ArrayList();
  4118. public ArrayList ColumnWidths = new ArrayList();
  4119. }
  4120. #endregion
  4121. #region Event handlers
  4122. /// <summary>
  4123. /// The application is idle. Trigger a SelectionChanged event.
  4124. /// </summary>
  4125. /// <param name="sender"></param>
  4126. /// <param name="e"></param>
  4127. protected virtual void HandleApplicationIdle(object sender, EventArgs e) {
  4128. // Remove the handler before triggering the event
  4129. Application.Idle -= new EventHandler(HandleApplicationIdle);
  4130. this.hasIdleHandler = false;
  4131. this.OnSelectionChanged(new EventArgs());
  4132. }
  4133. /// <summary>
  4134. /// The application is idle. Trigger a SelectionChanged event.
  4135. /// </summary>
  4136. /// <param name="sender"></param>
  4137. /// <param name="e"></param>
  4138. protected virtual void HandleApplicationIdle_ResizeColumns(object sender, EventArgs e) {
  4139. // Remove the handler before triggering the event
  4140. Application.Idle -= new EventHandler(HandleApplicationIdle_ResizeColumns);
  4141. this.hasResizeColumnsHandler = false;
  4142. this.ResizeFreeSpaceFillingColumns();
  4143. }
  4144. /// <summary>
  4145. /// Handle the BeginScroll listview notification
  4146. /// </summary>
  4147. /// <param name="m"></param>
  4148. /// <returns>True if the event was completely handled</returns>
  4149. protected virtual bool HandleBeginScroll(ref Message m) {
  4150. //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL");
  4151. NativeMethods.NMLVSCROLL nmlvscroll = (NativeMethods.NMLVSCROLL)m.GetLParam(typeof(NativeMethods.NMLVSCROLL));
  4152. if (nmlvscroll.dx != 0) {
  4153. int scrollPositionH = NativeMethods.GetScrollPosition(this, true);
  4154. ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, scrollPositionH - nmlvscroll.dx, scrollPositionH, ScrollOrientation.HorizontalScroll);
  4155. this.OnScroll(args);
  4156. // Force any empty list msg to redraw when the list is scrolled horizontally
  4157. if (this.GetItemCount() == 0)
  4158. this.Invalidate();
  4159. }
  4160. if (nmlvscroll.dy != 0) {
  4161. int scrollPositionV = NativeMethods.GetScrollPosition(this, false);
  4162. ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, scrollPositionV - nmlvscroll.dy, scrollPositionV, ScrollOrientation.VerticalScroll);
  4163. this.OnScroll(args);
  4164. }
  4165. return false;
  4166. }
  4167. /// <summary>
  4168. /// Handle the EndScroll listview notification
  4169. /// </summary>
  4170. /// <param name="m"></param>
  4171. /// <returns>True if the event was completely handled</returns>
  4172. protected virtual bool HandleEndScroll(ref Message m) {
  4173. //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL");
  4174. // There is a bug in ListView under XP that causes the gridlines to be incorrectly scrolled
  4175. // when the left button is clicked to scroll. This is supposedly documented at
  4176. // KB 813791, but I couldn't find it anywhere. You can follow this thread to see the discussion
  4177. // http://www.ureader.com/msg/1484143.aspx
  4178. if (!ObjectListView.IsVistaOrLater && Control.MouseButtons == MouseButtons.Left && this.GridLines) {
  4179. this.Invalidate();
  4180. this.Update();
  4181. }
  4182. return false;
  4183. }
  4184. /// <summary>
  4185. /// Handle the LinkClick listview notification
  4186. /// </summary>
  4187. /// <param name="m"></param>
  4188. /// <returns>True if the event was completely handled</returns>
  4189. protected virtual bool HandleLinkClick(ref Message m) {
  4190. //System.Diagnostics.Debug.WriteLine("HandleLinkClick");
  4191. NativeMethods.NMLVLINK nmlvlink = (NativeMethods.NMLVLINK)m.GetLParam(typeof(NativeMethods.NMLVLINK));
  4192. // Find the group that was clicked and trigger an event
  4193. foreach (OLVGroup x in this.OLVGroups) {
  4194. if (x.GroupId == nmlvlink.iSubItem) {
  4195. this.OnGroupTaskClicked(new GroupTaskClickedEventArgs(x));
  4196. return true;
  4197. }
  4198. }
  4199. return false;
  4200. }
  4201. /// <summary>
  4202. /// The cell tooltip control wants information about the tool tip that it should show.
  4203. /// </summary>
  4204. /// <param name="sender"></param>
  4205. /// <param name="e"></param>
  4206. protected virtual void HandleCellToolTipShowing(object sender, ToolTipShowingEventArgs e) {
  4207. this.BuildCellEvent(e, this.PointToClient(Cursor.Position));
  4208. if (e.Item != null) {
  4209. e.Text = this.GetCellToolTip(e.ColumnIndex, e.RowIndex);
  4210. this.OnCellToolTip(e);
  4211. }
  4212. }
  4213. /// <summary>
  4214. /// Allow the HeaderControl to call back into HandleHeaderToolTipShowing without making that method public
  4215. /// </summary>
  4216. /// <param name="sender"></param>
  4217. /// <param name="e"></param>
  4218. internal void headerToolTip_Showing(object sender, ToolTipShowingEventArgs e) {
  4219. this.HandleHeaderToolTipShowing(sender, e);
  4220. }
  4221. /// <summary>
  4222. /// The header tooltip control wants information about the tool tip that it should show.
  4223. /// </summary>
  4224. /// <param name="sender"></param>
  4225. /// <param name="e"></param>
  4226. protected virtual void HandleHeaderToolTipShowing(object sender, ToolTipShowingEventArgs e) {
  4227. e.ColumnIndex = this.HeaderControl.ColumnIndexUnderCursor;
  4228. if (e.ColumnIndex < 0)
  4229. return;
  4230. e.RowIndex = -1;
  4231. e.Model = null;
  4232. e.Column = this.GetColumn(e.ColumnIndex);
  4233. e.Text = this.GetHeaderToolTip(e.ColumnIndex);
  4234. e.ListView = this;
  4235. this.OnHeaderToolTip(e);
  4236. }
  4237. /// <summary>
  4238. /// Event handler for the column click event
  4239. /// </summary>
  4240. protected virtual void HandleColumnClick(object sender, ColumnClickEventArgs e) {
  4241. if (!this.PossibleFinishCellEditing())
  4242. return;
  4243. // Toggle the sorting direction on successive clicks on the same column
  4244. if (this.LastSortColumn != null && e.Column == this.LastSortColumn.Index)
  4245. this.LastSortOrder = (this.LastSortOrder == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending);
  4246. else
  4247. this.LastSortOrder = SortOrder.Ascending;
  4248. this.BeginUpdate();
  4249. try {
  4250. this.Sort(e.Column);
  4251. } finally {
  4252. this.EndUpdate();
  4253. }
  4254. }
  4255. #endregion
  4256. #region Low level Windows Message handling
  4257. /// <summary>
  4258. /// Override the basic message pump for this control
  4259. /// </summary>
  4260. /// <param name="m"></param>
  4261. protected override void WndProc(ref Message m) {
  4262. //System.Diagnostics.Debug.WriteLine(m.Msg);
  4263. switch (m.Msg) {
  4264. case 2: // WM_DESTROY
  4265. if (!this.HandleDestroy(ref m))
  4266. base.WndProc(ref m);
  4267. break;
  4268. //case 0x14: // WM_ERASEBKGND
  4269. // Can't do anything here since, when the control is double buffered, anything
  4270. // done here is immediately over-drawn
  4271. // break;
  4272. case 0x0F: // WM_PAINT
  4273. if (!this.HandlePaint(ref m))
  4274. base.WndProc(ref m);
  4275. break;
  4276. case 0x46: // WM_WINDOWPOSCHANGING
  4277. if (!this.HandleWindowPosChanging(ref m))
  4278. base.WndProc(ref m);
  4279. break;
  4280. case 0x4E: // WM_NOTIFY
  4281. if (!this.HandleNotify(ref m))
  4282. base.WndProc(ref m);
  4283. break;
  4284. case 0x0100: // WM_KEY_DOWN
  4285. if (!this.HandleKeyDown(ref m))
  4286. base.WndProc(ref m);
  4287. break;
  4288. case 0x0102: // WM_CHAR
  4289. if (!this.HandleChar(ref m))
  4290. base.WndProc(ref m);
  4291. break;
  4292. case 0x0201: // WM_LBUTTONDOWN
  4293. if (this.PossibleFinishCellEditing() && !this.HandleLButtonDown(ref m))
  4294. base.WndProc(ref m);
  4295. break;
  4296. case 0x202: // WM_LBUTTONUP
  4297. if (ObjectListView.IsVistaOrLater && this.HasCollapsibleGroups)
  4298. base.DefWndProc(ref m);
  4299. base.WndProc(ref m);
  4300. break;
  4301. case 0x0203: // WM_LBUTTONDBLCLK
  4302. if (this.PossibleFinishCellEditing() && !this.HandleLButtonDoubleClick(ref m))
  4303. base.WndProc(ref m);
  4304. break;
  4305. case 0x0204: // WM_RBUTTONDOWN
  4306. if (this.PossibleFinishCellEditing() && !this.HandleRButtonDown(ref m))
  4307. base.WndProc(ref m);
  4308. break;
  4309. case 0x0206: // WM_RBUTTONDBLCLK
  4310. if (this.PossibleFinishCellEditing() && !this.HandleRButtonDoubleClick(ref m))
  4311. base.WndProc(ref m);
  4312. break;
  4313. case 0x204E: // WM_REFLECT_NOTIFY
  4314. if (!this.HandleReflectNotify(ref m))
  4315. base.WndProc(ref m);
  4316. break;
  4317. case 0x114: // WM_HSCROLL:
  4318. case 0x115: // WM_VSCROLL:
  4319. if (this.PossibleFinishCellEditing())
  4320. base.WndProc(ref m);
  4321. break;
  4322. case 0x20A: // WM_MOUSEWHEEL:
  4323. case 0x20E: // WM_MOUSEHWHEEL:
  4324. if (this.PossibleFinishCellEditing())
  4325. base.WndProc(ref m);
  4326. break;
  4327. case 0x7B: // WM_CONTEXTMENU
  4328. if (!this.HandleContextMenu(ref m))
  4329. base.WndProc(ref m);
  4330. break;
  4331. default:
  4332. base.WndProc(ref m);
  4333. break;
  4334. }
  4335. }
  4336. /// <summary>
  4337. /// Handle the search for item m if possible.
  4338. /// </summary>
  4339. /// <param name="m">The m to be processed</param>
  4340. /// <returns>bool to indicate if the m has been handled</returns>
  4341. protected virtual bool HandleChar(ref Message m) {
  4342. // Trigger a normal KeyPress event, which listeners can handle if they want.
  4343. // Handling the event stops ObjectListView's fancy search-by-typing.
  4344. if (this.ProcessKeyEventArgs(ref m))
  4345. return true;
  4346. const int MILLISECONDS_BETWEEN_KEYPRESSES = 1000;
  4347. // What character did the user type and was it part of a longer string?
  4348. char character = (char)m.WParam.ToInt32(); //TODO: Will this work on 64 bit or MBCS?
  4349. if (character == (char)Keys.Back) {
  4350. // Backspace forces the next key to be considered the start of a new search
  4351. this.timeLastCharEvent = 0;
  4352. return true;
  4353. }
  4354. if (System.Environment.TickCount < (this.timeLastCharEvent + MILLISECONDS_BETWEEN_KEYPRESSES))
  4355. this.lastSearchString += character;
  4356. else
  4357. this.lastSearchString = character.ToString();
  4358. // If this control is showing checkboxes, we want to ignore single space presses,
  4359. // since they are used to toggle the selected checkboxes.
  4360. if (this.CheckBoxes && this.lastSearchString == " ") {
  4361. this.timeLastCharEvent = 0;
  4362. return true;
  4363. }
  4364. // Where should the search start?
  4365. int start = 0;
  4366. ListViewItem focused = this.FocusedItem;
  4367. if (focused != null) {
  4368. start = this.GetItemIndexInDisplayOrder(focused);
  4369. // If the user presses a single key, we search from after the focused item,
  4370. // being careful not to march past the end of the list
  4371. if (this.lastSearchString.Length == 1) {
  4372. start += 1;
  4373. if (start == this.GetItemCount())
  4374. start = 0;
  4375. }
  4376. }
  4377. // Give the world a chance to fiddle with or completely avoid the searching process
  4378. BeforeSearchingEventArgs args = new BeforeSearchingEventArgs(this.lastSearchString, start);
  4379. this.OnBeforeSearching(args);
  4380. if (args.Canceled)
  4381. return true;
  4382. // The parameters of the search may have been changed
  4383. string searchString = args.StringToFind;
  4384. start = args.StartSearchFrom;
  4385. // Do the actual search
  4386. int found = this.FindMatchingRow(searchString, start, SearchDirectionHint.Down);
  4387. if (found >= 0) {
  4388. // Select and focus on the found item
  4389. this.BeginUpdate();
  4390. try {
  4391. this.SelectedIndices.Clear();
  4392. ListViewItem lvi = this.GetNthItemInDisplayOrder(found);
  4393. lvi.Selected = true;
  4394. lvi.Focused = true;
  4395. this.EnsureVisible(lvi.Index);
  4396. } finally {
  4397. this.EndUpdate();
  4398. }
  4399. }
  4400. // Tell the world that a search has occurred
  4401. AfterSearchingEventArgs args2 = new AfterSearchingEventArgs(searchString, found);
  4402. this.OnAfterSearching(args2);
  4403. if (!args2.Handled) {
  4404. if (found < 0)
  4405. System.Media.SystemSounds.Beep.Play();
  4406. }
  4407. // When did this event occur?
  4408. this.timeLastCharEvent = System.Environment.TickCount;
  4409. return true;
  4410. }
  4411. private int timeLastCharEvent;
  4412. private string lastSearchString;
  4413. /// <summary>
  4414. /// The user wants to see the context menu.
  4415. /// </summary>
  4416. /// <param name="m">The windows m</param>
  4417. /// <returns>A bool indicating if this m has been handled</returns>
  4418. /// <remarks>
  4419. /// We want to ignore context menu requests that are triggered by right clicks on the header
  4420. /// </remarks>
  4421. protected virtual bool HandleContextMenu(ref Message m) {
  4422. // Don't try to handle context menu commands at design time.
  4423. if (this.DesignMode)
  4424. return false;
  4425. // If the context menu command was generated by the keyboard, LParam will be -1.
  4426. // We don't want to process these.
  4427. if ((m.LParam.ToInt32()) == -1)
  4428. return false;
  4429. // If the context menu came from somewhere other than the header control,
  4430. // we also don't want to ignore it
  4431. if (m.WParam != this.HeaderControl.Handle)
  4432. return false;
  4433. // OK. Looks like a right click in the header
  4434. if (!this.PossibleFinishCellEditing())
  4435. return true;
  4436. int columnIndex = this.HeaderControl.ColumnIndexUnderCursor;
  4437. return this.HandleHeaderRightClick(columnIndex);
  4438. }
  4439. /// <summary>
  4440. /// Handle the Custom draw series of notifications
  4441. /// </summary>
  4442. /// <param name="m">The message</param>
  4443. /// <returns>True if the message has been handled</returns>
  4444. protected virtual bool HandleCustomDraw(ref Message m) {
  4445. const int CDDS_PREPAINT = 1;
  4446. const int CDDS_POSTPAINT = 2;
  4447. const int CDDS_PREERASE = 3;
  4448. const int CDDS_POSTERASE = 4;
  4449. //const int CDRF_NEWFONT = 2;
  4450. //const int CDRF_SKIPDEFAULT = 4;
  4451. const int CDDS_ITEM = 0x00010000;
  4452. const int CDDS_SUBITEM = 0x00020000;
  4453. const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
  4454. const int CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT);
  4455. const int CDDS_ITEMPREERASE = (CDDS_ITEM | CDDS_PREERASE);
  4456. const int CDDS_ITEMPOSTERASE = (CDDS_ITEM | CDDS_POSTERASE);
  4457. const int CDDS_SUBITEMPREPAINT = (CDDS_SUBITEM | CDDS_ITEMPREPAINT);
  4458. const int CDDS_SUBITEMPOSTPAINT = (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT);
  4459. const int CDRF_NOTIFYPOSTPAINT = 0x10;
  4460. //const int CDRF_NOTIFYITEMDRAW = 0x20;
  4461. //const int CDRF_NOTIFYSUBITEMDRAW = 0x20; // same value as above!
  4462. const int CDRF_NOTIFYPOSTERASE = 0x40;
  4463. NativeMethods.NMLVCUSTOMDRAW nmcustomdraw = (NativeMethods.NMLVCUSTOMDRAW)m.GetLParam(typeof(NativeMethods.NMLVCUSTOMDRAW));
  4464. //System.Diagnostics.Debug.WriteLine(String.Format("cd: {0:x}, {1}, {2}", nmcustomdraw.nmcd.dwDrawStage, nmcustomdraw.dwItemType, nmcustomdraw.nmcd.dwItemSpec));
  4465. // There is a bug in owner drawn virtual lists which causes lots of custom draw messages
  4466. // to be sent to the control *outside* of a WmPaint event. AFAIK, these custom draw events
  4467. // are spurious and only serve to make the control flicker annoyingly.
  4468. // So, we ignore messages that are outside of a paint event.
  4469. if (!this.isInWmPaintEvent)
  4470. return true;
  4471. // One more complication! Sometimes with owner drawn virtual lists, the act of drawing
  4472. // the overlays triggers a second attempt to paint the control -- which makes an annoying
  4473. // flicker. So, we only do the custom drawing once per WmPaint event.
  4474. if (!this.shouldDoCustomDrawing)
  4475. return true;
  4476. switch (nmcustomdraw.nmcd.dwDrawStage) {
  4477. case CDDS_PREPAINT:
  4478. //System.Diagnostics.Debug.WriteLine("CDDS_PREPAINT");
  4479. // Remember which items were drawn during this paint cycle
  4480. if (this.prePaintLevel == 0)
  4481. this.drawnItems = new List<OLVListItem>();
  4482. // If there are any items, we have to wait until at least one has been painted
  4483. // before we draw the overlays. If there aren't any items, there will never be any
  4484. // item paint events, so we can draw the overlays whenever
  4485. this.isAfterItemPaint = (this.GetItemCount() == 0);
  4486. this.prePaintLevel++;
  4487. base.WndProc(ref m);
  4488. // Make sure that we get postpaint notifications
  4489. m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE);
  4490. return true;
  4491. case CDDS_POSTPAINT:
  4492. //System.Diagnostics.Debug.WriteLine("CDDS_POSTPAINT");
  4493. this.prePaintLevel--;
  4494. // When in group view, we have two problems. On XP, the control sends
  4495. // a whole heap of PREPAINT/POSTPAINT messages before drawing any items.
  4496. // We have to wait until after the first item paint before we draw overlays.
  4497. // On Vista, we have a different problem. On Vista, the control nests calls
  4498. // to PREPAINT and POSTPAINT. We only want to draw overlays on the outermost
  4499. // POSTPAINT.
  4500. if (this.prePaintLevel == 0 && (this.isMarqueSelecting || this.isAfterItemPaint)) {
  4501. this.shouldDoCustomDrawing = false;
  4502. // Draw our overlays after everything has been drawn
  4503. using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {
  4504. this.DrawAllDecorations(g, this.drawnItems);
  4505. }
  4506. }
  4507. break;
  4508. case CDDS_ITEMPREPAINT:
  4509. //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREPAINT");
  4510. // When in group view on XP, the control send a whole heap of PREPAINT/POSTPAINT
  4511. // messages before drawing any items.
  4512. // We have to wait until after the first item paint before we draw overlays
  4513. this.isAfterItemPaint = true;
  4514. // This scheme of catching custom draw msgs works fine, except
  4515. // for Tile view. Something in .NET's handling of Tile view causes lots
  4516. // of invalidates and erases. So, we just ignore completely
  4517. // .NET's handling of Tile view and let the underlying control
  4518. // do its stuff. Strangely, if the Tile view is
  4519. // completely owner drawn, those erasures don't happen.
  4520. if (this.View == View.Tile) {
  4521. if (this.OwnerDraw && this.ItemRenderer != null)
  4522. base.WndProc(ref m);
  4523. } else {
  4524. base.WndProc(ref m);
  4525. }
  4526. m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE);
  4527. return true;
  4528. case CDDS_ITEMPOSTPAINT:
  4529. //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTPAINT");
  4530. if (this.Columns.Count > 0) {
  4531. OLVListItem olvi = this.GetItem((int)nmcustomdraw.nmcd.dwItemSpec);
  4532. if (olvi != null)
  4533. this.drawnItems.Add(olvi);
  4534. }
  4535. break;
  4536. case CDDS_SUBITEMPREPAINT:
  4537. //System.Diagnostics.Debug.WriteLine(String.Format("CDDS_SUBITEMPREPAINT ({0},{1})", (int)nmcustomdraw.nmcd.dwItemSpec, nmcustomdraw.iSubItem));
  4538. // There is a bug in the .NET framework which appears when column 0 of an owner drawn listview
  4539. // is dragged to another column position.
  4540. // The bounds calculation always returns the left edge of column 0 as being 0.
  4541. // The effects of this bug become apparent
  4542. // when the listview is scrolled horizontally: the control can think that column 0
  4543. // is no longer visible (the horizontal scroll position is subtracted from the bounds, giving a
  4544. // rectangle that is offscreen). In those circumstances, column 0 is not redraw because
  4545. // the control thinks it is not visible and so does not trigger a DrawSubItem event.
  4546. // To fix this problem, we have to detected the situation -- owner drawing column 0 in any column except 0 --
  4547. // trigger our own DrawSubItem, and then prevent the default processing from occuring.
  4548. // Are we owner drawing column 0 when it's in any column except 0?
  4549. if (!this.OwnerDraw)
  4550. return false;
  4551. int columnIndex = nmcustomdraw.iSubItem;
  4552. if (columnIndex != 0)
  4553. return false;
  4554. int displayIndex = this.Columns[0].DisplayIndex;
  4555. if (displayIndex == 0)
  4556. return false;
  4557. int rowIndex = (int)nmcustomdraw.nmcd.dwItemSpec;
  4558. OLVListItem item = this.GetItem(rowIndex);
  4559. if (item == null)
  4560. return false;
  4561. // OK. We have the error condition, so lets do what the .NET framework should do.
  4562. // Trigger an event to draw column 0 when it is not at display index 0
  4563. using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {
  4564. // Correctly calculate the bounds of cell 0
  4565. Rectangle r = item.GetSubItemBounds(0);
  4566. // We can hardcode "0" here since we know we are only doing this for column 0
  4567. DrawListViewSubItemEventArgs args = new DrawListViewSubItemEventArgs(g, r, item, item.SubItems[0], rowIndex, 0,
  4568. this.Columns[0], (ListViewItemStates)nmcustomdraw.nmcd.uItemState);
  4569. this.OnDrawSubItem(args);
  4570. // If the event handler wants to do the default processing (i.e. DrawDefault = true), we are stuck.
  4571. // There is no way we can force the default drawing because of the bug in .NET we are trying to get around.
  4572. System.Diagnostics.Trace.Assert(!args.DrawDefault, "Default drawing is impossible in this situation");
  4573. }
  4574. m.Result = (IntPtr)4;
  4575. return true;
  4576. case CDDS_SUBITEMPOSTPAINT:
  4577. //System.Diagnostics.Debug.WriteLine("CDDS_SUBITEMPOSTPAINT");
  4578. break;
  4579. // I have included these stages, but it doesn't seem that they are sent for ListViews.
  4580. // http://www.tech-archive.net/Archive/VC/microsoft.public.vc.mfc/2006-08/msg00220.html
  4581. case CDDS_PREERASE:
  4582. //System.Diagnostics.Debug.WriteLine("CDDS_PREERASE");
  4583. break;
  4584. case CDDS_POSTERASE:
  4585. //System.Diagnostics.Debug.WriteLine("CDDS_POSTERASE");
  4586. break;
  4587. case CDDS_ITEMPREERASE:
  4588. //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREERASE");
  4589. break;
  4590. case CDDS_ITEMPOSTERASE:
  4591. //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTERASE");
  4592. break;
  4593. }
  4594. return false;
  4595. }
  4596. bool isAfterItemPaint;
  4597. List<OLVListItem> drawnItems;
  4598. /// <summary>
  4599. /// Handle the underlying control being destroyed
  4600. /// </summary>
  4601. /// <param name="m"></param>
  4602. /// <returns></returns>
  4603. protected virtual bool HandleDestroy(ref Message m) {
  4604. //System.Diagnostics.Debug.WriteLine("WM_DESTROY");
  4605. // Recreate the header control when the listview control is destroyed
  4606. this.BeginInvoke((MethodInvoker)delegate {
  4607. this.headerControl = null;
  4608. this.HeaderControl.WordWrap = this.HeaderWordWrap;
  4609. });
  4610. // When the underlying control is destroyed, we need to recreate
  4611. // and reconfigure its tooltip
  4612. if (this.cellToolTip == null)
  4613. return false;
  4614. else {
  4615. this.cellToolTip.PushSettings();
  4616. base.WndProc(ref m);
  4617. this.BeginInvoke((MethodInvoker)delegate {
  4618. this.UpdateCellToolTipHandle();
  4619. this.cellToolTip.PopSettings();
  4620. });
  4621. return true;
  4622. }
  4623. }
  4624. /// <summary>
  4625. /// Handle the search for item m if possible.
  4626. /// </summary>
  4627. /// <param name="m">The m to be processed</param>
  4628. /// <returns>bool to indicate if the m has been handled</returns>
  4629. protected virtual bool HandleFindItem(ref Message m) {
  4630. // NOTE: As far as I can see, this message is never actually sent to the control, making this
  4631. // method redundant!
  4632. const int LVFI_STRING = 0x0002;
  4633. NativeMethods.LVFINDINFO findInfo = (NativeMethods.LVFINDINFO)m.GetLParam(typeof(NativeMethods.LVFINDINFO));
  4634. // We can only handle string searches
  4635. if ((findInfo.flags & LVFI_STRING) != LVFI_STRING)
  4636. return false;
  4637. int start = m.WParam.ToInt32();
  4638. m.Result = (IntPtr)this.FindMatchingRow(findInfo.psz, start, SearchDirectionHint.Down);
  4639. return true;
  4640. }
  4641. /// <summary>
  4642. /// Find the first row after the given start in which the text value in the
  4643. /// comparison column begins with the given text. The comparison column is column 0,
  4644. /// unless IsSearchOnSortColumn is true, in which case the current sort column is used.
  4645. /// </summary>
  4646. /// <param name="text">The text to be prefix matched</param>
  4647. /// <param name="start">The index of the first row to consider</param>
  4648. /// <param name="direction">Which direction should be searched?</param>
  4649. /// <returns>The index of the first row that matched, or -1</returns>
  4650. /// <remarks>The text comparison is a case-insensitive, prefix match. The search will
  4651. /// search the every row until a match is found, wrapping at the end if needed.</remarks>
  4652. public virtual int FindMatchingRow(string text, int start, SearchDirectionHint direction) {
  4653. // We also can't do anything if we don't have data
  4654. int rowCount = this.GetItemCount();
  4655. if (rowCount == 0)
  4656. return -1;
  4657. // Which column are we going to use for our comparing?
  4658. OLVColumn column = this.GetColumn(0);
  4659. if (this.IsSearchOnSortColumn && this.View == View.Details && this.LastSortColumn != null)
  4660. column = this.LastSortColumn;
  4661. // Do two searches if necessary to find a match. The second search is the wrap-around part of searching
  4662. int i;
  4663. if (direction == SearchDirectionHint.Down) {
  4664. i = this.FindMatchInRange(text, start, rowCount - 1, column);
  4665. if (i == -1 && start > 0)
  4666. i = this.FindMatchInRange(text, 0, start - 1, column);
  4667. } else {
  4668. i = this.FindMatchInRange(text, start, 0, column);
  4669. if (i == -1 && start != rowCount)
  4670. i = this.FindMatchInRange(text, rowCount - 1, start + 1, column);
  4671. }
  4672. return i;
  4673. }
  4674. /// <summary>
  4675. /// Find the first row in the given range of rows that prefix matches the string value of the given column.
  4676. /// </summary>
  4677. /// <param name="text"></param>
  4678. /// <param name="first"></param>
  4679. /// <param name="last"></param>
  4680. /// <param name="column"></param>
  4681. /// <returns>The index of the matched row, or -1</returns>
  4682. protected virtual int FindMatchInRange(string text, int first, int last, OLVColumn column) {
  4683. if (first <= last) {
  4684. for (int i = first; i <= last; i++) {
  4685. string data = column.GetStringValue(this.GetNthItemInDisplayOrder(i).RowObject);
  4686. if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
  4687. return i;
  4688. }
  4689. } else {
  4690. for (int i = first; i >= last; i--) {
  4691. string data = column.GetStringValue(this.GetNthItemInDisplayOrder(i).RowObject);
  4692. if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
  4693. return i;
  4694. }
  4695. }
  4696. return -1;
  4697. }
  4698. /// <summary>
  4699. /// Handle a key down message
  4700. /// </summary>
  4701. /// <param name="m"></param>
  4702. /// <returns>True if the msg has been handled</returns>
  4703. protected virtual bool HandleKeyDown(ref Message m) {
  4704. // If this is a checkbox list, toggle the selected rows when the user presses Space
  4705. if (this.CheckBoxes && m.WParam.ToInt32() == (int)Keys.Space && this.SelectedIndices.Count > 0) {
  4706. this.ToggleSelectedRowCheckBoxes();
  4707. return true;
  4708. }
  4709. // Remember the scroll position so we can decide if the listview has scrolled in the
  4710. // handling of the event.
  4711. int scrollPositionH = NativeMethods.GetScrollPosition(this, true);
  4712. int scrollPositionV = NativeMethods.GetScrollPosition(this, false);
  4713. base.WndProc(ref m);
  4714. // It's possible that the processing in base.WndProc has actually destroyed this control
  4715. if (this.IsDisposed)
  4716. return true;
  4717. // If the keydown processing changed the scroll position, trigger a Scroll event
  4718. int newScrollPositionH = NativeMethods.GetScrollPosition(this, true);
  4719. int newScrollPositionV = NativeMethods.GetScrollPosition(this, false);
  4720. if (scrollPositionH != newScrollPositionH) {
  4721. ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll,
  4722. scrollPositionH, newScrollPositionH, ScrollOrientation.HorizontalScroll);
  4723. this.OnScroll(args);
  4724. this.RefreshHotItem();
  4725. }
  4726. if (scrollPositionV != newScrollPositionV) {
  4727. ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll,
  4728. scrollPositionV, newScrollPositionV, ScrollOrientation.VerticalScroll);
  4729. this.OnScroll(args);
  4730. this.RefreshHotItem();
  4731. }
  4732. return true;
  4733. }
  4734. private void ToggleSelectedRowCheckBoxes() {
  4735. // This doesn't actually toggle all rows. It toggles the first row, and
  4736. // all other rows get the check state of that first row.
  4737. Object primaryModel = this.GetItem(this.SelectedIndices[0]).RowObject;
  4738. this.ToggleCheckObject(primaryModel);
  4739. CheckState? state = this.GetCheckState(primaryModel);
  4740. if (state.HasValue) {
  4741. foreach (Object x in this.SelectedObjects)
  4742. this.SetObjectCheckedness(x, state.Value);
  4743. }
  4744. }
  4745. /// <summary>
  4746. /// Catch the Left Button down event.
  4747. /// </summary>
  4748. /// <param name="m">The m to be processed</param>
  4749. /// <returns>bool to indicate if the m has been handled</returns>
  4750. protected virtual bool HandleLButtonDown(ref Message m) {
  4751. // We have to intercept this low level message rather than the more natural
  4752. // overridding of OnMouseDown, since ListCtrl's internal mouse down behavior
  4753. // is to select (or deselect) rows when the mouse is released. We don't
  4754. // want the selection to change when the user checks or unchecks a checkbox, so if the
  4755. // mouse down event was to check/uncheck, we have to hide this mouse
  4756. // down event from the control.
  4757. int x = m.LParam.ToInt32() & 0xFFFF;
  4758. int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;
  4759. return this.ProcessLButtonDown(this.OlvHitTest(x, y));
  4760. }
  4761. /// <summary>
  4762. /// Handle a left mouse down at the given hit test location
  4763. /// </summary>
  4764. /// <remarks>Subclasses can override this to do something unique</remarks>
  4765. /// <param name="hti"></param>
  4766. /// <returns>True if the message has been handled</returns>
  4767. protected virtual bool ProcessLButtonDown(OlvListViewHitTestInfo hti) {
  4768. if (hti.Item == null)
  4769. return false;
  4770. // If they didn't click checkbox, we can just return
  4771. if (this.View != View.Details || hti.HitTestLocation != HitTestLocation.CheckBox)
  4772. return false;
  4773. // Did they click a sub item checkbox?
  4774. if (hti.Column.Index > 0) {
  4775. if (hti.Column.IsEditable)
  4776. this.ToggleSubItemCheckBox(hti.RowObject, hti.Column);
  4777. return true;
  4778. }
  4779. // They must have clicked the primary checkbox
  4780. this.ToggleCheckObject(hti.RowObject);
  4781. // If they change the checkbox of a selecte row, all the rows in the selection
  4782. // should be given the same state
  4783. if (hti.Item.Selected) {
  4784. CheckState? state = this.GetCheckState(hti.RowObject);
  4785. if (state.HasValue) {
  4786. foreach (Object x in this.SelectedObjects)
  4787. this.SetObjectCheckedness(x, state.Value);
  4788. }
  4789. }
  4790. return true;
  4791. }
  4792. /// <summary>
  4793. /// Catch the Right Button down event.
  4794. /// </summary>
  4795. /// <param name="m">The m to be processed</param>
  4796. /// <returns>bool to indicate if the m has been handled</returns>
  4797. protected virtual bool HandleRButtonDown(ref Message m) {
  4798. int x = m.LParam.ToInt32() & 0xFFFF;
  4799. int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;
  4800. return this.ProcessRButtonDown(this.OlvHitTest(x, y));
  4801. }
  4802. /// <summary>
  4803. /// Handle a left mouse down at the given hit test location
  4804. /// </summary>
  4805. /// <remarks>Subclasses can override this to do something unique</remarks>
  4806. /// <param name="hti"></param>
  4807. /// <returns>True if the message has been handled</returns>
  4808. protected virtual bool ProcessRButtonDown(OlvListViewHitTestInfo hti) {
  4809. if (hti.Item == null)
  4810. return false;
  4811. // Ignore clicks on checkboxes
  4812. return (this.View == View.Details && hti.HitTestLocation == HitTestLocation.CheckBox);
  4813. }
  4814. /// <summary>
  4815. /// Catch the Left Button double click event.
  4816. /// </summary>
  4817. /// <param name="m">The m to be processed</param>
  4818. /// <returns>bool to indicate if the m has been handled</returns>
  4819. protected virtual bool HandleLButtonDoubleClick(ref Message m) {
  4820. int x = m.LParam.ToInt32() & 0xFFFF;
  4821. int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;
  4822. return this.ProcessLButtonDoubleClick(this.OlvHitTest(x, y));
  4823. }
  4824. /// <summary>
  4825. /// Handle a mouse double click at the given hit test location
  4826. /// </summary>
  4827. /// <remarks>Subclasses can override this to do something unique</remarks>
  4828. /// <param name="hti"></param>
  4829. /// <returns>True if the message has been handled</returns>
  4830. protected virtual bool ProcessLButtonDoubleClick(OlvListViewHitTestInfo hti) {
  4831. // If the user double clicked on a checkbox, ignore it
  4832. return (hti.HitTestLocation == HitTestLocation.CheckBox);
  4833. }
  4834. /// <summary>
  4835. /// Catch the right Button double click event.
  4836. /// </summary>
  4837. /// <param name="m">The m to be processed</param>
  4838. /// <returns>bool to indicate if the m has been handled</returns>
  4839. protected virtual bool HandleRButtonDoubleClick(ref Message m) {
  4840. int x = m.LParam.ToInt32() & 0xFFFF;
  4841. int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;
  4842. return this.ProcessRButtonDoubleClick(this.OlvHitTest(x, y));
  4843. }
  4844. /// <summary>
  4845. /// Handle a right mouse double click at the given hit test location
  4846. /// </summary>
  4847. /// <remarks>Subclasses can override this to do something unique</remarks>
  4848. /// <param name="hti"></param>
  4849. /// <returns>True if the message has been handled</returns>
  4850. protected virtual bool ProcessRButtonDoubleClick(OlvListViewHitTestInfo hti) {
  4851. // If the user double clicked on a checkbox, ignore it
  4852. return (hti.HitTestLocation == HitTestLocation.CheckBox);
  4853. }
  4854. /// <summary>
  4855. /// In the notification messages, we handle change of state of list items
  4856. /// </summary>
  4857. /// <param name="m">The m to be processed</param>
  4858. /// <returns>bool to indicate if the m has been handled</returns>
  4859. protected virtual bool HandleReflectNotify(ref Message m) {
  4860. const int NM_CLICK = -2;
  4861. const int NM_DBLCLK = -3;
  4862. const int NM_RDBLCLK = -6;
  4863. const int NM_CUSTOMDRAW = -12;
  4864. const int NM_RELEASEDCAPTURE = -16;
  4865. const int LVN_ITEMCHANGED = -101;
  4866. const int LVN_ITEMCHANGING = -100;
  4867. const int LVN_MARQUEEBEGIN = -156;
  4868. const int LVN_GETINFOTIP = -158;
  4869. const int LVN_BEGINSCROLL = -180;
  4870. const int LVN_ENDSCROLL = -181;
  4871. const int LVN_LINKCLICK = -184;
  4872. const int LVIF_STATE = 8;
  4873. bool isMsgHandled = false;
  4874. // TODO: Don't do any logic in this method. Create separate methods for each message
  4875. NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
  4876. //System.Diagnostics.Debug.WriteLine(String.Format("rn: {0}", nmhdr->code));
  4877. switch (nmhdr.code) {
  4878. case NM_CLICK:
  4879. // The standard ListView does some strange stuff here when the list has checkboxes.
  4880. // If you shift click on non-primary columns when FullRowSelect is true, the
  4881. // checkedness of the selected rows changes.
  4882. // We avoid all that by just saying we've handled this message.
  4883. //System.Diagnostics.Debug.WriteLine("NM_CLICK");
  4884. isMsgHandled = true;
  4885. this.OnClick(EventArgs.Empty);
  4886. break;
  4887. case LVN_BEGINSCROLL:
  4888. isMsgHandled = this.HandleBeginScroll(ref m);
  4889. break;
  4890. case LVN_ENDSCROLL:
  4891. isMsgHandled = this.HandleEndScroll(ref m);
  4892. break;
  4893. case LVN_LINKCLICK:
  4894. isMsgHandled = this.HandleLinkClick(ref m);
  4895. break;
  4896. case LVN_MARQUEEBEGIN:
  4897. //System.Diagnostics.Debug.WriteLine("LVN_MARQUEEBEGIN");
  4898. this.isMarqueSelecting = true;
  4899. break;
  4900. case LVN_GETINFOTIP:
  4901. //System.Diagnostics.Debug.WriteLine("LVN_GETINFOTIP");
  4902. // When virtual lists are in SmallIcon view, they generates tooltip message with invalid item indicies.
  4903. NativeMethods.NMLVGETINFOTIP nmGetInfoTip = (NativeMethods.NMLVGETINFOTIP)m.GetLParam(typeof(NativeMethods.NMLVGETINFOTIP));
  4904. isMsgHandled = nmGetInfoTip.iItem >= this.GetItemCount();
  4905. break;
  4906. case NM_RELEASEDCAPTURE:
  4907. //System.Diagnostics.Debug.WriteLine("NM_RELEASEDCAPTURE");
  4908. this.isMarqueSelecting = false;
  4909. this.Invalidate();
  4910. break;
  4911. case NM_CUSTOMDRAW:
  4912. //System.Diagnostics.Debug.WriteLine("NM_CUSTOMDRAW");
  4913. isMsgHandled = this.HandleCustomDraw(ref m);
  4914. break;
  4915. case NM_DBLCLK:
  4916. // The default behavior of a .NET ListView with checkboxes is to toggle the checkbox on
  4917. // double-click. That's just silly, if you ask me :)
  4918. if (this.CheckBoxes) {
  4919. // How do we make ListView not do that silliness? We could just ignore the message
  4920. // but the last part of the base code sets up state information, and without that
  4921. // state, the ListView doesn't trigger MouseDoubleClick events. So we fake a
  4922. // right button double click event, which sets up the same state, but without
  4923. // toggling the checkbox.
  4924. nmhdr.code = NM_RDBLCLK;
  4925. Marshal.StructureToPtr(nmhdr, m.LParam, false);
  4926. }
  4927. break;
  4928. case LVN_ITEMCHANGED:
  4929. //System.Diagnostics.Debug.WriteLine("LVN_ITEMCHANGED");
  4930. NativeMethods.NMLISTVIEW nmlistviewPtr2 = (NativeMethods.NMLISTVIEW)m.GetLParam(typeof(NativeMethods.NMLISTVIEW));
  4931. if ((nmlistviewPtr2.uChanged & LVIF_STATE) != 0) {
  4932. CheckState currentValue = this.CalculateCheckState(nmlistviewPtr2.uOldState);
  4933. CheckState newCheckValue = this.CalculateCheckState(nmlistviewPtr2.uNewState);
  4934. if (currentValue != newCheckValue) {
  4935. // Remove the state indicies so that we don't trigger the OnItemChecked method
  4936. // when we call our base method after exiting this method
  4937. nmlistviewPtr2.uOldState = (nmlistviewPtr2.uOldState & 0x0FFF);
  4938. nmlistviewPtr2.uNewState = (nmlistviewPtr2.uNewState & 0x0FFF);
  4939. Marshal.StructureToPtr(nmlistviewPtr2, m.LParam, false);
  4940. }
  4941. }
  4942. break;
  4943. case LVN_ITEMCHANGING:
  4944. //System.Diagnostics.Debug.WriteLine("LVN_ITEMCHANGING");
  4945. NativeMethods.NMLISTVIEW nmlistviewPtr = (NativeMethods.NMLISTVIEW)m.GetLParam(typeof(NativeMethods.NMLISTVIEW));
  4946. if ((nmlistviewPtr.uChanged & LVIF_STATE) != 0) {
  4947. CheckState currentValue = this.CalculateCheckState(nmlistviewPtr.uOldState);
  4948. CheckState newCheckValue = this.CalculateCheckState(nmlistviewPtr.uNewState);
  4949. if (currentValue != newCheckValue) {
  4950. // Prevent the base method from seeing the state change,
  4951. // since we handled it elsewhere
  4952. nmlistviewPtr.uChanged &= ~LVIF_STATE;
  4953. Marshal.StructureToPtr(nmlistviewPtr, m.LParam, false);
  4954. }
  4955. }
  4956. break;
  4957. }
  4958. return isMsgHandled;
  4959. }
  4960. private CheckState CalculateCheckState(int state) {
  4961. switch ((state & 0xf000) >> 12) {
  4962. case 1:
  4963. return CheckState.Unchecked;
  4964. case 2:
  4965. return CheckState.Checked;
  4966. case 3:
  4967. return CheckState.Indeterminate;
  4968. default:
  4969. return CheckState.Checked;
  4970. }
  4971. }
  4972. /// <summary>
  4973. /// In the notification messages, we handle attempts to change the width of our columns
  4974. /// </summary>
  4975. /// <param name="m">The m to be processed</param>
  4976. /// <returns>bool to indicate if the m has been handled</returns>
  4977. protected bool HandleNotify(ref Message m) {
  4978. bool isMsgHandled = false;
  4979. const int NM_CUSTOMDRAW = -12;
  4980. const int HDN_FIRST = (0 - 300);
  4981. const int HDN_ITEMCHANGINGA = (HDN_FIRST - 0);
  4982. const int HDN_ITEMCHANGINGW = (HDN_FIRST - 20);
  4983. const int HDN_ITEMCLICKA = (HDN_FIRST - 2);
  4984. const int HDN_ITEMCLICKW = (HDN_FIRST - 22);
  4985. const int HDN_DIVIDERDBLCLICKA = (HDN_FIRST - 5);
  4986. const int HDN_DIVIDERDBLCLICKW = (HDN_FIRST - 25);
  4987. const int HDN_BEGINTRACKA = (HDN_FIRST - 6);
  4988. const int HDN_BEGINTRACKW = (HDN_FIRST - 26);
  4989. const int HDN_ENDTRACKA = (HDN_FIRST - 7);
  4990. const int HDN_ENDTRACKW = (HDN_FIRST - 27);
  4991. const int HDN_TRACKA = (HDN_FIRST - 8);
  4992. const int HDN_TRACKW = (HDN_FIRST - 28);
  4993. // Handle the notification, remembering to handle both ANSI and Unicode versions
  4994. NativeMethods.NMHEADER nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER));
  4995. //System.Diagnostics.Debug.WriteLine(String.Format("not: {0}", nmhdr->code));
  4996. //if (nmhdr.code < HDN_FIRST)
  4997. // System.Diagnostics.Debug.WriteLine(nmhdr.code);
  4998. // In KB Article #183258, MS states that when a header control has the HDS_FULLDRAG style, it will receive
  4999. // ITEMCHANGING events rather than TRACK events. Under XP SP2 (at least) this is not always true, which may be
  5000. // why MS has withdrawn that particular KB article. It is true that the header is always given the HDS_FULLDRAG
  5001. // style. But even while window style set, the control doesn't always received ITEMCHANGING events.
  5002. // The controlling setting seems to be the Explorer option "Show Window Contents While Dragging"!
  5003. // In the category of "truly bizarre side effects", if the this option is turned on, we will receive
  5004. // ITEMCHANGING events instead of TRACK events. But if it is turned off, we receive lots of TRACK events and
  5005. // only one ITEMCHANGING event at the very end of the process.
  5006. // If we receive HDN_TRACK messages, it's harder to control the resizing process. If we return a result of 1, we
  5007. // cancel the whole drag operation, not just that particular track event, which is clearly not what we want.
  5008. // If we are willing to compile with unsafe code enabled, we can modify the size of the column in place, using the
  5009. // commented out code below. But without unsafe code, the best we can do is allow the user to drag the column to
  5010. // any width, and then spring it back to within bounds once they release the mouse button. UI-wise it's very ugly.
  5011. switch (nmheader.nhdr.code) {
  5012. case NM_CUSTOMDRAW:
  5013. if (!this.OwnerDrawnHeader)
  5014. isMsgHandled = this.HeaderControl.HandleHeaderCustomDraw(ref m);
  5015. break;
  5016. case HDN_ITEMCLICKA:
  5017. case HDN_ITEMCLICKW:
  5018. if (!this.PossibleFinishCellEditing()) {
  5019. m.Result = (IntPtr)1; // prevent the change from happening
  5020. isMsgHandled = true;
  5021. }
  5022. break;
  5023. case HDN_DIVIDERDBLCLICKA:
  5024. case HDN_DIVIDERDBLCLICKW:
  5025. case HDN_BEGINTRACKA:
  5026. case HDN_BEGINTRACKW:
  5027. if (!this.PossibleFinishCellEditing()) {
  5028. m.Result = (IntPtr)1; // prevent the change from happening
  5029. isMsgHandled = true;
  5030. break;
  5031. }
  5032. if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) {
  5033. OLVColumn column = this.GetColumn(nmheader.iItem);
  5034. // Space filling columns can't be dragged or double-click resized
  5035. if (column.FillsFreeSpace) {
  5036. m.Result = (IntPtr)1; // prevent the change from happening
  5037. isMsgHandled = true;
  5038. }
  5039. }
  5040. break;
  5041. case HDN_ENDTRACKA:
  5042. case HDN_ENDTRACKW:
  5043. if (this.ShowGroups)
  5044. this.ResizeLastGroup();
  5045. break;
  5046. case HDN_TRACKA:
  5047. case HDN_TRACKW:
  5048. if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) {
  5049. NativeMethods.HDITEM hditem = (NativeMethods.HDITEM)Marshal.PtrToStructure(nmheader.pHDITEM, typeof(NativeMethods.HDITEM));
  5050. OLVColumn column = this.GetColumn(nmheader.iItem);
  5051. if (hditem.cxy < column.MinimumWidth)
  5052. hditem.cxy = column.MinimumWidth;
  5053. else if (column.MaximumWidth != -1 && hditem.cxy > column.MaximumWidth)
  5054. hditem.cxy = column.MaximumWidth;
  5055. Marshal.StructureToPtr(hditem, nmheader.pHDITEM, false);
  5056. }
  5057. break;
  5058. case HDN_ITEMCHANGINGA:
  5059. case HDN_ITEMCHANGINGW:
  5060. nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER));
  5061. if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) {
  5062. NativeMethods.HDITEM hditem = (NativeMethods.HDITEM)Marshal.PtrToStructure(nmheader.pHDITEM, typeof(NativeMethods.HDITEM));
  5063. OLVColumn column = this.GetColumn(nmheader.iItem);
  5064. // Check the mask to see if the width field is valid, and if it is, make sure it's within range
  5065. if ((hditem.mask & 1) == 1) {
  5066. if (hditem.cxy < column.MinimumWidth ||
  5067. (column.MaximumWidth != -1 && hditem.cxy > column.MaximumWidth)) {
  5068. m.Result = (IntPtr)1; // prevent the change from happening
  5069. isMsgHandled = true;
  5070. }
  5071. }
  5072. }
  5073. break;
  5074. case ToolTipControl.TTN_SHOW:
  5075. //System.Diagnostics.Debug.WriteLine("olv TTN_SHOW");
  5076. System.Diagnostics.Trace.Assert(this.CellToolTip.Handle == nmheader.nhdr.hwndFrom);
  5077. isMsgHandled = this.CellToolTip.HandleShow(ref m);
  5078. break;
  5079. case ToolTipControl.TTN_POP:
  5080. //System.Diagnostics.Debug.WriteLine("olv TTN_POP");
  5081. System.Diagnostics.Trace.Assert(this.CellToolTip.Handle == nmheader.nhdr.hwndFrom);
  5082. isMsgHandled = this.CellToolTip.HandlePop(ref m);
  5083. break;
  5084. case ToolTipControl.TTN_GETDISPINFO:
  5085. //System.Diagnostics.Debug.WriteLine("olv TTN_GETDISPINFO");
  5086. System.Diagnostics.Trace.Assert(this.CellToolTip.Handle == nmheader.nhdr.hwndFrom);
  5087. isMsgHandled = this.CellToolTip.HandleGetDispInfo(ref m);
  5088. break;
  5089. }
  5090. return isMsgHandled;
  5091. }
  5092. /// <summary>
  5093. /// Create a ToolTipControl to manage the tooltip control used by the listview control
  5094. /// </summary>
  5095. protected virtual void CreateCellToolTip() {
  5096. this.cellToolTip = new ToolTipControl();
  5097. this.cellToolTip.AssignHandle(NativeMethods.GetTooltipControl(this));
  5098. this.cellToolTip.Showing += new EventHandler<ToolTipShowingEventArgs>(HandleCellToolTipShowing);
  5099. this.cellToolTip.SetMaxWidth();
  5100. NativeMethods.MakeTopMost(this.cellToolTip);
  5101. }
  5102. /// <summary>
  5103. /// Update the handle used by our cell tooltip to be the tooltip used by
  5104. /// the underlying Windows listview control.
  5105. /// </summary>
  5106. protected virtual void UpdateCellToolTipHandle() {
  5107. if (this.cellToolTip != null && this.cellToolTip.Handle == IntPtr.Zero)
  5108. this.cellToolTip.AssignHandle(NativeMethods.GetTooltipControl(this));
  5109. }
  5110. /// <summary>
  5111. /// Handle the WM_PAINT event
  5112. /// </summary>
  5113. /// <param name="m"></param>
  5114. /// <returns>Return true if the msg has been handled and nothing further should be done</returns>
  5115. protected virtual bool HandlePaint(ref Message m) {
  5116. //System.Diagnostics.Debug.WriteLine("> WMPAINT");
  5117. // We only want to custom draw the control within WmPaint message and only
  5118. // once per paint event. We use these bools to insure this.
  5119. this.isInWmPaintEvent = true;
  5120. this.shouldDoCustomDrawing = true;
  5121. this.prePaintLevel = 0;
  5122. this.ShowOverlays();
  5123. this.HandlePrePaint();
  5124. base.WndProc(ref m);
  5125. this.HandlePostPaint();
  5126. this.isInWmPaintEvent = false;
  5127. //System.Diagnostics.Debug.WriteLine("< WMPAINT");
  5128. return true;
  5129. }
  5130. private int prePaintLevel;
  5131. /// <summary>
  5132. /// Perform any steps needed before painting the control
  5133. /// </summary>
  5134. protected virtual void HandlePrePaint() {
  5135. // When we get a WM_PAINT msg, remember the rectangle that is being updated.
  5136. // We can't get this information later, since the BeginPaint call wipes it out.
  5137. this.lastUpdateRectangle = NativeMethods.GetUpdateRect(this);
  5138. //// When the list is empty, we want to handle the drawing of the control by ourselves.
  5139. //// Unfortunately, there is no easy way to tell our superclass that we want to do this.
  5140. //// So we resort to guile and deception. We validate the list area of the control, which
  5141. //// effectively tells our superclass that this area does not need to be painted.
  5142. //// Our superclass will then not paint the control, leaving us free to do so ourselves.
  5143. //// Without doing this trickery, the superclass will draw the list as empty,
  5144. //// and then moments later, we will draw the empty list msg, giving a nasty flicker
  5145. //if (this.GetItemCount() == 0 && this.HasEmptyListMsg)
  5146. // NativeMethods.ValidateRect(this, this.ClientRectangle);
  5147. }
  5148. /// <summary>
  5149. /// Perform any steps needed after painting the control
  5150. /// </summary>
  5151. protected virtual void HandlePostPaint() {
  5152. // This message is no longer necessary, but we keep it for compatibility
  5153. }
  5154. /// <summary>
  5155. /// Handle the window position changing.
  5156. /// </summary>
  5157. /// <param name="m">The m to be processed</param>
  5158. /// <returns>bool to indicate if the m has been handled</returns>
  5159. protected virtual bool HandleWindowPosChanging(ref Message m) {
  5160. const int SWP_NOSIZE = 1;
  5161. NativeMethods.WINDOWPOS pos = (NativeMethods.WINDOWPOS)m.GetLParam(typeof(NativeMethods.WINDOWPOS));
  5162. if ((pos.flags & SWP_NOSIZE) == 0) {
  5163. if (pos.cx < this.Bounds.Width) // only when shrinking
  5164. // pos.cx is the window width, not the client area width, so we have to subtract the border widths
  5165. this.ResizeFreeSpaceFillingColumns(pos.cx - (this.Bounds.Width - this.ClientSize.Width));
  5166. if (this.ShowGroups)
  5167. this.ResizeLastGroup();
  5168. }
  5169. return false;
  5170. }
  5171. #endregion
  5172. #region Column header clicking, column hiding and resizing
  5173. /// <summary>
  5174. /// The user has right clicked on the column headers. Do whatever is required
  5175. /// </summary>
  5176. /// <returns>Return true if this event has been handle</returns>
  5177. protected virtual bool HandleHeaderRightClick(int columnIndex) {
  5178. ColumnClickEventArgs eventArgs = new ColumnClickEventArgs(columnIndex);
  5179. this.OnColumnRightClick(eventArgs);
  5180. return this.ShowHeaderRightClickMenu(columnIndex, Cursor.Position);
  5181. }
  5182. /// <summary>
  5183. /// Show a menu that is appropriate when the given column header is clicked.
  5184. /// </summary>
  5185. /// <param name="columnIndex">The index of the header that was clicked. This
  5186. /// can be -1, indicating that the header was clicked outside of a column</param>
  5187. /// <param name="pt">Where should the menu be shown</param>
  5188. /// <returns>True if a menu was displayed</returns>
  5189. protected virtual bool ShowHeaderRightClickMenu(int columnIndex, Point pt) {
  5190. ToolStripDropDown m = this.MakeHeaderRightClickMenu(columnIndex);
  5191. if (m.Items.Count > 0) {
  5192. m.Show(pt);
  5193. return true;
  5194. }
  5195. return false;
  5196. }
  5197. /// <summary>
  5198. /// Create the menu that should be displayed when the user right clicks
  5199. /// on the given column header.
  5200. /// </summary>
  5201. /// <param name="columnIndex">Index of the column that was right clicked.
  5202. /// This can be negative, which indicates a click outside of any header.</param>
  5203. /// <returns>The toolstrip that should be displayed</returns>
  5204. protected virtual ToolStripDropDown MakeHeaderRightClickMenu(int columnIndex) {
  5205. ToolStripDropDown m = new ContextMenuStrip();
  5206. if (columnIndex >= 0 && this.UseFiltering && this.ShowFilterMenuOnRightClick)
  5207. m = this.MakeFilteringMenu(m, columnIndex);
  5208. if (columnIndex >= 0 && this.ShowCommandMenuOnRightClick)
  5209. m = this.MakeColumnCommandMenu(m, columnIndex);
  5210. if (this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None) {
  5211. m = this.MakeColumnSelectMenu(m);
  5212. }
  5213. return m;
  5214. }
  5215. /// <summary>
  5216. /// The user has right clicked on the column headers. Do whatever is required
  5217. /// </summary>
  5218. /// <returns>Return true if this event has been handle</returns>
  5219. [Obsolete("Use HandleHeaderRightClick(int) instead")]
  5220. protected virtual bool HandleHeaderRightClick() {
  5221. return false;
  5222. }
  5223. /// <summary>
  5224. /// Show a popup menu at the given point which will allow the user to choose which columns
  5225. /// are visible on this listview
  5226. /// </summary>
  5227. /// <param name="pt">Where should the menu be placed</param>
  5228. [Obsolete("Use ShowHeaderRightClickMenu instead")]
  5229. protected virtual void ShowColumnSelectMenu(Point pt) {
  5230. ToolStripDropDown m = this.MakeColumnSelectMenu(new ContextMenuStrip());
  5231. m.Show(pt);
  5232. }
  5233. /// <summary>
  5234. /// Show a popup menu at the given point which will allow the user to choose which columns
  5235. /// are visible on this listview
  5236. /// </summary>
  5237. /// <param name="columnIndex"></param>
  5238. /// <param name="pt">Where should the menu be placed</param>
  5239. [Obsolete("Use ShowHeaderRightClickMenu instead")]
  5240. protected virtual void ShowColumnCommandMenu(int columnIndex, Point pt) {
  5241. ToolStripDropDown m = this.MakeColumnCommandMenu(new ContextMenuStrip(), columnIndex);
  5242. if (this.SelectColumnsOnRightClick) {
  5243. if (m.Items.Count > 0)
  5244. m.Items.Add(new ToolStripSeparator());
  5245. this.MakeColumnSelectMenu(m);
  5246. }
  5247. m.Show(pt);
  5248. }
  5249. /// <summary>
  5250. /// Gets or set the text to be used for the sorting ascending command
  5251. /// </summary>
  5252. [Category("Labels - ObjectListView"), DefaultValue("Sort ascending by '{0}'"), Localizable(true)]
  5253. public string MenuLabelSortAscending {
  5254. get { return this.menuLabelSortAscending; }
  5255. set { this.menuLabelSortAscending = value; }
  5256. }
  5257. private string menuLabelSortAscending = "Sort ascending by '{0}'";
  5258. /// <summary>
  5259. ///
  5260. /// </summary>
  5261. [Category("Labels - ObjectListView"), DefaultValue("Sort descending by '{0}'"), Localizable(true)]
  5262. public string MenuLabelSortDescending {
  5263. get { return this.menuLabelSortDescending; }
  5264. set { this.menuLabelSortDescending = value; }
  5265. }
  5266. private string menuLabelSortDescending = "Sort descending by '{0}'";
  5267. /// <summary>
  5268. ///
  5269. /// </summary>
  5270. [Category("Labels - ObjectListView"), DefaultValue("Group by '{0}'"), Localizable(true)]
  5271. public string MenuLabelGroupBy {
  5272. get { return this.menuLabelGroupBy; }
  5273. set { this.menuLabelGroupBy = value; }
  5274. }
  5275. private string menuLabelGroupBy = "Group by '{0}'";
  5276. /// <summary>
  5277. ///
  5278. /// </summary>
  5279. [Category("Labels - ObjectListView"), DefaultValue("Lock grouping on '{0}'"), Localizable(true)]
  5280. public string MenuLabelLockGroupingOn {
  5281. get { return this.menuLabelLockGroupingOn; }
  5282. set { this.menuLabelLockGroupingOn = value; }
  5283. }
  5284. private string menuLabelLockGroupingOn = "Lock grouping on '{0}'";
  5285. /// <summary>
  5286. ///
  5287. /// </summary>
  5288. [Category("Labels - ObjectListView"), DefaultValue("Unlock grouping from '{0}'"), Localizable(true)]
  5289. public string MenuLabelUnlockGroupingOn {
  5290. get { return this.menuLabelUnlockGroupingOn; }
  5291. set { this.menuLabelUnlockGroupingOn = value; }
  5292. }
  5293. private string menuLabelUnlockGroupingOn = "Unlock grouping from '{0}'";
  5294. /// <summary>
  5295. ///
  5296. /// </summary>
  5297. [Category("Labels - ObjectListView"), DefaultValue("Turn off groups"), Localizable(true)]
  5298. public string MenuLabelTurnOffGroups {
  5299. get { return this.menuLabelTurnOffGroups; }
  5300. set { this.menuLabelTurnOffGroups = value; }
  5301. }
  5302. private string menuLabelTurnOffGroups = "Turn off groups";
  5303. /// <summary>
  5304. ///
  5305. /// </summary>
  5306. [Category("Labels - ObjectListView"), DefaultValue("Unsort"), Localizable(true)]
  5307. public string MenuLabelUnsort {
  5308. get { return this.menuLabelUnsort; }
  5309. set { this.menuLabelUnsort = value; }
  5310. }
  5311. private string menuLabelUnsort = "Unsort";
  5312. /// <summary>
  5313. ///
  5314. /// </summary>
  5315. [Category("Labels - ObjectListView"), DefaultValue("Columns"), Localizable(true)]
  5316. public string MenuLabelColumns {
  5317. get { return this.menuLabelColumns; }
  5318. set { this.menuLabelColumns = value; }
  5319. }
  5320. private string menuLabelColumns = "Columns";
  5321. /// <summary>
  5322. ///
  5323. /// </summary>
  5324. [Category("Labels - ObjectListView"), DefaultValue("Select Columns..."), Localizable(true)]
  5325. public string MenuLabelSelectColumns {
  5326. get { return this.menuLabelSelectColumns; }
  5327. set { this.menuLabelSelectColumns = value; }
  5328. }
  5329. private string menuLabelSelectColumns = "Select Columns...";
  5330. /// <summary>
  5331. /// Gets or sets the image that will be place next to the Sort Ascending command
  5332. /// </summary>
  5333. static public Bitmap SortAscendingImage = BrightIdeasSoftware.Properties.Resources.SortAscending;
  5334. /// <summary>
  5335. /// Gets or sets the image that will be placed next to the Sort Descending command
  5336. /// </summary>
  5337. static public Bitmap SortDescendingImage = BrightIdeasSoftware.Properties.Resources.SortDescending;
  5338. /// <summary>
  5339. /// Append the column selection menu items to the given menu strip.
  5340. /// </summary>
  5341. /// <param name="strip">The menu to which the items will be added.</param>
  5342. /// <param name="columnIndex"></param>
  5343. /// <returns>Return the menu to which the items were added</returns>
  5344. public virtual ToolStripDropDown MakeColumnCommandMenu(ToolStripDropDown strip, int columnIndex) {
  5345. OLVColumn column = this.GetColumn(columnIndex);
  5346. if (column == null)
  5347. return strip;
  5348. if (strip.Items.Count > 0)
  5349. strip.Items.Add(new ToolStripSeparator());
  5350. string label = String.Format(this.MenuLabelSortAscending, column.Text);
  5351. if (column.Sortable && !String.IsNullOrEmpty(label)) {
  5352. strip.Items.Add(label, ObjectListView.SortAscendingImage, (EventHandler)delegate(object sender, EventArgs args) {
  5353. this.Sort(column, SortOrder.Ascending);
  5354. });
  5355. }
  5356. label = String.Format(this.MenuLabelSortDescending, column.Text);
  5357. if (column.Sortable && !String.IsNullOrEmpty(label)) {
  5358. strip.Items.Add(label, ObjectListView.SortDescendingImage, (EventHandler)delegate(object sender, EventArgs args) {
  5359. this.Sort(column, SortOrder.Descending);
  5360. });
  5361. }
  5362. if (this.CanShowGroups) {
  5363. label = String.Format(this.MenuLabelGroupBy, column.Text);
  5364. if (column.Groupable && !String.IsNullOrEmpty(label)) {
  5365. strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
  5366. this.ShowGroups = true;
  5367. this.PrimarySortColumn = column;
  5368. this.PrimarySortOrder = SortOrder.Ascending;
  5369. this.BuildList();
  5370. });
  5371. }
  5372. }
  5373. if (this.ShowGroups) {
  5374. if (this.AlwaysGroupByColumn == column) {
  5375. label = String.Format(this.MenuLabelUnlockGroupingOn, column.Text);
  5376. if (!String.IsNullOrEmpty(label)) {
  5377. strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
  5378. this.AlwaysGroupByColumn = null;
  5379. this.AlwaysGroupBySortOrder = SortOrder.Ascending;
  5380. this.BuildList();
  5381. });
  5382. }
  5383. } else {
  5384. label = String.Format(this.MenuLabelLockGroupingOn, column.Text);
  5385. if (column.Groupable && !String.IsNullOrEmpty(label)) {
  5386. strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
  5387. this.ShowGroups = true;
  5388. this.AlwaysGroupByColumn = column;
  5389. this.AlwaysGroupBySortOrder = SortOrder.Ascending;
  5390. this.BuildList();
  5391. });
  5392. }
  5393. }
  5394. label = String.Format(this.MenuLabelTurnOffGroups, column.Text);
  5395. if (!String.IsNullOrEmpty(label)) {
  5396. strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
  5397. this.ShowGroups = false;
  5398. this.BuildList();
  5399. });
  5400. }
  5401. } else {
  5402. label = String.Format(this.MenuLabelUnsort, column.Text);
  5403. if (column.Sortable && !String.IsNullOrEmpty(label) && this.PrimarySortOrder != SortOrder.None) {
  5404. strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
  5405. this.Unsort();
  5406. });
  5407. }
  5408. }
  5409. return strip;
  5410. }
  5411. /// <summary>
  5412. /// Append the column selection menu items to the given menu strip.
  5413. /// </summary>
  5414. /// <param name="strip">The menu to which the items will be added.</param>
  5415. /// <returns>Return the menu to which the items were added</returns>
  5416. public virtual ToolStripDropDown MakeColumnSelectMenu(ToolStripDropDown strip) {
  5417. System.Diagnostics.Debug.Assert(this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None);
  5418. // Append a separator if the menu isn't empty and the last item isn't already a separator
  5419. if (strip.Items.Count > 0 && (!(strip.Items[strip.Items.Count-1] is ToolStripSeparator)))
  5420. strip.Items.Add(new ToolStripSeparator());
  5421. if (this.AllColumns.Count > 0 && this.AllColumns[0].LastDisplayIndex == -1)
  5422. this.RememberDisplayIndicies();
  5423. if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.ModelDialog) {
  5424. strip.Items.Add(this.MenuLabelSelectColumns, null, delegate(object sender, EventArgs args) {
  5425. (new ColumnSelectionForm()).OpenOn(this);
  5426. });
  5427. }
  5428. if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.Submenu) {
  5429. ToolStripMenuItem menu = new ToolStripMenuItem(this.MenuLabelColumns);
  5430. menu.DropDownItemClicked += new ToolStripItemClickedEventHandler(ColumnSelectMenu_ItemClicked);
  5431. strip.Items.Add(menu);
  5432. this.AddItemsToColumnSelectMenu(menu.DropDownItems);
  5433. }
  5434. if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.InlineMenu) {
  5435. strip.ItemClicked += new ToolStripItemClickedEventHandler(ColumnSelectMenu_ItemClicked);
  5436. strip.Closing += new ToolStripDropDownClosingEventHandler(ColumnSelectMenu_Closing);
  5437. this.AddItemsToColumnSelectMenu(strip.Items);
  5438. }
  5439. return strip;
  5440. }
  5441. /// <summary>
  5442. /// Create the menu items that will allow columns to be choosen and add them to the
  5443. /// given collection
  5444. /// </summary>
  5445. /// <param name="items"></param>
  5446. protected void AddItemsToColumnSelectMenu(ToolStripItemCollection items) {
  5447. // Sort columns by display order
  5448. List<OLVColumn> columns = new List<OLVColumn>(this.AllColumns);
  5449. columns.Sort(delegate(OLVColumn x, OLVColumn y) { return (x.LastDisplayIndex - y.LastDisplayIndex); });
  5450. // Build menu from sorted columns
  5451. foreach (OLVColumn col in columns) {
  5452. ToolStripMenuItem mi = new ToolStripMenuItem(col.Text);
  5453. mi.Checked = col.IsVisible;
  5454. mi.Tag = col;
  5455. // The 'Index' property returns -1 when the column is not visible, so if the
  5456. // column isn't visible we have to enable the item. Also the first column can't be turned off
  5457. mi.Enabled = !col.IsVisible || col.CanBeHidden;
  5458. items.Add(mi);
  5459. }
  5460. }
  5461. private void ColumnSelectMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) {
  5462. this.contextMenuStaysOpen = false;
  5463. ToolStripMenuItem menuItemClicked = (ToolStripMenuItem)e.ClickedItem;
  5464. OLVColumn col = menuItemClicked.Tag as OLVColumn;
  5465. if (col == null)
  5466. return;
  5467. menuItemClicked.Checked = !menuItemClicked.Checked;
  5468. col.IsVisible = menuItemClicked.Checked;
  5469. this.contextMenuStaysOpen = this.SelectColumnsMenuStaysOpen;
  5470. this.BeginInvoke(new MethodInvoker(this.RebuildColumns));
  5471. }
  5472. internal bool contextMenuStaysOpen;
  5473. private void ColumnSelectMenu_Closing(object sender, ToolStripDropDownClosingEventArgs e) {
  5474. e.Cancel = this.contextMenuStaysOpen && e.CloseReason == ToolStripDropDownCloseReason.ItemClicked;
  5475. this.contextMenuStaysOpen = false;
  5476. }
  5477. /// <summary>
  5478. /// Create a Filtering menu
  5479. /// </summary>
  5480. /// <param name="strip"></param>
  5481. /// <param name="columnIndex"></param>
  5482. /// <returns></returns>
  5483. public virtual ToolStripDropDown MakeFilteringMenu(ToolStripDropDown strip, int columnIndex) {
  5484. OLVColumn column = this.GetColumn(columnIndex);
  5485. if (column == null)
  5486. return strip;
  5487. FilterMenuBuilder strategy = this.FilterMenuBuildStrategy;
  5488. if (strategy == null)
  5489. return strip;
  5490. return strategy.MakeFilterMenu(strip, this, column);
  5491. }
  5492. /// <summary>
  5493. /// Override the OnColumnReordered method to do what we want
  5494. /// </summary>
  5495. /// <param name="e"></param>
  5496. protected override void OnColumnReordered(ColumnReorderedEventArgs e) {
  5497. base.OnColumnReordered(e);
  5498. // The internal logic of the .NET code behind a ENDDRAG event means that,
  5499. // at this point, the DisplayIndex's of the columns are not yet as they are
  5500. // going to be. So we have to invoke a method to run later that will remember
  5501. // what the real DisplayIndex's are.
  5502. this.BeginInvoke(new MethodInvoker(this.RememberDisplayIndicies));
  5503. }
  5504. private void RememberDisplayIndicies() {
  5505. // Remember the display indexes so we can put them back at a later date
  5506. foreach (OLVColumn x in this.AllColumns)
  5507. x.LastDisplayIndex = x.DisplayIndex;
  5508. }
  5509. /// <summary>
  5510. /// When the column widths are changing, resize the space filling columns
  5511. /// </summary>
  5512. /// <param name="sender"></param>
  5513. /// <param name="e"></param>
  5514. protected virtual void HandleColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e) {
  5515. if (this.UpdateSpaceFillingColumnsWhenDraggingColumnDivider && !this.GetColumn(e.ColumnIndex).FillsFreeSpace) {
  5516. // If the width of a column is increasing, resize any space filling columns allowing the extra
  5517. // space that the new column width is going to consume
  5518. int oldWidth = this.GetColumn(e.ColumnIndex).Width;
  5519. if (e.NewWidth > oldWidth)
  5520. this.ResizeFreeSpaceFillingColumns(this.ClientSize.Width - (e.NewWidth - oldWidth));
  5521. else
  5522. this.ResizeFreeSpaceFillingColumns();
  5523. }
  5524. }
  5525. /// <summary>
  5526. /// When the column widths change, resize the space filling columns
  5527. /// </summary>
  5528. /// <param name="sender"></param>
  5529. /// <param name="e"></param>
  5530. protected virtual void HandleColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) {
  5531. if (!this.GetColumn(e.ColumnIndex).FillsFreeSpace)
  5532. this.ResizeFreeSpaceFillingColumns();
  5533. }
  5534. /// <summary>
  5535. /// When the size of the control changes, we have to resize our space filling columns.
  5536. /// </summary>
  5537. /// <param name="sender"></param>
  5538. /// <param name="e"></param>
  5539. protected virtual void HandleLayout(object sender, LayoutEventArgs e) {
  5540. // We have to delay executing the recalculation of the columns, since virtual lists
  5541. // get terribly confused if we resize the column widths during this event.
  5542. if (!this.hasResizeColumnsHandler) {
  5543. this.hasResizeColumnsHandler = true;
  5544. Application.Idle += new EventHandler(HandleApplicationIdle_ResizeColumns);
  5545. }
  5546. }
  5547. /// <summary>
  5548. /// Resize our space filling columns so they fill any unoccupied width in the control
  5549. /// </summary>
  5550. protected virtual void ResizeFreeSpaceFillingColumns() {
  5551. this.ResizeFreeSpaceFillingColumns(this.ClientSize.Width);
  5552. }
  5553. /// <summary>
  5554. /// Resize our space filling columns so they fill any unoccupied width in the control
  5555. /// </summary>
  5556. protected virtual void ResizeFreeSpaceFillingColumns(int freeSpace) {
  5557. // It's too confusing to dynamically resize columns at design time.
  5558. if (this.DesignMode)
  5559. return;
  5560. if (this.Frozen)
  5561. return;
  5562. // Calculate the free space available
  5563. int totalProportion = 0;
  5564. List<OLVColumn> spaceFillingColumns = new List<OLVColumn>();
  5565. for (int i = 0; i < this.Columns.Count; i++) {
  5566. OLVColumn col = this.GetColumn(i);
  5567. if (col.FillsFreeSpace) {
  5568. spaceFillingColumns.Add(col);
  5569. totalProportion += col.FreeSpaceProportion;
  5570. } else
  5571. freeSpace -= col.Width;
  5572. }
  5573. freeSpace = Math.Max(0, freeSpace);
  5574. // Any space filling column that would hit it's Minimum or Maximum
  5575. // width must be treated as a fixed column.
  5576. foreach (OLVColumn col in spaceFillingColumns.ToArray()) {
  5577. int newWidth = (freeSpace * col.FreeSpaceProportion) / totalProportion;
  5578. if (col.MinimumWidth != -1 && newWidth < col.MinimumWidth)
  5579. newWidth = col.MinimumWidth;
  5580. else if (col.MaximumWidth != -1 && newWidth > col.MaximumWidth)
  5581. newWidth = col.MaximumWidth;
  5582. else
  5583. newWidth = 0;
  5584. if (newWidth > 0) {
  5585. col.Width = newWidth;
  5586. freeSpace -= newWidth;
  5587. totalProportion -= col.FreeSpaceProportion;
  5588. spaceFillingColumns.Remove(col);
  5589. }
  5590. }
  5591. // Distribute the free space between the columns
  5592. foreach (OLVColumn col in spaceFillingColumns) {
  5593. col.Width = (freeSpace * col.FreeSpaceProportion) / totalProportion;
  5594. }
  5595. }
  5596. #endregion
  5597. #region Checkboxes
  5598. /// <summary>
  5599. /// Mark the given object as indeterminate check state
  5600. /// </summary>
  5601. /// <param name="modelObject">The model object to be marked indeterminate</param>
  5602. public virtual void CheckIndeterminateObject(object modelObject) {
  5603. this.SetObjectCheckedness(modelObject, CheckState.Indeterminate);
  5604. }
  5605. /// <summary>
  5606. /// Mark the given object as checked in the list
  5607. /// </summary>
  5608. /// <param name="modelObject">The model object to be checked</param>
  5609. public virtual void CheckObject(object modelObject) {
  5610. this.SetObjectCheckedness(modelObject, CheckState.Checked);
  5611. }
  5612. /// <summary>
  5613. /// Mark the given objects as checked in the list
  5614. /// </summary>
  5615. /// <param name="modelObjects">The model object to be checked</param>
  5616. public virtual void CheckObjects(IEnumerable modelObjects) {
  5617. foreach (object model in modelObjects)
  5618. this.CheckObject(model);
  5619. }
  5620. /// <summary>
  5621. /// Put a check into the check box at the given cell
  5622. /// </summary>
  5623. /// <param name="rowObject"></param>
  5624. /// <param name="column"></param>
  5625. public virtual void CheckSubItem(object rowObject, OLVColumn column) {
  5626. if (column == null || rowObject == null || !column.CheckBoxes)
  5627. return;
  5628. column.PutCheckState(rowObject, CheckState.Checked);
  5629. this.RefreshObject(rowObject);
  5630. }
  5631. /// <summary>
  5632. /// Put an indeterminate check into the check box at the given cell
  5633. /// </summary>
  5634. /// <param name="rowObject"></param>
  5635. /// <param name="column"></param>
  5636. public virtual void CheckIndeterminateSubItem(object rowObject, OLVColumn column) {
  5637. if (column == null || rowObject == null || !column.CheckBoxes)
  5638. return;
  5639. column.PutCheckState(rowObject, CheckState.Indeterminate);
  5640. this.RefreshObject(rowObject);
  5641. }
  5642. /// <summary>
  5643. /// Return true of the given object is checked
  5644. /// </summary>
  5645. /// <param name="modelObject">The model object whose checkedness is returned</param>
  5646. /// <returns>Is the given object checked?</returns>
  5647. /// <remarks>If the given object is not in the list, this method returns false.</remarks>
  5648. public virtual bool IsChecked(object modelObject) {
  5649. OLVListItem olvi = this.ModelToItem(modelObject);
  5650. if (olvi == null)
  5651. return false;
  5652. else
  5653. return olvi.CheckState == CheckState.Checked;
  5654. }
  5655. /// <summary>
  5656. /// Return true of the given object is indeterminately checked
  5657. /// </summary>
  5658. /// <param name="modelObject">The model object whose checkedness is returned</param>
  5659. /// <returns>Is the given object indeterminately checked?</returns>
  5660. /// <remarks>If the given object is not in the list, this method returns false.</remarks>
  5661. public virtual bool IsCheckedIndeterminate(object modelObject) {
  5662. OLVListItem olvi = this.ModelToItem(modelObject);
  5663. if (olvi == null)
  5664. return false;
  5665. else
  5666. return (olvi.CheckState == CheckState.Indeterminate);
  5667. }
  5668. /// <summary>
  5669. /// Is there a check at the check box at the given cell
  5670. /// </summary>
  5671. /// <param name="rowObject"></param>
  5672. /// <param name="column"></param>
  5673. public virtual bool IsSubItemChecked(object rowObject, OLVColumn column) {
  5674. if (column != null && rowObject != null && column.CheckBoxes)
  5675. return (column.GetCheckState(rowObject) == CheckState.Checked);
  5676. else
  5677. return false;
  5678. }
  5679. /// <summary>
  5680. /// Get the checkedness of an object from the model. Returning null means the
  5681. /// model does not know and the value from the control will be used.
  5682. /// </summary>
  5683. /// <param name="modelObject"></param>
  5684. /// <returns></returns>
  5685. protected virtual CheckState? GetCheckState(Object modelObject) {
  5686. if (this.CheckStateGetter == null)
  5687. return null;
  5688. else
  5689. return this.CheckStateGetter(modelObject);
  5690. }
  5691. /// <summary>
  5692. /// Record the change of checkstate for the given object in the model.
  5693. /// This does not update the UI -- only the model
  5694. /// </summary>
  5695. /// <param name="modelObject"></param>
  5696. /// <param name="state"></param>
  5697. /// <returns>The check state that was recorded and that should be used to update
  5698. /// the control.</returns>
  5699. protected virtual CheckState PutCheckState(Object modelObject, CheckState state) {
  5700. if (this.CheckStatePutter == null)
  5701. return state;
  5702. else
  5703. return this.CheckStatePutter(modelObject, state);
  5704. }
  5705. /// <summary>
  5706. /// Change the check state of the given object to be the given state.
  5707. /// </summary>
  5708. /// <remarks>
  5709. /// If the given model object isn't in the list, we still try to remember
  5710. /// its state, in case it is referenced in the future.</remarks>
  5711. /// <param name="modelObject"></param>
  5712. /// <param name="state"></param>
  5713. protected virtual void SetObjectCheckedness(object modelObject, CheckState state) {
  5714. OLVListItem olvi = this.ModelToItem(modelObject);
  5715. // If we didn't find the given, we still try to record the check state.
  5716. if (olvi == null) {
  5717. this.PutCheckState(modelObject, state);
  5718. return;
  5719. }
  5720. if (olvi.CheckState == state)
  5721. return;
  5722. // Trigger checkbox changing event. We only need to do this for virtual
  5723. // lists, since setting CheckState triggers these events for non-virtual lists
  5724. ItemCheckEventArgs ice = new ItemCheckEventArgs(olvi.Index, state, olvi.CheckState);
  5725. this.OnItemCheck(ice);
  5726. if (ice.NewValue == olvi.CheckState)
  5727. return;
  5728. olvi.CheckState = this.PutCheckState(modelObject, state);
  5729. this.RefreshItem(olvi);
  5730. // Trigger check changed event
  5731. this.OnItemChecked(new ItemCheckedEventArgs(olvi));
  5732. }
  5733. /// <summary>
  5734. /// Toggle the checkedness of the given object. A checked object becomes
  5735. /// unchecked; an unchecked or indeterminate object becomes checked.
  5736. /// If the list has tristate checkboxes, the order is:
  5737. /// unchecked -> checked -> indeterminate -> unchecked ...
  5738. /// </summary>
  5739. /// <param name="modelObject">The model object to be checked</param>
  5740. public virtual void ToggleCheckObject(object modelObject) {
  5741. OLVListItem olvi = this.ModelToItem(modelObject);
  5742. if (olvi == null)
  5743. return;
  5744. CheckState newState = CheckState.Checked;
  5745. if (olvi.CheckState == CheckState.Checked) {
  5746. if (this.TriStateCheckBoxes)
  5747. newState = CheckState.Indeterminate;
  5748. else
  5749. newState = CheckState.Unchecked;
  5750. } else {
  5751. if (olvi.CheckState == CheckState.Indeterminate && this.TriStateCheckBoxes)
  5752. newState = CheckState.Unchecked;
  5753. }
  5754. this.SetObjectCheckedness(modelObject, newState);
  5755. }
  5756. /// <summary>
  5757. /// Toggle the check at the check box of the given cell
  5758. /// </summary>
  5759. /// <param name="rowObject"></param>
  5760. /// <param name="column"></param>
  5761. public virtual void ToggleSubItemCheckBox(object rowObject, OLVColumn column) {
  5762. CheckState currentState = column.GetCheckState(rowObject);
  5763. CheckState newState = CalculateToggledCheckState(column, currentState);
  5764. SubItemCheckingEventArgs args = new SubItemCheckingEventArgs(column, this.ModelToItem(rowObject), column.Index, currentState, newState);
  5765. this.OnSubItemChecking(args);
  5766. if (args.Canceled)
  5767. return;
  5768. switch (args.NewValue) {
  5769. case CheckState.Checked:
  5770. this.CheckSubItem(rowObject, column);
  5771. break;
  5772. case CheckState.Indeterminate:
  5773. this.CheckIndeterminateSubItem(rowObject, column);
  5774. break;
  5775. case CheckState.Unchecked:
  5776. this.UncheckSubItem(rowObject, column);
  5777. break;
  5778. }
  5779. }
  5780. CheckState CalculateToggledCheckState(OLVColumn column, CheckState currentState) {
  5781. switch (currentState) {
  5782. case CheckState.Checked: return column.TriStateCheckBoxes ? CheckState.Indeterminate : CheckState.Unchecked;
  5783. case CheckState.Indeterminate: return CheckState.Unchecked;
  5784. case CheckState.Unchecked:
  5785. default: return CheckState.Checked;
  5786. }
  5787. }
  5788. /// <summary>
  5789. /// Mark the given object as unchecked in the list
  5790. /// </summary>
  5791. /// <param name="modelObject">The model object to be unchecked</param>
  5792. public virtual void UncheckObject(object modelObject) {
  5793. this.SetObjectCheckedness(modelObject, CheckState.Unchecked);
  5794. }
  5795. /// <summary>
  5796. /// Mark the given objects as unchecked in the list
  5797. /// </summary>
  5798. /// <param name="modelObjects">The model object to be checked</param>
  5799. public virtual void UncheckObjects(IEnumerable modelObjects) {
  5800. foreach (object model in modelObjects)
  5801. this.UncheckObject(model);
  5802. }
  5803. /// <summary>
  5804. /// Uncheck the check at the given cell
  5805. /// </summary>
  5806. /// <param name="rowObject"></param>
  5807. /// <param name="column"></param>
  5808. public virtual void UncheckSubItem(object rowObject, OLVColumn column) {
  5809. if (column == null || rowObject == null || !column.CheckBoxes)
  5810. return;
  5811. column.PutCheckState(rowObject, CheckState.Unchecked);
  5812. this.RefreshObject(rowObject);
  5813. }
  5814. #endregion
  5815. #region OLV accessing
  5816. /// <summary>
  5817. /// Return the column at the given index
  5818. /// </summary>
  5819. /// <param name="index">Index of the column to be returned</param>
  5820. /// <returns>An OLVColumn</returns>
  5821. public virtual OLVColumn GetColumn(int index) {
  5822. return (OLVColumn)this.Columns[index];
  5823. }
  5824. /// <summary>
  5825. /// Return the column at the given title.
  5826. /// </summary>
  5827. /// <param name="name">Name of the column to be returned</param>
  5828. /// <returns>An OLVColumn</returns>
  5829. public virtual OLVColumn GetColumn(string name) {
  5830. foreach (ColumnHeader column in this.Columns) {
  5831. if (column.Text == name)
  5832. return (OLVColumn)column;
  5833. }
  5834. return null;
  5835. }
  5836. /// <summary>
  5837. /// Return a collection of columns that are visible to the given view.
  5838. /// Only Tile and Details have columns; all other views have 0 columns.
  5839. /// </summary>
  5840. /// <param name="view">Which view are the columns being calculate for?</param>
  5841. /// <returns>A list of columns</returns>
  5842. public virtual List<OLVColumn> GetFilteredColumns(View view) {
  5843. // For both detail and tile view, the first column must be included. Normally, we would
  5844. // use the ColumnHeader.Index property, but if the header is not currently part of a ListView
  5845. // that property returns -1. So, we track the index of
  5846. // the column header, and always include the first header.
  5847. switch (view) {
  5848. case View.Details:
  5849. case View.Tile:
  5850. int index = 0;
  5851. return this.AllColumns.FindAll(delegate(OLVColumn x) {
  5852. return (index++ == 0) || x.IsVisible;
  5853. });
  5854. //return this.AllColumns.FindAll(delegate(OLVColumn x) { return (index++ == 0) || x.IsTileViewColumn; });
  5855. default:
  5856. return new List<OLVColumn>();
  5857. }
  5858. }
  5859. /// <summary>
  5860. /// Return the number of items in the list
  5861. /// </summary>
  5862. /// <returns>the number of items in the list</returns>
  5863. public virtual int GetItemCount() {
  5864. return this.Items.Count;
  5865. }
  5866. /// <summary>
  5867. /// Return the item at the given index
  5868. /// </summary>
  5869. /// <param name="index">Index of the item to be returned</param>
  5870. /// <returns>An OLVListItem</returns>
  5871. public virtual OLVListItem GetItem(int index) {
  5872. if (index >= 0 && index < this.GetItemCount())
  5873. return (OLVListItem)this.Items[index];
  5874. else
  5875. return null;
  5876. }
  5877. /// <summary>
  5878. /// Return the model object at the given index
  5879. /// </summary>
  5880. /// <param name="index">Index of the model object to be returned</param>
  5881. /// <returns>A model object</returns>
  5882. public virtual object GetModelObject(int index) {
  5883. OLVListItem item = this.GetItem(index);
  5884. if (item == null)
  5885. return null;
  5886. else
  5887. return item.RowObject;
  5888. }
  5889. /// <summary>
  5890. /// Find the item and column that are under the given co-ords
  5891. /// </summary>
  5892. /// <param name="x">X co-ord</param>
  5893. /// <param name="y">Y co-ord</param>
  5894. /// <param name="selectedColumn">The column under the given point</param>
  5895. /// <returns>The item under the given point. Can be null.</returns>
  5896. public virtual OLVListItem GetItemAt(int x, int y, out OLVColumn selectedColumn) {
  5897. selectedColumn = null;
  5898. ListViewHitTestInfo info = this.HitTest(x, y);
  5899. if (info.Item == null)
  5900. return null;
  5901. if (info.SubItem != null) {
  5902. int subItemIndex = info.Item.SubItems.IndexOf(info.SubItem);
  5903. selectedColumn = this.GetColumn(subItemIndex);
  5904. }
  5905. return (OLVListItem)info.Item;
  5906. }
  5907. /// <summary>
  5908. /// Return the sub item at the given index/column
  5909. /// </summary>
  5910. /// <param name="index">Index of the item to be returned</param>
  5911. /// <param name="columnIndex">Index of the subitem to be returned</param>
  5912. /// <returns>An OLVListSubItem</returns>
  5913. public virtual OLVListSubItem GetSubItem(int index, int columnIndex) {
  5914. OLVListItem olvi = this.GetItem(index);
  5915. if (olvi == null)
  5916. return null;
  5917. else
  5918. return olvi.GetSubItem(columnIndex);
  5919. }
  5920. #endregion
  5921. #region Object manipulation
  5922. /// <summary>
  5923. /// Ensure that the given model object is visible
  5924. /// </summary>
  5925. /// <param name="modelObject">The model object to be revealed</param>
  5926. public virtual void EnsureModelVisible(Object modelObject) {
  5927. int index = this.IndexOf(modelObject);
  5928. if (index >= 0)
  5929. this.EnsureVisible(index);
  5930. }
  5931. /// <summary>
  5932. /// Return the model object of the row that is selected or null if there is no selection or more than one selection
  5933. /// </summary>
  5934. /// <returns>Model object or null</returns>
  5935. [Obsolete("Use SelectedObject property instead of this method")]
  5936. public virtual object GetSelectedObject() {
  5937. return this.SelectedObject;
  5938. }
  5939. /// <summary>
  5940. /// Return the model objects of the rows that are selected or an empty collection if there is no selection
  5941. /// </summary>
  5942. /// <returns>ArrayList</returns>
  5943. [Obsolete("Use SelectedObjects property instead of this method")]
  5944. public virtual ArrayList GetSelectedObjects() {
  5945. return ObjectListView.EnumerableToArray(this.SelectedObjects, false);
  5946. }
  5947. /// <summary>
  5948. /// Return the model object of the row that is checked or null if no row is checked
  5949. /// or more than one row is checked
  5950. /// </summary>
  5951. /// <returns>Model object or null</returns>
  5952. /// <remarks>Use CheckedObject property instead of this method</remarks>
  5953. [Obsolete("Use CheckedObject property instead of this method")]
  5954. public virtual object GetCheckedObject() {
  5955. return this.CheckedObject;
  5956. }
  5957. /// <summary>
  5958. /// Get the collection of model objects that are checked.
  5959. /// </summary>
  5960. /// <remarks>Use CheckedObjects property instead of this method</remarks>
  5961. [Obsolete("Use CheckedObjects property instead of this method")]
  5962. public virtual ArrayList GetCheckedObjects() {
  5963. return ObjectListView.EnumerableToArray(this.CheckedObjects, false);
  5964. }
  5965. /// <summary>
  5966. /// Find the given model object within the listview and return its index
  5967. /// </summary>
  5968. /// <param name="modelObject">The model object to be found</param>
  5969. /// <returns>The index of the object. -1 means the object was not present</returns>
  5970. public virtual int IndexOf(Object modelObject) {
  5971. for (int i = 0; i < this.GetItemCount(); i++) {
  5972. if (this.GetModelObject(i).Equals(modelObject))
  5973. return i;
  5974. }
  5975. return -1;
  5976. }
  5977. /// <summary>
  5978. /// Update the ListViewItem with the data from its associated model.
  5979. /// </summary>
  5980. /// <remarks>This method does not resort or regroup the view. It simply updates
  5981. /// the displayed data of the given item</remarks>
  5982. public virtual void RefreshItem(OLVListItem olvi) {
  5983. olvi.UseItemStyleForSubItems = true;
  5984. olvi.SubItems.Clear();
  5985. this.FillInValues(olvi, olvi.RowObject);
  5986. this.PostProcessOneRow(olvi.Index, this.GetItemIndexInDisplayOrder(olvi), olvi);
  5987. }
  5988. /// <summary>
  5989. /// Update the rows that are showing the given objects
  5990. /// </summary>
  5991. /// <remarks>This method does not resort or regroup the view.</remarks>
  5992. public virtual void RefreshObject(object modelObject) {
  5993. this.RefreshObjects(new object[] { modelObject });
  5994. }
  5995. /// <summary>
  5996. /// Update the rows that are showing the given objects
  5997. /// </summary>
  5998. /// <remarks>
  5999. /// <para>This method does not resort or regroup the view.</para>
  6000. /// <para>This method can safely be called from background threads.</para>
  6001. /// </remarks>
  6002. public virtual void RefreshObjects(IList modelObjects) {
  6003. if (this.InvokeRequired) {
  6004. this.Invoke((MethodInvoker)delegate { this.RefreshObjects(modelObjects); });
  6005. return;
  6006. }
  6007. foreach (object modelObject in modelObjects) {
  6008. OLVListItem olvi = this.ModelToItem(modelObject);
  6009. if (olvi != null)
  6010. this.RefreshItem(olvi);
  6011. }
  6012. }
  6013. /// <summary>
  6014. /// Update the rows that are selected
  6015. /// </summary>
  6016. /// <remarks>This method does not resort or regroup the view.</remarks>
  6017. public virtual void RefreshSelectedObjects() {
  6018. foreach (ListViewItem lvi in this.SelectedItems)
  6019. this.RefreshItem((OLVListItem)lvi);
  6020. }
  6021. /// <summary>
  6022. /// Scroll the listview so that the given group is at the top.
  6023. /// </summary>
  6024. /// <param name="lvg">The group to be revealed</param>
  6025. /// <remarks><para>
  6026. /// If the group is already visible, the list will still be scrolled to move
  6027. /// the group to the top, if that is possible.
  6028. /// </para>
  6029. /// <para>This only works when the list is showing groups (obviously)</para>
  6030. /// </remarks>
  6031. public virtual void EnsureGroupVisible(ListViewGroup lvg) {
  6032. if (!this.ShowGroups || lvg == null)
  6033. return;
  6034. int groupIndex = this.Groups.IndexOf(lvg);
  6035. if (groupIndex <= 0) {
  6036. // There is no easy way to scroll back to the beginning of the list
  6037. int delta = 0 - NativeMethods.GetScrollPosition(this, false);
  6038. NativeMethods.Scroll(this, 0, delta);
  6039. } else {
  6040. // Find the display rectangle of the last item in the previous group
  6041. ListViewGroup previousGroup = this.Groups[groupIndex - 1];
  6042. ListViewItem lastItemInGroup = previousGroup.Items[previousGroup.Items.Count - 1];
  6043. Rectangle r = this.GetItemRect(lastItemInGroup.Index);
  6044. // Scroll so that the last item of the previous group is just out of sight,
  6045. // which will make the desired group header visible.
  6046. int delta = r.Y + r.Height / 2;
  6047. NativeMethods.Scroll(this, 0, delta);
  6048. }
  6049. }
  6050. /// <summary>
  6051. /// Select the row that is displaying the given model object, in addition to any current selection.
  6052. /// </summary>
  6053. /// <param name="modelObject">The object to be selected</param>
  6054. /// <remarks>Use the <see cref="SelectedObject"/> property to deselect all other rows</remarks>
  6055. public virtual void SelectObject(object modelObject) {
  6056. this.SelectObject(modelObject, false);
  6057. }
  6058. /// <summary>
  6059. /// Select the row that is displaying the given model object, in addition to any current selection.
  6060. /// </summary>
  6061. /// <param name="modelObject">The object to be selected</param>
  6062. /// <param name="setFocus">Should the object be focused as well?</param>
  6063. /// <remarks>Use the <see cref="SelectedObject"/> property to deselect all other rows</remarks>
  6064. public virtual void SelectObject(object modelObject, bool setFocus) {
  6065. OLVListItem olvi = this.ModelToItem(modelObject);
  6066. if (olvi != null) {
  6067. olvi.Selected = true;
  6068. if (setFocus)
  6069. olvi.Focused = true;
  6070. }
  6071. }
  6072. /// <summary>
  6073. /// Select the rows that is displaying any of the given model object. All other rows are deselected.
  6074. /// </summary>
  6075. /// <param name="modelObjects">A collection of model objects</param>
  6076. public virtual void SelectObjects(IList modelObjects) {
  6077. this.SelectedIndices.Clear();
  6078. if (modelObjects == null)
  6079. return;
  6080. foreach (object modelObject in modelObjects) {
  6081. OLVListItem olvi = this.ModelToItem(modelObject);
  6082. if (olvi != null)
  6083. olvi.Selected = true;
  6084. }
  6085. }
  6086. #endregion
  6087. #region Freezing
  6088. /// <summary>
  6089. /// Freeze the listview so that it no longer updates itself.
  6090. /// </summary>
  6091. /// <remarks>Freeze()/Unfreeze() calls nest correctly</remarks>
  6092. public virtual void Freeze() {
  6093. freezeCount++;
  6094. this.OnFreezing(new FreezeEventArgs(freezeCount));
  6095. }
  6096. /// <summary>
  6097. /// Unfreeze the listview. If this call is the outermost Unfreeze(),
  6098. /// the contents of the listview will be rebuilt.
  6099. /// </summary>
  6100. /// <remarks>Freeze()/Unfreeze() calls nest correctly</remarks>
  6101. public virtual void Unfreeze() {
  6102. if (freezeCount <= 0)
  6103. return;
  6104. freezeCount--;
  6105. if (freezeCount == 0)
  6106. DoUnfreeze();
  6107. this.OnFreezing(new FreezeEventArgs(freezeCount));
  6108. }
  6109. /// <summary>
  6110. /// Do the actual work required when the listview is unfrozen
  6111. /// </summary>
  6112. protected virtual void DoUnfreeze() {
  6113. this.ResizeFreeSpaceFillingColumns();
  6114. this.BuildList();
  6115. }
  6116. #endregion
  6117. #region Column sorting
  6118. /// <summary>
  6119. /// Sort the items by the last sort column and order
  6120. /// </summary>
  6121. new public void Sort() {
  6122. this.Sort(this.LastSortColumn, this.LastSortOrder);
  6123. }
  6124. /// <summary>
  6125. /// Sort the items in the list view by the values in the given column and the last sort order
  6126. /// </summary>
  6127. /// <param name="columnToSortName">The name of the column whose values will be used for the sorting</param>
  6128. public virtual void Sort(string columnToSortName) {
  6129. this.Sort(this.GetColumn(columnToSortName), this.LastSortOrder);
  6130. }
  6131. /// <summary>
  6132. /// Sort the items in the list view by the values in the given column and the last sort order
  6133. /// </summary>
  6134. /// <param name="columnToSortIndex">The index of the column whose values will be used for the sorting</param>
  6135. public virtual void Sort(int columnToSortIndex) {
  6136. if (columnToSortIndex >= 0 && columnToSortIndex < this.Columns.Count)
  6137. this.Sort(this.GetColumn(columnToSortIndex), this.LastSortOrder);
  6138. }
  6139. /// <summary>
  6140. /// Sort the items in the list view by the values in the given column and the last sort order
  6141. /// </summary>
  6142. /// <param name="columnToSort">The column whose values will be used for the sorting</param>
  6143. public virtual void Sort(OLVColumn columnToSort) {
  6144. if (this.InvokeRequired) {
  6145. this.Invoke((MethodInvoker)delegate { this.Sort(columnToSort); });
  6146. } else {
  6147. this.Sort(columnToSort, this.LastSortOrder);
  6148. }
  6149. }
  6150. /// <summary>
  6151. /// Sort the items in the list view by the values in the given column and by the given order.
  6152. /// </summary>
  6153. /// <param name="columnToSort">The column whose values will be used for the sorting.
  6154. /// If null, the first column will be used.</param>
  6155. /// <param name="order">The ordering to be used for sorting. If this is None,
  6156. /// this.Sorting and then SortOrder.Ascending will be used</param>
  6157. /// <remarks>If ShowGroups is true, the rows will be grouped by the given column.
  6158. /// If AlwaysGroupsByColumn is not null, the rows will be grouped by that column,
  6159. /// and the rows within each group will be sorted by the given column.</remarks>
  6160. public virtual void Sort(OLVColumn columnToSort, SortOrder order) {
  6161. if (this.InvokeRequired) {
  6162. this.Invoke((MethodInvoker)delegate { this.Sort(columnToSort, order); });
  6163. } else {
  6164. this.DoSort(columnToSort, order);
  6165. this.PostProcessRows();
  6166. }
  6167. }
  6168. private void DoSort(OLVColumn columnToSort, SortOrder order) {
  6169. // Sanity checks
  6170. if (this.GetItemCount() == 0 || this.Columns.Count == 0)
  6171. return;
  6172. // Fill in default values, if the parameters don't make sense
  6173. if (this.ShowGroups) {
  6174. columnToSort = columnToSort ?? this.GetColumn(0);
  6175. if (order == SortOrder.None) {
  6176. order = this.Sorting;
  6177. if (order == SortOrder.None)
  6178. order = SortOrder.Ascending;
  6179. }
  6180. }
  6181. // Give the world a chance to fiddle with or completely avoid the sorting process
  6182. BeforeSortingEventArgs args = this.BuildBeforeSortingEventArgs(columnToSort, order);
  6183. this.OnBeforeSorting(args);
  6184. if (args.Canceled)
  6185. return;
  6186. // Virtual lists don't preserve selection, so we have to do it specifically
  6187. // THINK: Do we need to preserve focus too?
  6188. IList selection = new ArrayList();
  6189. if (this.VirtualMode)
  6190. selection = this.SelectedObjects;
  6191. this.ClearHotItem();
  6192. // Finally, do the work of sorting, unless an event handler has already done the sorting for us
  6193. if (!args.Handled) {
  6194. // Sanity checks
  6195. if (args.ColumnToSort != null && args.SortOrder != SortOrder.None) {
  6196. if (this.ShowGroups)
  6197. this.BuildGroups(args.ColumnToGroupBy, args.GroupByOrder, args.ColumnToSort, args.SortOrder,
  6198. args.SecondaryColumnToSort, args.SecondarySortOrder);
  6199. else if (this.CustomSorter != null)
  6200. this.CustomSorter(columnToSort, order);
  6201. else
  6202. this.ListViewItemSorter = new ColumnComparer(args.ColumnToSort, args.SortOrder,
  6203. args.SecondaryColumnToSort, args.SecondarySortOrder);
  6204. }
  6205. }
  6206. if (this.ShowSortIndicators)
  6207. this.ShowSortIndicator(args.ColumnToSort, args.SortOrder);
  6208. this.LastSortColumn = args.ColumnToSort;
  6209. this.LastSortOrder = args.SortOrder;
  6210. if (selection.Count > 0)
  6211. this.SelectedObjects = selection;
  6212. this.RefreshHotItem();
  6213. this.OnAfterSorting(new AfterSortingEventArgs(args));
  6214. }
  6215. /// <summary>
  6216. /// Put a sort indicator next to the text of the sort column
  6217. /// </summary>
  6218. public virtual void ShowSortIndicator() {
  6219. if (this.ShowSortIndicators && this.LastSortOrder != SortOrder.None)
  6220. this.ShowSortIndicator(this.LastSortColumn, this.LastSortOrder);
  6221. }
  6222. /// <summary>
  6223. /// Put a sort indicator next to the text of the given given column
  6224. /// </summary>
  6225. /// <param name="columnToSort">The column to be marked</param>
  6226. /// <param name="sortOrder">The sort order in effect on that column</param>
  6227. protected virtual void ShowSortIndicator(OLVColumn columnToSort, SortOrder sortOrder) {
  6228. int imageIndex = -1;
  6229. if (!NativeMethods.HasBuiltinSortIndicators()) {
  6230. // If we can't use builtin image, we have to make and then locate the index of the
  6231. // sort indicator we want to use. SortOrder.None doesn't show an image.
  6232. if (this.SmallImageList == null || !this.SmallImageList.Images.ContainsKey(SORT_INDICATOR_UP_KEY))
  6233. MakeSortIndicatorImages();
  6234. if (sortOrder == SortOrder.Ascending)
  6235. imageIndex = this.SmallImageList.Images.IndexOfKey(SORT_INDICATOR_UP_KEY);
  6236. else if (sortOrder == SortOrder.Descending)
  6237. imageIndex = this.SmallImageList.Images.IndexOfKey(SORT_INDICATOR_DOWN_KEY);
  6238. }
  6239. // Set the image for each column
  6240. for (int i = 0; i < this.Columns.Count; i++) {
  6241. if (columnToSort != null && i == columnToSort.Index)
  6242. NativeMethods.SetColumnImage(this, i, sortOrder, imageIndex);
  6243. else
  6244. NativeMethods.SetColumnImage(this, i, SortOrder.None, -1);
  6245. }
  6246. }
  6247. /// <summary>
  6248. /// The name of the image used when a column is sorted ascending
  6249. /// </summary>
  6250. /// <remarks>This image is only used on pre-XP systems. System images are used for XP and later</remarks>
  6251. public const string SORT_INDICATOR_UP_KEY = "sort-indicator-up";
  6252. /// <summary>
  6253. /// The name of the image used when a column is sorted descending
  6254. /// </summary>
  6255. /// <remarks>This image is only used on pre-XP systems. System images are used for XP and later</remarks>
  6256. public const string SORT_INDICATOR_DOWN_KEY = "sort-indicator-down";
  6257. /// <summary>
  6258. /// If the sort indicator images don't already exist, this method will make and install them
  6259. /// </summary>
  6260. protected virtual void MakeSortIndicatorImages() {
  6261. // Don't mess with the image list in design mode
  6262. if (this.DesignMode)
  6263. return;
  6264. ImageList il = this.SmallImageList;
  6265. if (il == null) {
  6266. il = new ImageList();
  6267. il.ImageSize = new Size(16, 16);
  6268. il.ColorDepth = ColorDepth.Depth32Bit;
  6269. }
  6270. // This arrangement of points works well with (16,16) images, and OK with others
  6271. int midX = il.ImageSize.Width / 2;
  6272. int midY = (il.ImageSize.Height / 2) - 1;
  6273. int deltaX = midX - 2;
  6274. int deltaY = deltaX / 2;
  6275. if (il.Images.IndexOfKey(SORT_INDICATOR_UP_KEY) == -1) {
  6276. Point pt1 = new Point(midX - deltaX, midY + deltaY);
  6277. Point pt2 = new Point(midX, midY - deltaY - 1);
  6278. Point pt3 = new Point(midX + deltaX, midY + deltaY);
  6279. il.Images.Add(SORT_INDICATOR_UP_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 }));
  6280. }
  6281. if (il.Images.IndexOfKey(SORT_INDICATOR_DOWN_KEY) == -1) {
  6282. Point pt1 = new Point(midX - deltaX, midY - deltaY);
  6283. Point pt2 = new Point(midX, midY + deltaY);
  6284. Point pt3 = new Point(midX + deltaX, midY - deltaY);
  6285. il.Images.Add(SORT_INDICATOR_DOWN_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 }));
  6286. }
  6287. this.SmallImageList = il;
  6288. }
  6289. private Bitmap MakeTriangleBitmap(Size sz, Point[] pts) {
  6290. Bitmap bm = new Bitmap(sz.Width, sz.Height);
  6291. Graphics g = Graphics.FromImage(bm);
  6292. g.FillPolygon(new SolidBrush(Color.Gray), pts);
  6293. return bm;
  6294. }
  6295. /// <summary>
  6296. /// Remove any sorting and revert to the given order of the model objects
  6297. /// </summary>
  6298. public virtual void Unsort() {
  6299. this.ShowGroups = false;
  6300. this.PrimarySortColumn = null;
  6301. this.PrimarySortOrder = SortOrder.None;
  6302. this.BuildList();
  6303. }
  6304. #endregion
  6305. #region Utilities
  6306. /// <summary>
  6307. /// Do the actual work of creating the given list of groups
  6308. /// </summary>
  6309. /// <param name="groups"></param>
  6310. protected virtual void CreateGroups(IList<OLVGroup> groups) {
  6311. this.Groups.Clear();
  6312. // The group must be added before it is given items, otherwise an exception is thrown (is this documented?)
  6313. foreach (OLVGroup group in groups) {
  6314. group.InsertGroupOldStyle(this);
  6315. group.SetItemsOldStyle();
  6316. }
  6317. this.ResizeLastGroup();
  6318. }
  6319. internal void ResizeLastGroup()
  6320. {
  6321. // Don't mess with the control in design mode
  6322. if (this.IsDesignMode)
  6323. return;
  6324. // Sanity checks
  6325. if (this.GetItemCount() == 0 || !this.ShowGroups || this.Groups.Count == 0)
  6326. return;
  6327. // Get the last group and make sure we know which OLVGroup it came from
  6328. ListViewGroup grp = this.Groups[this.Groups.Count-1];
  6329. OLVGroup olvGroup = grp.Tag as OLVGroup;
  6330. if (olvGroup == null)
  6331. return;
  6332. int height = this.GetItem(0).Bounds.Height;
  6333. // Is the Horizontal scrollbar visible
  6334. if (NativeMethods.HasHorizontalScrollBar(this))
  6335. height += SystemInformation.HorizontalScrollBarHeight;
  6336. if (this.SpaceBetweenGroups > height)
  6337. return;
  6338. NativeMethods.LVGROUPMETRICS metrics = new NativeMethods.LVGROUPMETRICS();
  6339. metrics.cbSize = ((uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUPMETRICS)));
  6340. metrics.mask = (uint)GroupMetricsMask.LVGMF_BORDERSIZE;
  6341. metrics.Bottom = (uint)height;
  6342. NativeMethods.SetGroupMetrics(this, olvGroup.GroupId, metrics);
  6343. }
  6344. /// <summary>
  6345. /// For some reason, UseItemStyleForSubItems doesn't work for the colors
  6346. /// when owner drawing the list, so we have to specifically give each subitem
  6347. /// the desired colors
  6348. /// </summary>
  6349. /// <param name="olvi">The item whose subitems are to be corrected</param>
  6350. /// <remarks>Cells drawn via BaseRenderer don't need this, but it is needed
  6351. /// when an owner drawn cell uses DrawDefault=true</remarks>
  6352. protected virtual void CorrectSubItemColors(ListViewItem olvi) {
  6353. }
  6354. /// <summary>
  6355. /// Fill in the given OLVListItem with values of the given row
  6356. /// </summary>
  6357. /// <param name="lvi">the OLVListItem that is to be stuff with values</param>
  6358. /// <param name="rowObject">the model object from which values will be taken</param>
  6359. protected virtual void FillInValues(OLVListItem lvi, object rowObject) {
  6360. if (this.Columns.Count == 0)
  6361. return;
  6362. OLVListSubItem subItem = this.MakeSubItem(rowObject, this.GetColumn(0));
  6363. lvi.SubItems[0] = subItem;
  6364. lvi.ImageSelector = subItem.ImageSelector;
  6365. // Only Details and Tile views have subitems
  6366. if (this.View == View.Details) {
  6367. for (int i = 1; i < this.Columns.Count; i++) {
  6368. lvi.SubItems.Add(this.MakeSubItem(rowObject, this.GetColumn(i)));
  6369. }
  6370. } else {
  6371. if (this.View == View.Tile) {
  6372. for (int i = 1; i < this.Columns.Count; i++) {
  6373. OLVColumn column = this.GetColumn(i);
  6374. if (column.IsTileViewColumn)
  6375. lvi.SubItems.Add(this.MakeSubItem(rowObject, column));
  6376. }
  6377. }
  6378. }
  6379. // Give the item the same font/colors as the control
  6380. lvi.Font = this.Font;
  6381. lvi.BackColor = this.BackColor;
  6382. lvi.ForeColor = this.ForeColor;
  6383. // Set the check state of the row, if we are showing check boxes
  6384. if (this.CheckBoxes) {
  6385. CheckState? state = this.GetCheckState(lvi.RowObject);
  6386. if (state.HasValue)
  6387. lvi.CheckState = (CheckState)state;
  6388. }
  6389. // Give the RowFormatter a chance to mess with the item
  6390. if (this.RowFormatter != null) {
  6391. this.RowFormatter(lvi);
  6392. }
  6393. }
  6394. private OLVListSubItem MakeSubItem(object rowObject, OLVColumn column) {
  6395. object cellValue = column.GetValue(rowObject);
  6396. OLVListSubItem subItem = new OLVListSubItem(cellValue,
  6397. column.ValueToString(cellValue),
  6398. column.GetImage(rowObject));
  6399. if (this.UseHyperlinks && column.Hyperlink) {
  6400. IsHyperlinkEventArgs args = new IsHyperlinkEventArgs();
  6401. args.ListView = this;
  6402. args.Model = rowObject;
  6403. args.Column = column;
  6404. args.Text = subItem.Text;
  6405. args.Url = subItem.Text;
  6406. this.OnIsHyperlink(args);
  6407. subItem.Url = args.Url;
  6408. }
  6409. return subItem;
  6410. }
  6411. private void ApplyHyperlinkStyle(int rowIndex, OLVListItem olvi) {
  6412. olvi.UseItemStyleForSubItems = false;
  6413. // If subitem 0 is given a back color, the item back color is changed too.
  6414. // So we have to remember it here so we can used it even if subitem 0 is changed.
  6415. Color itemBackColor = olvi.BackColor;
  6416. for (int i = 0; i < this.Columns.Count; i++) {
  6417. OLVListSubItem subItem = olvi.GetSubItem(i);
  6418. if (subItem == null)
  6419. continue;
  6420. OLVColumn column = this.GetColumn(i);
  6421. subItem.BackColor = itemBackColor;
  6422. if (column.Hyperlink && !String.IsNullOrEmpty(subItem.Url)) {
  6423. if (this.IsUrlVisited(subItem.Url))
  6424. this.ApplyCellStyle(olvi, i, this.HyperlinkStyle.Visited);
  6425. else
  6426. this.ApplyCellStyle(olvi, i, this.HyperlinkStyle.Normal);
  6427. }
  6428. }
  6429. }
  6430. /// <summary>
  6431. /// Make sure the ListView has the extended style that says to display subitem images.
  6432. /// </summary>
  6433. /// <remarks>This method must be called after any .NET call that update the extended styles
  6434. /// since they seem to erase this setting.</remarks>
  6435. protected virtual void ForceSubItemImagesExStyle() {
  6436. // Virtual lists can't show subitem images natively, so don't turn on this flag
  6437. if (!this.VirtualMode)
  6438. NativeMethods.ForceSubItemImagesExStyle(this);
  6439. }
  6440. /// <summary>
  6441. /// Convert the given image selector to an index into our image list.
  6442. /// Return -1 if that's not possible
  6443. /// </summary>
  6444. /// <param name="imageSelector"></param>
  6445. /// <returns>Index of the image in the imageList, or -1</returns>
  6446. protected virtual int GetActualImageIndex(Object imageSelector) {
  6447. if (imageSelector == null)
  6448. return -1;
  6449. if (imageSelector is Int32)
  6450. return (int)imageSelector;
  6451. String imageSelectorAsString = imageSelector as String;
  6452. if (imageSelectorAsString != null && this.SmallImageList != null)
  6453. return this.SmallImageList.Images.IndexOfKey(imageSelectorAsString);
  6454. return -1;
  6455. }
  6456. /// <summary>
  6457. /// Return the tooltip that should be shown when the mouse is hovered over the given column
  6458. /// </summary>
  6459. /// <param name="columnIndex">The column index whose tool tip is to be fetched</param>
  6460. /// <returns>A string or null if no tool tip is to be shown</returns>
  6461. public virtual String GetHeaderToolTip(int columnIndex) {
  6462. OLVColumn column = this.GetColumn(columnIndex);
  6463. if (column == null)
  6464. return null;
  6465. String tooltip = column.ToolTipText;
  6466. if (this.HeaderToolTipGetter != null)
  6467. tooltip = this.HeaderToolTipGetter(column);
  6468. return tooltip;
  6469. }
  6470. /// <summary>
  6471. /// Return the tooltip that should be shown when the mouse is hovered over the given cell
  6472. /// </summary>
  6473. /// <param name="columnIndex">The column index whose tool tip is to be fetched</param>
  6474. /// <param name="rowIndex">The row index whose tool tip is to be fetched</param>
  6475. /// <returns>A string or null if no tool tip is to be shown</returns>
  6476. public virtual String GetCellToolTip(int columnIndex, int rowIndex) {
  6477. if (this.CellToolTipGetter != null)
  6478. return this.CellToolTipGetter(this.GetColumn(columnIndex), this.GetModelObject(rowIndex));
  6479. // Show the URL in the tooltip if it's different to the text
  6480. if (columnIndex >= 0) {
  6481. OLVListSubItem subItem = this.GetSubItem(rowIndex, columnIndex);
  6482. if (subItem != null && !String.IsNullOrEmpty(subItem.Url) && subItem.Url != subItem.Text &&
  6483. this.HotCellHitLocation == HitTestLocation.Text)
  6484. return subItem.Url;
  6485. }
  6486. return null;
  6487. }
  6488. /// <summary>
  6489. /// Return the OLVListItem that displays the given model object
  6490. /// </summary>
  6491. /// <param name="modelObject">The modelObject whose item is to be found</param>
  6492. /// <returns>The OLVListItem that displays the model, or null</returns>
  6493. /// <remarks>This method has O(n) performance.</remarks>
  6494. public virtual OLVListItem ModelToItem(object modelObject) {
  6495. if (modelObject == null)
  6496. return null;
  6497. foreach (OLVListItem olvi in this.Items) {
  6498. if (olvi.RowObject != null && olvi.RowObject.Equals(modelObject))
  6499. return olvi;
  6500. }
  6501. return null;
  6502. }
  6503. /// <summary>
  6504. /// Do the work required after the items in a listview have been created
  6505. /// </summary>
  6506. protected virtual void PostProcessRows() {
  6507. // If this method is called during a BeginUpdate/EndUpdate pair, changes to the
  6508. // Items collection are cached. Getting the Count flushes that cache.
  6509. int count = this.Items.Count;
  6510. int i = 0;
  6511. if (this.ShowGroups) {
  6512. foreach (ListViewGroup group in this.Groups) {
  6513. foreach (OLVListItem olvi in group.Items) {
  6514. this.PostProcessOneRow(olvi.Index, i, olvi);
  6515. i++;
  6516. }
  6517. }
  6518. } else {
  6519. foreach (OLVListItem olvi in this.Items) {
  6520. this.PostProcessOneRow(olvi.Index, i, olvi);
  6521. i++;
  6522. }
  6523. }
  6524. }
  6525. /// <summary>
  6526. /// Do the work required after one item in a listview have been created
  6527. /// </summary>
  6528. protected virtual void PostProcessOneRow(int rowIndex, int displayIndex, OLVListItem olvi) {
  6529. if (this.UseAlternatingBackColors && this.View == View.Details) {
  6530. if (displayIndex % 2 == 1) {
  6531. olvi.BackColor = this.AlternateRowBackColorOrDefault;
  6532. } else {
  6533. olvi.BackColor = this.BackColor;
  6534. }
  6535. }
  6536. if (this.ShowImagesOnSubItems && !this.VirtualMode) {
  6537. this.SetSubItemImages(rowIndex, olvi);
  6538. }
  6539. if (this.UseHyperlinks) {
  6540. this.ApplyHyperlinkStyle(rowIndex, olvi);
  6541. }
  6542. this.TriggerFormatRowEvent(rowIndex, displayIndex, olvi);
  6543. }
  6544. /// <summary>
  6545. /// Prepare the listview to show alternate row backcolors
  6546. /// </summary>
  6547. /// <remarks>We cannot rely on lvi.Index in this method.
  6548. /// In a straight list, lvi.Index is the display index, and can be used to determine
  6549. /// whether the row should be colored. But when organised by groups, lvi.Index is not
  6550. /// useable because it still refers to the position in the overall list, not the display order.
  6551. ///</remarks>
  6552. [Obsolete("This method is no longer used. Override PostProcessOneRow() to achieve a similar result")]
  6553. protected virtual void PrepareAlternateBackColors() {
  6554. }
  6555. /// <summary>
  6556. /// Setup all subitem images on all rows
  6557. /// </summary>
  6558. [Obsolete("This method is not longer maintained and will be removed", false)]
  6559. protected virtual void SetAllSubItemImages() {
  6560. //if (!this.ShowImagesOnSubItems || this.OwnerDraw)
  6561. // return;
  6562. //this.ForceSubItemImagesExStyle();
  6563. //for (int rowIndex = 0; rowIndex < this.GetItemCount(); rowIndex++)
  6564. // SetSubItemImages(rowIndex, this.GetItem(rowIndex));
  6565. }
  6566. /// <summary>
  6567. /// Tell the underlying list control which images to show against the subitems
  6568. /// </summary>
  6569. /// <param name="rowIndex">the index at which the item occurs</param>
  6570. /// <param name="item">the item whose subitems are to be set</param>
  6571. protected virtual void SetSubItemImages(int rowIndex, OLVListItem item) {
  6572. this.SetSubItemImages(rowIndex, item, false);
  6573. }
  6574. /// <summary>
  6575. /// Tell the underlying list control which images to show against the subitems
  6576. /// </summary>
  6577. /// <param name="rowIndex">the index at which the item occurs</param>
  6578. /// <param name="item">the item whose subitems are to be set</param>
  6579. /// <param name="shouldClearImages">will existing images be cleared if no new image is provided?</param>
  6580. protected virtual void SetSubItemImages(int rowIndex, OLVListItem item, bool shouldClearImages) {
  6581. if (!this.ShowImagesOnSubItems || this.OwnerDraw)
  6582. return;
  6583. for (int i = 1; i < item.SubItems.Count; i++) {
  6584. this.SetSubItemImage(rowIndex, i, item.GetSubItem(i), shouldClearImages);
  6585. }
  6586. }
  6587. /// <summary>
  6588. /// Set the subitem image natively
  6589. /// </summary>
  6590. /// <param name="rowIndex"></param>
  6591. /// <param name="subItemIndex"></param>
  6592. /// <param name="subItem"></param>
  6593. /// <param name="shouldClearImages"></param>
  6594. public virtual void SetSubItemImage(int rowIndex, int subItemIndex, OLVListSubItem subItem, bool shouldClearImages) {
  6595. int imageIndex = this.GetActualImageIndex(subItem.ImageSelector);
  6596. if (shouldClearImages || imageIndex != -1)
  6597. NativeMethods.SetSubItemImage(this, rowIndex, subItemIndex, imageIndex);
  6598. }
  6599. /// <summary>
  6600. /// Take ownership of the 'objects' collection. This separats our collection from the source.
  6601. /// </summary>
  6602. /// <remarks>
  6603. /// <para>
  6604. /// This method
  6605. /// separates the 'objects' instance variable from its source, so that any AddObject/RemoveObject
  6606. /// calls will modify our collection and not the original colleciton.
  6607. /// </para>
  6608. /// <para>
  6609. /// This method has the intentional side-effect of converting our list of objects to an ArrayList.
  6610. /// </para>
  6611. /// </remarks>
  6612. protected virtual void TakeOwnershipOfObjects() {
  6613. if (this.isOwnerOfObjects)
  6614. return;
  6615. this.isOwnerOfObjects = true;
  6616. this.objects = ObjectListView.EnumerableToArray(this.objects, true);
  6617. }
  6618. /// <summary>
  6619. /// Trigger FormatRow and possibly FormatCell events for the given item
  6620. /// </summary>
  6621. /// <param name="rowIndex"></param>
  6622. /// <param name="displayIndex"></param>
  6623. /// <param name="olvi"></param>
  6624. protected virtual void TriggerFormatRowEvent(int rowIndex, int displayIndex, OLVListItem olvi) {
  6625. FormatRowEventArgs args = new FormatRowEventArgs();
  6626. args.ListView = this;
  6627. args.RowIndex = rowIndex;
  6628. args.DisplayIndex = displayIndex;
  6629. args.Item = olvi;
  6630. args.UseCellFormatEvents = this.UseCellFormatEvents;
  6631. this.OnFormatRow(args);
  6632. if (args.UseCellFormatEvents && this.View == View.Details) {
  6633. // If a cell isn't given its own color, it should use the color of the item.
  6634. // However, there is a bug in the .NET framework where the cell are given
  6635. // the color of the ListView instead. So we have to explicitly give each
  6636. // cell the back color that it should have.
  6637. olvi.UseItemStyleForSubItems = false;
  6638. Color backColor = olvi.BackColor;
  6639. for (int i = 0; i < this.Columns.Count; i++) {
  6640. olvi.SubItems[i].BackColor = backColor;
  6641. }
  6642. // Fire one event per cell
  6643. FormatCellEventArgs args2 = new FormatCellEventArgs();
  6644. args2.ListView = this;
  6645. args2.RowIndex = rowIndex;
  6646. args2.DisplayIndex = displayIndex;
  6647. args2.Item = olvi;
  6648. for (int i = 0; i < this.Columns.Count; i++) {
  6649. args2.ColumnIndex = i;
  6650. args2.Column = this.GetColumn(i);
  6651. args2.SubItem = olvi.GetSubItem(i);
  6652. this.OnFormatCell(args2);
  6653. }
  6654. }
  6655. }
  6656. #endregion
  6657. #region ISupportInitialize Members
  6658. void ISupportInitialize.BeginInit() {
  6659. this.Frozen = true;
  6660. }
  6661. void ISupportInitialize.EndInit() {
  6662. if (this.RowHeight != -1) {
  6663. this.SmallImageList = this.SmallImageList;
  6664. if (this.CheckBoxes)
  6665. this.InitializeStateImageList();
  6666. }
  6667. if (this.UseCustomSelectionColors)
  6668. this.EnableCustomSelectionColors();
  6669. if (this.UseSubItemCheckBoxes || (this.VirtualMode && this.CheckBoxes))
  6670. this.SetupSubItemCheckBoxes();
  6671. this.Frozen = false;
  6672. }
  6673. #endregion
  6674. #region Image list manipulation
  6675. /// <summary>
  6676. /// Update our externally visible image list so it holds the same images as our shadow list, but sized correctly
  6677. /// </summary>
  6678. private void SetupBaseImageList() {
  6679. // If a row height hasn't been set, or an image list has been give which is the required size, just assign it
  6680. if (rowHeight == -1 ||
  6681. this.View != View.Details ||
  6682. (this.shadowedImageList != null && this.shadowedImageList.ImageSize.Height == rowHeight))
  6683. this.BaseSmallImageList = this.shadowedImageList;
  6684. else {
  6685. int width = (this.shadowedImageList == null ? 16 : this.shadowedImageList.ImageSize.Width);
  6686. this.BaseSmallImageList = this.MakeResizedImageList(width, rowHeight, shadowedImageList);
  6687. }
  6688. }
  6689. /// <summary>
  6690. /// Return a copy of the given source image list, where each image has been resized to be height x height in size.
  6691. /// If source is null, an empty image list of the given size is returned
  6692. /// </summary>
  6693. /// <param name="width">Height and width of the new images</param>
  6694. /// <param name="height">Height and width of the new images</param>
  6695. /// <param name="source">Source of the images (can be null)</param>
  6696. /// <returns>A new image list</returns>
  6697. private ImageList MakeResizedImageList(int width, int height, ImageList source) {
  6698. ImageList il = new ImageList();
  6699. il.ImageSize = new Size(width, height);
  6700. il.ColorDepth = ColorDepth.Depth32Bit;
  6701. // If there's nothing to copy, just return the new list
  6702. if (source == null)
  6703. return il;
  6704. il.TransparentColor = source.TransparentColor;
  6705. il.ColorDepth = source.ColorDepth;
  6706. // Fill the imagelist with resized copies from the source
  6707. for (int i = 0; i < source.Images.Count; i++) {
  6708. Bitmap bm = this.MakeResizedImage(width, height, source.Images[i], source.TransparentColor);
  6709. il.Images.Add(bm);
  6710. }
  6711. // Give each image the same key it has in the original
  6712. foreach (String key in source.Images.Keys) {
  6713. il.Images.SetKeyName(source.Images.IndexOfKey(key), key);
  6714. }
  6715. return il;
  6716. }
  6717. /// <summary>
  6718. /// Return a bitmap of the given height x height, which shows the given image, centred.
  6719. /// </summary>
  6720. /// <param name="width">Height and width of new bitmap</param>
  6721. /// <param name="height">Height and width of new bitmap</param>
  6722. /// <param name="image">Image to be centred</param>
  6723. /// <param name="transparent">The background color</param>
  6724. /// <returns>A new bitmap</returns>
  6725. private Bitmap MakeResizedImage(int width, int height, Image image, Color transparent) {
  6726. Bitmap bm = new Bitmap(width, height);
  6727. Graphics g = Graphics.FromImage(bm);
  6728. g.Clear(transparent);
  6729. int x = Math.Max(0, (bm.Size.Width - image.Size.Width) / 2);
  6730. int y = Math.Max(0, (bm.Size.Height - image.Size.Height) / 2);
  6731. g.DrawImage(image, x, y, image.Size.Width, image.Size.Height);
  6732. return bm;
  6733. }
  6734. /// <summary>
  6735. /// Initialize the state image list with the required checkbox images
  6736. /// </summary>
  6737. protected virtual void InitializeStateImageList() {
  6738. if (this.DesignMode)
  6739. return;
  6740. if (this.StateImageList == null) {
  6741. this.StateImageList = new ImageList();
  6742. this.StateImageList.ImageSize = new Size(16, 16);
  6743. this.StateImageList.ColorDepth = ColorDepth.Depth32Bit;
  6744. }
  6745. if (this.RowHeight != -1 &&
  6746. this.View == View.Details &&
  6747. this.StateImageList.ImageSize.Height != this.RowHeight) {
  6748. this.StateImageList = new ImageList();
  6749. this.StateImageList.ImageSize = new Size(16, this.RowHeight);
  6750. this.StateImageList.ColorDepth = ColorDepth.Depth32Bit;
  6751. }
  6752. if (!this.CheckBoxes)
  6753. return;
  6754. // The internal logic of ListView cycles through the state images when the primary
  6755. // checkbox is clicked. So we have to get exactly the right number of images in the
  6756. // image list.
  6757. if (this.StateImageList.Images.Count == 0)
  6758. this.AddCheckStateBitmap(this.StateImageList, UNCHECKED_KEY, CheckBoxState.UncheckedNormal);
  6759. if (this.StateImageList.Images.Count <= 1)
  6760. this.AddCheckStateBitmap(this.StateImageList, CHECKED_KEY, CheckBoxState.CheckedNormal);
  6761. if (this.TriStateCheckBoxes && this.StateImageList.Images.Count <= 2)
  6762. this.AddCheckStateBitmap(this.StateImageList, INDETERMINATE_KEY, CheckBoxState.MixedNormal);
  6763. else {
  6764. if (this.StateImageList.Images.ContainsKey(INDETERMINATE_KEY))
  6765. this.StateImageList.Images.RemoveByKey(INDETERMINATE_KEY);
  6766. }
  6767. }
  6768. /// <summary>
  6769. /// The name of the image used when a check box is checked
  6770. /// </summary>
  6771. public const string CHECKED_KEY = "checkbox-checked";
  6772. /// <summary>
  6773. /// The name of the image used when a check box is unchecked
  6774. /// </summary>
  6775. public const string UNCHECKED_KEY = "checkbox-unchecked";
  6776. /// <summary>
  6777. /// The name of the image used when a check box is Indeterminate
  6778. /// </summary>
  6779. public const string INDETERMINATE_KEY = "checkbox-indeterminate";
  6780. /// <summary>
  6781. /// Setup this control so it can display check boxes on subitems
  6782. /// (or primary checkboxes in virtual mode)
  6783. /// </summary>
  6784. /// <remarks>This gives the ListView a small image list, if it doesn't already have one.</remarks>
  6785. public virtual void SetupSubItemCheckBoxes() {
  6786. this.ShowImagesOnSubItems = true;
  6787. if (this.SmallImageList == null || !this.SmallImageList.Images.ContainsKey(CHECKED_KEY))
  6788. this.InitializeCheckBoxImages();
  6789. }
  6790. /// <summary>
  6791. /// Make sure the small image list for this control has checkbox images
  6792. /// (used for sub-item checkboxes).
  6793. /// </summary>
  6794. /// <remarks>This gives the ListView a small image list, if it doesn't already have one.</remarks>
  6795. protected virtual void InitializeCheckBoxImages() {
  6796. // Don't mess with the image list in design mode
  6797. if (this.DesignMode)
  6798. return;
  6799. ImageList il = this.SmallImageList;
  6800. if (il == null) {
  6801. il = new ImageList();
  6802. il.ImageSize = new Size(16, 16);
  6803. il.ColorDepth = ColorDepth.Depth32Bit;
  6804. }
  6805. this.AddCheckStateBitmap(il, CHECKED_KEY, CheckBoxState.CheckedNormal);
  6806. this.AddCheckStateBitmap(il, UNCHECKED_KEY, CheckBoxState.UncheckedNormal);
  6807. this.AddCheckStateBitmap(il, INDETERMINATE_KEY, CheckBoxState.MixedNormal);
  6808. this.SmallImageList = il;
  6809. }
  6810. private void AddCheckStateBitmap(ImageList il, string key, CheckBoxState boxState) {
  6811. Bitmap b = new Bitmap(il.ImageSize.Width, il.ImageSize.Height);
  6812. Graphics g = Graphics.FromImage(b);
  6813. g.Clear(il.TransparentColor);
  6814. Point location = new Point(b.Width / 2 - 5, b.Height / 2 - 6);
  6815. CheckBoxRenderer.DrawCheckBox(g, location, boxState);
  6816. il.Images.Add(key, b);
  6817. }
  6818. #endregion
  6819. #region Owner drawing
  6820. /// <summary>
  6821. /// Owner draw the column header
  6822. /// </summary>
  6823. /// <param name="e"></param>
  6824. protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e) {
  6825. e.DrawDefault = true;
  6826. base.OnDrawColumnHeader(e);
  6827. }
  6828. /// <summary>
  6829. /// Owner draw the item
  6830. /// </summary>
  6831. /// <param name="e"></param>
  6832. protected override void OnDrawItem(DrawListViewItemEventArgs e) {
  6833. if (this.View == View.Details)
  6834. e.DrawDefault = false;
  6835. else {
  6836. if (this.ItemRenderer == null)
  6837. e.DrawDefault = true;
  6838. else {
  6839. Object row = ((OLVListItem)e.Item).RowObject;
  6840. e.DrawDefault = !this.ItemRenderer.RenderItem(e, e.Graphics, e.Bounds, row);
  6841. }
  6842. }
  6843. if (e.DrawDefault)
  6844. base.OnDrawItem(e);
  6845. }
  6846. /// <summary>
  6847. /// Owner draw a single subitem
  6848. /// </summary>
  6849. /// <param name="e"></param>
  6850. protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e) {
  6851. //System.Diagnostics.Debug.WriteLine(String.Format("OnDrawSubItem ({0}, {1})", e.ItemIndex, e.ColumnIndex));
  6852. // Don't try to do owner drawing at design time
  6853. if (this.DesignMode) {
  6854. e.DrawDefault = true;
  6855. return;
  6856. }
  6857. // Calculate where the subitem should be drawn
  6858. Rectangle r = e.Bounds;
  6859. // Optimize drawing by only redrawing subitems that touch the area that was damaged
  6860. //if (!r.IntersectsWith(this.lastUpdateRectangle))
  6861. // return;
  6862. // Get the special renderer for this column. If there isn't one, use the default draw mechanism.
  6863. OLVColumn column = this.GetColumn(e.ColumnIndex);
  6864. IRenderer renderer = column.Renderer ?? this.DefaultRenderer;
  6865. // Get a graphics context for the renderer to use.
  6866. // But we have more complications. Virtual lists have a nasty habit of drawing column 0
  6867. // whenever there is any mouse move events over a row, and doing it in an un-double-buffered manner,
  6868. // which results in nasty flickers! There are also some unbuffered draw when a mouse is first
  6869. // hovered over column 0 of a normal row. So, to avoid all complications,
  6870. // we always manually double-buffer the drawing.
  6871. // Except with Mono, which doesn't seem to handle double buffering at all :-(
  6872. Graphics g = e.Graphics;
  6873. BufferedGraphics buffer = null;
  6874. bool avoidFlickerMode = true; // set this to false to see the problems with flicker
  6875. if (avoidFlickerMode) {
  6876. buffer = BufferedGraphicsManager.Current.Allocate(e.Graphics, r);
  6877. g = buffer.Graphics;
  6878. }
  6879. g.TextRenderingHint = ObjectListView.TextRenderingHint;
  6880. g.SmoothingMode = ObjectListView.SmoothingMode;
  6881. // Finally, give the renderer a chance to draw something
  6882. e.DrawDefault = !renderer.RenderSubItem(e, g, r, ((OLVListItem)e.Item).RowObject);
  6883. if (buffer != null) {
  6884. if (!e.DrawDefault)
  6885. buffer.Render();
  6886. buffer.Dispose();
  6887. }
  6888. }
  6889. #endregion
  6890. #region OnEvent Handling
  6891. /// <summary>
  6892. /// We need the click count in the mouse up event, but that is always 1.
  6893. /// So we have to remember the click count from the preceding mouse down event.
  6894. /// </summary>
  6895. /// <param name="e"></param>
  6896. protected override void OnMouseDown(MouseEventArgs e) {
  6897. this.lastMouseDownClickCount = e.Clicks;
  6898. base.OnMouseDown(e);
  6899. }
  6900. private int lastMouseDownClickCount;
  6901. /// <summary>
  6902. /// When the mouse leaves the control, remove any hot item highlighting
  6903. /// </summary>
  6904. /// <param name="e"></param>
  6905. protected override void OnMouseLeave(EventArgs e) {
  6906. base.OnMouseLeave(e);
  6907. if (!this.Created)
  6908. return;
  6909. this.UpdateHotItem(new Point(-1, -1));
  6910. }
  6911. // We could change the hot item on the mouse hover event, but it looks wrong.
  6912. //protected override void OnMouseHover(EventArgs e)
  6913. //{
  6914. // base.OnMouseHover(e);
  6915. // this.UpdateHotItem(this.PointToClient(Cursor.Position));
  6916. //}
  6917. /// <summary>
  6918. /// When the mouse moves, we might need to change the hot item.
  6919. /// </summary>
  6920. /// <param name="e"></param>
  6921. protected override void OnMouseMove(MouseEventArgs e) {
  6922. base.OnMouseMove(e);
  6923. if (!this.Created)
  6924. return;
  6925. CellOverEventArgs args = new CellOverEventArgs();
  6926. this.BuildCellEvent(args, e.Location);
  6927. this.OnCellOver(args);
  6928. this.MouseMoveHitTest = args.HitTest;
  6929. if (!args.Handled) {
  6930. this.UpdateHotItem(args.HitTest);
  6931. }
  6932. }
  6933. /// <summary>
  6934. /// Check to see if we need to start editing a cell
  6935. /// </summary>
  6936. /// <param name="e"></param>
  6937. protected override void OnMouseUp(MouseEventArgs e) {
  6938. base.OnMouseUp(e);
  6939. if (!this.Created)
  6940. return;
  6941. if (e.Button == MouseButtons.Right) {
  6942. this.OnRightMouseUp(e);
  6943. return;
  6944. }
  6945. // What event should we listen for to start cell editing?
  6946. // ------------------------------------------------------
  6947. //
  6948. // We can't use OnMouseClick, OnMouseDoubleClick, OnClick, or OnDoubleClick
  6949. // since they are not triggered for clicks on subitems without Full Row Select.
  6950. //
  6951. // We could use OnMouseDown, but selecting rows is done in OnMouseUp. This means
  6952. // that if we start the editing during OnMouseDown, the editor will automatically
  6953. // lose focus when mouse up happens.
  6954. //
  6955. // Tell the world about a cell click. If someone handles it, don't do anything else
  6956. CellClickEventArgs args = new CellClickEventArgs();
  6957. this.BuildCellEvent(args, e.Location);
  6958. args.ClickCount = this.lastMouseDownClickCount;
  6959. this.OnCellClick(args);
  6960. if (args.Handled)
  6961. return;
  6962. // Did the user click a hyperlink?
  6963. if (this.UseHyperlinks &&
  6964. args.HitTest.HitTestLocation == HitTestLocation.Text &&
  6965. args.SubItem != null &&
  6966. !String.IsNullOrEmpty(args.SubItem.Url)) {
  6967. // We have to delay the running of this process otherwise we can generate
  6968. // a series of MouseUp events (don't ask me why)
  6969. this.BeginInvoke((MethodInvoker)delegate { ProcessHyperlinkClicked(args); });
  6970. }
  6971. // No one handled it so check to see if we should start editing.
  6972. // We only start the edit if the user clicked on the image or text.
  6973. if (this.ShouldStartCellEdit(e) &&
  6974. args.HitTest.HitTestLocation != HitTestLocation.Nothing) {
  6975. // We don't edit the primary column by single clicks -- only subitems.
  6976. if (this.CellEditActivation != CellEditActivateMode.SingleClick || args.ColumnIndex > 0)
  6977. this.EditSubItem(args.Item, args.ColumnIndex);
  6978. }
  6979. }
  6980. /// <summary>
  6981. /// Tell the world that a hyperlink was clicked and if the event isn't handled,
  6982. /// do the default processing.
  6983. /// </summary>
  6984. /// <param name="e"></param>
  6985. protected virtual void ProcessHyperlinkClicked(CellClickEventArgs e) {
  6986. HyperlinkClickedEventArgs args = new HyperlinkClickedEventArgs();
  6987. args.HitTest = e.HitTest;
  6988. args.ListView = this;
  6989. args.Location = new Point(-1, -1);
  6990. args.Item = e.Item;
  6991. args.SubItem = e.SubItem;
  6992. args.Model = e.Model;
  6993. args.ColumnIndex = e.ColumnIndex;
  6994. args.Column = e.Column;
  6995. args.RowIndex = e.RowIndex;
  6996. args.ModifierKeys = Control.ModifierKeys;
  6997. args.Url = e.SubItem.Url;
  6998. this.OnHyperlinkClicked(args);
  6999. if (!args.Handled) {
  7000. this.StandardHyperlinkClickedProcessing(args);
  7001. }
  7002. }
  7003. /// <summary>
  7004. /// Do the default processing for a hyperlink clicked event, which
  7005. /// is to try and open the url.
  7006. /// </summary>
  7007. /// <param name="args"></param>
  7008. protected virtual void StandardHyperlinkClickedProcessing(HyperlinkClickedEventArgs args) {
  7009. Cursor originalCursor = this.Cursor;
  7010. try {
  7011. this.Cursor = Cursors.WaitCursor;
  7012. System.Diagnostics.Process.Start(args.Url);
  7013. } catch (Win32Exception) {
  7014. System.Media.SystemSounds.Beep.Play();
  7015. // ignore it
  7016. } finally {
  7017. this.Cursor = originalCursor;
  7018. }
  7019. this.MarkUrlVisited(args.Url);
  7020. this.RefreshHotItem();
  7021. }
  7022. /// <summary>
  7023. /// The user right clicked on the control
  7024. /// </summary>
  7025. /// <param name="e"></param>
  7026. protected virtual void OnRightMouseUp(MouseEventArgs e) {
  7027. CellRightClickEventArgs args = new CellRightClickEventArgs();
  7028. this.BuildCellEvent(args, e.Location);
  7029. this.OnCellRightClick(args);
  7030. if (!args.Handled) {
  7031. if (args.MenuStrip != null) {
  7032. args.MenuStrip.Show(this, args.Location);
  7033. }
  7034. }
  7035. }
  7036. private void BuildCellEvent(CellEventArgs args, Point location) {
  7037. OlvListViewHitTestInfo hitTest = this.OlvHitTest(location.X, location.Y);
  7038. args.HitTest = hitTest;
  7039. args.ListView = this;
  7040. args.Location = location;
  7041. args.Item = hitTest.Item;
  7042. args.SubItem = hitTest.SubItem;
  7043. args.Model = hitTest.RowObject;
  7044. args.ColumnIndex = hitTest.ColumnIndex;
  7045. args.Column = hitTest.Column;
  7046. if (hitTest.Item != null)
  7047. args.RowIndex = hitTest.Item.Index;
  7048. args.ModifierKeys = Control.ModifierKeys;
  7049. // In non-details view, we want any hit on an item to act as if it was a hit
  7050. // on column 0 -- which, effectively, it was.
  7051. if (args.Item != null && args.ListView.View != View.Details) {
  7052. args.ColumnIndex = 0;
  7053. args.Column = args.ListView.GetColumn(0);
  7054. args.SubItem = args.Item.GetSubItem(0);
  7055. }
  7056. }
  7057. /// <summary>
  7058. /// This method is called every time a row is selected or deselected. This can be
  7059. /// a pain if the user shift-clicks 100 rows. We override this method so we can
  7060. /// trigger one event for any number of select/deselects that come from one user action
  7061. /// </summary>
  7062. /// <param name="e"></param>
  7063. protected override void OnSelectedIndexChanged(EventArgs e) {
  7064. base.OnSelectedIndexChanged(e);
  7065. // If we haven't already scheduled an event, schedule it to be triggered
  7066. // By using idle time, we will wait until all select events for the same
  7067. // user action have finished before triggering the event.
  7068. if (!this.hasIdleHandler) {
  7069. this.hasIdleHandler = true;
  7070. Application.Idle += new EventHandler(HandleApplicationIdle);
  7071. }
  7072. }
  7073. /// <summary>
  7074. /// Called when the handle of the underlying control is created
  7075. /// </summary>
  7076. /// <param name="e"></param>
  7077. protected override void OnHandleCreated(EventArgs e) {
  7078. base.OnHandleCreated(e);
  7079. this.Invoke((MethodInvoker)this.OnControlCreated);
  7080. }
  7081. /// <summary>
  7082. /// This method is called after the control has been fully created.
  7083. /// </summary>
  7084. protected virtual void OnControlCreated() {
  7085. // Force the header control to be created when the listview handle is
  7086. HeaderControl hc = this.HeaderControl;
  7087. hc.WordWrap = this.HeaderWordWrap;
  7088. // Make sure any overlays that are set on the hot item style take effect
  7089. this.HotItemStyle = this.HotItemStyle;
  7090. // Arrange for any group images to be installed after the control is created
  7091. int x = NativeMethods.SetGroupImageList(this, this.GroupImageList);
  7092. this.UseExplorerTheme = this.UseExplorerTheme;
  7093. this.RememberDisplayIndicies();
  7094. }
  7095. #endregion
  7096. #region Cell editing
  7097. /// <summary>
  7098. /// Should we start editing the cell in response to the given mouse button event?
  7099. /// </summary>
  7100. /// <param name="e"></param>
  7101. /// <returns></returns>
  7102. protected virtual bool ShouldStartCellEdit(MouseEventArgs e) {
  7103. if (this.IsCellEditing)
  7104. return false;
  7105. if (e.Button != MouseButtons.Left)
  7106. return false;
  7107. if ((Control.ModifierKeys & (Keys.Shift | Keys.Control | Keys.Alt)) != 0)
  7108. return false;
  7109. if (this.lastMouseDownClickCount == 1 && this.CellEditActivation == CellEditActivateMode.SingleClick)
  7110. return true;
  7111. return (this.lastMouseDownClickCount == 2 && this.CellEditActivation == CellEditActivateMode.DoubleClick);
  7112. }
  7113. /// <summary>
  7114. /// Handle a key press on this control. We specifically look for F2 which edits the primary column,
  7115. /// or a Tab character during an edit operation, which tries to start editing on the next (or previous) cell.
  7116. /// </summary>
  7117. /// <param name="keyData"></param>
  7118. /// <returns></returns>
  7119. protected override bool ProcessDialogKey(Keys keyData) {
  7120. if (this.IsCellEditing)
  7121. return this.CellEditKeyEngine.HandleKey(this, keyData);
  7122. // Treat F2 as a request to edit the primary column
  7123. if (keyData == Keys.F2) {
  7124. this.EditSubItem((OLVListItem)this.FocusedItem, 0);
  7125. return base.ProcessDialogKey(keyData);
  7126. }
  7127. // Treat Ctrl-C as Copy To Clipboard.
  7128. if (this.CopySelectionOnControlC && keyData == (Keys.C | Keys.Control)) {
  7129. this.CopySelectionToClipboard();
  7130. return true;
  7131. }
  7132. // Treat Ctrl-A as Select All.
  7133. if (this.SelectAllOnControlA && keyData == (Keys.A | Keys.Control)) {
  7134. this.SelectAll();
  7135. return true;
  7136. }
  7137. return base.ProcessDialogKey(keyData);
  7138. }
  7139. /// <summary>
  7140. /// Begin an edit operation on the given cell.
  7141. /// </summary>
  7142. /// <remarks>This performs various sanity checks and passes off the real work to StartCellEdit().</remarks>
  7143. /// <param name="item">The row to be edited</param>
  7144. /// <param name="subItemIndex">The index of the cell to be edited</param>
  7145. public virtual void EditSubItem(OLVListItem item, int subItemIndex) {
  7146. if (item == null)
  7147. return;
  7148. if (subItemIndex < 0 && subItemIndex >= item.SubItems.Count)
  7149. return;
  7150. if (this.CellEditActivation == CellEditActivateMode.None)
  7151. return;
  7152. if (!this.GetColumn(subItemIndex).IsEditable)
  7153. return;
  7154. this.StartCellEdit(item, subItemIndex);
  7155. }
  7156. /// <summary>
  7157. /// Really start an edit operation on a given cell. The parameters are assumed to be sane.
  7158. /// </summary>
  7159. /// <param name="item">The row to be edited</param>
  7160. /// <param name="subItemIndex">The index of the cell to be edited</param>
  7161. public virtual void StartCellEdit(OLVListItem item, int subItemIndex) {
  7162. OLVColumn column = this.GetColumn(subItemIndex);
  7163. Rectangle r = this.CalculateCellEditorBounds(item, subItemIndex);
  7164. Control c = this.GetCellEditor(item, subItemIndex);
  7165. c.Bounds = r;
  7166. // Try to align the control as the column is aligned. Not all controls support this property
  7167. Munger.PutProperty(c, "TextAlign", column.TextAlign);
  7168. // Give the control the value from the model
  7169. this.SetControlValue(c, column.GetValue(item.RowObject), column.GetStringValue(item.RowObject));
  7170. // Give the outside world the chance to munge with the process
  7171. this.cellEditEventArgs = new CellEditEventArgs(column, c, r, item, subItemIndex);
  7172. this.OnCellEditStarting(this.cellEditEventArgs);
  7173. if (this.cellEditEventArgs.Cancel)
  7174. return;
  7175. // The event handler may have completely changed the control, so we need to remember it
  7176. this.cellEditor = this.cellEditEventArgs.Control;
  7177. // If the control isn't the height of the cell, centre it vertically. We don't
  7178. // need to do this when in Tile view.
  7179. if (this.View != View.Tile && this.cellEditor.Height != r.Height)
  7180. this.cellEditor.Top += (r.Height - this.cellEditor.Height) / 2;
  7181. this.Invalidate();
  7182. this.Controls.Add(this.cellEditor);
  7183. this.ConfigureControl();
  7184. this.PauseAnimations(true);
  7185. }
  7186. private Control cellEditor;
  7187. internal CellEditEventArgs cellEditEventArgs;
  7188. /// <summary>
  7189. /// Calculate the bounds of the edit control for the given item/column
  7190. /// </summary>
  7191. /// <param name="item"></param>
  7192. /// <param name="subItemIndex"></param>
  7193. /// <returns></returns>
  7194. public Rectangle CalculateCellEditorBounds(OLVListItem item, int subItemIndex) {
  7195. Rectangle r;
  7196. if (this.View == View.Details)
  7197. r = item.GetSubItemBounds(subItemIndex);
  7198. else
  7199. r = this.GetItemRect(item.Index, ItemBoundsPortion.Label);
  7200. if (this.OwnerDraw)
  7201. return CalculateCellEditorBoundsOwnerDrawn(item, subItemIndex, r);
  7202. else
  7203. return CalculateCellEditorBoundsStandard(item, subItemIndex, r);
  7204. }
  7205. /// <summary>
  7206. /// Calculate the bounds of the edit control for the given item/column, when the listview
  7207. /// is being owner drawn.
  7208. /// </summary>
  7209. /// <param name="item"></param>
  7210. /// <param name="subItemIndex"></param>
  7211. /// <param name="r"></param>
  7212. /// <returns>A rectangle that is the bounds of the cell editor</returns>
  7213. protected Rectangle CalculateCellEditorBoundsOwnerDrawn(OLVListItem item, int subItemIndex, Rectangle r) {
  7214. IRenderer renderer = null;
  7215. if (this.View == View.Details)
  7216. renderer = this.GetColumn(subItemIndex).Renderer ?? this.DefaultRenderer;
  7217. else
  7218. renderer = this.ItemRenderer;
  7219. if (renderer == null)
  7220. return r;
  7221. else {
  7222. using (Graphics g = this.CreateGraphics()) {
  7223. return renderer.GetEditRectangle(g, r, item, subItemIndex);
  7224. }
  7225. }
  7226. }
  7227. /// <summary>
  7228. /// Calculate the bounds of the edit control for the given item/column, when the listview
  7229. /// is not being owner drawn.
  7230. /// </summary>
  7231. /// <param name="item"></param>
  7232. /// <param name="subItemIndex"></param>
  7233. /// <param name="cellBounds"></param>
  7234. /// <returns>A rectangle that is the bounds of the cell editor</returns>
  7235. protected Rectangle CalculateCellEditorBoundsStandard(OLVListItem item, int subItemIndex, Rectangle cellBounds) {
  7236. if (this.View != View.Details)
  7237. return cellBounds;//
  7238. // Allow for image (if there is one)
  7239. int offset = 0;
  7240. object subItemImageSelector = item.ImageSelector;
  7241. if (subItemIndex > 0)
  7242. subItemImageSelector = ((OLVListSubItem)item.SubItems[subItemIndex]).ImageSelector;
  7243. if (this.GetActualImageIndex(subItemImageSelector) != -1) {
  7244. offset += this.SmallImageSize.Width + 2;
  7245. }
  7246. // Allow for checkbox
  7247. if (this.CheckBoxes && this.StateImageList != null && subItemIndex == 0) {
  7248. offset += this.StateImageList.ImageSize.Width + 2;
  7249. }
  7250. // Allow for indent (first column only)
  7251. if (subItemIndex == 0 && item.IndentCount > 0) {
  7252. offset += (this.SmallImageSize.Width * item.IndentCount);
  7253. }
  7254. // Do the adjustment
  7255. if (offset > 0) {
  7256. cellBounds.X += offset;
  7257. cellBounds.Width -= offset;
  7258. }
  7259. return cellBounds;
  7260. }
  7261. /// <summary>
  7262. /// Try to give the given value to the provided control. Fall back to assigning a string
  7263. /// if the value assignment fails.
  7264. /// </summary>
  7265. /// <param name="control">A control</param>
  7266. /// <param name="value">The value to be given to the control</param>
  7267. /// <param name="stringValue">The string to be given if the value doesn't work</param>
  7268. protected virtual void SetControlValue(Control control, Object value, String stringValue) {
  7269. // Handle combobox explicitly
  7270. if (control is ComboBox) {
  7271. ComboBox cb = ((ComboBox)control);
  7272. if (cb.Created)
  7273. cb.SelectedValue = value;
  7274. else
  7275. this.BeginInvoke(new MethodInvoker(delegate {
  7276. cb.SelectedValue = value;
  7277. }));
  7278. return;
  7279. }
  7280. if (Munger.PutProperty(control, "Value", value))
  7281. return;
  7282. // There wasn't a Value property, or we couldn't set it, so set the text instead
  7283. try {
  7284. String valueAsString = value as String;
  7285. if (valueAsString == null)
  7286. control.Text = stringValue;
  7287. else
  7288. control.Text = valueAsString;
  7289. } catch (ArgumentOutOfRangeException) {
  7290. // The value couldn't be set via the Text property.
  7291. }
  7292. }
  7293. /// <summary>
  7294. /// Setup the given control to be a cell editor
  7295. /// </summary>
  7296. protected virtual void ConfigureControl() {
  7297. this.cellEditor.Validating += new CancelEventHandler(CellEditor_Validating);
  7298. this.cellEditor.Select();
  7299. }
  7300. /// <summary>
  7301. /// Return the value that the given control is showing
  7302. /// </summary>
  7303. /// <param name="control"></param>
  7304. /// <returns></returns>
  7305. protected virtual Object GetControlValue(Control control) {
  7306. if (control == null)
  7307. return null;
  7308. if (control is TextBox)
  7309. return ((TextBox)control).Text;
  7310. if (control is ComboBox)
  7311. return ((ComboBox)control).SelectedValue;
  7312. if (control is CheckBox)
  7313. return ((CheckBox)control).Checked;
  7314. try {
  7315. return control.GetType().InvokeMember("Value", BindingFlags.GetProperty, null, control, null);
  7316. } catch (MissingMethodException) { // Microsoft throws this
  7317. return control.Text;
  7318. } catch (MissingFieldException) { // Mono throws this
  7319. return control.Text;
  7320. }
  7321. }
  7322. /// <summary>
  7323. /// Called when the cell editor could be about to lose focus. Time to commit the change
  7324. /// </summary>
  7325. /// <param name="sender"></param>
  7326. /// <param name="e"></param>
  7327. protected virtual void CellEditor_Validating(object sender, CancelEventArgs e) {
  7328. this.cellEditEventArgs.Cancel = false;
  7329. this.cellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
  7330. this.OnCellEditorValidating(this.cellEditEventArgs);
  7331. if (this.cellEditEventArgs.Cancel) {
  7332. this.cellEditEventArgs.Control.Select();
  7333. e.Cancel = true;
  7334. } else
  7335. FinishCellEdit();
  7336. }
  7337. /// <summary>
  7338. /// Return the bounds of the given cell
  7339. /// </summary>
  7340. /// <param name="item">The row to be edited</param>
  7341. /// <param name="subItemIndex">The index of the cell to be edited</param>
  7342. /// <returns>A Rectangle</returns>
  7343. public virtual Rectangle CalculateCellBounds(OLVListItem item, int subItemIndex) {
  7344. // We use ItemBoundsPortion.Label rather than ItemBoundsPortion.Item
  7345. // since Label extends to the right edge of the cell, whereas Item gives just the
  7346. // current text width.
  7347. return this.CalculateCellBounds(item, subItemIndex, ItemBoundsPortion.Label);
  7348. }
  7349. /// <summary>
  7350. /// Return the bounds of the given cell only until the edge of the current text
  7351. /// </summary>
  7352. /// <param name="item">The row to be edited</param>
  7353. /// <param name="subItemIndex">The index of the cell to be edited</param>
  7354. /// <returns>A Rectangle</returns>
  7355. public virtual Rectangle CalculateCellTextBounds(OLVListItem item, int subItemIndex) {
  7356. return this.CalculateCellBounds(item, subItemIndex, ItemBoundsPortion.ItemOnly);
  7357. }
  7358. private Rectangle CalculateCellBounds(OLVListItem item, int subItemIndex, ItemBoundsPortion portion) {
  7359. // SubItem.Bounds works for every subitem, except the first.
  7360. if (subItemIndex > 0)
  7361. return item.SubItems[subItemIndex].Bounds;
  7362. // For non detail views, we just use the requested portion
  7363. Rectangle r = this.GetItemRect(item.Index, portion);
  7364. if (r.Y < -10000000 || r.Y > 10000000) {
  7365. r.Y = item.Bounds.Y;
  7366. }
  7367. if (this.View != View.Details)
  7368. return r;
  7369. // Finding the bounds of cell 0 should not be a difficult task, but it is. Problems:
  7370. // 1) item.SubItem[0].Bounds is always the full bounds of the entire row, not just cell 0.
  7371. // 2) if column 0 has been dragged to some other position, the bounds always has a left edge of 0.
  7372. // We avoid both these problems by using the position of sides the column header to calculate
  7373. // the sides of the cell
  7374. Point sides = NativeMethods.GetScrolledColumnSides(this, 0);
  7375. r.X = sides.X + 4;
  7376. r.Width = sides.Y - sides.X - 5;
  7377. return r;
  7378. }
  7379. /// <summary>
  7380. /// Return a control that can be used to edit the value of the given cell.
  7381. /// </summary>
  7382. /// <param name="item">The row to be edited</param>
  7383. /// <param name="subItemIndex">The index of the cell to be edited</param>
  7384. /// <returns>A Control to edit the given cell</returns>
  7385. protected virtual Control GetCellEditor(OLVListItem item, int subItemIndex) {
  7386. OLVColumn column = this.GetColumn(subItemIndex);
  7387. Object value = column.GetValue(item.RowObject) ?? this.GetFirstNonNullValue(column);
  7388. // TODO: What do we do if value is still null here?
  7389. // Ask the registry for an instance of the appropriate editor.
  7390. Control editor = ObjectListView.EditorRegistry.GetEditor(item.RowObject, column, value);
  7391. // Use a default editor if the registry can't create one for us.
  7392. if (editor == null)
  7393. editor = this.MakeDefaultCellEditor(column);
  7394. return editor;
  7395. }
  7396. /// <summary>
  7397. /// Get the first non-null value of the given column.
  7398. /// At most 1000 rows will be considered.
  7399. /// </summary>
  7400. /// <param name="column"></param>
  7401. /// <returns>The first non-null value, or null if no non-null values were found</returns>
  7402. internal object GetFirstNonNullValue(OLVColumn column) {
  7403. for (int i = 0; i < Math.Min(this.GetItemCount(), 1000); i++) {
  7404. object value = column.GetValue(this.GetModelObject(i));
  7405. if (value != null)
  7406. return value;
  7407. }
  7408. return null;
  7409. }
  7410. /// <summary>
  7411. /// Return a TextBox that can be used as a default cell editor.
  7412. /// </summary>
  7413. /// <param name="column">What column does the cell belong to?</param>
  7414. /// <returns></returns>
  7415. protected virtual Control MakeDefaultCellEditor(OLVColumn column) {
  7416. TextBox tb = new TextBox();
  7417. if (column.AutoCompleteEditor)
  7418. this.ConfigureAutoComplete(tb, column);
  7419. return tb;
  7420. }
  7421. /// <summary>
  7422. /// Configure the given text box to autocomplete unique values
  7423. /// from the given column. At most 1000 rows will be considered.
  7424. /// </summary>
  7425. /// <param name="tb">The textbox to configure</param>
  7426. /// <param name="column">The column used to calculate values</param>
  7427. public void ConfigureAutoComplete(TextBox tb, OLVColumn column) {
  7428. this.ConfigureAutoComplete(tb, column, 1000);
  7429. }
  7430. /// <summary>
  7431. /// Configure the given text box to autocomplete unique values
  7432. /// from the given column. At most 1000 rows will be considered.
  7433. /// </summary>
  7434. /// <param name="tb">The textbox to configure</param>
  7435. /// <param name="column">The column used to calculate values</param>
  7436. /// <param name="maxRows">Consider only this many rows</param>
  7437. public void ConfigureAutoComplete(TextBox tb, OLVColumn column, int maxRows) {
  7438. // Don't consider more rows than we actually have
  7439. maxRows = Math.Min(this.GetItemCount(), maxRows);
  7440. // Reset any existing autocomplete
  7441. tb.AutoCompleteCustomSource.Clear();
  7442. // CONSIDER: Should we use ClusteringStrategy here?
  7443. // Build a list of unique values, to be used as autocomplete on the editor
  7444. Dictionary<string, bool> alreadySeen = new Dictionary<string, bool>();
  7445. List<string> values = new List<string>();
  7446. for (int i = 0; i < maxRows; i++) {
  7447. string valueAsString = column.GetStringValue(this.GetModelObject(i));
  7448. if (!String.IsNullOrEmpty(valueAsString) && !alreadySeen.ContainsKey(valueAsString)) {
  7449. values.Add(valueAsString);
  7450. alreadySeen[valueAsString] = true;
  7451. }
  7452. }
  7453. tb.AutoCompleteCustomSource.AddRange(values.ToArray());
  7454. tb.AutoCompleteSource = AutoCompleteSource.CustomSource;
  7455. tb.AutoCompleteMode = column.AutoCompleteEditorMode;
  7456. }
  7457. /// <summary>
  7458. /// Stop editing a cell and throw away any changes.
  7459. /// </summary>
  7460. public virtual void CancelCellEdit() {
  7461. if (!this.IsCellEditing)
  7462. return;
  7463. // Let the world know that the user has cancelled the edit operation
  7464. this.cellEditEventArgs.Cancel = true;
  7465. this.cellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
  7466. this.OnCellEditFinishing(this.cellEditEventArgs);
  7467. // Now cleanup the editing process
  7468. this.CleanupCellEdit();
  7469. }
  7470. /// <summary>
  7471. /// If a cell edit is in progress, finish the edit.
  7472. /// </summary>
  7473. /// <returns>Returns false if the finishing process was cancelled
  7474. /// (i.e. the cell editor is still on screen)</returns>
  7475. /// <remarks>This method does not guarantee that the editing will finish. The validation
  7476. /// process can cause the finishing to be aborted. Developers should check the return value
  7477. /// or use IsCellEditing property after calling this method to see if the user is still
  7478. /// editing a cell.</remarks>
  7479. public virtual bool PossibleFinishCellEditing() {
  7480. if (!this.IsCellEditing)
  7481. return true;
  7482. this.cellEditEventArgs.Cancel = false;
  7483. this.cellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
  7484. this.OnCellEditorValidating(this.cellEditEventArgs);
  7485. if (this.cellEditEventArgs.Cancel)
  7486. return false;
  7487. this.FinishCellEdit();
  7488. return true;
  7489. }
  7490. /// <summary>
  7491. /// Finish the cell edit operation, writing changed data back to the model object
  7492. /// </summary>
  7493. /// <remarks>This method does not trigger a Validating event, so it always finishes
  7494. /// the cell edit.</remarks>
  7495. public virtual void FinishCellEdit() {
  7496. if (!this.IsCellEditing)
  7497. return;
  7498. this.cellEditEventArgs.Cancel = false;
  7499. this.cellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
  7500. this.OnCellEditFinishing(this.cellEditEventArgs);
  7501. // If someone doesn't cancel the editing process, write the value back into the model
  7502. if (!this.cellEditEventArgs.Cancel) {
  7503. this.cellEditEventArgs.Column.PutValue(this.cellEditEventArgs.RowObject, this.cellEditEventArgs.NewValue);
  7504. this.RefreshItem(this.cellEditEventArgs.ListViewItem);
  7505. }
  7506. this.CleanupCellEdit();
  7507. }
  7508. /// <summary>
  7509. /// Remove all trace of any existing cell edit operation
  7510. /// </summary>
  7511. protected virtual void CleanupCellEdit() {
  7512. if (this.cellEditor == null)
  7513. return;
  7514. this.cellEditor.Validating -= new CancelEventHandler(CellEditor_Validating);
  7515. Control soonToBeOldCellEditor = this.cellEditor;
  7516. this.cellEditor = null;
  7517. // Delay cleaning up the cell editor so that if we are immediately going to
  7518. // start a new cell edit (because the user pressed Tab) the new cell editor
  7519. // has a chance to grab the focus. Without this, the ListView gains focus
  7520. // momentarily (after the cell editor is remove and before the new one is created)
  7521. // causing the list's selection to flash momentarily.
  7522. EventHandler toBeRun = null;
  7523. toBeRun = delegate(object sender, EventArgs e) {
  7524. Application.Idle -= toBeRun;
  7525. this.Controls.Remove(soonToBeOldCellEditor);
  7526. this.Invalidate();
  7527. if (!this.IsCellEditing) {
  7528. this.Select();
  7529. this.PauseAnimations(false);
  7530. }
  7531. };
  7532. Application.Idle += toBeRun;
  7533. }
  7534. #endregion
  7535. #region Hot row and cell handling
  7536. /// <summary>
  7537. /// Force the hot item to be recalculated
  7538. /// </summary>
  7539. public virtual void ClearHotItem() {
  7540. this.UpdateHotItem(new Point(-1, -1));
  7541. }
  7542. /// <summary>
  7543. /// Force the hot item to be recalculated
  7544. /// </summary>
  7545. public virtual void RefreshHotItem() {
  7546. this.UpdateHotItem(this.PointToClient(Cursor.Position));
  7547. }
  7548. /// <summary>
  7549. /// The mouse has moved to the given pt. See if the hot item needs to be updated
  7550. /// </summary>
  7551. /// <param name="pt">Where is the mouse?</param>
  7552. /// <remarks>This is the main entry point for hot item handling</remarks>
  7553. protected virtual void UpdateHotItem(Point pt) {
  7554. this.UpdateHotItem(this.OlvHitTest(pt.X, pt.Y));
  7555. }
  7556. /// <summary>
  7557. /// The mouse has moved to the given pt. See if the hot item needs to be updated
  7558. /// </summary>
  7559. /// <param name="hti"></param>
  7560. /// <remarks>This is the main entry point for hot item handling</remarks>
  7561. protected virtual void UpdateHotItem(OlvListViewHitTestInfo hti) {
  7562. if (!this.UseHotItem && !this.UseHyperlinks)
  7563. return;
  7564. int newHotRow = hti.RowIndex;
  7565. int newHotColumn = hti.ColumnIndex;
  7566. HitTestLocation newHotCellHitLocation = hti.HitTestLocation;
  7567. // In non-details view, we treat any hit on a row as if it were a hit
  7568. // on column 0 -- which (effectively) it is!
  7569. if (newHotRow >= 0 && this.View != View.Details)
  7570. newHotColumn = 0;
  7571. if (this.HotRowIndex == newHotRow &&
  7572. this.HotColumnIndex == newHotColumn &&
  7573. this.HotCellHitLocation == newHotCellHitLocation)
  7574. return;
  7575. // Trigger the hotitem changed event
  7576. HotItemChangedEventArgs args = new HotItemChangedEventArgs();
  7577. args.HotCellHitLocation = newHotCellHitLocation;
  7578. args.HotColumnIndex = newHotColumn;
  7579. args.HotRowIndex = newHotRow;
  7580. args.OldHotCellHitLocation = this.HotCellHitLocation;
  7581. args.OldHotColumnIndex = this.HotColumnIndex;
  7582. args.OldHotRowIndex = this.HotRowIndex;
  7583. this.OnHotItemChanged(args);
  7584. // Update the state of the control
  7585. this.HotRowIndex = newHotRow;
  7586. this.HotColumnIndex = newHotColumn;
  7587. this.HotCellHitLocation = newHotCellHitLocation;
  7588. // If the event handler handled it complete, don't do anything else
  7589. if (args.Handled)
  7590. return;
  7591. this.BeginUpdate();
  7592. try {
  7593. this.Invalidate();
  7594. if (args.OldHotRowIndex != -1)
  7595. this.UnapplyHotItem(args.OldHotRowIndex);
  7596. if (this.HotRowIndex != -1) {
  7597. // Virtual lists apply hot item style when fetching their rows
  7598. if (this.VirtualMode)
  7599. this.RedrawItems(this.HotRowIndex, this.HotRowIndex, true);
  7600. else
  7601. this.UpdateHotRow(this.HotRowIndex, this.HotColumnIndex, this.HotCellHitLocation, hti.Item);
  7602. }
  7603. if (this.UseHotItem && this.HotItemStyle != null && this.HotItemStyle.Overlay != null) {
  7604. this.RefreshOverlays();
  7605. }
  7606. } finally {
  7607. this.EndUpdate();
  7608. }
  7609. }
  7610. /// <summary>
  7611. /// Update the given row using the current hot item information
  7612. /// </summary>
  7613. /// <param name="olvi"></param>
  7614. protected virtual void UpdateHotRow(OLVListItem olvi) {
  7615. this.UpdateHotRow(this.HotRowIndex, this.HotColumnIndex, this.HotCellHitLocation, olvi);
  7616. }
  7617. /// <summary>
  7618. /// Update the given row using the given hot item information
  7619. /// </summary>
  7620. /// <param name="rowIndex"></param>
  7621. /// <param name="columnIndex"></param>
  7622. /// <param name="hitLocation"></param>
  7623. /// <param name="olvi"></param>
  7624. protected virtual void UpdateHotRow(int rowIndex, int columnIndex, HitTestLocation hitLocation, OLVListItem olvi) {
  7625. if (rowIndex < 0 || columnIndex < 0)
  7626. return;
  7627. if (this.UseHyperlinks) {
  7628. OLVColumn column = this.GetColumn(columnIndex);
  7629. OLVListSubItem subItem = olvi.GetSubItem(columnIndex);
  7630. if (column.Hyperlink && hitLocation == HitTestLocation.Text && !String.IsNullOrEmpty(subItem.Url)) {
  7631. this.ApplyCellStyle(olvi, columnIndex, this.HyperlinkStyle.Over);
  7632. this.Cursor = this.HyperlinkStyle.OverCursor ?? Cursors.Default;
  7633. } else {
  7634. this.Cursor = Cursors.Default;
  7635. }
  7636. }
  7637. if (this.UseHotItem) {
  7638. if (!olvi.Selected) {
  7639. this.ApplyRowStyle(olvi, this.HotItemStyle);
  7640. }
  7641. }
  7642. }
  7643. /// <summary>
  7644. /// Apply a style to the given row
  7645. /// </summary>
  7646. /// <param name="olvi"></param>
  7647. /// <param name="style"></param>
  7648. protected virtual void ApplyRowStyle(OLVListItem olvi, IItemStyle style) {
  7649. if (style == null)
  7650. return;
  7651. if (this.FullRowSelect || this.View != View.Details) {
  7652. if (style.Font != null)
  7653. olvi.Font = style.Font;
  7654. if (style.FontStyle != FontStyle.Regular)
  7655. olvi.Font = new Font(olvi.Font ?? this.Font, style.FontStyle);
  7656. if (!style.ForeColor.IsEmpty) {
  7657. if (olvi.UseItemStyleForSubItems)
  7658. olvi.ForeColor = style.ForeColor;
  7659. else {
  7660. foreach (ListViewItem.ListViewSubItem x in olvi.SubItems) {
  7661. x.ForeColor = style.ForeColor;
  7662. }
  7663. }
  7664. }
  7665. if (!style.BackColor.IsEmpty) {
  7666. if (olvi.UseItemStyleForSubItems)
  7667. olvi.BackColor = style.BackColor;
  7668. else {
  7669. foreach (ListViewItem.ListViewSubItem x in olvi.SubItems) {
  7670. x.BackColor = style.BackColor;
  7671. }
  7672. }
  7673. }
  7674. } else {
  7675. olvi.UseItemStyleForSubItems = false;
  7676. foreach (ListViewItem.ListViewSubItem x in olvi.SubItems) {
  7677. if (style.BackColor.IsEmpty)
  7678. x.BackColor = olvi.BackColor;
  7679. else
  7680. x.BackColor = style.BackColor;
  7681. }
  7682. this.ApplyCellStyle(olvi, 0, style);
  7683. }
  7684. }
  7685. /// <summary>
  7686. /// Apply a style to a cell
  7687. /// </summary>
  7688. /// <param name="olvi"></param>
  7689. /// <param name="columnIndex"></param>
  7690. /// <param name="style"></param>
  7691. protected virtual void ApplyCellStyle(OLVListItem olvi, int columnIndex, IItemStyle style) {
  7692. if (style == null)
  7693. return;
  7694. // Don't apply formatting to subitems when not in Details view
  7695. if (this.View != View.Details && columnIndex > 0)
  7696. return;
  7697. olvi.UseItemStyleForSubItems = false;
  7698. ListViewItem.ListViewSubItem subItem = olvi.SubItems[columnIndex];
  7699. if (style.Font != null)
  7700. subItem.Font = style.Font;
  7701. if (style.FontStyle != FontStyle.Regular)
  7702. subItem.Font = new Font(subItem.Font ?? olvi.Font ?? this.Font, style.FontStyle);
  7703. if (!style.ForeColor.IsEmpty)
  7704. subItem.ForeColor = style.ForeColor;
  7705. if (!style.BackColor.IsEmpty)
  7706. subItem.BackColor = style.BackColor;
  7707. }
  7708. /// <summary>
  7709. /// Remove hot item styling from the given row
  7710. /// </summary>
  7711. /// <param name="index"></param>
  7712. protected virtual void UnapplyHotItem(int index) {
  7713. this.Cursor = Cursors.Default;
  7714. // Virtual lists will apply the appropriate formatting when the row is fetched
  7715. if (this.VirtualMode) {
  7716. if (index < this.VirtualListSize)
  7717. this.RedrawItems(index, index, true);
  7718. } else {
  7719. OLVListItem olvi = this.GetItem(index);
  7720. if (olvi != null) {
  7721. //this.PostProcessOneRow(index, index, olvi);
  7722. this.RefreshItem(olvi);
  7723. }
  7724. }
  7725. }
  7726. #endregion
  7727. #region Drag and drop
  7728. /// <summary>
  7729. ///
  7730. /// </summary>
  7731. /// <param name="e"></param>
  7732. protected override void OnItemDrag(ItemDragEventArgs e) {
  7733. base.OnItemDrag(e);
  7734. if (this.DragSource == null)
  7735. return;
  7736. Object data = this.DragSource.StartDrag(this, e.Button, (OLVListItem)e.Item);
  7737. if (data != null) {
  7738. DragDropEffects effect = this.DoDragDrop(data, this.DragSource.GetAllowedEffects(data));
  7739. this.DragSource.EndDrag(data, effect);
  7740. }
  7741. }
  7742. /// <summary>
  7743. ///
  7744. /// </summary>
  7745. /// <param name="args"></param>
  7746. protected override void OnDragEnter(DragEventArgs args) {
  7747. base.OnDragEnter(args);
  7748. if (this.DropSink != null)
  7749. this.DropSink.Enter(args);
  7750. }
  7751. /// <summary>
  7752. ///
  7753. /// </summary>
  7754. /// <param name="args"></param>
  7755. protected override void OnDragOver(DragEventArgs args) {
  7756. base.OnDragOver(args);
  7757. if (this.DropSink != null)
  7758. this.DropSink.Over(args);
  7759. }
  7760. /// <summary>
  7761. ///
  7762. /// </summary>
  7763. /// <param name="args"></param>
  7764. protected override void OnDragDrop(DragEventArgs args) {
  7765. base.OnDragDrop(args);
  7766. if (this.DropSink != null)
  7767. this.DropSink.Drop(args);
  7768. }
  7769. /// <summary>
  7770. ///
  7771. /// </summary>
  7772. /// <param name="e"></param>
  7773. protected override void OnDragLeave(EventArgs e) {
  7774. base.OnDragLeave(e);
  7775. if (this.DropSink != null)
  7776. this.DropSink.Leave();
  7777. }
  7778. /// <summary>
  7779. ///
  7780. /// </summary>
  7781. /// <param name="args"></param>
  7782. protected override void OnGiveFeedback(GiveFeedbackEventArgs args) {
  7783. base.OnGiveFeedback(args);
  7784. if (this.DropSink != null)
  7785. this.DropSink.GiveFeedback(args);
  7786. }
  7787. /// <summary>
  7788. ///
  7789. /// </summary>
  7790. /// <param name="args"></param>
  7791. protected override void OnQueryContinueDrag(QueryContinueDragEventArgs args) {
  7792. base.OnQueryContinueDrag(args);
  7793. if (this.DropSink != null)
  7794. this.DropSink.QueryContinue(args);
  7795. }
  7796. #endregion
  7797. #region Decorations and Overlays
  7798. /// <summary>
  7799. /// Add the given decoration to those on this list and make it appear
  7800. /// </summary>
  7801. /// <param name="decoration">The decoration</param>
  7802. /// <remarks>
  7803. /// A decoration scrolls with the listview. An overlay stays fixed in place.
  7804. /// </remarks>
  7805. public virtual void AddDecoration(IDecoration decoration) {
  7806. if (decoration == null)
  7807. return;
  7808. this.Decorations.Add(decoration);
  7809. this.Invalidate();
  7810. }
  7811. /// <summary>
  7812. /// Add the given overlay to those on this list and make it appear
  7813. /// </summary>
  7814. /// <param name="overlay">The overlay</param>
  7815. public virtual void AddOverlay(IOverlay overlay) {
  7816. if (overlay == null)
  7817. return;
  7818. this.Overlays.Add(overlay);
  7819. this.Invalidate();
  7820. }
  7821. /// <summary>
  7822. /// Draw all the decorations
  7823. /// </summary>
  7824. /// <param name="g">A Graphics</param>
  7825. /// <param name="drawnItems">The items that were redrawn and whose decorations should also be redrawn</param>
  7826. protected virtual void DrawAllDecorations(Graphics g, List<OLVListItem> drawnItems) {
  7827. g.TextRenderingHint = ObjectListView.TextRenderingHint;
  7828. g.SmoothingMode = ObjectListView.SmoothingMode;
  7829. Rectangle contentRectangle = this.ContentRectangle;
  7830. if (this.HasEmptyListMsg && this.GetItemCount() == 0) {
  7831. this.EmptyListMsgOverlay.Draw(this, g, contentRectangle);
  7832. }
  7833. // Let the drop sink draw whatever feedback it likes
  7834. if (this.DropSink != null) {
  7835. this.DropSink.DrawFeedback(g, contentRectangle);
  7836. }
  7837. // Draw our item and subitem decorations
  7838. foreach (OLVListItem olvi in drawnItems) {
  7839. if (olvi.HasDecoration) {
  7840. foreach (IDecoration d in olvi.Decorations) {
  7841. d.ListItem = olvi;
  7842. d.SubItem = null;
  7843. d.Draw(this, g, contentRectangle);
  7844. }
  7845. }
  7846. foreach (OLVListSubItem subItem in olvi.SubItems) {
  7847. if (subItem.HasDecoration) {
  7848. foreach (IDecoration d in subItem.Decorations) {
  7849. d.ListItem = olvi;
  7850. d.SubItem = subItem;
  7851. d.Draw(this, g, contentRectangle);
  7852. }
  7853. }
  7854. }
  7855. if (this.SelectedRowDecoration != null && olvi.Selected) {
  7856. this.SelectedRowDecoration.ListItem = olvi;
  7857. this.SelectedRowDecoration.SubItem = null;
  7858. this.SelectedRowDecoration.Draw(this, g, contentRectangle);
  7859. }
  7860. }
  7861. // Now draw the specifically registered decorations
  7862. foreach (IDecoration decoration in this.Decorations) {
  7863. decoration.ListItem = null;
  7864. decoration.SubItem = null;
  7865. decoration.Draw(this, g, contentRectangle);
  7866. }
  7867. // Finally, draw any hot item decoration
  7868. if (this.UseHotItem && this.HotItemStyle != null && this.HotItemStyle.Decoration != null) {
  7869. IDecoration hotItemDecoration = this.HotItemStyle.Decoration;
  7870. hotItemDecoration.ListItem = this.GetItem(this.HotRowIndex);
  7871. if (hotItemDecoration.ListItem == null)
  7872. hotItemDecoration.SubItem = null;
  7873. else
  7874. hotItemDecoration.SubItem = hotItemDecoration.ListItem.GetSubItem(this.HotColumnIndex);
  7875. hotItemDecoration.Draw(this, g, contentRectangle);
  7876. }
  7877. // If we are in design mode, we don't want to use the glass panels,
  7878. // so we draw the background overlays here
  7879. if (this.DesignMode) {
  7880. foreach (IOverlay overlay in this.Overlays) {
  7881. overlay.Draw(this, g, contentRectangle);
  7882. }
  7883. }
  7884. }
  7885. /// <summary>
  7886. /// Is the given decoration shown on this list
  7887. /// </summary>
  7888. /// <param name="decoration">The overlay</param>
  7889. public virtual bool HasDecoration(IDecoration decoration) {
  7890. return this.Decorations.Contains(decoration);
  7891. }
  7892. /// <summary>
  7893. /// Is the given overlay shown on this list?
  7894. /// </summary>
  7895. /// <param name="overlay">The overlay</param>
  7896. public virtual bool HasOverlay(IOverlay overlay) {
  7897. return this.Overlays.Contains(overlay);
  7898. }
  7899. /// <summary>
  7900. /// Hide any overlays.
  7901. /// </summary>
  7902. /// <remarks>
  7903. /// This is only a temporary hiding -- the overlays will be shown
  7904. /// the next time the ObjectListView redraws.
  7905. /// </remarks>
  7906. public virtual void HideOverlays() {
  7907. foreach (GlassPanelForm glassPanel in this.glassPanels) {
  7908. glassPanel.HideGlass();
  7909. }
  7910. }
  7911. /// <summary>
  7912. /// Create and configure the empty list msg overlay
  7913. /// </summary>
  7914. protected virtual void InitializeEmptyListMsgOverlay() {
  7915. TextOverlay textOverlay = new TextOverlay();
  7916. textOverlay.Alignment = System.Drawing.ContentAlignment.MiddleCenter;
  7917. textOverlay.TextColor = SystemColors.ControlDarkDark;
  7918. textOverlay.BackColor = Color.BlanchedAlmond;
  7919. textOverlay.BorderColor = SystemColors.ControlDark;
  7920. textOverlay.BorderWidth = 2.0f;
  7921. this.EmptyListMsgOverlay = textOverlay;
  7922. }
  7923. /// <summary>
  7924. /// Initialize the standard image and text overlays
  7925. /// </summary>
  7926. protected virtual void InitializeStandardOverlays() {
  7927. this.OverlayImage = new ImageOverlay();
  7928. this.AddOverlay(this.OverlayImage);
  7929. this.OverlayText = new TextOverlay();
  7930. this.AddOverlay(this.OverlayText);
  7931. }
  7932. /// <summary>
  7933. /// Make sure that any overlays are visible.
  7934. /// </summary>
  7935. public virtual void ShowOverlays() {
  7936. // If we shouldn't show overlays, then don't create glass panels
  7937. if (!this.ShouldShowOverlays())
  7938. return;
  7939. // Make sure that each overlay has its own glass panels
  7940. if (this.Overlays.Count != this.glassPanels.Count) {
  7941. foreach (IOverlay overlay in this.Overlays) {
  7942. GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay);
  7943. if (glassPanel == null) {
  7944. glassPanel = new GlassPanelForm();
  7945. glassPanel.Bind(this, overlay);
  7946. this.glassPanels.Add(glassPanel);
  7947. }
  7948. }
  7949. }
  7950. foreach (GlassPanelForm glassPanel in this.glassPanels) {
  7951. glassPanel.ShowGlass();
  7952. }
  7953. }
  7954. private bool ShouldShowOverlays() {
  7955. // If we are in design mode, we dont show the overlays
  7956. if (this.DesignMode)
  7957. return false;
  7958. // If we are explicitly not using overlays, also don't show them
  7959. if (!this.UseOverlays)
  7960. return false;
  7961. // If there are no overlays, guess...
  7962. if (!this.HasOverlays)
  7963. return false;
  7964. // If we don't have 32-bit display, alpha blending doesn't work, so again, no overlays
  7965. // TODO: This should actually figure out which screen(s) the control is on, and make sure
  7966. // that each one is 32-bit.
  7967. if (Screen.PrimaryScreen.BitsPerPixel < 32)
  7968. return false;
  7969. // Finally, we can show the overlays
  7970. return true;
  7971. }
  7972. private GlassPanelForm FindGlassPanelForOverlay(IOverlay overlay) {
  7973. return this.glassPanels.Find(delegate(GlassPanelForm x) { return x.Overlay == overlay; });
  7974. }
  7975. /// <summary>
  7976. /// Refresh the display of the overlays
  7977. /// </summary>
  7978. public virtual void RefreshOverlays() {
  7979. foreach (GlassPanelForm glassPanel in this.glassPanels) {
  7980. glassPanel.Invalidate();
  7981. }
  7982. }
  7983. /// <summary>
  7984. /// Refresh the display of just one overlays
  7985. /// </summary>
  7986. public virtual void RefreshOverlay(IOverlay overlay) {
  7987. GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay);
  7988. if (glassPanel != null)
  7989. glassPanel.Invalidate();
  7990. }
  7991. /// <summary>
  7992. /// Remove the given decoration from this list
  7993. /// </summary>
  7994. /// <param name="decoration">The decoration to remove</param>
  7995. public virtual void RemoveDecoration(IDecoration decoration) {
  7996. if (decoration == null)
  7997. return;
  7998. this.Decorations.Remove(decoration);
  7999. this.Invalidate();
  8000. }
  8001. /// <summary>
  8002. /// Remove the given overlay to those on this list
  8003. /// </summary>
  8004. /// <param name="overlay">The overlay</param>
  8005. public virtual void RemoveOverlay(IOverlay overlay) {
  8006. if (overlay == null)
  8007. return;
  8008. this.Overlays.Remove(overlay);
  8009. GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay);
  8010. if (glassPanel != null) {
  8011. this.glassPanels.Remove(glassPanel);
  8012. glassPanel.Unbind();
  8013. glassPanel.Dispose();
  8014. }
  8015. }
  8016. #endregion
  8017. #region Filtering
  8018. /// <summary>
  8019. /// Create a filter that will enact all the filtering currently installed
  8020. /// on the visible columns.
  8021. /// </summary>
  8022. public virtual IModelFilter CreateColumnFilter() {
  8023. List<IModelFilter> filters = new List<IModelFilter>();
  8024. foreach (OLVColumn column in this.Columns) {
  8025. IModelFilter filter = column.ValueBasedFilter;
  8026. if (filter != null)
  8027. filters.Add(filter);
  8028. }
  8029. return (filters.Count == 0) ? null : new CompositeAllFilter(filters);
  8030. }
  8031. /// <summary>
  8032. /// Do the actual work of filtering
  8033. /// </summary>
  8034. /// <param name="objects"></param>
  8035. /// <param name="aModelFilter"></param>
  8036. /// <param name="aListFilter"></param>
  8037. /// <returns></returns>
  8038. virtual protected IEnumerable FilterObjects(IEnumerable objects, IModelFilter aModelFilter, IListFilter aListFilter) {
  8039. // Being cautious
  8040. objects = objects ?? new ArrayList();
  8041. // Tell the world to filter the objects. If they do so, don't do anything else
  8042. FilterEventArgs args = new FilterEventArgs(objects);
  8043. this.OnFilter(args);
  8044. if (args.FilteredObjects != null)
  8045. return args.FilteredObjects;
  8046. // Apply a filter to the list as a whole
  8047. if (aListFilter != null)
  8048. objects = aListFilter.Filter(objects);
  8049. // Apply the object filter if there is one
  8050. if (aModelFilter != null) {
  8051. ArrayList filteredObjects = new ArrayList();
  8052. foreach (object model in objects) {
  8053. if (aModelFilter.Filter(model))
  8054. filteredObjects.Add(model);
  8055. }
  8056. objects = filteredObjects;
  8057. }
  8058. return objects;
  8059. }
  8060. /// <summary>
  8061. /// Remove all column filtering.
  8062. /// </summary>
  8063. public virtual void ResetColumnFiltering() {
  8064. foreach (OLVColumn column in this.Columns) {
  8065. column.ValuesChosenForFiltering.Clear();
  8066. }
  8067. this.UpdateColumnFiltering();
  8068. }
  8069. /// <summary>
  8070. /// Update the filtering of this ObjectListView based on the value filtering
  8071. /// defined in each column
  8072. /// </summary>
  8073. public virtual void UpdateColumnFiltering() {
  8074. this.ModelFilter = this.CreateColumnFilter();
  8075. }
  8076. /// <summary>
  8077. /// When some setting related to filtering changes, this method is called.
  8078. /// </summary>
  8079. protected virtual void UpdateFiltering() {
  8080. this.BuildList(true);
  8081. }
  8082. #endregion
  8083. #region Implementation variables
  8084. private Rectangle lastUpdateRectangle; // remember the update rect from the last WM_PAINT message
  8085. private bool isOwnerOfObjects; // does this ObjectListView own the Objects collection?
  8086. private bool hasIdleHandler; // has an Idle handler already been installed?
  8087. private bool hasResizeColumnsHandler; // has an idle handler been installed which will handle column resizing?
  8088. private bool isInWmPaintEvent; // is a WmPaint event currently being handled?
  8089. private bool shouldDoCustomDrawing; // should the list do its custom drawing?
  8090. private bool isMarqueSelecting; // Is a marque selection in progress?
  8091. private List<GlassPanelForm> glassPanels = new List<GlassPanelForm>(); // The transparent panel that draws overlays
  8092. Dictionary<string, bool> visitedUrlMap = new Dictionary<string, bool>(); // Which urls have been visited?
  8093. #endregion
  8094. }
  8095. }