Imported existing sources

This commit is contained in:
Daniel Brunner
2017-02-04 13:47:58 +01:00
parent 14a5be61cc
commit 7c97b9e539
86 changed files with 49665 additions and 0 deletions
+335
View File
@@ -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
{
}
}
+291
View File
@@ -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
}
}
+613
View File
@@ -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
}
}
+151
View File
@@ -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
}
+407
View File
@@ -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
+99
View File
@@ -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
+175
View File
@@ -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;
}
}
+747
View File
@@ -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
}
}
+568
View File
@@ -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
+87
View File
@@ -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;
}
}
}
}
+272
View File
@@ -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
}
}
+162
View File
@@ -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
}
}
+383
View File
@@ -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;
}
}
}
+262
View File
@@ -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;
}
}
+355
View File
@@ -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
}
}
+334
View File
@@ -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
}
}