I am receiving a ArgumentOutOfRangeException while calling base.WndProc in an OwnerDrawn listview.
The exception occurs after a click is performed to the right (empty space) of the last subitem of any ListViewItem.
The listview column widths are programmatically set to fill the entire listview, however on occasion data is not received as expected leading to some extra space, this is an edge case which usually does not occur.
The message to be processed at the time of exception is WM_LBUTTONDOWN (0x0201), or WM_RBUTTONDOWN (0x0204).
All data for painting the LV subitems is from a class referenced by the tag of the LVI, at no point do I try to read the subitems, nor do I write data to any of the subitems. Below is the smallest reproducible code, in C# (10.0), using .NET 6.0 that shows the exception.
I attempted to simply exclude processing of WM_LBUTTONDOWN. This did remove the exception, however it also stopped left click events from reaching MouseUp which is required. (also for right clicks)
Whilst I am working to fix my errors in column sizing after receiving bad or no data, I would like to check for this possible exception and simply return from the method before the exception can occur.
Data class
namespace testCase;
class myClass
{
public string s1 = "subitem1";
public string s2 = "subitem2";
}
Form code
namespace testCase;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ListViewExWatch lv = new();
Controls.Add(lv);
lv.MouseUp += Lv_MouseUp;
lv.Top = 0;
lv.Left = 0;
lv.Width = ClientSize.Width;
lv.Height = ClientSize.Height;
lv.OwnerDraw = true;
lv.BackColor = Color.AntiqueWhite;
lv.Columns.Add("Row", 50);
lv.Columns.Add("sub1", 50);
lv.Columns.Add("sub2", 50);
for(int i = 0; i < 10; i++)
{
ListViewItem lvi = new(){ Text = "Row " + i, Tag = new myClass() };
lvi.SubItems.Add("");
lvi.SubItems.Add("");
lv.Items.Add(lvi);
}
}
private void Lv_MouseUp(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
MessageBox.Show("Left click - doing action A");
}
if (e.Button == MouseButtons.Right)
{
MessageBox.Show("Right click - Creating context menu");
}
}
}
Custom ListView override
using System.Runtime.InteropServices;
namespace testCase;
public class ListViewExWatch : ListView
{
#region Windows API
[StructLayout(LayoutKind.Sequential)]
struct DRAWITEMSTRUCT
{
public int ctlType;
public int ctlID;
public int itemID;
public int itemAction;
public int itemState;
public IntPtr hWndItem;
public IntPtr hDC;
public int rcLeft;
public int rcTop;
public int rcRight;
public int rcBottom;
public IntPtr itemData;
}
const int LVS_OWNERDRAWFIXED = 0x0400;
const int WM_SHOWWINDOW = 0x0018;
const int WM_DRAWITEM = 0x002B;
const int WM_MEASUREITEM = 0x002C;
const int WM_REFLECT = 0x2000;
const int WM_LBUTTONDOWN = 0x0201;
#endregion
public ListViewExWatch()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
protected override CreateParams CreateParams
{
get
{
CreateParams k_Params = base.CreateParams;
k_Params.Style |= LVS_OWNERDRAWFIXED;
return k_Params;
}
}
protected override void WndProc(ref Message k_Msg)
{
//if (k_Msg.Msg == WM_LBUTTONDOWN) return;
base.WndProc(ref k_Msg); // Exception: System.ArgumentOutOfRangeException: 'InvalidArgument=Value of '-1' is not valid for 'index'.
// Only occurs when clicking to the right of the last subItem
switch (k_Msg.Msg)
{
case WM_SHOWWINDOW:
View = View.Details;
OwnerDraw = false;
break;
case WM_REFLECT + WM_MEASUREITEM:
Marshal.WriteInt32(k_Msg.LParam + 4 * sizeof(int), 14);
k_Msg.Result = (IntPtr)1;
break;
case WM_REFLECT + WM_DRAWITEM:
{
object? lParam = k_Msg.GetLParam(typeof(DRAWITEMSTRUCT));
if (lParam is null) throw new Exception("lParam shouldn't be null");
DRAWITEMSTRUCT k_Draw = (DRAWITEMSTRUCT)lParam;
using Graphics gfx = Graphics.FromHdc(k_Draw.hDC);
ListViewItem lvi = Items[k_Draw.itemID];
myClass wi = (myClass)lvi.Tag;
TextRenderer.DrawText(gfx, lvi.Text, Font, lvi.SubItems[0].Bounds, Color.Black, TextFormatFlags.Left);
TextRenderer.DrawText(gfx, wi.s1, Font, lvi.SubItems[1].Bounds, Color.Black, TextFormatFlags.Left);
TextRenderer.DrawText(gfx, wi.s2, Font, lvi.SubItems[2].Bounds, Color.Black, TextFormatFlags.Left);
break;
}
}
}
}
The error message
System.ArgumentOutOfRangeException
HResult=0x80131502
Message=InvalidArgument=Value of '-1' is not valid for 'index'. Arg_ParamName_Name
ArgumentOutOfRange_ActualValue
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.ListViewItem.ListViewSubItemCollection.get_Item(Int32 index)
at System.Windows.Forms.ListView.HitTest(Int32 x, Int32 y)
at System.Windows.Forms.ListView.ListViewAccessibleObject.HitTest(Int32 x, Int32 y)
at System.Windows.Forms.ListView.WmMouseDown(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.ListView.WndProc(Message& m)
at testCase.ListViewExWatch.WndProc(Message& k_Msg) in C:\Users\XXXX\source\repos\testCase\ListViewExWatch.cs:line 50
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam)
From the error it is apparent that the base listview code it is trying to get a subitem that doesn't exist because the click is not on a subitem, hence the -1 for index within the error message.
At the time of exception there is no member of k_Msg that contains the index (-1), so I cannot check for that and simply return.
I could surround the call to base.WndProc in a try catch, since it is an edge case. I've always had the mindset to check for possible exceptions and prevent them whenever possible rather than catch them. But this one has me stumped. Am I being too pedantic in this regard?
Most likely I am missing something basic here, could you fine folks please point me in the right direction?
I had implemented the following code to catch only this specific exception and still allow any other to bubble up as desired.
With further research however, I managed to specifically workaround the issue
From https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondown
I ended up modifying the lParam before processing the message. By moving the x-coordinate to inside the last subitem the exception is averted and clicks are processed as normal.
It is interesting to note the correct x and y coordinates are still reported in the Lv_MouseUp handler. This may not be the case for other handlers such as MouseDown which has obviously been modified, since I do not use them it has not presented a problem
Here are the helper functions used above (from referencesource.microsoft.com)