Notify Icon appears to be disposed after running batch file - vb.net

I have a WinForms application in VB.net with a NotifyIcon with ContextMenu attached. For some reason when I click the menu item to set the main form as Me.TopMost = false, then copy the batch file to the PC and run it, the NotifyIcon seems to get disposed (no longer appears in system tray). This only appears to happen intermittently.... I have no idea what could be causing this.
The reason that I am thinking that the NotifyIcon is disposed is because if I add a readerNotify.Visible = True after the operation the icon still does not re-appear. See below for the code
Private Sub ResetWindowsUpdateComponentsToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ResetWindowsUpdateComponentsToolStripMenuItem.Click
'Adds Windows Update Components reset batch file to ProgramData and runs the file
Dim fileContent As String = My.Resources.WindowsUpdate_Components_Reset
Dim filename As String = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\WUCR.bat"
Me.TopMost = False
clearWUCache()
My.Computer.FileSystem.WriteAllText(filename, fileContent, False, System.Text.Encoding.ASCII)
'**WHERE NOTIFYICON SEEMS TO DIE**
Dim objProcess As Process = New Process
objProcess.StartInfo.FileName = filename
objProcess.Start()
objProcess.WaitForExit()
prgBarTemps.Value = 100
lblStatus.Text = "Windows Update components reset successfully!"
Me.TopMost = True
End Sub
Private Sub clearWUCache()
Dim wuCacheFolder As String = Environment.GetFolderPath(Environment.SpecialFolder.Windows) & "\SoftwareDistribution\Download"
Try
WebFixProcesses.deleteFolders(wuCacheFolder)
WebFixProcesses.deleteFiles(wuCacheFolder)
Catch ex As Exception
MsgBox(ex.Message)
Dim wuCacheDirect As String = "C:\Windows\SoftwareDistribution\Download"
WebFixProcesses.deleteFolders(wuCacheDirect)
WebFixProcesses.deleteFiles(wuCacheDirect)
lblStatus.Text = "Press a button to fix problems"
End Try
End Sub
I must be doing something wrong here in the way I am setting up the batch file in conjunction with setting Me.TopMost = False or something like that. Any help on this is greatly appreciated!
EDIT:
Here is some additional info from my Form.Designer InitializeComponent() method on the NotifyIcon.
Private Sub InitializeComponent()
'Have omitted other controls
Me.readerNotify = New System.Windows.Forms.NotifyIcon(Me.components)
'readerNotify
'
Me.readerNotify.ContextMenuStrip = Me.ContextMenu
Me.readerNotify.Icon = CType(resources.GetObject("readerNotify.Icon"), System.Drawing.Icon)
Me.readerNotify.Text = "WebFix"
Me.readerNotify.Visible = True
End Sub
Public WithEvents readerNotify As System.Windows.Forms.NotifyIcon

Related

Folder Browser Replacement

I want to be able to add multiple download links and for them to go into a single folder which is selected by the user in a Folder Browser Dialog
The code you see below works great except only for a single file. I have tried changing all 'savefiledialog1' to 'folderbrowserdialog1' instead. However this leads to me clicking download and nothing happening even if only a single link is entered.
Private Sub BtnBrowse_Click(sender As Object, e As EventArgs) Handles btnBrowse.Click
If (SaveFileDialog1.ShowDialog() = DialogResult.OK) Then
txtSave1.Text = SaveFileDialog1.FileName
btnDownload.Enabled = True
End If
End Sub
' ------------ DOWNLOADING SECTION ------------
Private WithEvents HTTPCLIENT As WebClient
Private Sub BtnDownload_Click(sender As Object, e As EventArgs) Handles
btnDownload.Click
btnDownload.Enabled = False
txtSave1.Enabled = False
btnBrowse.Enabled = False
btnDownload.Enabled = False
HTTPCLIENT = New WebClient
Dim Download As String
Download = Links(i)
Dim User = Environment.UserName
Dim Save As String = txtSave1.Text
Try
HTTPCLIENT.DownloadFileAsync(New Uri(Download), Save)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
I expected the folder browser dialog to just be a general save path where the file being downloaded is placed into that folder, however I am given an error.
The code above works but only for a single file.
I have code that can retrieve the download name and extension which I plan to add to the path once i figure this part out.
You can use the FolderBrowserDialog. Once you get the path, you combine it with each filename you will download. Use System.IO.Path.Combine()
Private Sub BtnBrowse_Click(sender As Object, e As EventArgs) Handles btnBrowse.Click
Using fbd As New FolderBrowserDialog()
If fbd.ShowDialog() = DialogResult.OK Then
txtSave1.Text = fbd.SelectedPath
btnDownload.Enabled = True
End If
End Using
End Sub
Private Sub BtnDownload_Click(sender As Object, e As EventArgs) Handles btnDownload.Click
Try
btnDownload.Enabled = False
txtSave1.Enabled = False
btnBrowse.Enabled = False
btnDownload.Enabled = False
Dim exceptionMessages As New List(Of String)
Using client = New WebClient()
' configure client here as needed i.e. add Credentials
For Each link In Links
Try
client.DownloadFileAsync(New Uri(link), Path.Combine(txtSave1.Text, link))
Catch ex As Exception
exceptionMessages.Add(ex.Message)
End Try
Next
End Using
If exceptionMessages.Any() Then MessageBox.Show($"Exception{If(exceptionMessages.Count > 1, "s", "")}: {String.Join(Environment.NewLine, exceptionMessages)}")
Finally
txtSave1.Enabled = True
btnBrowse.Enabled = True
btnDownload.Enabled = True
End Try
End Sub
Note that I will not post an answer with IDisposable objects without Using (in most cases) so FolderBrowserDialog and WebClient are both in Usings. You may need to add additional configuration to the WebClient before downloading.
Also, you probably don't want a separate message for each Exception, if any. So the messages can be cached and shown all at once.
I inserted a Finally for you to set control states back to default when done. This is up to you.
Lastly, the work is being done on a UI thread as evidenced by it being one inside the button click handler. You should move it off the UI even though you aren't blocking. This is outside the scope of the question.

