I did a quick google:
winform listview group collapse - Google Search[
^] and the first search result:
ListView hide or collapse selected group [solution] - StackOverflow[
^] pointed to an article right here on Code Project that does exactly what you want:
Add Group Collapse Behavior on a Listview Control[
^]
UPDATE
It's been a long time since I've spent any real-time WinForms as I work in WPF when doing Windows apps. In WPF, this is possible to do using templates. I was curious as to if it was possible in WinForms using the new feature. So I fired up VS and wrote a mock app using the ListView.
What I learned, after experimenting, then digging into the
ListViewGroup
control source code is that the
SubTitle
and
Footer
properties of the
ListViewGroup
are always shown if set. (ref:
SubTitle Property[
^] &
Footer Property[
^] ). It is a limitation of Microsoft's implementation. You can read more about the discussion of the request and implementation of this feature here:
Add Collapse Support to ListViewGroup · Issue #3067 · dotnet/winforms · GitHub[
^]
The only option at this stage, without further, digging into the source code, is to set the values to a " " (space), not an empty string or
default
value. This will leave a blank row on the control.
Alternatively, the other option is to set the SubTitle/Footer with updated information for when the
CollapsedState
changes in the
GroupCollapsedStateChanged
event.
Otherwise, look at the initial link above and see if there is an "old school" way of doing what you want.
UPDATE #2
Here is my test code:
public partial class Form1 : Form
{
private Random random = new();
private List<Person> friends = new();
private List<string> groups = new()
{
"Enemies",
"People That I Know",
"Friends",
"Close Friends",
"Best Friends"
};
public Form1()
{
InitializeComponent();
InitData();
InitListBox();
InitListView();
}
private void InitListBox()
{
listBox1.Dock = DockStyle.Bottom;
listBox1.Height = 200;
}
private void InitData()
{
for (int i = 0; i < 100; i++)
friends.Add(new Person($"Firstname {i}", $"LastName {i}", groups[random.Next(0, 5)]));
}
private void InitListView()
{
listView1.View = View.Details;
listView1.Dock = DockStyle.Fill;
listView1.FullRowSelect = true;
listView1.GroupCollapsedStateChanged += ListView1_GroupCollapsedStateChanged;
listView1.Columns.Add("FirstName", 150);
listView1.Columns.Add("LastName", 150);
listView1.Columns.Add("Group", 100);
foreach (string group in groups)
{
ListViewGroup item = new(group)
{
CollapsedState = ListViewGroupCollapsedState.Expanded,
Subtitle = $"Subtitle for {group}",
Footer = $"Footer for {group}"
};
listView1.Groups.Add(item);
}
foreach (Person friend in friends)
{
ListViewItem item = new(new string[]
{
friend.FirstName,
friend.LastName,
friend.Group
});
item.Group = listView1.Groups[groups.IndexOf(friend.Group)];
listView1.Items.Add(item);
}
}
private void ListView1_GroupCollapsedStateChanged(object? sender, ListViewGroupEventArgs e)
{
Debug.WriteLine("ListView1_GroupCollapsedStateChanged");
ListViewGroup group = listView1.Groups[e.GroupIndex];
ListViewGroupCollapsedState state = group.CollapsedState;
group.Footer = null;
listView1.call("UpdateGroupNative", group);
listView1.call("UpdateGroupView");
listBox1.Items.Insert(0, $"{listBox1.Items.Count + 1}: Group[{e.GroupIndex}] {group.Name} is now {state}");
}
}
public record Person(string FirstName, string LastName, string Group);
static class ObjectExtensions
{
public static object? call(this object obj, string methodName, params object[]? args)
{
MethodInfo? mi = obj.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
return mi != null
? mi.Invoke (obj, args)
: null;
}
}