1

I want to do something seemingly simple - programmatically select and highlight a row of a ListView in VB.NET.

VB.NET: How to dynamically select a list view item?

tells me what should to be all that is needed, but it isn't. The row is selected, but not highlighted.

http://vbcity.com/forums/t/28260.aspx

tells me about the "HideSelection" property and the .Focus() method (also referenced at Select programmatically a row of a Listview), which sounded hopeful, but the best I can get is the faint highlight mentioned, I want the full monty. I tried a Noddy example with just a ListView, in Details mode, FullRowSelection = true, HideSelection = False, one columnheader defined and then

    ListView1.Items.Add("Red")
    ListView1.Items.Add("Orange")
    ListView1.Items.Add("Yellow")
    ListView1.Items.Add("Green")

    ListView1.Items(2).Selected = True

I get this

enter image description here

but I want this

enter image description here

I can simulate highlighting by adding these lines

ListView1.SelectedItems(0).BackColor = Color.CornflowerBlue
ListView1.SelectedItems(0).ForeColor = Color.White

but then how can I be sure to undo the artificial highlight if the row can be implicitly as well as explicitly deselected? Do I have to think of all the possible cases? That's too much work for what should be a simple operation. Plus, since I want to color-code my rows, there is an additional challenge that when I undo the highlight color, I have to figure out what color is appropriate at that point. Is there a better, simpler way?

DJDave
  • 865
  • 1
  • 13
  • 28
  • 2
    I think it is selected, it's just grayed out because the control doesn't have focus – soohoonigan Aug 15 '17 at 12:50
  • yup. I can confirm via the debugger that it's selected, but I need it to be *seen* to be selected by the user and that faint grey stuff is no use if I also need to color-code my rows – DJDave Aug 15 '17 at 12:52
  • As @soohoonigan said, you only get the full monty if the control has focus. – Brian M Stafford Aug 15 '17 at 12:56
  • I have ListView1.Focus() as the last line of Form.Load but I don't get the highlight – DJDave Aug 15 '17 at 13:14
  • 2
    Have you tried to set the focus in the Form.Shown event instead? I don't think you can set focus before the form is visible. – Magnus Aug 15 '17 at 13:20
  • Magnus, I think that's the key - I tried your suggestion for my Noddy example (select a row at form load) and it worked. Then in my real code (long after form load or form shown) you made me think "how do I make sure that setting the focus is the LAST thing that happens after the row is selected" - I had tried to set the focus in the next line after I set the selection, but other things happened after that which look like they changed the focus to something else. – DJDave Aug 15 '17 at 14:05

2 Answers2

0

You can access the Graphics object used to draw each item, and draw them yourself.

Make a new project with a Button and ListView. Paste the following code:

Form_Load to use multiple subitems

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Me.ListView1.OwnerDraw = True ' or else can't handle DrawItem event
    ListView1.Columns.Add("ColumnHeader1")
    ListView1.Columns.Add("ColumnHeader2")
    ListView1.Columns.Add("ColumnHeader3")
    Me.ListView1.Items.Add("Red")
    Me.ListView1.Items.Add("Orange")
    Me.ListView1.Items.Add("Yellow")
    Me.ListView1.Items.Add("Green")
    ListView1.Items(0).SubItems.Add("Strawberry")
    ListView1.Items(0).SubItems.Add("Apple")
    ListView1.Items(1).SubItems.Add("Pepper")
    ListView1.Items(1).SubItems.Add("Apricot")
    ListView1.Items(2).SubItems.Add("Plum")
    ListView1.Items(2).SubItems.Add("Banana")
    ListView1.Items(3).SubItems.Add("Apple")
    ListView1.Items(3).SubItems.Add("Lime")
End Sub

Three handlers for the ListView's drawing related events. Code copied from this answer

Private Sub listView1_DrawColumnHeader(sender As Object, e As DrawListViewColumnHeaderEventArgs) Handles ListView1.DrawColumnHeader
    e.DrawDefault = True
End Sub

Private Sub listView1_DrawSubItem(sender As Object, e As DrawListViewSubItemEventArgs) Handles ListView1.DrawSubItem
    Const TEXT_OFFSET As Integer = 1
    ' I don't know why the text is located at 1px to the right. Maybe it's only for me.
    Dim listView As ListView = DirectCast(sender, ListView)

    ' Check if e.Item is selected and the ListView has a focus.
    If Not listView.Focused AndAlso e.Item.Selected Then
        Dim rowBounds As Rectangle = e.SubItem.Bounds
        Dim labelBounds As Rectangle = e.Item.GetBounds(ItemBoundsPortion.Label)
        Dim leftMargin As Integer = labelBounds.Left - TEXT_OFFSET
        Dim bounds As New Rectangle(rowBounds.Left + leftMargin, rowBounds.Top, If(e.ColumnIndex = 0, labelBounds.Width, (rowBounds.Width - leftMargin - TEXT_OFFSET)), rowBounds.Height)
        Dim align As TextFormatFlags
        Select Case listView.Columns(e.ColumnIndex).TextAlign
            Case HorizontalAlignment.Right
                align = TextFormatFlags.Right
                Exit Select
            Case HorizontalAlignment.Center
                align = TextFormatFlags.HorizontalCenter
                Exit Select
            Case Else
                align = TextFormatFlags.Left
                Exit Select
        End Select
        TextRenderer.DrawText(e.Graphics, e.SubItem.Text, listView.Font, bounds, SystemColors.HighlightText, align Or TextFormatFlags.SingleLine Or TextFormatFlags.GlyphOverhangPadding Or TextFormatFlags.VerticalCenter Or TextFormatFlags.WordEllipsis)
    Else
        e.DrawDefault = True
    End If