Put long running method into task, showing new form meantime and closing it once the task completes

My application has some long running method which taking some time to be finalised therefore i decided to push it into the separate task then meantime show some new form as ShowDialog which inside is placed hourglass animation and then this form should be closed when task finished the job. On that moment situation is that my new waiting form is not going to Close at all its just show up and stay. I read somewhere that its because ShowDialog in that case will not return nothing that's why Close will be never reached until user click Close manually on form, but how its possible as if I put form.Close it should be the same as user would click on that form. Please of explanation and support here what should be changed to achieve the point. Below my code.
Main form:
WinScp = New WinScpOperation("ftp", "myserver", "login", "password", 21, 0)
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
Return WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
End Function)
Dim pic As New Waiting
pic.ShowDialog() 'show waiting form
Task.WaitAll(tsk) 'waiting on task to be finalized
pic.Close() 'close waiting form
...
Waiting form (nothing but the hourglass gif)
Public Class Waiting
End Class
Further discussion #1:
Option 1: (your working version)
Dim pic As New Waiting
Dim tsk As Task(Of Boolean) =
Task.Factory.StartNew(Of Boolean)(
Function()
' Run lenghty task
Dim Result As Boolean = WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
' Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.Close()))
Return Result
End Function)
' Show the form
pic.ShowDialog()
Task.WaitAll(tsk)
Option2: (not working why??)
Dim pic As New Waiting
Dim tsk As Task(Of Boolean) =
Task.Factory.StartNew(Of Boolean)(
Function()
' Run lenghty task
Dim Result As Boolean = WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
Return Result
End Function)
' Show the form
pic.ShowDialog()
Task.WaitAll(tsk)
' Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.Close()))
Additional question:
Dim pic As New Waiting
Dim tsk As Task(Of Boolean) =
Task.Factory.StartNew(Of Boolean)(
Function()
' Run lenghty task
Dim Result As Boolean = WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
' Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.Close()))
Return Result
End Function)
' Show the form
pic.ShowDialog()
Task.WaitAll(tsk)
If tsk.Result Then 'if return value is true
'Do something when file was downloaded correctly
Else
'Do something when file was NOT downloaded
End If
Additional #2:
This was oryginally:
If WinScp.GetFile(lsbxPicPaths.SelectedItem, temp_dir & "\" & Path.GetFileName(lsbxPicPaths.SelectedItem), False) Then
temp_pic = temp_dir & "\" & Path.GetFileName(lsbxPicPaths.SelectedItem)
End If
I implemented our solution:
Dim pic As New Waiting
Dim tsk As Task(Of Boolean) =
Task.Factory.StartNew(Of Boolean)(
Function()
Run lenghty task
Dim Result As Boolean = WinScp.GetFile(lsbxPicPaths.SelectedItem, temp_dir & "\" & Path.GetFileName(lsbxPicPaths.SelectedItem), False)
' Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.Close()))
Return Result
End Function)
' Show the form
pic.ShowDialog()
Task.WaitAll(tsk)
If tsk.Result Then 'if return value is true
temp_pic = temp_dir & "\" & Path.GetFileName(lsbxPicPaths.SelectedItem)
' Else
End If
'*************************************
Solution to above:
have no idea why when defined two variables instead directly put it into GetFile arguments solve it because previously also there was not variables and was working. Can someone explain that behaviour?
Dim remotefile As String = lsbxPicPaths.SelectedItem
Dim temp_file As String = temp_dir & "\" & Path.GetFileName(lsbxPicPaths.SelectedItem)
'http://stackoverflow.com/questions/33030706/put-long-running-method-into-task-showing-new-form-meantime-and-closing-it-once
Dim pic2 As New Waiting
Dim tsk2 As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
'Run lenghty task
Dim Result As Boolean = WinScp.GetFile(remotefile, temp_file, False)
'Close form once done (on GUI thread)
pic2.Invoke(New Action(Sub() pic2.Close()))
Return Result
End Function)
pic2.ShowDialog()
Task.WaitAll(tsk2)
If tsk2.Result = True Then
MsgBox("GetFile zwrocilo true")
temp_pic = temp_dir & "\" & Path.GetFileName(lsbxPicPaths.SelectedItem)
End If
You already have the answer here:
Showing WinSCP .NET assembly transfer progress on WinForm's progress bar
To put it simple, I'm just extracting the relevant part:
You need to close the form at the end of the task.
As the task runs on a background thread, you need to use .Invoke method to invoke the closing on a GUI thread.
A simple implementation is like:
' Create the form before the task, so that we can reference it in the task
Dim pic As New Waiting
Dim tsk As Task(Of Boolean) =
Task.Factory.StartNew(Of Boolean)(
Function()
' Run lenghty task
Dim Result As Boolean =
WinScp.GetFile(myremotePicturePath, ladujZdjeciaPath, True)
' Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.Close()))
Return Result
End Function)
' Show the form
pic.ShowDialog()
Internally the WinForms Form works like:
Class Form
Private closed as Boolean
Function ShowDialog
While Not closed
If ' X button clicked
Close
End IF
If ' anything in the Invoke queue
' Get action from the Invoke queue
' Run the action
' In our case the "action" is .Close
End If
' Process message queue (mouse clicks, key presses, draw form)
End While
End Sub
Sub Close
closed = True
End Sub
Sub Invoke(action)
' Add action to invoke queue
End Sub
End Class
As of generic handler without tasks, this is how I achieved in one of applications a while ago
Add a new form (frmProgress)
Add hourglass GIF image (picProgress) to the center of this form
Set following form (frmProgress) properties either in GUI or in code on load event:
Private Sub frmProgress_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
FormBorderStyle = FixedSingle
Opacity = 0.5R
ShowInTaskbar = False
TopMost = True
WindowState = Maximized
End Sub
Center image in form on resize event, if required
Private Sub frmProgress_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
picProgress.Left = CInt(Me.Width / 2 - picProgress.Width / 2)
picProgress.Top = CInt(Me.Height / 2 - picProgress.Height / 2)
End Sub
Load the form before initiating your processing (DO NOT use ShowDialog())
frmProgress.Show()'DO NOT use ShowDialog()
Initiate your processing
Hide progress form after task is finished
frmProgress.Hide()

Streaming output from CMD console to VB ListBox

I am trying to pass text from console to the Listbox1 line by line so hidden console will stream its output to my ListBox. But I'm getting Cross-thread operation not valid
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim start_info As New ProcessStartInfo()
start_info.FileName = ("cmd.exe")
start_info.UseShellExecute = False
start_info.CreateNoWindow = False
start_info.RedirectStandardOutput = True
start_info.WindowStyle = ProcessWindowStyle.Hidden
start_info.Arguments = ("ipconfig")
Dim proc As New Process
proc.StartInfo = start_info
proc.Start()
Dim std_out As System.IO.StreamReader
std_out = proc.StandardOutput
Do
ListBox1.Items.Add(std_out.ReadLine)
Loop While proc.HasExited = False
End Sub
The DoWork event of the BackgroundWorker runs in a different thread than the UI. You'll have to make sure the Items are being added in the UI thread. Use for example Invoke to accomplish this.
Change your Do ... Loop While to the following:
Do
Dim line As String = std_out.ReadLine()
ListBox1.Invoke(Sub() ListBox1.Items.Add(line))
Loop While proc.HasExited = False
More information also on MSDN:
How to: Make Thread-Safe Calls to Windows Forms Controls
Control.Invoke Method

Console application doesn't want to read standard input

I am writing an application to manage other console application(game server - jampded.exe)
When it's running in console it writes data and reads commands with no problem.
In my application I redirected standard I/O to StreamWriter and StreamReader
Public out As StreamReader
Public input As StreamWriter
Dim p As New Process()
p.StartInfo.FileName = My.Application.Info.DirectoryPath & "\" &
TextBox6.Text 'PATH TO JAMPDED.EXE
p.StartInfo.Arguments = TextBox1.Text 'EXTRA PARAMETERS
p.StartInfo.CreateNoWindow = True
p.StartInfo.RedirectStandardInput = True
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.UseShellExecute = False
p.Start()
input = p.StandardInput
out = p.StandardOutput
Dim thr As Thread = New Thread(AddressOf updatetextbox)
thr.IsBackground = True
thr.Start()
Sub updatetextbox()
While True
While Not out.EndOfStream
RichTextBox1.AppendText(out.ReadLine())
RichTextBox1.AppendText(vbNewLine)
End While
End While
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) _
Handles Button2.Click
input.WriteLine(TextBox4.Text)
TextBox4.Text = ""
input.Flush()
End Sub
When I am pressing Button2 that should write to STD/I text from my textbox, jampded.exe acts like it wasn't written. Also Output works well at startup, after that new lines are added rarely when there is a lot data in buffer.
Am I doing something wrong, or is it the application's fault?
For the standard input question:
Are you certain that the application you're starting is reading data from standard input (and not trapping keyboard events or something)? To test this, put some text that you're trying to send to the application in a text file (named, for example, commands.txt). Then send it to the application from a command prompt like so:
type commands.txt | jampded.exe
If that application reads those commands, then it is indeed reading from standard input. If it isn't, then redirecting standard input isn't going to help you get data to that application.
For the standard output question:
Instead of launching your own thread to handle the data coming from the other application, I would suggest doing something like this:
AddHandler p.OutputDataReceived, AddressOf OutputData
p.Start()
p.BeginOutputReadLine()
Private Sub AddLineToTextBox(ByVal line As String)
RichTextBox1.AppendText(e.Data)
RichTextBox1.AppendText(vbNewLine)
End Sub
Private Delegate Sub AddLineDelegate(ByVal line As String)
Private Sub OutputData(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
If IsNothing(e.Data) Then Exit Sub
Dim d As AddLineDelegate
d = AddressOf AddLineToTextBox
Invoke(d, e.Data)
End Sub
The Invoke call is required because OutputData may get called on a different thread, and UI updates all have to happen on the UI thread.
I've seen the same issue with data coming in batches when reading from the StandardOutput stream directly. The asynchronous read + event handler combo fixed it.

closing msaccess.exe in vb.net

I am working on creating a vb.net program i have a button that when clicked on will browse for MDB files (code 1) and when selected will execute some lines of code that will populate all of the macros within the access database into a combo box (code 2). The problem i'm having is MSACCESS.EXE process is not closing after code 2 runs. I've tried a couple different things like objAccess.CloseCurrentDatabase() none of which are working.. Any ideas on what i'm doing wrong?
code 1
Private Sub CommandDBPath_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CommandDBPath.Click
Dim dialog As New OpenFileDialog()
dialog.Filter = "Access database (*.mdb)|*.mdb"
If DialogResult.OK = dialog.ShowDialog Then
TextDBPath.Text = dialog.FileName
End If
SelectDatabaseMacro()
End Sub
code 2
Private Sub SelectDatabaseMacro()
Dim objAccess As Object '' Access.Application
Dim i As Long
Dim path As String
path = TextDBPath.Text
objAccess = CreateObject("Access.Application")
objAccess.OpenCurrentDatabase(path)
For i = 0 To objAccess.CurrentProject.AllMacros.Count - 1
TextReportMacro.Items.Add(objAccess.CurrentProject.AllMacros(i).Name)
Next
objAccess.CloseCurrentDatabase()
objAccess = Nothing
End Sub
Try adding an objAccess.Quit statement after you objAccess.CloseCurrentDatabase().
To abruptly kill the process,
For Each p As Process In Process.GetProcesses()
If p.ProcessName = "MSAccess" Then
p.Kill()
End If
Next
Or for a more "graceful" approach, try this,
The process must have a windows interface (window) in order to work.
For Each p As Process In Process.GetProcesses()
If p.ProcessName = "MSAccess" Then
p.CloseMainWindow()
End If
Next