// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Linq;
public sealed class DetailsListView : ListView
{
private Rectangle changeBounds;
- private EventHandlerList _handlers = new EventHandlerList();
- public event EventHandler VScrolled;
- public event EventHandler HScrolled;
+ public ContextMenuStrip? ColumnHeaderContextMenuStrip { get; set; }
+
+ public event EventHandler? VScrolled;
+ public event EventHandler? HScrolled;
public DetailsListView()
{
DoubleBuffered = true;
}
- //[System.ComponentModel.DefaultValue(0),
- // System.ComponentModel.RefreshProperties(System.ComponentModel.RefreshProperties.Repaint)]
- //public new int VirtualListSize
- //{
- // get { return base.VirtualListSize; }
- // set
- // {
- // if (value == base.VirtualListSize) return;
- // if (base.VirtualListSize > 0 && value > 0)
- // {
- // int topIndex = 0;
- // if (!this.IsDisposed)
- // {
- // if (base.VirtualListSize < value)
- // {
- // if (this.TopItem == null)
- // {
- // topIndex = 0;
- // }
- // else
- // {
- // topIndex = this.TopItem.Index;
- // }
- // topIndex = Math.Min(topIndex, Math.Abs(value - 1));
- // this.TopItem = this.Items[topIndex];
- // }
- // else
- // {
- // if (this.TopItem == null)
- // {
- // topIndex = 0;
- // }
- // else
- // {
- //
- // }
- // this.TopItem = this.Items[0];
- // }
- // }
- // }
- // base.VirtualListSize = value;
- // }
- //}
-
- public void ChangeItemBackColor(int index, Color backColor)
+ /// <summary>
+ /// 複数選択時の起点になるアイテム (selection mark) の位置を取得・設定する
+ /// </summary>
+ /// <remarks>
+ /// Items[idx].Selected の設定では mark が設定されるが、SelectedIndices.Add(idx) では設定されないため、
+ /// 主に後者と合わせて使用する
+ /// </remarks>
+ public int SelectionMark
{
- ChangeSubItemBackColor(index, 0, backColor);
+ get => NativeMethods.ListView_GetSelectionMark(this.Handle);
+ set => NativeMethods.ListView_SetSelectionMark(this.Handle, value);
}
- public void ChangeItemForeColor(int index, Color foreColor)
+ public void SelectItems(int[] indices)
{
- ChangeSubItemForeColor(index, 0, foreColor);
- }
+ foreach (var index in indices)
+ {
+ if (index < 0 || index >= this.VirtualListSize)
+ throw new ArgumentOutOfRangeException(nameof(indices));
- public void ChangeItemFont(int index, Font fnt)
- {
- ChangeSubItemFont(index, 0, fnt);
- }
+ NativeMethods.SelectItem(this, index);
+ }
- public void ChangeItemFontAndColor(int index, Color foreColor, Font fnt)
- {
- ChangeSubItemStyles(index, 0, BackColor, foreColor, fnt);
+ this.OnSelectedIndexChanged(EventArgs.Empty);
}
- public void ChangeItemStyles(int index, Color backColor, Color foreColor, Font fnt)
+ public void SelectAllItems()
{
- ChangeSubItemStyles(index, 0, backColor, foreColor, fnt);
- }
+ NativeMethods.SelectAllItems(this);
- public void ChangeSubItemBackColor(int itemIndex, int subitemIndex, Color backColor)
- {
- var item = this.Items[itemIndex];
- item.SubItems[subitemIndex].BackColor = backColor;
- SetUpdateBounds(item, subitemIndex);
- this.Update();
- this.changeBounds = Rectangle.Empty;
+ this.OnSelectedIndexChanged(EventArgs.Empty);
}
- public void ChangeSubItemForeColor(int itemIndex, int subitemIndex, Color foreColor)
+ public void ChangeItemBackColor(ListViewItem item, Color backColor)
{
- var item = this.Items[itemIndex];
- item.SubItems[subitemIndex].ForeColor = foreColor;
- SetUpdateBounds(item, subitemIndex);
- this.Update();
- this.changeBounds = Rectangle.Empty;
- }
+ if (item.BackColor == backColor)
+ return;
- public void ChangeSubItemFont(int itemIndex, int subitemIndex, Font fnt)
- {
- var item = this.Items[itemIndex];
- item.SubItems[subitemIndex].Font = fnt;
- SetUpdateBounds(item, subitemIndex);
- this.Update();
- this.changeBounds = Rectangle.Empty;
+ item.BackColor = backColor;
+ this.RefreshItemBounds(item);
}
- public void ChangeSubItemFontAndColor(int itemIndex, int subitemIndex, Color foreColor, Font fnt)
+ public void ChangeItemForeColor(ListViewItem item, Color foreColor)
{
- var item = this.Items[itemIndex];
- var subItem = item.SubItems[subitemIndex];
- subItem.ForeColor = foreColor;
- subItem.Font = fnt;
- SetUpdateBounds(item, subitemIndex);
- this.Update();
- this.changeBounds = Rectangle.Empty;
+ if (item.ForeColor == foreColor)
+ return;
+
+ item.ForeColor = foreColor;
+ this.RefreshItemBounds(item);
}
- public void ChangeSubItemStyles(int itemIndex, int subitemIndex, Color backColor, Color foreColor, Font fnt)
+ public void ChangeItemFontAndColor(ListViewItem item, Color foreColor, Font fnt)
{
- var item = this.Items[itemIndex];
- var subItem = item.SubItems[subitemIndex];
- subItem.BackColor = backColor;
- subItem.ForeColor = foreColor;
- subItem.Font = fnt;
- SetUpdateBounds(item, subitemIndex);
- this.Update();
- this.changeBounds = Rectangle.Empty;
+ if (item.ForeColor == foreColor && item.Font.Equals(fnt))
+ return;
+
+ item.ForeColor = foreColor;
+ item.Font = fnt;
+ this.RefreshItemBounds(item);
}
- private void SetUpdateBounds(ListViewItem item, int subItemIndex)
+ private void RefreshItemBounds(ListViewItem item)
{
try
{
- if (subItemIndex > this.Columns.Count)
- {
- throw new ArgumentOutOfRangeException("subItemIndex");
- }
- if (item.UseItemStyleForSubItems)
- {
- this.changeBounds = item.Bounds;
- }
- else
- {
- this.changeBounds = this.GetSubItemBounds(item, subItemIndex);
- }
+ var itemBounds = item.Bounds;
+ var drawBounds = Rectangle.Intersect(this.ClientRectangle, itemBounds);
+ if (drawBounds == Rectangle.Empty)
+ return;
+
+ this.changeBounds = drawBounds;
+ this.Update();
+ this.changeBounds = Rectangle.Empty;
}
catch (ArgumentException)
{
}
}
- private Rectangle GetSubItemBounds(ListViewItem item, int subitemIndex)
- {
- if (subitemIndex == 0 && this.Columns.Count > 0)
- {
- Rectangle col0 = item.Bounds;
- return new Rectangle(col0.Left, col0.Top, item.SubItems[1].Bounds.X + 1, col0.Height);
- }
- else
- {
- return item.SubItems[subitemIndex].Bounds;
- }
- }
-
[StructLayout(LayoutKind.Sequential)]
- private struct SCROLLINFO
- {
- public int cbSize;
- public int fMask;
- public int nMin;
- public int nMax;
- public int nPage;
- public int nPos;
- public int nTrackPos;
- }
-
- private enum ScrollBarDirection
- {
- SB_HORZ = 0,
- SB_VERT = 1,
- SB_CTL = 2,
- SB_BOTH = 3,
- }
-
- private enum ScrollInfoMask
+ private struct NMHDR
{
- SIF_RANGE = 0x1,
- SIF_PAGE = 0x2,
- SIF_POS = 0x4,
- SIF_DISABLENOSCROLL = 0x8,
- SIF_TRACKPOS = 0x10,
- SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS),
+ public IntPtr hwndFrom;
+ public IntPtr idFrom;
+ public int code;
}
- [DllImport("user32.dll")]
- private static extern int GetScrollInfo(IntPtr hWnd, ScrollBarDirection fnBar, ref SCROLLINFO lpsi);
-
- private SCROLLINFO si = new SCROLLINFO {
- cbSize = Marshal.SizeOf(new SCROLLINFO()),
- fMask = (int)ScrollInfoMask.SIF_POS
- };
-
- [DebuggerStepThrough()]
+ [DebuggerStepThrough]
protected override void WndProc(ref Message m)
{
const int WM_ERASEBKGND = 0x14;
const int WM_HSCROLL = 0x114;
const int WM_VSCROLL = 0x115;
const int WM_KEYDOWN = 0x100;
+ const int WM_USER = 0x400;
+ const int WM_REFLECT = WM_USER + 0x1C00;
+ const int WM_NOTIFY = 0x004E;
+ const int WM_CONTEXTMENU = 0x7B;
const int LVM_SETITEMCOUNT = 0x102F;
+ const int LVN_ODSTATECHANGED = ((0 - 100) - 15);
const long LVSICF_NOSCROLL = 0x2;
const long LVSICF_NOINVALIDATEALL = 0x1;
- int hPos = -1;
- int vPos = -1;
+ var hPos = -1;
+ var vPos = -1;
switch (m.Msg)
{
case WM_PAINT:
if (this.changeBounds != Rectangle.Empty)
{
- Win32Api.ValidateRect(this.Handle, IntPtr.Zero);
+ NativeMethods.ValidateRect(this.Handle, IntPtr.Zero);
this.Invalidate(this.changeBounds);
this.changeBounds = Rectangle.Empty;
}
break;
case WM_HSCROLL:
- if (HScrolled != null)
- HScrolled(this, EventArgs.Empty);
+ HScrolled?.Invoke(this, EventArgs.Empty);
break;
case WM_VSCROLL:
- if (VScrolled != null)
- VScrolled(this, EventArgs.Empty);
+ VScrolled?.Invoke(this, EventArgs.Empty);
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
case WM_KEYDOWN:
- if (GetScrollInfo(this.Handle, ScrollBarDirection.SB_VERT, ref si) != 0)
- vPos = si.nPos;
- if (GetScrollInfo(this.Handle, ScrollBarDirection.SB_HORZ, ref si) != 0)
- hPos = si.nPos;
+ vPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT);
+ hPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ);
+ break;
+ case WM_CONTEXTMENU:
+ if (m.WParam != this.Handle)
+ {
+ //カラムヘッダメニューを表示
+ this.ColumnHeaderContextMenuStrip?.Show(new Point(m.LParam.ToInt32()));
+ return;
+ }
break;
case LVM_SETITEMCOUNT:
m.LParam = new IntPtr(LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL);
break;
+ case WM_REFLECT + WM_NOTIFY:
+ var nmhdr = Marshal.PtrToStructure<NMHDR>(m.LParam);
+
+ // Ctrl+クリックで選択状態を変更した場合にイベントが発生しない問題への対処
+ if (nmhdr.code == LVN_ODSTATECHANGED)
+ this.OnSelectedIndexChanged(EventArgs.Empty);
+ break;
}
try
if (this.IsDisposed) return;
if (vPos != -1)
- if (GetScrollInfo(this.Handle, ScrollBarDirection.SB_VERT, ref si) != 0 && vPos != si.nPos)
- if (VScrolled != null)
- VScrolled(this, EventArgs.Empty);
+ if (vPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT))
+ VScrolled?.Invoke(this, EventArgs.Empty);
if (hPos != -1)
- if (GetScrollInfo(this.Handle, ScrollBarDirection.SB_HORZ, ref si) != 0 && hPos != si.nPos)
- if (HScrolled != null)
- HScrolled(this, EventArgs.Empty);
+ if (hPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ))
+ HScrolled?.Invoke(this, EventArgs.Empty);
}
}
}