End Sub

Private Sub listView1_DrawItem(sender As Object, e As DrawListViewItemEventArgs) Handles ListView1.DrawItem
    Dim listView As ListView = DirectCast(sender, ListView)

    ' Check if e.Item is selected and the ListView has a focus.
    If Not listView.Focused AndAlso e.Item.Selected Then
        Dim rowBounds As Rectangle = e.Bounds
        Dim leftMargin As Integer = e.Item.GetBounds(ItemBoundsPortion.Label).Left
        Dim bounds As New Rectangle(leftMargin, rowBounds.Top, rowBounds.Width - leftMargin, rowBounds.Height)
        e.Graphics.FillRectangle(SystemBrushes.Highlight, bounds)
    Else
        e.DrawDefault = True
    End If
End Sub

Button click handler to simulate item(2) selected

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Me.ListView1.Items(2).Selected = True
End Sub

This will draw the background color regardless of focus. You have a lot of control over other colors and fonts going this route too.

Here, the button has been clicked, to select item 2, while the button still has focus, and item 2 is selected.

enter image description here

djv
  • 15,168
  • 7
  • 48
  • 72
  • This works for the Noddy example, but not for the real case because my Listview has Subitems, which disappear. This may be easily fixed, happy to try a suggestion. If it can be made to work then I note that it has an advantage over the solution that Magnus pointed to, which is that it should work if multiple ListViews need programmatically highlighted simultaneously - obviously the .Focus() solution can only work for one. – DJDave Aug 16 '17 at 10:18
  • 1
    One small suggestion - if you replace "Dim item = ListView1.Items(e.ItemIndex)" with "Dim item = sender.Items(e.ItemIndex)" then the code becomes reusable for any number of ListViews, i.e. .. Handles ListView1.DrawItem, ListView2.DrawItem, ListView3.DrawItem etc – DJDave Aug 16 '17 at 10:20
  • 1
    You should post your real case example, not the Noddy example (what does "Noddy" mean anyway?) if you want your actual problem to be solved. Happy to help. I'll change my handler to act on the `sender` as per your suggestion. – djv Aug 16 '17 at 14:20
  • The real case would be a nightmare. The Noddy example is intended to be the simplest code that highlights (sic) the problem. It enabled Magnus to steer me in the direction of a workable solution. Your solution promises to be more readily generalizable but it doesn't yet handle ListViews which have SubItems (and I fully accept that is because I over-simplified the problem when Noddyfying it). If you add these lines to the end of Form Load you will see that the second column isn't rendered unless you turn off OwnerDraw: ListView1.Items(0).SubItems.Add("Strawberry"); (continued..) – DJDave Aug 16 '17 at 16:40
  • ListView1.Items(1).SubItems.Add("Pepper"); ListView1.Items(2).SubItems.Add("Plum"); ListView1.Items(3).SubItems.Add("Apple") – DJDave Aug 16 '17 at 16:41
  • ListView has handlers for DrawSubItem and DrawColumnHeader as well. If using columns and subitems, they should also be included. See my edit (this is as far as I'll go with this, you should be able to take it and run with it). – djv Aug 16 '17 at 17:38
  • The second column now appears when I run the app, but when I move the mouse over the data, the items in the second column disappear. If I click on a row, the item reappears (and stays thereafter). Does this happen at your end, and is there an easy fix? I'm going to accept your solution but it would be good to iron out this last niggle. – DJDave Aug 16 '17 at 17:51
  • So it had to do with FullRowSelect = true. And after a web search, I found [this answer](https://stackoverflow.com/a/20012661/832052) which is a more complete set of event handlers. I've converted the C# in the other answer to vb.net and updated my answer. – djv Aug 16 '17 at 18:19
  • Wow, fantastic stuff. A lot of work to add functionality that IMJO should be there anyway, but no less impressive for that. – DJDave Aug 17 '17 at 07:57
0

Easiest thing,

Just allocate the LST_ItemIndex = lstList.FocusedItem.Index everytime you select a different item

and then fire the below whenever you want the highlight

If lstList.Items.Count > 0 Then

        lstList.Items(LST_ItemIndex).Selected = True
        lstList.Items(LST_ItemIndex).EnsureVisible()
       
    End If