#region License Information /* HeuristicLab * Copyright (C) 2002-2013 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.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using HeuristicLab.DataImporter.Data.Command; using HeuristicLab.DataImporter.Data.Model; namespace HeuristicLab.DataImporter.Data.View { public delegate void ColumnGroupActivatedEventHandler(object sender, bool addToActiveColumnGroups); public partial class ColumnGroupView : UserControl { private TextBox txtColumnName; private ColumnGroupView() : base() { InitializeComponent(); txtColumnName = new TextBox(); this.splitContainer1.Panel2.Controls.Add(txtColumnName); txtColumnName.Visible = false; txtColumnName.Validated += new EventHandler(txtColumnName_Validated); txtColumnName.KeyDown += new KeyEventHandler(txtColumnName_KeyDown); this.dataGridView.EnableHeadersVisualStyles = false; this.dataGridView.Dock = DockStyle.Top | DockStyle.Bottom; this.dataGridView.VirtualMode = true; this.dataGridView.ShowCellToolTips = false; this.dataGridView.ColumnHeaderMouseClick += new DataGridViewCellMouseEventHandler(dataGridView_ColumnHeaderMouseClick); this.dataGridView.ColumnHeaderMouseDoubleClick += new DataGridViewCellMouseEventHandler(dataGridView_ColumnHeaderMouseDoubleClick); this.dataGridView.ColumnWidthChanged += new DataGridViewColumnEventHandler(dataGridView_ColumnWidthChanged); this.dataGridView.MouseClick += new MouseEventHandler(dataGridView_MouseClick); this.dataGridView.SelectionChanged += new EventHandler(dataGridView_SelectionChanged); this.dataGridView.AllowUserToOrderColumnsChanged += new EventHandler(dataGridView_AllowUserToOrderColumnsChanged); this.dataGridView.KeyDown += new KeyEventHandler(dataGridView_KeyDown); this.dataGridView.RowHeadersWidthChanged += new EventHandler(dataGridView_RowHeadersWidthChanged); this.dataGridView.CellMouseEnter += new DataGridViewCellEventHandler(dataGridView_CellMouseEnter); this.dataGridView.Scroll += new ScrollEventHandler(dataGridView_Scroll); this.dataGridView.Resize += new EventHandler(dataGridView_Resize); //delegates for virtual mode this.dataGridView.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView_CellValueNeeded); this.dataGridView.CellValuePushed += new DataGridViewCellValueEventHandler(dataGridView_CellValuePushed); this.dataGridView.UserDeletingRow += new DataGridViewRowCancelEventHandler(dataGridView_UserDeletingRow); } private ColumnGroupView(ColumnGroup columnGroup) : this() { this.ColumnGroup = columnGroup; this.ColumnGroup.Changed += this.ColumnGroupChanged; this.dataGridView.ClearSelection(); this.state = ColumnGroupState.None; this.UpdateDataGridView(); } public ColumnGroupView(ColumnGroup columnGroup, CommandChain commandChain) : this(columnGroup) { this.commandChain = commandChain; } public bool ColumnGroupActive { get { return this.ColumnGroup.Active; } set { this.ColumnGroup.Active = value; if (this.ColumnGroup.Active) this.txtColumnGroupName.BackColor = Color.LightGreen; else { this.dataGridView.ClearSelection(); this.txtColumnGroupName.BackColor = Control.DefaultBackColor; } UpdateStateInformation(); } } private ColumnGroup columnGroup; public ColumnGroup ColumnGroup { get { return (ColumnGroup)this.columnGroup; } private set { this.columnGroup = value; } } private Data.Model.DataSet DataSet { get { return ((DataSetView)this.Parent).DataSet; } } private CommandChain commandChain; public CommandChain CommandChain { get { return this.commandChain; } set { this.commandChain = value; } } public bool AllowReorderColumns { get { return this.dataGridView.AllowUserToOrderColumns; } set { this.dataGridView.AllowUserToOrderColumns = value; } } public int[] DisplayIndexes { get { int[] ret = new int[this.ColumnGroup.Columns.Count()]; for (int i = 0; i < this.dataGridView.Columns.Count; i++) { ret[dataGridView.Columns[i].DisplayIndex] = i; } return ret; } } public int MaxWidth { get { return this.MaximumSize.Width; } set { this.MaximumSize = new Size(value, this.MaximumSize.Height); this.dataGridView.MaximumSize = new Size(value, this.MaximumSize.Height); RecalculateWidthOfControl(); } } //IMPORTANT: use the defined property to change the state, because the event StateChanged must be fired! private ColumnGroupState state; public ColumnGroupState State { get { return this.state; } protected set { this.state = value; FireStateChanged(); } } public event EventHandler StateChanged; protected void FireStateChanged() { OnStateChanged(); } protected virtual void OnStateChanged() { if (StateChanged != null) { StateChanged(this, new EventArgs()); } } public event ColumnGroupActivatedEventHandler Activated; public void FireActivated(bool addToActiveColumnGroups) { OnActivated(addToActiveColumnGroups); } protected virtual void OnActivated(bool addToActiveColumnGroups) { if (Activated != null) { Activated(this, addToActiveColumnGroups); } } private void UpdateStateInformation() { this.UpdateStateInformation(false); } private void UpdateStateInformation(bool selectionChanged) { ColumnGroupState newState = this.ColumnGroupActive ? ColumnGroupState.Active : ColumnGroupState.None; foreach (ColumnBase col in ColumnGroup.SelectedColumns) { if (col is DoubleColumn) newState |= ColumnGroupState.DoubleColumnSelected; else if (col is StringColumn) newState |= ColumnGroupState.StringColumnSelected; else if (col is DateTimeColumn) newState |= ColumnGroupState.DateTimeColumnSelected; else if (col is ProgrammableColumn) newState |= ColumnGroupState.ProgrammableColumnSelected; if (col.ContainsNullValues) newState |= ColumnGroupState.AnySelectedColumnContainsNull; } if (ColumnGroup.Sorted) { if (ColumnGroup.SelectedColumns.Any(col => col.SortOrder == SortOrder.None)) newState |= ColumnGroupState.AnySelectedColumnNotSorted; else newState |= ColumnGroupState.Sorted; } if (newState != this.State || selectionChanged) this.State = newState; } private void PasteClipboardContent() { if (dataGridView.CurrentCell != null) { string values = Clipboard.GetText(); values = values.Remove(values.Length - Environment.NewLine.Length); this.commandChain.Add(new PasteValuesCommand(DataSet, this.ColumnGroup.Name, dataGridView.CurrentCell.ColumnIndex, dataGridView.CurrentCell.RowIndex, values)); } } private void CopyClipboardContent() { if (dataGridView.SelectedCells.Count != 0) { StringBuilder s = new StringBuilder(); DataGridViewCell cell; int minRowIndex = dataGridView.SelectedCells[0].RowIndex; int maxRowIndex = dataGridView.SelectedCells[dataGridView.SelectedCells.Count - 1].RowIndex; int minColIndex = dataGridView.SelectedCells[0].ColumnIndex; int maxColIndex = dataGridView.SelectedCells[dataGridView.SelectedCells.Count - 1].ColumnIndex; if (minRowIndex > maxRowIndex) { int temp = minRowIndex; minRowIndex = maxRowIndex; maxRowIndex = temp; } if (minColIndex > maxColIndex) { int temp = minColIndex; minColIndex = maxColIndex; maxColIndex = temp; } if (maxRowIndex == dataGridView.RowCount - 1) maxRowIndex--; for (int i = minRowIndex; i < maxRowIndex + 1; i++) { for (int j = minColIndex; j < maxColIndex + 1; j++) { cell = dataGridView[j, i]; if (cell.Selected) { if (cell.Value != null) s.Append(cell.Value.ToString()); } if (j != maxColIndex) s.Append("\t"); } s.Append(Environment.NewLine); } Clipboard.SetText(s.ToString()); } } public override void Refresh() { base.Refresh(); if (ColumnGroup != null) { this.UpdateDataGridView(); } else { dataGridView.ColumnCount = 0; dataGridView.RowCount = 0; } } public void ColumnGroupChanged(object sender, EventArgs e) { this.UpdateDataGridView(); } private void UpdateDataGridView() { int firstVisibleRowIndex = 0; int firstVisibleColIndex = 0; if (dataGridView.FirstDisplayedCell != null) { if (firstVisibleColIndex < ColumnGroup.Columns.Count()) firstVisibleColIndex = dataGridView.FirstDisplayedCell.ColumnIndex; if (firstVisibleRowIndex < ColumnGroup.RowCount) firstVisibleRowIndex = dataGridView.FirstDisplayedCell.RowIndex; } //needed because otherwise columns could not be added this.dataGridView.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect; if (dataGridView.ColumnCount != ColumnGroup.Columns.Count()) { this.dataGridView.ColumnCount = ColumnGroup.Columns.Count(); for (int i = 0; i < ColumnGroup.Columns.Count(); i++) { dataGridView.Columns[i].SortMode = DataGridViewColumnSortMode.Programmatic; } } for (int i = 0; i < ColumnGroup.Columns.Count(); i++) dataGridView.Columns[i].HeaderText = ColumnGroup.Columns.ElementAt(i).ToString().Insert(ColumnGroup.Columns.ElementAt(i).ToString().IndexOf('<'), "\n"); // needed for performance reasons; further information at http://whiletrue.nl/blog/?p=38 if (dataGridView.RowCount != ColumnGroup.RowCount + (ColumnGroup.Columns.Count() == 0 ? 0 : 1)) { //event handler must be deregistered cause otherwise cellvaluepushed is fired again this.dataGridView.CellValuePushed -= new DataGridViewCellValueEventHandler(dataGridView_CellValuePushed); if (Math.Abs(dataGridView.RowCount - ColumnGroup.RowCount) > 10) dataGridView.Rows.Clear(); bool rowAdded = dataGridView.RowCount == ColumnGroup.RowCount && dataGridView.RowCount != 0; dataGridView.RowCount = ColumnGroup.RowCount + (!ColumnGroup.Columns.Any() ? 0 : 1); if (rowAdded) { Point p = this.dataGridView.CurrentCellAddress; p.Y += 1; this.dataGridView.CurrentCell = this.dataGridView.Rows[p.Y].Cells[p.X]; this.dataGridView.ClearSelection(); this.dataGridView.CurrentCell.Selected = true; } this.dataGridView.CellValuePushed += new DataGridViewCellValueEventHandler(dataGridView_CellValuePushed); } UpdateDataGridViewHeaderCells(); this.dataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.DisplayedCells); this.dataGridView.AutoResizeRowHeadersWidth(DataGridViewRowHeadersWidthSizeMode.AutoSizeToDisplayedHeaders); if (dataGridView.RowCount != 0 && dataGridView.ColumnCount != 0) { if (firstVisibleColIndex >= dataGridView.ColumnCount) firstVisibleColIndex = 0; if (firstVisibleRowIndex >= dataGridView.RowCount) firstVisibleRowIndex = 0; dataGridView.FirstDisplayedCell = dataGridView[firstVisibleColIndex, firstVisibleRowIndex]; } UpdateSortGlyph(); this.txtColumnGroupName.Text = this.ColumnGroup.Name + " " + ColumnGroup.RowCount + " rows"; RecalculateWidthOfControl(); this.dataGridView.Invalidate(); UpdateStateInformation(); } private void UpdateDataGridViewHeaderCells() { int index = dataGridView.FirstDisplayedScrollingRowIndex; if (index == -1) index = 0; int updatedRows = 0; int count = dataGridView.DisplayedRowCount(true); while (updatedRows < count) { dataGridView.Rows[index].HeaderCell.Value = (index + 1).ToString(); if (dataGridView.Rows[index].Visible) updatedRows++; index++; } } private void dataGridView_ColumnHeaderMouseClick(object sender, System.Windows.Forms.DataGridViewCellMouseEventArgs e) { if (e.Button == MouseButtons.Right) { this.commandChain.Add(new SortCommand(DataSet, this.ColumnGroup.Name, e.ColumnIndex, (Control.ModifierKeys & Keys.Control) == Keys.Control)); } } private void dataGridView_ColumnHeaderMouseDoubleClick(object sender, System.Windows.Forms.DataGridViewCellMouseEventArgs e) { if (e.Button == MouseButtons.Left) { dataGridView.ClearSelection(); Rectangle rect = dataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true); ColumnBase col = this.ColumnGroup.Columns.ElementAt(e.ColumnIndex); txtColumnName.Text = col.Name; txtColumnName.Size = rect.Size; txtColumnName.Location = rect.Location; txtColumnName.Visible = true; txtColumnName.Focus(); txtColumnName.BringToFront(); } } private void UpdateSortGlyph() { System.Collections.IEnumerator e = this.dataGridView.Columns.GetEnumerator(); e.MoveNext(); foreach (SortOrder sortOrder in this.ColumnGroup.SortOrdersForColumns) { ((DataGridViewColumn)e.Current).HeaderCell.SortGlyphDirection = sortOrder; e.MoveNext(); } } private void dataGridView_SelectionChanged(object sender, EventArgs e) { int nullValuesCount = 0; int totalValuesCount = 0; IComparable minimum = ""; IComparable maximum = ""; double? mean = null; double? median = null; double? stddev = null; ColumnBase column; this.ColumnGroup.ClearSelectedColumns(); if (this.dataGridView.SelectedColumns.Count != 0) { foreach (DataGridViewColumn col in this.dataGridView.SelectedColumns) { column = this.ColumnGroup.Columns.ElementAt(col.Index); column.Selected = true; nullValuesCount += column.NullValuesCount; totalValuesCount += column.NonNullValuesCount; } if (this.dataGridView.SelectedColumns.Count == 1) { column = this.ColumnGroup.Columns.ElementAt(dataGridView.SelectedColumns[0].Index); minimum = column.Minimum; maximum = column.Maximum; if (column is DoubleColumn) { mean = ((DoubleColumn)column).Mean; stddev = ((DoubleColumn)column).StandardDeviation; median = ((DoubleColumn)column).Median; } } } lblNullValues.Text = nullValuesCount.ToString(); double nullPercentage = (100.0 / (double)(totalValuesCount + nullValuesCount)) * (double)nullValuesCount; lblNullPercentage.Text = Double.IsNaN(nullPercentage) ? "" : nullPercentage.ToString(); lblValues.Text = totalValuesCount.ToString(); lblMinimum.Text = minimum == null ? "" : minimum.ToString(); lblMaximum.Text = maximum == null ? "" : maximum.ToString(); lblMean.Text = mean == null ? "" : mean.ToString(); lblStdDev.Text = stddev == null ? "" : stddev.ToString(); lblMedian.Text = median == null ? "" : median.ToString(); UpdateStateInformation(true); } private void dataGridView_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) { RecalculateWidthOfControl(); } private void RecalculateWidthOfControl() { int width = 0; foreach (DataGridViewColumn col in this.dataGridView.Columns) width += col.Width; //no columns in datagrid if (width != 0) { width += this.dataGridView.RowHeadersWidth; //datagridview.controls[1] is always the vertical scrollbar if (dataGridView.Controls[1].Visible) width += 20; } this.dataGridView.Width = width; this.Width = width; } private void dataGridView_MouseClick(object sender, MouseEventArgs e) { this.ColumnGroupActive = true; this.FireActivated(false); if (this.dataGridView.AllowUserToOrderColumns || e.Clicks >= 2) return; System.Windows.Forms.DataGridView.HitTestInfo hit = dataGridView.HitTest(e.X, e.Y); // row header click if (hit.ColumnIndex == -1 && hit.RowIndex >= 0) { if (e.Button == MouseButtons.Right) { this.commandChain.Add(new InsertRowCommand(DataSet, this.ColumnGroup.Name, hit.RowIndex)); } else { if (dataGridView.SelectionMode != DataGridViewSelectionMode.RowHeaderSelect) dataGridView.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect; } } // column header click else if (hit.RowIndex == -1 && hit.ColumnIndex >= 0 && e.Button == MouseButtons.Left) { if (dataGridView.SelectionMode != DataGridViewSelectionMode.ColumnHeaderSelect) dataGridView.SelectionMode = DataGridViewSelectionMode.ColumnHeaderSelect; } } private void dataGridView_AllowUserToOrderColumnsChanged(object sender, EventArgs e) { if (this.dataGridView.AllowUserToOrderColumns) this.dataGridView.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect; } private void dataGridView_KeyDown(object sender, KeyEventArgs e) { //handle deletion of columns and emptying of cells if (e.KeyCode == Keys.Delete) { if (this.dataGridView.SelectedColumns.Count != 0) this.commandChain.Add(new DeleteColumnCommand(DataSet, this.ColumnGroup.Name, ColumnGroup.SelectedColumnIndexes)); else if (this.dataGridView.SelectedRows.Count != 0) { //to ensure that cells are not emptied before the rows got deleted //deleting of rows handled by user deleting rows } else if (this.dataGridView.SelectedCells.Count != 0) { List cells = new List(); foreach (DataGridViewCell cell in this.dataGridView.SelectedCells) { if (cell.RowIndex < this.ColumnGroup.Columns.ElementAt(cell.ColumnIndex).TotalValuesCount && cell.Value != null) cells.Add(new Point(cell.ColumnIndex, cell.RowIndex)); } if (cells.Count != 0) this.commandChain.Add(new ChangeValuesToNullCommand(DataSet, this.ColumnGroup.Name, cells)); } } //handle paste of values else if (e.Control && e.KeyCode == Keys.V) PasteClipboardContent(); else if (e.Control && e.KeyCode == Keys.C) CopyClipboardContent(); } private void dataGridView_RowHeadersWidthChanged(object sender, EventArgs e) { this.RecalculateWidthOfControl(); } private void dataGridView_CellMouseEnter(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex == -1 && e.ColumnIndex != -1) { toolTip.SetToolTip(this.dataGridView, ToExcelColumnIndex(e.ColumnIndex)); } else if (toolTip.Active) { toolTip.RemoveAll(); } } private void dataGridView_Resize(object sender, EventArgs e) { UpdateDataGridViewHeaderCells(); } private void dataGridView_Scroll(object sender, ScrollEventArgs e) { if (e.ScrollOrientation == ScrollOrientation.VerticalScroll) UpdateDataGridViewHeaderCells(); } private string ToExcelColumnIndex(int index) { #region digits var digits = new char[] { '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; int b = digits.Length - 1; #endregion string excelIndex = string.Empty; index = index + 1; while (index > 0) { int d = index / b; int rem = index % b; index = d; excelIndex = digits[rem] + excelIndex; } return excelIndex; } #region DataGridView virtual mode event handler private void dataGridView_CellValueNeeded(object sender, System.Windows.Forms.DataGridViewCellValueEventArgs e) { if (e.ColumnIndex >= ColumnGroup.Columns.Count()) return; if (e.RowIndex >= ColumnGroup.RowCount) return; e.Value = ColumnGroup.Columns.ElementAt(e.ColumnIndex).GetValue(e.RowIndex); } private void dataGridView_CellValuePushed(object sender, System.Windows.Forms.DataGridViewCellValueEventArgs e) { IComparable value = null; try { if (e.Value == null) value = null; else if (this.ColumnGroup.Columns.ElementAt(e.ColumnIndex).DataType == typeof(double?)) value = double.Parse((string)e.Value); else if (this.ColumnGroup.Columns.ElementAt(e.ColumnIndex).DataType == typeof(DateTime?)) value = DateTime.Parse((string)e.Value); else value = e.Value.ToString(); } catch (FormatException) { } if (e.RowIndex == this.dataGridView.RowCount - 1) { IComparable[] row = new IComparable[this.ColumnGroup.Columns.Count()]; row[e.ColumnIndex] = value; this.commandChain.Add(new AddRowCommand(this.DataSet, this.ColumnGroup.Name, row)); } else { this.commandChain.Add(new ChangeValueCommand(this.DataSet, this.ColumnGroup.Name, e.ColumnIndex, e.RowIndex, value)); } } private void dataGridView_UserDeletingRow(object sender, System.Windows.Forms.DataGridViewRowCancelEventArgs e) { e.Cancel = true; if (e.Row.Index < this.ColumnGroup.RowCount) { List positions; if (this.dataGridView.AreAllCellsSelected(true)) positions = Enumerable.Range(0, this.ColumnGroup.RowCount).ToList(); else { positions = new List(); for (int i = 0; i < this.dataGridView.SelectedRows.Count; i++) if (this.dataGridView.SelectedRows[i].Index < this.ColumnGroup.RowCount) positions.Add(this.dataGridView.SelectedRows[i].Index); positions.Sort(); } if (positions.Count != 0) { this.commandChain.Add(new DeleteRowsCommand(DataSet, this.ColumnGroup.Name, positions)); } this.dataGridView.ClearSelection(); } } #endregion #region txtColumnName event handler private void txtColumnName_Validated(object source, EventArgs e) { if (!this.txtColumnName.Visible) return; this.txtColumnName.Visible = false; DataGridView.HitTestInfo h = this.dataGridView.HitTest(txtColumnName.Location.X, txtColumnName.Location.Y); if (txtColumnName.Text != this.ColumnGroup.GetColumn(h.ColumnIndex).Name) this.commandChain.Add(new RenameColumnCommand(DataSet, this.ColumnGroup.Name, h.ColumnIndex, txtColumnName.Text)); } private void txtColumnName_KeyDown(object source, KeyEventArgs e) { if (e.KeyCode == Keys.Escape) { DataGridView.HitTestInfo h = this.dataGridView.HitTest(txtColumnName.Location.X, txtColumnName.Location.Y); this.txtColumnName.Text = this.ColumnGroup.GetColumn(h.ColumnIndex).Name; dataGridView.Select(); } else if (e.KeyCode == Keys.Enter) dataGridView.Select(); } #endregion #region txtColumnGroupName event handler private void txtColumnGroupName_Click(object sender, EventArgs e) { bool ctrlPressed = (Control.ModifierKeys & Keys.Control) == Keys.Control; this.dataGridView.ClearSelection(); this.ColumnGroupActive = !this.ColumnGroupActive; FireActivated(ctrlPressed); UpdateStateInformation(); } private void txtColumnGroupName_DoubleClick(object sender, EventArgs e) { txtColumnGroupName.ReadOnly = false; txtColumnGroupName.Text = ColumnGroup.Name; } private void txtColumnGroupName_Leave(object sender, EventArgs e) { if (!txtColumnGroupName.ReadOnly) { txtColumnGroupName.ReadOnly = true; if (CheckIfNewColumnGroupNameIsAllowed(txtColumnGroupName.Text)) this.commandChain.Add(new RenameColumnGroupCommand(DataSet, this.ColumnGroup.Name, txtColumnGroupName.Text)); } } private void txtColumnGroupName_KeyDown(object sender, KeyEventArgs e) { if (this.txtColumnGroupName.ReadOnly) //user can not change the name if it is readonly return; if (e.KeyCode != Keys.Enter && e.KeyCode != Keys.Escape) return; if (e.KeyCode == Keys.Enter && CheckIfNewColumnGroupNameIsAllowed(txtColumnGroupName.Text)) { this.commandChain.Add(new RenameColumnGroupCommand(DataSet, this.ColumnGroup.Name, txtColumnGroupName.Text)); } txtColumnGroupName.ReadOnly = true; this.Focus(); this.Refresh(); } private bool CheckIfNewColumnGroupNameIsAllowed(string newName) { return this.DataSet.ColumnGroups.All(cg => cg.Name != newName); } #endregion } }