|
using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using BinaryNorthwest;
// Source code by Owen Emlen (owene_1998@yahoo.com, owen@binarynorthwest.com)
namespace MultiSortDemo
{
public partial class FormMultiSortDemo : Form
{
private int nSortedColumn = int.MinValue;
private bool fDescending = false;
private int nStartingItems = 4096;
/// <summary>
/// Stores a potentially large list of work items for sorting
/// </summary>
public List<WorkItem> AllWorkItems;
/// <summary>
/// Stores sort order and direction for multiple sort criteria
/// </summary>
public List<SortPropOrFieldAndDirection> rgSortBy;
/// <summary>
/// Stores bosses and workers, used for creating dummy "assigned by" & "assigned to" values
/// </summary>
public string[] rgBosses = new string[5];
public string[] rgWorkers = new string[5];
/// <summary>
/// Stores the property names for class "WorkItem". Could be constructed dynamically via reflection.
/// </summary>
public List<WorkItemPropertyName> AllPropertyNames = new List<WorkItemPropertyName>(16);
/// <summary>
/// Standard component initialization / Constructor
/// </summary>
public FormMultiSortDemo()
{
InitializeComponent();
}
private void FormMultiSortDemo_Load(object sender, EventArgs e)
{
// Allocate basic lists that will be used throughout the demo
rgSortBy = new List<SortPropOrFieldAndDirection>(5);
// Set up property<->field links
WorkItem.SetupQuickAndDirtyPropertyToFieldLinkLookup();
// Initialize dummy names
InitializeNames();
txtItemCount.Text = nStartingItems.ToString();
// Generate test data
GenerateRealWorkItems(nStartingItems);
// Add a blank property name entry
AllPropertyNames.Add(new WorkItemPropertyName(""));
// Add property names via reflection
PropertyInfo[] rgProperties = typeof(WorkItem).GetProperties();
for (int i = 0; i < rgProperties.Length; i++)
{
string sPropertyName = rgProperties[i].Name;
AllPropertyNames.Add(new WorkItemPropertyName(sPropertyName));
}
// Add property names. This could also be done via reflection, but is currently hardcoded for the demo
//AllPropertyNames.Add(new WorkItemPropertyName(""));
//AllPropertyNames.Add(new WorkItemPropertyName("AssignedBy"));
//AllPropertyNames.Add(new WorkItemPropertyName("AssignedTo"));
//AllPropertyNames.Add(new WorkItemPropertyName("DateAssigned"));
//AllPropertyNames.Add(new WorkItemPropertyName("DateFinished"));
//AllPropertyNames.Add(new WorkItemPropertyName("ItemFinished"));
//AllPropertyNames.Add(new WorkItemPropertyName("Priority"));
//AllPropertyNames.Add(new WorkItemPropertyName("TaskBoredom"));
// Bind the sample data to the grid
workItemBindingSource.DataSource = AllWorkItems;
workItemPropertyNameBindingSource1.DataSource = AllPropertyNames;
workItemPropertyNameBindingSource2.DataSource = AllPropertyNames;
workItemPropertyNameBindingSource3.DataSource = AllPropertyNames;
workItemPropertyNameBindingSource4.DataSource = AllPropertyNames;
workItemPropertyNameBindingSource5.DataSource = AllPropertyNames;
cb1.SelectedIndex = 6;
}
/// <summary>
/// When a column header is clicked, sort by the property associated with the header.
/// Also handles glyph display (momentarily - glyphs disappear after rebind) and reversal of sort direction.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void dgvList_ColumnHeaderMouseClick(object sender, System.Windows.Forms.DataGridViewCellMouseEventArgs e)
{
this.Cursor = Cursors.WaitCursor;
try
{
// Retrieve the property name associated with the header
string sPropertyName = dgvList.Columns[e.ColumnIndex].DataPropertyName;
// Clear previous sort glyph
if (nSortedColumn != int.MinValue)
{
dgvList.Columns[nSortedColumn].HeaderCell.SortGlyphDirection = SortOrder.None;
}
dgvList.Columns[e.ColumnIndex].SortMode = DataGridViewColumnSortMode.Programmatic;
// Determine if the user is re-clicking on the same column header
if (nSortedColumn == e.ColumnIndex)
{
dgvList.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection =
fDescending ? SortOrder.Ascending : SortOrder.Descending;
// Reverse the sort direction
fDescending = !fDescending;
}
else
{
// User clicked on a new column header. Sort Ascending.
fDescending = false;
nSortedColumn = e.ColumnIndex;
dgvList.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
}
SortPropOrFieldAndDirection sortBy;
// Determine if the user is requesting the sort via use of field values or property values
bool fUseFieldLookup = rbFields.Checked;
if (fUseFieldLookup)
{
// Sort using direct field lookup
sortBy = new SortFieldAndDirection(
WorkItem.htPropertyToFieldNameMapping.GetFieldNameForProperty(sPropertyName), fDescending
);
}
else
{
// Sort using property lookup
sortBy = new SortPropertyAndDirection(sPropertyName, fDescending);
}
// Suspend binding and layout for more accurate timing of the actual sort
workItemBindingSource.SuspendBinding();
dgvList.SuspendLayout();
{
Stopwatch t = new Stopwatch();
t.Start();
{
// Perform the actual (in-place) sort
Sorting.SortInPlace<WorkItem>(AllWorkItems, sortBy);
}
t.Stop();
lblResults.Text = "Sorted " + AllWorkItems.Count + " items by " + sPropertyName + " using " +
(fUseFieldLookup ? "field value lookups " : "property get ") + " in " + t.ElapsedMilliseconds.ToString() + "ms";
Application.DoEvents();
workItemBindingSource.ResetBindings(false);
}
dgvList.ResumeLayout(false);
workItemBindingSource.ResumeBinding();
}
finally
{
this.Cursor = Cursors.Default;
}
}
/// <summary>
/// Initialize some dummy boss and worker names (include null for demonstration purposes)
/// </summary>
public void InitializeNames()
{
rgBosses[0] = "Frankenstein";
rgBosses[1] = "JoMomma";
rgBosses[2] = "Bob in accounting";
rgBosses[3] = null;
rgBosses[4] = "Your wife";
rgWorkers[0] = SystemInformation.UserName;
rgWorkers[1] = "Programmer using computer " + SystemInformation.ComputerName;
rgWorkers[2] = "Fred";
rgWorkers[3] = "JoMomma Jr.";
rgWorkers[4] = "Will Pastabuck Tuyu";
}
/// <summary>
/// We need something to sort. Set up some "real" work items...
/// </summary>
/// <param name="nItems"></param>
public void GenerateRealWorkItems(int nItems)
{
Random rnd = new Random();
AllWorkItems = new List<WorkItem>(nItems);
for (int i = 0; i < nItems; i++)
{
WorkItem item = new WorkItem();
// Select a random boss (assigned by)
item.AssignedBy = rgBosses[rnd.Next(0, 5)];
// Select a random worker (assigned to)
item.AssignedTo = rgWorkers[rnd.Next(0, 5)];
// Select a random priority
item.Priority = rnd.Next(1, 10);
// Select a random task interest level
item.TaskBoredom = (BoredomRating)rnd.Next(
(int)BoredomRating.EvenWorseThanCodingASPWebPages,
(int)BoredomRating.MaxExcitement);
item.FoundOutAboutWorkItemDaysLater(rnd.Next(0, 20));
item.PretendCompleted(rnd.Next(0, 20));
AllWorkItems.Add(item);
}
}
/// <summary>
/// Creates a SortPropertyAndDirection instance or a SortFieldAndDirection instance,
/// depending on whether the user wants value retrieval by Property or Field
/// </summary>
/// <param name="sPropertyName"></param>
/// <param name="fDescending"></param>
/// <returns></returns>
public SortPropOrFieldAndDirection CreateSortBy(string sPropertyName, bool fDescending)
{
if (rbFields.Checked)
{
return new SortFieldAndDirection(
WorkItem.htPropertyToFieldNameMapping.GetFieldNameForProperty(sPropertyName),
fDescending);
}
else
{
return new SortPropertyAndDirection(sPropertyName, fDescending);
}
}
/// <summary>
/// Handle the main Sort button. Sort by (potentially) multiple field values or property values.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSort_Click(object sender, EventArgs e)
{
string sWindowTitle = this.Text;
this.Cursor = Cursors.WaitCursor;
Application.DoEvents();
try
{
rgSortBy.Clear();
// Retrieve sort parameters from the UI (combo boxes) and sort direction from check boxes
if (!string.IsNullOrEmpty(cb1.Text))
{
rgSortBy.Add(CreateSortBy(cb1.Text, chk1.Checked));
}
if (!string.IsNullOrEmpty(cb2.Text))
{
rgSortBy.Add(CreateSortBy(cb2.Text, chk2.Checked));
}
if (!string.IsNullOrEmpty(cb3.Text))
{
rgSortBy.Add(CreateSortBy(cb3.Text, chk3.Checked));
}
if (!string.IsNullOrEmpty(cb4.Text))
{
rgSortBy.Add(CreateSortBy(cb4.Text, chk4.Checked));
}
if (!string.IsNullOrEmpty(cb5.Text))
{
rgSortBy.Add(CreateSortBy(cb5.Text, chk5.Checked));
}
// Suspend data binding and layout to obtain a more accurate sort timing
workItemBindingSource.SuspendBinding();
dgvList.SuspendLayout();
{
Stopwatch t = new Stopwatch();
t.Start();
// Perform the actual multi-sort. Sort the work items given a list of sort criteria
List<WorkItem> sortedList = Sorting.MultiSort<WorkItem>(AllWorkItems, rgSortBy);
t.Stop();
// Replace the old work item list with the new, sorted list, using an atomic operation for thread safety
Interlocked.Exchange<List<WorkItem>>(ref AllWorkItems, sortedList);
lblResults.Text = "Sorted " + AllWorkItems.Count + " items based on your selected criteria using " +
(rbFields.Checked ? "field value lookups " : "property get ") + "in " + t.ElapsedMilliseconds.ToString() + "ms";
this.Text = "Rebinding grid...";
Application.DoEvents();
if (!chkHideGrid.Checked) workItemBindingSource.DataSource = AllWorkItems;
}
dgvList.ResumeLayout(false);
workItemBindingSource.ResumeBinding();
}
finally
{
this.Cursor = Cursors.Default;
this.Text = sWindowTitle;
}
}
/// <summary>
/// Generate a number (user-specified) of new "dummy" items
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnGenerateItems_Click(object sender, EventArgs e)
{
string sWindowTitle = this.Text;
this.Cursor = Cursors.WaitCursor;
Application.DoEvents();
try
{
int nItems = 0;
// Check user input for number of new items to create
try
{
nItems = Convert.ToInt32(txtItemCount.Text);
}
catch { }
// Limit number from 64 to 100,000 items
if (nItems < 64 || nItems > 100000)
{
lblResults.Text = "Please enter a number between 64 and 100000";
}
else
{
dgvList.SuspendLayout();
{
// Generate the items
GenerateRealWorkItems(nItems);
this.Text = "Created " + nItems.ToString() + " work items. Rebinding grid...";
Application.DoEvents();
if (!chkHideGrid.Checked) workItemBindingSource.DataSource = AllWorkItems;
}
dgvList.ResumeLayout(false);
}
}
finally
{
this.Text = sWindowTitle;
this.Cursor = Cursors.Default;
}
}
/// <summary>
/// Hide or show the grid of items
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void chkHideGrid_CheckedChanged(object sender, EventArgs e)
{
this.Cursor = Cursors.WaitCursor;
try
{
if (!chkHideGrid.Checked)
{
workItemBindingSource.DataSource = AllWorkItems;
}
else
{
dgvList.SuspendLayout();
{
workItemBindingSource.DataSource = new List<WorkItem>();
workItemBindingSource.ResetBindings(false);
}
dgvList.ResumeLayout(false);
}
}
finally
{
this.Cursor = Cursors.Default;
}
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
Currently working as a Senior Silverlight Developer with Troppus Software in Superior, CO. I enjoy statistics, programming, new technology, playing the cello, and reading codeproject articles.