Home > database >  Select List View Items Using Dynamically Created Buttons - Code Limitations?
Select List View Items Using Dynamically Created Buttons - Code Limitations?

Time:09-14

enter image description here

After days of searching on the web and checking all SO similar questions here, I have a working version of the code I'm looking for! :-) But, it's very limited as I describe below! The reason is, I can't find a way to code for the way I want my program to work! The code below has taken me days and many attempts to get a working version! I'm using WIN10, Visual Studio 2019, VB.NET. Grateful to TEME64 for the example of creating buttons dynamically dynamic buttons w/ icons

The little program I'm trying to create 1. Dynamically creates buttons from the total number of List View items (i.e. this is already working). 2. It should then extract and apply program icons for each List View subitem to the dynamic buttons on a Flow Layout Panel (i.e. got this working for one button at a time, but I don't know how to assign a different program icon to each button from each of the paths in List View subitems)? 3. Then, when you click each button, it should start a different external program (i.e. from paths in List View subitems - i.e. Process Start below). My List View columns are: NAME = Program Name; PATH = Program Exec file.

Question 1: How do I write more concise code? I tried changing the item's Index number dynamically using a Numeric Up Down control, but I couldn't get this working? I.e. only the first item remains selected and only the first program opens??? Question 2: How do I apply the program icons from each listview item to the dynamic button's background image independently of how many items there are in the List View or dynamic buttons which will be the same amount? The List View is sorted alphabetically!
Question 3: How do I handle user Cancel for external programs?

What I have tried:

Creating Buttons Dynamically:

The following code creates a button for each List View item. The buttons are contained on a Flow Layout Panel separately from the List View. The minus 1 is because the code was creating 4 buttons for 3 List View Items:

Dim i As Integer

    For i = 0 To ListView1.Items.Count.ToString - 1 '30 = number of buttons to create!!!
        NewButton(i)
    Next i

Use Buttons to Select Different List View Items and Run Programs From Paths:

The following code actually works! But, it is very limited in several ways: 1. Repetitive: It's old school long hand. So, I need to repeat the code for each button/List View item! I don't want to limit the number of List View items or buttons created from them? A more objective code would be nice! 2. ButtonNumber: Each button identifies itself by number (i.e. 0, 1, 2) but I don't know how to connect this to each List View item's index number (SelectedIndices?) in order to select each List View item using its corresponding button (i.e. button with program icon derived from path in List View)? 3. Exception: The second program runs (i.e. Iobit Smart Defrag), but if I click Cancel on the Smart Defrag's startup dialog my code throws an exception which I don't know how to handle because it's an external program?

If ButtonNumber.ToString = 0 Then
        
        ListView1.SelectedItems.Clear()
        ListView1.SelectedIndices.Clear()
        ListView1.FocusedItem = ListView1.Items(0)
        ListView1.FocusedItem.Selected = True
        ListView1.FocusedItem.EnsureVisible()
        ListView1.Focus()

        Process.Start(ListView1.SelectedItems.Item(0).SubItems(1).Text.ToString())

    ElseIf ButtonNumber.ToString = 1 Then
        
        ListView1.SelectedItems.Clear()
        ListView1.SelectedIndices.Clear()
        ListView1.FocusedItem = ListView1.Items(1)
        ListView1.FocusedItem.Selected = True
        ListView1.FocusedItem.EnsureVisible()
        ListView1.Focus()

        'USER CANCEL THROWS EXCEPTION - HOW DO YOU HANDLE THIS???
        Process.Start(ListView1.SelectedItems.Item(0).SubItems(1).Text.ToString())

    ElseIf ButtonNumber.ToString = 2 Then
        
        ListView1.SelectedItems.Clear()
        ListView1.SelectedIndices.Clear()
        ListView1.FocusedItem = ListView1.Items(2)
        ListView1.FocusedItem.Selected = True
        ListView1.FocusedItem.EnsureVisible()
        ListView1.Focus()

        Process.Start(ListView1.SelectedItems.Item(0).SubItems(1).Text.ToString())

    End If

Suggested Code Adaptations:

I saved my original code to file and added the suggested code. I have made a few alterations to the code in an attempt to achieve my objective for the program (See new diagram above). However, although the alterations got me closer to what I need I currently can only get the results I want by selecting a LVW item and pressing the Get Selected Item button for each button to be added and their corresponding programs to run? Any ideas what I still need to do with the code to get it to: 1. Create a button for each LVW item; 2. Run a program from the selected item's subitem (i.e. exec Path)? 3. Pressing each button should select the corresponding LVW item and run the program?

The Adapted Code:

