EdgarGad
EdgarGad

Reputation: 79

prevent listview to lose selected item

I'm currently working on a listview in winform c# and everytime I click on an empty space on the listview, the selected item is lost.

Upvotes: 7

Views: 15391

Answers (7)

RikLain
RikLain

Reputation: 11

Here are simple, but a bit hack way, based on LVN_ITEMCHANGED event realization (Sorry - plain C, not C#, but i hope it's understandable):

case LVN_ITEMCHANGED:
        {
            NMLISTVIEW* lPoint = (LPNMLISTVIEW)lParam;
            if (lPoint->uNewState & LVIS_SELECTED && lPoint->iItem != -1) {
                // Select other item...
                eItem = (int)lPoint->lParam;
                SendMessage(GetParent(hDlg), WM_APP + 2, 0, 1);
            }
            else {
                if (!lPoint->uNewState && ListView_GetItemState(((NMHDR*)lParam)->hwndFrom, lPoint->iItem, LVIS_FOCUSED)) {
                    // Click on empty space
                    ListView_SetItemState(((NMHDR*)lParam)->hwndFrom, lPoint->iItem, LVIS_SELECTED, LVIS_SELECTED);
                }
                else
                    // Click on other item
                    ListView_SetItemState(((NMHDR*)lParam)->hwndFrom, lPoint->iItem, 0, LVIS_SELECTED);
            }
        } break;

Upvotes: 0

Bogdan Samchuk
Bogdan Samchuk

Reputation: 168

I know that question asked 10 years ago. But I face the same problem and found a simple and elegant solution just now and sincerely want to share it.

There is a code (in VB.NET but its no big problem to write the same in C#):

Public Class SettingsBox ' Form that contains ListView (lvScreen)

    Private nScreenTracer As Integer
    Private nSelectedScreen As Integer

    Private Sub lvScreen_ItemSelectionChanged(sender As Object,
                                              e As ListViewItemSelectionChangedEventArgs) Handles lvScreen.ItemSelectionChanged
        If e.IsSelected Then nScreenTracer = e.Item.Index
    End Sub

    Private Sub lvScreen_MouseDown(sender As Object,
                                   e As MouseEventArgs) Handles lvScreen.MouseDown
        nScreenTracer = -1
    End Sub

    Private Sub lvScreen_MouseUp(sender As Object,
                                 e As MouseEventArgs) Handles lvScreen.MouseUp
        If nScreenTracer = -1 Then
            lvScreen.SelectedIndices.Add(nSelectedScreen)
        Else
            nSelectedScreen = nScreenTracer
        End If
    End Sub

End Class

This solution works for a single item selection but also can be simply redesigned with List(Of Integer) for multi-select

Upvotes: 0

Learner
Learner

Reputation: 3416

The listview control has a HideSelection property that defaults to True. Make it False and you're good to go... in some cases this is enough.

Upvotes: 8

Alex from Jitbit
Alex from Jitbit

Reputation: 60872

You have to inherit from the ListView class and do some low-level message processing

class ListViewThatKeepsSelection : ListView
{
    protected override void WndProc(ref Message m)
    {
        // Suppress mouse messages that are OUTSIDE of the items area
        if (m.Msg >= 0x201 && m.Msg <= 0x209)
        {
            Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
            var hit = this.HitTest(pos);
            switch (hit.Location)
            {
                case ListViewHitTestLocations.AboveClientArea:
                case ListViewHitTestLocations.BelowClientArea:
                case ListViewHitTestLocations.LeftOfClientArea:
                case ListViewHitTestLocations.RightOfClientArea:
                case ListViewHitTestLocations.None:
                    return;
            }
        }
        base.WndProc(ref m);
    }
}

Upvotes: 3

Ready Cent
Ready Cent

Reputation: 1838

I accomplished it like this:

private void lvReads_MouseUp(object sender, MouseEventArgs e)
    {
        if (lvReads.SelectedItems.Count == 0)
            if (lvReads.Items.Count > 0)
                lvReads.Items.Find(currentName, false)[0].Selected = true;
    }

and

private void lvReads_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (lvReads.SelectedItems.Count == 1)
        {               
            selectedIndex = lvReads.SelectedIndices[0];

            if (currentName != lvReads.Items[selectedIndex].Name)
            {

                //load item
            }

            currentName = lvReads.Items[selectedIndex].Name;
        }
    }

Upvotes: 2

Grammarian
Grammarian

Reputation: 6882

This is much harder to do in WinForms than in WPF. WinForms has a SelectedIndexChanged event which doesn't tell you anything about what was already selected, plus it is fired every time a row is selected or deselected.

So if a row is selected and you select a different row, you receive two SelectedIndexChanged events:

  1. one after the selected row is deselected
  2. another when the new row is selected.

The problem is that, during event #1, the ListView has nothing selected and you don't know if event #2 is coming that will select the second row.

The best you can do is wait until your application is idle (a few milliseconds after the selection has changed), and if the listview still has nothing selected, put back the last selected row.

private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
    ListView lv = (ListView)sender;
    if (lv.SelectedIndices.Count == 0)
    {
        if (!this.appIdleEventScheduled)
        {
            this.appIdleEventScheduled = true;
            this.listViewToMunge = lv;
            Application.Idle += new EventHandler(Application_Idle);
        }
    }
    else
        this.lastSelectedIndex = lv.SelectedIndices[0];
}

void Application_Idle(object sender, EventArgs e)
{
    Application.Idle -= new EventHandler(Application_Idle);
    this.appIdleEventScheduled = false;
    if (listViewToMunge.SelectedIndices.Count == 0) 
        listViewToMunge.SelectedIndices.Add(this.lastSelectedIndex);
}

private bool appIdleEventScheduled = false;
private int lastSelectedIndex = -1;
private ListView listViewToMunge;

Upvotes: 2

Jeffrey L Whitledge
Jeffrey L Whitledge

Reputation: 59533

I thought there was a property that prevented this from happening, but now I can't find it.

You could try this:

private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ListView listView = sender as ListView;
    if (listView.SelectedItems.Count == 0)
        foreach (object item in e.RemovedItems)
            listView.SelectedItems.Add(item);
}

Upvotes: 2

Related Questions