### 2021-01-12 dev更新

------
#### ryControls    V2.1.2101.1201
- *.[更新]内置的ObjectListView从1.13更新到2.9.1版本,并对主要属性进行汉化。
- *.[修复]修复新版ObjectListView选中项有筛选结果时,筛选结果白色字体看不清的BUG。
- *.[改进]TextBoxEx2默认事件改为TextChanged2。
This commit is contained in:
鑫Intel 2021-01-12 16:32:13 +08:00
parent 7a89ce0821
commit 523add43be
137 changed files with 85892 additions and 6360 deletions

View File

@ -1,2 +1,2 @@
[Money_Op]
hwnd=3411152
hwnd=5315574

View File

@ -1,4 +1,4 @@
<root>
<list id="LastUpdateTime" Value="2020/12/27 16:40:03" />
<list id="LastUpdateTime" Value="2021/1/12 15:16:09" />
<list id="UpdateAfterTime" Value="0" />
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,11 @@
### 2020-12-29 dev更新
### 2021-01-12更新
------
#### ryControls V2.1.2101.1201
- *.[更新]内置的ObjectListView从1.13更新到2.9.1版本,并对主要属性进行汉化。
- *.[修复]修复新版ObjectListView选中项有筛选结果时,筛选结果白色字体看不清的BUG。
- *.[改进]TextBoxEx2默认事件改为TextChanged2。
### 2020-12-29 dev更新
------
#### ryControls V2.1.2012.2901
- *.[新增]IconViewEx控件支持角标设置。

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
// 方法是按如下所示使用“*”: :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.1.2012.2001")]
[assembly: AssemblyFileVersion("2.1.2012.2001")]
[assembly: AssemblyVersion("2.1.2012.3001")]
[assembly: AssemblyFileVersion("2.1.2012.3001")]

View File

@ -195,7 +195,7 @@ namespace ryCommon
try
{
System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); // 当地时区
DateTime dt = startTime.AddSeconds(timeStamp.ToInt64(0));
DateTime dt = startTime.AddSeconds(timeStamp.ToDouble(0));
return dt;
}
catch { return new System.DateTime(1970, 1, 1); }

Binary file not shown.

Binary file not shown.

View File