Changes: 1. Adapted myApps to load from LVW1 and added For Each loop; 2. Removed Button Text from create buttons because I only want the icon; 3. Added button size; 4. Moved LoadMyAppsList() and CreateButtons(myApps) from Form_Load to a button temporarily.

    Private Sub LoadMyAppsList()

'List Source = ListView1:
        For Each item As ListViewItem In ListView1.Items

            myApps = New List(Of ApplicationDetails)
            myApps.Add(New ApplicationDetails With
                       {.ApplicationName = ListView1.SelectedItems.Item(0).Text.ToString,
                       .ApplicationShortcut = ListView1.SelectedItems.Item(0).SubItems(1).Text.ToString(),
                       .ApplicationIcon = Icon.ExtractAssociatedIcon(ListView1.SelectedItems.Item(0).SubItems(1).Text.ToString())})
        Next

    End Sub

    Private Sub CreateButtons(myApps As List(Of ApplicationDetails))

        For Each appdet As ApplicationDetails In myApps

            Dim shortcutButton As Button = New Button With
                {.Image = appdet.ApplicationIcon.ToBitmap,
                .Tag = appdet.ApplicationShortcut}
            AddHandler shortcutButton.Click, AddressOf CommonButton_Click
            FlowLayoutPanel1.Controls.Add(shortcutButton)

            'Added - Button Size:
            shortcutButton.Width = 63
            shortcutButton.Height = 63

            MsgBox(appdet.ApplicationShortcut.ToString)

        Next

        'Removed:
        '.Text = appdet.ApplicationName,

    End Sub

    Private Sub Button5_Click(sender As Object, e As EventArgs) Handles Button5.Click

        ListView1.BeginUpdate()

        If ListView1.Items.Count > 0 AndAlso ListView1.SelectedItems.Count > 0 Then

            LoadMyAppsList()
            CreateButtons(myApps)

        End If

        ListView1.EndUpdate()

    End Sub

LVW Item Selection Using Buttons?

After working with the code for quite a while I discovered a way to get the corresponding LVW items to select when their buttons are clicked. I'm sure there's probably a better way, but this works! What I need now is a way to open all the buttons in one go creating them from the LVW items. Currently, I need to manually select a LVW item, click the Get Selected Item button and a button is created. Then, repeat this for each button creation??? I've tried changing the LVW code lines from SelectedItems to Items, but this doesn't fix the problem??? The following code works for selecting LVW items:

Private Sub CommonButton_Click(sender As Object, e As EventArgs)

    'Use variable to get exe path from button tag:
    Dim exePath As String = DirectCast(sender, Button).Tag.ToString

    'Find lvw item which contains same path as button tag:
    Dim item1 As ListViewItem = ListView1.FindItemWithText(exePath.ToString)
    
    'Select lvw item which corresponds to button: 
    ListView1.SelectedIndices.Clear()
    ListView1.FocusedItem = ListView1.Items(item1.Text.ToString)
    ListView1.FocusedItem.Selected = True
    ListView1.FocusedItem.EnsureVisible()
    ListView1.Focus()

    'Run program:
    Process.Start(exePath)

End Sub

THE APP

My Favourite Apps List

THE CODE:

I spent a lot of time debugging through the first code I found on Daniweb and also Andrew's code suggestions below to understand both of them. But, the code I have submitted below is a "no bells and whistles" version (i.e. minimal code for the app to work as proposed) that I managed to get working which is an adaptation of the code from Daniweb dynamic buttons:

'APP SETUP:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    'LVW SETUP:
    Me.ListView1.View = View.Details
    Me.ListView1.HideSelection = False
    ListView1.FullRowSelect = True
    ListView1.GridLines = True
    ListView1.Sorting = SortOrder.Ascending

    'SELECT FIRST LVW ITEM: 
    'COMMENT: Commented out lines which don't
    'affect first LVW item selection!!!
    'ListView1.SelectedIndices.Clear()
    ListView1.FocusedItem = ListView1.Items(0)
    ListView1.FocusedItem.Selected = True
    'ListView1.FocusedItem.EnsureVisible()
    ListView1.Select()
    'ListView1.Focus()

    'AUTORESIZE Name And Path COLUMNS
    Me.ListView1.AutoResizeColumn(0, ColumnHeaderAutoResizeStyle.ColumnContent)
    Me.ListView1.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.ColumnContent)

End Sub

