View Full Version : [Visual Basic] Interfaccia grafica per installazione software
Salve a tutti!
Di tanto in tanto mi capita di andare da qualche conoscente per dare una sistemata al loro PC e spesso e volentieri mi chiedono di fargli anche un ripristino completo, così mi ritrovo molte volte a dover reinstallare decine di software manualmente. Per ovviare al problema sto sviluppando una piccola applicazione in Visual Basic per installare in modo automatico tutti i software "essenziali" di cui ho bisogno. A dire il vero l'applicazione in sé è solamente un'interfaccia grafica, poichè gli eseguibili dei programmi li faccio avviare da un file Batch, l'app in VB la uso solo per far scegliere all'utente il tipo d'installazione (Minimale o Completa) e il tipo di browser (Chrome,Firefox o Opera).
Ora, chiedo il vostro aiuto perchè ho un problema riguardante la parte in VB: vorrei aggiungere un Form che mostri il nome dell'applicazione che sto installando e una barra di progresso che indica l'avanzamento complessivo dell'installazione di tutte le applicazioni, il problema è che non so come fare. Vorrei inserire il nome dei programmi in testa allo script batch (che ho chiamato Install.bat) e poi farli leggere all'applicazione in VB, che modificherà la proprietà "Text" di una Label chiamata "lbl_app_name" man mano che viene avviata l'installazione di ogni programma. Il fatto è che non ho la minima idea di come fare, ho cercato ovunque ma non sono riuscito a trovare un codice che facesse al caso mio. Tutti i codici che ho trovato mi facevano leggere integralmente tutte le righe del Batch, io invece voglio fargli leggere solo alcune righe in una specifica posizione del file.
E oltretutto come faccio a impostare la barra di progresso?
Scusate se non ho spiegato molto beno, spero di essere stato abbastanza chiaro!! :D
Grazie in anticipo :)
Daniels118
25-07-2014, 14:44
Parliamo di VB6 o .net?
Poi mi sfugge un dettaglio... se ho ben capito hai un unico file batch che installa diversi programmi in un colpo solo. Se è così risulta abbastanza difficile capire a che punto è arrivato il batch semplicemente lanciandolo (si potrebbero fare vari accrocchi, ma non sarebbe né semplice né corretto).
L'ideale sarebbe che il batch scrivesse ciò che sta per installare o ha già installato sullo standard output oppure in un file.
L'interfaccia grafica potrebbe leggere ad intervalli regolari dallo stdout oppure dal file ed aggiornare la label.
Per la barra di progresso ciò che devi sapere è che essa rappresenta un valore relativo. Nel tuo caso è relativo al numero di installazioni che vuoi eseguire. Se questa informazione è già conosciuta dall'interfaccia grafica basterà dividere il numero di installazioni completate per il numero di installazioni totali e moltiplicare per il valore corrispondente alla barra piena. Il numero di installazioni completate puoi ricavarlo dai messaggi stampati dal batch: se ad esempio stampi due righe per ogni installazione ("sto iniziando l'installazione di X" e "ho completato l'installazione di X"), basterà dividere il numero di righe lette per 2 ed arrotondare per difetto.
Grazie della risposta Daniels118!
Prima di tutto io uso Visual Basic 2010 Express. Poi per il file Batch hai capito bene, ho un unico file "Install.bat" che in modo automatico determina la versione dell'OS installato e l'architettura, poi a seconda del browser e del metodo di installazione scelto, l'applicazione in VB avvia il file Batch con un determinato parametro per dirgli il tipo di installazione da effettuare [Shell ("Install.bat 1")]
Per il fatto che dicevi tu di far scrivere l'output al batch, quello lo fa già, infatti nel codice del bat ho inserito un "@ECHO Installazione di [NomeApplicazione]" prima dell'avvio di ogni eseguibile, ma non ho idea di come utilizzarlo in VB. Non c'è un modo per far leggere al programma in VB l'output @ECHO?
Daniels118
25-07-2014, 16:16
Purtroppo io sono rimasto a VB6, comunque puoi seguire 2 strade, abbastanza simili invero.
La prima consiste nell'utilizzare un thread adhoc per gestire delle chiamate bloccanti al metodo StreamReader.ReadLine, la seconda invece è quella di utilizzare un timer e combinare il metodo ReadLine con Peek per ottenere delle read non bloccanti.
Puoi prendere spunto da qui: http://www.vb-helper.com/howto_net_run_dos.html
Naturalmente il metodo ReadToEnd va eliminato e sostituito con una delle gestioni descritte prima, le chiamate ai metodi Close vanno fatte al termine dell'esecuzione del batch.
Se non sono stato abbastanza chiaro chiedi pure.
Guarda non sono molto ferrato in VB e in programmazione in generale, di solito tendo a prendere codice già fatto e a modificarlo per adattarlo ai miei scopi non me la cavo bene a programmare da solo, quindi non ho capito molto bene come gestire le chiamate bloccanti, perdonami...
Comunque, una delle tue proposte iniziali era quella di far scrivere al file Batch il suo output in un file e così ho fatto, sembra funzionare, adesso il bat scrive il titolo del programma che sta installando in un file "Output.dat", poi cancella il titolo e lo sostituisce con quello dell'app successiva. Ora come faccio a far leggere il contenuto di "Sutput.dat" al programma in VB e a modificare il campo Text della Label "lbl_app_name"? Potresti buttarmi giù qualche riga di codice, giusto per rendere l'idea?
Grazie ancora :)
Daniels118
25-07-2014, 16:51
Vedi qui: http://msdn.microsoft.com/it-it/library/a77w6kkx.aspx
fossi in te imparerei ad usare Powershell, tanto per farti un regalo da solo ed abbandonare VB.
http://microsoft.com/powershell
Grazie Freaxxx poi do un occhio a PowerShell.
Per Daniels118: ero già stato su quella pagina e infatti adesso stavo provando un codice simile a quello. Ho dichiarato una variabile Integer
Dim fullPath As String = "Software\Output.tmp"
Poi, dopo il frammento di codice che avvia il file Batch ho inserito il codice per modificare la Label lbl_app_name:
lbl_app_name.Text = IO.File.ReadAllText(fullPath)
Ora, ho un piccolo problemino nel senso che mi dice che il file "Output.tmp" non esiste e poi crasha il programma, comunque credo di riuscire a sistemare da solo. Ho bisogno di un'altra cosa invece, come gli dico al programma di controllare più volte il file Output.tmp? O meglio, come faccio a fargli capire che deve controllare appena termina l'installazione di un programma?
Nel link che mi hai inviato il codice permette di controllare il file una singola volta.
Daniels118
25-07-2014, 18:41
Solo in teoria: devi fare un loop che ripete la lettura sul file.
In pratica questo approccio non va bene in un'applicazione che ha un interfaccia grafica, perché bloccherebbe tutto.
Le alternative sono 2:
1) inserire il loop in un thread ad hoc;
2) effettuare le letture all'interno di un evento schedulato.
Il secondo approccio è più semplice, qui trovi un esempio:
http://www.vbtutor.net/vb2010/vb2010_Lesson27.htm
Ok, il problema del file "Output.tmp" che il software mi diceva essere inesistente l'ho risolto facendogli creare il file direttamente all'applicazione.
Anche il problema del titolo dei programmi è risolto, mi è bastato utilizzare la funzione Timer (impostata a 100 millisecondi):
Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer.Tick
lbl_app_name.Text = IO.File.ReadAllText(OutputPath)
End Sub
Adesso mi manca un'ultima cosa, poi la smetto di stressarti, Daniel118 :)
La barra di progresso. Per cambiare la "pienezza" della barra devo agire sulla proprietà Value, ma non so come collegare l'incremento di Value al variare della proprietà Text di lbl_app_name. Non posso utilizzare il timer di prima perchè sennò naturalmente la barra si riempe di un tot. ogni 100 millisecondi, io invece voglio farla aumentare quando cambia il titolo del programma. Sai come fare?
Daniels118
25-07-2014, 22:09
Come ti dicevo prima la barra rappresenta una grandezza relativa, devi sapere a priori quante installazioni devi fare in totale e quante installazioni sono state completate man mano che vengono eseguite.
Supponiamo che il numero di installazioni sia fisso, rimane da calcolare quante installazioni sono state completate in un certo istante.
Ci sono vari modi, comunque io ti suggerisco di modificare il tuo batch in modo da non sovrascrivere il file, bensì da accodare l'installazione in corso. Per farlo basta utilizzare l'operatore >> al posto di > quando fai echo.
Dopo aver letto il file in una stringa utilizza la funzione split impostando come separatore la costante vbcrlf per dividere il contenuto del file in un array, ogni elemento conterrà una riga. Contando il numero di elementi saprai quante installazioni hai completato (occhio che ad ogni echo viene inserito anche un a capo).
Ok sono riuscito a risolvere da solo, per far progredire la barra mi è bastato dire al programma di aggiungere 10 alla proprietà Value ogni volta che si attiva l'evento Paint su "lbl_app_name".
Private Sub lbl_app_name_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles lbl_app_name.Paint
If InstallMode = 0 Then
pbr_install.Value = pbr_install.Value + 10
Else
pbr_install.Value = pbr_install.Value + 7
End If
End Sub
Quando arriva alla fine però mi da errore e l'applicazione crasha perchè dice la barra di rpogresso ha superato il massimo. Matematicamente non è così però perchè se ho 10 applicazioni da installare è giusto che ognuna mi incrementi la barra di 10. C'è un modo per eliminare il limite della barra? Esiste poi un evento che indica che la barra è completamente piena?
Daniels118
25-07-2014, 22:18
Ti basterebbe controllare quando la barra arriva al massimo e disattivare il timer.
malatodihardware
26-07-2014, 09:41
Perché andare a reinventarsi la ruota quando esiste già Ninite che fa proprio quello che ti serve?
Per malatodihardware: avevo già dato un'occhiata a Ninite ma quest'ultimo si serve di internet per fare tutto e spesso e volentieri mi è capitato di ripristinare computer momentaneamente sprovvisti di connessione Internet, quindi ho bisogno di uno strumento che sia 100% offline e Ninite (forse sono io che ho visto male eh :) ) lavora solo online.
Daniels118: La barra di progresso deve essere posseduta :D ,alla fine dell'installazione a volte è completamente piena, a volte a tre quarti e a volte a metà... bah.
Comunque, visto che con gli script Batch è così incasinato lavorare ho deciso di riscrivere il programma e fargli fare tutto da sé, senza l'ausilio di alcuno script esterno. Sono già a buon punto ma avrei bisogno, quando hai un attimo di tempo, di ancora un po' d'aiuto.
Per permettere al software di "trovare" i software da installare ho inserito un form "Impostazioni" in cui l'utente attraverso un pulsante "Sfoglia" apre un FolderDialogBrowser in cui seleziona la directory contenente i software da installare e, dopo aver confermato con OK, in una CheckedListBox compaiono automaticamente tutti i file eseguibili trovati nella cartella scelta. Nella CheckedListBox, però, non compare il percorso di ogni file eseguibile ma soltanto il suo "ProductName" o, in assenza di ques'ultimo, il solo nome del file. Il problema è che adesso vorrei che alla pressione di un pulsante "Salva", il percorso di ogni singolo file scelto dalla lista venga inserito in un file "Settings.ini", ma non riesco perchè nella CheckedListBox compare solo il ProductName e quello è l'unica cosa che riesco a fare. C'è un modo di mostrare nella CheckedListBox il ProductName e nel frattempo memorizzare il percorso del file?
Ho bisogno di tutto questo perchè alla fine quando l'utente premerà il pulsante "Installa" il programma andrà nel file Settings.ini e leggerà il percorso degli eseguibili da avviare e poi li passerà uno ad uno alla funzione "Shell".
Ti allego il codice del form almeno riesci a capirci un po' di più della mia spiegazione ingarbugliata :D
Module Modulo_Variabili
Public AppPath As String '//Contiene il percorso della directory dei software
End Module
Imports System.IO
Imports System
Imports System.Diagnostics
Public Class frm_settings
'//Contatore dei file presenti nella CheckedTextBox
Dim fileWriterPath As System.IO.File
Dim CountApps32 As Integer
Dim CountApps64 As Integer
'//Processazione di file e directory presenti in un percorso passato
Public Sub ProcessDir(ByVal Dir As String)
'//Processa la lista dei files trovati nella directory
Dim fileEntries As String() = Directory.GetFiles(Dir)
For Each fileName As String In fileEntries
ProcessFile(fileName)
Next
' //Processa tutte le directory trovate nella directory passata alla funzione
Dim subdirectoryEntries As String() = Directory.GetDirectories(Dir)
For Each subdirectory As String In subdirectoryEntries
ProcessDir(subdirectory)
ProcessSubDir(subdirectory)
Next
End Sub
Public Sub ProcessSubDir(ByVal SubDir As String)
'//Processa tutte le sottodirectory delle directory in subdirectoryEntries
Dim DoubleSubDirectory As String() = Directory.GetDirectories(SubDir) '//Processa tutte le subdirectory delle directory in subdirectoryEntries
For Each SingleSubDirectory As String In DoubleSubDirectory
ProcessDir(SingleSubDirectory)
Next
End Sub
'//Processa tutti i file trovati.
Public Sub ProcessFile(ByVal FilePath As String)
Dim AppInfo As System.IO.FileInfo
'//Acquisice informazioni su ogni file
AppInfo = My.Computer.FileSystem.GetFileInfo(FilePath)
Dim AppName As FileVersionInfo = FileVersionInfo.GetVersionInfo(FilePath)
Dim FileExtension As String = Path.GetExtension(FilePath).ToLowerInvariant()
'//Aggiunge alla CheckedListBox solo i file eseguibili
'//Se l'estensione è ".exe" e la Checkbox "cbx_show_only_path" non è attiva
'//allora a clb_app_list vengono aggiunte le voci con il ProductName di ciascun eseguibile,
'//altrimenti (cbx_show_only_path attiva) viene mostrato il percorso del file.
If FileExtension = ".exe" Then
If cbx_show_only_path.Checked = False Then
If AppName.ProductName <> "" Then
'//Se non esiste il ProductName, le voci inserite conterranno solo il nome dell'eseguibile
clb_app_list.Items.Add(AppName.ProductName)
Else : clb_app_list.Items.Add(AppInfo.Name)
End If
Else : clb_app_list.Items.Add(FilePath)
End If
'//Se l'estensione è ".bat" e cbx_show_script è attiva, alla lista
'//sono aggiunte voci contenenti solo il percorso di ciascun file.
ElseIf FileExtension = ".bat" And cbx_show_script.Checked = True Then
clb_app_list.Items.Clear()
clb_app_list.Items.Add(FilePath)
End If
End Sub
'//Evento click pulsante "Sfoglia"
Private Sub cmd_open_dir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmd_open_dir.Click
'//Se il risultato di fbd_dir_path è OK, il percorso scelto dall'utente...
If fbd_dir_path.ShowDialog = DialogResult.OK Then
clb_app_list.Items.Clear()
'//...viene inserito in tbx_dir_path
tbx_dir_path.Text = fbd_dir_path.SelectedPath
'//e viene archiviato il percorso della directory
'//nella variabile AppPath.
AppPath = fbd_dir_path.SelectedPath
'//Viene processato il percorso utente e vengono aggiunti i file
'//trovati a clb_app_list.
ProcessDir(AppPath)
'//Il percorso viene salvato in un file esterno denominato "Settings.ini"
IniWrite("D:\Settings.ini", "SoftwareDir", "PATH", AppPath & vbCrLf)
End If
End Sub
'//Evento Load frm_settings
Private Sub frm_settings_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load '//CARICAMENTO DEL FORM
'//All'apertura di frm_settings, in tbx_dir_path viene inserito
'//il percorso della directory utente, precedentemente archiviato nella variabile AppPath.
tbx_dir_path.Text = AppPath
'//Refresh della CheckedListBox
If My.Computer.FileSystem.FileExists("D:\Settings.ini") Then
ProcessDir(AppPath)
End If
End Sub
'//Evento click pulsante "Aggiorna"
Private Sub cmd_refresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmd_refresh.Click
'//Refresh della CheckedListBox
clb_app_list.Items.Clear()
ProcessDir(AppPath)
End Sub
'//Evento click pulsante "Predefinito"
Private Sub cmd_default_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmd_default.Click
End Sub
'//Evento CheckedChanged di cbx_show_only_path
Private Sub cbx_show_only_path_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cbx_show_only_path.CheckedChanged
clb_app_list.Items.Clear()
ProcessDir(AppPath)
End Sub
'//Evento CheckedChanged di cbx_show_script
Private Sub cbx_show_script_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cbx_show_script.CheckedChanged
clb_app_list.Items.Clear()
ProcessDir(AppPath)
End Sub
Private Sub cmn_set_default_32ToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmn_set_default_32.Click
'//Salva il percorso di ciascun file a 32Bit in clb_app_list nel file "Settings.ini"
CountApps32 = 0
For Each clbItem As Object In clb_app_list.CheckedItems
CountApps32 += 1
IniWrite("D:\Settings.ini", "32BitUserPath", "ExePath" & CountApps32, clbItem)
Next
End Sub
Private Sub cmn_set_default_64ToolStripMenuItem1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmn_set_default_64.Click
'//Salva il percorso di ciascun file a 64Bit in clb_app_list nel file "Settings.ini"
CountApps64 = 0
For Each clbItem As Object In clb_app_list.CheckedItems
CountApps64 += 1
IniWrite("D:\Settings.ini", "64BitUserPath", "ExePath" & CountApps64, clbItem)
Next
End Sub
End Class
Grazie ancora dell'aiuto che mi hai dato fino ad adesso! :D
Daniels118
28-07-2014, 20:03
Modifica la sub ProcessFile passando in input la variabile dir oltre a filename.
Puoi inserire direttamente dir & filename all'interno della lista, oppure se ritieni che sia brutto puoi costruire parallelamente alla lista un array nel quale metti i path completi, gli indici della lista e quelli dell'array coincideranno.
Ho provato a fare come hai detto tu, Daniels118, ho creato un array in cui inserire il percorso di ciascun file inserito nella CheckedListBox, ma sembra non funzionare come dovrebbe. Premetto che sugli array so quasi niente, ma dando un'occhiata sul web sono riuscito a capire almeno come si dichiarano e come aggiungervi gli elementi. Il primo problema è che se quando dichiaro l'array non gli do subito la dimensione massima dell'indice, questo mi incasina il programma, nel senso che mi inserisce nella CheckedListBox soltanto il primo file della directory, il primo analizzato quindi, tralasciando tutti gli altri e io non capisco perchè. Ho provato ad aggiungere la dimensione dell'indice e pareva che il problemafosse risolto. In realtà non è così perchè quando vado a estrarre dall'array il percorso di ogni Item selezionato nella CheckedListBox per inserirlo in un file INI, alla fine mi ritrovo soltanto con il percorso dell'ULTIMO item della lista. Non capisco proprio dove sbaglio :confused:
Per quanto riguarda la funzione ProcessFile, che dicevi di modificare in modo da passargli il percorso della directory oltre che al nome del file, mi sono accorto che era già così, nel senso che la variabile che ho chiamato "filename" in realtà contiene il percorso del file...lo so sono idiota :D
Comunque ti posto ancora il codice modificato almeno puoi dirmi se ho fatto qualche cavolata con l'Array (molto probabile).
Grazie ancora :)
Imports System.IO
Imports System
Imports System.Diagnostics
Public Class frm_settings
Dim fileWriterPath As System.IO.File
Dim CountApps32 As Integer '//Contatore dei file selezionati nella CheckedTextBox (32Bit)
Dim CountApps64 As Integer '//Contatore dei file selezionati nella CheckedTextBox (64Bit)
Dim FilePathArrayIndex As Integer '//Array che contiene il percorso di ciascun file scansionato
Dim FilePathArray(50) As String '//Indice dell'Array precedentemente dichiarato
'//Processazione di file e directory presenti in un percorso passato
Public Sub ProcessDir(ByVal Dir As String)
'//Processa la lista dei files trovati nella directory
Dim fileEntries As String() = Directory.GetFiles(Dir)
For Each fileName As String In fileEntries
ProcessFile(fileName)
Next
' //Processa tutte le directory trovate nella directory passata alla funzione
Dim subdirectoryEntries As String() = Directory.GetDirectories(Dir)
For Each subdirectory As String In subdirectoryEntries
ProcessDir(subdirectory)
ProcessSubDir(subdirectory)
Next
End Sub
Public Sub ProcessSubDir(ByVal SubDir As String)
'//Processa tutte le sottodirectory delle directory in subdirectoryEntries
Dim DoubleSubDirectory As String() = Directory.GetDirectories(SubDir) '//Processa tutte le subdirectory delle directory in subdirectoryEntries
For Each SingleSubDirectory As String In DoubleSubDirectory
ProcessDir(SingleSubDirectory)
Next
End Sub
'//Processa tutti i file trovati.
Public Sub ProcessFile(ByVal FilePath As String)
'//Array per contenere il percorso di ciascun file
Dim AppInfo As System.IO.FileInfo
'//Acquisice informazioni su ogni file
AppInfo = My.Computer.FileSystem.GetFileInfo(FilePath)
Dim AppName As FileVersionInfo = FileVersionInfo.GetVersionInfo(FilePath)
Dim FileExtension As String = Path.GetExtension(FilePath).ToLowerInvariant()
'//Aggiunge alla CheckedListBox solo i file eseguibili
'//Se l'estensione è ".exe" e la Checkbox "cbx_show_only_path" non è attiva
'//allora a clb_app_list vengono aggiunte le voci con il ProductName di ciascun eseguibile,
'//altrimenti (cbx_show_only_path attiva) viene mostrato il percorso del file.
If FileExtension = ".exe" Then
If cbx_show_only_path.Checked = False Then
If AppName.ProductName <> "" Then
'//Se non esiste il ProductName, le voci inserite conterranno solo il nome dell'eseguibile
clb_app_list.Items.Add(AppName.ProductName)
FilePathArray(FilePathArrayIndex + 1) = FilePath '//Aggiunge il percorso del file all'Array
Else : clb_app_list.Items.Add(AppInfo.Name)
FilePathArray(FilePathArrayIndex + 1) = FilePath '//Aggiunge il percorso del file all'Array
End If
Else : clb_app_list.Items.Add(FilePath)
FilePathArray(FilePathArrayIndex + 1) = FilePath '//Aggiunge il percorso del file all'Array
End If
'//Se l'estensione è ".bat" e cbx_show_script è attiva, alla lista
'//sono aggiunte voci contenenti solo il percorso di ciascun file.
ElseIf FileExtension = ".bat" And cbx_show_script.Checked = True Then
clb_app_list.Items.Clear()
clb_app_list.Items.Add(FilePath)
FilePathArray(FilePathArrayIndex + 1) = FilePath '//Aggiunge il percorso del file all'Array
End If
End Sub
'//Evento click pulsante "Sfoglia"
Private Sub cmd_open_dir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmd_open_dir.Click
'//Se il risultato di fbd_dir_path è OK, il percorso scelto dall'utente...
If fbd_dir_path.ShowDialog = DialogResult.OK Then
clb_app_list.Items.Clear()
'//...viene inserito in tbx_dir_path
tbx_dir_path.Text = fbd_dir_path.SelectedPath
'//e viene archiviato il percorso della directory
'//nella variabile AppPath.
AppPath = fbd_dir_path.SelectedPath
'//Viene processato il percorso utente e vengono aggiunti i file
'//trovati a clb_app_list.
ProcessDir(AppPath)
'//Il percorso viene salvato in un file esterno denominato "Settings.ini"
IniWrite("D:\Settings.ini", "SoftwareDir", "PATH", AppPath & vbCrLf)
End If
End Sub
'//Evento Load frm_settings
Private Sub frm_settings_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load '//CARICAMENTO DEL FORM
'//All'apertura di frm_settings, in tbx_dir_path viene inserito
'//il percorso della directory utente, precedentemente archiviato nella variabile AppPath.
tbx_dir_path.Text = AppPath
'//Refresh della CheckedListBox
If My.Computer.FileSystem.FileExists("D:\Settings.ini") Then
ProcessDir(AppPath)
End If
End Sub
'//Evento click pulsante "Aggiorna"
Private Sub cmd_refresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmd_refresh.Click
'//Refresh della CheckedListBox
clb_app_list.Items.Clear()
FilePathArrayIndex = 0
ProcessDir(AppPath)
End Sub
'//Evento click pulsante "Predefinito"
Private Sub cmd_default_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmd_default.Click
End Sub
'//Evento CheckedChanged di cbx_show_only_path
Private Sub cbx_show_only_path_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cbx_show_only_path.CheckedChanged
clb_app_list.Items.Clear()
ProcessDir(AppPath)
End Sub
'//Evento CheckedChanged di cbx_show_script
Private Sub cbx_show_script_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cbx_show_script.CheckedChanged
clb_app_list.Items.Clear()
ProcessDir(AppPath)
End Sub
Private Sub cmn_set_default_32ToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmn_set_default_32.Click
'//Salva il percorso di ciascun file a 32Bit in clb_app_list nel file "Settings.ini"
CountApps32 = 0
For Each clbItem As Object In clb_app_list.CheckedItems
CountApps32 += 1
MessageBox.Show(clbItem)
IniWrite("D:\Settings.ini", "32BitUserPath", "ExePath" & CountApps32, FilePathArray(clb_app_list.CheckedItems.IndexOf(clbItem)))
Next
End Sub
Private Sub cmn_set_default_64ToolStripMenuItem1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmn_set_default_64.Click
'//Salva il percorso di ciascun file a 64Bit in clb_app_list nel file "Settings.ini"
CountApps64 = 0
For Each clbItem As Object In clb_app_list.CheckedItems
CountApps64 += 1
MessageBox.Show(clbItem)
IniWrite("D:\Settings.ini", "64BitUserPath", "ExePath" & CountApps64, FilePathArray(clb_app_list.CheckedItems.IndexOf(clbItem)))
Next
End Sub
End Class
Daniels118
01-08-2014, 19:48
Ci sono 2 problemi:
1) ogni volta che chiami il metodo Items.Clear() devi anche impostare FilePathArrayIndex a zero; l'hai fatto in cmd_refresh_Click ma non in ProcessFile;
2) ogni volta che chiami il metodo Items.Add() devi scrivere:
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
e non
FilePathArray(FilePathArrayIndex + 1) = FilePath
altrimenti vai a scrivere sempre nella posizione 1, perché il valore di FilePathArrayIndex non cambia.
Ok ho modificato e il programma scrive i percorsi nel file ini, ma ancora una volta c'è un "ma". Non mi scrive i percorsi corretti. Se io seleziono 7 voci a caso, il programma mi scrive nel file ini 7 percorsi, ma sono i file che ho scelto bensì sono i primi 7 item della CheckedListBox. Ho controllato con una MessageBox e gli indici dell'Array corrispondono a quelli della CheckedListBox, quindi quello è corretto. Credo sia sbagliata la parte in cui gli faccio scrivere i percorsi nell'INI, forse gli passo la variabile sbagliata.
Questa è la parte di codice che si occupa di scrivere il file INI dopo aver premuto la voce "Salva per 32Bit" nel ContextMenu.
Private Sub cmn_set_default_32ToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmn_set_default_32.Click
'//Salva il percorso di ciascun file a 32Bit in clb_app_list nel file "Settings.ini"
CountApps32 = 0
For Each clbItem As Object In clb_app_list.CheckedItems
CountApps32 += 1
IniWrite("D:\Settings.ini", "32BitUserPath", "ExePath" & CountApps32, FilePathArray(clb_app_list.CheckedItems.IndexOf(clbItem)))
Next
End Sub
Penso che l'errore stia nella variabile "clbItem" perchè con la MessageBox mi dice che il suo indice non corrisponde a quello della lista, inizia da 0 e aumenta di 1 ogni volta che viene inserito in lista un file. Il fatto è che non so come indicare all'Array gli indici delle voci premute nella CheckedListBox, l'unico modo che so è quello riportaso sopra ma evidentemente non va bene.
Riesci a vedere cosa c'è che non va?
Daniels118
01-08-2014, 21:42
Questo accade perché stai accedendo all'array tramite gli indici degli elementi della collection CheckedItems, che non corrispondono agli indici degli stessi elementi nella collection Items.
Se ad esempio hai questi Items
0 A
1 B checked
2 C
3 D checked
4 E
5 F
in CheckedItems avrai:
0 B
1 D
Prova così:
FilePathArray(clb_app_list.Items.IndexOf(clbItem))
Ok ho appena provato e sembra funzionare correttamente, mi sono accorto di una cosa però, la CheckBox (cbx_show_script) che dovrebbe farmi vedere nella CheckedListBox soltanto gli Script in Bat non funziona, mi fa vedere una decina di file eseguibili ma nel codice non pare vi siano errori.
If FileExtension = ".exe" Then
If cbx_show_only_path.Checked = False Then
If AppName.ProductName <> "" Then
'//Se non esiste il ProductName, le voci inserite conterranno solo il nome dell'eseguibile
clb_app_list.Items.Add(AppName.ProductName)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
Else : clb_app_list.Items.Add(AppInfo.Name)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
End If
Else : clb_app_list.Items.Add(FilePath)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
End If
'//Se l'estensione è ".bat" e cbx_show_script è attiva, alla lista
'//sono aggiunte voci contenenti solo il percorso di ciascun file.
ElseIf FileExtension = ".bat" And cbx_show_script.Checked = True Then
clb_app_list.Items.Clear()
FilePathArrayIndex = 0
clb_app_list.Items.Add(FilePath)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
End If
Daniels118
01-08-2014, 22:01
La condizione nell'if viene controllata prima di quella nell'elseif, se viene soddisfatta la prima la seconda non viene mai controllata.
Può darsi che tutti i file che analizzi abbiano estensione .exe, è impossibile risponderti senza conoscere i dati, comunque il mio consiglio è di mettere un breakpoint e verificare cosa succede a runtime.
Allora, nelle cartelle che vado a scansionare con il programma ci sono sia file exe che bat, la funzione della CheckBox "cbx_show_script" è quella di aggiungere semplicemente alla lista anche i file Bat oltre che agli eseguibili. Il problema è che come ti dicevo prima mi vengono mostrati solo alcuni eseguibili, una decina circa quando invece sono oltre 30, e un unico file Bat quando invece anche questi sono un bel po'. Ho sostituito l'istruzione IF, usata per determinare l'estensione del file, con un CASE SELECT ma il risultato non cambia.
Select Case FileExtension
Case ".exe"
If cbx_show_only_path.Checked = False Then
If AppName.ProductName <> "" Then
'//Se non esiste il ProductName, le voci inserite conterranno solo il nome dell'eseguibile
clb_app_list.Items.Add(AppName.ProductName)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
Else : clb_app_list.Items.Add(AppInfo.Name)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
End If
Else : clb_app_list.Items.Add(FilePath)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
End If
Case ".bat"
'//Se l'estensione è ".bat" e cbx_show_script è attiva, alla lista
'//sono aggiunte voci contenenti solo il percorso di ciascun file.
If cbx_show_script.Checked = True Then
clb_app_list.Items.Clear()
FilePathArrayIndex = 0
clb_app_list.Items.Add(AppInfo.Name)
FilePathArray(FilePathArrayIndex) = FilePath
FilePathArrayIndex = FilePathArrayIndex + 1
End If
End Select
Daniels118
02-08-2014, 12:43
Quando trovi un bat fai il clear della lista, se i file vengono elaborati in questo ordine:
20 file exe
1 file bat
10 file exe
è normale che i primi 20 exe vengano cancellati.
Lascia perdere era una cretinata, bastava eliminare "clb_app_list.Clear" nel Case "bat" e ha incominciato a funzionare nel modo giusto. C'è un altro problema (lo so, sono un rimpiscatole e il mio software fa acqua da tutte le parti, ma vorrei riuscire a vederlo funzionare sto dannato programmino!:) ), il form che si dovrebbe occupare di far partire i programmi della lista non funziona, cioè fino a dieci minuti fa mi faceva partire soltanto il primo programma della lista, poi ho modificato il codice svariate volte e addirittura adesso il programma crasha, e la finestra del debug di NET Framework dice "cast non valido alla stringa "" al tipo 'integer'" e fa riferimento alla riga 12. Questo è il frammento di codice:
Public Class frm_install
Dim SoftwarePath As String '//Percorso di ogni eseguibile da avviare
Dim CountAppsToInstall32 As Integer '//Contatore delle applicazioni da installare (32Bit)
Dim CountAppsToInstall64 As Integer '//Contatore delle applicazioni da installare (64Bit)
Private Sub frm_install_Leave(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Leave
frm_main.Show()
End Sub
Private Sub frm_install_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
CountAppsToInstall32 = IniRead("D:\Settings", "32BitAppsCounter", "AppsNumber") '//Lettura numero di app da installare da "Settings.ini (32Bit)"
CountAppsToInstall64 = IniRead("D:\Settings", "64BitAppsCounter", "AppsNumber") '//Lettura numero di app da installare da "Settings.ini (64Bit)"
Select Case InstallType '//Rileva il tipo di installazione scelta, "Standard" o "Personalizzata".
Case "Standard"
If Is64Bit = False Then '//Se il sistema è a 32Bit
Do While CountAppsToInstall32 > 0
SoftwarePath = IniRead("D:\Settings.ini", "32BitUserPath", "ExePath" & CountAppsToInstall32)
Shell(SoftwarePath, vbNormalFocus)
CountAppsToInstall32 -= 1
Loop
Else '//Se il sistema è a 64Bit
Do While CountAppsToInstall64 > 0
SoftwarePath = IniRead("D:\Settings.ini", "64BitUserPath", "ExePath" & CountAppsToInstall64)
Shell(SoftwarePath, vbNormalFocus)
CountAppsToInstall64 -= 1
Loop
End If
Case "Custom"
lbl_app_name.Text = ThisApplication
End Select
End Sub
End Class
Ho sostituito anche il Do While con un For, ma non funziona comunque. Il programma crasha solo se lo avvio fuori dall'IDE, se invece lo avvio come "Avvia Debug" da VB il form d'installazione si apre ma resta li statico, senza fare nulla. Non si capisce se il problema dipenda dal "Do While" o più indietro, dove assegno alle variabili "CountAppsToInstall" i rispettivi valori contenuti nell'INI. Volevo utilizzare quelle due variabili (che indicano il numero di app da avviare, in versione 32Bit o 64Bit) in un ciclo per indicare al programma quando smettere di leggere l'INI, ovvero quando la variabile arriva a 0. Non so se mi spiego.
Daniels118
02-08-2014, 13:36
Verifica nell'ini che 32BitAppsCounter sia valorizzata correttamente.
Nel file ini sono corretti, infatti si presentano così:
[32BitAppsCounter]
AppsNumber=7
[64BitAppsCounter]
AppsNumber=7
Usando una MessageBox il valore di CountAppsToInstall32 e CountAppsToInstall64 risulta zero. Il valore nell'INI c'è, ma è come se non riuscisse a leggerlo.
Questa è la funzione per la lettura/scrittura del file INI (l'ho messa in un modulo a parte):
'//Funzione per la scrittura di file INI
Public Declare Auto Function GetPrivateProfileString Lib "kernel32.dll" (ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Integer, ByVal lpFileName As String) As Integer
Public Declare Auto Function WritePrivateProfileString Lib "kernel32.dll" (ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal lpString As String, ByVal lpFileName As String) As Integer
Friend Function IniRead(ByVal Filename As String, ByVal Section As String, ByVal Key As String, Optional ByVal lpDefault As String = "", Optional ByVal bRaiseError As Boolean = False) As String
Dim RetVal As String = New String(" ", 255)
Dim LenResult As Integer
Dim ErrString As String
LenResult = GetPrivateProfileString(Section, Key, lpDefault, RetVal, RetVal.Length, Filename)
If LenResult = 0 AndAlso bRaiseError Then
If Not (System.IO.File.Exists(Filename)) Then
ErrString = "Impossibile aprire il file INI" & Filename
Else
ErrString = "La sezione o la chiave sono errate oppure l’accesso al file non è consentito"
End If
Throw New Exception(ErrString)
End If
Return RetVal.Substring(0, LenResult)
End Function
Friend Function IniWrite(ByVal Filename As String, ByVal Section As String, ByVal Key As String, ByVal Value As String, Optional ByVal bRaiseError As Boolean = False) As Boolean
Dim LenResult As Integer
Dim ErrString As String
LenResult = WritePrivateProfileString(Section, Key, Value, Filename)
If LenResult = 0 And bRaiseError Then
If Not (System.IO.File.Exists(Filename)) Then
ErrString = "Impossibile aprire il file INI" & Filename
Else
ErrString = "Accesso al file non consentito"
End If
Throw New Exception(ErrString)
End If
Return If(LenResult = 0, False, True)
End
End Function
'//Funzione per l'installazione dei Software
Ed oltretutto sono obbligato a creare il file "Settings.ini" nell'unità D:\ perchè in C:\ oppure nella cartella dove risiede il programma il file non me lo crea proprio. Credo che il problema sia proprio la funzione dell'INI ma non vedo dove sia l'errore.
Daniels118
02-08-2014, 16:39
Inserisci un messagebox in IniRead subito dopo GetPrivateProfileString, e fai stampare RetVal, probabilmente legge qualcosa che non è di lunghezza zero ma che non rappresenta nemmeno un numero.
Ho provato a mettere una MessageBox subito dopo a
LenResult = GetPrivateProfileString(Section, Key, lpDefault, RetVal, RetVal.Length, Filename)
Non mi restituisce nulla.
La funzione comunque l'ho presa già fatta da un altro forum, non l'ho nemmeno modificata quindi a livello di codice non ho la minima idea di come funzioni e non saprei proprio dove andare a mettere le mani per cercare di risolvere il problema...sempre che sia causato dalla funzione e non da altro.
Daniels118
04-08-2014, 08:41
Per "nulla" intendi una stringa vuota?
Chiama IniRead specificando true sul parametro bRaiseError, così vedrai qual è l'eccezione.
Se non ne vieni a capo, metti un'altra messagebox nello stesso punto e stampa queste variabili:
Section, Key, lpDefault, RetVal, Filename
e controlla che siano valorizzate correttamente.
Ho impostato bRaiseError come True e ho inserito dopo GetPrivateProfileString delle MessageBox e a quanto pare vengono valorizzati correttamente Section, Key e Filename, mentre lpDefault e soprattutto RetVal sono vuoti, la MessageBox è completamente bianca.
Ho dato un'occhiata un pochino al codice, subito dopo l'inizio della funzione c'è questa riga:
Dim RetVal As String = New String(" ", 255)
Mi chiedo, visto che non ricordo, le variabili "String" possono contenere anche valori oltre che stringhe?
Daniels118
06-08-2014, 12:56
Le stringhe possono contenere solo stringhe, che poi esse rappresentino valori numerici è un altro discorso, comunque quell'istruzione inizializza la stringa riempendola di spazi, è un'operazione necessaria perché - semplificando -GetPrivateProfileString non è capace di ridimensionare la stringa, per cui devi passargliela già dimensionata con spazio a sufficienza, poi ti verrà sostituito il numero di byte effettivamente utilizzati e la potrai troncare (RetVal.Substring(0, LenResult)).
lpDefault è vuoto perché sei tu a passargli una stringa vuota, o non gli passi nulla e prende il valore predefinito, che è una stringa vuota appunto.
Da MSDN:
lpDefault [in]
A default string. If the lpKeyName key cannot be found in the initialization file, GetPrivateProfileString copies the default string to the lpReturnedString buffer. If this parameter is NULL, the default is an empty string, "".
Significa che nel tuo ini non c'è la chiave che stai cercando, o si trova in una sezione diversa.
Ho ricontrollato eppure la chiave nell'INI c'è, è questa :
[64BitAppsCounter]
AppsNumber=18
Mi sono accorto di una cosa però, ho usato IniRead anche per leggere il percorso degli eseguibili da avviare in fase d'installazione e ho notato che nemmeno in quel caso IniRead funziona, il codice è questo:
Do While CountAppsToInstall32 > 0
SoftwarePath = IniRead("D:\Settings.ini", "32BitUserPath", "ExePath" & CountAppsToInstall32)
SoftwareName = IniRead("D:\Settings.ini", "ApplicationsName32", "AppName" & CountAppsToInstall32)
lbl_app_name.Text = SoftwareName
Shell(SoftwarePath, vbNormalFocus)
pbr_install.Value += pbr_i
CountAppsToInstall32 -= 1
Loop
Nella chiamata della funzione ho provato ad eliminare il "& CountAppsToInstall32" e "ExePath" l'ho fatto diventare "ExePath1" per indicargli di avviare il primo item della lista che c'è nell'INI. Ha funzionato, quindi forse potrebbe essere quell'aggiunta dopo "&" a bloccare il tutto. Il problema è che non posso eliminarlo, perchè lo uso per contare il numero di item da avviare. Come posso fare?
Daniels118
06-08-2014, 15:15
Tu hai impostato AppsNumber a 18, il loop parte dall'ultima e decrementa fino a 1, nell'ini ci sono ExePath18 e AppName18?
Niente quel problema l'ho risolto, probabilemente sbagliavo io qualcosa. E' il problema iniziale con "CountAppsToInstall" che persiste. A meno che non gli assegno un valore preciso, l'installazione non parte. Il problema è che dipende dal numero di programmi scelti dall'utente e non posso sapere a priori quanti sono. Inoltre ho notato che con la funzione Shell() non riesco ad avviare i bat.
vBulletin® v3.6.4, Copyright ©2000-2026, Jelsoft Enterprises Ltd.