@ -219,6 +219,7 @@ namespace ryControls
/// <param name="r">区域大小</param>
/// <param name="image"></param>
/// <param name="backColor"></param>
/// <param name="BadgeImageIndex"></param>
/// <returns></returns>
private Size DrawIcon(Graphics g, Rectangle r, Image image, Color backColor,int BadgeImageIndex)
{

View File

@ -9,6 +9,7 @@ namespace ryControls
/// <summary>
/// 美化文本框控件
/// </summary>
[DefaultEvent("TextChanged2")]
public partial class TextBoxEx2 : UserControl
{
/// <summary>

View File

@ -0,0 +1,520 @@
/*
* CellEditKeyEngine - A engine that allows the behaviour of arbitrary keys to be configured
*
* Author: Phillip Piper
* Date: 3-March-2011 10:53 pm
*
* Change log:
* v2.8
* 2014-05-30 JPP - When a row is disabled, skip over it when looking for another cell to edit
* v2.5
* 2012-04-14 JPP - Fixed bug where, on a OLV with only a single editable column, tabbing
* to change rows would edit the cell above rather than the cell below
* the cell being edited.
* 2.5
* 2011-03-03 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using BrightIdeasSoftware;
namespace BrightIdeasSoftware {
/// <summary>
/// Indicates the behavior of a key when a cell "on the edge" is being edited.
/// and the normal behavior of that key would exceed the edge. For example,
/// for a key that normally moves one column to the left, the "edge" would be
/// the left most column, since the normal action of the key cannot be taken
/// (since there are no more columns to the left).
/// </summary>
public enum CellEditAtEdgeBehaviour {
/// <summary>
/// The key press will be ignored
/// </summary>
Ignore,
/// <summary>
/// The key press will result in the cell editing wrapping to the
/// cell on the opposite edge.
/// </summary>
Wrap,
/// <summary>
/// The key press will wrap, but the column will be changed to the
/// appropiate adjacent column. This only makes sense for keys where
/// the normal action is ChangeRow.
/// </summary>
ChangeColumn,
/// <summary>
/// The key press will wrap, but the row will be changed to the
/// appropiate adjacent row. This only makes sense for keys where
/// the normal action is ChangeColumn.
/// </summary>
ChangeRow,
/// <summary>
/// The key will result in the current edit operation being ended.
/// </summary>
EndEdit
};
/// <summary>
/// Indicates the normal behaviour of a key when used during a cell edit
/// operation.
/// </summary>
public enum CellEditCharacterBehaviour {
/// <summary>
/// The key press will be ignored
/// </summary>
Ignore,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the next editable cell to the left.
/// </summary>
ChangeColumnLeft,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the next editable cell to the right.
/// </summary>
ChangeColumnRight,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the row above.
/// </summary>
ChangeRowUp,
/// <summary>
/// The key press will end the current edit and begin an edit
/// operation on the row below
/// </summary>
ChangeRowDown,
/// <summary>
/// The key press will cancel the current edit
/// </summary>
CancelEdit,
/// <summary>
/// The key press will finish the current edit operation
/// </summary>
EndEdit,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb1,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb2,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb3,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb4,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb5,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb6,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb7,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb8,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb9,
/// <summary>
/// Custom verb that can be used for specialized actions.
/// </summary>
CustomVerb10,
};
/// <summary>
/// Instances of this class handle key presses during a cell edit operation.
/// </summary>
public class CellEditKeyEngine {
#region Public interface
/// <summary>
/// Sets the behaviour of a given key
/// </summary>
/// <param name="key"></param>
/// <param name="normalBehaviour"></param>
/// <param name="atEdgeBehaviour"></param>
public virtual void SetKeyBehaviour(Keys key, CellEditCharacterBehaviour normalBehaviour, CellEditAtEdgeBehaviour atEdgeBehaviour) {
this.CellEditKeyMap[key] = normalBehaviour;
this.CellEditKeyAtEdgeBehaviourMap[key] = atEdgeBehaviour;
}
/// <summary>
/// Handle a key press
/// </summary>
/// <param name="olv"></param>
/// <param name="keyData"></param>
/// <returns>True if the key was completely handled.</returns>
public virtual bool HandleKey(ObjectListView olv, Keys keyData) {
if (olv == null) throw new ArgumentNullException("olv");
CellEditCharacterBehaviour behaviour;
if (!CellEditKeyMap.TryGetValue(keyData, out behaviour))
return false;
this.ListView = olv;
switch (behaviour) {
case CellEditCharacterBehaviour.Ignore:
break;
case CellEditCharacterBehaviour.CancelEdit:
this.HandleCancelEdit();
break;
case CellEditCharacterBehaviour.EndEdit:
this.HandleEndEdit();
break;
case CellEditCharacterBehaviour.ChangeColumnLeft:
case CellEditCharacterBehaviour.ChangeColumnRight:
this.HandleColumnChange(keyData, behaviour);
break;
case CellEditCharacterBehaviour.ChangeRowDown:
case CellEditCharacterBehaviour.ChangeRowUp:
this.HandleRowChange(keyData, behaviour);
break;
default:
return this.HandleCustomVerb(keyData, behaviour);
};
return true;
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the ObjectListView on which the current key is being handled.
/// This cannot be null.
/// </summary>
protected ObjectListView ListView {
get { return listView; }
set { listView = value; }
}
private ObjectListView listView;
/// <summary>
/// Gets the row of the cell that is currently being edited
/// </summary>
protected OLVListItem ItemBeingEdited {
get {
return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? null : this.ListView.CellEditEventArgs.ListViewItem;
}
}
/// <summary>
/// Gets the index of the column of the cell that is being edited
/// </summary>
protected int SubItemIndexBeingEdited {
get {
return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? -1 : this.ListView.CellEditEventArgs.SubItemIndex;
}
}
/// <summary>
/// Gets or sets the map that remembers the normal behaviour of keys
/// </summary>
protected IDictionary<Keys, CellEditCharacterBehaviour> CellEditKeyMap {
get {
if (cellEditKeyMap == null)
this.InitializeCellEditKeyMaps();
return cellEditKeyMap;
}
set {
cellEditKeyMap = value;
}
}
private IDictionary<Keys, CellEditCharacterBehaviour> cellEditKeyMap;
/// <summary>
/// Gets or sets the map that remembers the desired behaviour of keys
/// on edge cases.
/// </summary>
protected IDictionary<Keys, CellEditAtEdgeBehaviour> CellEditKeyAtEdgeBehaviourMap {
get {
if (cellEditKeyAtEdgeBehaviourMap == null)
this.InitializeCellEditKeyMaps();
return cellEditKeyAtEdgeBehaviourMap;
}
set {
cellEditKeyAtEdgeBehaviourMap = value;
}
}
private IDictionary<Keys, CellEditAtEdgeBehaviour> cellEditKeyAtEdgeBehaviourMap;
#endregion
#region Initialization
/// <summary>
/// Setup the default key mapping
/// </summary>
protected virtual void InitializeCellEditKeyMaps() {
this.cellEditKeyMap = new Dictionary<Keys, CellEditCharacterBehaviour>();
this.cellEditKeyMap[Keys.Escape] = CellEditCharacterBehaviour.CancelEdit;
this.cellEditKeyMap[Keys.Return] = CellEditCharacterBehaviour.EndEdit;
this.cellEditKeyMap[Keys.Enter] = CellEditCharacterBehaviour.EndEdit;
this.cellEditKeyMap[Keys.Tab] = CellEditCharacterBehaviour.ChangeColumnRight;
this.cellEditKeyMap[Keys.Tab | Keys.Shift] = CellEditCharacterBehaviour.ChangeColumnLeft;
this.cellEditKeyMap[Keys.Left | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnLeft;
this.cellEditKeyMap[Keys.Right | Keys.Alt] = CellEditCharacterBehaviour.ChangeColumnRight;
this.cellEditKeyMap[Keys.Up | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowUp;
this.cellEditKeyMap[Keys.Down | Keys.Alt] = CellEditCharacterBehaviour.ChangeRowDown;
this.cellEditKeyAtEdgeBehaviourMap = new Dictionary<Keys, CellEditAtEdgeBehaviour>();
this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Tab | Keys.Shift] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Left | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Right | Keys.Alt] = CellEditAtEdgeBehaviour.Wrap;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Up | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
this.cellEditKeyAtEdgeBehaviourMap[Keys.Down | Keys.Alt] = CellEditAtEdgeBehaviour.ChangeColumn;
}
#endregion
#region Command handling
/// <summary>
/// Handle the end edit command
/// </summary>
protected virtual void HandleEndEdit() {
this.ListView.PossibleFinishCellEditing();
}
/// <summary>
/// Handle the cancel edit command
/// </summary>
protected virtual void HandleCancelEdit() {
this.ListView.CancelCellEdit();
}
/// <summary>
/// Placeholder that subclasses can override to handle any custom verbs
/// </summary>
/// <param name="keyData"></param>
/// <param name="behaviour"></param>
/// <returns></returns>
protected virtual bool HandleCustomVerb(Keys keyData, CellEditCharacterBehaviour behaviour) {
return false;
}
/// <summary>
/// Handle a change row command
/// </summary>
/// <param name="keyData"></param>
/// <param name="behaviour"></param>
protected virtual void HandleRowChange(Keys keyData, CellEditCharacterBehaviour behaviour) {
// If we couldn't finish editing the current cell, don't try to move it
if (!this.ListView.PossibleFinishCellEditing())
return;
OLVListItem olvi = this.ItemBeingEdited;
int subItemIndex = this.SubItemIndexBeingEdited;
bool isGoingUp = behaviour == CellEditCharacterBehaviour.ChangeRowUp;
// Try to find a row above (or below) the currently edited cell
// If we find one, start editing it and we're done.
OLVListItem adjacentOlvi = this.GetAdjacentItemOrNull(olvi, isGoingUp);
if (adjacentOlvi != null) {
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
return;
}
// There is no adjacent row in the direction we want, so we must be on an edge.
CellEditAtEdgeBehaviour atEdgeBehaviour;
if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
switch (atEdgeBehaviour) {
case CellEditAtEdgeBehaviour.Ignore:
break;
case CellEditAtEdgeBehaviour.EndEdit:
this.ListView.PossibleFinishCellEditing();
break;
case CellEditAtEdgeBehaviour.Wrap:
adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
break;
case CellEditAtEdgeBehaviour.ChangeColumn:
// Figure out the next editable column
List<OLVColumn> editableColumnsInDisplayOrder = this.EditableColumnsInDisplayOrder;
int displayIndex = Math.Max(0, editableColumnsInDisplayOrder.IndexOf(this.ListView.GetColumn(subItemIndex)));
if (isGoingUp)
displayIndex = (editableColumnsInDisplayOrder.Count + displayIndex - 1) % editableColumnsInDisplayOrder.Count;
else
displayIndex = (displayIndex + 1) % editableColumnsInDisplayOrder.Count;
subItemIndex = editableColumnsInDisplayOrder[displayIndex].Index;
// Wrap to the next row and start the cell edit
adjacentOlvi = this.GetAdjacentItemOrNull(null, isGoingUp);
this.StartCellEditIfDifferent(adjacentOlvi, subItemIndex);
break;
}
}
/// <summary>
/// Handle a change column command
/// </summary>
/// <param name="keyData"></param>
/// <param name="behaviour"></param>
protected virtual void HandleColumnChange(Keys keyData, CellEditCharacterBehaviour behaviour)
{
// If we couldn't finish editing the current cell, don't try to move it
if (!this.ListView.PossibleFinishCellEditing())
return;
// Changing columns only works in details mode
if (this.ListView.View != View.Details)
return;
List<OLVColumn> editableColumns = this.EditableColumnsInDisplayOrder;
OLVListItem olvi = this.ItemBeingEdited;
int displayIndex = Math.Max(0,
editableColumns.IndexOf(this.ListView.GetColumn(this.SubItemIndexBeingEdited)));
bool isGoingLeft = behaviour == CellEditCharacterBehaviour.ChangeColumnLeft;
// Are we trying to continue past one of the edges?
if ((isGoingLeft && displayIndex == 0) ||
(!isGoingLeft && displayIndex == editableColumns.Count - 1))
{
// Yes, so figure out our at edge behaviour
CellEditAtEdgeBehaviour atEdgeBehaviour;
if (!this.CellEditKeyAtEdgeBehaviourMap.TryGetValue(keyData, out atEdgeBehaviour))
atEdgeBehaviour = CellEditAtEdgeBehaviour.Wrap;
switch (atEdgeBehaviour)
{
case CellEditAtEdgeBehaviour.Ignore:
return;
case CellEditAtEdgeBehaviour.EndEdit:
this.HandleEndEdit();
return;
case CellEditAtEdgeBehaviour.ChangeRow:
case CellEditAtEdgeBehaviour.Wrap:
if (atEdgeBehaviour == CellEditAtEdgeBehaviour.ChangeRow)
olvi = GetAdjacentItem(olvi, isGoingLeft && displayIndex == 0);
if (isGoingLeft)
displayIndex = editableColumns.Count - 1;
else
displayIndex = 0;
break;
}
}
else
{
if (isGoingLeft)
displayIndex -= 1;
else
displayIndex += 1;
}
int subItemIndex = editableColumns[displayIndex].Index;
this.StartCellEditIfDifferent(olvi, subItemIndex);
}
#endregion
#region Utilities
/// <summary>
/// Start editing the indicated cell if that cell is not already being edited
/// </summary>
/// <param name="olvi">The row to edit</param>
/// <param name="subItemIndex">The cell within that row to edit</param>
protected void StartCellEditIfDifferent(OLVListItem olvi, int subItemIndex) {
if (this.ItemBeingEdited == olvi && this.SubItemIndexBeingEdited == subItemIndex)
return;
this.ListView.EnsureVisible(olvi.Index);
this.ListView.StartCellEdit(olvi, subItemIndex);
}
/// <summary>
/// Gets the adjacent item to the given item in the given direction.
/// If that item is disabled, continue in that direction until an enabled item is found.
/// </summary>
/// <param name="olvi">The row whose neighbour is sought</param>
/// <param name="up">The direction of the adjacentness</param>
/// <returns>An OLVListView adjacent to the given item, or null if there are no more enabled items in that direction.</returns>
protected OLVListItem GetAdjacentItemOrNull(OLVListItem olvi, bool up) {
OLVListItem item = up ? this.ListView.GetPreviousItem(olvi) : this.ListView.GetNextItem(olvi);
while (item != null && !item.Enabled)
item = up ? this.ListView.GetPreviousItem(item) : this.ListView.GetNextItem(item);
return item;
}
/// <summary>
/// Gets the adjacent item to the given item in the given direction, wrapping if needed.
/// </summary>
/// <param name="olvi">The row whose neighbour is sought</param>
/// <param name="up">The direction of the adjacentness</param>
/// <returns>An OLVListView adjacent to the given item, or null if there are no more items in that direction.</returns>
protected OLVListItem GetAdjacentItem(OLVListItem olvi, bool up) {
return this.GetAdjacentItemOrNull(olvi, up) ?? this.GetAdjacentItemOrNull(null, up);
}
/// <summary>
/// Gets a collection of columns that are editable in the order they are shown to the user
/// </summary>
protected List<OLVColumn> EditableColumnsInDisplayOrder {
get {
List<OLVColumn> editableColumnsInDisplayOrder = new List<OLVColumn>();
foreach (OLVColumn x in this.ListView.ColumnsInDisplayOrder)
if (x.IsEditable)
editableColumnsInDisplayOrder.Add(x);
return editableColumnsInDisplayOrder;
}
}
#endregion
}
}

View File

@ -0,0 +1,288 @@
/*
* CellEditors - Several slightly modified controls that are used as celleditors within ObjectListView.
*
* Author: Phillip Piper
* Date: 20/10/2008 5:15 PM
*
* Change log:
* v2.6
* 2012-08-02 JPP - Make most editors public so they can be reused/subclassed
* v2.3
* 2009-08-13 JPP - Standardized code formatting
* v2.2.1
* 2008-01-18 JPP - Added special handling for enums
* 2008-01-16 JPP - Added EditorRegistry
* v2.0.1
* 2008-10-20 JPP - Separated from ObjectListView.cs
*
* Copyright (C) 2006-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// These items allow combo boxes to remember a value and its description.
/// </summary>
public class ComboBoxItem
{
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="description"></param>
public ComboBoxItem(Object key, String description) {
this.key = key;
this.description = description;
}
private readonly String description;
/// <summary>
///
/// </summary>
public Object Key {
get { return key; }
}
private readonly Object key;
/// <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 this.description;
}
}
//-----------------------------------------------------------------------
// Cell editors
// These classes are simple cell editors that make it easier to get and set
// the value that the control is showing.
// In many cases, you can intercept the CellEditStarting event to
// change the characteristics of the editor. For example, changing
// the acceptable range for a numeric editor or changing the strings
// that respresent true and false values for a boolean editor.
/// <summary>
/// This editor shows and auto completes values from the given listview column.
/// </summary>
[ToolboxItem(false)]
public class AutoCompleteCellEditor : ComboBox
{
/// <summary>
/// Create an AutoCompleteCellEditor
/// </summary>
/// <param name="lv"></param>
/// <param name="column"></param>
public AutoCompleteCellEditor(ObjectListView lv, OLVColumn column) {
this.DropDownStyle = ComboBoxStyle.DropDown;
Dictionary<String, bool> alreadySeen = new Dictionary<string, bool>();
for (int i = 0; i < Math.Min(lv.GetItemCount(), 1000); i++) {
String str = column.GetStringValue(lv.GetModelObject(i));
if (!alreadySeen.ContainsKey(str)) {
this.Items.Add(str);
alreadySeen[str] = true;
}
}
this.Sorted = true;
this.AutoCompleteSource = AutoCompleteSource.ListItems;
this.AutoCompleteMode = AutoCompleteMode.Append;
}
}
/// <summary>
/// This combo box is specialised to allow editing of an enum.
/// </summary>
[ToolboxItem(false)]
public class EnumCellEditor : ComboBox
{
/// <summary>
///
/// </summary>
/// <param name="type"></param>
public EnumCellEditor(Type type) {
this.DropDownStyle = ComboBoxStyle.DropDownList;
this.ValueMember = "Key";
ArrayList values = new ArrayList();
foreach (object value in Enum.GetValues(type))
values.Add(new ComboBoxItem(value, Enum.GetName(type, value)));
this.DataSource = values;
}
}
/// <summary>
/// This editor simply shows and edits integer values.
/// </summary>
[ToolboxItem(false)]
public class IntUpDown : NumericUpDown
{
/// <summary>
///
/// </summary>
public IntUpDown() {
this.DecimalPlaces = 0;
this.Minimum = -9999999;
this.Maximum = 9999999;
}
/// <summary>
/// Gets or sets the value shown by this editor
/// </summary>
new public int Value {
get { return Decimal.ToInt32(base.Value); }
set { base.Value = new Decimal(value); }
}
}
/// <summary>
/// This editor simply shows and edits unsigned integer values.
/// </summary>
/// <remarks>This class can't be made public because unsigned int is not a
/// CLS-compliant type. If you want to use, just copy the code to this class
/// into your project and use it from there.</remarks>
[ToolboxItem(false)]
internal class UintUpDown : NumericUpDown
{
public UintUpDown() {
this.DecimalPlaces = 0;
this.Minimum = 0;
this.Maximum = 9999999;
}
new public uint Value {
get { return Decimal.ToUInt32(base.Value); }
set { base.Value = new Decimal(value); }
}
}
/// <summary>
/// This editor simply shows and edits boolean values.
/// </summary>
[ToolboxItem(false)]
public class BooleanCellEditor : ComboBox
{
/// <summary>
///
/// </summary>
public BooleanCellEditor() {
this.DropDownStyle = ComboBoxStyle.DropDownList;
this.ValueMember = "Key";
ArrayList values = new ArrayList();
values.Add(new ComboBoxItem(false, "False"));
values.Add(new ComboBoxItem(true, "True"));
this.DataSource = values;
}
}
/// <summary>
/// This editor simply shows and edits boolean values using a checkbox
/// </summary>
[ToolboxItem(false)]
public class BooleanCellEditor2 : CheckBox
{
/// <summary>
/// Gets or sets the value shown by this editor
/// </summary>
public bool? Value {
get {
switch (this.CheckState) {
case CheckState.Checked: return true;
case CheckState.Indeterminate: return null;
case CheckState.Unchecked:
default: return false;
}
}
set {
if (value.HasValue)
this.CheckState = value.Value ? CheckState.Checked : CheckState.Unchecked;
else
this.CheckState = CheckState.Indeterminate;
}
}
/// <summary>
/// Gets or sets how the checkbox will be aligned
/// </summary>
public new HorizontalAlignment TextAlign {
get {
switch (this.CheckAlign) {
case ContentAlignment.MiddleRight: return HorizontalAlignment.Right;
case ContentAlignment.MiddleCenter: return HorizontalAlignment.Center;
case ContentAlignment.MiddleLeft:
default: return HorizontalAlignment.Left;
}
}
set {
switch (value) {
case HorizontalAlignment.Left:
this.CheckAlign = ContentAlignment.MiddleLeft;
break;
case HorizontalAlignment.Center:
this.CheckAlign = ContentAlignment.MiddleCenter;
break;
case HorizontalAlignment.Right:
this.CheckAlign = ContentAlignment.MiddleRight;
break;
}
}
}
}
/// <summary>
/// This editor simply shows and edits floating point values.
/// </summary>
/// <remarks>You can intercept the CellEditStarting event if you want
/// to change the characteristics of the editor. For example, by increasing
/// the number of decimal places.</remarks>
[ToolboxItem(false)]
public class FloatCellEditor : NumericUpDown
{
/// <summary>
///
/// </summary>
public FloatCellEditor() {
this.DecimalPlaces = 2;
this.Minimum = -9999999;
this.Maximum = 9999999;
}
/// <summary>
/// Gets or sets the value shown by this editor
/// </summary>
new public double Value {
get { return Convert.ToDouble(base.Value); }
set { base.Value = Convert.ToDecimal(value); }
}
}
}

View File

@ -0,0 +1,213 @@
/*
* EditorRegistry - A registry mapping types to cell editors.
*
* Author: Phillip Piper
* Date: 6-March-2011 7:53 am
*
* Change log:
* 2011-03-31 JPP - Use OLVColumn.DataType if the value to be edited is null
* 2011-03-06 JPP - Separated from CellEditors.cs
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
namespace BrightIdeasSoftware {
/// <summary>
/// A delegate that creates an editor for the given value
/// </summary>
/// <param name="model">The model from which that value came</param>
/// <param name="column">The column for which the editor is being created</param>
/// <param name="value">A representative value of the type to be edited. This value may not be the exact
/// value for the column/model combination. It could be simply representative of
/// the appropriate type of value.</param>
/// <returns>A control which can edit the given value</returns>
public delegate Control EditorCreatorDelegate(Object model, OLVColumn column, Object value);
/// <summary>
/// An editor registry gives a way to decide what cell editor should be used to edit
/// the value of a cell. Programmers can register non-standard types and the control that
/// should be used to edit instances of that type.
/// </summary>
/// <remarks>
/// <para>All ObjectListViews share the same editor registry.</para>
/// </remarks>
public class EditorRegistry {
#region Initializing
/// <summary>
/// Create an EditorRegistry
/// </summary>
public EditorRegistry() {
this.InitializeStandardTypes();
}
private void InitializeStandardTypes() {
this.Register(typeof(Boolean), typeof(BooleanCellEditor));
this.Register(typeof(Int16), typeof(IntUpDown));
this.Register(typeof(Int32), typeof(IntUpDown));
this.Register(typeof(Int64), typeof(IntUpDown));
this.Register(typeof(UInt16), typeof(UintUpDown));
this.Register(typeof(UInt32), typeof(UintUpDown));
this.Register(typeof(UInt64), typeof(UintUpDown));
this.Register(typeof(Single), typeof(FloatCellEditor));
this.Register(typeof(Double), typeof(FloatCellEditor));
this.Register(typeof(DateTime), delegate(Object model, OLVColumn column, Object value) {
DateTimePicker c = new DateTimePicker();
c.Format = DateTimePickerFormat.Short;
return c;
});
this.Register(typeof(Boolean), delegate(Object model, OLVColumn column, Object value) {
CheckBox c = new BooleanCellEditor2();
c.ThreeState = column.TriStateCheckBoxes;
return c;
});
}
#endregion
#region Registering
/// <summary>
/// Register that values of 'type' should be edited by instances of 'controlType'.
/// </summary>
/// <param name="type">The type of value to be edited</param>
/// <param name="controlType">The type of the Control that will edit values of 'type'</param>
/// <example>
/// ObjectListView.EditorRegistry.Register(typeof(Color), typeof(MySpecialColorEditor));
/// </example>
public void Register(Type type, Type controlType) {
this.Register(type, delegate(Object model, OLVColumn column, Object value) {
return controlType.InvokeMember("", BindingFlags.CreateInstance, null, null, null) as Control;
});
}
/// <summary>
/// Register the given delegate so that it is called to create editors
/// for values of the given type
/// </summary>
/// <param name="type">The type of value to be edited</param>
/// <param name="creator">The delegate that will create a control that can edit values of 'type'</param>
/// <example>
/// ObjectListView.EditorRegistry.Register(typeof(Color), CreateColorEditor);
/// ...
/// public Control CreateColorEditor(Object model, OLVColumn column, Object value)
/// {
/// return new MySpecialColorEditor();
/// }
/// </example>
public void Register(Type type, EditorCreatorDelegate creator) {
this.creatorMap[type] = creator;
}
/// <summary>
/// Register a delegate that will be called to create an editor for values
/// that have not been handled.
/// </summary>
/// <param name="creator">The delegate that will create a editor for all other types</param>
public void RegisterDefault(EditorCreatorDelegate creator) {
this.defaultCreator = creator;
}
/// <summary>
/// Register a delegate that will be given a chance to create a control
/// before any other option is considered.
/// </summary>
/// <param name="creator">The delegate that will create a control</param>
public void RegisterFirstChance(EditorCreatorDelegate creator) {
this.firstChanceCreator = creator;
}
/// <summary>
/// Remove the registered handler for the given type
/// </summary>
/// <remarks>Does nothing if the given type doesn't exist</remarks>
/// <param name="type">The type whose registration is to be removed</param>
public void Unregister(Type type) {
if (this.creatorMap.ContainsKey(type))
this.creatorMap.Remove(type);
}
#endregion
#region Accessing
/// <summary>
/// Create and return an editor that is appropriate for the given value.
/// Return null if no appropriate editor can be found.
/// </summary>
/// <param name="model">The model involved</param>
/// <param name="column">The column to be edited</param>
/// <param name="value">The value to be edited. This value may not be the exact
/// value for the column/model combination. It could be simply representative of
/// the appropriate type of value.</param>
/// <returns>A Control that can edit the given type of values</returns>
public Control GetEditor(Object model, OLVColumn column, Object value) {
Control editor;
// Give the first chance delegate a chance to decide
if (this.firstChanceCreator != null) {
editor = this.firstChanceCreator(model, column, value);
if (editor != null)
return editor;
}
// Try to find a creator based on the type of the value (or the column)
Type type = value == null ? column.DataType : value.GetType();
if (type != null && this.creatorMap.ContainsKey(type)) {
editor = this.creatorMap[type](model, column, value);
if (editor != null)
return editor;
}
// Enums without other processing get a special editor
if (value != null && value.GetType().IsEnum)
return this.CreateEnumEditor(value.GetType());
// Give any default creator a final chance
if (this.defaultCreator != null)
return this.defaultCreator(model, column, value);
return null;
}
/// <summary>
/// Create and return an editor that will edit values of the given type
/// </summary>
/// <param name="type">A enum type</param>
protected Control CreateEnumEditor(Type type) {
return new EnumCellEditor(type);
}
#endregion
#region Private variables
private EditorCreatorDelegate firstChanceCreator;
private EditorCreatorDelegate defaultCreator;
private Dictionary<Type, EditorCreatorDelegate> creatorMap = new Dictionary<Type, EditorCreatorDelegate>();
#endregion
}
}

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Dictionary>
<Words>
<Recognized>
<Word>br</Word>
<Word>Canceled</Word>
<Word>Center</Word>
<Word>Color</Word>
<Word>Colors</Word>
<Word>f</Word>
<Word>fmt</Word>
<Word>g</Word>
<Word>gdi</Word>
<Word>hti</Word>
<Word>i</Word>
<Word>lightbox</Word>
<Word>lv</Word>
<Word>lvi</Word>
<Word>lvsi</Word>
<Word>m</Word>
<Word>multi</Word>
<Word>Munger</Word>
<Word>n</Word>
<Word>olv</Word>
<Word>olvi</Word>
<Word>p</Word>
<Word>parms</Word>
<Word>r</Word>
<Word>Renderer</Word>
<Word>s</Word>
<Word>SubItem</Word>
<Word>Unapply</Word>
<Word>Unpause</Word>
<Word>x</Word>
<Word>y</Word>
</Recognized>
<Deprecated>
<Term PreferredAlternate="EnterpriseServices">ComPlus</Term>
</Deprecated>
</Words>
<Acronyms>
<CasingExceptions>
<Acronym>OLV</Acronym>
</CasingExceptions>
</Acronyms>
</Dictionary>

View File

@ -0,0 +1,239 @@
/*
* DataListView - A data-bindable listview
*
* Author: Phillip Piper
* Date: 27/09/2008 9:15 AM
*
* Change log:
* 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call
* v2.6
* 2011-02-27 JPP - Moved most of the logic to DataSourceAdapter (where it
* can be used by FastDataListView too)
* v2.3
* 2009-01-18 JPP - Boolean columns are now handled as checkboxes
* - Auto-generated columns would fail if the data source was
* reseated, even to the same data source
* v2.0.1
* 2009-01-07 JPP - Made all public and protected methods virtual
* 2008-10-03 JPP - Separated from ObjectListView.cs
*
* Copyright (C) 2006-2015 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@gmail.com.
*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing.Design;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A DataListView is a ListView that can be bound to a datasource (which would normally be a DataTable or DataView).
/// </summary>
/// <remarks>
/// <para>This listview keeps itself in sync with its source datatable by listening for change events.</para>
/// <para>The DataListView will automatically create columns to show all of the data source's columns/properties, if there is not already
/// a column showing that property. This allows you to define one or two columns in the designer and then have the others generated automatically.
/// If you don't want any column to be auto generated, set <see cref="AutoGenerateColumns"/> to false.
/// These generated columns will be only the simplest view of the world, and would look more interesting with a few delegates installed.</para>
/// <para>This listview will also automatically generate missing aspect getters to fetch the values from the data view.</para>
/// <para>Changing data sources is possible, but error prone. Before changing data sources, the programmer is responsible for modifying/resetting
/// the column collection to be valid for the new data source.</para>
/// <para>Internally, a CurrencyManager controls keeping the data source in-sync with other users of the data source (as per normal .NET
/// behavior). This means that the model objects in the DataListView are DataRowView objects. If you write your own AspectGetters/Setters,
/// they will be given DataRowView objects.</para>
/// </remarks>
public class DataListView : ObjectListView
{
#region Life and death
/// <summary>
/// Make a DataListView
/// </summary>
public DataListView()
{
this.Adapter = new DataSourceAdapter(this);
}
/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing) {
this.Adapter.Dispose();
base.Dispose(disposing);
}
#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>
[Category("Data"),
Description("Should the control automatically generate columns from the DataSource"),
DefaultValue(true)]
public bool AutoGenerateColumns {
get { return this.Adapter.AutoGenerateColumns; }
set { this.Adapter.AutoGenerateColumns = value; }
}
/// <summary>
/// Get or set the DataSource that will be displayed in this list view.
/// </summary>
/// <remarks>The DataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
/// <list type="unordered">
/// <item><description><see cref="DataView"/></description></item>
/// <item><description><see cref="DataTable"/></description></item>
/// <item><description><see cref="DataSet"/></description></item>
/// <item><description><see cref="DataViewManager"/></description></item>
/// <item><description><see cref="BindingSource"/></description></item>
/// </list>
/// <para>When binding to a list container (i.e. one that implements the
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
/// you must also set the <see cref="DataMember"/> property in order
/// to identify which particular list you would like to display. You
/// may also set the <see cref="DataMember"/> property even when
/// DataSource refers to a list, since <see cref="DataMember"/> can
/// also be used to navigate relations between lists.</para>
/// <para>When a DataSource is set, the control will create OLVColumns to show any
/// data source columns that are not already shown.</para>
/// <para>If the DataSource is changed, you will have to remove any previously
/// created columns, since they will be configured for the previous DataSource.
/// <see cref="ObjectListView.Reset()"/>.</para>
/// </remarks>
[Category("Data"),
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
public virtual Object DataSource
{
get { return this.Adapter.DataSource; }
set { this.Adapter.DataSource = value; }
}
/// <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>
[Category("Data"),
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
DefaultValue("")]
public virtual string DataMember
{
get { return this.Adapter.DataMember; }
set { this.Adapter.DataMember = value; }
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
/// for data binding.
/// </summary>
/// <remarks>
/// Adaptors cannot be shared between controls. Each DataListView needs its own adapter.
/// </remarks>
protected DataSourceAdapter Adapter {
get {
Debug.Assert(adapter != null, "Data adapter should not be null");
return adapter;
}
set { adapter = value; }
}
private DataSourceAdapter adapter;
#endregion
#region Object manipulations
/// <summary>
/// Add the given collection of model objects to this control.
/// </summary>
/// <param name="modelObjects">A collection of model objects</param>
/// <remarks>This is a no-op for data lists, since the data
/// is controlled by the DataSource. Manipulate the data source
/// rather than this view of the data source.</remarks>
public override void AddObjects(ICollection modelObjects)
{
}
/// <summary>
/// Insert the given collection of objects before the given position
/// </summary>
/// <param name="index">Where to insert the objects</param>
/// <param name="modelObjects">The objects to be inserted</param>
/// <remarks>This is a no-op for data lists, since the data
/// is controlled by the DataSource. Manipulate the data source
/// rather than this view of the data source.</remarks>
public override void InsertObjects(int index, ICollection modelObjects) {
}
/// <summary>
/// Remove the given collection of model objects from this control.
/// </summary>
/// <remarks>This is a no-op for data lists, since the data
/// is controlled by the DataSource. Manipulate the data source
/// rather than this view of the data source.</remarks>
public override void RemoveObjects(ICollection modelObjects)
{
}
#endregion
#region Event Handlers
/// <summary>
/// Change the Unfreeze behaviour
/// </summary>
protected override void DoUnfreeze() {
// Copied from base method, but we don't need to BuildList() since we know that our
// data adaptor is going to do that immediately after this method exits.
this.EndUpdate();
this.ResizeFreeSpaceFillingColumns();
// this.BuildList();
}
/// <summary>
/// Handles parent binding context changes
/// </summary>
/// <param name="e">Unused EventArgs.</param>
protected override void OnParentBindingContextChanged(EventArgs e)
{
base.OnParentBindingContextChanged(e);
// BindingContext is an ambient property - by default it simply picks
// up the parent control's context (unless something has explicitly
// given us our own). So we must respond to changes in our parent's
// binding context in the same way we would changes to our own
// binding context.
// THINK: Do we need to forward this to the adapter?
}
#endregion
}
}

View File

@ -0,0 +1,240 @@
/*
* DataTreeListView - A data bindable TreeListView
*
* Author: Phillip Piper
* Date: 05/05/2012 3:26 PM
*
* Change log:
* 2012-05-05 JPP Initial version
*
* TO DO:
*
* Copyright (C) 2012 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing.Design;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A DataTreeListView is a TreeListView that calculates its hierarchy based on
/// information in the data source.
/// </summary>
/// <remarks>
/// <para>Like a <see cref="DataListView"/>, a DataTreeListView sources all its information
/// from a combination of <see cref="DataSource"/> and <see cref="DataMember"/>.
/// <see cref="DataSource"/> can be a DataTable, DataSet,
/// or anything that implements <see cref="IList"/>.
/// </para>
/// <para>
/// To function properly, the DataTreeListView requires:
/// <list type="bullet">
/// <item>the table to have a column which holds a unique for the row. The name of this column must be set in <see cref="KeyAspectName"/>.</item>
/// <item>the table to have a column which holds id of the hierarchical parent of the row. The name of this column must be set in <see cref="ParentKeyAspectName"/>.</item>
/// <item>a value which identifies which rows are the roots of the tree (<see cref="RootKeyValue"/>).</item>
/// </list>
/// The hierarchy structure is determined finding all the rows where the parent key is equal to <see cref="RootKeyValue"/>. These rows
/// become the root objects of the hierarchy.
/// </para>
/// <para>Like a TreeListView, the hierarchy must not contain cycles. Bad things will happen if the data is cyclic.</para>
/// </remarks>
public partial class DataTreeListView : TreeListView
{
#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>
[Category("数据"),
Description("Should the control automatically generate columns from the DataSource"),
DefaultValue(true)]
public bool AutoGenerateColumns
{
get { return this.Adapter.AutoGenerateColumns; }
set { this.Adapter.AutoGenerateColumns = value; }
}
/// <summary>
/// Get or set the DataSource that will be displayed in this list view.
/// </summary>
/// <remarks>The DataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
/// <list type="unordered">
/// <item><description><see cref="DataView"/></description></item>
/// <item><description><see cref="DataTable"/></description></item>
/// <item><description><see cref="DataSet"/></description></item>
/// <item><description><see cref="DataViewManager"/></description></item>
/// <item><description><see cref="BindingSource"/></description></item>
/// </list>
/// <para>When binding to a list container (i.e. one that implements the
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
/// you must also set the <see cref="DataMember"/> property in order
/// to identify which particular list you would like to display. You
/// may also set the <see cref="DataMember"/> property even when
/// DataSource refers to a list, since <see cref="DataMember"/> can
/// also be used to navigate relations between lists.</para>
/// </remarks>
[Category("数据"),
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
public virtual Object DataSource {
get { return this.Adapter.DataSource; }
set { this.Adapter.DataSource = value; }
}
/// <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>
[Category("数据"),
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
DefaultValue("")]
public virtual string DataMember {
get { return this.Adapter.DataMember; }
set { this.Adapter.DataMember = value; }
}
/// <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>
[Category("数据"),
Description("The name of the property/column that holds the key of a row"),
DefaultValue(null)]
public virtual string KeyAspectName {
get { return this.Adapter.KeyAspectName; }
set { this.Adapter.KeyAspectName = value; }
}
/// <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>
[Category("数据"),
Description("The name of the property/column that holds the key of the parent of a row"),
DefaultValue(null)]
public virtual string ParentKeyAspectName {
get { return this.Adapter.ParentKeyAspectName; }
set { this.Adapter.ParentKeyAspectName = value; }
}
/// <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. Actually, it can be any value that can
/// be compared for equality against a basic type.</para>
/// <para>If this is set to the wrong value (i.e. to a value that no row
/// has in the parent id column), the list will be empty.</para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual object RootKeyValue {
get { return this.Adapter.RootKeyValue; }
set { this.Adapter.RootKeyValue = value; }
}
/// <summary>
/// Gets or sets the value that identifies a row as a root object.
/// <see cref="RootKeyValue"/>. The RootKeyValue can be of any type,
/// but the IDE cannot sensibly represent a value of any type,
/// so this is a typed wrapper around that property.
/// </summary>
/// <remarks>
/// If you want the root value to be something other than a string,
/// you will have set it yourself.
/// </remarks>
[Category("数据"),
Description("The parent id value that identifies a row as a root object"),
DefaultValue(null)]
public virtual string RootKeyValueString {
get { return Convert.ToString(this.Adapter.RootKeyValue); }
set { this.Adapter.RootKeyValue = value; }
}
/// <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>
[Category("数据"),
Description("Should the keys columns (id and parent id) be shown to the user?"),
DefaultValue(true)]
public virtual bool ShowKeyColumns {
get { return this.Adapter.ShowKeyColumns; }
set { this.Adapter.ShowKeyColumns = value; }
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
/// for data binding.
/// </summary>
protected TreeDataSourceAdapter Adapter {
get {
if (this.adapter == null)
this.adapter = new TreeDataSourceAdapter(this);
return adapter;
}
set { adapter = value; }
}
private TreeDataSourceAdapter adapter;
#endregion
}
}

View File

@ -0,0 +1,219 @@
/*
* DragSource.cs - Add drag source functionality to an ObjectListView
*
* Author: Phillip Piper
* Date: 2009-03-17 5:15 PM
*
* Change log:
* 2011-03-29 JPP - Separate OLVDataObject.cs
* v2.3
* 2009-07-06 JPP - Make sure Link is acceptable as an drop effect by default
* (since MS didn't make it part of the 'All' value)
* v2.2
* 2009-04-15 JPP - Separated DragSource.cs into DropSink.cs
* 2009-03-17 JPP - Initial version
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.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. The data
/// object will normally include all the selected objects.
/// </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) {
return new OLVDataObject(olv);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,185 @@
/*
* OLVDataObject.cs - An OLE DataObject that knows how to convert rows of an OLV to text and HTML
*
* Author: Phillip Piper
* Date: 2011-03-29 3:34PM
*
* Change log:
* v2.8
* 2014-05-02 JPP - When the listview is completely empty, don't try to set CSV text in the clipboard.
* v2.6
* 2012-08-08 JPP - Changed to use OLVExporter.
* - Added CSV to formats exported to Clipboard
* v2.4
* 2011-03-29 JPP - Initial version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Windows.Forms;
namespace BrightIdeasSoftware {
/// <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;
this.CreateTextFormats();
}
#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 readonly 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 readonly bool includeColumnHeaders;
/// <summary>
/// Gets the ObjectListView that is being used as the source of the data
/// </summary>
public ObjectListView ListView {
get { return objectListView; }
}
private readonly ObjectListView objectListView;
/// <summary>
/// Gets the model objects that are to be placed in the data object
/// </summary>
public IList ModelObjects {
get { return modelObjects; }
}
private readonly IList modelObjects;
#endregion
/// <summary>
/// Put a text and HTML representation of our model objects
/// into the data object.
/// </summary>
public void CreateTextFormats() {
OLVExporter exporter = this.CreateExporter();
// Put both the text and html versions onto the clipboard.
// For some reason, SetText() with UnicodeText doesn't set the basic CF_TEXT format,
// but using SetData() does.
//this.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
this.SetData(exporter.ExportTo(OLVExporter.ExportFormat.TabSeparated));
string exportTo = exporter.ExportTo(OLVExporter.ExportFormat.CSV);
if (!String.IsNullOrEmpty(exportTo))
this.SetText(exportTo, TextDataFormat.CommaSeparatedValue);
this.SetText(ConvertToHtmlFragment(exporter.ExportTo(OLVExporter.ExportFormat.HTML)), TextDataFormat.Html);
}
/// <summary>
/// Create an exporter for the data contained in this object
/// </summary>
/// <returns></returns>
protected OLVExporter CreateExporter() {
OLVExporter exporter = new OLVExporter(this.ListView);
exporter.IncludeColumnHeaders = this.IncludeColumnHeaders;
exporter.IncludeHiddenColumns = this.IncludeHiddenColumns;
exporter.ModelObjects = this.ModelObjects;
return exporter;
}
/// <summary>
/// Make a HTML representation of our model objects
/// </summary>
[Obsolete("Use OLVExporter directly instead", false)]
public string CreateHtml() {
OLVExporter exporter = this.CreateExporter();
return exporter.ExportTo(OLVExporter.ExportFormat.HTML);
}
/// <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
const string SOURCE = "http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView";
const String MARKER_BLOCK =
"Version:1.0\r\n" +
"StartHTML:{0,8}\r\n" +
"EndHTML:{1,8}\r\n" +
"StartFragment:{2,8}\r\n" +
"EndFragment:{3,8}\r\n" +
"StartSelection:{2,8}\r\n" +
"EndSelection:{3,8}\r\n" +
"SourceURL:{4}\r\n" +
"{5}";
int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, SOURCE, "").Length;
const String DEFAULT_HTML_BODY =
"<!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, StringComparison.Ordinal);
int endFragment = startFragment + fragment.Length;
return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, SOURCE, html);
}
}
}

View File

@ -0,0 +1,169 @@
/*
* FastDataListView - A data bindable listview that has the speed of a virtual list
*
* Author: Phillip Piper
* Date: 22/09/2010 8:11 AM
*
* Change log:
* 2015-02-02 JPP - Made Unfreezing more efficient by removing a redundant BuildList() call
* v2.6
* 2010-09-22 JPP - Initial version
*
* Copyright (C) 2006-2015 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@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing.Design;
namespace BrightIdeasSoftware
{
/// <summary>
/// A FastDataListView virtualizes the display of data from a DataSource. It operates on
/// DataSets and DataTables in the same way as a DataListView, but does so much more efficiently.
/// </summary>
/// <remarks>
/// <para>
/// A FastDataListView still has to load all its data from the DataSource. If you have SQL statement
/// that returns 1 million rows, all 1 million rows will still need to read from the database.
/// However, once the rows are loaded, the FastDataListView will only build rows as they are displayed.
/// </para>
/// </remarks>
public class FastDataListView : FastObjectListView
{
/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if (this.adapter != null) {
this.adapter.Dispose();
this.adapter = null;
}
base.Dispose(disposing);
}
#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>
[Category("数据"),
Description("Should the control automatically generate columns from the DataSource"),
DefaultValue(true)]
public bool AutoGenerateColumns
{
get { return this.Adapter.AutoGenerateColumns; }
set { this.Adapter.AutoGenerateColumns = value; }
}
/// <summary>
/// Get or set the VirtualListDataSource that will be displayed in this list view.
/// </summary>
/// <remarks>The VirtualListDataSource should implement either <see cref="IList"/>, <see cref="IBindingList"/>,
/// or <see cref="IListSource"/>. Some common examples are the following types of objects:
/// <list type="unordered">
/// <item><description><see cref="DataView"/></description></item>
/// <item><description><see cref="DataTable"/></description></item>
/// <item><description><see cref="DataSet"/></description></item>
/// <item><description><see cref="DataViewManager"/></description></item>
/// <item><description><see cref="BindingSource"/></description></item>
/// </list>
/// <para>When binding to a list container (i.e. one that implements the
/// <see cref="IListSource"/> interface, such as <see cref="DataSet"/>)
/// you must also set the <see cref="DataMember"/> property in order
/// to identify which particular list you would like to display. You
/// may also set the <see cref="DataMember"/> property even when
/// VirtualListDataSource refers to a list, since <see cref="DataMember"/> can
/// also be used to navigate relations between lists.</para>
/// </remarks>
[Category("数据"),
TypeConverter("System.Windows.Forms.Design.DataSourceConverter, System.Design")]
public virtual Object DataSource {
get { return this.Adapter.DataSource; }
set { this.Adapter.DataSource = value; }
}
/// <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>
[Category("数据"),
Editor("System.Windows.Forms.Design.DataMemberListEditor, System.Design", typeof(UITypeEditor)),
DefaultValue("")]
public virtual string DataMember {
get { return this.Adapter.DataMember; }
set { this.Adapter.DataMember = value; }
}
#endregion
#region Implementation properties
/// <summary>
/// Gets or sets the DataSourceAdaptor that does the bulk of the work needed
/// for data binding.
/// </summary>
protected DataSourceAdapter Adapter {
get {
if (adapter == null)
adapter = this.CreateDataSourceAdapter();
return adapter;
}
set { adapter = value; }
}
private DataSourceAdapter adapter;
#endregion
#region Implementation
/// <summary>
/// Create the DataSourceAdapter that this control will use.
/// </summary>
/// <returns>A DataSourceAdapter configured for this list</returns>
/// <remarks>Subclasses should override this to create their
/// own specialized adapters</remarks>
protected virtual DataSourceAdapter CreateDataSourceAdapter() {
return new DataSourceAdapter(this);
}
/// <summary>
/// Change the Unfreeze behaviour
/// </summary>
protected override void DoUnfreeze()
{
// Copied from base method, but we don't need to BuildList() since we know that our
// data adaptor is going to do that immediately after this method exits.
this.EndUpdate();
this.ResizeFreeSpaceFillingColumns();
// this.BuildList();
}
#endregion
}
}

View File

@ -0,0 +1,422 @@
/*
* FastObjectListView - A listview that behaves like an ObjectListView but has the speed of a virtual list
*
* Author: Phillip Piper
* Date: 27/09/2008 9:15 AM
*
* Change log:
* 2014-10-15 JPP - Fire Filter event when applying filters
* v2.8
* 2012-06-11 JPP - Added more efficient version of FilteredObjects
* v2.5.1
* 2011-04-25 JPP - Fixed problem with removing objects from filtered or sorted list
* v2.4
* 2010-04-05 JPP - Added filtering
* v2.3
* 2009-08-27 JPP - Added GroupingStrategy
* - Added optimized Objects property
* v2.2.1
* 2009-01-07 JPP - Made all public and protected methods virtual
* 2008-09-27 JPP - Separated from ObjectListView.cs
*
* Copyright (C) 2006-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
namespace BrightIdeasSoftware
{
/// <summary>
/// A FastObjectListView trades function for speed.
/// </summary>
/// <remarks>
/// <para>On my mid-range laptop, this view builds a list of 10,000 objects in 0.1 seconds,
/// as opposed to a normal ObjectListView which takes 10-15 seconds. Lists of up to 50,000 items should be
/// able to be handled with sub-second response times even on low end machines.</para>
/// <para>
/// A FastObjectListView is implemented as a virtual list with many of the virtual modes limits (e.g. no sorting)
/// fixed through coding. There are some functions that simply cannot be provided. Specifically, a FastObjectListView cannot:
/// <list type="bullet">
/// <item><description>use Tile view</description></item>
/// <item><description>show groups on XP</description></item>
/// </list>
/// </para>
/// </remarks>
public class FastObjectListView : VirtualObjectListView
{
/// <summary>
/// Make a FastObjectListView
/// </summary>
public FastObjectListView() {
this.VirtualListDataSource = new FastObjectListDataSource(this);
this.GroupingStrategy = new FastListGroupingStrategy();
}
/// <summary>
/// Gets the collection of objects that survive any filtering that may be in place.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override IEnumerable FilteredObjects {
get {
// This is much faster than the base method
return ((FastObjectListDataSource)this.VirtualListDataSource).FilteredObjectList;
}
}
/// <summary>
/// Get/set the collection of objects that this list will show
/// </summary>
/// <remarks>
/// <para>
/// The contents of the control will be updated immediately after setting this property.
/// </para>
/// <para>This method preserves selection, if possible. Use SetObjects() if
/// you do not want to preserve the selection. Preserving selection is the slowest part of this
/// code and performance is O(n) where n is the number of selected rows.</para>
/// <para>This method is not thread safe.</para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override IEnumerable Objects {
get {
// This is much faster than the base method
return ((FastObjectListDataSource)this.VirtualListDataSource).ObjectList;
}
set { base.Objects = value; }
}
/// <summary>
/// Move the given collection of objects to the given index.
/// </summary>
/// <remarks>This operation only makes sense on non-grouped ObjectListViews.</remarks>
/// <param name="index"></param>
/// <param name="modelObjects"></param>
public override void MoveObjects(int index, ICollection modelObjects) {
if (this.InvokeRequired) {
this.Invoke((MethodInvoker)delegate() { this.MoveObjects(index, modelObjects); });
return;
}
// If any object that is going to be moved is before the point where the insertion
// will occur, then we have to reduce the location of our insertion point
int displacedObjectCount = 0;
foreach (object modelObject in modelObjects) {
int i = this.IndexOf(modelObject);
if (i >= 0 && i <= index)
displacedObjectCount++;
}
index -= displacedObjectCount;
this.BeginUpdate();
try {
this.RemoveObjects(modelObjects);
this.InsertObjects(index, modelObjects);
}
finally {
this.EndUpdate();
}
}
/// <summary>
///删除任何排序并恢复到模型对象的给定顺序
/// </summary>
/// <remarks>To be really honest, Unsort() doesn't work on FastObjectListViews since
/// the original ordering of model objects is lost when Sort() is called. So this method
/// effectively just turns off sorting.</remarks>
public override void Unsort() {
this.ShowGroups = false;
this.PrimarySortColumn = null;
this.PrimarySortOrder = SortOrder.None;
this.SetObjects(this.Objects);
}
}
/// <summary>
/// 为FastObjectListView提供数据源
/// </summary>
/// <remarks>
/// This class isn't intended to be used directly, but it is left as a public
/// class just in case someone wants to subclass it.
/// </remarks>
public class FastObjectListDataSource : AbstractVirtualListDataSource
{
/// <summary>
/// Create a FastObjectListDataSource
/// </summary>
/// <param name="listView"></param>
public FastObjectListDataSource(FastObjectListView listView)
: base(listView) {
}
#region IVirtualListDataSource Members
/// <summary>
/// Get n'th object
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
public override object GetNthObject(int n) {
if (n >= 0 && n < this.filteredObjectList.Count)
return this.filteredObjectList[n];
return null;
}
/// <summary>
/// How many items are in the data source
/// </summary>
/// <returns></returns>
public override int GetObjectCount() {
return this.filteredObjectList.Count;
}
/// <summary>
/// Get the index of the given model
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override int GetObjectIndex(object model) {
int index;
if (model != null && this.objectsToIndexMap.TryGetValue(model, out index))
return index;
return -1;
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="first"></param>
/// <param name="last"></param>
/// <param name="column"></param>
/// <returns></returns>
public override int SearchText(string text, int first, int last, OLVColumn column) {
if (first <= last) {
for (int i = first; i <= last; i++) {
string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
return i;
}
} else {
for (int i = first; i >= last; i--) {
string data = column.GetStringValue(this.listView.GetNthItemInDisplayOrder(i).RowObject);
if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
return i;
}
}
return -1;
}
/// <summary>
///
/// </summary>
/// <param name="column"></param>
/// <param name="sortOrder"></param>
public override void Sort(OLVColumn column, SortOrder sortOrder) {
if (sortOrder != SortOrder.None) {
ModelObjectComparer comparer = new ModelObjectComparer(column, sortOrder, this.listView.SecondarySortColumn, this.listView.SecondarySortOrder);
this.fullObjectList.Sort(comparer);
this.filteredObjectList.Sort(comparer);
}
this.RebuildIndexMap();
}
/// <summary>
///
/// </summary>
/// <param name="modelObjects"></param>
public override void AddObjects(ICollection modelObjects) {
foreach (object modelObject in modelObjects) {
if (modelObject != null)
this.fullObjectList.Add(modelObject);
}
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="modelObjects"></param>
public override void InsertObjects(int index, ICollection modelObjects) {
this.fullObjectList.InsertRange(index, modelObjects);
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
/// Remove the given collection of models from this source.
/// </summary>
/// <param name="modelObjects"></param>
public override void RemoveObjects(ICollection modelObjects) {
// We have to unselect any object that is about to be deleted
List<int> indicesToRemove = new List<int>();
foreach (object modelObject in modelObjects) {
int i = this.GetObjectIndex(modelObject);
if (i >= 0)
indicesToRemove.Add(i);
}
// Sort the indices from highest to lowest so that we
// remove latter ones before earlier ones. In this way, the
// indices of the rows doesn't change after the deletes.
indicesToRemove.Sort();
indicesToRemove.Reverse();
foreach (int i in indicesToRemove)
this.listView.SelectedIndices.Remove(i);
// Remove the objects from the unfiltered list
foreach (object modelObject in modelObjects)
this.fullObjectList.Remove(modelObject);
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
///
/// </summary>
/// <param name="collection"></param>
public override void SetObjects(IEnumerable collection) {
ArrayList newObjects = ObjectListView.EnumerableToArray(collection, true);
this.fullObjectList = newObjects;
this.FilterObjects();
this.RebuildIndexMap();
}
/// <summary>
/// Update/replace the nth object with the given object
/// </summary>
/// <param name="index"></param>
/// <param name="modelObject"></param>
public override void UpdateObject(int index, object modelObject) {
if (index < 0 || index >= this.filteredObjectList.Count)
return;
int i = this.fullObjectList.IndexOf(this.filteredObjectList[index]);
if (i < 0)
return;
if (ReferenceEquals(this.fullObjectList[i], modelObject))
return;
this.fullObjectList[i] = modelObject;
this.filteredObjectList[index] = modelObject;
this.objectsToIndexMap[modelObject] = index;
}
private ArrayList fullObjectList = new ArrayList();
private ArrayList filteredObjectList = new ArrayList();
private IModelFilter modelFilter;
private IListFilter listFilter;
#endregion
#region IFilterableDataSource Members
/// <summary>
/// Apply the given filters to this data source. One or both may be null.
/// </summary>
/// <param name="iModelFilter"></param>
/// <param name="iListFilter"></param>
public override void ApplyFilters(IModelFilter iModelFilter, IListFilter iListFilter) {
this.modelFilter = iModelFilter;
this.listFilter = iListFilter;
this.SetObjects(this.fullObjectList);
}
#endregion
#region Implementation
/// <summary>
/// Gets the full list of objects being used for this fast list.
/// This list is unfiltered.
/// </summary>
public ArrayList ObjectList {
get { return fullObjectList; }
}
/// <summary>
/// Gets the list of objects from ObjectList which survive any installed filters.
/// </summary>
public ArrayList FilteredObjectList {
get { return filteredObjectList; }
}
/// <summary>
/// Rebuild the map that remembers which model object is displayed at which line
/// </summary>
protected void RebuildIndexMap() {
this.objectsToIndexMap.Clear();
for (int i = 0; i < this.filteredObjectList.Count; i++)
this.objectsToIndexMap[this.filteredObjectList[i]] = i;
}
readonly Dictionary<Object, int> objectsToIndexMap = new Dictionary<Object, int>();
/// <summary>
/// Build our filtered list from our full list.
/// </summary>
protected void FilterObjects() {
// If this list isn't filtered, we don't need to do anything else
if (!this.listView.UseFiltering) {
this.filteredObjectList = new ArrayList(this.fullObjectList);
return;
}
// Tell the world to filter the objects. If they do so, don't do anything else
// ReSharper disable PossibleMultipleEnumeration
FilterEventArgs args = new FilterEventArgs(this.fullObjectList);
this.listView.OnFilter(args);
if (args.FilteredObjects != null) {
this.filteredObjectList = ObjectListView.EnumerableToArray(args.FilteredObjects, false);
return;
}
IEnumerable objects = (this.listFilter == null) ?
this.fullObjectList : this.listFilter.Filter(this.fullObjectList);
// Apply the object filter if there is one
if (this.modelFilter == null) {
this.filteredObjectList = ObjectListView.EnumerableToArray(objects, false);
} else {
this.filteredObjectList = new ArrayList();
foreach (object model in objects) {
if (this.modelFilter.Filter(model))
this.filteredObjectList.Add(model);
}
}
}
#endregion
}
}

View File

@ -0,0 +1,125 @@
/*
* Cluster - Implements a simple cluster
*
* Author: Phillip Piper
* Date: 3-March-2011 10:53 pm
*
* Change log:
* 2011-03-03 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// Concrete implementation of the ICluster interface.
/// </summary>
public class Cluster : ICluster {
#region Life and death
/// <summary>
/// Create a cluster
/// </summary>
/// <param name="key">The key for the cluster</param>
public Cluster(object key) {
this.Count = 1;
this.ClusterKey = key;
}
#endregion
#region Public overrides
/// <summary>
/// Return a string representation of this cluster
/// </summary>
/// <returns></returns>
public override string ToString() {
return this.DisplayLabel ?? "[empty]";
}
#endregion
#region Implementation of ICluster
/// <summary>
/// Gets or sets how many items belong to this cluster
/// </summary>
public int Count {
get { return count; }
set { count = value; }
}
private int count;
/// <summary>
/// Gets or sets the label that will be shown to the user to represent
/// this cluster
/// </summary>
public string DisplayLabel {
get { return displayLabel; }
set { displayLabel = value; }
}
private string displayLabel;
/// <summary>
/// Gets or sets the actual data object that all members of this cluster
/// have commonly returned.
/// </summary>
public object ClusterKey {
get { return clusterKey; }
set { clusterKey = value; }
}
private object clusterKey;
#endregion
#region Implementation of IComparable
/// <summary>
/// Return an indication of the ordering between this object and the given one
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public int CompareTo(object other) {
if (other == null || other == System.DBNull.Value)
return 1;
ICluster otherCluster = other as ICluster;
if (otherCluster == null)
return 1;
string keyAsString = this.ClusterKey as string;
if (keyAsString != null)
return String.Compare(keyAsString, otherCluster.ClusterKey as string, StringComparison.CurrentCultureIgnoreCase);
IComparable keyAsComparable = this.ClusterKey as IComparable;
if (keyAsComparable != null)
return keyAsComparable.CompareTo(otherCluster.ClusterKey);
return -1;
}
#endregion
}
}

View File

@ -0,0 +1,189 @@
/*
* ClusteringStrategy - Implements a simple clustering strategy
*
* Author: Phillip Piper
* Date: 3-March-2011 10:53 pm
*
* Change log:
* 2011-03-03 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// This class provides a useful base implemention of a clustering
/// strategy where the clusters are grouped around the value of a given column.
/// </summary>
public class ClusteringStrategy : IClusteringStrategy {
#region Static properties
/// <summary>
/// This field is the text that will be shown to the user when a cluster
/// key is null. It is exposed so it can be localized.
/// </summary>
static public string NULL_LABEL = "[null]";
/// <summary>
/// This field is the text that will be shown to the user when a cluster
/// key is empty (i.e. a string of zero length). It is exposed so it can be localized.
/// </summary>
static public string EMPTY_LABEL = "[empty]";
/// <summary>
/// Gets or sets the format that will be used by default for clusters that only
/// contain 1 item. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster (always 1 in this case)
/// </summary>
static public string DefaultDisplayLabelFormatSingular {
get { return defaultDisplayLabelFormatSingular; }
set { defaultDisplayLabelFormatSingular = value; }
}
static private string defaultDisplayLabelFormatSingular = "{0} ({1} 项)";
/// <summary>
/// Gets or sets the format that will be used by default for clusters that
/// contain 0 or two or more items. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster
/// </summary>
static public string DefaultDisplayLabelFormatPlural {
get { return defaultDisplayLabelFormatPural; }
set { defaultDisplayLabelFormatPural = value; }
}
static private string defaultDisplayLabelFormatPural = "{0} ({1} 项)";
#endregion
#region Life and death
/// <summary>
/// Create a clustering strategy
/// </summary>
public ClusteringStrategy() {
this.DisplayLabelFormatSingular = DefaultDisplayLabelFormatSingular;
this.DisplayLabelFormatPlural = DefaultDisplayLabelFormatPlural;
}
#endregion
#region Public properties
/// <summary>
/// Gets or sets the column upon which this strategy is operating
/// </summary>
public OLVColumn Column {
get { return column; }
set { column = value; }
}
private OLVColumn column;
/// <summary>
/// Gets or sets the format that will be used when the cluster
/// contains only 1 item. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster (always 1 in this case)
/// </summary>
/// <remarks>If this is not set, the value from
/// ClusteringStrategy.DefaultDisplayLabelFormatSingular will be used</remarks>
public string DisplayLabelFormatSingular {
get { return displayLabelFormatSingular; }
set { displayLabelFormatSingular = value; }
}
private string displayLabelFormatSingular;
/// <summary>
/// Gets or sets the format that will be used when the cluster
/// contains 0 or two or more items. The format string must accept two placeholders:
/// - {0} is the cluster key converted to a string
/// - {1} is the number of items in the cluster
/// </summary>
/// <remarks>If this is not set, the value from
/// ClusteringStrategy.DefaultDisplayLabelFormatPlural will be used</remarks>
public string DisplayLabelFormatPlural {
get { return displayLabelFormatPural; }
set { displayLabelFormatPural = value; }
}
private string displayLabelFormatPural;
#endregion
#region ICluster implementation
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
virtual public object GetClusterKey(object model) {
return this.Column.GetValue(model);
}
/// <summary>
/// Create a cluster to hold the given cluster key
/// </summary>
/// <param name="clusterKey"></param>
/// <returns></returns>
virtual public ICluster CreateCluster(object clusterKey) {
return new Cluster(clusterKey);
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
virtual public string GetClusterDisplayLabel(ICluster cluster) {
string s = this.Column.ValueToString(cluster.ClusterKey) ?? NULL_LABEL;
if (String.IsNullOrEmpty(s))
s = EMPTY_LABEL;
return this.ApplyDisplayFormat(cluster, s);
}
/// <summary>
/// Create a filter that will include only model objects that
/// match one or more of the given values.
/// </summary>
/// <param name="valuesChosenForFiltering"></param>
/// <returns></returns>
virtual public IModelFilter CreateFilter(IList valuesChosenForFiltering) {
return new OneOfFilter(this.GetClusterKey, valuesChosenForFiltering);
}
/// <summary>
/// Create a label that combines the string representation of the cluster
/// key with a format string that holds an "X [N items in cluster]" type layout.
/// </summary>
/// <param name="cluster"></param>
/// <param name="s"></param>
/// <returns></returns>
virtual protected string ApplyDisplayFormat(ICluster cluster, string s) {
string format = (cluster.Count == 1) ? this.DisplayLabelFormatSingular : this.DisplayLabelFormatPlural;
return String.IsNullOrEmpty(format) ? s : String.Format(format, s, cluster.Count);
}
#endregion
}
}

View File

@ -0,0 +1,70 @@
/*
* ClusteringStrategy - Implements a simple clustering strategy
*
* Author: Phillip Piper
* Date: 1-April-2011 8:12am
*
* Change log:
* 2011-04-01 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// This class calculates clusters from the groups that the column uses.
/// </summary>
/// <remarks>
/// <para>
/// This is the default strategy for all non-date, filterable columns.
/// </para>
/// <para>
/// This class does not strictly mimic the groups created by the given column.
/// In particular, if the programmer changes the default grouping technique
/// by listening for grouping events, this class will not mimic that behaviour.
/// </para>
/// </remarks>
public class ClustersFromGroupsStrategy : ClusteringStrategy {
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override object GetClusterKey(object model) {
return this.Column.GetGroupKey(model);
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
public override string GetClusterDisplayLabel(ICluster cluster) {
string s = this.Column.ConvertGroupKeyToTitle(cluster.ClusterKey);
if (String.IsNullOrEmpty(s))
s = EMPTY_LABEL;
return this.ApplyDisplayFormat(cluster, s);
}
}
}

View File

@ -0,0 +1,187 @@
/*
* DateTimeClusteringStrategy - A strategy to cluster objects by a date time
*
* Author: Phillip Piper
* Date: 30-March-2011 9:40am
*
* Change log:
* 2011-03-30 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Globalization;
namespace BrightIdeasSoftware {
/// <summary>
/// This enum is used to indicate various portions of a datetime
/// </summary>
[Flags]
public enum DateTimePortion {
/// <summary>
/// Year
/// </summary>
Year = 0x01,
/// <summary>
/// Month
/// </summary>
Month = 0x02,
/// <summary>
/// Day of the month
/// </summary>
Day = 0x04,
/// <summary>
/// Hour
/// </summary>
Hour = 0x08,
/// <summary>
/// Minute
/// </summary>
Minute = 0x10,
/// <summary>
/// Second
/// </summary>
Second = 0x20
}
/// <summary>
/// This class implements a strategy where the model objects are clustered
/// according to some portion of the datetime value in the configured column.
/// </summary>
/// <remarks>To create a strategy that grouped people who were born in
/// the same month, you would create a strategy that extracted just
/// the month, and formatted it to show just the month's name. Like this:
/// </remarks>
/// <example>
/// someColumn.ClusteringStrategy = new DateTimeClusteringStrategy(DateTimePortion.Month, "MMMM");
/// </example>
public class DateTimeClusteringStrategy : ClusteringStrategy {
#region Life and death
/// <summary>
/// Create a strategy that clusters by month/year
/// </summary>
public DateTimeClusteringStrategy()
: this(DateTimePortion.Year | DateTimePortion.Month, "MMMM yyyy") {
}
/// <summary>
/// Create a strategy that clusters around the given parts
/// </summary>
/// <param name="portions"></param>
/// <param name="format"></param>
public DateTimeClusteringStrategy(DateTimePortion portions, string format) {
this.Portions = portions;
this.Format = format;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the format string will will be used to create a user-presentable
/// version of the cluster key.
/// </summary>
/// <remarks>The format should use the date/time format strings, as documented
/// in the Windows SDK. Both standard formats and custom format will work.</remarks>
/// <example>"D" - long date pattern</example>
/// <example>"MMMM, yyyy" - "January, 1999"</example>
public string Format {
get { return format; }
set { format = value; }
}
private string format;
/// <summary>
/// Gets or sets the parts of the DateTime that will be extracted when
/// determining the clustering key for an object.
/// </summary>
public DateTimePortion Portions {
get { return portions; }
set { portions = value; }
}
private DateTimePortion portions = DateTimePortion.Year | DateTimePortion.Month;
#endregion
#region IClusterStrategy implementation
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override object GetClusterKey(object model) {
// Get the data attribute we want from the given model
// Make sure the returned value is a DateTime
DateTime? dateTime = this.Column.GetValue(model) as DateTime?;
if (!dateTime.HasValue)
return null;
// Extract the parts of the datetime that we are intereted in.
// Even if we aren't interested in a particular portion, we still have to give it a reasonable default
// otherwise we won't be able to build a DateTime object for it
int year = ((this.Portions & DateTimePortion.Year) == DateTimePortion.Year) ? dateTime.Value.Year : 1;
int month = ((this.Portions & DateTimePortion.Month) == DateTimePortion.Month) ? dateTime.Value.Month : 1;
int day = ((this.Portions & DateTimePortion.Day) == DateTimePortion.Day) ? dateTime.Value.Day : 1;
int hour = ((this.Portions & DateTimePortion.Hour) == DateTimePortion.Hour) ? dateTime.Value.Hour : 0;
int minute = ((this.Portions & DateTimePortion.Minute) == DateTimePortion.Minute) ? dateTime.Value.Minute : 0;
int second = ((this.Portions & DateTimePortion.Second) == DateTimePortion.Second) ? dateTime.Value.Second : 0;
return new DateTime(year, month, day, hour, minute, second);
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
public override string GetClusterDisplayLabel(ICluster cluster) {
DateTime? dateTime = cluster.ClusterKey as DateTime?;
return this.ApplyDisplayFormat(cluster, dateTime.HasValue ? this.DateToString(dateTime.Value) : NULL_LABEL);
}
/// <summary>
/// Convert the given date into a user presentable string
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
protected virtual string DateToString(DateTime dateTime) {
if (String.IsNullOrEmpty(this.Format))
return dateTime.ToString(CultureInfo.CurrentUICulture);
try {
return dateTime.ToString(this.Format);
}
catch (FormatException) {
return String.Format("Bad format string '{0}' for value '{1}'", this.Format, dateTime);
}
}
#endregion
}
}

View File

@ -0,0 +1,369 @@
/*
* FilterMenuBuilder - Responsible for creating a Filter menu
*
* Author: Phillip Piper
* Date: 4-March-2011 11:59 pm
*
* Change log:
* 2012-05-20 JPP - Allow the same model object to be in multiple clusters
* Useful for xor'ed flag fields, and multi-value strings
* (e.g. hobbies that are stored as comma separated values).
* v2.5.1
* 2012-04-14 JPP - Fixed rare bug with clustering an empty list (SF #3445118)
* v2.5
* 2011-04-12 JPP - Added some images to menu
* 2011-03-04 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Drawing;
namespace BrightIdeasSoftware {
/// <summary>
/// Instances of this class know how to build a Filter menu.
/// It is responsible for clustering the values in the target column,
/// build a menu that shows those clusters, and then constructing
/// a filter that will enact the users choices.
/// </summary>
/// <remarks>
/// Almost all of the methods in this class are declared as "virtual protected"
/// so that subclasses can provide alternative behaviours.
/// </remarks>
public class FilterMenuBuilder {
#region Static properties
/// <summary>
/// Gets or sets the string that labels the Apply button.
/// Exposed so it can be localized.
/// </summary>
static public string APPLY_LABEL = "应用";
/// <summary>
/// Gets or sets the string that labels the Clear All menu item.
/// Exposed so it can be localized.
/// </summary>
static public string CLEAR_ALL_FILTERS_LABEL = "清空筛选规则";
/// <summary>
/// Gets or sets the string that labels the Filtering menu as a whole..
/// Exposed so it can be localized.
/// </summary>
static public string FILTERING_LABEL = "筛选";
/// <summary>
/// Gets or sets the string that represents Select All values.
/// If this is set to null or empty, no Select All option will be included.
/// Exposed so it can be localized.
/// </summary>
static public string SELECT_ALL_LABEL = "全选";
/// <summary>
/// Gets or sets the image that will be placed next to the Clear Filtering menu item
/// </summary>
static public Bitmap ClearFilteringImage = ryControls.Properties.Resources.ClearFiltering;
/// <summary>
/// Gets or sets the image that will be placed next to all "Apply" menu items on the filtering menu
/// </summary>
static public Bitmap FilteringImage = ryControls.Properties.Resources.Filtering;
#endregion
#region Public properties
/// <summary>
/// Gets or sets whether null should be considered as a valid data value.
/// If this is true (the default), then a cluster will null as a key will be allow.
/// If this is false, object that return a cluster key of null will ignored.
/// </summary>
public bool TreatNullAsDataValue {
get { return treatNullAsDataValue; }
set { treatNullAsDataValue = value; }
}
private bool treatNullAsDataValue = true;
/// <summary>
/// Gets or sets the maximum number of objects that the clustering strategy
/// will consider. This should be large enough to collect all unique clusters,
/// but small enough to finish in a reasonable time.
/// </summary>
/// <remarks>The default value is 10,000. This should be perfectly
/// acceptable for almost all lists.</remarks>
public int MaxObjectsToConsider {
get { return maxObjectsToConsider; }
set { maxObjectsToConsider = value; }
}
private int maxObjectsToConsider = 10000;
#endregion
/// <summary>
/// Create a Filter menu on the given tool tip for the given column in the given ObjectListView.
/// </summary>
/// <remarks>This is the main entry point into this class.</remarks>
/// <param name="strip"></param>
/// <param name="listView"></param>
/// <param name="column"></param>
/// <returns>The strip that should be shown to the user</returns>
virtual public ToolStripDropDown MakeFilterMenu(ToolStripDropDown strip, ObjectListView listView, OLVColumn column) {
if (strip == null) throw new ArgumentNullException("strip");
if (listView == null) throw new ArgumentNullException("listView");
if (column == null) throw new ArgumentNullException("column");
if (!column.UseFiltering || column.ClusteringStrategy == null || listView.Objects == null)
return strip;
List<ICluster> clusters = this.Cluster(column.ClusteringStrategy, listView, column);
if (clusters.Count > 0) {
this.SortClusters(column.ClusteringStrategy, clusters);
strip.Items.Add(this.CreateFilteringMenuItem(column, clusters));
}
return strip;
}
/// <summary>
/// Create a collection of clusters that should be presented to the user
/// </summary>
/// <param name="strategy"></param>
/// <param name="listView"></param>
/// <param name="column"></param>
/// <returns></returns>
virtual protected List<ICluster> Cluster(IClusteringStrategy strategy, ObjectListView listView, OLVColumn column) {
// Build a map that correlates cluster key to clusters
NullableDictionary<object, ICluster> map = new NullableDictionary<object, ICluster>();
int count = 0;
foreach (object model in listView.ObjectsForClustering) {
this.ClusterOneModel(strategy, map, model);
if (count++ > this.MaxObjectsToConsider)
break;
}
// Now that we know exactly how many items are in each cluster, create a label for it
foreach (ICluster cluster in map.Values)
cluster.DisplayLabel = strategy.GetClusterDisplayLabel(cluster);
return new List<ICluster>(map.Values);
}
private void ClusterOneModel(IClusteringStrategy strategy, NullableDictionary<object, ICluster> map, object model) {
object clusterKey = strategy.GetClusterKey(model);
// If the returned value is an IEnumerable, that means the given model can belong to more than one cluster
IEnumerable keyEnumerable = clusterKey as IEnumerable;
if (clusterKey is string || keyEnumerable == null)
keyEnumerable = new object[] {clusterKey};
// Deal with nulls and DBNulls
ArrayList nullCorrected = new ArrayList();
foreach (object key in keyEnumerable) {
if (key == null || key == System.DBNull.Value) {
if (this.TreatNullAsDataValue)
nullCorrected.Add(null);
} else nullCorrected.Add(key);
}
// Group by key
foreach (object key in nullCorrected) {
if (map.ContainsKey(key))
map[key].Count += 1;
else
map[key] = strategy.CreateCluster(key);
}
}
/// <summary>
/// Order the given list of clusters in the manner in which they should be presented to the user.
/// </summary>
/// <param name="strategy"></param>
/// <param name="clusters"></param>
virtual protected void SortClusters(IClusteringStrategy strategy, List<ICluster> clusters) {
clusters.Sort();
}
/// <summary>
/// Do the work of making a menu that shows the clusters to the users
/// </summary>
/// <param name="column"></param>
/// <param name="clusters"></param>
/// <returns></returns>
virtual protected ToolStripMenuItem CreateFilteringMenuItem(OLVColumn column, List<ICluster> clusters) {
ToolStripCheckedListBox checkedList = new ToolStripCheckedListBox();
checkedList.Tag = column;
foreach (ICluster cluster in clusters)
checkedList.AddItem(cluster, column.ValuesChosenForFiltering.Contains(cluster.ClusterKey));
if (!String.IsNullOrEmpty(SELECT_ALL_LABEL)) {
int checkedCount = checkedList.CheckedItems.Count;
if (checkedCount == 0)
checkedList.AddItem(SELECT_ALL_LABEL, CheckState.Unchecked);
else
checkedList.AddItem(SELECT_ALL_LABEL, checkedCount == clusters.Count ? CheckState.Checked : CheckState.Indeterminate);
}
checkedList.ItemCheck += new ItemCheckEventHandler(HandleItemCheckedWrapped);
ToolStripMenuItem clearAll = new ToolStripMenuItem(CLEAR_ALL_FILTERS_LABEL, ClearFilteringImage, delegate(object sender, EventArgs args) {
this.ClearAllFilters(column);
});
ToolStripMenuItem apply = new ToolStripMenuItem(APPLY_LABEL, FilteringImage, delegate(object sender, EventArgs args) {
this.EnactFilter(checkedList, column);
});
ToolStripMenuItem subMenu = new ToolStripMenuItem(FILTERING_LABEL, null, new ToolStripItem[] {
clearAll, new ToolStripSeparator(), checkedList, apply });
return subMenu;
}
/// <summary>
/// Wrap a protected section around the real HandleItemChecked method, so that if
/// that method tries to change a "checkedness" of an item, we don't get a recursive
/// stack error. Effectively, this ensure that HandleItemChecked is only called
/// in response to a user action.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleItemCheckedWrapped(object sender, ItemCheckEventArgs e) {
if (alreadyInHandleItemChecked)
return;
try {
alreadyInHandleItemChecked = true;
this.HandleItemChecked(sender, e);
}
finally {
alreadyInHandleItemChecked = false;
}
}
bool alreadyInHandleItemChecked = false;
/// <summary>
/// Handle a user-generated ItemCheck event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
virtual protected void HandleItemChecked(object sender, ItemCheckEventArgs e) {
ToolStripCheckedListBox checkedList = sender as ToolStripCheckedListBox;
if (checkedList == null) return;
OLVColumn column = checkedList.Tag as OLVColumn;
if (column == null) return;
ObjectListView listView = column.ListView as ObjectListView;
if (listView == null) return;
// Deal with the "Select All" item if there is one
int selectAllIndex = checkedList.Items.IndexOf(SELECT_ALL_LABEL);
if (selectAllIndex >= 0)
HandleSelectAllItem(e, checkedList, selectAllIndex);
}
/// <summary>
/// Handle any checking/unchecking of the Select All option, and keep
/// its checkedness in sync with everything else that is checked.
/// </summary>
/// <param name="e"></param>
/// <param name="checkedList"></param>
/// <param name="selectAllIndex"></param>
virtual protected void HandleSelectAllItem(ItemCheckEventArgs e, ToolStripCheckedListBox checkedList, int selectAllIndex) {
// Did they check/uncheck the "Select All"?
if (e.Index == selectAllIndex) {
if (e.NewValue == CheckState.Checked)
checkedList.CheckAll();
if (e.NewValue == CheckState.Unchecked)
checkedList.UncheckAll();
return;
}
// OK. The user didn't check/uncheck SelectAll. Now we have to update it's
// checkedness to reflect the state of everything else
// If all clusters are checked, we check the Select All.
// If no clusters are checked, the uncheck the Select All.
// For everything else, Select All is set to indeterminate.
// How many items are currenty checked?
int count = checkedList.CheckedItems.Count;
// First complication.
// The value of the Select All itself doesn't count
if (checkedList.GetItemCheckState(selectAllIndex) != CheckState.Unchecked)
count -= 1;
// Another complication.
// CheckedItems does not yet know about the item the user has just
// clicked, so we have to adjust the count of checked items to what
// it is going to be
if (e.NewValue != e.CurrentValue) {
if (e.NewValue == CheckState.Checked)
count += 1;
else
count -= 1;
}
// Update the state of the Select All item
if (count == 0)
checkedList.SetItemState(selectAllIndex, CheckState.Unchecked);
else if (count == checkedList.Items.Count - 1)
checkedList.SetItemState(selectAllIndex, CheckState.Checked);
else
checkedList.SetItemState(selectAllIndex, CheckState.Indeterminate);
}
/// <summary>
/// Clear all the filters that are applied to the given column
/// </summary>
/// <param name="column">The column from which filters are to be removed</param>
virtual protected void ClearAllFilters(OLVColumn column) {
ObjectListView olv = column.ListView as ObjectListView;
if (olv == null || olv.IsDisposed)
return;
olv.ResetColumnFiltering();
}
/// <summary>
/// Apply the selected values from the given list as a filter on the given column
/// </summary>
/// <param name="checkedList">A list in which the checked items should be used as filters</param>
/// <param name="column">The column for which a filter should be generated</param>
virtual protected void EnactFilter(ToolStripCheckedListBox checkedList, OLVColumn column) {
ObjectListView olv = column.ListView as ObjectListView;
if (olv == null || olv.IsDisposed)
return;
// Collect all the checked values
ArrayList chosenValues = new ArrayList();
foreach (object x in checkedList.CheckedItems) {
ICluster cluster = x as ICluster;
if (cluster != null) {
chosenValues.Add(cluster.ClusterKey);
}
}
column.ValuesChosenForFiltering = chosenValues;
olv.UpdateColumnFiltering();
}
}
}

View File

@ -0,0 +1,489 @@
/*
* Filters - Filtering on ObjectListViews
*
* Author: Phillip Piper
* Date: 03/03/2010 17:00
*
* Change log:
* 2011-03-01 JPP Added CompositeAllFilter, CompositeAnyFilter and OneOfFilter
* v2.4.1
* 2010-06-23 JPP Extended TextMatchFilter to handle regular expressions and string prefix matching.
* v2.4
* 2010-03-03 JPP Initial version
*
* TO DO:
*
* 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@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Drawing;
namespace BrightIdeasSoftware
{
/// <summary>
/// Interface for model-by-model filtering
/// </summary>
public interface IModelFilter
{
/// <summary>
/// Should the given model be included when this filter is installed
/// </summary>
/// <param name="modelObject">The model object to consider</param>
/// <returns>Returns true if the model will be included by the filter</returns>
bool Filter(object modelObject);
}
/// <summary>
/// Interface for whole list filtering
/// </summary>
public interface IListFilter
{
/// <summary>
/// Return a subset of the given list of model objects as the new
/// contents of the ObjectListView
/// </summary>
/// <param name="modelObjects">The collection of model objects that the list will possibly display</param>
/// <returns>The filtered collection that holds the model objects that will be displayed.</returns>
IEnumerable Filter(IEnumerable modelObjects);
}
/// <summary>
/// Base class for model-by-model filters
/// </summary>
public class AbstractModelFilter : IModelFilter
{
/// <summary>
/// Should the given model be included when this filter is installed
/// </summary>
/// <param name="modelObject">The model object to consider</param>
/// <returns>Returns true if the model will be included by the filter</returns>
virtual public bool Filter(object modelObject) {
return true;
}
}
/// <summary>
/// This filter calls a given Predicate to decide if a model object should be included
/// </summary>
public class ModelFilter : IModelFilter
{
/// <summary>
/// Create a filter based on the given predicate
/// </summary>
/// <param name="predicate">The function that will filter objects</param>
public ModelFilter(Predicate<object> predicate) {
this.Predicate = predicate;
}
/// <summary>
/// Gets or sets the predicate used to filter model objects
/// </summary>
protected Predicate<object> Predicate {
get { return predicate; }
set { predicate = value; }
}
private Predicate<object> predicate;
/// <summary>
/// Should the given model object be included?
/// </summary>
/// <param name="modelObject"></param>
/// <returns></returns>
virtual public bool Filter(object modelObject) {
return this.Predicate == null ? true : this.Predicate(modelObject);
}
}
/// <summary>
/// A CompositeFilter joins several other filters together.
/// If there are no filters, all model objects are included
/// </summary>
abstract public class CompositeFilter : IModelFilter {
/// <summary>
/// Create an empty filter
/// </summary>
public CompositeFilter() {
}
/// <summary>
/// Create a composite filter from the given list of filters
/// </summary>
/// <param name="filters">A list of filters</param>
public CompositeFilter(IEnumerable<IModelFilter> filters) {
foreach (IModelFilter filter in filters) {
if (filter != null)
Filters.Add(filter);
}
}
/// <summary>
/// Gets or sets the filters used by this composite
/// </summary>
public IList<IModelFilter> Filters {
get { return filters; }
set { filters = value; }
}
private IList<IModelFilter> filters = new List<IModelFilter>();
/// <summary>
/// Get the sub filters that are text match filters
/// </summary>
public IEnumerable<TextMatchFilter> TextFilters {
get {
foreach (IModelFilter filter in this.Filters) {
TextMatchFilter textFilter = filter as TextMatchFilter;
if (textFilter != null)
yield return textFilter;
}
}
}
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <param name="modelObject"></param>
/// <returns>True if the object is included by the filter</returns>
virtual public bool Filter(object modelObject) {
if (this.Filters == null || this.Filters.Count == 0)
return true;
return this.FilterObject(modelObject);
}
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
/// <param name="modelObject">The model object under consideration</param>
/// <returns>True if the object is included by the filter</returns>
abstract public bool FilterObject(object modelObject);
}
/// <summary>
/// A CompositeAllFilter joins several other filters together.
/// A model object must satisfy all filters to be included.
/// If there are no filters, all model objects are included
/// </summary>
public class CompositeAllFilter : CompositeFilter {
/// <summary>
/// Create a filter
/// </summary>
/// <param name="filters"></param>
public CompositeAllFilter(List<IModelFilter> filters)
: base(filters) {
}
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
/// <param name="modelObject">The model object under consideration</param>
/// <returns>True if the object is included by the filter</returns>
override public bool FilterObject(object modelObject) {
foreach (IModelFilter filter in this.Filters)
if (!filter.Filter(modelObject))
return false;
return true;
}
}
/// <summary>
/// A CompositeAllFilter joins several other filters together.
/// A model object must only satisfy one of the filters to be included.
/// If there are no filters, all model objects are included
/// </summary>
public class CompositeAnyFilter : CompositeFilter {
/// <summary>
/// Create a filter from the given filters
/// </summary>
/// <param name="filters"></param>
public CompositeAnyFilter(List<IModelFilter> filters)
: base(filters) {
}
/// <summary>
/// Decide whether or not the given model should be included by the filter
/// </summary>
/// <remarks>Filters is guaranteed to be non-empty when this method is called</remarks>
/// <param name="modelObject">The model object under consideration</param>
/// <returns>True if the object is included by the filter</returns>
override public bool FilterObject(object modelObject) {
foreach (IModelFilter filter in this.Filters)
if (filter.Filter(modelObject))
return true;
return false;
}
}
/// <summary>
/// Instances of this class extract a value from the model object
/// and compare that value to a list of fixed values. The model
/// object is included if the extracted value is in the list
/// </summary>
/// <remarks>If there is no delegate installed or there are
/// no values to match, no model objects will be matched</remarks>
public class OneOfFilter : IModelFilter {
/// <summary>
/// Create a filter that will use the given delegate to extract values
/// </summary>
/// <param name="valueGetter"></param>
public OneOfFilter(AspectGetterDelegate valueGetter) :
this(valueGetter, new ArrayList()) {
}
/// <summary>
/// Create a filter that will extract values using the given delegate
/// and compare them to the values in the given list.
/// </summary>
/// <param name="valueGetter"></param>
/// <param name="possibleValues"></param>
public OneOfFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) {
this.ValueGetter = valueGetter;
this.PossibleValues = new ArrayList(possibleValues);
}
/// <summary>
/// Gets or sets the delegate that will be used to extract values
/// from model objects
/// </summary>
virtual public AspectGetterDelegate ValueGetter {
get { return valueGetter; }
set { valueGetter = value; }
}
private AspectGetterDelegate valueGetter;
/// <summary>
/// Gets or sets the list of values that the value extracted from
/// the model object must match in order to be included.
/// </summary>
virtual public IList PossibleValues {
get { return possibleValues; }
set { possibleValues = value; }
}
private IList possibleValues;
/// <summary>
/// Should the given model object be included?
/// </summary>
/// <param name="modelObject"></param>
/// <returns></returns>
public virtual bool Filter(object modelObject) {
if (this.ValueGetter == null || this.PossibleValues == null || this.PossibleValues.Count == 0)
return false;
object result = this.ValueGetter(modelObject);
IEnumerable enumerable = result as IEnumerable;
if (result is string || enumerable == null)
return this.DoesValueMatch(result);
foreach (object x in enumerable) {
if (this.DoesValueMatch(x))
return true;
}
return false;
}
/// <summary>
/// Decides if the given property is a match for the values in the PossibleValues collection
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
protected virtual bool DoesValueMatch(object result) {
return this.PossibleValues.Contains(result);
}
}
/// <summary>
/// Instances of this class match a property of a model objects against
/// a list of bit flags. The property should be an xor-ed collection
/// of bits flags.
/// </summary>
/// <remarks>Both the property compared and the list of possible values
/// must be convertible to ulongs.</remarks>
public class FlagBitSetFilter : OneOfFilter {
/// <summary>
/// Create an instance
/// </summary>
/// <param name="valueGetter"></param>
/// <param name="possibleValues"></param>
public FlagBitSetFilter(AspectGetterDelegate valueGetter, ICollection possibleValues) : base(valueGetter, possibleValues) {
this.ConvertPossibleValues();
}
/// <summary>
/// Gets or sets the collection of values that will be matched.
/// These must be ulongs (or convertible to ulongs).
/// </summary>
public override IList PossibleValues {
get { return base.PossibleValues; }
set {
base.PossibleValues = value;
this.ConvertPossibleValues();
}
}
private void ConvertPossibleValues() {
this.possibleValuesAsUlongs = new List<UInt64>();
foreach (object x in this.PossibleValues)
this.possibleValuesAsUlongs.Add(Convert.ToUInt64(x));
}
/// <summary>
/// Decides if the given property is a match for the values in the PossibleValues collection
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
protected override bool DoesValueMatch(object result) {
try {
UInt64 value = Convert.ToUInt64(result);
foreach (ulong flag in this.possibleValuesAsUlongs) {
if ((value & flag) == flag)
return true;
}
return false;
}
catch (InvalidCastException) {
return false;
}
catch (FormatException) {
return false;
}
}
private List<UInt64> possibleValuesAsUlongs = new List<UInt64>();
}
/// <summary>
/// Base class for whole list filters
/// </summary>
public class AbstractListFilter : IListFilter
{
/// <summary>
/// Return a subset of the given list of model objects as the new
/// contents of the ObjectListView
/// </summary>
/// <param name="modelObjects">The collection of model objects that the list will possibly display</param>
/// <returns>The filtered collection that holds the model objects that will be displayed.</returns>
virtual public IEnumerable Filter(IEnumerable modelObjects) {
return modelObjects;
}
}
/// <summary>
/// Instance of this class implement delegate based whole list filtering
/// </summary>
public class ListFilter : AbstractListFilter
{
/// <summary>
/// A delegate that filters on a whole list
/// </summary>
/// <param name="rowObjects"></param>
/// <returns></returns>
public delegate IEnumerable ListFilterDelegate(IEnumerable rowObjects);
/// <summary>
/// Create a ListFilter
/// </summary>
/// <param name="function"></param>
public ListFilter(ListFilterDelegate function) {
this.Function = function;
}
/// <summary>
/// Gets or sets the delegate that will filter the list
/// </summary>
public ListFilterDelegate Function {
get { return function; }
set { function = value; }
}
private ListFilterDelegate function;
/// <summary>
/// Do the actual work of filtering
/// </summary>
/// <param name="modelObjects"></param>
/// <returns></returns>
public override IEnumerable Filter(IEnumerable modelObjects) {
if (this.Function == null)
return modelObjects;
return this.Function(modelObjects);
}
}
/// <summary>
/// Filter the list so only the last N entries are displayed
/// </summary>
public class TailFilter : AbstractListFilter
{
/// <summary>
/// Create a no-op tail filter
/// </summary>
public TailFilter() {
}
/// <summary>
/// Create a filter that includes on the last N model objects
/// </summary>
/// <param name="numberOfObjects"></param>
public TailFilter(int numberOfObjects) {
this.Count = numberOfObjects;
}
/// <summary>
/// Gets or sets the number of model objects that will be
/// returned from the tail of the list
/// </summary>
public int Count {
get { return count; }
set { count = value; }
}
private int count;
/// <summary>
/// Return the last N subset of the model objects
/// </summary>
/// <param name="modelObjects"></param>
/// <returns></returns>
public override IEnumerable Filter(IEnumerable modelObjects) {
if (this.Count <= 0)
return modelObjects;
ArrayList list = ObjectListView.EnumerableToArray(modelObjects, false);
if (this.Count > list.Count)
return list;
object[] tail = new object[this.Count];
list.CopyTo(list.Count - this.Count, tail, 0, this.Count);
return new ArrayList(tail);
}
}
}

View File

@ -0,0 +1,160 @@
/*
* FlagClusteringStrategy - Implements a clustering strategy for a field which is a single integer
* containing an XOR'ed collection of bit flags
*
* Author: Phillip Piper
* Date: 23-March-2012 8:33 am
*
* Change log:
* 2012-03-23 JPP - First version
*
* Copyright (C) 2012 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
namespace BrightIdeasSoftware {
/// <summary>
/// Instances of this class cluster model objects on the basis of a
/// property that holds an xor-ed collection of bit flags.
/// </summary>
public class FlagClusteringStrategy : ClusteringStrategy
{
#region Life and death
/// <summary>
/// Create a clustering strategy that operates on the flags of the given enum
/// </summary>
/// <param name="enumType"></param>
public FlagClusteringStrategy(Type enumType) {
if (enumType == null) throw new ArgumentNullException("enumType");
if (!enumType.IsEnum) throw new ArgumentException("Type must be enum", "enumType");
if (enumType.GetCustomAttributes(typeof(FlagsAttribute), false) == null) throw new ArgumentException("Type must have [Flags] attribute", "enumType");
List<long> flags = new List<long>();
foreach (object x in Enum.GetValues(enumType))
flags.Add(Convert.ToInt64(x));
List<string> flagLabels = new List<string>();
foreach (string x in Enum.GetNames(enumType))
flagLabels.Add(x);
this.SetValues(flags.ToArray(), flagLabels.ToArray());
}
/// <summary>
/// Create a clustering strategy around the given collections of flags and their display labels.
/// There must be the same number of elements in both collections.
/// </summary>
/// <param name="values">The list of flags. </param>
/// <param name="labels"></param>
public FlagClusteringStrategy(long[] values, string[] labels) {
this.SetValues(values, labels);
}
#endregion
#region Implementation
/// <summary>
/// Gets the value that will be xor-ed to test for the presence of a particular value.
/// </summary>
public long[] Values {
get { return this.values; }
private set { this.values = value; }
}
private long[] values;
/// <summary>
/// Gets the labels that will be used when the corresponding Value is XOR present in the data.
/// </summary>
public string[] Labels {
get { return this.labels; }
private set { this.labels = value; }
}
private string[] labels;
private void SetValues(long[] flags, string[] flagLabels) {
if (flags == null || flags.Length == 0) throw new ArgumentNullException("flags");
if (flagLabels == null || flagLabels.Length == 0) throw new ArgumentNullException("flagLabels");
if (flags.Length != flagLabels.Length) throw new ArgumentException("values and labels must have the same number of entries", "flags");
this.Values = flags;
this.Labels = flagLabels;
}
#endregion
#region Implementation of IClusteringStrategy
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public override object GetClusterKey(object model) {
List<long> flags = new List<long>();
try {
long modelValue = Convert.ToInt64(this.Column.GetValue(model));
foreach (long x in this.Values) {
if ((x & modelValue) == x)
flags.Add(x);
}
return flags;
}
catch (InvalidCastException ex) {
System.Diagnostics.Debug.Write(ex);
return flags;
}
catch (FormatException ex) {
System.Diagnostics.Debug.Write(ex);
return flags;
}
}
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
public override string GetClusterDisplayLabel(ICluster cluster) {
long clusterKeyAsUlong = Convert.ToInt64(cluster.ClusterKey);
for (int i = 0; i < this.Values.Length; i++ ) {
if (clusterKeyAsUlong == this.Values[i])
return this.ApplyDisplayFormat(cluster, this.Labels[i]);
}
return this.ApplyDisplayFormat(cluster, clusterKeyAsUlong.ToString(CultureInfo.CurrentUICulture));
}
/// <summary>
/// Create a filter that will include only model objects that
/// match one or more of the given values.
/// </summary>
/// <param name="valuesChosenForFiltering"></param>
/// <returns></returns>
public override IModelFilter CreateFilter(IList valuesChosenForFiltering) {
return new FlagBitSetFilter(this.GetClusterKey, valuesChosenForFiltering);
}
#endregion
}
}

View File

@ -0,0 +1,56 @@
/*
* ICluster - A cluster is a group of objects that can be included or excluded as a whole
*
* Author: Phillip Piper
* Date: 4-March-2011 11:59 pm
*
* Change log:
* 2011-03-04 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware {
/// <summary>
/// A cluster is a like collection of objects that can be usefully filtered
/// as whole using the filtering UI provided by the ObjectListView.
/// </summary>
public interface ICluster : IComparable {
/// <summary>
/// Gets or sets how many items belong to this cluster
/// </summary>
int Count { get; set; }
/// <summary>
/// Gets or sets the label that will be shown to the user to represent
/// this cluster
/// </summary>
string DisplayLabel { get; set; }
/// <summary>
/// Gets or sets the actual data object that all members of this cluster
/// have commonly returned.
/// </summary>
object ClusterKey { get; set; }
}
}

View File

@ -0,0 +1,80 @@
/*
* IClusterStrategy - Encapsulates the ability to create a list of clusters from an ObjectListView
*
* Author: Phillip Piper
* Date: 4-March-2011 11:59 pm
*
* Change log:
* 2012-05-23 JPP - Added CreateFilter() method to interface to allow the strategy
* to control the actual model filter that is created.
* v2.5
* 2011-03-04 JPP - First version
*
* Copyright (C) 2011-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace BrightIdeasSoftware{
/// <summary>
/// Implementation of this interface control the selecting of cluster keys
/// and how those clusters will be presented to the user
/// </summary>
public interface IClusteringStrategy {
/// <summary>
/// Gets or sets the column upon which this strategy will operate
/// </summary>
OLVColumn Column { get; set; }
/// <summary>
/// Get the cluster key by which the given model will be partitioned by this strategy
/// </summary>
/// <remarks>If the returned value is an IEnumerable, the given model is considered
/// to belong to multiple clusters</remarks>
/// <param name="model"></param>
/// <returns></returns>
object GetClusterKey(object model);
/// <summary>
/// Create a cluster to hold the given cluster key
/// </summary>
/// <param name="clusterKey"></param>
/// <returns></returns>
ICluster CreateCluster(object clusterKey);
/// <summary>
/// Gets the display label that the given cluster should use
/// </summary>
/// <param name="cluster"></param>
/// <returns></returns>
string GetClusterDisplayLabel(ICluster cluster);
/// <summary>
/// Create a filter that will include only model objects that
/// match one or more of the given values.
/// </summary>
/// <param name="valuesChosenForFiltering"></param>
/// <returns></returns>
IModelFilter CreateFilter(IList valuesChosenForFiltering);
}
}

View File

@ -0,0 +1,629 @@
/*
* TextMatchFilter - Text based filtering on ObjectListViews
*
* Author: Phillip Piper
* Date: 31/05/2011 7:45am
*
* Change log:
* v2.6
* 2012-10-13 JPP Allow filtering to consider additional columns
* v2.5.1
* 2011-06-22 JPP Handle searching for empty strings
* v2.5.0
* 2011-05-31 JPP Initial version
*
* TO DO:
*
* 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@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
namespace BrightIdeasSoftware {
/// <summary>
/// Instances of this class include only those rows of the listview
/// that match one or more given strings.
/// </summary>
/// <remarks>This class can match strings by prefix, regex, or simple containment.
/// There are factory methods for each of these matching strategies.</remarks>
public class TextMatchFilter : AbstractModelFilter {
#region Life and death
/// <summary>
/// Create a text filter that will include rows where any cell matches
/// any of the given regex expressions.
/// </summary>
/// <param name="olv"></param>
/// <param name="texts"></param>
/// <returns></returns>
/// <remarks>Any string that is not a valid regex expression will be ignored.</remarks>
public static TextMatchFilter Regex(ObjectListView olv, params string[] texts) {
TextMatchFilter filter = new TextMatchFilter(olv);
filter.RegexStrings = texts;
return filter;
}
/// <summary>
/// Create a text filter that includes rows where any cell begins with one of the given strings
/// </summary>
/// <param name="olv"></param>
/// <param name="texts"></param>
/// <returns></returns>
public static TextMatchFilter Prefix(ObjectListView olv, params string[] texts) {
TextMatchFilter filter = new TextMatchFilter(olv);
filter.PrefixStrings = texts;
return filter;
}
/// <summary>
/// Create a text filter that includes rows where any cell contains any of the given strings.
/// </summary>
/// <param name="olv"></param>
/// <param name="texts"></param>
/// <returns></returns>
public static TextMatchFilter Contains(ObjectListView olv, params string[] texts) {
TextMatchFilter filter = new TextMatchFilter(olv);
filter.ContainsStrings = texts;
return filter;
}
/// <summary>
/// Create a TextFilter
/// </summary>
/// <param name="olv"></param>
public TextMatchFilter(ObjectListView olv) {
this.ListView = olv;
}
/// <summary>
/// Create a TextFilter that finds the given string
/// </summary>
/// <param name="olv"></param>
/// <param name="text"></param>
public TextMatchFilter(ObjectListView olv, string text) {
this.ListView = olv;
this.ContainsStrings = new string[] { text };
}
/// <summary>
/// Create a TextFilter that finds the given string using the given comparison
/// </summary>
/// <param name="olv"></param>
/// <param name="text"></param>
/// <param name="comparison"></param>
public TextMatchFilter(ObjectListView olv, string text, StringComparison comparison) {
this.ListView = olv;
this.ContainsStrings = new string[] { text };
this.StringComparison = comparison;
}
#endregion
#region Public properties
/// <summary>
/// Gets or sets which columns will be used for the comparisons? If this is null, all columns will be used
/// </summary>
public OLVColumn[] Columns {
get { return columns; }
set { columns = value; }
}
private OLVColumn[] columns;
/// <summary>
/// Gets or sets additional columns which will be used in the comparison. These will be used
/// in addition to either the Columns property or to all columns taken from the control.
/// </summary>
public OLVColumn[] AdditionalColumns {
get { return additionalColumns; }
set { additionalColumns = value; }
}
private OLVColumn[] additionalColumns;
/// <summary>
/// Gets or sets the collection of strings that will be used for
/// contains matching. Setting this replaces all previous texts
/// of any kind.
/// </summary>
public IEnumerable<string> ContainsStrings {
get {
foreach (TextMatchingStrategy component in this.MatchingStrategies)
yield return component.Text;
}
set {
this.MatchingStrategies = new List<TextMatchingStrategy>();
if (value != null) {
foreach (string text in value)
this.MatchingStrategies.Add(new TextContainsMatchingStrategy(this, text));
}
}
}
/// <summary>
/// Gets whether or not this filter has any search criteria
/// </summary>
public bool HasComponents {
get {
return this.MatchingStrategies.Count > 0;
}
}
/// <summary>
/// Gets or set the ObjectListView upon which this filter will work
/// </summary>
/// <remarks>
/// You cannot really rebase a filter after it is created, so do not change this value.
/// It is included so that it can be set in an object initializer.
/// </remarks>
public ObjectListView ListView {
get { return listView; }
set { listView = value; }
}
private ObjectListView listView;
/// <summary>
/// Gets or sets the collection of strings that will be used for
/// prefix matching. Setting this replaces all previous texts
/// of any kind.
/// </summary>
public IEnumerable<string> PrefixStrings {
get {
foreach (TextMatchingStrategy component in this.MatchingStrategies)
yield return component.Text;
}
set {
this.MatchingStrategies = new List<TextMatchingStrategy>();
if (value != null) {
foreach (string text in value)
this.MatchingStrategies.Add(new TextBeginsMatchingStrategy(this, text));
}
}
}
/// <summary>
/// Gets or sets the options that will be used when compiling the regular expression.
/// </summary>
/// <remarks>
/// This is only used when doing Regex matching (obviously).
/// If this is not set specifically, the appropriate options are chosen to match the
/// StringComparison setting (culture invariant, case sensitive).
/// </remarks>
public RegexOptions RegexOptions {
get {
if (!regexOptions.HasValue) {
switch (this.StringComparison) {
case StringComparison.CurrentCulture:
regexOptions = RegexOptions.None;
break;
case StringComparison.CurrentCultureIgnoreCase:
regexOptions = RegexOptions.IgnoreCase;
break;
case StringComparison.Ordinal:
case StringComparison.InvariantCulture:
regexOptions = RegexOptions.CultureInvariant;
break;
case StringComparison.OrdinalIgnoreCase:
case StringComparison.InvariantCultureIgnoreCase:
regexOptions = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
break;
default:
regexOptions = RegexOptions.None;
break;
}
}
return regexOptions.Value;
}
set {
regexOptions = value;
}
}
private RegexOptions? regexOptions;
/// <summary>
/// Gets or sets the collection of strings that will be used for
/// regex pattern matching. Setting this replaces all previous texts
/// of any kind.
/// </summary>
public IEnumerable<string> RegexStrings {
get {
foreach (TextMatchingStrategy component in this.MatchingStrategies)
yield return component.Text;
}
set {
this.MatchingStrategies = new List<TextMatchingStrategy>();
if (value != null) {
foreach (string text in value)
this.MatchingStrategies.Add(new TextRegexMatchingStrategy(this, text));
}
}
}
/// <summary>
/// Gets or sets how the filter will match text
/// </summary>
public StringComparison StringComparison {
get { return this.stringComparison; }
set { this.stringComparison = value; }
}
private StringComparison stringComparison = StringComparison.InvariantCultureIgnoreCase;
#endregion
#region Implementation
/// <summary>
/// Loop over the columns that are being considering by the filter
/// </summary>
/// <returns></returns>
protected virtual IEnumerable<OLVColumn> IterateColumns() {
if (this.Columns == null) {
foreach (OLVColumn column in this.ListView.Columns)
yield return column;
} else {
foreach (OLVColumn column in this.Columns)
yield return column;
}
if (this.AdditionalColumns != null) {
foreach (OLVColumn column in this.AdditionalColumns)
yield return column;
}
}
#endregion
#region Public interface
/// <summary>
/// Do the actual work of filtering
/// </summary>
/// <param name="modelObject"></param>
/// <returns></returns>
public override bool Filter(object modelObject) {
if (this.ListView == null || !this.HasComponents)
return true;
foreach (OLVColumn column in this.IterateColumns()) {
if (column.IsVisible && column.Searchable) {
string[] cellTexts = column.GetSearchValues(modelObject);
if (cellTexts != null && cellTexts.Length > 0) {
foreach (TextMatchingStrategy filter in this.MatchingStrategies) {
if (String.IsNullOrEmpty(filter.Text))
return true;
foreach (string cellText in cellTexts) {
if (filter.MatchesText(cellText))
return true;
}
}
}
}
}
return false;
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>This is used by the renderer to decide which bits of
/// the string should be highlighted</remarks>
/// <param name="cellText"></param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
foreach (TextMatchingStrategy filter in this.MatchingStrategies) {
if (!String.IsNullOrEmpty(filter.Text))
ranges.AddRange(filter.FindAllMatchedRanges(cellText));
}
return ranges;
}
/// <summary>
/// Is the given column one of the columns being used by this filter?
/// </summary>
/// <param name="column"></param>
/// <returns></returns>
public bool IsIncluded(OLVColumn column) {
if (this.Columns == null) {
return column.ListView == this.ListView;
}
foreach (OLVColumn x in this.Columns) {
if (x == column)
return true;
}
return false;
}
#endregion
#region Implementation members
private List<TextMatchingStrategy> MatchingStrategies = new List<TextMatchingStrategy>();
#endregion
#region Components
/// <summary>
/// Base class for the various types of string matching that TextMatchFilter provides
/// </summary>
abstract protected class TextMatchingStrategy {
/// <summary>
/// Gets how the filter will match text
/// </summary>
public StringComparison StringComparison {
get { return this.TextFilter.StringComparison; }
}
/// <summary>
/// Gets the text filter to which this component belongs
/// </summary>
public TextMatchFilter TextFilter {
get { return textFilter; }
set { textFilter = value; }
}
private TextMatchFilter textFilter;
/// <summary>
/// Gets or sets the text that will be matched
/// </summary>
public string Text {
get { return this.text; }
set { this.text = value; }
}
private string text;
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
abstract public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText);
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
abstract public bool MatchesText(string cellText);
}
/// <summary>
/// This component provides text contains matching strategy.
/// </summary>
protected class TextContainsMatchingStrategy : TextMatchingStrategy {
/// <summary>
/// Create a text contains strategy
/// </summary>
/// <param name="filter"></param>
/// <param name="text"></param>
public TextContainsMatchingStrategy(TextMatchFilter filter, string text) {
this.TextFilter = filter;
this.Text = text;
}
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
override public bool MatchesText(string cellText) {
return cellText.IndexOf(this.Text, this.StringComparison) != -1;
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
int matchIndex = cellText.IndexOf(this.Text, this.StringComparison);
while (matchIndex != -1) {
ranges.Add(new CharacterRange(matchIndex, this.Text.Length));
matchIndex = cellText.IndexOf(this.Text, matchIndex + this.Text.Length, this.StringComparison);
}
return ranges;
}
}
/// <summary>
/// This component provides text begins with matching strategy.
/// </summary>
protected class TextBeginsMatchingStrategy : TextMatchingStrategy {
/// <summary>
/// Create a text begins strategy
/// </summary>
/// <param name="filter"></param>
/// <param name="text"></param>
public TextBeginsMatchingStrategy(TextMatchFilter filter, string text) {
this.TextFilter = filter;
this.Text = text;
}
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
override public bool MatchesText(string cellText) {
return cellText.StartsWith(this.Text, this.StringComparison);
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
if (cellText.StartsWith(this.Text, this.StringComparison))
ranges.Add(new CharacterRange(0, this.Text.Length));
return ranges;
}
}
/// <summary>
/// This component provides regex matching strategy.
/// </summary>
protected class TextRegexMatchingStrategy : TextMatchingStrategy {
/// <summary>
/// Creates a regex strategy
/// </summary>
/// <param name="filter"></param>
/// <param name="text"></param>
public TextRegexMatchingStrategy(TextMatchFilter filter, string text) {
this.TextFilter = filter;
this.Text = text;
}
/// <summary>
/// Gets or sets the options that will be used when compiling the regular expression.
/// </summary>
public RegexOptions RegexOptions {
get {
return this.TextFilter.RegexOptions;
}
}
/// <summary>
/// Gets or sets a compilex regular expression, based on our current Text and RegexOptions.
/// </summary>
/// <remarks>
/// If Text fails to compile as a regular expression, this will return a Regex object
/// that will match all strings.
/// </remarks>
protected Regex Regex {
get {
if (this.regex == null) {
try {
this.regex = new Regex(this.Text, this.RegexOptions);
}
catch (ArgumentException) {
this.regex = TextRegexMatchingStrategy.InvalidRegexMarker;
}
}
return this.regex;
}
set {
this.regex = value;
}
}
private Regex regex;
/// <summary>
/// Gets whether or not our current regular expression is a valid regex
/// </summary>
protected bool IsRegexInvalid {
get {
return this.Regex == TextRegexMatchingStrategy.InvalidRegexMarker;
}
}
static private Regex InvalidRegexMarker = new Regex(".*");
/// <summary>
/// Does the given text match the filter
/// </summary>
/// <remarks>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>Return true if the given cellText matches our strategy</returns>
public override bool MatchesText(string cellText) {
if (this.IsRegexInvalid)
return true;
return this.Regex.Match(cellText).Success;
}
/// <summary>
/// Find all the ways in which this filter matches the given string.
/// </summary>
/// <remarks>
/// <para>
/// This is used by the renderer to decide which bits of
/// the string should be highlighted.
/// </para>
/// <para>this.Text will not be null or empty when this is called.</para>
/// </remarks>
/// <param name="cellText">The text of the cell we want to search</param>
/// <returns>A list of character ranges indicating the matched substrings</returns>
override public IEnumerable<CharacterRange> FindAllMatchedRanges(string cellText) {
List<CharacterRange> ranges = new List<CharacterRange>();
if (!this.IsRegexInvalid) {
foreach (Match match in this.Regex.Matches(cellText)) {
if (match.Length > 0)
ranges.Add(new CharacterRange(match.Index, match.Length));
}
}
return ranges;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

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@gmail.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
{
}
}

View File

@ -0,0 +1,330 @@
/*
* Comparers - Various Comparer classes used within ObjectListView
*
* Author: Phillip Piper
* Date: 25/11/2008 17:15
*
* Change log:
* v2.8.1
* 2014-12-03 JPP - Added StringComparer
* 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@gmail.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>
/// <para>This is used by normal (non-virtual) ObjectListViews. Virtual lists use
/// ModelObjectComparer</para>
/// </remarks>
public class ColumnComparer : IComparer, IComparer<OLVListItem>
{
/// <summary>
/// Gets or sets the method that will be used to compare two strings.
/// The default is to compare on the current culture, case-insensitive
/// </summary>
public static StringCompareDelegate StringComparer
{
get { return stringComparer; }
set { stringComparer = value; }
}
private static StringCompareDelegate stringComparer;
/// <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 CompareStrings(xAsString, y as String);
IComparable comparable = x as IComparable;
return comparable != null ? comparable.CompareTo(y) : 0;
}
private static int CompareStrings(string x, string y)
{
if (StringComparer == null)
return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase);
else
return StringComparer(x, y);
}
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>
/// <remarks>
/// <para>This is used by virtual ObjectListViews. Non-virtual lists use
/// ColumnComparer</para>
/// </remarks>
public class ModelObjectComparer : IComparer, IComparer<object>
{
/// <summary>
/// Gets or sets the method that will be used to compare two strings.
/// The default is to compare on the current culture, case-insensitive
/// </summary>
public static StringCompareDelegate StringComparer
{
get { return stringComparer; }
set { stringComparer = value; }
}
private static StringCompareDelegate stringComparer;
/// <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 CompareStrings(xStr, y as String);
IComparable comparable = x as IComparable;
return comparable != null ? comparable.CompareTo(y) : 0;
}
private static int CompareStrings(string x, string y)
{
if (StringComparer == null)
return String.Compare(x, y, StringComparison.CurrentCultureIgnoreCase);
else
return StringComparer(x, y);
}
private OLVColumn column;
private SortOrder sortOrder;
private ModelObjectComparer secondComparer;
#region IComparer<object> Members
#endregion
}
}

View File

@ -0,0 +1,626 @@
/*
* DataSourceAdapter - A helper class that translates DataSource events for an ObjectListView
*
* Author: Phillip Piper
* Date: 20/09/2010 7:42 AM
*
* Change log:
* v2.9
* 2015-10-31 JPP - Put back sanity check on upper limit of source items
* 2015-02-02 JPP - Made CreateColumnsFromSource() only rebuild columns when new ones were added
* v2.8.1
* 2014-11-23 JPP - Honour initial CurrencyManager.Position when setting DataSource.
* 2014-10-27 JPP - Fix issue where SelectedObject was not sync'ed with CurrencyManager.Position (SF #129)
* 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@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Data;
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;
// ReSharper disable once DoNotCallOverridableMethodsInConstructor
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;
#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.SelectionChanged += new EventHandler(HandleListViewSelectionChanged);
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.SelectionChanged -= new EventHandler(HandleListViewSelectionChanged);
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();
// Fake a position change event so that the control matches any initial Position
this.HandleCurrencyManagerPositionChanged(null, null);
}
/// <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;
bool wereColumnsAdded = false;
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);
wereColumnsAdded = true;
}
if (wereColumnsAdded)
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 = Stopwatch.StartNew();
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("PERF - 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 HandleListViewSelectionChanged(object sender, EventArgs e) {
// Prevent recursion
if (this.isChangingIndex)
return;
// Sanity
if (this.CurrencyManager == null)
return;
// If only one item is selected, tell the currency manager which item is selected.
// CurrencyManager can't handle multiple selection so there's nothing we can do
// if more than one row is selected.
if (this.ListView.SelectedIndices.Count != 1)
return;
try {
this.isChangingIndex = true;
// We can't use the selectedIndex directly, since our listview may be sorted and/or filtered
// 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
}
}

View File

@ -0,0 +1,168 @@
/*
* Delegates - All delegate definitions used in ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* v2.10
* 2015-12-30 JPP - Added CellRendererGetterDelegate
* v2.?
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011-2015 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@gmail.com.
*/
using System;
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>
/// These delegates are used to get the renderer for a particular cell
/// </summary>
public delegate IRenderer CellRendererGetterDelegate(Object rowObject, OLVColumn column);
/// <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 can be used to return the array of texts that should be searched for text filtering
/// </summary>
public delegate string[] SearchValueGetterDelegate(Object value);
/// <summary>
/// These delegates are used to sort the listview in some custom fashion
/// </summary>
public delegate void SortDelegate(OLVColumn column, SortOrder sortOrder);
/// <summary>
/// These delegates are used to order two strings.
/// x cannot be null. y can be null.
/// </summary>
public delegate int StringCompareDelegate(string x, string y);
#endregion
}

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

View File

@ -0,0 +1,104 @@
/*
* 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@gmail.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>
/// 列表将不支持编辑(同时F2按键无效)
/// </summary>
None = 0,
/// <summary>
///单击 <strong>单元格</strong> 将编辑值.
///选择该行就像正常选择行一样。用户必须按F2键才能编辑主列。
/// </summary>
SingleClick = 1,
/// <summary>
/// 双击子项或主列将编辑该单元格。
/// F2键将编辑主列。
/// </summary>
DoubleClick = 2,
/// <summary>
/// 按F2键是编辑单元格的唯一方法。一旦主列被编辑
///行中的其他单元格可以通过按Tab键进行编辑。
/// </summary>
F2Only = 3,
/// <summary>
/// 只需单击任意单元格即可编辑值,即使是主列也是如此。
/// </summary>
SingleClickAlways = 4,
}
/// <summary>
///这些值指定向用户显示列选择的方式
/// </summary>
public enum ColumnSelectBehaviour {
/// <summary>
/// 不会显示任何列选择
/// </summary>
None,
/// <summary>
///这些列将显示在主菜单中
/// </summary>
InlineMenu,
/// <summary>
/// 这些列将显示在子菜单中
/// </summary>
Submenu,
/// <summary>
/// 将显示一个模式对话框,允许用户选择列
/// </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

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@gmail.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;
}
}

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@gmail.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("默认组标题") {
}
/// <summary>
/// 按指定标题创建分组
/// </summary>
/// <param name="header">分组标题</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
}
}

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@gmail.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

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@gmail.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;
}
}
}
}

View File

@ -0,0 +1,321 @@
/*
* OLVListItem - A row in an ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2015-08-22 JPP - Added OLVListItem.SelectedBackColor and SelectedForeColor
* 2015-06-09 JPP - Added HasAnyHyperlinks property
* 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-2015 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@gmail.com.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
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.
/// Unfortunately, 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>
/// Gets whether any cell on this item is showing a hyperlink
/// </summary>
public bool HasAnyHyperlinks {
get {
foreach (OLVListSubItem subItem in this.SubItems) {
if (!String.IsNullOrEmpty(subItem.Url))
return true;
}
return false;
}
}
/// <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;
/// <summary>
/// Gets or sets the color that will be used for this row's background when it is selected and
/// the control is focused.
/// </summary>
/// <remarks>
/// <para>To work reliably, this property must be set during a FormatRow event.</para>
/// <para>
/// If this is not set, the normal selection BackColor will be used.
/// </para>
/// </remarks>
public Color? SelectedBackColor {
get { return this.selectedBackColor; }
set { this.selectedBackColor = value; }
}
private Color? selectedBackColor;
/// <summary>
/// Gets or sets the color that will be used for this row's foreground when it is selected and
/// the control is focused.
/// </summary>
/// <remarks>
/// <para>To work reliably, this property must be set during a FormatRow event.</para>
/// <para>
/// If this is not set, the normal selection ForeColor will be used.
/// </para>
/// </remarks>
public Color? SelectedForeColor
{
get { return this.selectedForeColor; }
set { this.selectedForeColor = value; }
}
private Color? selectedForeColor;
#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
}
}

View File

@ -0,0 +1,173 @@
/*
* 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@gmail.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;
/// <summary>
/// Gets or sets whether this cell is selected
/// </summary>
public bool Selected
{
get { return this.selected; }
set { this.selected = value; }
}
private bool selected;
#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
}
}

View File

@ -0,0 +1,388 @@
/*
* 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@gmail.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 a button (cell must have ButtonRenderer)
/// </summary>
Button,
/// <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;
}
}
}

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;
}
}

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@gmail.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
}
}

View File

@ -0,0 +1,349 @@
/*
* 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@gmail.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>
/// Insert the given collection of model objects to this control at the position
/// </summary>
/// <param name="index">Index where the collection will be added</param>
/// <param name="modelObjects">A collection of model objects</param>
void InsertObjects(int index, 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="index"></param>
/// <param name="modelObjects"></param>
public virtual void InsertObjects(int index, 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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,550 @@
/*
* DesignSupport - Design time support for the various classes within ObjectListView
*
* Author: Phillip Piper
* Date: 12/08/2009 8:36 PM
*
* Change log:
* 2012-08-27 JPP - Fall back to more specific type name for the ListViewDesigner if
* the first GetType() fails.
* v2.5.1
* 2012-04-26 JPP - Filter group events from TreeListView since it can't have groups
* 2011-06-06 JPP - Vastly improved ObjectListViewDesigner, based off information in
* "'Inheriting' from an Internal WinForms Designer" on CodeProject.
* v2.3
* 2009-08-12 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@gmail.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace BrightIdeasSoftware.Design
{
/// <summary>
/// Designer for <see cref="ObjectListView"/> and its subclasses.
/// </summary>
/// <remarks>
/// <para>
/// This designer removes properties and events that are available on ListView but that are not
/// useful on ObjectListView.
/// </para>
/// <para>
/// We can't inherit from System.Windows.Forms.Design.ListViewDesigner, since it is marked internal.
/// So, this class uses reflection to create a ListViewDesigner and then forwards messages to that designer.
/// </para>
/// </remarks>
public class ObjectListViewDesigner : ControlDesigner
{
#region Initialize & Dispose
/// <summary>
/// Initializes the designer with the specified component.
/// </summary>
/// <param name="component">The <see cref="T:System.ComponentModel.IComponent"/> to associate the designer with. This component must always be an instance of, or derive from, <see cref="T:System.Windows.Forms.Control"/>. </param>
public override void Initialize(IComponent component) {
// Debug.WriteLine("ObjectListViewDesigner.Initialize");
// Use reflection to bypass the "internal" marker on ListViewDesigner
// If we can't get the unversioned designer, look specifically for .NET 4.0 version of it.
Type tListViewDesigner = Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design") ??
Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design, " +
"Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
if (tListViewDesigner == null) throw new ArgumentException("Could not load ListViewDesigner");
this.listViewDesigner = (ControlDesigner)Activator.CreateInstance(tListViewDesigner, BindingFlags.Instance | BindingFlags.Public, null, null, null);
this.designerFilter = this.listViewDesigner;
// Fetch the methods from the ListViewDesigner that we know we want to use
this.listViewDesignGetHitTest = tListViewDesigner.GetMethod("GetHitTest", BindingFlags.Instance | BindingFlags.NonPublic);
this.listViewDesignWndProc = tListViewDesigner.GetMethod("WndProc", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(this.listViewDesignGetHitTest != null, "Required method (GetHitTest) not found on ListViewDesigner");
Debug.Assert(this.listViewDesignWndProc != null, "Required method (WndProc) not found on ListViewDesigner");
// Tell the Designer to use properties of default designer as well as the properties of this class (do before base.Initialize)
TypeDescriptor.CreateAssociation(component, this.listViewDesigner);
IServiceContainer site = (IServiceContainer)component.Site;
if (site != null && GetService(typeof(DesignerCommandSet)) == null) {
site.AddService(typeof(DesignerCommandSet), new CDDesignerCommandSet(this));
} else {
Debug.Fail("site != null && GetService(typeof (DesignerCommandSet)) == null");
}
this.listViewDesigner.Initialize(component);
base.Initialize(component);
RemoveDuplicateDockingActionList();
}
/// <summary>
/// Initializes a newly created component.
/// </summary>
/// <param name="defaultValues">A name/value dictionary of default values to apply to properties. May be null if no default values are specified.</param>
public override void InitializeNewComponent(IDictionary defaultValues) {
// Debug.WriteLine("ObjectListViewDesigner.InitializeNewComponent");
base.InitializeNewComponent(defaultValues);
this.listViewDesigner.InitializeNewComponent(defaultValues);
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Design.ControlDesigner"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
protected override void Dispose(bool disposing) {
// Debug.WriteLine("ObjectListViewDesigner.Dispose");
if (disposing) {
if (this.listViewDesigner != null) {
this.listViewDesigner.Dispose();
// Normally we would now null out the designer, but this designer
// still has methods called AFTER it is disposed.
}
}
base.Dispose(disposing);
}
/// <summary>
/// Removes the duplicate DockingActionList added by this designer to the <see cref="DesignerActionService"/>.
/// </summary>
/// <remarks>
/// <see cref="ControlDesigner.Initialize"/> adds an internal DockingActionList : 'Dock/Undock in Parent Container'.
/// But the default designer has already added that action list. So we need to remove one.
/// </remarks>
private void RemoveDuplicateDockingActionList() {
// This is a true hack -- in a class that is basically a huge hack itself.
// Reach into the bowel of our base class, get a private field, and use that fields value to
// remove an action from the designer.
// In ControlDesigner, there is "private DockingActionList dockingAction;"
// Don't you just love Reflector?!
FieldInfo fi = typeof(ControlDesigner).GetField("dockingAction", BindingFlags.Instance | BindingFlags.NonPublic);
if (fi != null) {
DesignerActionList dockingAction = (DesignerActionList)fi.GetValue(this);
if (dockingAction != null) {
DesignerActionService service = (DesignerActionService)GetService(typeof(DesignerActionService));
if (service != null) {
service.Remove(this.Control, dockingAction);
}
}
}
}
#endregion
#region IDesignerFilter overrides
/// <summary>
/// Adjusts the set of properties the component exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="properties">An <see cref="T:System.Collections.IDictionary"/> containing the properties for the class of the component. </param>
protected override void PreFilterProperties(IDictionary properties) {
// Debug.WriteLine("ObjectListViewDesigner.PreFilterProperties");
// Always call the base PreFilterProperties implementation
// before you modify the properties collection.
base.PreFilterProperties(properties);
// Give the listviewdesigner a chance to filter the properties
// (though we already know it's not going to do anything)
this.designerFilter.PreFilterProperties(properties);
// I'd like to just remove the redundant properties, but that would
// break backward compatibility. The deserialiser that handles the XXX.Designer.cs file
// works off the designer, so even if the property exists in the class, the deserialiser will
// throw an error if the associated designer actually removes that property.
// So we shadow the unwanted properties, and give the replacement properties
// non-browsable attributes so that they are hidden from the user
List<string> unwantedProperties = new List<string>(new string[] {
"BackgroundImage", "BackgroundImageTiled", "HotTracking", "HoverSelection",
"LabelEdit", "VirtualListSize", "VirtualMode" });
// Also hid Tooltip properties, since giving a tooltip to the control through the IDE
// messes up the tooltip handling
foreach (string propertyName in properties.Keys) {
if (propertyName.StartsWith("ToolTip")) {
unwantedProperties.Add(propertyName);
}
}
// If we are looking at a TreeListView, remove group related properties
// since TreeListViews can't show groups
if (this.Control is TreeListView) {
unwantedProperties.AddRange(new string[] {
"GroupImageList", "GroupWithItemCountFormat", "GroupWithItemCountSingularFormat", "HasCollapsibleGroups",
"SpaceBetweenGroups", "ShowGroups", "SortGroupItemsByPrimaryColumn", "ShowItemCountOnGroups"
});
}
// Shadow the unwanted properties, and give the replacement properties
// non-browsable attributes so that they are hidden from the user
foreach (string unwantedProperty in unwantedProperties) {
PropertyDescriptor propertyDesc = TypeDescriptor.CreateProperty(
typeof(ObjectListView),
(PropertyDescriptor)properties[unwantedProperty],
new BrowsableAttribute(false));
properties[unwantedProperty] = propertyDesc;
}
}
/// <summary>
/// Allows a designer to add to the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="events">The events for the class of the component. </param>
protected override void PreFilterEvents(IDictionary events) {
// Debug.WriteLine("ObjectListViewDesigner.PreFilterEvents");
base.PreFilterEvents(events);
this.designerFilter.PreFilterEvents(events);
// Remove the events that don't make sense for an ObjectListView.
// See PreFilterProperties() for why we do this dance rather than just remove the event.
List<string> unwanted = new List<string>(new string[] {
"AfterLabelEdit",
"BeforeLabelEdit",
"DrawColumnHeader",
"DrawItem",
"DrawSubItem",
"RetrieveVirtualItem",
"SearchForVirtualItem",
"VirtualItemsSelectionRangeChanged"
});
// If we are looking at a TreeListView, remove group related events
// since TreeListViews can't show groups
if (this.Control is TreeListView) {
unwanted.AddRange(new string[] {
"AboutToCreateGroups",
"AfterCreatingGroups",
"BeforeCreatingGroups",
"GroupTaskClicked",
"GroupExpandingCollapsing",
"GroupStateChanged"
});
}
foreach (string unwantedEvent in unwanted) {
EventDescriptor eventDesc = TypeDescriptor.CreateEvent(
typeof(ObjectListView),
(EventDescriptor)events[unwantedEvent],
new BrowsableAttribute(false));
events[unwantedEvent] = eventDesc;
}
}
/// <summary>
/// Allows a designer to change or remove items from the set of attributes that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="attributes">The attributes for the class of the component. </param>
protected override void PostFilterAttributes(IDictionary attributes) {
// Debug.WriteLine("ObjectListViewDesigner.PostFilterAttributes");
this.designerFilter.PostFilterAttributes(attributes);
base.PostFilterAttributes(attributes);
}
/// <summary>
/// Allows a designer to change or remove items from the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="events">The events for the class of the component. </param>
protected override void PostFilterEvents(IDictionary events) {
// Debug.WriteLine("ObjectListViewDesigner.PostFilterEvents");
this.designerFilter.PostFilterEvents(events);
base.PostFilterEvents(events);
}
#endregion
#region Overrides
/// <summary>
/// Gets the design-time action lists supported by the component associated with the designer.
/// </summary>
/// <returns>
/// The design-time action lists supported by the component associated with the designer.
/// </returns>
public override DesignerActionListCollection ActionLists {
get {
// We want to change the first action list so it only has the commands we want
DesignerActionListCollection actionLists = this.listViewDesigner.ActionLists;
if (actionLists.Count > 0 && !(actionLists[0] is ListViewActionListAdapter)) {
actionLists[0] = new ListViewActionListAdapter(this, actionLists[0]);
}
return actionLists;
}
}
/// <summary>
/// Gets the collection of components associated with the component managed by the designer.
/// </summary>
/// <returns>
/// The components that are associated with the component managed by the designer.
/// </returns>
public override ICollection AssociatedComponents {
get {
ArrayList components = new ArrayList(base.AssociatedComponents);
components.AddRange(this.listViewDesigner.AssociatedComponents);
return components;
}
}
/// <summary>
/// Indicates whether a mouse click at the specified point should be handled by the control.
/// </summary>
/// <returns>
/// true if a click at the specified point is to be handled by the control; otherwise, false.
/// </returns>
/// <param name="point">A <see cref="T:System.Drawing.Point"/> indicating the position at which the mouse was clicked, in screen coordinates. </param>
protected override bool GetHitTest(Point point) {
// The ListViewDesigner wants to allow column dividers to be resized
return (bool)this.listViewDesignGetHitTest.Invoke(listViewDesigner, new object[] { point });
}
/// <summary>
/// Processes Windows messages and optionally routes them to the control.
/// </summary>
/// <param name="m">The <see cref="T:System.Windows.Forms.Message"/> to process. </param>
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case 0x4e:
case 0x204e:
// The listview designer is interested in HDN_ENDTRACK notifications
this.listViewDesignWndProc.Invoke(listViewDesigner, new object[] { m });
break;
default:
base.WndProc(ref m);
break;
}
}
#endregion
#region Implementation variables
private ControlDesigner listViewDesigner;
private IDesignerFilter designerFilter;
private MethodInfo listViewDesignGetHitTest;
private MethodInfo listViewDesignWndProc;
#endregion
#region Custom action list
/// <summary>
/// This class modifies a ListViewActionList, by removing the "Edit Items" and "Edit Groups" actions.
/// </summary>
/// <remarks>
/// <para>
/// That class is internal, so we cannot simply subclass it, which would be simplier.
/// </para>
/// <para>
/// Action lists use reflection to determine if that action can be executed, so we not
/// only have to modify the returned collection of actions, but we have to implement
/// the properties and commands that the returned actions use. </para>
/// </remarks>
private class ListViewActionListAdapter : DesignerActionList
{
public ListViewActionListAdapter(ObjectListViewDesigner designer, DesignerActionList wrappedList)
: base(wrappedList.Component) {
this.designer = designer;
this.wrappedList = wrappedList;
}
public override DesignerActionItemCollection GetSortedActionItems() {
DesignerActionItemCollection items = wrappedList.GetSortedActionItems();
items.RemoveAt(2); // remove Edit Groups
items.RemoveAt(0); // remove Edit Items
return items;
}
private void EditValue(ComponentDesigner componentDesigner, IComponent iComponent, string propertyName) {
// One more complication. The ListViewActionList classes uses an internal class, EditorServiceContext, to
// edit the items/columns/groups collections. So, we use reflection to bypass the data hiding.
Type tEditorServiceContext = Type.GetType("System.Windows.Forms.Design.EditorServiceContext, System.Design");
tEditorServiceContext.InvokeMember("EditValue", BindingFlags.InvokeMethod | BindingFlags.Static, null, null, new object[] { componentDesigner, iComponent, propertyName });
}
private void SetValue(object target, string propertyName, object value) {
TypeDescriptor.GetProperties(target)[propertyName].SetValue(target, value);
}
public void InvokeColumnsDialog() {
EditValue(this.designer, base.Component, "Columns");
}
// Don't need these since we removed their corresponding actions from the list.
// Keep the methods just in case.
//public void InvokeGroupsDialog() {
// EditValue(this.designer, base.Component, "Groups");
//}
//public void InvokeItemsDialog() {
// EditValue(this.designer, base.Component, "Items");
//}
public ImageList LargeImageList {
get { return ((ListView)base.Component).LargeImageList; }
set { SetValue(base.Component, "LargeImageList", value); }
}
public ImageList SmallImageList {
get { return ((ListView)base.Component).SmallImageList; }
set { SetValue(base.Component, "SmallImageList", value); }
}
public View View {
get { return ((ListView)base.Component).View; }
set { SetValue(base.Component, "View", value); }
}
ObjectListViewDesigner designer;
DesignerActionList wrappedList;
}
#endregion
#region DesignerCommandSet
private class CDDesignerCommandSet : DesignerCommandSet
{
public CDDesignerCommandSet(ComponentDesigner componentDesigner) {
this.componentDesigner = componentDesigner;
}
public override ICollection GetCommands(string name) {
// Debug.WriteLine("CDDesignerCommandSet.GetCommands:" + name);
if (componentDesigner != null) {
if (name.Equals("Verbs")) {
return componentDesigner.Verbs;
}
if (name.Equals("ActionLists")) {
return componentDesigner.ActionLists;
}
}
return base.GetCommands(name);
}
private readonly ComponentDesigner componentDesigner;
}
#endregion
}
/// <summary>
/// This class works in conjunction with the OLVColumns property to allow OLVColumns
/// to be added to the ObjectListView.
/// </summary>
public class OLVColumnCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
/// <summary>
/// Create a OLVColumnCollectionEditor
/// </summary>
/// <param name="t"></param>
public OLVColumnCollectionEditor(Type t)
: base(t) {
}
/// <summary>
/// What type of object does this editor create?
/// </summary>
/// <returns></returns>
protected override Type CreateCollectionItemType() {
return typeof(OLVColumn);
}
/// <summary>
/// Edit a given value
/// </summary>
/// <param name="context"></param>
/// <param name="provider"></param>
/// <param name="value"></param>
/// <returns></returns>
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
if (context == null)
throw new ArgumentNullException("context");
if (provider == null)
throw new ArgumentNullException("provider");
// Figure out which ObjectListView we are working on. This should be the Instance of the context.
ObjectListView olv = context.Instance as ObjectListView;
Debug.Assert(olv != null, "Instance must be an ObjectListView");
// Edit all the columns, not just the ones that are visible
base.EditValue(context, provider, olv.AllColumns);
// Set the columns on the ListView to just the visible columns
List<OLVColumn> newColumns = olv.GetFilteredColumns(View.Details);
olv.Columns.Clear();
olv.Columns.AddRange(newColumns.ToArray());
return olv.Columns;
}
/// <summary>
/// What text should be shown in the list for the given object?
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected override string GetDisplayText(object value) {
OLVColumn col = value as OLVColumn;
if (col == null || String.IsNullOrEmpty(col.AspectName))
return base.GetDisplayText(value);
return String.Format("{0} ({1})", base.GetDisplayText(value), col.AspectName);
}
}
/// <summary>
/// Control how the overlay is presented in the IDE
/// </summary>
internal class OverlayConverter : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) {
if (destinationType == typeof(string)) {
ImageOverlay imageOverlay = value as ImageOverlay;
if (imageOverlay != null) {
return imageOverlay.Image == null ? "(none)" : "(set)";
}
TextOverlay textOverlay = value as TextOverlay;
if (textOverlay != null) {
return String.IsNullOrEmpty(textOverlay.Text) ? "(none)" : "(set)";
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
<project schemaVersion="1.6.0.2">
<assemblies>
<assembly assemblyPath=".\bin\Debug\ObjectListViewDemo.exe" xmlCommentsPath=".\bin\Debug\ObjectListViewDemo.XML" commentsOnly="False" />
</assemblies>
<namespaceSummaries>
<namespaceSummaryItem name="" isDocumented="False" />
<namespaceSummaryItem name="BrightIdeasSoftware" isDocumented="True">All ObjectListView appears in this namespace</namespaceSummaryItem>
<namespaceSummaryItem name="ObjectListViewDemo" isDocumented="False" />
</namespaceSummaries>
<ProjectSummary>ObjectListViewDemo demonstrates helpful techniques when using an ObjectListView</ProjectSummary>
<MissingTags>Summary, Parameter, Returns, AutoDocumentCtors, Namespace</MissingTags>
<VisibleItems>InheritedMembers, Protected, SealedProtected</VisibleItems>
<HtmlHelp1xCompilerPath path="" />
<HtmlHelp2xCompilerPath path="" />
<OutputPath>.\Help\</OutputPath>
<SandcastlePath path="" />
<WorkingPath path="" />
<CleanIntermediates>True</CleanIntermediates>
<KeepLogFile>True</KeepLogFile>
<HelpFileFormat>HtmlHelp1x</HelpFileFormat>
<PurgeDuplicateTopics>True</PurgeDuplicateTopics>
<CppCommentsFixup>False</CppCommentsFixup>
<FrameworkVersion>2.0.50727</FrameworkVersion>
<BinaryTOC>True</BinaryTOC>
<IncludeFavorites>False</IncludeFavorites>
<Preliminary>True</Preliminary>
<RootNamespaceContainer>False</RootNamespaceContainer>
<RootNamespaceTitle />
<HelpTitle>ObjectListView Reference</HelpTitle>
<HtmlHelpName>Documentation</HtmlHelpName>
<Language>en-US</Language>
<CopyrightHref />
<CopyrightText>(c) Copyright 2006-2008 Phillip Piper All Rights Reserved</CopyrightText>
<FeedbackEMailAddress>phillip.piper@gmail.com</FeedbackEMailAddress>
<HeaderText />
<FooterText />
<ProjectLinkType>Local</ProjectLinkType>
<SdkLinkType>Msdn</SdkLinkType>
<SdkLinkTarget>Blank</SdkLinkTarget>
<PresentationStyle>Prototype</PresentationStyle>
<NamingMethod>Guid</NamingMethod>
<SyntaxFilters>CSharp</SyntaxFilters>
<ShowFeedbackControl>False</ShowFeedbackControl>
<ContentPlacement>AboveNamespaces</ContentPlacement>
<ContentSiteMap path="" />
<TopicFileTransform path="" />
</project>

View File

@ -0,0 +1,182 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{18FEDA0C-D147-4286-B39A-01204808106A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BrightIdeasSoftware</RootNamespace>
<AssemblyName>ObjectListView</AssemblyName>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>olv-keyfile.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Accessibility" />
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DataTreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="DragDrop\OLVDataObject.cs" />
<Compile Include="Filtering\ClustersFromGroupsStrategy.cs" />
<Compile Include="Filtering\DateTimeClusteringStrategy.cs" />
<Compile Include="Filtering\FlagClusteringStrategy.cs" />
<Compile Include="Filtering\TextMatchFilter.cs" />
<Compile Include="Implementation\Delegates.cs" />
<Compile Include="Implementation\Enums.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\GroupingParameters.cs" />
<Compile Include="Implementation\NullableDictionary.cs" />
<Compile Include="Implementation\OLVListItem.cs" />
<Compile Include="Implementation\OLVListSubItem.cs" />
<Compile Include="Implementation\OlvListViewHitTestInfo.cs" />
<Compile Include="Implementation\TreeDataSourceAdapter.cs" />
<Compile Include="OLVColumn.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Rendering\Adornments.cs" />
<Compile Include="Implementation\Attributes.cs" />
<Compile Include="CellEditing\CellEditors.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="CellEditing\EditorRegistry.cs" />
<Compile Include="DataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\DataSourceAdapter.cs" />
<Compile Include="Rendering\Decorations.cs" />
<Compile Include="DragDrop\DragSource.cs" />
<Compile Include="DragDrop\DropSink.cs" />
<Compile Include="Implementation\Events.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="FastDataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\ClusteringStrategy.cs" />
<Compile Include="Filtering\Cluster.cs" />
<Compile Include="FastObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\FilterMenuBuilder.cs" />
<Compile Include="Filtering\Filters.cs" />
<Compile Include="Filtering\ICluster.cs" />
<Compile Include="Filtering\IClusteringStrategy.cs" />
<Compile Include="Rendering\TreeRenderer.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolStripCheckedListBox.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.Designer.cs">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
</Compile>
<Compile Include="Utilities\Generator.cs" />
<Compile Include="SubControls\GlassPanelForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Implementation\Groups.cs" />
<Compile Include="SubControls\HeaderControl.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="CellEditing\CellEditKeyEngine.cs" />
<Compile Include="Implementation\Munger.cs" />
<Compile Include="Implementation\NativeMethods.cs" />
<Compile Include="Implementation\Comparers.cs">
</Compile>
<Compile Include="ObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ObjectListView.DesignTime.cs" />
<Compile Include="Rendering\Overlays.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rendering\Renderers.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Rendering\Styles.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolTipControl.cs" />
<Compile Include="TreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\OLVExporter.cs" />
<Compile Include="Utilities\TypedObjectListView.cs" />
<Compile Include="Implementation\VirtualGroups.cs" />
<Compile Include="Implementation\VirtualListDataSource.cs" />
<Compile Include="VirtualObjectListView.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Include="CustomDictionary.xml" />
<None Include="FullClassDiagram.cd" />
<None Include="ObjectListView2012.nuspec">
<SubType>Designer</SubType>
</None>
<None Include="Resources\sort-descending.png" />
<None Include="Resources\sort-ascending.png" />
<None Include="Resources\filter.png" />
<None Include="Resources\clear-filter.png" />
<None Include="Resources\filter-icons3.png" />
</ItemGroup>
<ItemGroup>
<None Include="olv-keyfile.snk" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Utilities\ColumnSelectionForm.resx">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,188 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{18FEDA0C-D147-4286-B39A-01204808106A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BrightIdeasSoftware</RootNamespace>
<AssemblyName>ObjectListView</AssemblyName>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>2.0</OldToolsVersion>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>olv-keyfile.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Accessibility" />
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DataTreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="DragDrop\OLVDataObject.cs" />
<Compile Include="Filtering\ClustersFromGroupsStrategy.cs" />
<Compile Include="Filtering\DateTimeClusteringStrategy.cs" />
<Compile Include="Filtering\FlagClusteringStrategy.cs" />
<Compile Include="Filtering\TextMatchFilter.cs" />
<Compile Include="Implementation\Delegates.cs" />
<Compile Include="Implementation\Enums.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\GroupingParameters.cs" />
<Compile Include="Implementation\NullableDictionary.cs" />
<Compile Include="Implementation\OLVListItem.cs" />
<Compile Include="Implementation\OLVListSubItem.cs" />
<Compile Include="Implementation\OlvListViewHitTestInfo.cs" />
<Compile Include="Implementation\TreeDataSourceAdapter.cs" />
<Compile Include="OLVColumn.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Rendering\Adornments.cs" />
<Compile Include="Implementation\Attributes.cs" />
<Compile Include="CellEditing\CellEditors.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="CellEditing\EditorRegistry.cs" />
<Compile Include="DataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\DataSourceAdapter.cs" />
<Compile Include="Rendering\Decorations.cs" />
<Compile Include="DragDrop\DragSource.cs" />
<Compile Include="DragDrop\DropSink.cs" />
<Compile Include="Implementation\Events.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="FastDataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\ClusteringStrategy.cs" />
<Compile Include="Filtering\Cluster.cs" />
<Compile Include="FastObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\FilterMenuBuilder.cs" />
<Compile Include="Filtering\Filters.cs" />
<Compile Include="Filtering\ICluster.cs" />
<Compile Include="Filtering\IClusteringStrategy.cs" />
<Compile Include="Rendering\TreeRenderer.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolStripCheckedListBox.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.Designer.cs">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
</Compile>
<Compile Include="Utilities\Generator.cs" />
<Compile Include="SubControls\GlassPanelForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Implementation\Groups.cs" />
<Compile Include="SubControls\HeaderControl.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="CellEditing\CellEditKeyEngine.cs" />
<Compile Include="Implementation\Munger.cs" />
<Compile Include="Implementation\NativeMethods.cs" />
<Compile Include="Implementation\Comparers.cs">
</Compile>
<Compile Include="ObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ObjectListView.DesignTime.cs" />
<Compile Include="Rendering\Overlays.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rendering\Renderers.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Rendering\Styles.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolTipControl.cs" />
<Compile Include="TreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\OLVExporter.cs" />
<Compile Include="Utilities\TypedObjectListView.cs" />
<Compile Include="Implementation\VirtualGroups.cs" />
<Compile Include="Implementation\VirtualListDataSource.cs" />
<Compile Include="VirtualObjectListView.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Include="CustomDictionary.xml" />
<None Include="Resources\sort-descending.png" />
<None Include="Resources\sort-ascending.png" />
<None Include="Resources\filter.png" />
<None Include="Resources\clear-filter.png" />
<None Include="Resources\filter-icons3.png" />
</ItemGroup>
<ItemGroup>
<None Include="olv-keyfile.snk" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Utilities\ColumnSelectionForm.resx">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,16 @@
<ProjectConfiguration>
<CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
<ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
<RunPreBuildEvents>false</RunPreBuildEvents>
<RunPostBuildEvents>false</RunPostBuildEvents>
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
<InstrumentAssembly>true</InstrumentAssembly>
<PreventSigningOfAssembly>false</PreventSigningOfAssembly>
<AnalyseExecutionTimes>true</AnalyseExecutionTimes>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration />
<ProxyProcessPath />
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
</ProjectConfiguration>

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{18FEDA0C-D147-4286-B39A-01204808106A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BrightIdeasSoftware</RootNamespace>
<AssemblyName>ObjectListView</AssemblyName>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>olv-keyfile.snk</AssemblyOriginatorKeyFile>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>bin\Debug\ObjectListView.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Accessibility" />
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DataTreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="DragDrop\OLVDataObject.cs" />
<Compile Include="Filtering\DateTimeClusteringStrategy.cs" />
<Compile Include="Filtering\FlagClusteringStrategy.cs" />
<Compile Include="Filtering\TextMatchFilter.cs" />
<Compile Include="Implementation\Delegates.cs" />
<Compile Include="Implementation\Enums.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\GroupingParameters.cs" />
<Compile Include="Implementation\NullableDictionary.cs" />
<Compile Include="Implementation\OLVListItem.cs" />
<Compile Include="Implementation\OLVListSubItem.cs" />
<Compile Include="Implementation\OlvListViewHitTestInfo.cs" />
<Compile Include="Implementation\TreeDataSourceAdapter.cs" />
<Compile Include="OLVColumn.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Rendering\Adornments.cs" />
<Compile Include="Implementation\Attributes.cs" />
<Compile Include="CellEditing\CellEditors.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="CellEditing\EditorRegistry.cs" />
<Compile Include="DataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\DataSourceAdapter.cs" />
<Compile Include="Rendering\Decorations.cs" />
<Compile Include="DragDrop\DragSource.cs" />
<Compile Include="DragDrop\DropSink.cs" />
<Compile Include="Implementation\Events.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="FastDataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\ClusteringStrategy.cs" />
<Compile Include="Filtering\Cluster.cs" />
<Compile Include="Filtering\ClustersFromGroupsStrategy.cs" />
<Compile Include="FastObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\FilterMenuBuilder.cs" />
<Compile Include="Filtering\Filters.cs" />
<Compile Include="Filtering\ICluster.cs" />
<Compile Include="Filtering\IClusteringStrategy.cs" />
<Compile Include="Rendering\TreeRenderer.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolStripCheckedListBox.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.Designer.cs">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
</Compile>
<Compile Include="Utilities\Generator.cs" />
<Compile Include="SubControls\GlassPanelForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Implementation\Groups.cs" />
<Compile Include="SubControls\HeaderControl.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="CellEditing\CellEditKeyEngine.cs" />
<Compile Include="Implementation\Munger.cs" />
<Compile Include="Implementation\NativeMethods.cs" />
<Compile Include="Implementation\Comparers.cs">
</Compile>
<Compile Include="ObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ObjectListView.DesignTime.cs" />
<Compile Include="Rendering\Overlays.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rendering\Renderers.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Rendering\Styles.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolTipControl.cs" />
<Compile Include="TreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\OLVExporter.cs" />
<Compile Include="Utilities\TypedObjectListView.cs" />
<Compile Include="Implementation\VirtualGroups.cs" />
<Compile Include="Implementation\VirtualListDataSource.cs" />
<Compile Include="VirtualObjectListView.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Include="CustomDictionary.xml" />
<None Include="FullClassDiagram.cd" />
<None Include="Resources\sort-descending.png" />
<None Include="Resources\sort-ascending.png" />
<None Include="Resources\filter.png" />
<None Include="Resources\clear-filter.png" />
<None Include="Resources\filter-icons3.png" />
</ItemGroup>
<ItemGroup>
<None Include="olv-keyfile.snk" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Utilities\ColumnSelectionForm.resx">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,27 @@
<ProjectConfiguration>
<CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
<ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
<PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
<RunPreBuildEvents>false</RunPreBuildEvents>
<RunPostBuildEvents>false</RunPostBuildEvents>
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
<InstrumentAssembly>true</InstrumentAssembly>
<PreventSigningOfAssembly>false</PreventSigningOfAssembly>
<AnalyseExecutionTimes>true</AnalyseExecutionTimes>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration></UseBuildConfiguration>
<UseBuildPlatform />
<ProxyProcessPath></ProxyProcessPath>
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture>
<IgnoredTests>
<RegexTestSelector>
<RegularExpression>.*</RegularExpression>
</RegexTestSelector>
</IgnoredTests>
</ProjectConfiguration>

View File

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{18FEDA0C-D147-4286-B39A-01204808106A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BrightIdeasSoftware</RootNamespace>
<AssemblyName>ObjectListView</AssemblyName>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>olv-keyfile.snk</AssemblyOriginatorKeyFile>
<TargetFrameworkProfile />
<SccProjectName>%24/ObjectListView/trunk/ObjectListView</SccProjectName>
<SccLocalPath>.</SccLocalPath>
<SccAuxPath>https://grammarian.visualstudio.com/defaultcollection</SccAuxPath>
<SccProvider>{4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}</SccProvider>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>1</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>bin\Debug\ObjectListView.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>bin\Release\ObjectListView.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Accessibility" />
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Design" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DataTreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="DragDrop\OLVDataObject.cs" />
<Compile Include="Filtering\DateTimeClusteringStrategy.cs" />
<Compile Include="Filtering\FlagClusteringStrategy.cs" />
<Compile Include="Filtering\TextMatchFilter.cs" />
<Compile Include="Implementation\Delegates.cs" />
<Compile Include="Implementation\Enums.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\GroupingParameters.cs" />
<Compile Include="Implementation\NullableDictionary.cs" />
<Compile Include="Implementation\OLVListItem.cs" />
<Compile Include="Implementation\OLVListSubItem.cs" />
<Compile Include="Implementation\OlvListViewHitTestInfo.cs" />
<Compile Include="Implementation\TreeDataSourceAdapter.cs" />
<Compile Include="OLVColumn.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Rendering\Adornments.cs" />
<Compile Include="Implementation\Attributes.cs" />
<Compile Include="CellEditing\CellEditors.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="CellEditing\EditorRegistry.cs" />
<Compile Include="DataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Implementation\DataSourceAdapter.cs" />
<Compile Include="Rendering\Decorations.cs" />
<Compile Include="DragDrop\DragSource.cs" />
<Compile Include="DragDrop\DropSink.cs" />
<Compile Include="Implementation\Events.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="FastDataListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\ClusteringStrategy.cs" />
<Compile Include="Filtering\Cluster.cs" />
<Compile Include="Filtering\ClustersFromGroupsStrategy.cs" />
<Compile Include="FastObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Filtering\FilterMenuBuilder.cs" />
<Compile Include="Filtering\Filters.cs" />
<Compile Include="Filtering\ICluster.cs" />
<Compile Include="Filtering\IClusteringStrategy.cs" />
<Compile Include="Rendering\TreeRenderer.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolStripCheckedListBox.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Utilities\ColumnSelectionForm.Designer.cs">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
</Compile>
<Compile Include="Utilities\Generator.cs" />
<Compile Include="SubControls\GlassPanelForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Implementation\Groups.cs" />
<Compile Include="SubControls\HeaderControl.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="CellEditing\CellEditKeyEngine.cs" />
<Compile Include="Implementation\Munger.cs" />
<Compile Include="Implementation\NativeMethods.cs" />
<Compile Include="Implementation\Comparers.cs">
</Compile>
<Compile Include="ObjectListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ObjectListView.DesignTime.cs" />
<Compile Include="Rendering\Overlays.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rendering\Renderers.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Rendering\Styles.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="SubControls\ToolTipControl.cs" />
<Compile Include="TreeListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Utilities\OLVExporter.cs" />
<Compile Include="Utilities\TypedObjectListView.cs" />
<Compile Include="Implementation\VirtualGroups.cs" />
<Compile Include="Implementation\VirtualListDataSource.cs" />
<Compile Include="VirtualObjectListView.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="CustomDictionary.xml" />
<None Include="FullClassDiagram.cd" />
<None Include="ObjectListView2012.nuspec">
<SubType>Designer</SubType>
</None>
<None Include="Resources\sort-descending.png" />
<None Include="Resources\sort-ascending.png" />
<None Include="Resources\filter.png" />
<None Include="Resources\clear-filter.png" />
<None Include="Resources\filter-icons3.png" />
</ItemGroup>
<ItemGroup>
<None Include="olv-keyfile.snk" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Utilities\ColumnSelectionForm.resx">
<DependentUpon>ColumnSelectionForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,22 @@
<ProjectConfiguration>
<CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
<ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
<PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
<RunPreBuildEvents>false</RunPreBuildEvents>
<RunPostBuildEvents>false</RunPostBuildEvents>
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
<InstrumentAssembly>true</InstrumentAssembly>
<PreventSigningOfAssembly>false</PreventSigningOfAssembly>
<AnalyseExecutionTimes>true</AnalyseExecutionTimes>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration />
<UseBuildPlatform />
<ProxyProcessPath />
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture>
</ProjectConfiguration>

View File

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>ObjectListView.Official</id>
<title>ObjectListView (Official)</title>
<version>2.9.1</version>
<authors>Phillip Piper</authors>
<owners>Phillip Piper</owners>
<licenseUrl>http://www.gnu.org/licenses/gpl.html</licenseUrl>
<projectUrl>http://objectlistview.sourceforge.net</projectUrl>
<iconUrl>http://objectlistview.sourceforge.net/cs/_static/index-icon.png</iconUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<summary>ObjectListView is a .NET ListView wired on caffeine, guarana and steroids.</summary>
<description>ObjectListView is a .NET ListView wired on caffeine, guarana and steroids.
More calmly, it is a C# wrapper around a .NET ListView, which makes the ListView much easier to use and teaches it lots of neat new tricks.</description>
<releaseNotes>v2.9.1 Added CellRendererGetter to allow each cell to have a different renderer, plus Fixes a few small bugs.
v2.9 adds buttons to cells, fixed some formatting bugs, and completely rewrote the demo to be much easier to understand.</releaseNotes>
<copyright>Copyright 2006-2016 Bright Ideas Software</copyright>
<tags>.Net WinForms Net20 Net40 ListView Controls</tags>
</metadata>
</package>

View File

@ -0,0 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToAutoProperty/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=FieldCanBeMadeReadOnly_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantThisQualifier/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestUseVarKeywordEverywhere/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestUseVarKeywordEvident/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String></wpf:ResourceDictionary>

View File

@ -0,0 +1,22 @@
<ProjectConfiguration>
<CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
<ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
<PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
<RunPreBuildEvents>false</RunPreBuildEvents>
<RunPostBuildEvents>false</RunPostBuildEvents>
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
<InstrumentAssembly>true</InstrumentAssembly>
<PreventSigningOfAssembly>false</PreventSigningOfAssembly>
<AnalyseExecutionTimes>true</AnalyseExecutionTimes>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration />
<UseBuildPlatform />
<ProxyProcessPath />
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture>
</ProjectConfiguration>

View File

@ -0,0 +1,743 @@
/*
* Adornments - Adornments are the basis for overlays and decorations -- things that can be rendered over the top of a ListView
*
* Author: Phillip Piper
* Date: 16/08/2009 1:02 AM
*
* Change log:
* v2.6
* 2012-08-18 JPP - Correctly dispose of brush and pen resources
* v2.3
* 2009-09-22 JPP - Added Wrap property to TextAdornment, to allow text wrapping to be disabled
* - Added ShrinkToWidth property to ImageAdornment
* 2009-08-17 JPP - Initial version
*
* To do:
* - Use IPointLocator rather than Corners
* - Add RotationCenter property ratherr than always using middle center
*
* 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@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
namespace BrightIdeasSoftware
{
/// <summary>
/// An adorment is the common base for overlays and decorations.
/// </summary>
public class GraphicAdornment
{
#region Public properties
/// <summary>
/// Gets or sets the corner of the adornment that will be positioned at the reference corner
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public System.Drawing.ContentAlignment AdornmentCorner {
get { return this.adornmentCorner; }
set { this.adornmentCorner = value; }
}
private System.Drawing.ContentAlignment adornmentCorner = System.Drawing.ContentAlignment.MiddleCenter;
/// <summary>
/// Gets or sets location within the reference rectange where the adornment will be drawn
/// </summary>
/// <remarks>This is a simplied interface to ReferenceCorner and AdornmentCorner </remarks>
[Category("ObjectListView"),
Description("How will the adornment be aligned"),
DefaultValue(System.Drawing.ContentAlignment.BottomRight),
NotifyParentProperty(true)]
public System.Drawing.ContentAlignment Alignment {
get { return this.alignment; }
set {
this.alignment = value;
this.ReferenceCorner = value;
this.AdornmentCorner = value;
}
}
private System.Drawing.ContentAlignment alignment = System.Drawing.ContentAlignment.BottomRight;
/// <summary>
/// Gets or sets the offset by which the position of the adornment will be adjusted
/// </summary>
[Category("ObjectListView"),
Description("The offset by which the position of the adornment will be adjusted"),
DefaultValue(typeof(Size), "0,0")]
public Size Offset {
get { return this.offset; }
set { this.offset = value; }
}
private Size offset = new Size();
/// <summary>
/// Gets or sets the point of the reference rectangle to which the adornment will be aligned.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public System.Drawing.ContentAlignment ReferenceCorner {
get { return this.referenceCorner; }
set { this.referenceCorner = value; }
}
private System.Drawing.ContentAlignment referenceCorner = System.Drawing.ContentAlignment.MiddleCenter;
/// <summary>
/// Gets or sets the degree of rotation by which the adornment will be transformed.
/// The centre of rotation will be the center point of the adornment.
/// </summary>
[Category("ObjectListView"),
Description("The degree of rotation that will be applied to the adornment."),
DefaultValue(0),
NotifyParentProperty(true)]
public int Rotation {
get { return this.rotation; }
set { this.rotation = value; }
}
private int rotation;
/// <summary>
/// Gets or sets the transparency of the overlay.
/// 0 is completely transparent, 255 is completely opaque.
/// </summary>
[Category("ObjectListView"),
Description("The transparency of this adornment. 0 is completely transparent, 255 is completely opaque."),
DefaultValue(128)]
public int Transparency {
get { return this.transparency; }
set { this.transparency = Math.Min(255, Math.Max(0, value)); }
}
private int transparency = 128;
#endregion
#region Calculations
/// <summary>
/// Calculate the location of rectangle of the given size,
/// so that it's indicated corner would be at the given point.
/// </summary>
/// <param name="pt">The point</param>
/// <param name="size"></param>
/// <param name="corner">Which corner will be positioned at the reference point</param>
/// <returns></returns>
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.TopLeft) -> Point(50, 100)</example>
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.MiddleCenter) -> Point(45, 90)</example>
/// <example>CalculateAlignedPosition(new Point(50, 100), new Size(10, 20), System.Drawing.ContentAlignment.BottomRight) -> Point(40, 80)</example>
public virtual Point CalculateAlignedPosition(Point pt, Size size, System.Drawing.ContentAlignment corner) {
switch (corner) {
case System.Drawing.ContentAlignment.TopLeft:
return pt;
case System.Drawing.ContentAlignment.TopCenter:
return new Point(pt.X - (size.Width / 2), pt.Y);
case System.Drawing.ContentAlignment.TopRight:
return new Point(pt.X - size.Width, pt.Y);
case System.Drawing.ContentAlignment.MiddleLeft:
return new Point(pt.X, pt.Y - (size.Height / 2));
case System.Drawing.ContentAlignment.MiddleCenter:
return new Point(pt.X - (size.Width / 2), pt.Y - (size.Height / 2));
case System.Drawing.ContentAlignment.MiddleRight:
return new Point(pt.X - size.Width, pt.Y - (size.Height / 2));
case System.Drawing.ContentAlignment.BottomLeft:
return new Point(pt.X, pt.Y - size.Height);
case System.Drawing.ContentAlignment.BottomCenter:
return new Point(pt.X - (size.Width / 2), pt.Y - size.Height);
case System.Drawing.ContentAlignment.BottomRight:
return new Point(pt.X - size.Width, pt.Y - size.Height);
}
// Should never reach here
return pt;
}
/// <summary>
/// Calculate a rectangle that has the given size which is positioned so that
/// its alignment point is at the reference location of the given rect.
/// </summary>
/// <param name="r"></param>
/// <param name="sz"></param>
/// <returns></returns>
public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz) {
return this.CreateAlignedRectangle(r, sz, this.ReferenceCorner, this.AdornmentCorner, this.Offset);
}
/// <summary>
/// Create a rectangle of the given size which is positioned so that
/// its indicated corner is at the indicated corner of the reference rect.
/// </summary>
/// <param name="r"></param>
/// <param name="sz"></param>
/// <param name="corner"></param>
/// <param name="referenceCorner"></param>
/// <param name="offset"></param>
/// <returns></returns>
/// <remarks>
/// <para>Creates a rectangle so that its bottom left is at the centre of the reference:
/// corner=BottomLeft, referenceCorner=MiddleCenter</para>
/// <para>This is a powerful concept that takes some getting used to, but is
/// very neat once you understand it.</para>
/// </remarks>
public virtual Rectangle CreateAlignedRectangle(Rectangle r, Size sz,
System.Drawing.ContentAlignment corner, System.Drawing.ContentAlignment referenceCorner, Size offset) {
Point referencePt = this.CalculateCorner(r, referenceCorner);
Point topLeft = this.CalculateAlignedPosition(referencePt, sz, corner);
return new Rectangle(topLeft + offset, sz);
}
/// <summary>
/// Return the point at the indicated corner of the given rectangle (it doesn't
/// have to be a corner, but a named location)
/// </summary>
/// <param name="r">The reference rectangle</param>
/// <param name="corner">Which point of the rectangle should be returned?</param>
/// <returns>A point</returns>
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.TopLeft) -> Point(0, 0)</example>
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.MiddleCenter) -> Point(25, 50)</example>
/// <example>CalculateReferenceLocation(new Rectangle(0, 0, 50, 100), System.Drawing.ContentAlignment.BottomRight) -> Point(50, 100)</example>
public virtual Point CalculateCorner(Rectangle r, System.Drawing.ContentAlignment corner) {
switch (corner) {
case System.Drawing.ContentAlignment.TopLeft:
return new Point(r.Left, r.Top);
case System.Drawing.ContentAlignment.TopCenter:
return new Point(r.X + (r.Width / 2), r.Top);
case System.Drawing.ContentAlignment.TopRight:
return new Point(r.Right, r.Top);
case System.Drawing.ContentAlignment.MiddleLeft:
return new Point(r.Left, r.Top + (r.Height / 2));
case System.Drawing.ContentAlignment.MiddleCenter:
return new Point(r.X + (r.Width / 2), r.Top + (r.Height / 2));
case System.Drawing.ContentAlignment.MiddleRight:
return new Point(r.Right, r.Top + (r.Height / 2));
case System.Drawing.ContentAlignment.BottomLeft:
return new Point(r.Left, r.Bottom);
case System.Drawing.ContentAlignment.BottomCenter:
return new Point(r.X + (r.Width / 2), r.Bottom);
case System.Drawing.ContentAlignment.BottomRight:
return new Point(r.Right, r.Bottom);
}
// Should never reach here
return r.Location;
}
/// <summary>
/// Given the item and the subitem, calculate its bounds.
/// </summary>
/// <param name="item"></param>
/// <param name="subItem"></param>
/// <returns></returns>
public virtual Rectangle CalculateItemBounds(OLVListItem item, OLVListSubItem subItem) {
if (item == null)
return Rectangle.Empty;
if (subItem == null)
return item.Bounds;
return item.GetSubItemBounds(item.SubItems.IndexOf(subItem));
}
#endregion
#region Commands
/// <summary>
/// Apply any specified rotation to the Graphic content.
/// </summary>
/// <param name="g">The Graphics to be transformed</param>
/// <param name="r">The rotation will be around the centre of this rect</param>
protected virtual void ApplyRotation(Graphics g, Rectangle r) {
if (this.Rotation == 0)
return;
// THINK: Do we want to reset the transform? I think we want to push a new transform
g.ResetTransform();
Matrix m = new Matrix();
m.RotateAt(this.Rotation, new Point(r.Left + r.Width / 2, r.Top + r.Height / 2));
g.Transform = m;
}
/// <summary>
/// Reverse the rotation created by ApplyRotation()
/// </summary>
/// <param name="g"></param>
protected virtual void UnapplyRotation(Graphics g) {
if (this.Rotation != 0)
g.ResetTransform();
}
#endregion
}
/// <summary>
/// An overlay that will draw an image over the top of the ObjectListView
/// </summary>
public class ImageAdornment : GraphicAdornment
{
#region Public properties
/// <summary>
/// Gets or sets the image that will be drawn
/// </summary>
[Category("ObjectListView"),
Description("The image that will be drawn"),
DefaultValue(null),
NotifyParentProperty(true)]
public Image Image {
get { return this.image; }
set { this.image = value; }
}
private Image image;
/// <summary>
/// Gets or sets if the image will be shrunk to fit with its horizontal bounds
/// </summary>
[Category("ObjectListView"),
Description("Will the image be shrunk to fit within its width?"),
DefaultValue(false)]
public bool ShrinkToWidth {
get { return this.shrinkToWidth; }
set { this.shrinkToWidth = value; }
}
private bool shrinkToWidth;
#endregion
#region Commands
/// <summary>
/// Draw the image in its specified location
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
public virtual void DrawImage(Graphics g, Rectangle r) {
if (this.ShrinkToWidth)
this.DrawScaledImage(g, r, this.Image, this.Transparency);
else
this.DrawImage(g, r, this.Image, this.Transparency);
}
/// <summary>
/// Draw the image in its specified location
/// </summary>
/// <param name="image">The image to be drawn</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
public virtual void DrawImage(Graphics g, Rectangle r, Image image, int transparency) {
if (image != null)
this.DrawImage(g, r, image, image.Size, transparency);
}
/// <summary>
/// Draw the image in its specified location
/// </summary>
/// <param name="image">The image to be drawn</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
/// <param name="sz">How big should the image be?</param>
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
public virtual void DrawImage(Graphics g, Rectangle r, Image image, Size sz, int transparency) {
if (image == null)
return;
Rectangle adornmentBounds = this.CreateAlignedRectangle(r, sz);
try {
this.ApplyRotation(g, adornmentBounds);
this.DrawTransparentBitmap(g, adornmentBounds, image, transparency);
}
finally {
this.UnapplyRotation(g);
}
}
/// <summary>
/// Draw the image in its specified location, scaled so that it is not wider
/// than the given rectangle. Height is scaled proportional to the width.
/// </summary>
/// <param name="image">The image to be drawn</param>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The bounds of the rendering</param>
/// <param name="transparency">How transparent should the image be (0 is completely transparent, 255 is opaque)</param>
public virtual void DrawScaledImage(Graphics g, Rectangle r, Image image, int transparency) {
if (image == null)
return;
// If the image is too wide to be drawn in the space provided, proportionally scale it down.
// Too tall images are not scaled.
Size size = image.Size;
if (image.Width > r.Width) {
float scaleRatio = (float)r.Width / (float)image.Width;
size.Height = (int)((float)image.Height * scaleRatio);
size.Width = r.Width - 1;
}
this.DrawImage(g, r, image, size, transparency);
}
/// <summary>
/// Utility to draw a bitmap transparenly.
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="image"></param>
/// <param name="transparency"></param>
protected virtual void DrawTransparentBitmap(Graphics g, Rectangle r, Image image, int transparency) {
ImageAttributes imageAttributes = null;
if (transparency != 255) {
imageAttributes = new ImageAttributes();
float a = (float)transparency / 255.0f;
float[][] colorMatrixElements = {
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, a, 0},
new float[] {0, 0, 0, 0, 1}};
imageAttributes.SetColorMatrix(new ColorMatrix(colorMatrixElements));
}
g.DrawImage(image,
r, // destination rectangle
0, 0, image.Size.Width, image.Size.Height, // source rectangle
GraphicsUnit.Pixel,
imageAttributes);
}
#endregion
}
/// <summary>
/// An adornment that will draw text
/// </summary>
public class TextAdornment : GraphicAdornment
{
#region Public properties
/// <summary>
/// Gets or sets the background color of the text
/// Set this to Color.Empty to not draw a background
/// </summary>
[Category("ObjectListView"),
Description("The background color of the text"),
DefaultValue(typeof(Color), "")]
public Color BackColor {
get { return this.backColor; }
set { this.backColor = value; }
}
private Color backColor = Color.Empty;
/// <summary>
/// Gets the brush that will be used to paint the text
/// </summary>
[Browsable(false)]
public Brush BackgroundBrush {
get {
return new SolidBrush(Color.FromArgb(this.workingTransparency, this.BackColor));
}
}
/// <summary>
/// Gets or sets the color of the border around the billboard.
/// Set this to Color.Empty to remove the border
/// </summary>
[Category("ObjectListView"),
Description("The color of the border around the text"),
DefaultValue(typeof(Color), "")]
public Color BorderColor {
get { return this.borderColor; }
set { this.borderColor = value; }
}
private Color borderColor = Color.Empty;
/// <summary>
/// Gets the brush that will be used to paint the text
/// </summary>
[Browsable(false)]
public Pen BorderPen {
get {
return new Pen(Color.FromArgb(this.workingTransparency, this.BorderColor), this.BorderWidth);
}
}
/// <summary>
/// Gets or sets the width of the border around the text
/// </summary>
[Category("ObjectListView"),
Description("The width of the border around the text"),
DefaultValue(0.0f)]
public float BorderWidth {
get { return this.borderWidth; }
set { this.borderWidth = value; }
}
private float borderWidth;
/// <summary>
/// How rounded should the corners of the border be? 0 means no rounding.
/// </summary>
/// <remarks>If this value is too large, the edges of the border will appear odd.</remarks>
[Category("ObjectListView"),
Description("How rounded should the corners of the border be? 0 means no rounding."),
DefaultValue(16.0f),
NotifyParentProperty(true)]
public float CornerRounding {
get { return this.cornerRounding; }
set { this.cornerRounding = value; }
}
private float cornerRounding = 16.0f;
/// <summary>
/// Gets or sets the font that will be used to draw the text
/// </summary>
[Category("ObjectListView"),
Description("The font that will be used to draw the text"),
DefaultValue(null),
NotifyParentProperty(true)]
public Font Font {
get { return this.font; }
set { this.font = value; }
}
private Font font;
/// <summary>
/// Gets the font that will be used to draw the text or a reasonable default
/// </summary>
[Browsable(false)]
public Font FontOrDefault {
get {
return this.Font ?? new Font("Tahoma", 16);
}
}
/// <summary>
/// Does this text have a background?
/// </summary>
[Browsable(false)]
public bool HasBackground {
get {
return this.BackColor != Color.Empty;
}
}
/// <summary>
/// Does this overlay have a border?
/// </summary>
[Browsable(false)]
public bool HasBorder {
get {
return this.BorderColor != Color.Empty && this.BorderWidth > 0;
}
}
/// <summary>
/// Gets or sets the maximum width of the text. Text longer than this will wrap.
/// 0 means no maximum.
/// </summary>
[Category("ObjectListView"),
Description("The maximum width the text (0 means no maximum). Text longer than this will wrap"),
DefaultValue(0)]
public int MaximumTextWidth {
get { return this.maximumTextWidth; }
set { this.maximumTextWidth = value; }
}
private int maximumTextWidth;
/// <summary>
/// Gets or sets the formatting that should be used on the text
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual StringFormat StringFormat {
get {
if (this.stringFormat == null) {
this.stringFormat = new StringFormat();
this.stringFormat.Alignment = StringAlignment.Center;
this.stringFormat.LineAlignment = StringAlignment.Center;
this.stringFormat.Trimming = StringTrimming.EllipsisCharacter;
if (!this.Wrap)
this.stringFormat.FormatFlags = StringFormatFlags.NoWrap;
}
return this.stringFormat;
}
set { this.stringFormat = value; }
}
private StringFormat stringFormat;
/// <summary>
/// Gets or sets the text that will be drawn
/// </summary>
[Category("ObjectListView"),
Description("The text that will be drawn over the top of the ListView"),
DefaultValue(null),
NotifyParentProperty(true),
Localizable(true)]
public string Text {
get { return this.text; }
set { this.text = value; }
}
private string text;
/// <summary>
/// Gets the brush that will be used to paint the text
/// </summary>
[Browsable(false)]
public Brush TextBrush {
get {
return new SolidBrush(Color.FromArgb(this.workingTransparency, this.TextColor));
}
}
/// <summary>
/// Gets or sets the color of the text
/// </summary>
[Category("ObjectListView"),
Description("The color of the text"),
DefaultValue(typeof(Color), "DarkBlue"),
NotifyParentProperty(true)]
public Color TextColor {
get { return this.textColor; }
set { this.textColor = value; }
}
private Color textColor = Color.DarkBlue;
/// <summary>
/// Gets or sets whether the text will wrap when it exceeds its bounds
/// </summary>
[Category("ObjectListView"),
Description("Will the text wrap?"),
DefaultValue(true)]
public bool Wrap {
get { return this.wrap; }
set { this.wrap = value; }
}
private bool wrap = true;
#endregion
#region Implementation
/// <summary>
/// Draw our text with our stored configuration in relation to the given
/// reference rectangle
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The reference rectangle in relation to which the text will be drawn</param>
public virtual void DrawText(Graphics g, Rectangle r) {
this.DrawText(g, r, this.Text, this.Transparency);
}
/// <summary>
/// Draw the given text with our stored configuration
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="r">The reference rectangle in relation to which the text will be drawn</param>
/// <param name="s">The text to draw</param>
/// <param name="transparency">How opaque should be text be</param>
public virtual void DrawText(Graphics g, Rectangle r, string s, int transparency) {
if (String.IsNullOrEmpty(s))
return;
Rectangle textRect = this.CalculateTextBounds(g, r, s);
this.DrawBorderedText(g, textRect, s, transparency);
}
/// <summary>
/// Draw the text with a border
/// </summary>
/// <param name="g">The Graphics used for drawing</param>
/// <param name="textRect">The bounds within which the text should be drawn</param>
/// <param name="text">The text to draw</param>
/// <param name="transparency">How opaque should be text be</param>
protected virtual void DrawBorderedText(Graphics g, Rectangle textRect, string text, int transparency) {
Rectangle borderRect = textRect;
borderRect.Inflate((int)this.BorderWidth / 2, (int)this.BorderWidth / 2);
borderRect.Y -= 1; // Looker better a little higher
try {
this.ApplyRotation(g, textRect);
using (GraphicsPath path = this.GetRoundedRect(borderRect, this.CornerRounding)) {
this.workingTransparency = transparency;
if (this.HasBackground) {
using (Brush b = this.BackgroundBrush)
g.FillPath(b, path);
}
using (Brush b = this.TextBrush)
g.DrawString(text, this.FontOrDefault, b, textRect, this.StringFormat);
if (this.HasBorder) {
using (Pen p = this.BorderPen)
g.DrawPath(p, path);
}
}
}
finally {
this.UnapplyRotation(g);
}
}
/// <summary>
/// Return the rectangle that will be the precise bounds of the displayed text
/// </summary>
/// <param name="g"></param>
/// <param name="r"></param>
/// <param name="s"></param>
/// <returns>The bounds of the text</returns>
protected virtual Rectangle CalculateTextBounds(Graphics g, Rectangle r, string s) {
int maxWidth = this.MaximumTextWidth <= 0 ? r.Width : this.MaximumTextWidth;
SizeF sizeF = g.MeasureString(s, this.FontOrDefault, maxWidth, this.StringFormat);
Size size = new Size(1 + (int)sizeF.Width, 1 + (int)sizeF.Height);
return this.CreateAlignedRectangle(r, size);
}
/// <summary>
/// Return a GraphicPath that is a round cornered rectangle
/// </summary>
/// <param name="rect">The rectangle</param>
/// <param name="diameter">The diameter of the corners</param>
/// <returns>A round cornered rectagle path</returns>
/// <remarks>If I could rely on people using C# 3.0+, this should be
/// an extension method of GraphicsPath.</remarks>
protected virtual GraphicsPath GetRoundedRect(Rectangle rect, float diameter) {
GraphicsPath path = new GraphicsPath();
if (diameter > 0) {
RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
path.AddArc(arc, 180, 90);
arc.X = rect.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rect.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rect.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
} else {
path.AddRectangle(rect);
}
return path;
}
#endregion
private int workingTransparency;
}
}

Some files were not shown because too many files have changed in this diff Show More