#region License Information (GPL v3) /* ShareX - A program that allows you to take screenshots and share any file type Copyright (c) 2007-2018 ShareX Team 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Optionally you can also view the license at . */ #endregion License Information (GPL v3) using System; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; namespace ShareX.UploadersLib { /// /// Inherited ListView to allow in-place editing of subitems /// public class ListViewEx : ListView { #region Interop structs, imports and constants /// /// MessageHeader for WM_NOTIFY /// [StructLayout(LayoutKind.Sequential)] private struct NMHDR { public IntPtr hwndFrom; public Int32 idFrom; public Int32 code; } [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wPar, IntPtr lPar); [DllImport("user32.dll", CharSet = CharSet.Ansi)] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int len, ref int[] order); // ListView messages private const int LVM_FIRST = 0x1000; private const int LVM_GETCOLUMNORDERARRAY = LVM_FIRST + 59; // Windows Messages that will abort editing private const int WM_HSCROLL = 0x114; private const int WM_VSCROLL = 0x115; private const int WM_SIZE = 0x05; private const int WM_NOTIFY = 0x4E; private const int HDN_FIRST = -300; private const int HDN_BEGINDRAG = HDN_FIRST - 10; private const int HDN_ITEMCHANGINGA = HDN_FIRST - 0; private const int HDN_ITEMCHANGINGW = HDN_FIRST - 20; #endregion Interop structs, imports and constants /// /// Required designer variable. /// private Container components; public event SubItemEventHandler SubItemClicked; public event SubItemEventHandler SubItemBeginEditing; public event SubItemEndEditingEventHandler SubItemEndEditing; public ListViewEx() { DoubleClickActivation = false; // This call is required by the Windows.Forms Form Designer. InitializeComponent(); FullRowSelect = true; View = View.Details; AllowColumnReorder = true; } /// /// Clean up any resources being used. /// protected override void Dispose(bool disposing) { if (disposing) { if (components != null) components.Dispose(); } base.Dispose(disposing); } #region Component Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { components = new Container(); } #endregion Component Designer generated code /// /// Is a double click required to start editing a cell? /// public bool DoubleClickActivation { get; set; } /// /// Retrieve the order in which columns appear /// /// Current display order of column indices public int[] GetColumnOrder() { IntPtr lPar = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)) * Columns.Count); IntPtr res = SendMessage(Handle, LVM_GETCOLUMNORDERARRAY, new IntPtr(Columns.Count), lPar); if (res.ToInt32() == 0) // Something went wrong { Marshal.FreeHGlobal(lPar); return null; } int[] order = new int[Columns.Count]; Marshal.Copy(lPar, order, 0, Columns.Count); Marshal.FreeHGlobal(lPar); return order; } /// /// Find ListViewItem and SubItem Index at position (x,y) /// /// relative to ListView /// relative to ListView /// Item at position (x,y) /// SubItem index public int GetSubItemAt(int x, int y, out ListViewItem item) { item = GetItemAt(x, y); if (item != null) { int[] order = GetColumnOrder(); Rectangle lviBounds = item.GetBounds(ItemBoundsPortion.Entire); int subItemX = lviBounds.Left; foreach (int t in order) { ColumnHeader h = Columns[t]; if (x < subItemX + h.Width) { return h.Index; } subItemX += h.Width; } } return -1; } /// /// Get bounds for a SubItem /// /// Target ListViewItem /// Target SubItem index /// Bounds of SubItem (relative to ListView) public Rectangle GetSubItemBounds(ListViewItem Item, int SubItem) { int[] order = GetColumnOrder(); if (SubItem >= order.Length) throw new IndexOutOfRangeException("SubItem " + SubItem + " out of range"); if (Item == null) throw new ArgumentNullException("Item"); Rectangle lviBounds = Item.GetBounds(ItemBoundsPortion.Entire); int subItemX = lviBounds.Left; ColumnHeader col; int i; for (i = 0; i < order.Length; i++) { col = Columns[order[i]]; if (col.Index == SubItem) break; subItemX += col.Width; } Rectangle subItemRect = new Rectangle(subItemX, lviBounds.Top, Columns[order[i]].Width, lviBounds.Height); return subItemRect; } protected override void WndProc(ref Message msg) { switch (msg.Msg) { // Look for WM_VSCROLL,WM_HSCROLL or WM_SIZE messages. case WM_VSCROLL: case WM_HSCROLL: case WM_SIZE: EndEditing(false); break; case WM_NOTIFY: // Look for WM_NOTIFY of events that might also change the // editor's position/size: Column reordering or resizing NMHDR h = (NMHDR)Marshal.PtrToStructure(msg.LParam, typeof(NMHDR)); if (h.code == HDN_BEGINDRAG || h.code == HDN_ITEMCHANGINGA || h.code == HDN_ITEMCHANGINGW) EndEditing(false); break; } base.WndProc(ref msg); } #region Initialize editing depending of DoubleClickActivation property protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (DoubleClickActivation) { return; } EditSubitemAt(new Point(e.X, e.Y)); } protected override void OnDoubleClick(EventArgs e) { base.OnDoubleClick(e); if (!DoubleClickActivation) { return; } Point pt = PointToClient(Cursor.Position); EditSubitemAt(pt); } /// /// Fire SubItemClicked /// ///Point of click/doubleclick private void EditSubitemAt(Point p) { ListViewItem item; int idx = GetSubItemAt(p.X, p.Y, out item); if (idx >= 0) { OnSubItemClicked(new SubItemEventArgs(item, idx)); } } #endregion Initialize editing depending of DoubleClickActivation property #region In-place editing functions // The control performing the actual editing private Control _editingControl; // The LVI being edited private ListViewItem _editItem; // The SubItem being edited private int _editSubItem; protected void OnSubItemBeginEditing(SubItemEventArgs e) { if (SubItemBeginEditing != null) SubItemBeginEditing(this, e); } protected void OnSubItemEndEditing(SubItemEndEditingEventArgs e) { if (SubItemEndEditing != null) SubItemEndEditing(this, e); } protected void OnSubItemClicked(SubItemEventArgs e) { if (SubItemClicked != null) SubItemClicked(this, e); } /// /// Begin in-place editing of given cell /// /// Control used as cell editor /// ListViewItem to edit /// SubItem index to edit public void StartEditing(Control c, ListViewItem Item, int SubItem) { OnSubItemBeginEditing(new SubItemEventArgs(Item, SubItem)); Rectangle rcSubItem = GetSubItemBounds(Item, SubItem); if (rcSubItem.X < 0) { // Left edge of SubItem not visible - adjust rectangle position and width rcSubItem.Width += rcSubItem.X; rcSubItem.X = 0; } if (rcSubItem.X + rcSubItem.Width > Width) { // Right edge of SubItem not visible - adjust rectangle width rcSubItem.Width = Width - rcSubItem.Left; } // Subitem bounds are relative to the location of the ListView! rcSubItem.Offset(Left, Top); // In case the editing control and the listview are on different parents, // account for different origins Point origin = new Point(0, 0); Point lvOrigin = Parent.PointToScreen(origin); Point ctlOrigin = c.Parent.PointToScreen(origin); rcSubItem.Offset(lvOrigin.X - ctlOrigin.X, lvOrigin.Y - ctlOrigin.Y); // Position and show editor c.Bounds = rcSubItem; c.Text = Item.SubItems[SubItem].Text; c.Visible = true; c.BringToFront(); c.Focus(); _editingControl = c; _editingControl.Leave += _editControl_Leave; _editingControl.KeyPress += _editControl_KeyPress; _editItem = Item; _editSubItem = SubItem; } private void _editControl_Leave(object sender, EventArgs e) { // cell editor losing focus EndEditing(true); } private void _editControl_KeyPress(object sender, KeyPressEventArgs e) { switch (e.KeyChar) { case (char)(int)Keys.Escape: { EndEditing(false); break; } case (char)(int)Keys.Enter: { EndEditing(true); break; } } } /// /// Accept or discard current value of cell editor control /// /// Use the _editingControl's Text as new SubItem text or discard changes? public void EndEditing(bool AcceptChanges) { if (_editingControl == null) return; SubItemEndEditingEventArgs e = new SubItemEndEditingEventArgs( _editItem, // The item being edited _editSubItem, // The subitem index being edited AcceptChanges ? _editingControl.Text : // Use editControl text if changes are accepted _editItem.SubItems[_editSubItem].Text, // or the original subitem's text, if changes are discarded !AcceptChanges // Cancel? ); OnSubItemEndEditing(e); if (!string.IsNullOrEmpty(e.DisplayText)) { _editItem.SubItems[_editSubItem].Text = e.DisplayText; } _editingControl.Leave -= _editControl_Leave; _editingControl.KeyPress -= _editControl_KeyPress; _editingControl.Visible = false; _editingControl = null; _editItem = null; _editSubItem = -1; } #endregion In-place editing functions } /// /// Event Handler for SubItem events /// public delegate void SubItemEventHandler(object sender, SubItemEventArgs e); /// /// Event Handler for SubItemEndEditing events /// public delegate void SubItemEndEditingEventHandler(object sender, SubItemEndEditingEventArgs e); /// /// Event Args for SubItemClicked event /// public class SubItemEventArgs : EventArgs { public SubItemEventArgs(ListViewItem item, int subItem) { _subItemIndex = subItem; _item = item; } private int _subItemIndex = -1; private ListViewItem _item; public int SubItem { get { return _subItemIndex; } } public ListViewItem Item { get { return _item; } } } /// /// Event Args for SubItemEndEditingClicked event /// public class SubItemEndEditingEventArgs : SubItemEventArgs { private string _text = ""; private bool _cancel = true; public SubItemEndEditingEventArgs(ListViewItem item, int subItem, string display, bool cancel) : base(item, subItem) { _text = display; _cancel = cancel; } public string DisplayText { get { return _text; } set { _text = value; } } public bool Cancel { get { return _cancel; } set { _cancel = value; } } } }