#region License Information
/* HeuristicLab
* Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab 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.
*
* HeuristicLab 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 HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using HeuristicLab.Common;
using HeuristicLab.MainForm;
using HeuristicLab.MainForm.WindowsForms;
namespace HeuristicLab.Data.Views {
[View("StringConvertibleMatrix View")]
[Content(typeof(IStringConvertibleMatrix), true)]
public partial class StringConvertibleMatrixView : AsynchronousContentView {
protected int[] virtualRowIndices;
protected List> sortedColumnIndices;
private RowComparer rowComparer;
public new IStringConvertibleMatrix Content {
get { return (IStringConvertibleMatrix)base.Content; }
set { base.Content = value; }
}
public DataGridView DataGridView {
get { return dataGridView; }
}
public override bool ReadOnly {
get {
if ((Content != null) && Content.ReadOnly) return true;
return base.ReadOnly;
}
set { base.ReadOnly = value; }
}
private bool showRowsAndColumnsTextBox;
public bool ShowRowsAndColumnsTextBox {
get { return showRowsAndColumnsTextBox; }
set {
showRowsAndColumnsTextBox = value;
UpdateVisibilityOfTextBoxes();
}
}
private bool showStatisticalInformation;
public bool ShowStatisticalInformation {
get { return showStatisticalInformation; }
set {
showStatisticalInformation = value;
UpdateVisibilityOfStatisticalInformation();
}
}
public StringConvertibleMatrixView() {
InitializeComponent();
ShowRowsAndColumnsTextBox = true;
ShowStatisticalInformation = true;
errorProvider.SetIconAlignment(rowsTextBox, ErrorIconAlignment.MiddleLeft);
errorProvider.SetIconPadding(rowsTextBox, 2);
errorProvider.SetIconAlignment(columnsTextBox, ErrorIconAlignment.MiddleLeft);
errorProvider.SetIconPadding(columnsTextBox, 2);
sortedColumnIndices = new List>();
rowComparer = new RowComparer();
}
protected override void DeregisterContentEvents() {
Content.ItemChanged -= new EventHandler>(Content_ItemChanged);
Content.Reset -= new EventHandler(Content_Reset);
Content.ColumnNamesChanged -= new EventHandler(Content_ColumnNamesChanged);
Content.RowNamesChanged -= new EventHandler(Content_RowNamesChanged);
base.DeregisterContentEvents();
}
protected override void RegisterContentEvents() {
base.RegisterContentEvents();
Content.ItemChanged += new EventHandler>(Content_ItemChanged);
Content.Reset += new EventHandler(Content_Reset);
Content.ColumnNamesChanged += new EventHandler(Content_ColumnNamesChanged);
Content.RowNamesChanged += new EventHandler(Content_RowNamesChanged);
}
protected override void OnContentChanged() {
base.OnContentChanged();
if (Content == null) {
rowsTextBox.Text = "";
columnsTextBox.Text = "";
dataGridView.Rows.Clear();
dataGridView.Columns.Clear();
virtualRowIndices = new int[0];
} else if (!dataGridView.IsCurrentCellInEditMode) {
UpdateData();
}
}
protected override void SetEnabledStateOfControls() {
base.SetEnabledStateOfControls();
rowsTextBox.Enabled = Content != null;
columnsTextBox.Enabled = Content != null;
dataGridView.Enabled = Content != null;
rowsTextBox.ReadOnly = ReadOnly;
columnsTextBox.ReadOnly = ReadOnly;
dataGridView.ReadOnly = ReadOnly;
}
protected virtual void UpdateData() {
rowsTextBox.Text = Content.Rows.ToString();
rowsTextBox.Enabled = true;
columnsTextBox.Text = Content.Columns.ToString();
columnsTextBox.Enabled = true;
virtualRowIndices = Enumerable.Range(0, Content.Rows).ToArray();
dataGridView.RowCount = 0;
DataGridViewColumn[] columns = new DataGridViewColumn[Content.Columns];
for (int i = 0; i < columns.Length; ++i) {
var column = new DataGridViewTextBoxColumn();
column.SortMode = DataGridViewColumnSortMode.Programmatic;
column.FillWeight = 1;
columns[i] = column;
}
dataGridView.Columns.Clear();
if (Content.Columns != 0) {
dataGridView.Columns.AddRange(columns);
dataGridView.RowCount = Content.Rows;
}
ClearSorting();
UpdateColumnHeaders();
UpdateRowHeaders();
dataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.ColumnHeader);
dataGridView.AutoResizeRowHeadersWidth(DataGridViewRowHeadersWidthSizeMode.AutoSizeToDisplayedHeaders);
dataGridView.Enabled = true;
}
public virtual void UpdateColumnHeaders() {
HashSet invisibleColumnNames = new HashSet(dataGridView.Columns.OfType()
.Where(c => !c.Visible && !string.IsNullOrEmpty(c.HeaderText)).Select(c => c.HeaderText));
for (int i = 0; i < dataGridView.ColumnCount; i++) {
if (i < Content.ColumnNames.Count())
dataGridView.Columns[i].HeaderText = Content.ColumnNames.ElementAt(i);
else
dataGridView.Columns[i].HeaderText = "Column " + (i + 1);
dataGridView.Columns[i].Visible = !invisibleColumnNames.Contains(dataGridView.Columns[i].HeaderText);
}
}
public virtual void UpdateRowHeaders() {
int index = dataGridView.FirstDisplayedScrollingRowIndex;
if (index == -1) index = 0;
int updatedRows = 0;
int count = dataGridView.DisplayedRowCount(true);
while (updatedRows < count) {
if (virtualRowIndices[index] < Content.RowNames.Count())
dataGridView.Rows[index].HeaderCell.Value = Content.RowNames.ElementAt(virtualRowIndices[index]);
else
dataGridView.Rows[index].HeaderCell.Value = "Row " + (index + 1);
if (dataGridView.Rows[index].Visible)
updatedRows++;
index++;
}
}
private void Content_RowNamesChanged(object sender, EventArgs e) {
if (InvokeRequired)
Invoke(new EventHandler(Content_RowNamesChanged), sender, e);
else
UpdateRowHeaders();
}
private void Content_ColumnNamesChanged(object sender, EventArgs e) {
if (InvokeRequired)
Invoke(new EventHandler(Content_ColumnNamesChanged), sender, e);
else
UpdateColumnHeaders();
}
private void Content_ItemChanged(object sender, EventArgs e) {
if (InvokeRequired)
Invoke(new EventHandler>(Content_ItemChanged), sender, e);
else
dataGridView.InvalidateCell(e.Value2, e.Value);
}
private void Content_Reset(object sender, EventArgs e) {
if (InvokeRequired)
Invoke(new EventHandler(Content_Reset), sender, e);
else
UpdateData();
}
#region TextBox Events
private void rowsTextBox_Validating(object sender, CancelEventArgs e) {
if (ReadOnly || Locked)
return;
int i = 0;
if (!int.TryParse(rowsTextBox.Text, out i) || (i <= 0)) {
e.Cancel = true;
errorProvider.SetError(rowsTextBox, "Invalid Number of Rows (Valid values are positive integers larger than 0)");
rowsTextBox.SelectAll();
}
}
private void rowsTextBox_Validated(object sender, EventArgs e) {
if (!Content.ReadOnly) Content.Rows = int.Parse(rowsTextBox.Text);
errorProvider.SetError(rowsTextBox, string.Empty);
}
private void rowsTextBox_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return)
rowsLabel.Focus(); // set focus on label to validate data
if (e.KeyCode == Keys.Escape) {
rowsTextBox.Text = Content.Rows.ToString();
rowsLabel.Focus(); // set focus on label to validate data
}
}
private void columnsTextBox_Validating(object sender, CancelEventArgs e) {
if (ReadOnly || Locked)
return;
int i = 0;
if (!int.TryParse(columnsTextBox.Text, out i) || (i <= 0)) {
e.Cancel = true;
errorProvider.SetError(columnsTextBox, "Invalid Number of Columns (Valid values are positive integers larger than 0)");
columnsTextBox.SelectAll();
}
}
private void columnsTextBox_Validated(object sender, EventArgs e) {
if (!Content.ReadOnly) Content.Columns = int.Parse(columnsTextBox.Text);
errorProvider.SetError(columnsTextBox, string.Empty);
}
private void columnsTextBox_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return)
columnsLabel.Focus(); // set focus on label to validate data
if (e.KeyCode == Keys.Escape) {
columnsTextBox.Text = Content.Columns.ToString();
columnsLabel.Focus(); // set focus on label to validate data
}
}
#endregion
#region DataGridView Events
protected virtual void dataGridView_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) {
if (dataGridView.ReadOnly) return;
if (Content == null) return;
if (Content.Rows <= e.RowIndex || Content.Columns <= e.ColumnIndex) return;
string errorMessage;
if (!Content.Validate(e.FormattedValue.ToString(), out errorMessage)) {
e.Cancel = true;
dataGridView.Rows[e.RowIndex].ErrorText = errorMessage;
}
}
protected virtual void dataGridView_CellParsing(object sender, DataGridViewCellParsingEventArgs e) {
if (!dataGridView.ReadOnly) {
string value = e.Value.ToString();
int rowIndex = virtualRowIndices[e.RowIndex];
e.ParsingApplied = Content.SetValue(value, rowIndex, e.ColumnIndex);
if (e.ParsingApplied) e.Value = Content.GetValue(rowIndex, e.ColumnIndex);
}
}
private void dataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e) {
dataGridView.Rows[e.RowIndex].ErrorText = string.Empty;
}
protected virtual void dataGridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) {
if (Content != null && e.RowIndex < Content.Rows && e.ColumnIndex < Content.Columns) {
int rowIndex = virtualRowIndices[e.RowIndex];
e.Value = Content.GetValue(rowIndex, e.ColumnIndex);
}
}
private void dataGridView_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e) {
this.UpdateRowHeaders();
}
private void dataGridView_Resize(object sender, EventArgs e) {
this.UpdateRowHeaders();
}
protected virtual void dataGridView_KeyDown(object sender, KeyEventArgs e) {
if (!ReadOnly && e.Control && e.KeyCode == Keys.V) {
PasteValuesToDataGridView();
e.Handled = true;
} else if (e.Control && e.KeyCode == Keys.C) {
CopyValuesFromDataGridView();
e.Handled = true;
}
}
private void CopyValuesFromDataGridView() {
if (dataGridView.SelectedCells.Count == 0) return;
//if not all cells are selected use the built-in functionality for copying values to the clipboard
if (!dataGridView.AreAllCellsSelected(false)) {
dataGridView.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
var data = dataGridView.GetClipboardContent();
Clipboard.SetDataObject(data);
return;
}
//If all cells are selected we want to include row and column headers if they are set in the content.
//This is not possible with the built-in functionality, because only both headers can be added.
//Furthermore, the current implementation of the view with the virtual DataGridView does set
//Row headers only when they are displayed (see UpdateRowHeaders) and otherwise leaves them empty.
StringBuilder s = new StringBuilder();
bool addColumnNames = Content.ColumnNames.Any();
bool addRowNames = Content.RowNames.Any();
//add first row with column headers
if (addColumnNames) {
var columnNames = Content.ColumnNames.ToArray();
if (addRowNames) s.Append('\t'); //there is no column header for the row headers
DataGridViewColumn column = dataGridView.Columns.GetFirstColumn(DataGridViewElementStates.Visible);
while (column != null) {
s.Append(columnNames[column.Index]);
s.Append('\t');
column = dataGridView.Columns.GetNextColumn(column, DataGridViewElementStates.Visible, DataGridViewElementStates.None);
}
s.Append(Environment.NewLine);
}
var rowNames = Content.RowNames.ToArray();
for (int r = 0; r < dataGridView.RowCount; r++) {
if (!dataGridView.Rows[r].Visible) continue; //skip invisible rows
int rowIndex = virtualRowIndices[r];
if (addRowNames) {
s.Append(rowNames[rowIndex]);
s.Append('\t');
}
DataGridViewColumn column = dataGridView.Columns.GetFirstColumn(DataGridViewElementStates.Visible);
while (column != null) {
s.Append(Content.GetValue(rowIndex, column.Index));
s.Append('\t');
column = dataGridView.Columns.GetNextColumn(column, DataGridViewElementStates.Visible, DataGridViewElementStates.None);
}
s.Remove(s.Length - 1, 1); //remove last tab
s.Append(Environment.NewLine);
}
Clipboard.SetText(s.ToString());
}
protected virtual void PasteValuesToDataGridView() {
string[,] values = SplitClipboardString(Clipboard.GetText());
int rowIndex = 0;
int columnIndex = 0;
if (dataGridView.CurrentCell != null) {
rowIndex = dataGridView.CurrentCell.RowIndex;
columnIndex = dataGridView.CurrentCell.ColumnIndex;
}
if (Content.Rows < values.GetLength(1) + rowIndex) Content.Rows = values.GetLength(1) + rowIndex;
if (Content.Columns < values.GetLength(0) + columnIndex) Content.Columns = values.GetLength(0) + columnIndex;
for (int row = 0; row < values.GetLength(1); row++) {
for (int col = 0; col < values.GetLength(0); col++) {
Content.SetValue(values[col, row], row + rowIndex, col + columnIndex);
}
}
ClearSorting();
}
protected string[,] SplitClipboardString(string clipboardText) {
if (clipboardText.EndsWith(Environment.NewLine))
clipboardText = clipboardText.Remove(clipboardText.Length - Environment.NewLine.Length); //remove last newline constant
string[,] values = null;
string[] lines = clipboardText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
string[] cells;
for (int i = 0; i < lines.Length; i++) {
cells = lines[i].Split('\t');
if (values == null)
values = new string[cells.Length, lines.Length];
for (int j = 0; j < cells.Length; j++)
values[j, i] = string.IsNullOrEmpty(cells[j]) ? string.Empty : cells[j];
}
return values;
}
protected virtual void dataGridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) {
if (Content != null) {
if (e.Button == MouseButtons.Left && Content.SortableView) {
SortColumn(e.ColumnIndex);
}
}
}
protected virtual void ClearSorting() {
virtualRowIndices = Enumerable.Range(0, Content.Rows).ToArray();
sortedColumnIndices.Clear();
UpdateSortGlyph();
}
protected void Sort() {
virtualRowIndices = Sort(sortedColumnIndices);
UpdateSortGlyph();
UpdateRowHeaders();
dataGridView.Invalidate();
}
protected virtual void SortColumn(int columnIndex) {
bool addToSortedIndices = (Control.ModifierKeys & Keys.Control) == Keys.Control;
SortOrder newSortOrder = SortOrder.Ascending;
if (sortedColumnIndices.Any(x => x.Key == columnIndex)) {
SortOrder oldSortOrder = sortedColumnIndices.Where(x => x.Key == columnIndex).First().Value;
int enumLength = Enum.GetValues(typeof(SortOrder)).Length;
newSortOrder = oldSortOrder = (SortOrder)Enum.Parse(typeof(SortOrder), ((((int)oldSortOrder) + 1) % enumLength).ToString());
}
if (!addToSortedIndices)
sortedColumnIndices.Clear();
if (sortedColumnIndices.Any(x => x.Key == columnIndex)) {
int sortedIndex = sortedColumnIndices.FindIndex(x => x.Key == columnIndex);
if (newSortOrder != SortOrder.None)
sortedColumnIndices[sortedIndex] = new KeyValuePair(columnIndex, newSortOrder);
else
sortedColumnIndices.RemoveAt(sortedIndex);
} else
if (newSortOrder != SortOrder.None)
sortedColumnIndices.Add(new KeyValuePair(columnIndex, newSortOrder));
Sort();
}
protected virtual int[] Sort(IEnumerable> sortedColumns) {
int[] newSortedIndex = Enumerable.Range(0, Content.Rows).ToArray();
if (sortedColumns.Count() != 0) {
rowComparer.SortedIndices = sortedColumns;
rowComparer.Matrix = Content;
Array.Sort(newSortedIndex, rowComparer);
}
return newSortedIndex;
}
private void UpdateSortGlyph() {
foreach (DataGridViewColumn col in this.dataGridView.Columns)
col.HeaderCell.SortGlyphDirection = SortOrder.None;
foreach (KeyValuePair p in sortedColumnIndices)
this.dataGridView.Columns[p.Key].HeaderCell.SortGlyphDirection = p.Value;
}
#endregion
public int GetRowIndex(int originalIndex) {
return virtualRowIndices[originalIndex];
}
public class RowComparer : IComparer {
public RowComparer() {
}
private List> sortedIndices;
public IEnumerable> SortedIndices {
get { return this.sortedIndices; }
set { sortedIndices = new List>(value); }
}
private IStringConvertibleMatrix matrix;
public IStringConvertibleMatrix Matrix {
get { return this.matrix; }
set { this.matrix = value; }
}
public int Compare(int x, int y) {
int result = 0;
double double1, double2;
DateTime dateTime1, dateTime2;
TimeSpan timeSpan1, timeSpan2;
string string1, string2;
if (matrix == null)
throw new InvalidOperationException("Could not sort IStringConvertibleMatrix if the matrix member is null.");
if (sortedIndices == null)
return 0;
foreach (KeyValuePair pair in sortedIndices.Where(p => p.Value != SortOrder.None)) {
string1 = matrix.GetValue(x, pair.Key);
string2 = matrix.GetValue(y, pair.Key);
if (double.TryParse(string1, out double1) && double.TryParse(string2, out double2))
result = double1.CompareTo(double2);
else if (DateTime.TryParse(string1, out dateTime1) && DateTime.TryParse(string2, out dateTime2))
result = dateTime1.CompareTo(dateTime2);
else if (TimeSpan.TryParse(string1, out timeSpan1) && TimeSpan.TryParse(string2, out timeSpan2))
result = timeSpan1.CompareTo(timeSpan2);
else {
if (string1 != null)
result = string1.CompareTo(string2);
else if (string2 != null)
result = string2.CompareTo(string1) * -1;
}
if (pair.Value == SortOrder.Descending)
result *= -1;
if (result != 0)
return result;
}
return result;
}
}
protected virtual void dataGridView_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e) {
if (Content == null) return;
if (e.Button == MouseButtons.Right && Content.ColumnNames.Count() != 0)
contextMenu.Show(MousePosition);
}
protected virtual void ShowHideColumns_Click(object sender, EventArgs e) {
new StringConvertibleMatrixColumnVisibilityDialog(this.dataGridView.Columns.Cast()).ShowDialog();
columnsTextBox.Text = dataGridView.Columns.GetColumnCount(DataGridViewElementStates.Visible).ToString();
}
private void UpdateVisibilityOfTextBoxes() {
rowsTextBox.Visible = columnsTextBox.Visible = showRowsAndColumnsTextBox;
rowsLabel.Visible = columnsLabel.Visible = showRowsAndColumnsTextBox;
UpdateDataGridViewSizeAndLocation();
}
private void UpdateVisibilityOfStatisticalInformation() {
statisticsTextBox.Visible = showStatisticalInformation;
UpdateDataGridViewSizeAndLocation();
}
private void UpdateDataGridViewSizeAndLocation() {
int headerSize = columnsTextBox.Location.Y + columnsTextBox.Size.Height +
columnsTextBox.Margin.Bottom + dataGridView.Margin.Top;
int offset = showRowsAndColumnsTextBox ? headerSize : 0;
dataGridView.Location = new Point(0, offset);
int statisticsTextBoxHeight = showStatisticalInformation ? statisticsTextBox.Height + statisticsTextBox.Margin.Top + statisticsTextBox.Margin.Bottom : 0;
dataGridView.Size = new Size(Size.Width, Size.Height - offset - statisticsTextBoxHeight);
}
protected virtual void dataGridView_SelectionChanged(object sender, EventArgs e) {
statisticsTextBox.Text = string.Empty;
if (dataGridView.SelectedCells.Count > 1) {
List selectedValues = new List();
foreach (DataGridViewCell cell in dataGridView.SelectedCells) {
double value;
if (!double.TryParse(cell.Value.ToString(), out value)) return;
selectedValues.Add(value);
}
if (selectedValues.Count > 1) {
statisticsTextBox.Text = CreateStatisticsText(selectedValues);
}
}
}
protected virtual string CreateStatisticsText(ICollection values) {
string stringFormat = "{0,20:0.0000}";
int overallCount = values.Count;
values = values.Where(x => !double.IsNaN(x)).ToList();
if (!values.Any()) {
return "";
}
StringBuilder statisticsText = new StringBuilder();
statisticsText.Append("Count: " + values.Count + " ");
statisticsText.Append("Sum: " + string.Format(stringFormat, values.Sum()) + " ");
statisticsText.Append("Min: " + string.Format(stringFormat, values.Min()) + " ");
statisticsText.Append("Max: " + string.Format(stringFormat, values.Max()) + " ");
statisticsText.Append("Average: " + string.Format(stringFormat, values.Average()) + " ");
statisticsText.Append("Standard Deviation: " + string.Format(stringFormat, values.StandardDeviation()) + " ");
if (overallCount > 0)
statisticsText.Append("Missing Values: " + string.Format(stringFormat, ((overallCount - values.Count) / (double)overallCount) * 100) + "% ");
return statisticsText.ToString();
}
}
}