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
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
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