'LOAD BUTTONS BUTTON:
Private Sub Button8_Click(sender As Object, e As EventArgs) Handles Button8.Click

    'CLEAR BUTTONS TO PREVENT DUPLICATES:
    FlowLayoutPanel1.Controls.Clear()

    'GET LVW ITEM'S TEXT:
    Dim username As String
    Dim session As String
    Dim output As String

    For Each item As ListViewItem In Me.ListView1.Items

        username = item.Text
        session = item.SubItems.Item(1).Text
        output = session 'username   " : "   session

        'OUTPUT RESULTS TO CONSOLE:
        Console.WriteLine(output)

        'EXTRACT PROGRAM EXE FILES FROM LVW SUBITEM PATHS:
        Dim ico As Icon = Icon.ExtractAssociatedIcon(output)

        'CREATE AND ADD NEW BUTTONS:
        Dim btn As New Button
        FlowLayoutPanel1.Controls.Add(btn)
        btn.BackgroundImage = ico.ToBitmap
        btn.BackgroundImageLayout = ImageLayout.Stretch
        btn.Size = New Size(63, 63)

        'ADD PATHS FROM LVW SUBITEMS TO EACH BUTTON TAG:
        btn.Tag = output

        'HANDLE DYNAMIC BUTTON'S CLICK (i.e. Links to dynamic buttons click event)
        AddHandler btn.Click, AddressOf onButtonClick

    Next

End Sub

'DYNAMIC BUTTONS CLICK - RUN PROGRAMS FROM BUTTONS:
Private Sub onButtonClick(ByVal sender As System.Object, ByVal e As System.EventArgs)

    'UPDATE LVW:
    ListView1.BeginUpdate()

    'CHECK SENDING BUTTON'S TAG:
    If CType(sender, Button).Tag IsNot Nothing Then

        'FIND LVW SUBITEM PATH WHICH CORRESPONDS TO PATH IN BUTTON TAG:
        Dim item1 As ListViewItem = ListView1.FindItemWithText(CType(sender, Button).Tag.ToString)

        'CHECK PATHS CORRESPOND:
        If (item1 IsNot Nothing) Then

            With ListView1

                'BUTTON CLICK SELECTS CORRESPONDING LVW ITEM: 
                .SelectedIndices.Clear()
                .FocusedItem = item1
                .FocusedItem.Selected = True
                .FocusedItem.EnsureVisible()
                .Focus()

            End With

            'RUN CORRESPONDING PROGRAM - PTL!!!
            Process.Start(ListView1.SelectedItems.Item(0).SubItems(1).Text.ToString)

            'THIS ALSO RUNS EACH PROGRAM CORRESPONDING WITH BUTTONS-PTL!!!
            'Process.Start(CType(sender, Button).Tag.ToString)

        Else

            'USER NOTIFICATION:
            MessageBox.Show("Item not found!")

        End If

    End If

    'END UPDATE LVW:
    ListView1.EndUpdate()

End Sub

Andrew Mortimer's Code:

I have tried the new code, but after renaming according to the naming convention I have used, adding the path to the CSV file, etc., I get the following exception: System.InvalidCastException: 'Conversion from string "ASC;C:\Program Files (x86)\IObit" to type 'Integer' is not valid.' on the following code block:

myApps.Add(New ApplicationDetails With
                   {.ID = values(0),
                   .ApplicationName = values(1),
                   .ApplicationShortcut = values(2),
                   .ApplicationIcon = Icon.ExtractAssociatedIcon(values(2))
                   })

CodePudding user response:

Hopefully this will get you close to what you need.
It is using a custom class to store your application details.
It uses a common button click event handler to handle all the click events.

