#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 int idFrom;
public int 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;
}
}
}
}