Imported existing sources
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Attributes - Attributes that can be attached to properties of models to allow columns to be
|
||||
* built from them directly
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 15/08/2009 22:01
|
||||
*
|
||||
* Change log:
|
||||
* v2.6
|
||||
* 2012-08-16 JPP - Added [OLVChildren] and [OLVIgnore]
|
||||
* - OLV attributes can now only be set on properties
|
||||
* v2.4
|
||||
* 2010-04-14 JPP - Allow Name property to be set
|
||||
*
|
||||
* v2.3
|
||||
* 2009-08-15 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute is used to mark a property of a model
|
||||
/// class that should be noticed by Generator class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All the attributes of this class match their equivilent properties on OLVColumn.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OLVColumnAttribute : Attribute
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
// There are several property where we actually want nullable value (bool?, int?),
|
||||
// but it seems attribute properties can't be nullable types.
|
||||
// So we explicitly track if those properties have been set.
|
||||
|
||||
/// <summary>
|
||||
/// Create a new OLVColumnAttribute
|
||||
/// </summary>
|
||||
public OLVColumnAttribute() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new OLVColumnAttribute with the given title
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the column</param>
|
||||
public OLVColumnAttribute(string title) {
|
||||
this.Title = title;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string AspectToStringFormat {
|
||||
get { return aspectToStringFormat; }
|
||||
set { aspectToStringFormat = value; }
|
||||
}
|
||||
private string aspectToStringFormat;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool CheckBoxes {
|
||||
get { return checkBoxes; }
|
||||
set {
|
||||
checkBoxes = value;
|
||||
this.IsCheckBoxesSet = true;
|
||||
}
|
||||
}
|
||||
private bool checkBoxes;
|
||||
internal bool IsCheckBoxesSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int DisplayIndex {
|
||||
get { return displayIndex; }
|
||||
set { displayIndex = value; }
|
||||
}
|
||||
private int displayIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool FillsFreeSpace {
|
||||
get { return fillsFreeSpace; }
|
||||
set { fillsFreeSpace = value; }
|
||||
}
|
||||
private bool fillsFreeSpace;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int FreeSpaceProportion {
|
||||
get { return freeSpaceProportion; }
|
||||
set {
|
||||
freeSpaceProportion = value;
|
||||
IsFreeSpaceProportionSet = true;
|
||||
}
|
||||
}
|
||||
private int freeSpaceProportion;
|
||||
internal bool IsFreeSpaceProportionSet = false;
|
||||
|
||||
/// <summary>
|
||||
/// An array of IComparables that mark the cutoff points for values when
|
||||
/// grouping on this column.
|
||||
/// </summary>
|
||||
public object[] GroupCutoffs {
|
||||
get { return groupCutoffs; }
|
||||
set { groupCutoffs = value; }
|
||||
}
|
||||
private object[] groupCutoffs;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string[] GroupDescriptions {
|
||||
get { return groupDescriptions; }
|
||||
set { groupDescriptions = value; }
|
||||
}
|
||||
private string[] groupDescriptions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string GroupWithItemCountFormat {
|
||||
get { return groupWithItemCountFormat; }
|
||||
set { groupWithItemCountFormat = value; }
|
||||
}
|
||||
private string groupWithItemCountFormat;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string GroupWithItemCountSingularFormat {
|
||||
get { return groupWithItemCountSingularFormat; }
|
||||
set { groupWithItemCountSingularFormat = value; }
|
||||
}
|
||||
private string groupWithItemCountSingularFormat;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Hyperlink {
|
||||
get { return hyperlink; }
|
||||
set { hyperlink = value; }
|
||||
}
|
||||
private bool hyperlink;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string ImageAspectName {
|
||||
get { return imageAspectName; }
|
||||
set { imageAspectName = value; }
|
||||
}
|
||||
private string imageAspectName;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsEditable {
|
||||
get { return isEditable; }
|
||||
set {
|
||||
isEditable = value;
|
||||
this.IsEditableSet = true;
|
||||
}
|
||||
}
|
||||
private bool isEditable = true;
|
||||
internal bool IsEditableSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsVisible {
|
||||
get { return isVisible; }
|
||||
set { isVisible = value; }
|
||||
}
|
||||
private bool isVisible = true;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsTileViewColumn {
|
||||
get { return isTileViewColumn; }
|
||||
set { isTileViewColumn = value; }
|
||||
}
|
||||
private bool isTileViewColumn;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int MaximumWidth {
|
||||
get { return maximumWidth; }
|
||||
set { maximumWidth = value; }
|
||||
}
|
||||
private int maximumWidth = -1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int MinimumWidth {
|
||||
get { return minimumWidth; }
|
||||
set { minimumWidth = value; }
|
||||
}
|
||||
private int minimumWidth = -1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String Name {
|
||||
get { return name; }
|
||||
set { name = value; }
|
||||
}
|
||||
private String name;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public HorizontalAlignment TextAlign {
|
||||
get { return this.textAlign; }
|
||||
set {
|
||||
this.textAlign = value;
|
||||
IsTextAlignSet = true;
|
||||
}
|
||||
}
|
||||
private HorizontalAlignment textAlign = HorizontalAlignment.Left;
|
||||
internal bool IsTextAlignSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String Tag {
|
||||
get { return tag; }
|
||||
set { tag = value; }
|
||||
}
|
||||
private String tag;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String Title {
|
||||
get { return title; }
|
||||
set { title = value; }
|
||||
}
|
||||
private String title;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public String ToolTipText {
|
||||
get { return toolTipText; }
|
||||
set { toolTipText = value; }
|
||||
}
|
||||
private String toolTipText;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool TriStateCheckBoxes {
|
||||
get { return triStateCheckBoxes; }
|
||||
set {
|
||||
triStateCheckBoxes = value;
|
||||
this.IsTriStateCheckBoxesSet = true;
|
||||
}
|
||||
}
|
||||
private bool triStateCheckBoxes;
|
||||
internal bool IsTriStateCheckBoxesSet = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool UseInitialLetterForGroup {
|
||||
get { return useInitialLetterForGroup; }
|
||||
set { useInitialLetterForGroup = value; }
|
||||
}
|
||||
private bool useInitialLetterForGroup;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Width {
|
||||
get { return width; }
|
||||
set { width = value; }
|
||||
}
|
||||
private int width = 150;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties marked with [OLVChildren] will be used as the children source in a TreeListView.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OLVChildrenAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties marked with [OLVIgnore] will not have columns generated for them.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class OLVIgnoreAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Comparers - Various Comparer classes used within ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 25/11/2008 17:15
|
||||
*
|
||||
* Change log:
|
||||
* v2.3
|
||||
* 2009-08-24 JPP - Added OLVGroupComparer
|
||||
* 2009-06-01 JPP - ModelObjectComparer would crash if secondary sort column was null.
|
||||
* 2008-12-20 JPP - Fixed bug with group comparisons when a group key was null (SF#2445761)
|
||||
* 2008-11-25 JPP Initial version
|
||||
*
|
||||
* TO DO:
|
||||
*
|
||||
* Copyright (C) 2006-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// ColumnComparer is the workhorse for all comparison between two values of a particular column.
|
||||
/// If the column has a specific comparer, use that to compare the values. Otherwise, do
|
||||
/// a case insensitive string compare of the string representations of the values.
|
||||
/// </summary>
|
||||
/// <remarks><para>This class inherits from both IComparer and its generic counterpart
|
||||
/// so that it can be used on untyped and typed collections.</para></remarks>
|
||||
public class ColumnComparer : IComparer, IComparer<OLVListItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a ColumnComparer that will order the rows in a list view according
|
||||
/// to the values in a given column
|
||||
/// </summary>
|
||||
/// <param name="col">The column whose values will be compared</param>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public ColumnComparer(OLVColumn col, SortOrder order)
|
||||
{
|
||||
this.column = col;
|
||||
this.sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a ColumnComparer that will order the rows in a list view according
|
||||
/// to the values in a given column, and by a secondary column if the primary
|
||||
/// column is equal.
|
||||
/// </summary>
|
||||
/// <param name="col">The column whose values will be compared</param>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
/// <param name="col2">The column whose values will be compared for secondary sorting</param>
|
||||
/// <param name="order2">The ordering for secondary column values</param>
|
||||
public ColumnComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2)
|
||||
: this(col, order)
|
||||
{
|
||||
// There is no point in secondary sorting on the same column
|
||||
if (col != col2)
|
||||
this.secondComparer = new ColumnComparer(col2, order2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two rows
|
||||
/// </summary>
|
||||
/// <param name="x">row1</param>
|
||||
/// <param name="y">row2</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
return this.Compare((OLVListItem)x, (OLVListItem)y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two rows
|
||||
/// </summary>
|
||||
/// <param name="x">row1</param>
|
||||
/// <param name="y">row2</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int Compare(OLVListItem x, OLVListItem y)
|
||||
{
|
||||
if (this.sortOrder == SortOrder.None)
|
||||
return 0;
|
||||
|
||||
int result = 0;
|
||||
object x1 = this.column.GetValue(x.RowObject);
|
||||
object y1 = this.column.GetValue(y.RowObject);
|
||||
|
||||
// Handle nulls. Null values come last
|
||||
bool xIsNull = (x1 == null || x1 == System.DBNull.Value);
|
||||
bool yIsNull = (y1 == null || y1 == System.DBNull.Value);
|
||||
if (xIsNull || yIsNull) {
|
||||
if (xIsNull && yIsNull)
|
||||
result = 0;
|
||||
else
|
||||
result = (xIsNull ? -1 : 1);
|
||||
} else {
|
||||
result = this.CompareValues(x1, y1);
|
||||
}
|
||||
|
||||
if (this.sortOrder == SortOrder.Descending)
|
||||
result = 0 - result;
|
||||
|
||||
// If the result was equality, use the secondary comparer to resolve it
|
||||
if (result == 0 && this.secondComparer != null)
|
||||
result = this.secondComparer.Compare(x, y);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the actual values to be used for sorting
|
||||
/// </summary>
|
||||
/// <param name="x">The aspect extracted from the first row</param>
|
||||
/// <param name="y">The aspect extracted from the second row</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int CompareValues(object x, object y)
|
||||
{
|
||||
// Force case insensitive compares on strings
|
||||
String xAsString = x as String;
|
||||
if (xAsString != null)
|
||||
return String.Compare(xAsString, (String)y, StringComparison.CurrentCultureIgnoreCase);
|
||||
else {
|
||||
IComparable comparable = x as IComparable;
|
||||
if (comparable != null)
|
||||
return comparable.CompareTo(y);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private OLVColumn column;
|
||||
private SortOrder sortOrder;
|
||||
private ColumnComparer secondComparer;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This comparer sort list view groups. OLVGroups have a "SortValue" property,
|
||||
/// which is used if present. Otherwise, the titles of the groups will be compared.
|
||||
/// </summary>
|
||||
public class OLVGroupComparer : IComparer<OLVGroup>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a group comparer
|
||||
/// </summary>
|
||||
/// <param name="order">The ordering for column values</param>
|
||||
public OLVGroupComparer(SortOrder order) {
|
||||
this.sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two groups. OLVGroups have a "SortValue" property,
|
||||
/// which is used if present. Otherwise, the titles of the groups will be compared.
|
||||
/// </summary>
|
||||
/// <param name="x">group1</param>
|
||||
/// <param name="y">group2</param>
|
||||
/// <returns>An ordering indication: -1, 0, 1</returns>
|
||||
public int Compare(OLVGroup x, OLVGroup y) {
|
||||
// If we can compare the sort values, do that.
|
||||
// Otherwise do a case insensitive compare on the group header.
|
||||
int result;
|
||||
if (x.SortValue != null && y.SortValue != null)
|
||||
result = x.SortValue.CompareTo(y.SortValue);
|
||||
else
|
||||
result = String.Compare(x.Header, y.Header, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
if (this.sortOrder == SortOrder.Descending)
|
||||
result = 0 - result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SortOrder sortOrder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This comparer can be used to sort a collection of model objects by a given column
|
||||
/// </summary>
|
||||
public class ModelObjectComparer : IComparer, IComparer<object>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a model object comparer
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="order"></param>
|
||||
public ModelObjectComparer(OLVColumn col, SortOrder order)
|
||||
{
|
||||
this.column = col;
|
||||
this.sortOrder = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a model object comparer with a secondary sorting column
|
||||
/// </summary>
|
||||
/// <param name="col"></param>
|
||||
/// <param name="order"></param>
|
||||
/// <param name="col2"></param>
|
||||
/// <param name="order2"></param>
|
||||
public ModelObjectComparer(OLVColumn col, SortOrder order, OLVColumn col2, SortOrder order2)
|
||||
: this(col, order)
|
||||
{
|
||||
// There is no point in secondary sorting on the same column
|
||||
if (col != col2 && col2 != null && order2 != SortOrder.None)
|
||||
this.secondComparer = new ModelObjectComparer(col2, order2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the two model objects
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
int result = 0;
|
||||
object x1 = this.column.GetValue(x);
|
||||
object y1 = this.column.GetValue(y);
|
||||
|
||||
if (this.sortOrder == SortOrder.None)
|
||||
return 0;
|
||||
|
||||
// Handle nulls. Null values come last
|
||||
bool xIsNull = (x1 == null || x1 == System.DBNull.Value);
|
||||
bool yIsNull = (y1 == null || y1 == System.DBNull.Value);
|
||||
if (xIsNull || yIsNull) {
|
||||
if (xIsNull && yIsNull)
|
||||
result = 0;
|
||||
else
|
||||
result = (xIsNull ? -1 : 1);
|
||||
} else {
|
||||
result = this.CompareValues(x1, y1);
|
||||
}
|
||||
|
||||
if (this.sortOrder == SortOrder.Descending)
|
||||
result = 0 - result;
|
||||
|
||||
// If the result was equality, use the secondary comparer to resolve it
|
||||
if (result == 0 && this.secondComparer != null)
|
||||
result = this.secondComparer.Compare(x, y);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the actual values
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public int CompareValues(object x, object y)
|
||||
{
|
||||
// Force case insensitive compares on strings
|
||||
String xStr = x as String;
|
||||
if (xStr != null)
|
||||
return String.Compare(xStr, (String)y, StringComparison.CurrentCultureIgnoreCase);
|
||||
else {
|
||||
IComparable comparable = x as IComparable;
|
||||
if (comparable != null)
|
||||
return comparable.CompareTo(y);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private OLVColumn column;
|
||||
private SortOrder sortOrder;
|
||||
private ModelObjectComparer secondComparer;
|
||||
|
||||
#region IComparer<object> Members
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
* DataSourceAdapter - A helper class that translates DataSource events for an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 20/09/2010 7:42 AM
|
||||
*
|
||||
* Change log:
|
||||
* v2.6
|
||||
* 2012-08-16 JPP - Unify common column creation functionality with Generator when possible
|
||||
*
|
||||
* 2010-09-20 JPP - Initial version
|
||||
*
|
||||
* Copyright (C) 2010-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing.Design;
|
||||
using System.Globalization;
|
||||
using System.Windows.Forms;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class that translates DataSource events for an ObjectListView
|
||||
/// </summary>
|
||||
public class DataSourceAdapter : IDisposable
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Make a DataSourceAdapter
|
||||
/// </summary>
|
||||
public DataSourceAdapter(ObjectListView olv) {
|
||||
if (olv == null) throw new ArgumentNullException("olv");
|
||||
|
||||
this.ListView = olv;
|
||||
this.BindListView(this.ListView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalize this object
|
||||
/// </summary>
|
||||
~DataSourceAdapter() {
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all the resources used by this instance
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release all the resources used by this instance
|
||||
/// </summary>
|
||||
public virtual void Dispose(bool fromUser) {
|
||||
this.UnbindListView(this.ListView);
|
||||
this.UnbindDataSource();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not columns will be automatically generated to show the
|
||||
/// columns when the DataSource is set.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect afterwards.</remarks>
|
||||
public bool AutoGenerateColumns {
|
||||
get { return this.autoGenerateColumns; }
|
||||
set { this.autoGenerateColumns = value; }
|
||||
}
|
||||
private bool autoGenerateColumns = true;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the DataSource that will be displayed in this list view.
|
||||
/// </summary>
|
||||
public virtual Object DataSource {
|
||||
get { return dataSource; }
|
||||
set {
|
||||
dataSource = value;
|
||||
this.RebindDataSource(true);
|
||||
}
|
||||
}
|
||||
private Object dataSource;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the list or table in the data source for which the DataListView is displaying data.
|
||||
/// </summary>
|
||||
/// <remarks>If the data source is not a DataSet or DataViewManager, this property has no effect</remarks>
|
||||
public virtual string DataMember {
|
||||
get { return dataMember; }
|
||||
set {
|
||||
if (dataMember != value) {
|
||||
dataMember = value;
|
||||
RebindDataSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
private string dataMember = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView upon which this adaptor will operate
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return listView; }
|
||||
internal set { listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currency manager which is handling our binding context
|
||||
/// </summary>
|
||||
protected CurrencyManager CurrencyManager {
|
||||
get { return currencyManager; }
|
||||
set { currencyManager = value; }
|
||||
}
|
||||
private CurrencyManager currencyManager = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Binding and unbinding
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
protected virtual void BindListView(ObjectListView olv) {
|
||||
if (olv == null)
|
||||
return;
|
||||
|
||||
olv.Freezing += new EventHandler<FreezeEventArgs>(HandleListViewFreezing);
|
||||
olv.SelectedIndexChanged += new EventHandler(HandleListViewSelectedIndexChanged);
|
||||
olv.BindingContextChanged += new EventHandler(HandleListViewBindingContextChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
protected virtual void UnbindListView(ObjectListView olv) {
|
||||
if (olv == null)
|
||||
return;
|
||||
|
||||
olv.Freezing -= new EventHandler<FreezeEventArgs>(HandleListViewFreezing);
|
||||
olv.SelectedIndexChanged -= new EventHandler(HandleListViewSelectedIndexChanged);
|
||||
olv.BindingContextChanged -= new EventHandler(HandleListViewBindingContextChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected virtual void BindDataSource() {
|
||||
if (this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
this.CurrencyManager.MetaDataChanged += new EventHandler(HandleCurrencyManagerMetaDataChanged);
|
||||
this.CurrencyManager.PositionChanged += new EventHandler(HandleCurrencyManagerPositionChanged);
|
||||
this.CurrencyManager.ListChanged += new ListChangedEventHandler(CurrencyManagerListChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected virtual void UnbindDataSource() {
|
||||
if (this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
this.CurrencyManager.MetaDataChanged -= new EventHandler(HandleCurrencyManagerMetaDataChanged);
|
||||
this.CurrencyManager.PositionChanged -= new EventHandler(HandleCurrencyManagerPositionChanged);
|
||||
this.CurrencyManager.ListChanged -= new ListChangedEventHandler(CurrencyManagerListChanged);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Our data source has changed. Figure out how to handle the new source
|
||||
/// </summary>
|
||||
protected virtual void RebindDataSource() {
|
||||
RebindDataSource(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Our data source has changed. Figure out how to handle the new source
|
||||
/// </summary>
|
||||
protected virtual void RebindDataSource(bool forceDataInitialization) {
|
||||
|
||||
CurrencyManager tempCurrencyManager = null;
|
||||
if (this.ListView != null && this.ListView.BindingContext != null && this.DataSource != null) {
|
||||
tempCurrencyManager = this.ListView.BindingContext[this.DataSource, this.DataMember] as CurrencyManager;
|
||||
}
|
||||
|
||||
// Has our currency manager changed?
|
||||
if (this.CurrencyManager != tempCurrencyManager) {
|
||||
this.UnbindDataSource();
|
||||
this.CurrencyManager = tempCurrencyManager;
|
||||
this.BindDataSource();
|
||||
|
||||
// Our currency manager has changed so we have to initialize a new data source
|
||||
forceDataInitialization = true;
|
||||
}
|
||||
|
||||
if (forceDataInitialization)
|
||||
InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The data source for this control has changed. Reconfigure the control for the new source
|
||||
/// </summary>
|
||||
protected virtual void InitializeDataSource() {
|
||||
if (this.ListView.Frozen || this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
this.CreateColumnsFromSource();
|
||||
this.CreateMissingAspectGettersAndPutters();
|
||||
this.SetListContents();
|
||||
this.ListView.AutoSizeColumns();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take the contents of the currently bound list and put them into the control
|
||||
/// </summary>
|
||||
protected virtual void SetListContents() {
|
||||
this.ListView.Objects = this.CurrencyManager.List;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create columns for the listview based on what properties are available in the data source
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method will create columns if there is not already a column displaying that property.</para>
|
||||
/// </remarks>
|
||||
protected virtual void CreateColumnsFromSource() {
|
||||
if (this.CurrencyManager == null)
|
||||
return;
|
||||
|
||||
// Don't generate any columns in design mode. If we do, the user will see them,
|
||||
// but the Designer won't know about them and won't persist them, which is very confusing
|
||||
if (this.ListView.IsDesignMode)
|
||||
return;
|
||||
|
||||
// Don't create columns if we've been told not to
|
||||
if (!this.AutoGenerateColumns)
|
||||
return;
|
||||
|
||||
// Use a Generator to create columns
|
||||
Generator generator = Generator.Instance as Generator ?? new Generator();
|
||||
|
||||
PropertyDescriptorCollection properties = this.CurrencyManager.GetItemProperties();
|
||||
if (properties.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (PropertyDescriptor property in properties) {
|
||||
|
||||
if (!this.ShouldCreateColumn(property))
|
||||
continue;
|
||||
|
||||
// Create a column
|
||||
OLVColumn column = generator.MakeColumnFromPropertyDescriptor(property);
|
||||
this.ConfigureColumn(column, property);
|
||||
|
||||
// Add it to our list
|
||||
this.ListView.AllColumns.Add(column);
|
||||
}
|
||||
|
||||
generator.PostCreateColumns(this.ListView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decide if a new column should be added to the control to display
|
||||
/// the given property
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool ShouldCreateColumn(PropertyDescriptor property) {
|
||||
|
||||
// Is there a column that already shows this property? If so, we don't show it again
|
||||
if (this.ListView.AllColumns.Exists(delegate(OLVColumn x) { return x.AspectName == property.Name; }))
|
||||
return false;
|
||||
|
||||
// Relationships to other tables turn up as IBindibleLists. Don't make columns to show them.
|
||||
// CHECK: Is this always true? What other things could be here? Constraints? Triggers?
|
||||
if (property.PropertyType == typeof(IBindingList))
|
||||
return false;
|
||||
|
||||
// Ignore anything marked with [OLVIgnore]
|
||||
return property.Attributes[typeof(OLVIgnoreAttribute)] == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the given column to show the given property.
|
||||
/// The title and aspect name of the column are already filled in.
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="property"></param>
|
||||
protected virtual void ConfigureColumn(OLVColumn column, PropertyDescriptor property) {
|
||||
|
||||
column.LastDisplayIndex = this.ListView.AllColumns.Count;
|
||||
|
||||
// If our column is a BLOB, it could be an image, so assign a renderer to draw it.
|
||||
// CONSIDER: Is this a common enough case to warrant this code?
|
||||
if (property.PropertyType == typeof(System.Byte[]))
|
||||
column.Renderer = new ImageRenderer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate aspect getters and putters for any columns that are missing them (and for which we have
|
||||
/// enough information to actually generate a getter)
|
||||
/// </summary>
|
||||
protected virtual void CreateMissingAspectGettersAndPutters() {
|
||||
foreach (OLVColumn x in this.ListView.AllColumns) {
|
||||
OLVColumn column = x; // stack based variable accessible from closures
|
||||
if (column.AspectGetter == null && !String.IsNullOrEmpty(column.AspectName)) {
|
||||
column.AspectGetter = delegate(object row) {
|
||||
// In most cases, rows will be DataRowView objects
|
||||
DataRowView drv = row as DataRowView;
|
||||
if (drv == null)
|
||||
return column.GetAspectByName(row);
|
||||
return (drv.Row.RowState == DataRowState.Detached) ? null : drv[column.AspectName];
|
||||
};
|
||||
}
|
||||
if (column.IsEditable && column.AspectPutter == null && !String.IsNullOrEmpty(column.AspectName)) {
|
||||
column.AspectPutter = delegate(object row, object newValue) {
|
||||
// In most cases, rows will be DataRowView objects
|
||||
DataRowView drv = row as DataRowView;
|
||||
if (drv == null)
|
||||
column.PutAspectByName(row, newValue);
|
||||
else {
|
||||
if (drv.Row.RowState != DataRowState.Detached)
|
||||
drv[column.AspectName] = newValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// CurrencyManager ListChanged event handler.
|
||||
/// Deals with fine-grained changes to list items.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It's actually difficult to deal with these changes in a fine-grained manner.
|
||||
/// If our listview is grouped, then any change may make a new group appear or
|
||||
/// an old group disappear. It is rarely enough to simply update the affected row.
|
||||
/// </remarks>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void CurrencyManagerListChanged(object sender, ListChangedEventArgs e) {
|
||||
Debug.Assert(sender == this.CurrencyManager);
|
||||
|
||||
// Ignore changes make while frozen, since we will do a complete rebuild when we unfreeze
|
||||
if (this.ListView.Frozen)
|
||||
return;
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine(e.ListChangedType);
|
||||
//Stopwatch sw = new Stopwatch();
|
||||
//sw.Start();
|
||||
switch (e.ListChangedType) {
|
||||
|
||||
case ListChangedType.Reset:
|
||||
this.HandleListChangedReset(e);
|
||||
break;
|
||||
|
||||
case ListChangedType.ItemChanged:
|
||||
this.HandleListChangedItemChanged(e);
|
||||
break;
|
||||
|
||||
case ListChangedType.ItemAdded:
|
||||
this.HandleListChangedItemAdded(e);
|
||||
break;
|
||||
|
||||
// An item has gone away.
|
||||
case ListChangedType.ItemDeleted:
|
||||
this.HandleListChangedItemDeleted(e);
|
||||
break;
|
||||
|
||||
// An item has changed its index.
|
||||
case ListChangedType.ItemMoved:
|
||||
this.HandleListChangedItemMoved(e);
|
||||
break;
|
||||
|
||||
// Something has changed in the metadata.
|
||||
// CHECK: When are these events actually fired?
|
||||
case ListChangedType.PropertyDescriptorAdded:
|
||||
case ListChangedType.PropertyDescriptorChanged:
|
||||
case ListChangedType.PropertyDescriptorDeleted:
|
||||
this.HandleListChangedMetadataChanged(e);
|
||||
break;
|
||||
}
|
||||
//sw.Stop();
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("Processing {0} event on {1} rows took {2}ms", e.ListChangedType, this.ListView.GetItemCount(), sw.ElapsedMilliseconds));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle PropertyDescriptor* events
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedMetadataChanged(ListChangedEventArgs e) {
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle ItemMoved event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedItemMoved(ListChangedEventArgs e) {
|
||||
// When is this actually triggered?
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the ItemDeleted event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedItemDeleted(ListChangedEventArgs e) {
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle an ItemAdded event.
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedItemAdded(ListChangedEventArgs e) {
|
||||
// We get this event twice if certain grid controls are used to add a new row to a
|
||||
// datatable: once when the editing of a new row begins, and once again when that
|
||||
// editing commits. (If the user cancels the creation of the new row, we never see
|
||||
// the second creation.) We detect this by seeing if this is a view on a row in a
|
||||
// DataTable, and if it is, testing to see if it's a new row under creation.
|
||||
|
||||
Object newRow = this.CurrencyManager.List[e.NewIndex];
|
||||
DataRowView drv = newRow as DataRowView;
|
||||
if (drv == null || !drv.IsNew) {
|
||||
// Either we're not dealing with a view on a data table, or this is the commit
|
||||
// notification. Either way, this is the final notification, so we want to
|
||||
// handle the new row now!
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the Reset event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListChangedReset(ListChangedEventArgs e) {
|
||||
// The whole list has changed utterly, so reload it.
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle ItemChanged event. This is triggered when a single item
|
||||
/// has changed, so just refresh that one item.
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>Even in this simple case, we should probably rebuild the list.
|
||||
/// For example, the change could put the item into its own new group.</remarks>
|
||||
protected virtual void HandleListChangedItemChanged(ListChangedEventArgs e) {
|
||||
// A single item has changed, so just refresh that.
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("HandleListChangedItemChanged: {0}, {1}", e.NewIndex, e.PropertyDescriptor.Name));
|
||||
|
||||
Object changedRow = this.CurrencyManager.List[e.NewIndex];
|
||||
this.ListView.RefreshObject(changedRow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CurrencyManager calls this if the data source looks
|
||||
/// different. We just reload everything.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// CHECK: Do we need this if we are handle ListChanged metadata events?
|
||||
/// </remarks>
|
||||
protected virtual void HandleCurrencyManagerMetaDataChanged(object sender, EventArgs e) {
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the CurrencyManager when the currently selected item
|
||||
/// changes. We update the ListView selection so that we stay in sync
|
||||
/// with any other controls bound to the same source.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleCurrencyManagerPositionChanged(object sender, EventArgs e) {
|
||||
int index = this.CurrencyManager.Position;
|
||||
|
||||
// Make sure the index is sane (-1 pops up from time to time)
|
||||
if (index < 0 || index >= this.ListView.GetItemCount())
|
||||
return;
|
||||
|
||||
// Avoid recursion. If we are currently changing the index, don't
|
||||
// start the process again.
|
||||
if (this.isChangingIndex)
|
||||
return;
|
||||
|
||||
try {
|
||||
this.isChangingIndex = true;
|
||||
|
||||
this.ChangePosition(index);
|
||||
}
|
||||
finally {
|
||||
this.isChangingIndex = false;
|
||||
}
|
||||
}
|
||||
private bool isChangingIndex = false;
|
||||
|
||||
/// <summary>
|
||||
/// Change the control's position (which is it's currently selected row)
|
||||
/// to the nth row in the dataset
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the row to be selected</param>
|
||||
protected virtual void ChangePosition(int index) {
|
||||
// We can't use the index directly, since our listview may be sorted
|
||||
this.ListView.SelectedObject = this.CurrencyManager.List[index];
|
||||
|
||||
// THINK: Do we always want to bring it into view?
|
||||
if (this.ListView.SelectedIndices.Count > 0)
|
||||
this.ListView.EnsureVisible(this.ListView.SelectedIndices[0]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ObjectListView event handlers
|
||||
|
||||
/// <summary>
|
||||
/// Handle the selection changing in our ListView.
|
||||
/// We need to tell our currency manager about the new position.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListViewSelectedIndexChanged(object sender, EventArgs e) {
|
||||
// Prevent recursion
|
||||
if (this.isChangingIndex)
|
||||
return;
|
||||
|
||||
// If we are bound to a data source, and only one item is selected,
|
||||
// tell the currency manager which item is selected.
|
||||
if (this.ListView.SelectedIndices.Count == 1 && this.CurrencyManager != null) {
|
||||
try {
|
||||
this.isChangingIndex = true;
|
||||
|
||||
// We can't use the selectedIndex directly, since our listview may be sorted.
|
||||
// So we have to find the index of the selected object within the original list.
|
||||
this.CurrencyManager.Position = this.CurrencyManager.List.IndexOf(this.ListView.SelectedObject);
|
||||
} finally {
|
||||
this.isChangingIndex = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the frozenness of our ListView changing.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListViewFreezing(object sender, FreezeEventArgs e) {
|
||||
if (!alreadyFreezing && e.FreezeLevel == 0) {
|
||||
try {
|
||||
alreadyFreezing = true;
|
||||
this.RebindDataSource(true);
|
||||
} finally {
|
||||
alreadyFreezing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool alreadyFreezing = false;
|
||||
|
||||
/// <summary>
|
||||
/// Handle a change to the BindingContext of our ListView.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void HandleListViewBindingContextChanged(object sender, EventArgs e) {
|
||||
this.RebindDataSource(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Delegates - All delegate definitions used in ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
#region Delegate declarations
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to extract an aspect from a row object
|
||||
/// </summary>
|
||||
public delegate Object AspectGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to put a changed value back into a model object
|
||||
/// </summary>
|
||||
public delegate void AspectPutterDelegate(Object rowObject, Object newValue);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates can be used to convert an aspect value to a display string,
|
||||
/// instead of using the default ToString()
|
||||
/// </summary>
|
||||
public delegate string AspectToStringConverterDelegate(Object value);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to get the tooltip for a cell
|
||||
/// </summary>
|
||||
public delegate String CellToolTipGetterDelegate(OLVColumn column, Object modelObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to the state of the checkbox for a row object.
|
||||
/// </summary>
|
||||
/// <remarks><para>
|
||||
/// For reasons known only to someone in Microsoft, we can only set
|
||||
/// a boolean on the ListViewItem to indicate it's "checked-ness", but when
|
||||
/// we receive update events, we have to use a tristate CheckState. So we can
|
||||
/// be told about an indeterminate state, but we can't set it ourselves.
|
||||
/// </para>
|
||||
/// <para>As of version 2.0, we can now return indeterminate state.</para>
|
||||
/// </remarks>
|
||||
public delegate CheckState CheckStateGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to get the state of the checkbox for a row object.
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool BooleanCheckStateGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to put a changed check state back into a model object
|
||||
/// </summary>
|
||||
public delegate CheckState CheckStatePutterDelegate(Object rowObject, CheckState newValue);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to put a changed check state back into a model object
|
||||
/// </summary>
|
||||
/// <param name="rowObject"></param>
|
||||
/// <param name="newValue"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool BooleanCheckStatePutterDelegate(Object rowObject, bool newValue);
|
||||
|
||||
/// <summary>
|
||||
/// The callbacks for RightColumnClick events
|
||||
/// </summary>
|
||||
public delegate void ColumnRightClickEventHandler(object sender, ColumnClickEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// This delegate will be used to own draw header column.
|
||||
/// </summary>
|
||||
public delegate bool HeaderDrawingDelegate(Graphics g, Rectangle r, int columnIndex, OLVColumn column, bool isPressed, HeaderStateStyle stateStyle);
|
||||
|
||||
/// <summary>
|
||||
/// This delegate is called when a group has been created but not yet made
|
||||
/// into a real ListViewGroup. The user can take this opportunity to fill
|
||||
/// in lots of other details about the group.
|
||||
/// </summary>
|
||||
public delegate void GroupFormatterDelegate(OLVGroup group, GroupingParameters parms);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to retrieve the object that is the key of the group to which the given row belongs.
|
||||
/// </summary>
|
||||
public delegate Object GroupKeyGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to convert a group key into a title for the group
|
||||
/// </summary>
|
||||
public delegate string GroupKeyToTitleConverterDelegate(Object groupKey);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to get the tooltip for a column header
|
||||
/// </summary>
|
||||
public delegate String HeaderToolTipGetterDelegate(OLVColumn column);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to fetch the image selector that should be used
|
||||
/// to choose an image for this column.
|
||||
/// </summary>
|
||||
public delegate Object ImageGetterDelegate(Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to draw a cell
|
||||
/// </summary>
|
||||
public delegate bool RenderDelegate(EventArgs e, Graphics g, Rectangle r, Object rowObject);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to fetch a row object for virtual lists
|
||||
/// </summary>
|
||||
public delegate Object RowGetterDelegate(int rowIndex);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to format a listviewitem before it is added to the control.
|
||||
/// </summary>
|
||||
public delegate void RowFormatterDelegate(OLVListItem olvItem);
|
||||
|
||||
/// <summary>
|
||||
/// These delegates are used to sort the listview in some custom fashion
|
||||
/// </summary>
|
||||
public delegate void SortDelegate(OLVColumn column, SortOrder sortOrder);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* DragSource.cs - Add drag source functionality to an ObjectListView
|
||||
*
|
||||
* UNFINISHED
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 2009-03-17 5:15 PM
|
||||
*
|
||||
* Change log:
|
||||
* v2.3
|
||||
* 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default
|
||||
* (since MS didn't make it part of the 'All' value)
|
||||
* v2.2
|
||||
* 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs
|
||||
* 2009-03-17 JPP - Initial version
|
||||
*
|
||||
* Copyright (C) 2009 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// An IDragSource controls how drag out from the ObjectListView will behave
|
||||
/// </summary>
|
||||
public interface IDragSource
|
||||
{
|
||||
/// <summary>
|
||||
/// A drag operation is beginning. Return the data object that will be used
|
||||
/// for data transfer. Return null to prevent the drag from starting.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The returned object is later passed to the GetAllowedEffect() and EndDrag()
|
||||
/// methods.
|
||||
/// </remarks>
|
||||
/// <param name="olv">What ObjectListView is being dragged from.</param>
|
||||
/// <param name="button">Which mouse button is down?</param>
|
||||
/// <param name="item">What item was directly dragged by the user? There may be more than just this
|
||||
/// item selected.</param>
|
||||
/// <returns>The data object that will be used for data transfer. This will often be a subclass
|
||||
/// of DataObject, but does not need to be.</returns>
|
||||
Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item);
|
||||
|
||||
/// <summary>
|
||||
/// What operations are possible for this drag? This controls the icon shown during the drag
|
||||
/// </summary>
|
||||
/// <param name="dragObject">The data object returned by StartDrag()</param>
|
||||
/// <returns>A combination of DragDropEffects flags</returns>
|
||||
DragDropEffects GetAllowedEffects(Object dragObject);
|
||||
|
||||
/// <summary>
|
||||
/// The drag operation is complete. Do whatever is necessary to complete the action.
|
||||
/// </summary>
|
||||
/// <param name="dragObject">The data object returned by StartDrag()</param>
|
||||
/// <param name="effect">The value returned from GetAllowedEffects()</param>
|
||||
void EndDrag(Object dragObject, DragDropEffects effect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A do-nothing implementation of IDragSource that can be safely subclassed.
|
||||
/// </summary>
|
||||
public class AbstractDragSource : IDragSource
|
||||
{
|
||||
#region IDragSource Members
|
||||
|
||||
/// <summary>
|
||||
/// See IDragSource documentation
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="button"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See IDragSource documentation
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public virtual DragDropEffects GetAllowedEffects(Object data) {
|
||||
return DragDropEffects.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See IDragSource documentation
|
||||
/// </summary>
|
||||
/// <param name="dragObject"></param>
|
||||
/// <param name="effect"></param>
|
||||
public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A reasonable implementation of IDragSource that provides normal
|
||||
/// drag source functionality. It creates a data object that supports
|
||||
/// inter-application dragging of text and HTML representation of
|
||||
/// the dragged rows. It can optionally force a refresh of all dragged
|
||||
/// rows when the drag is complete.
|
||||
/// </summary>
|
||||
/// <remarks>Subclasses can override GetDataObject() to add new
|
||||
/// data formats to the data transfer object.</remarks>
|
||||
public class SimpleDragSource : IDragSource
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a SimpleDragSource
|
||||
/// </summary>
|
||||
public SimpleDragSource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a SimpleDragSource that refreshes the dragged rows when
|
||||
/// the drag is complete
|
||||
/// </summary>
|
||||
/// <param name="refreshAfterDrop"></param>
|
||||
public SimpleDragSource(bool refreshAfterDrop) {
|
||||
this.RefreshAfterDrop = refreshAfterDrop;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the dragged rows should be refreshed when the
|
||||
/// drag operation is complete.
|
||||
/// </summary>
|
||||
public bool RefreshAfterDrop {
|
||||
get { return refreshAfterDrop; }
|
||||
set { refreshAfterDrop = value; }
|
||||
}
|
||||
private bool refreshAfterDrop;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDragSource Members
|
||||
|
||||
/// <summary>
|
||||
/// Create a DataObject when the user does a left mouse drag operation.
|
||||
/// See IDragSource for further information.
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="button"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Object StartDrag(ObjectListView olv, MouseButtons button, OLVListItem item) {
|
||||
// We only drag on left mouse
|
||||
if (button != MouseButtons.Left)
|
||||
return null;
|
||||
|
||||
return this.CreateDataObject(olv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Which operations are allowed in the operation? By default, all operations are supported.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns>All opertions are supported</returns>
|
||||
public virtual DragDropEffects GetAllowedEffects(Object data) {
|
||||
return DragDropEffects.All | DragDropEffects.Link; // why didn't MS include 'Link' in 'All'??
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The drag operation is finished. Refreshe the dragged rows if so configured.
|
||||
/// </summary>
|
||||
/// <param name="dragObject"></param>
|
||||
/// <param name="effect"></param>
|
||||
public virtual void EndDrag(Object dragObject, DragDropEffects effect) {
|
||||
OLVDataObject data = dragObject as OLVDataObject;
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
if (this.RefreshAfterDrop)
|
||||
data.ListView.RefreshObjects(data.ModelObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a data object that will be used to as the data object
|
||||
/// for the drag operation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Subclasses can override this method add new formats to the data object.
|
||||
/// </remarks>
|
||||
/// <param name="olv">The ObjectListView that is the source of the drag</param>
|
||||
/// <returns>A data object for the drag</returns>
|
||||
protected virtual object CreateDataObject(ObjectListView olv) {
|
||||
OLVDataObject data = new OLVDataObject(olv);
|
||||
data.CreateTextFormats();
|
||||
return data;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A data transfer object that knows how to transform a list of model
|
||||
/// objects into a text and HTML representation.
|
||||
/// </summary>
|
||||
public class OLVDataObject : DataObject
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a data object from the selected objects in the given ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="olv">The source of the data object</param>
|
||||
public OLVDataObject(ObjectListView olv) : this(olv, olv.SelectedObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a data object which operates on the given model objects
|
||||
/// in the given ObjectListView
|
||||
/// </summary>
|
||||
/// <param name="olv">The source of the data object</param>
|
||||
/// <param name="modelObjects">The model objects to be put into the data object</param>
|
||||
public OLVDataObject(ObjectListView olv, IList modelObjects) {
|
||||
this.objectListView = olv;
|
||||
this.modelObjects = modelObjects;
|
||||
this.includeHiddenColumns = olv.IncludeHiddenColumnsInDataTransfer;
|
||||
this.includeColumnHeaders = olv.IncludeColumnHeadersInCopy;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether hidden columns will also be included in the text
|
||||
/// and HTML representation. If this is false, only visible columns will
|
||||
/// be included.
|
||||
/// </summary>
|
||||
public bool IncludeHiddenColumns {
|
||||
get { return includeHiddenColumns; }
|
||||
}
|
||||
private bool includeHiddenColumns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether column headers will also be included in the text
|
||||
/// and HTML representation.
|
||||
/// </summary>
|
||||
public bool IncludeColumnHeaders
|
||||
{
|
||||
get { return includeColumnHeaders; }
|
||||
}
|
||||
private bool includeColumnHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView that is being used as the source of the data
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return objectListView; }
|
||||
}
|
||||
private ObjectListView objectListView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model objects that are to be placed in the data object
|
||||
/// </summary>
|
||||
public IList ModelObjects {
|
||||
get { return modelObjects; }
|
||||
}
|
||||
private IList modelObjects = new ArrayList();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Put a text and HTML representation of our model objects
|
||||
/// into the data object.
|
||||
/// </summary>
|
||||
public void CreateTextFormats() {
|
||||
IList<OLVColumn> columns = this.IncludeHiddenColumns ? this.ListView.AllColumns : this.ListView.ColumnsInDisplayOrder;
|
||||
|
||||
// Build text and html versions of the selection
|
||||
StringBuilder sbText = new StringBuilder();
|
||||
StringBuilder sbHtml = new StringBuilder("<table>");
|
||||
|
||||
// Include column headers
|
||||
if (includeColumnHeaders)
|
||||
{
|
||||
sbHtml.Append("<tr><td>");
|
||||
foreach (OLVColumn col in columns)
|
||||
{
|
||||
if (col != columns[0])
|
||||
{
|
||||
sbText.Append("\t");
|
||||
sbHtml.Append("</td><td>");
|
||||
}
|
||||
string strValue = col.Text;
|
||||
sbText.Append(strValue);
|
||||
sbHtml.Append(strValue); //TODO: Should encode the string value
|
||||
}
|
||||
sbText.AppendLine();
|
||||
sbHtml.AppendLine("</td></tr>");
|
||||
}
|
||||
|
||||
foreach (object modelObject in this.ModelObjects)
|
||||
{
|
||||
sbHtml.Append("<tr><td>");
|
||||
foreach (OLVColumn col in columns) {
|
||||
if (col != columns[0]) {
|
||||
sbText.Append("\t");
|
||||
sbHtml.Append("</td><td>");
|
||||
}
|
||||
string strValue = col.GetStringValue(modelObject);
|
||||
sbText.Append(strValue);
|
||||
sbHtml.Append(strValue); //TODO: Should encode the string value
|
||||
}
|
||||
sbText.AppendLine();
|
||||
sbHtml.AppendLine("</td></tr>");
|
||||
}
|
||||
sbHtml.AppendLine("</table>");
|
||||
|
||||
// Put both the text and html versions onto the clipboard.
|
||||
// For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format,
|
||||
// but using SetData() does.
|
||||
//this.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
|
||||
this.SetData(sbText.ToString());
|
||||
this.SetText(ConvertToHtmlFragment(sbHtml.ToString()), TextDataFormat.Html);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make a HTML representation of our model objects
|
||||
/// </summary>
|
||||
public string CreateHtml() {
|
||||
IList<OLVColumn> columns = this.ListView.ColumnsInDisplayOrder;
|
||||
|
||||
// Build html version of the selection
|
||||
StringBuilder sbHtml = new StringBuilder("<table>");
|
||||
|
||||
foreach (object modelObject in this.ModelObjects) {
|
||||
sbHtml.Append("<tr><td>");
|
||||
foreach (OLVColumn col in columns) {
|
||||
if (col != columns[0]) {
|
||||
sbHtml.Append("</td><td>");
|
||||
}
|
||||
string strValue = col.GetStringValue(modelObject);
|
||||
sbHtml.Append(strValue); //TODO: Should encode the string value
|
||||
}
|
||||
sbHtml.AppendLine("</td></tr>");
|
||||
}
|
||||
sbHtml.AppendLine("</table>");
|
||||
|
||||
return sbHtml.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the fragment of HTML into the Clipboards HTML format.
|
||||
/// </summary>
|
||||
/// <remarks>The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx
|
||||
/// </remarks>
|
||||
/// <param name="fragment">The HTML to put onto the clipboard. It must be valid HTML!</param>
|
||||
/// <returns>A string that can be put onto the clipboard and will be recognized as HTML</returns>
|
||||
private string ConvertToHtmlFragment(string fragment) {
|
||||
// Minimal implementation of HTML clipboard format
|
||||
string source = "http://www.codeproject.com/KB/list/ObjectListView.aspx";
|
||||
|
||||
const String MARKER_BLOCK =
|
||||
"Version:1.0\r\n" +
|
||||
"StartHTML:{0,8}\r\n" +
|
||||
"EndHTML:{1,8}\r\n" +
|
||||
"StartFragment:{2,8}\r\n" +
|
||||
"EndFragment:{3,8}\r\n" +
|
||||
"StartSelection:{2,8}\r\n" +
|
||||
"EndSelection:{3,8}\r\n" +
|
||||
"SourceURL:{4}\r\n" +
|
||||
"{5}";
|
||||
|
||||
int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, source, "").Length;
|
||||
|
||||
const String DEFAULT_HTML_BODY =
|
||||
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
|
||||
"<HTML><HEAD></HEAD><BODY><!--StartFragment-->{0}<!--EndFragment--></BODY></HTML>";
|
||||
|
||||
string html = String.Format(DEFAULT_HTML_BODY, fragment);
|
||||
int startFragment = prefixLength + html.IndexOf(fragment);
|
||||
int endFragment = startFragment + fragment.Length;
|
||||
|
||||
return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, source, html);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Enums - All enum definitions used in ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
public partial class ObjectListView {
|
||||
/// <summary>
|
||||
/// How does a user indicate that they want to edit cells?
|
||||
/// </summary>
|
||||
public enum CellEditActivateMode {
|
||||
/// <summary>
|
||||
/// This list cannot be edited. F2 does nothing.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A single click on a <strong>subitem</strong> will edit the value. Single clicking the primary column,
|
||||
/// selects the row just like normal. The user must press F2 to edit the primary column.
|
||||
/// </summary>
|
||||
SingleClick = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Double clicking a subitem or the primary column will edit that cell.
|
||||
/// F2 will edit the primary column.
|
||||
/// </summary>
|
||||
DoubleClick = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Pressing F2 is the only way to edit the cells. Once the primary column is being edited,
|
||||
/// the other cells in the row can be edited by pressing Tab.
|
||||
/// </summary>
|
||||
F2Only = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These values specify how column selection will be presented to the user
|
||||
/// </summary>
|
||||
public enum ColumnSelectBehaviour {
|
||||
/// <summary>
|
||||
/// No column selection will be presented
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The columns will be show in the main menu
|
||||
/// </summary>
|
||||
InlineMenu,
|
||||
|
||||
/// <summary>
|
||||
/// The columns will be shown in a submenu
|
||||
/// </summary>
|
||||
Submenu,
|
||||
|
||||
/// <summary>
|
||||
/// A model dialog will be presented to allow the user to choose columns
|
||||
/// </summary>
|
||||
ModelDialog,
|
||||
|
||||
/*
|
||||
* NonModelDialog is just a little bit tricky since the OLV can change views while the dialog is showing
|
||||
* So, just comment this out for the time being.
|
||||
|
||||
/// <summary>
|
||||
/// A non-model dialog will be presented to allow the user to choose columns
|
||||
/// </summary>
|
||||
NonModelDialog
|
||||
*
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* GroupingParameters - All the data that is used to create groups in an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// This class contains all the settings used when groups are created
|
||||
/// </summary>
|
||||
public class GroupingParameters {
|
||||
/// <summary>
|
||||
/// Create a GroupingParameters
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <param name="groupByColumn"></param>
|
||||
/// <param name="groupByOrder"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="order"></param>
|
||||
/// <param name="secondaryColumn"></param>
|
||||
/// <param name="secondaryOrder"></param>
|
||||
/// <param name="titleFormat"></param>
|
||||
/// <param name="titleSingularFormat"></param>
|
||||
/// <param name="sortItemsByPrimaryColumn"></param>
|
||||
public GroupingParameters(ObjectListView olv, OLVColumn groupByColumn, SortOrder groupByOrder,
|
||||
OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder,
|
||||
string titleFormat, string titleSingularFormat, bool sortItemsByPrimaryColumn) {
|
||||
this.ListView = olv;
|
||||
this.GroupByColumn = groupByColumn;
|
||||
this.GroupByOrder = groupByOrder;
|
||||
this.PrimarySort = column;
|
||||
this.PrimarySortOrder = order;
|
||||
this.SecondarySort = secondaryColumn;
|
||||
this.SecondarySortOrder = secondaryOrder;
|
||||
this.SortItemsByPrimaryColumn = sortItemsByPrimaryColumn;
|
||||
this.TitleFormat = titleFormat;
|
||||
this.TitleSingularFormat = titleSingularFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ObjectListView being grouped
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return this.listView; }
|
||||
set { this.listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column used to create groups
|
||||
/// </summary>
|
||||
public OLVColumn GroupByColumn {
|
||||
get { return this.groupByColumn; }
|
||||
set { this.groupByColumn = value; }
|
||||
}
|
||||
private OLVColumn groupByColumn;
|
||||
|
||||
/// <summary>
|
||||
/// In what order will the groups themselves be sorted?
|
||||
/// </summary>
|
||||
public SortOrder GroupByOrder {
|
||||
get { return this.groupByOrder; }
|
||||
set { this.groupByOrder = value; }
|
||||
}
|
||||
private SortOrder groupByOrder;
|
||||
|
||||
/// <summary>
|
||||
/// If this is set, this comparer will be used to order the groups
|
||||
/// </summary>
|
||||
public IComparer<OLVGroup> GroupComparer {
|
||||
get { return this.groupComparer; }
|
||||
set { this.groupComparer = value; }
|
||||
}
|
||||
private IComparer<OLVGroup> groupComparer;
|
||||
|
||||
/// <summary>
|
||||
/// If this is set, this comparer will be used to order items within each group
|
||||
/// </summary>
|
||||
public IComparer<OLVListItem> ItemComparer {
|
||||
get { return this.itemComparer; }
|
||||
set { this.itemComparer = value; }
|
||||
}
|
||||
private IComparer<OLVListItem> itemComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column that will be the primary sort
|
||||
/// </summary>
|
||||
public OLVColumn PrimarySort {
|
||||
get { return this.primarySort; }
|
||||
set { this.primarySort = value; }
|
||||
}
|
||||
private OLVColumn primarySort;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ordering for the primary sort
|
||||
/// </summary>
|
||||
public SortOrder PrimarySortOrder {
|
||||
get { return this.primarySortOrder; }
|
||||
set { this.primarySortOrder = value; }
|
||||
}
|
||||
private SortOrder primarySortOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column used for secondary sorting
|
||||
/// </summary>
|
||||
public OLVColumn SecondarySort {
|
||||
get { return this.secondarySort; }
|
||||
set { this.secondarySort = value; }
|
||||
}
|
||||
private OLVColumn secondarySort;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ordering for the secondary sort
|
||||
/// </summary>
|
||||
public SortOrder SecondarySortOrder {
|
||||
get { return this.secondarySortOrder; }
|
||||
set { this.secondarySortOrder = value; }
|
||||
}
|
||||
private SortOrder secondarySortOrder;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title format used for groups with zero or more than one element
|
||||
/// </summary>
|
||||
public string TitleFormat {
|
||||
get { return this.titleFormat; }
|
||||
set { this.titleFormat = value; }
|
||||
}
|
||||
private string titleFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title format used for groups with only one element
|
||||
/// </summary>
|
||||
public string TitleSingularFormat {
|
||||
get { return this.titleSingularFormat; }
|
||||
set { this.titleSingularFormat = value; }
|
||||
}
|
||||
private string titleSingularFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the items should be sorted by the primary column
|
||||
/// </summary>
|
||||
public bool SortItemsByPrimaryColumn {
|
||||
get { return this.sortItemsByPrimaryColumn; }
|
||||
set { this.sortItemsByPrimaryColumn = value; }
|
||||
}
|
||||
private bool sortItemsByPrimaryColumn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
/*
|
||||
* Groups - Enhancements to the normal ListViewGroup
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 22/08/2009 6:03PM
|
||||
*
|
||||
* Change log:
|
||||
* v2.3
|
||||
* 2009-09-09 JPP - Added Collapsed and Collapsible properties
|
||||
* 2009-09-01 JPP - Cleaned up code, added more docs
|
||||
* - Works under VS2005 again
|
||||
* 2009-08-22 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
* - Implement subseting
|
||||
* - Implement footer items
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// These values indicate what is the state of the group. These values
|
||||
/// are taken directly from the SDK and many are not used by ObjectListView.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GroupState
|
||||
{
|
||||
/// <summary>
|
||||
/// Normal
|
||||
/// </summary>
|
||||
LVGS_NORMAL = 0x0,
|
||||
|
||||
/// <summary>
|
||||
/// Collapsed
|
||||
/// </summary>
|
||||
LVGS_COLLAPSED = 0x1,
|
||||
|
||||
/// <summary>
|
||||
/// Hidden
|
||||
/// </summary>
|
||||
LVGS_HIDDEN = 0x2,
|
||||
|
||||
/// <summary>
|
||||
/// NoHeader
|
||||
/// </summary>
|
||||
LVGS_NOHEADER = 0x4,
|
||||
|
||||
/// <summary>
|
||||
/// Can be collapsed
|
||||
/// </summary>
|
||||
LVGS_COLLAPSIBLE = 0x8,
|
||||
|
||||
/// <summary>
|
||||
/// Has focus
|
||||
/// </summary>
|
||||
LVGS_FOCUSED = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Is Selected
|
||||
/// </summary>
|
||||
LVGS_SELECTED = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// Is subsetted
|
||||
/// </summary>
|
||||
LVGS_SUBSETED = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Subset link has focus
|
||||
/// </summary>
|
||||
LVGS_SUBSETLINKFOCUSED = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// All styles
|
||||
/// </summary>
|
||||
LVGS_ALL = 0xFFFF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This mask indicates which members of a LVGROUP have valid data. These values
|
||||
/// are taken directly from the SDK and many are not used by ObjectListView.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GroupMask
|
||||
{
|
||||
/// <summary>
|
||||
/// No mask
|
||||
/// </summary>
|
||||
LVGF_NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Group has header
|
||||
/// </summary>
|
||||
LVGF_HEADER = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Group has footer
|
||||
/// </summary>
|
||||
LVGF_FOOTER = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Group has state
|
||||
/// </summary>
|
||||
LVGF_STATE = 4,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGF_ALIGN = 8,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGF_GROUPID = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// pszSubtitle is valid
|
||||
/// </summary>
|
||||
LVGF_SUBTITLE = 0x00100,
|
||||
|
||||
/// <summary>
|
||||
/// pszTask is valid
|
||||
/// </summary>
|
||||
LVGF_TASK = 0x00200,
|
||||
|
||||
/// <summary>
|
||||
/// pszDescriptionTop is valid
|
||||
/// </summary>
|
||||
LVGF_DESCRIPTIONTOP = 0x00400,
|
||||
|
||||
/// <summary>
|
||||
/// pszDescriptionBottom is valid
|
||||
/// </summary>
|
||||
LVGF_DESCRIPTIONBOTTOM = 0x00800,
|
||||
|
||||
/// <summary>
|
||||
/// iTitleImage is valid
|
||||
/// </summary>
|
||||
LVGF_TITLEIMAGE = 0x01000,
|
||||
|
||||
/// <summary>
|
||||
/// iExtendedImage is valid
|
||||
/// </summary>
|
||||
LVGF_EXTENDEDIMAGE = 0x02000,
|
||||
|
||||
/// <summary>
|
||||
/// iFirstItem and cItems are valid
|
||||
/// </summary>
|
||||
LVGF_ITEMS = 0x04000,
|
||||
|
||||
/// <summary>
|
||||
/// pszSubsetTitle is valid
|
||||
/// </summary>
|
||||
LVGF_SUBSET = 0x08000,
|
||||
|
||||
/// <summary>
|
||||
/// readonly, cItems holds count of items in visible subset, iFirstItem is valid
|
||||
/// </summary>
|
||||
LVGF_SUBSETITEMS = 0x10000
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This mask indicates which members of a GROUPMETRICS structure are valid
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum GroupMetricsMask
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_BORDERSIZE = 1,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_BORDERCOLOR = 2,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVGMF_TEXTCOLOR = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class enhance the capabilities of a normal ListViewGroup,
|
||||
/// enabling the functionality that was released in v6 of the common controls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// In this implementation (2009-09), these objects are essentially passive.
|
||||
/// Setting properties does not automatically change the associated group in
|
||||
/// the listview. Collapsed and Collapsible are two exceptions to this and
|
||||
/// give immediate results.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This really should be a subclass of ListViewGroup, but that class is
|
||||
/// sealed (why is that?). So this class provides the same interface as a
|
||||
/// ListViewGroup, plus many other new properties.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class OLVGroup
|
||||
{
|
||||
#region Creation
|
||||
|
||||
/// <summary>
|
||||
/// Create an OLVGroup
|
||||
/// </summary>
|
||||
public OLVGroup() : this("Default group header") {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a group with the given title
|
||||
/// </summary>
|
||||
/// <param name="header">Title of the group</param>
|
||||
public OLVGroup(string header) {
|
||||
this.Header = header;
|
||||
this.Id = OLVGroup.nextId++;
|
||||
this.TitleImage = -1;
|
||||
this.ExtendedImage = -1;
|
||||
}
|
||||
private static int nextId;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bottom description of the group
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Descriptions only appear when group is centered and there is a title image
|
||||
/// </remarks>
|
||||
public string BottomDescription {
|
||||
get { return this.bottomDescription; }
|
||||
set { this.bottomDescription = value; }
|
||||
}
|
||||
private string bottomDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not this group is collapsed
|
||||
/// </summary>
|
||||
public bool Collapsed {
|
||||
get { return this.GetOneState(GroupState.LVGS_COLLAPSED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_COLLAPSED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not this group can be collapsed
|
||||
/// </summary>
|
||||
public bool Collapsible {
|
||||
get { return this.GetOneState(GroupState.LVGS_COLLAPSIBLE); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_COLLAPSIBLE); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets some representation of the contents of this group
|
||||
/// </summary>
|
||||
/// <remarks>This is user defined (like Tag)</remarks>
|
||||
public IList Contents {
|
||||
get { return this.contents; }
|
||||
set { this.contents = value; }
|
||||
}
|
||||
private IList contents;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this group has been created.
|
||||
/// </summary>
|
||||
public bool Created {
|
||||
get { return this.ListView != null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the int or string that will select the extended image to be shown against the title
|
||||
/// </summary>
|
||||
public object ExtendedImage {
|
||||
get { return this.extendedImage; }
|
||||
set { this.extendedImage = value; }
|
||||
}
|
||||
private object extendedImage;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the footer of the group
|
||||
/// </summary>
|
||||
public string Footer {
|
||||
get { return this.footer; }
|
||||
set { this.footer = value; }
|
||||
}
|
||||
private string footer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal id of our associated ListViewGroup.
|
||||
/// </summary>
|
||||
public int GroupId {
|
||||
get {
|
||||
if (this.ListViewGroup == null)
|
||||
return this.Id;
|
||||
|
||||
// Use reflection to get around the access control on the ID property
|
||||
if (OLVGroup.groupIdPropInfo == null) {
|
||||
OLVGroup.groupIdPropInfo = typeof(ListViewGroup).GetProperty("ID",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
System.Diagnostics.Debug.Assert(OLVGroup.groupIdPropInfo != null);
|
||||
}
|
||||
|
||||
int? groupId = OLVGroup.groupIdPropInfo.GetValue(this.ListViewGroup, null) as int?;
|
||||
return groupId.HasValue ? groupId.Value : -1;
|
||||
}
|
||||
}
|
||||
private static PropertyInfo groupIdPropInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the header of the group
|
||||
/// </summary>
|
||||
public string Header {
|
||||
get { return this.header; }
|
||||
set { this.header = value; }
|
||||
}
|
||||
private string header;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the horizontal alignment of the group header
|
||||
/// </summary>
|
||||
public HorizontalAlignment HeaderAlignment {
|
||||
get { return this.headerAlignment; }
|
||||
set { this.headerAlignment = value; }
|
||||
}
|
||||
private HorizontalAlignment headerAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the internally created id of the group
|
||||
/// </summary>
|
||||
public int Id {
|
||||
get { return this.id; }
|
||||
set { this.id = value; }
|
||||
}
|
||||
private int id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets ListViewItems that are members of this group
|
||||
/// </summary>
|
||||
/// <remarks>Listener of the BeforeCreatingGroups event can populate this collection.
|
||||
/// It is only used on non-virtual lists.</remarks>
|
||||
public IList<OLVListItem> Items {
|
||||
get { return this.items; }
|
||||
set { this.items = value; }
|
||||
}
|
||||
private IList<OLVListItem> items = new List<OLVListItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key that was used to partition objects into this group
|
||||
/// </summary>
|
||||
/// <remarks>This is user defined (like Tag)</remarks>
|
||||
public object Key {
|
||||
get { return this.key; }
|
||||
set { this.key = value; }
|
||||
}
|
||||
private object key;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView that this group belongs to
|
||||
/// </summary>
|
||||
/// <remarks>If this is null, the group has not yet been created.</remarks>
|
||||
public ObjectListView ListView {
|
||||
get { return this.listView; }
|
||||
protected set { this.listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the group
|
||||
/// </summary>
|
||||
/// <remarks>As of 2009-09-01, this property is not used.</remarks>
|
||||
public string Name {
|
||||
get { return this.name; }
|
||||
set { this.name = value; }
|
||||
}
|
||||
private string name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this group is focused
|
||||
/// </summary>
|
||||
public bool Focused
|
||||
{
|
||||
get { return this.GetOneState(GroupState.LVGS_FOCUSED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_FOCUSED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this group is selected
|
||||
/// </summary>
|
||||
public bool Selected
|
||||
{
|
||||
get { return this.GetOneState(GroupState.LVGS_SELECTED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_SELECTED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text that will show that this group is subsetted
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As of WinSDK v7.0, subsetting of group is officially unimplemented.
|
||||
/// We can get around this using undocumented interfaces and may do so.
|
||||
/// </remarks>
|
||||
public string SubsetTitle {
|
||||
get { return this.subsetTitle; }
|
||||
set { this.subsetTitle = value; }
|
||||
}
|
||||
private string subsetTitle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the subtitleof the task
|
||||
/// </summary>
|
||||
public string Subtitle {
|
||||
get { return this.subtitle; }
|
||||
set { this.subtitle = value; }
|
||||
}
|
||||
private string subtitle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value by which this group will be sorted.
|
||||
/// </summary>
|
||||
public IComparable SortValue {
|
||||
get { return this.sortValue; }
|
||||
set { this.sortValue = value; }
|
||||
}
|
||||
private IComparable sortValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state of the group
|
||||
/// </summary>
|
||||
public GroupState State {
|
||||
get { return this.state; }
|
||||
set { this.state = value; }
|
||||
}
|
||||
private GroupState state;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which bits of State are valid
|
||||
/// </summary>
|
||||
public GroupState StateMask {
|
||||
get { return this.stateMask; }
|
||||
set { this.stateMask = value; }
|
||||
}
|
||||
private GroupState stateMask;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this group is showing only a subset of its elements
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As of WinSDK v7.0, this property officially does nothing.
|
||||
/// </remarks>
|
||||
public bool Subseted {
|
||||
get { return this.GetOneState(GroupState.LVGS_SUBSETED); }
|
||||
set { this.SetOneState(value, GroupState.LVGS_SUBSETED); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user-defined data attached to this group
|
||||
/// </summary>
|
||||
public object Tag {
|
||||
get { return this.tag; }
|
||||
set { this.tag = value; }
|
||||
}
|
||||
private object tag;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the task of this group
|
||||
/// </summary>
|
||||
/// <remarks>This task is the clickable text that appears on the right margin
|
||||
/// of the group header.</remarks>
|
||||
public string Task {
|
||||
get { return this.task; }
|
||||
set { this.task = value; }
|
||||
}
|
||||
private string task;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the int or string that will select the image to be shown against the title
|
||||
/// </summary>
|
||||
public object TitleImage {
|
||||
get { return this.titleImage; }
|
||||
set { this.titleImage = value; }
|
||||
}
|
||||
private object titleImage;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the top description of the group
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Descriptions only appear when group is centered and there is a title image
|
||||
/// </remarks>
|
||||
public string TopDescription {
|
||||
get { return this.topDescription; }
|
||||
set { this.topDescription = value; }
|
||||
}
|
||||
private string topDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of items that are within this group.
|
||||
/// </summary>
|
||||
/// <remarks>This should only be used for virtual groups.</remarks>
|
||||
public int VirtualItemCount {
|
||||
get { return this.virtualItemCount; }
|
||||
set { this.virtualItemCount = value; }
|
||||
}
|
||||
private int virtualItemCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ListViewGroup that is shadowed by this group.
|
||||
/// </summary>
|
||||
/// <remarks>For virtual groups, this will always be null.</remarks>
|
||||
protected ListViewGroup ListViewGroup {
|
||||
get { return this.listViewGroup; }
|
||||
set { this.listViewGroup = value; }
|
||||
}
|
||||
private ListViewGroup listViewGroup;
|
||||
#endregion
|
||||
|
||||
#region Calculations/Conversions
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the index into the group image list of the given image selector
|
||||
/// </summary>
|
||||
/// <param name="imageSelector"></param>
|
||||
/// <returns></returns>
|
||||
public int GetImageIndex(object imageSelector) {
|
||||
if (imageSelector == null || this.ListView == null || this.ListView.GroupImageList == null)
|
||||
return -1;
|
||||
|
||||
if (imageSelector is Int32)
|
||||
return (int)imageSelector;
|
||||
|
||||
String imageSelectorAsString = imageSelector as String;
|
||||
if (imageSelectorAsString != null)
|
||||
return this.ListView.GroupImageList.Images.IndexOfKey(imageSelectorAsString);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert this object to a string representation
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString() {
|
||||
return this.Header;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Insert a native group into the underlying Windows control,
|
||||
/// *without* using a ListViewGroup
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
/// <remarks>This is used when creating virtual groups</remarks>
|
||||
public void InsertGroupNewStyle(ObjectListView olv) {
|
||||
this.ListView = olv;
|
||||
NativeMethods.InsertGroup(olv, this.AsNativeGroup(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a native group into the underlying control via a ListViewGroup
|
||||
/// </summary>
|
||||
/// <param name="olv"></param>
|
||||
public void InsertGroupOldStyle(ObjectListView olv) {
|
||||
this.ListView = olv;
|
||||
|
||||
// Create/update the associated ListViewGroup
|
||||
if (this.ListViewGroup == null)
|
||||
this.ListViewGroup = new ListViewGroup();
|
||||
this.ListViewGroup.Header = this.Header;
|
||||
this.ListViewGroup.HeaderAlignment = this.HeaderAlignment;
|
||||
this.ListViewGroup.Name = this.Name;
|
||||
|
||||
// Remember which OLVGroup created the ListViewGroup
|
||||
this.ListViewGroup.Tag = this;
|
||||
|
||||
// Add the group to the control
|
||||
olv.Groups.Add(this.ListViewGroup);
|
||||
|
||||
// Add any extra information
|
||||
NativeMethods.SetGroupInfo(olv, this.GroupId, this.AsNativeGroup(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the members of the group to match the current contents of Items,
|
||||
/// using a ListViewGroup
|
||||
/// </summary>
|
||||
public void SetItemsOldStyle() {
|
||||
List<OLVListItem> list = this.Items as List<OLVListItem>;
|
||||
if (list == null) {
|
||||
foreach (OLVListItem item in this.Items) {
|
||||
this.ListViewGroup.Items.Add(item);
|
||||
}
|
||||
} else {
|
||||
this.ListViewGroup.Items.AddRange(list.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Create a native LVGROUP structure that matches this group
|
||||
/// </summary>
|
||||
internal NativeMethods.LVGROUP2 AsNativeGroup(bool withId) {
|
||||
|
||||
NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2();
|
||||
group.cbSize = (uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2));
|
||||
group.mask = (uint)(GroupMask.LVGF_HEADER ^ GroupMask.LVGF_ALIGN ^ GroupMask.LVGF_STATE);
|
||||
group.pszHeader = this.Header;
|
||||
group.uAlign = (uint)this.HeaderAlignment;
|
||||
group.stateMask = (uint)this.StateMask;
|
||||
group.state = (uint)this.State;
|
||||
|
||||
if (withId) {
|
||||
group.iGroupId = this.GroupId;
|
||||
group.mask ^= (uint)GroupMask.LVGF_GROUPID;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.Footer)) {
|
||||
group.pszFooter = this.Footer;
|
||||
group.mask ^= (uint)GroupMask.LVGF_FOOTER;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.Subtitle)) {
|
||||
group.pszSubtitle = this.Subtitle;
|
||||
group.mask ^= (uint)GroupMask.LVGF_SUBTITLE;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.Task)) {
|
||||
group.pszTask = this.Task;
|
||||
group.mask ^= (uint)GroupMask.LVGF_TASK;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.TopDescription)) {
|
||||
group.pszDescriptionTop = this.TopDescription;
|
||||
group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONTOP;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.BottomDescription)) {
|
||||
group.pszDescriptionBottom = this.BottomDescription;
|
||||
group.mask ^= (uint)GroupMask.LVGF_DESCRIPTIONBOTTOM;
|
||||
}
|
||||
|
||||
int imageIndex = this.GetImageIndex(this.TitleImage);
|
||||
if (imageIndex >= 0) {
|
||||
group.iTitleImage = imageIndex;
|
||||
group.mask ^= (uint)GroupMask.LVGF_TITLEIMAGE;
|
||||
}
|
||||
|
||||
imageIndex = this.GetImageIndex(this.ExtendedImage);
|
||||
if (imageIndex >= 0) {
|
||||
group.iExtendedImage = imageIndex;
|
||||
group.mask ^= (uint)GroupMask.LVGF_EXTENDEDIMAGE;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(this.SubsetTitle)) {
|
||||
group.pszSubsetTitle = this.SubsetTitle;
|
||||
group.mask ^= (uint)GroupMask.LVGF_SUBSET;
|
||||
}
|
||||
|
||||
if (this.VirtualItemCount > 0) {
|
||||
group.cItems = this.VirtualItemCount;
|
||||
group.mask ^= (uint)GroupMask.LVGF_ITEMS;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private bool GetOneState(GroupState mask) {
|
||||
if (this.Created)
|
||||
this.State = this.GetState();
|
||||
return (this.State & mask) == mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current state of this group from the underlying control
|
||||
/// </summary>
|
||||
protected GroupState GetState() {
|
||||
return NativeMethods.GetGroupState(this.ListView, this.GroupId, GroupState.LVGS_ALL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current state of this group from the underlying control
|
||||
/// </summary>
|
||||
protected int SetState(GroupState newState, GroupState mask) {
|
||||
NativeMethods.LVGROUP2 group = new NativeMethods.LVGROUP2();
|
||||
group.cbSize = ((uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUP2)));
|
||||
group.mask = (uint)GroupMask.LVGF_STATE;
|
||||
group.state = (uint)newState;
|
||||
group.stateMask = (uint)mask;
|
||||
return NativeMethods.SetGroupInfo(this.ListView, this.GroupId, group);
|
||||
}
|
||||
|
||||
private void SetOneState(bool value, GroupState mask)
|
||||
{
|
||||
this.StateMask ^= mask;
|
||||
if (value)
|
||||
this.State ^= mask;
|
||||
else
|
||||
this.State &= ~mask;
|
||||
|
||||
if (this.Created)
|
||||
this.SetState(this.State, mask);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
/*
|
||||
* Munger - An Interface pattern on getting and setting values from object through Reflection
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 28/11/2008 17:15
|
||||
*
|
||||
* Change log:
|
||||
* v2.5.1
|
||||
* 2012-05-01 JPP - Added IgnoreMissingAspects property
|
||||
* v2.5
|
||||
* 2011-05-20 JPP - Accessing through an indexer when the target had both a integer and
|
||||
* a string indexer didn't work reliably.
|
||||
* v2.4.1
|
||||
* 2010-08-10 JPP - Refactored into Munger/SimpleMunger. 3x faster!
|
||||
* v2.3
|
||||
* 2009-02-15 JPP - Made Munger a public class
|
||||
* 2009-01-20 JPP - Made the Munger capable of handling indexed access.
|
||||
* Incidentally, this removed the ugliness that the last change introduced.
|
||||
* 2009-01-18 JPP - Handle target objects from a DataListView (normally DataRowViews)
|
||||
* v2.0
|
||||
* 2008-11-28 JPP Initial version
|
||||
*
|
||||
* TO DO:
|
||||
*
|
||||
* Copyright (C) 2006-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// An instance of Munger gets a value from or puts a value into a target object. The property
|
||||
/// to be peeked (or poked) is determined from a string. The peeking or poking is done using reflection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Name of the aspect to be peeked can be a field, property or parameterless method. The name of an
|
||||
/// aspect to poke can be a field, writable property or single parameter method.
|
||||
/// <para>
|
||||
/// Aspect names can be dotted to chain a series of references.
|
||||
/// </para>
|
||||
/// <example>Order.Customer.HomeAddress.State</example>
|
||||
/// </remarks>
|
||||
public class Munger
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a do nothing Munger
|
||||
/// </summary>
|
||||
public Munger()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a Munger that works on the given aspect name
|
||||
/// </summary>
|
||||
/// <param name="aspectName">The name of the </param>
|
||||
public Munger(String aspectName)
|
||||
{
|
||||
this.AspectName = aspectName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static utility methods
|
||||
|
||||
/// <summary>
|
||||
/// A helper method to put the given value into the given aspect of the given object.
|
||||
/// </summary>
|
||||
/// <remarks>This method catches and silently ignores any errors that occur
|
||||
/// while modifying the target object</remarks>
|
||||
/// <param name="target">The object to be modified</param>
|
||||
/// <param name="propertyName">The name of the property/field to be modified</param>
|
||||
/// <param name="value">The value to be assigned</param>
|
||||
/// <returns>Did the modification work?</returns>
|
||||
public static bool PutProperty(object target, string propertyName, object value) {
|
||||
try {
|
||||
Munger munger = new Munger(propertyName);
|
||||
return munger.PutValue(target, value);
|
||||
}
|
||||
catch (MungerException) {
|
||||
// Not a lot we can do about this. Something went wrong in the bowels
|
||||
// of the property. Let's take the ostrich approach and just ignore it :-)
|
||||
|
||||
// Normally, we would never just silently ignore an exception.
|
||||
// However, in this case, this is a utility method that explicitly
|
||||
// contracts to catch and ignore errors. If this is not acceptible,
|
||||
// the programmer should not use this method.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether Mungers will silently ignore missing aspect errors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// By default, if a Munger is asked to fetch a field/property/method
|
||||
/// that does not exist from a model, it returns an error message, since that
|
||||
/// condition is normally a programming error. There are some use cases where
|
||||
/// this is not an error, and the munger should simply keep quiet.
|
||||
/// </para>
|
||||
/// <para>By default this is true during release builds.</para>
|
||||
/// </remarks>
|
||||
public static bool IgnoreMissingAspects {
|
||||
get { return ignoreMissingAspects; }
|
||||
set { ignoreMissingAspects = value; }
|
||||
}
|
||||
private static bool ignoreMissingAspects
|
||||
#if !DEBUG
|
||||
= true
|
||||
#endif
|
||||
;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the aspect that is to be peeked or poked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This name can be a field, property or parameter-less method.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The name can be dotted, which chains references. If any link in the chain returns
|
||||
/// null, the entire chain is considered to return null.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>"DateOfBirth"</example>
|
||||
/// <example>"Owner.HomeAddress.Postcode"</example>
|
||||
public string AspectName
|
||||
{
|
||||
get { return aspectName; }
|
||||
set {
|
||||
aspectName = value;
|
||||
|
||||
// Clear any cache
|
||||
aspectParts = null;
|
||||
}
|
||||
}
|
||||
private string aspectName;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Public interface
|
||||
|
||||
/// <summary>
|
||||
/// Extract the value indicated by our AspectName from the given target.
|
||||
/// </summary>
|
||||
/// <remarks>If the aspect name is null or empty, this will return null.</remarks>
|
||||
/// <param name="target">The object that will be peeked</param>
|
||||
/// <returns>The value read from the target</returns>
|
||||
public Object GetValue(Object target) {
|
||||
if (this.Parts.Count == 0)
|
||||
return null;
|
||||
|
||||
try {
|
||||
return this.EvaluateParts(target, this.Parts);
|
||||
} catch (MungerException ex) {
|
||||
if (Munger.IgnoreMissingAspects)
|
||||
return null;
|
||||
|
||||
return String.Format("'{0}' is not a parameter-less method, property or field of type '{1}'",
|
||||
ex.Munger.AspectName, ex.Target.GetType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract the value indicated by our AspectName from the given target, raising exceptions
|
||||
/// if the munger fails.
|
||||
/// </summary>
|
||||
/// <remarks>If the aspect name is null or empty, this will return null.</remarks>
|
||||
/// <param name="target">The object that will be peeked</param>
|
||||
/// <returns>The value read from the target</returns>
|
||||
public Object GetValueEx(Object target) {
|
||||
if (this.Parts.Count == 0)
|
||||
return null;
|
||||
|
||||
return this.EvaluateParts(target, this.Parts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poke the given value into the given target indicated by our AspectName.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If the AspectName is a dotted path, all the selectors bar the last
|
||||
/// are used to find the object that should be updated, and the last
|
||||
/// selector is used as the property to update on that object.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// So, if 'target' is a Person and the AspectName is "HomeAddress.Postcode",
|
||||
/// this method will first fetch "HomeAddress" property, and then try to set the
|
||||
/// "Postcode" property on the home address object.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="target">The object that will be poked</param>
|
||||
/// <param name="value">The value that will be poked into the target</param>
|
||||
/// <returns>bool indicating whether the put worked</returns>
|
||||
public bool PutValue(Object target, Object value)
|
||||
{
|
||||
if (this.Parts.Count == 0)
|
||||
return false;
|
||||
|
||||
SimpleMunger lastPart = this.Parts[this.Parts.Count - 1];
|
||||
|
||||
if (this.Parts.Count > 1) {
|
||||
List<SimpleMunger> parts = new List<SimpleMunger>(this.Parts);
|
||||
parts.RemoveAt(parts.Count - 1);
|
||||
try {
|
||||
target = this.EvaluateParts(target, parts);
|
||||
} catch (MungerException ex) {
|
||||
this.ReportPutValueException(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
try {
|
||||
return lastPart.PutValue(target, value);
|
||||
} catch (MungerException ex) {
|
||||
this.ReportPutValueException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of SimpleMungers that match our AspectName
|
||||
/// </summary>
|
||||
private IList<SimpleMunger> Parts {
|
||||
get {
|
||||
if (aspectParts == null)
|
||||
aspectParts = BuildParts(this.AspectName);
|
||||
return aspectParts;
|
||||
}
|
||||
}
|
||||
private IList<SimpleMunger> aspectParts;
|
||||
|
||||
/// <summary>
|
||||
/// Convert a possibly dotted AspectName into a list of SimpleMungers
|
||||
/// </summary>
|
||||
/// <param name="aspect"></param>
|
||||
/// <returns></returns>
|
||||
private IList<SimpleMunger> BuildParts(string aspect) {
|
||||
List<SimpleMunger> parts = new List<SimpleMunger>();
|
||||
if (!String.IsNullOrEmpty(aspect)) {
|
||||
foreach (string part in aspect.Split('.')) {
|
||||
parts.Add(new SimpleMunger(part.Trim()));
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate the given chain of SimpleMungers against an initial target.
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="parts"></param>
|
||||
/// <returns></returns>
|
||||
private object EvaluateParts(object target, IList<SimpleMunger> parts) {
|
||||
foreach (SimpleMunger part in parts) {
|
||||
if (target == null)
|
||||
break;
|
||||
target = part.GetValue(target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private void ReportPutValueException(MungerException ex) {
|
||||
//TODO: How should we report this error?
|
||||
System.Diagnostics.Debug.WriteLine("PutValue failed");
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("- Culprit aspect: {0}", ex.Munger.AspectName));
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("- Target: {0} of type {1}", ex.Target, ex.Target.GetType()));
|
||||
System.Diagnostics.Debug.WriteLine(String.Format("- Inner exception: {0}", ex.InnerException));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A SimpleMunger deals with a single property/field/method on its target.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Munger uses a chain of these resolve a dotted aspect name.
|
||||
/// </remarks>
|
||||
public class SimpleMunger
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a SimpleMunger
|
||||
/// </summary>
|
||||
/// <param name="aspectName"></param>
|
||||
public SimpleMunger(String aspectName)
|
||||
{
|
||||
this.aspectName = aspectName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the aspect that is to be peeked or poked.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This name can be a field, property or method.
|
||||
/// When using a method to get a value, the method must be parameter-less.
|
||||
/// When using a method to set a value, the method must accept 1 parameter.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It cannot be a dotted name.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public string AspectName {
|
||||
get { return aspectName; }
|
||||
}
|
||||
private readonly string aspectName;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public interface
|
||||
|
||||
/// <summary>
|
||||
/// Get a value from the given target
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
public Object GetValue(Object target) {
|
||||
if (target == null)
|
||||
return null;
|
||||
|
||||
this.ResolveName(target, this.AspectName, 0);
|
||||
|
||||
try {
|
||||
if (this.resolvedPropertyInfo != null)
|
||||
return this.resolvedPropertyInfo.GetValue(target, null);
|
||||
|
||||
if (this.resolvedMethodInfo != null)
|
||||
return this.resolvedMethodInfo.Invoke(target, null);
|
||||
|
||||
if (this.resolvedFieldInfo != null)
|
||||
return this.resolvedFieldInfo.GetValue(target);
|
||||
|
||||
// If that didn't work, try to use the indexer property.
|
||||
// This covers things like dictionaries and DataRows.
|
||||
if (this.indexerPropertyInfo != null)
|
||||
return this.indexerPropertyInfo.GetValue(target, new object[] { this.AspectName });
|
||||
} catch (Exception ex) {
|
||||
// Lots of things can do wrong in these invocations
|
||||
throw new MungerException(this, target, ex);
|
||||
}
|
||||
|
||||
// If we get to here, we couldn't find a match for the aspect
|
||||
throw new MungerException(this, target, new MissingMethodException());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poke the given value into the given target indicated by our AspectName.
|
||||
/// </summary>
|
||||
/// <param name="target">The object that will be poked</param>
|
||||
/// <param name="value">The value that will be poked into the target</param>
|
||||
/// <returns>bool indicating if the put worked</returns>
|
||||
public bool PutValue(object target, object value) {
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
this.ResolveName(target, this.AspectName, 1);
|
||||
|
||||
try {
|
||||
if (this.resolvedPropertyInfo != null) {
|
||||
this.resolvedPropertyInfo.SetValue(target, value, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.resolvedMethodInfo != null) {
|
||||
this.resolvedMethodInfo.Invoke(target, new object[] { value });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.resolvedFieldInfo != null) {
|
||||
this.resolvedFieldInfo.SetValue(target, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If that didn't work, try to use the indexer property.
|
||||
// This covers things like dictionaries and DataRows.
|
||||
if (this.indexerPropertyInfo != null) {
|
||||
this.indexerPropertyInfo.SetValue(target, value, new object[] { this.AspectName });
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Lots of things can do wrong in these invocations
|
||||
throw new MungerException(this, target, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
private void ResolveName(object target, string name, int numberMethodParameters) {
|
||||
|
||||
if (cachedTargetType == target.GetType() && cachedName == name && cachedNumberParameters == numberMethodParameters)
|
||||
return;
|
||||
|
||||
cachedTargetType = target.GetType();
|
||||
cachedName = name;
|
||||
cachedNumberParameters = numberMethodParameters;
|
||||
|
||||
resolvedFieldInfo = null;
|
||||
resolvedPropertyInfo = null;
|
||||
resolvedMethodInfo = null;
|
||||
indexerPropertyInfo = null;
|
||||
|
||||
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance /*| BindingFlags.NonPublic*/;
|
||||
|
||||
foreach (PropertyInfo pinfo in target.GetType().GetProperties(flags)) {
|
||||
if (pinfo.Name == name) {
|
||||
resolvedPropertyInfo = pinfo;
|
||||
return;
|
||||
}
|
||||
|
||||
// See if we can find an string indexer property while we are here.
|
||||
// We also need to allow for old style <object> keyed collections.
|
||||
if (indexerPropertyInfo == null && pinfo.Name == "Item") {
|
||||
ParameterInfo[] par = pinfo.GetGetMethod().GetParameters();
|
||||
if (par.Length > 0) {
|
||||
Type parameterType = par[0].ParameterType;
|
||||
if (parameterType == typeof(string) || parameterType == typeof(object))
|
||||
indexerPropertyInfo = pinfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FieldInfo info in target.GetType().GetFields(flags)) {
|
||||
if (info.Name == name) {
|
||||
resolvedFieldInfo = info;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (MethodInfo info in target.GetType().GetMethods(flags)) {
|
||||
if (info.Name == name && info.GetParameters().Length == numberMethodParameters) {
|
||||
resolvedMethodInfo = info;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Type cachedTargetType;
|
||||
private string cachedName;
|
||||
private int cachedNumberParameters;
|
||||
|
||||
private FieldInfo resolvedFieldInfo;
|
||||
private PropertyInfo resolvedPropertyInfo;
|
||||
private MethodInfo resolvedMethodInfo;
|
||||
private PropertyInfo indexerPropertyInfo;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These exceptions are raised when a munger finds something it cannot process
|
||||
/// </summary>
|
||||
public class MungerException : ApplicationException
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a MungerException
|
||||
/// </summary>
|
||||
/// <param name="munger"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="ex"></param>
|
||||
public MungerException(SimpleMunger munger, object target, Exception ex)
|
||||
: base("Munger failed", ex) {
|
||||
this.munger = munger;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the munger that raised the exception
|
||||
/// </summary>
|
||||
public SimpleMunger Munger {
|
||||
get { return munger; }
|
||||
}
|
||||
private readonly SimpleMunger munger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target that threw the exception
|
||||
/// </summary>
|
||||
public object Target {
|
||||
get { return target; }
|
||||
}
|
||||
private readonly object target;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't currently need this
|
||||
* 2010-08-06
|
||||
*
|
||||
|
||||
internal class SimpleBinder : Binder
|
||||
{
|
||||
public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, System.Globalization.CultureInfo culture) {
|
||||
//return Type.DefaultBinder.BindToField(
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ChangeType(object value, Type type, System.Globalization.CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] names, out object state) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void ReorderArgumentArray(ref object[] args, object state) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers) {
|
||||
if (match == null)
|
||||
throw new ArgumentNullException("match");
|
||||
|
||||
if (match.Length == 0)
|
||||
return null;
|
||||
|
||||
return match[0];
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* NullableDictionary - A simple Dictionary that can handle null as a key
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// A simple-minded implementation of a Dictionary that can handle null as a key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the dictionary key</typeparam>
|
||||
/// <typeparam name="TValue">The type of the values to be stored</typeparam>
|
||||
/// <remarks>This is not a full implementation and is only meant to handle
|
||||
/// collecting groups by their keys, since groups can have null as a key value.</remarks>
|
||||
internal class NullableDictionary<TKey, TValue> : Dictionary<TKey, TValue> {
|
||||
private bool hasNullKey;
|
||||
private TValue nullValue;
|
||||
|
||||
new public TValue this[TKey key] {
|
||||
get {
|
||||
if (key != null)
|
||||
return base[key];
|
||||
|
||||
if (this.hasNullKey)
|
||||
return this.nullValue;
|
||||
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
set {
|
||||
if (key == null) {
|
||||
this.hasNullKey = true;
|
||||
this.nullValue = value;
|
||||
} else
|
||||
base[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
new public bool ContainsKey(TKey key) {
|
||||
return key == null ? this.hasNullKey : base.ContainsKey(key);
|
||||
}
|
||||
|
||||
new public IList Keys {
|
||||
get {
|
||||
ArrayList list = new ArrayList(base.Keys);
|
||||
if (this.hasNullKey)
|
||||
list.Add(null);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
new public IList<TValue> Values {
|
||||
get {
|
||||
List<TValue> list = new List<TValue>(base.Values);
|
||||
if (this.hasNullKey)
|
||||
list.Add(this.nullValue);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* OLVListItem - A row in an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* v2.8
|
||||
* 2014-09-27 JPP - Remove faulty caching of CheckState
|
||||
* 2014-05-06 JPP - Added OLVListItem.Enabled flag
|
||||
* vOld
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// OLVListItems are specialized ListViewItems that know which row object they came from,
|
||||
/// and the row index at which they are displayed, even when in group view mode. They
|
||||
/// also know the image they should draw against themselves
|
||||
/// </summary>
|
||||
public class OLVListItem : ListViewItem {
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListItem for the given row object
|
||||
/// </summary>
|
||||
public OLVListItem(object rowObject) {
|
||||
this.rowObject = rowObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListItem for the given row object, represented by the given string and image
|
||||
/// </summary>
|
||||
public OLVListItem(object rowObject, string text, Object image)
|
||||
: base(text, -1) {
|
||||
this.rowObject = rowObject;
|
||||
this.imageSelector = image;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding rectangle of the item, including all subitems
|
||||
/// </summary>
|
||||
new public Rectangle Bounds {
|
||||
get {
|
||||
try {
|
||||
return base.Bounds;
|
||||
}
|
||||
catch (System.ArgumentException) {
|
||||
// If the item is part of a collapsed group, Bounds will throw an exception
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how many pixels will be left blank around each cell of this item
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public Rectangle? CellPadding {
|
||||
get { return this.cellPadding; }
|
||||
set { this.cellPadding = value; }
|
||||
}
|
||||
private Rectangle? cellPadding;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the cells of this item will be vertically aligned
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public StringAlignment? CellVerticalAlignment {
|
||||
get { return this.cellVerticalAlignment; }
|
||||
set { this.cellVerticalAlignment = value; }
|
||||
}
|
||||
private StringAlignment? cellVerticalAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the checkedness of this item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Virtual lists don't handle checkboxes well, so we have to intercept attempts to change them
|
||||
/// through the items, and change them into something that will work.
|
||||
/// Unfortuneately, this won't work if this property is set through the base class, since
|
||||
/// the property is not declared as virtual.
|
||||
/// </remarks>
|
||||
new public bool Checked {
|
||||
get {
|
||||
return base.Checked;
|
||||
}
|
||||
set {
|
||||
if (this.Checked != value) {
|
||||
if (value)
|
||||
((ObjectListView)this.ListView).CheckObject(this.RowObject);
|
||||
else
|
||||
((ObjectListView)this.ListView).UncheckObject(this.RowObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable tri-state checkbox.
|
||||
/// </summary>
|
||||
/// <remarks>.NET's Checked property was not built to handle tri-state checkboxes,
|
||||
/// and will return True for both Checked and Indeterminate states.</remarks>
|
||||
public CheckState CheckState {
|
||||
get {
|
||||
switch (this.StateImageIndex) {
|
||||
case 0:
|
||||
return System.Windows.Forms.CheckState.Unchecked;
|
||||
case 1:
|
||||
return System.Windows.Forms.CheckState.Checked;
|
||||
case 2:
|
||||
return System.Windows.Forms.CheckState.Indeterminate;
|
||||
default:
|
||||
return System.Windows.Forms.CheckState.Unchecked;
|
||||
}
|
||||
}
|
||||
set {
|
||||
switch (value) {
|
||||
case System.Windows.Forms.CheckState.Unchecked:
|
||||
this.StateImageIndex = 0;
|
||||
break;
|
||||
case System.Windows.Forms.CheckState.Checked:
|
||||
this.StateImageIndex = 1;
|
||||
break;
|
||||
case System.Windows.Forms.CheckState.Indeterminate:
|
||||
this.StateImageIndex = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this item has any decorations set for it.
|
||||
/// </summary>
|
||||
public bool HasDecoration {
|
||||
get {
|
||||
return this.decorations != null && this.decorations.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the decoration that will be drawn over this item
|
||||
/// </summary>
|
||||
/// <remarks>Setting this replaces all other decorations</remarks>
|
||||
public IDecoration Decoration {
|
||||
get {
|
||||
if (this.HasDecoration)
|
||||
return this.Decorations[0];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
set {
|
||||
this.Decorations.Clear();
|
||||
if (value != null)
|
||||
this.Decorations.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of decorations that will be drawn over this item
|
||||
/// </summary>
|
||||
public IList<IDecoration> Decorations {
|
||||
get {
|
||||
if (this.decorations == null)
|
||||
this.decorations = new List<IDecoration>();
|
||||
return this.decorations;
|
||||
}
|
||||
}
|
||||
private IList<IDecoration> decorations;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not this row can be selected and activated
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
get { return this.enabled; }
|
||||
internal set { this.enabled = value; }
|
||||
}
|
||||
private bool enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the image that should be shown against this item
|
||||
/// </summary>
|
||||
/// <remarks><para>This can be an Image, a string or an int. A string or an int will
|
||||
/// be used as an index into the small image list.</para></remarks>
|
||||
public Object ImageSelector {
|
||||
get { return imageSelector; }
|
||||
set {
|
||||
imageSelector = value;
|
||||
if (value is Int32)
|
||||
this.ImageIndex = (Int32)value;
|
||||
else if (value is String)
|
||||
this.ImageKey = (String)value;
|
||||
else
|
||||
this.ImageIndex = -1;
|
||||
}
|
||||
}
|
||||
private Object imageSelector;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the the model object that is source of the data for this list item.
|
||||
/// </summary>
|
||||
public object RowObject {
|
||||
get { return rowObject; }
|
||||
set { rowObject = value; }
|
||||
}
|
||||
private object rowObject;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Accessing
|
||||
|
||||
/// <summary>
|
||||
/// Return the sub item at the given index
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the subitem to be returned</param>
|
||||
/// <returns>An OLVListSubItem</returns>
|
||||
public virtual OLVListSubItem GetSubItem(int index) {
|
||||
if (index >= 0 && index < this.SubItems.Count)
|
||||
return (OLVListSubItem)this.SubItems[index];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Return bounds of the given subitem
|
||||
/// </summary>
|
||||
/// <remarks>This correctly calculates the bounds even for column 0.</remarks>
|
||||
public virtual Rectangle GetSubItemBounds(int subItemIndex) {
|
||||
if (subItemIndex == 0) {
|
||||
Rectangle r = this.Bounds;
|
||||
Point sides = NativeMethods.GetScrolledColumnSides(this.ListView, subItemIndex);
|
||||
r.X = sides.X + 1;
|
||||
r.Width = sides.Y - sides.X;
|
||||
return r;
|
||||
}
|
||||
|
||||
OLVListSubItem subItem = this.GetSubItem(subItemIndex);
|
||||
return subItem == null ? new Rectangle() : subItem.Bounds;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* OLVListSubItem - A single cell in an ObjectListView
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// A ListViewSubItem that knows which image should be drawn against it.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public class OLVListSubItem : ListViewItem.ListViewSubItem {
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListSubItem
|
||||
/// </summary>
|
||||
public OLVListSubItem() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a OLVListSubItem that shows the given string and image
|
||||
/// </summary>
|
||||
public OLVListSubItem(object modelValue, string text, Object image) {
|
||||
this.ModelValue = modelValue;
|
||||
this.Text = text;
|
||||
this.ImageSelector = image;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how many pixels will be left blank around this cell
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public Rectangle? CellPadding {
|
||||
get { return this.cellPadding; }
|
||||
set { this.cellPadding = value; }
|
||||
}
|
||||
private Rectangle? cellPadding;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how this cell will be vertically aligned
|
||||
/// </summary>
|
||||
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
|
||||
public StringAlignment? CellVerticalAlignment {
|
||||
get { return this.cellVerticalAlignment; }
|
||||
set { this.cellVerticalAlignment = value; }
|
||||
}
|
||||
private StringAlignment? cellVerticalAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the model value is being displayed by this subitem.
|
||||
/// </summary>
|
||||
public object ModelValue
|
||||
{
|
||||
get { return modelValue; }
|
||||
private set { modelValue = value; }
|
||||
}
|
||||
private object modelValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this subitem has any decorations set for it.
|
||||
/// </summary>
|
||||
public bool HasDecoration {
|
||||
get {
|
||||
return this.decorations != null && this.decorations.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the decoration that will be drawn over this item
|
||||
/// </summary>
|
||||
/// <remarks>Setting this replaces all other decorations</remarks>
|
||||
public IDecoration Decoration {
|
||||
get {
|
||||
return this.HasDecoration ? this.Decorations[0] : null;
|
||||
}
|
||||
set {
|
||||
this.Decorations.Clear();
|
||||
if (value != null)
|
||||
this.Decorations.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of decorations that will be drawn over this item
|
||||
/// </summary>
|
||||
public IList<IDecoration> Decorations {
|
||||
get {
|
||||
if (this.decorations == null)
|
||||
this.decorations = new List<IDecoration>();
|
||||
return this.decorations;
|
||||
}
|
||||
}
|
||||
private IList<IDecoration> decorations;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the image that should be shown against this item
|
||||
/// </summary>
|
||||
/// <remarks><para>This can be an Image, a string or an int. A string or an int will
|
||||
/// be used as an index into the small image list.</para></remarks>
|
||||
public Object ImageSelector {
|
||||
get { return imageSelector; }
|
||||
set { imageSelector = value; }
|
||||
}
|
||||
private Object imageSelector;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the url that should be invoked when this subitem is clicked
|
||||
/// </summary>
|
||||
public string Url {
|
||||
get { return this.url; }
|
||||
set { this.url = value; }
|
||||
}
|
||||
private string url;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation Properties
|
||||
|
||||
/// <summary>
|
||||
/// Return the state of the animatation of the image on this subitem.
|
||||
/// Null means there is either no image, or it is not an animation
|
||||
/// </summary>
|
||||
internal ImageRenderer.AnimationState AnimationState;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* OlvListViewHitTestInfo - All information gathered during a OlvHitTest() operation
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 31-March-2011 5:53 pm
|
||||
*
|
||||
* Change log:
|
||||
* 2011-03-31 JPP - Split into its own file
|
||||
*
|
||||
* Copyright (C) 2011-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware {
|
||||
|
||||
/// <summary>
|
||||
/// An indication of where a hit was within ObjectListView cell
|
||||
/// </summary>
|
||||
public enum HitTestLocation {
|
||||
/// <summary>
|
||||
/// Nowhere
|
||||
/// </summary>
|
||||
Nothing,
|
||||
|
||||
/// <summary>
|
||||
/// On the text
|
||||
/// </summary>
|
||||
Text,
|
||||
|
||||
/// <summary>
|
||||
/// On the image
|
||||
/// </summary>
|
||||
Image,
|
||||
|
||||
/// <summary>
|
||||
/// On the checkbox
|
||||
/// </summary>
|
||||
CheckBox,
|
||||
|
||||
/// <summary>
|
||||
/// On the expand button (TreeListView)
|
||||
/// </summary>
|
||||
ExpandButton,
|
||||
|
||||
/// <summary>
|
||||
/// in the cell but not in any more specific location
|
||||
/// </summary>
|
||||
InCell,
|
||||
|
||||
/// <summary>
|
||||
/// UserDefined location1 (used for custom renderers)
|
||||
/// </summary>
|
||||
UserDefined,
|
||||
|
||||
/// <summary>
|
||||
/// On the expand/collapse widget of the group
|
||||
/// </summary>
|
||||
GroupExpander,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere on a group
|
||||
/// </summary>
|
||||
Group,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere in a column header
|
||||
/// </summary>
|
||||
Header,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere in a column header checkbox
|
||||
/// </summary>
|
||||
HeaderCheckBox,
|
||||
|
||||
/// <summary>
|
||||
/// Somewhere in a header divider
|
||||
/// </summary>
|
||||
HeaderDivider,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A collection of ListViewHitTest constants
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum HitTestLocationEx {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_NOWHERE = 0x00000001,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEMICON = 0x00000002,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEMLABEL = 0x00000004,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEMSTATEICON = 0x00000008,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON),
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_ABOVE = 0x00000008,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_BELOW = 0x00000010,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_TORIGHT = 0x00000020,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_TOLEFT = 0x00000040,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_HEADER = 0x10000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_FOOTER = 0x20000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_COLLAPSE = 0x40000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_BACKGROUND = -2147483648, // 0x80000000
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_STATEICON = 0x01000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_SUBSETLINK = 0x02000000,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP = (LVHT_EX_GROUP_BACKGROUND | LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_FOOTER | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK),
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD = (LVHT_EX_GROUP_COLLAPSE | LVHT_EX_GROUP_HEADER | LVHT_EX_GROUP_STATEICON | LVHT_EX_GROUP_SUBSETLINK),
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_ONCONTENTS = 0x04000000, // On item AND not on the background
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
LVHT_EX_FOOTER = 0x08000000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instances of this class encapsulate the information gathered during a OlvHitTest()
|
||||
/// operation.
|
||||
/// </summary>
|
||||
/// <remarks>Custom renderers can use HitTestLocation.UserDefined and the UserData
|
||||
/// object to store more specific locations for use during event handlers.</remarks>
|
||||
public class OlvListViewHitTestInfo {
|
||||
|
||||
/// <summary>
|
||||
/// Create a OlvListViewHitTestInfo
|
||||
/// </summary>
|
||||
public OlvListViewHitTestInfo(OLVListItem olvListItem, OLVListSubItem subItem, int flags, OLVGroup group, int iColumn)
|
||||
{
|
||||
this.item = olvListItem;
|
||||
this.subItem = subItem;
|
||||
this.location = ConvertNativeFlagsToDotNetLocation(olvListItem, flags);
|
||||
this.HitTestLocationEx = (HitTestLocationEx)flags;
|
||||
this.Group = group;
|
||||
this.ColumnIndex = iColumn;
|
||||
this.ListView = olvListItem == null ? null : (ObjectListView)olvListItem.ListView;
|
||||
|
||||
switch (location) {
|
||||
case ListViewHitTestLocations.StateImage:
|
||||
this.HitTestLocation = HitTestLocation.CheckBox;
|
||||
break;
|
||||
case ListViewHitTestLocations.Image:
|
||||
this.HitTestLocation = HitTestLocation.Image;
|
||||
break;
|
||||
case ListViewHitTestLocations.Label:
|
||||
this.HitTestLocation = HitTestLocation.Text;
|
||||
break;
|
||||
default:
|
||||
if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE) == HitTestLocationEx.LVHT_EX_GROUP_COLLAPSE)
|
||||
this.HitTestLocation = HitTestLocation.GroupExpander;
|
||||
else if ((this.HitTestLocationEx & HitTestLocationEx.LVHT_EX_GROUP_MINUS_FOOTER_AND_BKGRD) != 0)
|
||||
this.HitTestLocation = HitTestLocation.Group;
|
||||
else
|
||||
this.HitTestLocation = HitTestLocation.Nothing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a OlvListViewHitTestInfo when the header was hit
|
||||
/// </summary>
|
||||
public OlvListViewHitTestInfo(ObjectListView olv, int iColumn, bool isOverCheckBox, int iDivider) {
|
||||
this.ListView = olv;
|
||||
this.ColumnIndex = iColumn;
|
||||
this.HeaderDividerIndex = iDivider;
|
||||
this.HitTestLocation = isOverCheckBox ? HitTestLocation.HeaderCheckBox : (iDivider < 0 ? HitTestLocation.Header : HitTestLocation.HeaderDivider);
|
||||
}
|
||||
|
||||
private static ListViewHitTestLocations ConvertNativeFlagsToDotNetLocation(OLVListItem hitItem, int flags)
|
||||
{
|
||||
// Untangle base .NET behaviour.
|
||||
|
||||
// In Windows SDK, the value 8 can have two meanings here: LVHT_ONITEMSTATEICON or LVHT_ABOVE.
|
||||
// .NET changes these to be:
|
||||
// - LVHT_ABOVE becomes ListViewHitTestLocations.AboveClientArea (which is 0x100).
|
||||
// - LVHT_ONITEMSTATEICON becomes ListViewHitTestLocations.StateImage (which is 0x200).
|
||||
// So, if we see the 8 bit set in flags, we change that to either a state image hit
|
||||
// (if we hit an item) or to AboveClientAream if nothing was hit.
|
||||
|
||||
if ((8 & flags) == 8)
|
||||
return (ListViewHitTestLocations)(0xf7 & flags | (hitItem == null ? 0x100 : 0x200));
|
||||
|
||||
// Mask off the LVHT_EX_XXXX values since ListViewHitTestLocations doesn't have them
|
||||
return (ListViewHitTestLocations)(flags & 0xffff);
|
||||
}
|
||||
|
||||
#region Public fields
|
||||
|
||||
/// <summary>
|
||||
/// Where is the hit location?
|
||||
/// </summary>
|
||||
public HitTestLocation HitTestLocation;
|
||||
|
||||
/// <summary>
|
||||
/// Where is the hit location?
|
||||
/// </summary>
|
||||
public HitTestLocationEx HitTestLocationEx;
|
||||
|
||||
/// <summary>
|
||||
/// Which group was hit?
|
||||
/// </summary>
|
||||
public OLVGroup Group;
|
||||
|
||||
/// <summary>
|
||||
/// Custom renderers can use this information to supply more details about the hit location
|
||||
/// </summary>
|
||||
public Object UserData;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public read-only properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item that was hit
|
||||
/// </summary>
|
||||
public OLVListItem Item {
|
||||
get { return item; }
|
||||
internal set { item = value; }
|
||||
}
|
||||
private OLVListItem item;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subitem that was hit
|
||||
/// </summary>
|
||||
public OLVListSubItem SubItem {
|
||||
get { return subItem; }
|
||||
internal set { subItem = value; }
|
||||
}
|
||||
private OLVListSubItem subItem;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the part of the subitem that was hit
|
||||
/// </summary>
|
||||
public ListViewHitTestLocations Location {
|
||||
get { return location; }
|
||||
internal set { location = value; }
|
||||
}
|
||||
private ListViewHitTestLocations location;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ObjectListView that was tested
|
||||
/// </summary>
|
||||
public ObjectListView ListView {
|
||||
get { return listView; }
|
||||
internal set { listView = value; }
|
||||
}
|
||||
private ObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model object that was hit
|
||||
/// </summary>
|
||||
public Object RowObject {
|
||||
get {
|
||||
return this.Item == null ? null : this.Item.RowObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the row under the hit point or -1
|
||||
/// </summary>
|
||||
public int RowIndex {
|
||||
get { return this.Item == null ? -1 : this.Item.Index; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the column under the hit point
|
||||
/// </summary>
|
||||
public int ColumnIndex {
|
||||
get { return columnIndex; }
|
||||
internal set { columnIndex = value; }
|
||||
}
|
||||
private int columnIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the header divider
|
||||
/// </summary>
|
||||
public int HeaderDividerIndex {
|
||||
get { return headerDividerIndex; }
|
||||
internal set { headerDividerIndex = value; }
|
||||
}
|
||||
private int headerDividerIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the column that was hit
|
||||
/// </summary>
|
||||
public OLVColumn Column {
|
||||
get {
|
||||
int index = this.ColumnIndex;
|
||||
return index < 0 || this.ListView == null ? null : this.ListView.GetColumn(index);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string that represents the current object.
|
||||
/// </returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("HitTestLocation: {0}, HitTestLocationEx: {1}, Item: {2}, SubItem: {3}, Location: {4}, Group: {5}, ColumnIndex: {6}",
|
||||
this.HitTestLocation, this.HitTestLocationEx, this.item, this.subItem, this.location, this.Group, this.ColumnIndex);
|
||||
}
|
||||
|
||||
internal class HeaderHitTestInfo
|
||||
{
|
||||
public int ColumnIndex;
|
||||
public bool IsOverCheckBox;
|
||||
public int OverDividerIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A TreeDataSourceAdapter knows how to build a tree structure from a binding list.
|
||||
/// </summary>
|
||||
/// <remarks>To build a tree</remarks>
|
||||
public class TreeDataSourceAdapter : DataSourceAdapter
|
||||
{
|
||||
#region Life and death
|
||||
|
||||
/// <summary>
|
||||
/// Create a data source adaptor that knows how to build a tree structure
|
||||
/// </summary>
|
||||
/// <param name="tlv"></param>
|
||||
public TreeDataSourceAdapter(DataTreeListView tlv)
|
||||
: base(tlv) {
|
||||
this.treeListView = tlv;
|
||||
this.treeListView.CanExpandGetter = delegate(object model) { return this.CalculateHasChildren(model); };
|
||||
this.treeListView.ChildrenGetter = delegate(object model) { return this.CalculateChildren(model); };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property/column that uniquely identifies each row.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value contained by this column must be unique across all rows
|
||||
/// in the data source. Odd and unpredictable things will happen if two
|
||||
/// rows have the same id.
|
||||
/// </para>
|
||||
/// <para>Null cannot be a valid key value.</para>
|
||||
/// </remarks>
|
||||
public virtual string KeyAspectName {
|
||||
get { return keyAspectName; }
|
||||
set {
|
||||
if (keyAspectName == value)
|
||||
return;
|
||||
keyAspectName = value;
|
||||
this.keyMunger = new Munger(this.KeyAspectName);
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
private string keyAspectName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property/column that contains the key of
|
||||
/// the parent of a row.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The test condition for deciding if one row is the parent of another is functionally
|
||||
/// equivilent to this:
|
||||
/// <code>
|
||||
/// Object.Equals(candidateParentRow[this.KeyAspectName], row[this.ParentKeyAspectName])
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>Unlike key value, parent keys can be null but a null parent key can only be used
|
||||
/// to identify root objects.</para>
|
||||
/// </remarks>
|
||||
public virtual string ParentKeyAspectName {
|
||||
get { return parentKeyAspectName; }
|
||||
set {
|
||||
if (parentKeyAspectName == value)
|
||||
return;
|
||||
parentKeyAspectName = value;
|
||||
this.parentKeyMunger = new Munger(this.ParentKeyAspectName);
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
private string parentKeyAspectName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value that identifies a row as a root object.
|
||||
/// When the ParentKey of a row equals the RootKeyValue, that row will
|
||||
/// be treated as root of the TreeListView.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The test condition for deciding a root object is functionally
|
||||
/// equivilent to this:
|
||||
/// <code>
|
||||
/// Object.Equals(candidateRow[this.ParentKeyAspectName], this.RootKeyValue)
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>The RootKeyValue can be null.</para>
|
||||
/// </remarks>
|
||||
public virtual object RootKeyValue {
|
||||
get { return rootKeyValue; }
|
||||
set {
|
||||
if (Equals(rootKeyValue, value))
|
||||
return;
|
||||
rootKeyValue = value;
|
||||
this.InitializeDataSource();
|
||||
}
|
||||
}
|
||||
private object rootKeyValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not the key columns (id and parent id) should
|
||||
/// be shown to the user.
|
||||
/// </summary>
|
||||
/// <remarks>This must be set before the DataSource is set. It has no effect
|
||||
/// afterwards.</remarks>
|
||||
public virtual bool ShowKeyColumns {
|
||||
get { return showKeyColumns; }
|
||||
set { showKeyColumns = value; }
|
||||
}
|
||||
private bool showKeyColumns = true;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DataTreeListView that is being managed
|
||||
/// </summary>
|
||||
protected DataTreeListView TreeListView {
|
||||
get { return treeListView; }
|
||||
}
|
||||
private readonly DataTreeListView treeListView;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override void InitializeDataSource() {
|
||||
base.InitializeDataSource();
|
||||
this.TreeListView.RebuildAll(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override void SetListContents() {
|
||||
this.TreeListView.Roots = this.CalculateRoots();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ShouldCreateColumn(PropertyDescriptor property) {
|
||||
// If the property is a key column, and we aren't supposed to show keys, don't show it
|
||||
if (!this.ShowKeyColumns && (property.Name == this.KeyAspectName || property.Name == this.ParentKeyAspectName))
|
||||
return false;
|
||||
|
||||
return base.ShouldCreateColumn(property);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void HandleListChangedItemChanged(System.ComponentModel.ListChangedEventArgs e) {
|
||||
// If the id or the parent id of a row changes, we just rebuild everything.
|
||||
// We can't do anything more specific. We don't know what the previous values, so we can't
|
||||
// tell the previous parent to refresh itself. If the id itself has changed, things that used
|
||||
// to be children will no longer be children. Just rebuild everything.
|
||||
// It seems PropertyDescriptor is only filled in .NET 4 :(
|
||||
if (e.PropertyDescriptor != null &&
|
||||
(e.PropertyDescriptor.Name == this.KeyAspectName ||
|
||||
e.PropertyDescriptor.Name == this.ParentKeyAspectName))
|
||||
this.InitializeDataSource();
|
||||
else
|
||||
base.HandleListChangedItemChanged(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
protected override void ChangePosition(int index) {
|
||||
// We can't use our base method directly, since the normal position management
|
||||
// doesn't know about our tree structure. They treat our dataset as a flat list
|
||||
// but we have a collapsable structure. This means that the 5'th row to them
|
||||
// may not even be visible to us
|
||||
|
||||
// To display the n'th row, we have to make sure that all its ancestors
|
||||
// are expanded. Then we will be able to select it.
|
||||
object model = this.CurrencyManager.List[index];
|
||||
object parent = this.CalculateParent(model);
|
||||
while (parent != null && !this.TreeListView.IsExpanded(parent)) {
|
||||
this.TreeListView.Expand(parent);
|
||||
parent = this.CalculateParent(parent);
|
||||
}
|
||||
|
||||
base.ChangePosition(index);
|
||||
}
|
||||
|
||||
private IEnumerable CalculateRoots() {
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object parentKey = this.GetParentValue(x);
|
||||
if (Object.Equals(this.RootKeyValue, parentKey))
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CalculateHasChildren(object model) {
|
||||
object keyValue = this.GetKeyValue(model);
|
||||
if (keyValue == null)
|
||||
return false;
|
||||
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object parentKey = this.GetParentValue(x);
|
||||
if (Object.Equals(keyValue, parentKey))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable CalculateChildren(object model) {
|
||||
object keyValue = this.GetKeyValue(model);
|
||||
if (keyValue != null) {
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object parentKey = this.GetParentValue(x);
|
||||
if (Object.Equals(keyValue, parentKey))
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object CalculateParent(object model) {
|
||||
object parentValue = this.GetParentValue(model);
|
||||
if (parentValue == null)
|
||||
return null;
|
||||
|
||||
foreach (object x in this.CurrencyManager.List) {
|
||||
object key = this.GetKeyValue(x);
|
||||
if (Object.Equals(parentValue, key))
|
||||
return x;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private object GetKeyValue(object model) {
|
||||
return this.keyMunger == null ? null : this.keyMunger.GetValue(model);
|
||||
}
|
||||
|
||||
private object GetParentValue(object model) {
|
||||
return this.parentKeyMunger == null ? null : this.parentKeyMunger.GetValue(model);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Munger keyMunger;
|
||||
private Munger parentKeyMunger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Virtual groups - Classes and interfaces needed to implement virtual groups
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 28/08/2009 11:10am
|
||||
*
|
||||
* Change log:
|
||||
* 2011-02-21 JPP - Correctly honor group comparer and collapsible groups settings
|
||||
* v2.3
|
||||
* 2009-08-28 JPP - Initial version
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A IVirtualGroups is the interface that a virtual list must implement to support virtual groups
|
||||
/// </summary>
|
||||
public interface IVirtualGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the list of groups that should be shown according to the given parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
IList<OLVGroup> GetGroups(GroupingParameters parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the item that appears at the given position within the given group.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="indexWithinGroup"></param>
|
||||
/// <returns></returns>
|
||||
int GetGroupMember(OLVGroup group, int indexWithinGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the group to which the given item belongs
|
||||
/// </summary>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
int GetGroup(int itemIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Return the index at which the given item is shown in the given group
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
int GetIndexWithinGroup(OLVGroup group, int itemIndex);
|
||||
|
||||
/// <summary>
|
||||
/// A hint that the given range of items are going to be required
|
||||
/// </summary>
|
||||
/// <param name="fromGroupIndex"></param>
|
||||
/// <param name="fromIndex"></param>
|
||||
/// <param name="toGroupIndex"></param>
|
||||
/// <param name="toIndex"></param>
|
||||
void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a safe, do nothing implementation of a grouping strategy
|
||||
/// </summary>
|
||||
public class AbstractVirtualGroups : IVirtualGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the list of groups that should be shown according to the given parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public virtual IList<OLVGroup> GetGroups(GroupingParameters parameters) {
|
||||
return new List<OLVGroup>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the item that appears at the given position within the given group.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="indexWithinGroup"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetGroupMember(OLVGroup group, int indexWithinGroup) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of the group to which the given item belongs
|
||||
/// </summary>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetGroup(int itemIndex) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the index at which the given item is shown in the given group
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetIndexWithinGroup(OLVGroup group, int itemIndex) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A hint that the given range of items are going to be required
|
||||
/// </summary>
|
||||
/// <param name="fromGroupIndex"></param>
|
||||
/// <param name="fromIndex"></param>
|
||||
/// <param name="toGroupIndex"></param>
|
||||
/// <param name="toIndex"></param>
|
||||
public virtual void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Provides grouping functionality to a FastObjectListView
|
||||
/// </summary>
|
||||
public class FastListGroupingStrategy : AbstractVirtualGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Create groups for FastListView
|
||||
/// </summary>
|
||||
/// <param name="parmameters"></param>
|
||||
/// <returns></returns>
|
||||
public override IList<OLVGroup> GetGroups(GroupingParameters parmameters) {
|
||||
|
||||
// There is a lot of overlap between this method and ObjectListView.MakeGroups()
|
||||
// Any changes made here may need to be reflected there
|
||||
|
||||
// This strategy can only be used on FastObjectListViews
|
||||
FastObjectListView folv = (FastObjectListView)parmameters.ListView;
|
||||
|
||||
// Separate the list view items into groups, using the group key as the descrimanent
|
||||
int objectCount = 0;
|
||||
NullableDictionary<object, List<object>> map = new NullableDictionary<object, List<object>>();
|
||||
foreach (object model in folv.FilteredObjects) {
|
||||
object key = parmameters.GroupByColumn.GetGroupKey(model);
|
||||
if (!map.ContainsKey(key))
|
||||
map[key] = new List<object>();
|
||||
map[key].Add(model);
|
||||
objectCount++;
|
||||
}
|
||||
|
||||
// Sort the items within each group
|
||||
// TODO: Give parameters a ModelComparer property
|
||||
OLVColumn primarySortColumn = parmameters.SortItemsByPrimaryColumn ? parmameters.ListView.GetColumn(0) : parmameters.PrimarySort;
|
||||
ModelObjectComparer sorter = new ModelObjectComparer(primarySortColumn, parmameters.PrimarySortOrder,
|
||||
parmameters.SecondarySort, parmameters.SecondarySortOrder);
|
||||
foreach (object key in map.Keys) {
|
||||
map[key].Sort(sorter);
|
||||
}
|
||||
|
||||
// Make a list of the required groups
|
||||
List<OLVGroup> groups = new List<OLVGroup>();
|
||||
foreach (object key in map.Keys) {
|
||||
string title = parmameters.GroupByColumn.ConvertGroupKeyToTitle(key);
|
||||
if (!String.IsNullOrEmpty(parmameters.TitleFormat)) {
|
||||
int count = map[key].Count;
|
||||
string format = (count == 1 ? parmameters.TitleSingularFormat : parmameters.TitleFormat);
|
||||
try {
|
||||
title = String.Format(format, title, count);
|
||||
} catch (FormatException) {
|
||||
title = "Invalid group format: " + format;
|
||||
}
|
||||
}
|
||||
OLVGroup lvg = new OLVGroup(title);
|
||||
lvg.Collapsible = folv.HasCollapsibleGroups;
|
||||
lvg.Key = key;
|
||||
lvg.SortValue = key as IComparable;
|
||||
lvg.Contents = map[key].ConvertAll<int>(delegate(object x) { return folv.IndexOf(x); });
|
||||
lvg.VirtualItemCount = map[key].Count;
|
||||
if (parmameters.GroupByColumn.GroupFormatter != null)
|
||||
parmameters.GroupByColumn.GroupFormatter(lvg, parmameters);
|
||||
groups.Add(lvg);
|
||||
}
|
||||
|
||||
// Sort the groups
|
||||
if (parmameters.GroupByOrder != SortOrder.None)
|
||||
groups.Sort(parmameters.GroupComparer ?? new OLVGroupComparer(parmameters.GroupByOrder));
|
||||
|
||||
// Build an array that remembers which group each item belongs to.
|
||||
this.indexToGroupMap = new List<int>(objectCount);
|
||||
this.indexToGroupMap.AddRange(new int[objectCount]);
|
||||
|
||||
for (int i = 0; i < groups.Count; i++) {
|
||||
OLVGroup group = groups[i];
|
||||
List<int> members = (List<int>)group.Contents;
|
||||
foreach (int j in members)
|
||||
this.indexToGroupMap[j] = i;
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
private List<int> indexToGroupMap;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="indexWithinGroup"></param>
|
||||
/// <returns></returns>
|
||||
public override int GetGroupMember(OLVGroup group, int indexWithinGroup) {
|
||||
return (int)group.Contents[indexWithinGroup];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public override int GetGroup(int itemIndex) {
|
||||
return this.indexToGroupMap[itemIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="itemIndex"></param>
|
||||
/// <returns></returns>
|
||||
public override int GetIndexWithinGroup(OLVGroup group, int itemIndex) {
|
||||
return group.Contents.IndexOf(itemIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is the COM interface that a ListView must be given in order for groups in virtual lists to work.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This interface is NOT documented by MS. It was found on Greg Chapell's site. This means that there is
|
||||
/// no guarantee that it will work on future versions of Windows, nor continue to work on current ones.
|
||||
/// </remarks>
|
||||
[ComImport(),
|
||||
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
|
||||
Guid("44C09D56-8D3B-419D-A462-7B956B105B47")]
|
||||
internal interface IOwnerDataCallback
|
||||
{
|
||||
/// <summary>
|
||||
/// Not sure what this does
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <param name="pt"></param>
|
||||
void GetItemPosition(int i, out NativeMethods.POINT pt);
|
||||
|
||||
/// <summary>
|
||||
/// Not sure what this does
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <param name="pt"></param>
|
||||
void SetItemPosition(int t, NativeMethods.POINT pt);
|
||||
|
||||
/// <summary>
|
||||
/// Get the index of the item that occurs at the n'th position of the indicated group.
|
||||
/// </summary>
|
||||
/// <param name="groupIndex">Index of the group</param>
|
||||
/// <param name="n">Index within the group</param>
|
||||
/// <param name="itemIndex">Index of the item within the whole list</param>
|
||||
void GetItemInGroup(int groupIndex, int n, out int itemIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Get the index of the group to which the given item belongs
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">Index of the item within the whole list</param>
|
||||
/// <param name="occurrenceCount">Which occurences of the item is wanted</param>
|
||||
/// <param name="groupIndex">Index of the group</param>
|
||||
void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of groups that contain the given item
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">Index of the item within the whole list</param>
|
||||
/// <param name="occurrenceCount">How many groups does it occur within</param>
|
||||
void GetItemGroupCount(int itemIndex, out int occurrenceCount);
|
||||
|
||||
/// <summary>
|
||||
/// A hint to prepare any cache for the given range of requests
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <param name="j"></param>
|
||||
void OnCacheHint(NativeMethods.LVITEMINDEX i, NativeMethods.LVITEMINDEX j);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A default implementation of the IOwnerDataCallback interface
|
||||
/// </summary>
|
||||
[Guid("6FC61F50-80E8-49b4-B200-3F38D3865ABD")]
|
||||
internal class OwnerDataCallbackImpl : IOwnerDataCallback
|
||||
{
|
||||
public OwnerDataCallbackImpl(VirtualObjectListView olv) {
|
||||
this.olv = olv;
|
||||
}
|
||||
VirtualObjectListView olv;
|
||||
|
||||
#region IOwnerDataCallback Members
|
||||
|
||||
public void GetItemPosition(int i, out NativeMethods.POINT pt) {
|
||||
//System.Diagnostics.Debug.WriteLine("GetItemPosition");
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void SetItemPosition(int t, NativeMethods.POINT pt) {
|
||||
//System.Diagnostics.Debug.WriteLine("SetItemPosition");
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void GetItemInGroup(int groupIndex, int n, out int itemIndex) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("-> GetItemInGroup({0}, {1})", groupIndex, n));
|
||||
itemIndex = this.olv.GroupingStrategy.GetGroupMember(this.olv.OLVGroups[groupIndex], n);
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", itemIndex));
|
||||
}
|
||||
|
||||
public void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroup({0}, {1})", itemIndex, occurrenceCount));
|
||||
groupIndex = this.olv.GroupingStrategy.GetGroup(itemIndex);
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", groupIndex));
|
||||
}
|
||||
|
||||
public void GetItemGroupCount(int itemIndex, out int occurrenceCount) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroupCount({0})", itemIndex));
|
||||
occurrenceCount = 1;
|
||||
}
|
||||
|
||||
public void OnCacheHint(NativeMethods.LVITEMINDEX from, NativeMethods.LVITEMINDEX to) {
|
||||
//System.Diagnostics.Debug.WriteLine(String.Format("OnCacheHint({0}, {1}, {2}, {3})", from.iGroup, from.iItem, to.iGroup, to.iItem));
|
||||
this.olv.GroupingStrategy.CacheHint(from.iGroup, from.iItem, to.iGroup, to.iItem);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* VirtualListDataSource - Encapsulate how data is provided to a virtual list
|
||||
*
|
||||
* Author: Phillip Piper
|
||||
* Date: 28/08/2009 11:10am
|
||||
*
|
||||
* Change log:
|
||||
* v2.4
|
||||
* 2010-04-01 JPP - Added IFilterableDataSource
|
||||
* v2.3
|
||||
* 2009-08-28 JPP - Initial version (Separated from VirtualObjectListView.cs)
|
||||
*
|
||||
* To do:
|
||||
*
|
||||
* Copyright (C) 2009-2014 Phillip Piper
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BrightIdeasSoftware
|
||||
{
|
||||
/// <summary>
|
||||
/// A VirtualListDataSource is a complete manner to provide functionality to a virtual list.
|
||||
/// An object that implements this interface provides a VirtualObjectListView with all the
|
||||
/// information it needs to be fully functional.
|
||||
/// </summary>
|
||||
/// <remarks>Implementors must provide functioning implementations of at least GetObjectCount()
|
||||
/// and GetNthObject(), otherwise nothing will appear in the list.</remarks>
|
||||
public interface IVirtualListDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the object that should be displayed at the n'th row.
|
||||
/// </summary>
|
||||
/// <param name="n">The index of the row whose object is to be returned.</param>
|
||||
/// <returns>The model object at the n'th row, or null if the fetching was unsuccessful.</returns>
|
||||
Object GetNthObject(int n);
|
||||
|
||||
/// <summary>
|
||||
/// Return the number of rows that should be visible in the virtual list
|
||||
/// </summary>
|
||||
/// <returns>The number of rows the list view should have.</returns>
|
||||
int GetObjectCount();
|
||||
|
||||
/// <summary>
|
||||
/// Get the index of the row that is showing the given model object
|
||||
/// </summary>
|
||||
/// <param name="model">The model object sought</param>
|
||||
/// <returns>The index of the row showing the model, or -1 if the object could not be found.</returns>
|
||||
int GetObjectIndex(Object model);
|
||||
|
||||
/// <summary>
|
||||
/// The ListView is about to request the given range of items. Do
|
||||
/// whatever caching seems appropriate.
|
||||
/// </summary>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
void PrepareCache(int first, int last);
|
||||
|
||||
/// <summary>
|
||||
/// Find the first row that "matches" the given text in the given range.
|
||||
/// </summary>
|
||||
/// <param name="value">The text typed by the user</param>
|
||||
/// <param name="first">Start searching from this index. This may be greater than the 'to' parameter,
|
||||
/// in which case the search should descend</param>
|
||||
/// <param name="last">Do not search beyond this index. This may be less than the 'from' parameter.</param>
|
||||
/// <param name="column">The column that should be considered when looking for a match.</param>
|
||||
/// <returns>Return the index of row that was matched, or -1 if no match was found</returns>
|
||||
int SearchText(string value, int first, int last, OLVColumn column);
|
||||
|
||||
/// <summary>
|
||||
/// Sort the model objects in the data source.
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="order"></param>
|
||||
void Sort(OLVColumn column, SortOrder order);
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
// Modification commands
|
||||
// THINK: Should we split these four into a separate interface?
|
||||
|
||||
/// <summary>
|
||||
/// Add the given collection of model objects to this control.
|
||||
/// </summary>
|
||||
/// <param name="modelObjects">A collection of model objects</param>
|
||||
void AddObjects(ICollection modelObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all of the given objects from the control
|
||||
/// </summary>
|
||||
/// <param name="modelObjects">Collection of objects to be removed</param>
|
||||
void RemoveObjects(ICollection modelObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Set the collection of objects that this control will show.
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
void SetObjects(IEnumerable collection);
|
||||
|
||||
/// <summary>
|
||||
/// Update/replace the nth object with the given object
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObject"></param>
|
||||
void UpdateObject(int index, object modelObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This extension allow virtual lists to filter their contents
|
||||
/// </summary>
|
||||
public interface IFilterableDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// All subsequent retrievals on this data source should be filtered
|
||||
/// through the given filters. null means no filtering of that kind.
|
||||
/// </summary>
|
||||
/// <param name="modelFilter"></param>
|
||||
/// <param name="listFilter"></param>
|
||||
void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A do-nothing implementation of the VirtualListDataSource interface.
|
||||
/// </summary>
|
||||
public class AbstractVirtualListDataSource : IVirtualListDataSource, IFilterableDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an AbstractVirtualListDataSource
|
||||
/// </summary>
|
||||
/// <param name="listView"></param>
|
||||
public AbstractVirtualListDataSource(VirtualObjectListView listView) {
|
||||
this.listView = listView;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list view that this data source is giving information to.
|
||||
/// </summary>
|
||||
protected VirtualObjectListView listView;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
public virtual object GetNthObject(int n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual int GetObjectCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int GetObjectIndex(object model) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
public virtual void PrepareCache(int from, int to) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public virtual int SearchText(string value, int first, int last, OLVColumn column) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="order"></param>
|
||||
public virtual void Sort(OLVColumn column, SortOrder order) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
public virtual void AddObjects(ICollection modelObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelObjects"></param>
|
||||
public virtual void RemoveObjects(ICollection modelObjects) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
public virtual void SetObjects(IEnumerable collection) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update/replace the nth object with the given object
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="modelObject"></param>
|
||||
public virtual void UpdateObject(int index, object modelObject) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a useful default implementation of SearchText method, intended to be called
|
||||
/// by implementors of IVirtualListDataSource.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
static public int DefaultSearchText(string value, int first, int last, OLVColumn column, IVirtualListDataSource source) {
|
||||
if (first <= last) {
|
||||
for (int i = first; i <= last; i++) {
|
||||
string data = column.GetStringValue(source.GetNthObject(i));
|
||||
if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
for (int i = first; i >= last; i--) {
|
||||
string data = column.GetStringValue(source.GetNthObject(i));
|
||||
if (data.StartsWith(value, StringComparison.CurrentCultureIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#region IFilterableDataSource Members
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="modelFilter"></param>
|
||||
/// <param name="listFilter"></param>
|
||||
virtual public void ApplyFilters(IModelFilter modelFilter, IListFilter listFilter) {
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class mimics the behavior of VirtualObjectListView v1.x.
|
||||
/// </summary>
|
||||
public class VirtualListVersion1DataSource : AbstractVirtualListDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a VirtualListVersion1DataSource
|
||||
/// </summary>
|
||||
/// <param name="listView"></param>
|
||||
public VirtualListVersion1DataSource(VirtualObjectListView listView)
|
||||
: base(listView) {
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// How will the n'th object of the data source be fetched?
|
||||
/// </summary>
|
||||
public RowGetterDelegate RowGetter {
|
||||
get { return rowGetter; }
|
||||
set { rowGetter = value; }
|
||||
}
|
||||
private RowGetterDelegate rowGetter;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IVirtualListDataSource implementation
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
public override object GetNthObject(int n) {
|
||||
if (this.RowGetter == null)
|
||||
return null;
|
||||
else
|
||||
return this.RowGetter(n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="first"></param>
|
||||
/// <param name="last"></param>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public override int SearchText(string value, int first, int last, OLVColumn column) {
|
||||
return DefaultSearchText(value, first, last, column, this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user