Imports System.IO
Public Class ButtonsForm

    Private myApps As List(Of ApplicationDetails)
    Private maxAppId As Integer

    Private Class ApplicationDetails

        Public Property ID As Integer
        Public Property ApplicationName As String
        Public Property ApplicationShortcut As String
        Public Property ApplicationIcon As Icon

        ''' <summary>
        ''' Comma delim property to use when writing out app file
        ''' </summary>
        ''' <returns></returns>
        Public ReadOnly Property WriteLine As String
            Get
                Return String.Join(",", ID, ApplicationName, ApplicationShortcut)
            End Get
        End Property

    End Class

    Private Sub CommonButton_Click(sender As Object, e As EventArgs)

        'get the application id
        Dim searchID As Integer = DirectCast(sender, Button).Tag

        'find it in the listview
        For Each lv As ListViewItem In AppListView.Items
            If lv.Tag = searchID Then
                lv.Selected = True
                LaunchApplication(lv.SubItems(1).Text)
            End If
        Next

    End Sub

    Private Shared Sub LaunchApplication(applicationPath As String)

        Process.Start(applicationPath)

    End Sub

    Private Sub LoadMyAppsListFromCSV()

        Dim csvFilePath As String = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MyApps.csv")

        'initialise app list
        myApps = New List(Of ApplicationDetails)

        Dim lines() As String = File.ReadAllLines(csvFilePath)

        For Each line In lines
            Dim values() As String = line.Split(Convert.ToChar(","))
            myApps.Add(New ApplicationDetails With
                       {.ID = values(0),
                       .ApplicationName = values(1),
                       .ApplicationShortcut = values(2),
                       .ApplicationIcon = Icon.ExtractAssociatedIcon(values(2))
                       })
        Next

        'get the max app id to be used if another application is added
        maxAppId = myApps.Max(Function(x) x.ID)

    End Sub

    Private Sub CreateButtons(myApps As List(Of ApplicationDetails))

        'clear the controls
        ButtonsFLP.Controls.Clear()

        'add back
        For Each appdet As ApplicationDetails In myApps

            Dim shortcutButton As Button = New Button With
                {.Text = appdet.ApplicationName,
                .Image = appdet.ApplicationIcon.ToBitmap,
                .Tag = appdet.ID}
            AddHandler shortcutButton.Click, AddressOf CommonButton_Click
            ButtonsFLP.Controls.Add(shortcutButton)

        Next

    End Sub

    Private Sub InitFormButton_Click(sender As Object, e As EventArgs) Handles InitFormButton.Click

        InitForm()

    End Sub

    Private Sub InitForm()

        LoadMyAppsListFromCSV()
        CreateButtons(myApps)
        CreateListView(myApps)

    End Sub

    Private Sub CreateListView(myApps As List(Of ApplicationDetails))

        AppListView.Items.Clear()

        For Each appdet As ApplicationDetails In myApps

            Dim lvitem As ListViewItem = AppListView.Items.Add(appdet.ApplicationName)
            lvitem.SubItems.Add(appdet.ApplicationShortcut)
            lvitem.Tag = appdet.ID

        Next

    End Sub

    Private Sub AddAppButton_Click(sender As Object, e As EventArgs) Handles AddAppButton.Click

        Try
            Dim myNewApp As ApplicationDetails = New ApplicationDetails() With
                    {.ApplicationName = ApplicationNameTextBox.Text,
                    .ApplicationShortcut = ApplicationPathTextBox.Text
            }
            If ValidForAdd(myNewApp) Then
                AddApplication(myNewApp)
                InitForm()
            End If

        Catch ex As Exception
            MessageBox.Show(String.Concat("An error occurred: ", ex.Message))
        End Try

    End Sub

    Private Sub AddApplication(myNewApp As ApplicationDetails)

        'get the next id
        myNewApp.ID = maxAppId   1
        'add to the collection
        myApps.Add(myNewApp)
        'rewrite the collection
        WriteAppCollectionToCsv(myApps)

    End Sub

    Private Sub WriteAppCollectionToCsv(myApps As List(Of ApplicationDetails))

        Dim csvFilePath As String = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MyApps.csv")
        Dim backupFile As String = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, String.Format("MyApps_{0}.csv", Guid.NewGuid.ToString()))
        'backup the current file just in case..
        File.Move(csvFilePath, backupFile)
        'write the new file
        Using sw As New StreamWriter(csvFilePath)
            For Each item As ApplicationDetails In myApps
                sw.WriteLine(item.WriteLine)
            Next
        End Using

    End Sub

    Private Function ValidForAdd(myNewApp As ApplicationDetails) As Boolean

        If myNewApp.ApplicationName.Trim.Length = 0 Then
            MessageBox.Show("Please enter an application name", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning)
            ApplicationNameTextBox.Focus()
            Return False
        End If

        If myNewApp.ApplicationShortcut.Trim.Length = 0 Then
            MessageBox.Show("Please enter an application path", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning)
            ApplicationPathTextBox.Focus()
            Return False
        End If

        Dim appExists As Boolean = myApps.Any(Function(x) x.ApplicationName = myNewApp.ApplicationName And x.ApplicationShortcut = myNewApp.ApplicationShortcut)
        If appExists Then
            MessageBox.Show("You've added this already", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning)
            ApplicationNameTextBox.Focus()
            ApplicationNameTextBox.SelectAll()
            Return False
        End If

        Return True

    End Function

    Private Sub RemoveAppButton_Click(sender As Object, e As EventArgs) Handles RemoveAppButton.Click

        Try
            If AppListView.SelectedItems.Count = 0 Then
                MessageBox.Show("Please select an application to remove", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Return
            End If

            If DeleteSelectedApp() Then
                InitForm()
            End If

        Catch ex As Exception
            MessageBox.Show(String.Concat("An error occurred: ", ex.Message))
        End Try

    End Sub

    Private Function DeleteSelectedApp() As Boolean

        'get the id
        Dim delId As Integer = DirectCast(AppListView.SelectedItems(0).Tag, Integer)

        Dim appItemToRemove As ApplicationDetails = myApps.Where(Function(x) x.ID = delId).First
        myApps.Remove(appItemToRemove)
        WriteAppCollectionToCsv(myApps)

        Return True

    End Function

End Class
  • Related