PageRenderTime 51ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 2ms

/GDFlacToolVS2008/ObjectListView2010/ObjectListView.cs

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