/* * 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 . * * 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 { /// /// 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). /// public enum CellEditAtEdgeBehaviour { /// /// 按键将被忽略 /// Ignore, /// ///按键将导致单元格编辑环绕到相对边缘的单元格。 /// Wrap, /// /// 按键将Wrap,但该列将更改为相应的相邻列。这只对正常操作为ChangeRow的键有意义。 /// ChangeColumn, /// ///按键将Wrap,但该行将更改为相应的相邻行。这只对正常操作为ChangeColumn的键有意义. /// ChangeRow, /// ///该键将导致当前编辑操作结束。 /// EndEdit }; /// /// Indicates the normal behaviour of a key when used during a cell edit /// operation. /// public enum CellEditCharacterBehaviour { /// /// The key press will be ignored /// Ignore, /// /// The key press will end the current edit and begin an edit /// operation on the next editable cell to the left. /// ChangeColumnLeft, /// /// The key press will end the current edit and begin an edit /// operation on the next editable cell to the right. /// ChangeColumnRight, /// /// The key press will end the current edit and begin an edit /// operation on the row above. /// ChangeRowUp, /// /// The key press will end the current edit and begin an edit /// operation on the row below /// ChangeRowDown, /// /// The key press will cancel the current edit /// CancelEdit, /// /// The key press will finish the current edit operation /// EndEdit, /// /// Custom verb that can be used for specialized actions. /// CustomVerb1, /// /// Custom verb that can be used for specialized actions. /// CustomVerb2, /// /// Custom verb that can be used for specialized actions. /// CustomVerb3, /// /// Custom verb that can be used for specialized actions. /// CustomVerb4, /// /// Custom verb that can be used for specialized actions. /// CustomVerb5, /// /// Custom verb that can be used for specialized actions. /// CustomVerb6, /// /// Custom verb that can be used for specialized actions. /// CustomVerb7, /// /// Custom verb that can be used for specialized actions. /// CustomVerb8, /// /// Custom verb that can be used for specialized actions. /// CustomVerb9, /// /// Custom verb that can be used for specialized actions. /// CustomVerb10, }; /// /// Instances of this class handle key presses during a cell edit operation. /// public class CellEditKeyEngine { #region Public interface /// /// 设置给定键的行为 /// /// /// /// public virtual void SetKeyBehaviour(Keys key, CellEditCharacterBehaviour normalBehaviour, CellEditAtEdgeBehaviour atEdgeBehaviour) { this.CellEditKeyMap[key] = normalBehaviour; this.CellEditKeyAtEdgeBehaviourMap[key] = atEdgeBehaviour; } /// /// 处理按键操作 /// /// /// /// True if the key was completely handled. 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 /// /// Gets or sets the ObjectListView on which the current key is being handled. /// This cannot be null. /// protected ObjectListView ListView { get { return listView; } set { listView = value; } } private ObjectListView listView; /// /// Gets the row of the cell that is currently being edited /// protected OLVListItem ItemBeingEdited { get { return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? null : this.ListView.CellEditEventArgs.ListViewItem; } } /// /// Gets the index of the column of the cell that is being edited /// protected int SubItemIndexBeingEdited { get { return (this.ListView == null || this.ListView.CellEditEventArgs == null) ? -1 : this.ListView.CellEditEventArgs.SubItemIndex; } } /// /// Gets or sets the map that remembers the normal behaviour of keys /// protected IDictionary CellEditKeyMap { get { if (cellEditKeyMap == null) this.InitializeCellEditKeyMaps(); return cellEditKeyMap; } set { cellEditKeyMap = value; } } private IDictionary cellEditKeyMap; /// /// Gets or sets the map that remembers the desired behaviour of keys /// on edge cases. /// protected IDictionary CellEditKeyAtEdgeBehaviourMap { get { if (cellEditKeyAtEdgeBehaviourMap == null) this.InitializeCellEditKeyMaps(); return cellEditKeyAtEdgeBehaviourMap; } set { cellEditKeyAtEdgeBehaviourMap = value; } } private IDictionary cellEditKeyAtEdgeBehaviourMap; #endregion #region Initialization /// /// Setup the default key mapping /// protected virtual void InitializeCellEditKeyMaps() { this.cellEditKeyMap = new Dictionary(); 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(); 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 /// /// Handle the end edit command /// protected virtual void HandleEndEdit() { this.ListView.PossibleFinishCellEditing(); } /// /// Handle the cancel edit command /// protected virtual void HandleCancelEdit() { this.ListView.CancelCellEdit(); } /// /// Placeholder that subclasses can override to handle any custom verbs /// /// /// /// protected virtual bool HandleCustomVerb(Keys keyData, CellEditCharacterBehaviour behaviour) { return false; } /// /// Handle a change row command /// /// /// 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 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; } } /// /// Handle a change column command /// /// /// 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 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 /// /// Start editing the indicated cell if that cell is not already being edited /// /// The row to edit /// The cell within that row to edit 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); } /// /// 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. /// /// The row whose neighbour is sought /// The direction of the adjacentness /// An OLVListView adjacent to the given item, or null if there are no more enabled items in that direction. 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; } /// /// Gets the adjacent item to the given item in the given direction, wrapping if needed. /// /// The row whose neighbour is sought /// The direction of the adjacentness /// An OLVListView adjacent to the given item, or null if there are no more items in that direction. protected OLVListItem GetAdjacentItem(OLVListItem olvi, bool up) { return this.GetAdjacentItemOrNull(olvi, up) ?? this.GetAdjacentItemOrNull(null, up); } /// /// Gets a collection of columns that are editable in the order they are shown to the user /// protected List EditableColumnsInDisplayOrder { get { List editableColumnsInDisplayOrder = new List(); foreach (OLVColumn x in this.ListView.ColumnsInDisplayOrder) if (x.IsEditable) editableColumnsInDisplayOrder.Add(x); return editableColumnsInDisplayOrder; } } #endregion } }