|
Hey Steve,
Ok so here the deal, i think i found a solution for you.
My code here on Cp is a bit out of date, but my code could be optimized with the method on the ListView called HitTest. In my code i made my own hittest. That aside i found i had to make a copy of the HitTest to cope with the new features. The HitTest retuns ListViewHitTestInfo class, but thats not good enough, so you have to make your own.
But for now, im able to see what im clicking on, and i should be able to cancel that event on specified areas.
I will post the code when i get the time.
but if you want to look into it yourself, heres a link:
http://msdn.microsoft.com/en-us/library/bb774754(VS.85).aspx[^]
With great code, comes great complexity, so keep it simple stupid...
|
|
|
|
|
Thanks.
To test this in your demo app, I overrided mouse up and did a hit test on e.Location. The only results I could get back (Win7 with .NET 3.5) were None and Label. Label was being returned when clicking on an item, but in all other click locations I got back None. I tried in Tile and Details views, and with/without collapsing on. I also tried not calling the base handler when the hit test returned None, but that had no effect - the items in the group are still all being selected.
Maybe I don't fully understand what you are talking about. I need to detect a click in the group header and cancel before it affects anything.
Thanks again,
_Steve
|
|
|
|
|
Hey Steve, im sorry i dont really have the time to make and test the code before posting @ the moment. i will when i get the time i promise.
Just to clear something up. you have to make your own hittest method and your own LVHITTESTINFO struct, because the one in the framework does not support these new things.
And remember when using the SendMessage native call in your hittest, the wParam MUST be -1 instead of the normal 0. look here: http://msdn.microsoft.com/en-us/library/bb761099(VS.85).aspx[^]
Im sorry for the bad replies, but im working double time at the moment. ill get back to you when i can. ;o)
best regards
With great code, comes great complexity, so keep it simple stupid...
|
|
|
|
|
Hey Steve.
Thanks to Ji.Zhou from MSDN forum i was able to figure this one out.
First, you dont need to make the extended HitTest method.
Heres the fix:
public class ListViewInterceptor : NativeWindow
{
private ListView _listView;
private const int WM_LBUTTONDOWN = 0x0201;
public ListViewInterceptor(ListView listView)
{
_listView = listView;
this.AssignHandle(_listView.Handle);
}
private static Point LParamToPoint(IntPtr lparam)
{
return new Point(lparam.ToInt32() & 0xFFFF, lparam.ToInt32() >> 16);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN)
{
ListViewHitTestInfo htInfo = _listView.HitTest(LParamToPoint(m.LParam));
if (htInfo.Item != null)
base.WndProc(ref m);
}
else
base.WndProc(ref m);
}
}
in your load method of you form try this:
ListViewInterceptor lvi = new ListViewInterceptor(your_listview);
now when you click the group header, the subitems are not selected.
hope it helps.
With great code, comes great complexity, so keep it simple stupid...
|
|
|
|
|
Hi Paw,
Thanks for staying on this - I really appreciate it.
I implemented the code as described, and found that it worked initially, but then I found a problem... It seems that it works as desired when there is nothing selected in the lv prior to the click. But if one or more items are already selected, then the click on the group header works as it did before, i.e. it selects all of the items in the group whose header was clicked on.
I did see that the result from the hittest on LButtonDown seems identical in either case. I will need to spend more time on it to figure out what's going on, but am hopeful that this is a big step in the right direction. Maybe I will post to Ji Zhou's page so you both can respond if you want...
Thanks again,
_Steve
|
|
|
|
|
Yeah ok I see, when you use this code as I posted it, it will still select all the subitems like you said,
but I managed to make this work with some extended features, but this includes the HitTestEx extended method that I described previously. I will make some code changes and post the code here soon.
Best regards
With great code, comes great complexity, so keep it simple stupid...
|
|
|
|
|
Thanks - I'm looking forward to your update.
BTW, I just noticed you are from Denmark. Are you in Copenhagen? I used to work for a US-based company that had an office in Skanderborg, and got to visit there once in 2005. Beautiful country - too bad it was mid December and it was dark for all of my free time... And if the days weren't short enough there, I got stuck for an extra day in Iceland on the way back!
Thanks again,
_Steve
PS, please feel free to reply to my email addr: sroche@fusiontrade.com so I don't have to keep looking here. I'm not getting the automated emails for replies, as I think I should be...
|
|
|
|
|
If you subclass & inhibit WM_LBUTTONDOWN,you could lose LVN_LINKCLICK notification.The sequence is WM_LBUTTONDOWN --> LVN_LINKCLICK.
|
|
|
|
|
I can set my Groups to Collapsible which shows the collapse down-arrow, but there is now event for that so how do I collapse the group when that arrow is pressed?
|
|
|
|
|
No there is not an event for that, I could program one, but its not necessary, since the group should toggle the collaps and expand by itself, when the button is clicked.
With great code, comes great complexity, so keep it simple stupid...
|
|
|
|
|
"AccessViolationException was unhandled" message was thrown from the line "base.WndProc(ref m) in the following method when I click the button "Make Groups Collapsible". I use VS2008 under Vista platform. Do you have any idea and get a chance to look at it? Thanks.
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONUP)
base.DefWndProc(ref m);
base.WndProc(ref m);
}
modified on Friday, June 12, 2009 1:31 AM
|
|
|
|
|
Try adding this attribute to the WndProc override
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
With great code, comes great complexity, so keep it simple stupid...
|
|
|
|
|
That doesn't really help me, I'm still getting the same exception (running in VS2008 Professional on Vista 32Bit Ultimate), even if I run the app outside of VS and even with administrator privileges
|
|
|
|
|
I found this control today and took a look at it. I too was experiencing the AccessViolationException. While I have not done extensive testing I believe I may know what the problem is.
The signature for SendMessage is:
LRESULT SendMessage(HWND, UINT, WPARAM, LPARAM)
in C# this would translate to roughly:
IntPtr SendMessage(IntPtr, Int32, IntPtr, IntPtr)
The version of SendMessage in this sample is defined as:
int SendMessage(IntPtr, int, int, LVGROUP)
LVGROUP is a struct and should be passed as a reference in the lParam slot. Try adding a "ref" to the LVGROUP param - and then of course updating all calls to SendMessage - and see if that solves the problem for you.
John
|
|
|
|
|
|
hi man, did you ever update the code?, i need it urgently!!!
Please help me, thanks in advance.
|
|
|
|
|
Cant remember, but try what lpvoid has writtin in this post... it should help...
I dont get the exception, sorry.
Let me know if it still throws an exception... thanks
With great code, comes great complexity, so keep it simple stupid...
|
|
|
|
|
I ran into the same exception. Applied the fix as suggested (adding the ref-operator to the calls and signature) which solved the problem for me.
Added a small piece of code to allow me to set the groupstate of each individual group.
<br />
public void SetGroupState(ListViewGroup lvg, ListViewGroupState state) {<br />
setGrpState(lvg, state);<br />
}<br /> Dennis
|
|
|
|
|
changing SendMessage lParam to be passed by ref does resolve the exception
|
|
|
|
|
I added "ref" as lpvoid said. It's working now
|
|
|
|
|
This is the correct solution. I believe it only happens if your project is configured to build the x86 architecture and is run on a 64-bit machine. This is the updated method declaration:
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref LVGROUP lParam);
You'll also need to update all SendMessage invocations to add the "ref" modifier to the LVGROUP argument.
FWIW, it looks like there's some discrepancies in the struct declaration as well. Some string fields are passed as strings, and others as IntPtr. Here's my updated version of the code, including a method to set the icon padding in View.Large/SmallIcon modes:
<code>
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Reflection;
namespace ListViewGroupCollapse
{
public class ListViewExtended : ListView
{
private const int LVM_FIRST = 0x1000;
private const int LVM_SETGROUPINFO = (LVM_FIRST + 147);
private const int LVM_SETICONSPACING = (LVM_FIRST + 53);
private const int WM_LBUTTONUP = 0x0202;
private delegate void CallBackSetGroupState(ListViewGroup lstvwgrp, ListViewGroupState state);
private delegate void CallbackSetGroupString(ListViewGroup lstvwgrp, string value);
[DllImport("User32.dll"), Description("Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message. To send a message and return immediately, use the SendMessageCallback or SendNotifyMessage function. To post a message to a thread's message queue and return immediately, use the PostMessage or PostThreadMessage function.")]
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref LVGROUP lParam);
[DllImport("User32.dll"), Description("Sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message. To send a message and return immediately, use the SendMessageCallback or SendNotifyMessage function. To post a message to a thread's message queue and return immediately, use the PostMessage or PostThreadMessage function.")]
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private static int? GetGroupID(ListViewGroup lstvwgrp)
{
int? rtnval = null;
Type GrpTp = lstvwgrp.GetType();
if (GrpTp != null)
{
PropertyInfo pi = GrpTp.GetProperty("ID", BindingFlags.NonPublic | BindingFlags.Instance);
if (pi != null)
{
object tmprtnval = pi.GetValue(lstvwgrp, null);
if (tmprtnval != null)
{
rtnval = tmprtnval as int?;
}
}
}
return rtnval;
}
private static void setGrpState(ListViewGroup lstvwgrp, ListViewGroupState state)
{
if (Environment.OSVersion.Version.Major < 6)
return;
if (lstvwgrp == null || lstvwgrp.ListView == null)
return;
if (lstvwgrp.ListView.InvokeRequired)
lstvwgrp.ListView.Invoke(new CallBackSetGroupState(setGrpState), lstvwgrp, state);
else
{
int? GrpId = GetGroupID(lstvwgrp);
int gIndex = lstvwgrp.ListView.Groups.IndexOf(lstvwgrp);
LVGROUP group = new LVGROUP();
group.CbSize = (uint)Marshal.SizeOf(group);
group.State = state;
group.Mask = ListViewGroupMask.State;
if (GrpId != null)
{
group.IGroupId = GrpId.Value;
SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, GrpId.Value, ref group);
SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, GrpId.Value, ref group);
}
else
{
group.IGroupId = gIndex;
SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, gIndex, ref group);
SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, gIndex, ref group);
}
lstvwgrp.ListView.Refresh();
}
}
private static void setIconSpacing(ListView listView, int width, int height)
{
SendMessage(listView.Handle, LVM_SETICONSPACING, width, height);
}
private static void setGrpFooter(ListViewGroup lstvwgrp, string footer)
{
if (Environment.OSVersion.Version.Major < 6)
return;
if (lstvwgrp == null || lstvwgrp.ListView == null)
return;
if (lstvwgrp.ListView.InvokeRequired)
lstvwgrp.ListView.Invoke(new CallbackSetGroupString(setGrpFooter), lstvwgrp, footer);
else
{
int? GrpId = GetGroupID(lstvwgrp);
int gIndex = lstvwgrp.ListView.Groups.IndexOf(lstvwgrp);
LVGROUP group = new LVGROUP();
group.CbSize = (uint)Marshal.SizeOf(group);
group.PszFooter = footer;
group.Mask = ListViewGroupMask.Footer;
if (GrpId != null)
{
group.IGroupId = GrpId.Value;
SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, GrpId.Value, ref group);
}
else
{
group.IGroupId = gIndex;
SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, gIndex, ref group);
}
}
}
public void SetGroupState(ListViewGroupState state)
{
foreach (ListViewGroup lvg in this.Groups)
setGrpState(lvg, state);
}
public void SetGroupFooter(ListViewGroup lvg, string footerText)
{
setGrpFooter(lvg, footerText);
}
public void SetIconSpacing(int width, int height)
{
setIconSpacing(this, width, height);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONUP)
base.DefWndProc(ref m);
base.WndProc(ref m);
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), Description("LVGROUP StructureUsed to set and retrieve groups.")]
public struct LVGROUP
{
[Description("Size of this structure, in bytes.")]
public uint CbSize;
[Description("Mask that specifies which members of the structure are valid input. One or more of the following values:LVGF_NONE No other items are valid.")]
public ListViewGroupMask Mask;
[Description("Pointer to a null-terminated string that contains the header text when item information is being set. If group information is being retrieved, this member specifies the address of the buffer that receives the header text.")]
[MarshalAs(UnmanagedType.LPWStr)]
public string PszHeader;
[Description("Size in TCHARs of the buffer pointed to by the pszHeader member. If the structure is not receiving information about a group, this member is ignored.")]
public int CchHeader;
[Description("Pointer to a null-terminated string that contains the footer text when item information is being set. If group information is being retrieved, this member specifies the address of the buffer that receives the footer text.")]
[MarshalAs(UnmanagedType.LPWStr)]
public string PszFooter;
[Description("Size in TCHARs of the buffer pointed to by the pszFooter member. If the structure is not receiving information about a group, this member is ignored.")]
public int CchFooter;
[Description("ID of the group.")]
public int IGroupId;
[Description("Mask used with LVM_GETGROUPINFO (Microsoft Windows XP and Windows Vista) and LVM_SETGROUPINFO (Windows Vista only) to specify which flags in the state value are being retrieved or set.")]
public uint StateMask;
[Description("Flag that can have one of the following values:LVGS_NORMAL Groups are expanded, the group name is displayed, and all items in the group are displayed.")]
public ListViewGroupState State;
[Description("Indicates the alignment of the header or footer text for the group. It can have one or more of the following values. Use one of the header flags. Footer flags are optional. Windows XP: Footer flags are reserved.LVGA_FOOTER_CENTERReserved.")]
public uint UAlign;
[Description("Windows Vista. Pointer to a null-terminated string that contains the subtitle text when item information is being set. If group information is being retrieved, this member specifies the address of the buffer that receives the subtitle text. This element is drawn under the header text.")]
[MarshalAs(UnmanagedType.LPWStr)]
public string PszSubtitle;
[Description("Windows Vista. Size, in TCHARs, of the buffer pointed to by the pszSubtitle member. If the structure is not receiving information about a group, this member is ignored.")]
public uint CchSubtitle;
[Description("Windows Vista. Pointer to a null-terminated string that contains the text for a task link when item information is being set. If group information is being retrieved, this member specifies the address of the buffer that receives the task text. This item is drawn right-aligned opposite the header text. When clicked by the user, the task link generates an LVN_LINKCLICK notification.")]
[MarshalAs(UnmanagedType.LPWStr)]
public string PszTask;
[Description("Windows Vista. Size in TCHARs of the buffer pointed to by the pszTask member. If the structure is not receiving information about a group, this member is ignored.")]
public uint CchTask;
[Description("Windows Vista. Pointer to a null-terminated string that contains the top description text when item information is being set. If group information is being retrieved, this member specifies the address of the buffer that receives the top description text. This item is drawn opposite the title image when there is a title image, no extended image, and uAlign==LVGA_HEADER_CENTER.")]
[MarshalAs(UnmanagedType.LPWStr)]
public string PszDescriptionTop;
[Description("Windows Vista. Size in TCHARs of the buffer pointed to by the pszDescriptionTop member. If the structure is not receiving information about a group, this member is ignored.")]
public uint CchDescriptionTop;
[Description("Windows Vista. Pointer to a null-terminated string that contains the bottom description text when item information is being set. If group information is being retrieved, this member specifies the address of the buffer that receives the bottom description text. This item is drawn under the top description text when there is a title image, no extended image, and uAlign==LVGA_HEADER_CENTER.")]
[MarshalAs(UnmanagedType.LPWStr)]
public string PszDescriptionBottom;
[Description("Windows Vista. Size in TCHARs of the buffer pointed to by the pszDescriptionBottom member. If the structure is not receiving information about a group, this member is ignored.")]
public uint CchDescriptionBottom;
[Description("Windows Vista. Index of the title image in the control imagelist.")]
public int ITitleImage;
[Description("Windows Vista. Index of the extended image in the control imagelist.")]
public int IExtendedImage;
[Description("Windows Vista. Read-only.")]
public int IFirstItem;
[Description("Windows Vista. Read-only in non-owner data mode.")]
public uint CItems;
[Description("Windows Vista. NULL if group is not a subset. Pointer to a null-terminated string that contains the subset title text when item information is being set. If group information is being retrieved, this member specifies the address of the buffer that receives the subset title text.")]
[MarshalAs(UnmanagedType.LPWStr)]
public string PszSubsetTitle;
[Description("Windows Vista. Size in TCHARs of the buffer pointed to by the pszSubsetTitle member. If the structure is not receiving information about a group, this member is ignored.")]
public uint CchSubsetTitle;
}
public enum ListViewGroupMask
{
None = 0x00000,
Header = 0x00001,
Footer = 0x00002,
State = 0x00004,
Align = 0x00008,
GroupId = 0x00010,
SubTitle = 0x00100,
Task = 0x00200,
DescriptionTop = 0x00400,
DescriptionBottom = 0x00800,
TitleImage = 0x01000,
ExtendedImage = 0x02000,
Items = 0x04000,
Subset = 0x08000,
SubsetItems = 0x10000
}
public enum ListViewGroupState
{
Normal = 0,
Collapsed = 1,
Hidden = 2,
NoHeader = 4,
Collapsible = 8,
Focused = 16,
Selected = 32,
SubSeted = 64,
SubSetLinkFocused = 128,
}
}
</code>
|
|
|
|
|
Awesome, thanks Ipvoid... works perfectly.
|
|
|
|
|
Thanks. I actually used this on my VB.Net project and your solution worked fine.
Thanks bro and of course to Paw!
|
|
|
|
|
It's nice to know that some .NET junkies are still issuing powerful commands to the API when it is needed.
I remember doing this kind of stuff a while back. I haven't dug into the Win API in a long time. Just imagine if all of the capabilities of Windows controls were surfaced to their .NET counterparts. I suppose the issue of Windows versioning is the reason they are not completely surfaced.
Great job indeed!
|
|
|
|
|
I love it when I see people that know Win32, and how to use it with Winforms/WPF.
I wish I did.
Sacha Barber
- Microsoft Visual C# MVP 2008/2009
- Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
My Blog : sachabarber.net
|
|
|
|
|