0

I am trying to change the state of a button from another function where I use the AddHandler and AddressOf but it turns out that said function does not allow me to make changes to the UI .

Sub Button1Click(sender As Object, e As EventArgs)
    CMD_CS("C:\Users\Gabr\Desktop","dir element.docx /s /p")
End Sub

Private Sub CMD_CS(ByVal path As String, ByVal comand As String)
        button1.Enabled = False
        Dim p As Process = New Process()
        Dim ps As ProcessStartInfo = New ProcessStartInfo()
        Environment.CurrentDirectory = path
        ps.UseShellExecute = True
        ps.WindowStyle = ProcessWindowStyle.Hidden
        ps.FileName = "cmd"
        ps.Arguments = " /c " + comand 
        p.StartInfo = ps
        p.Start()
        p.EnableRaisingEvents = True
        AddHandler p.Exited, AddressOf PsExit
        
End Sub
    
Public Sub PsExit()
    Me.button1.Enabled = True ' <---- error Button1 no access
    Console.WriteLine("process end")
   ' get p.StandardOutput.ReadLine
        
End Sub

It would also be very useful to know the result that the console is throwing at me but I have no idea.

Here I have two objectives, the first is that I do not know how to change the state of the button from the PsExit() function and the second is that in that same function print the results generated by the cmd

Error

System.InvalidOperationException: Cross-thread operation not valid: Control 'button1' accessed from a thread other than the thread it was created on.

royer
  • 615
  • 1
  • 6
  • 19
  • 1
    `BeginInvoke(new Action(sub() Me.button1.Enabled = True))` – Jimi Jul 09 '21 at 23:38
  • @Jimi Thank you very much, this function allows changing states of the UI, do you know how I can extract the text output from the cmd? As I read they say they use the `StandardOutput.ReadLine`, but the `PsExit` function does not allow it. – royer Jul 10 '21 at 00:22
  • See the notes and sample code here: [How do I get output from a command to appear in a control on a Form in real-time?](https://stackoverflow.com/a/51682585/7444103) – Jimi Jul 10 '21 at 00:37
  • ps.SynchronizingObject = Me – Hans Passant Jul 12 '21 at 00:08

1 Answers1

0

There are better ways to check if a file exists, but perhaps you're just using it for testing.

If one needs to update a property for a Button during cross-threaded operations, one can do either of the following:

Option 1: (Action)

Button1.Invoke(New Action(Sub()
                              Button1.Enabled = True
                          End Sub))

Option 2: (MethodInvoker)

Button1.Invoke(New MethodInvoker(Sub()
                                     Button1.Enabled = True
                                 End Sub))

The code below shows how to use System.Diagnostics.Process. I've included two different ways of determining whether or not the command being run by Process completed successfully or not.

  1. Using an event named ProcessCompleted. The value of hasSuccessfullyCompleted lets one know if the the operation successfully completed without errors.
  2. Using a return value from the Function. If the return value is "Success", then the operation successfully completed without errors. If an error occurred, the error message is returned.

Create a WinForms project

VS 2017:

  • Open Visual Studio
  • Click File
  • Select New
  • Select Project
  • Expand Installed
  • Expand Visual Basic
  • Click Windows Desktop
  • Select Windows Forms App (.NET Framework)
  • Specify project name (name: ReadSerialPort)
  • Click OK

VS 2019:

  • Open Visual Studio
  • Click Continue without code
  • Click File
  • Select New
  • Select Project
  • Visual Basic Windows Desktop
  • Click Windows Forms App (.NET Framework)
  • Click Next
  • Specify project name (name: ReadSerialPort)
  • Click Create

Note: From this point forward, the process is the same for both VS 2017 and VS 2019.

Create a class (name: HelperProcess.vb)

  • In VS menu, click Project
  • Select Add Class
  • For name, enter "HelperProcess.vb"
  • Click Add

HelperProcess.vb

Public Class HelperProcess

    Public Event ErrorDataReceived(sender As Object, data As String)
    Public Event OutputDataReceived(sender As Object, data As String)
    Public Event ProcessCompleted(sender As Object, hasSuccessfullyCompleted As Boolean)

    Private ProcessError As String = String.Empty

    Public Function RunCmd(ByVal exePath As String, ByVal Optional arguments As String = Nothing) As String
        Dim errMsg As String = String.Empty

        'set value
        ProcessError = String.Empty

        If String.IsNullOrEmpty(exePath) Then
            errMsg = "exePath not specified"
            Debug.WriteLine(errMsg)

            're-initialize
            ProcessError = "Error: " & errMsg

            Throw New Exception(errMsg)
        End If

        Try
            'create new instance
            Dim psInfo As ProcessStartInfo = New ProcessStartInfo(exePath, arguments)

            'set properties
            psInfo.Arguments = arguments 'arguments
            psInfo.CreateNoWindow = True 'don't create a window
            psInfo.RedirectStandardError = True 'redirect standard Error
            psInfo.RedirectStandardOutput = True 'redirect standard output
            psInfo.RedirectStandardInput = False
            psInfo.UseShellExecute = False 'If True, uses 'ShellExecute'; if false, uses 'CreateProcess'
            psInfo.WindowStyle = ProcessWindowStyle.Hidden
            psInfo.ErrorDialog = False

            'create new instance - setting the desired properties
            Using p As Process = New Process() With {.EnableRaisingEvents = True, .StartInfo = psInfo}

                'subscribe to events (add event handlers)
                AddHandler p.ErrorDataReceived, AddressOf Process_ErrorDataReceived
                AddHandler p.OutputDataReceived, AddressOf Process_OutputDataReceived

                'start process
                p.Start()

                p.BeginErrorReadLine() 'begin async reading for standard error
                p.BeginOutputReadLine() 'begin async reading for standard output

                'waits until the process is finished before continuing
                p.WaitForExit()

                'unsubscribe from events (remove event handlers)
                RemoveHandler p.ErrorDataReceived, AddressOf Process_ErrorDataReceived
                RemoveHandler p.OutputDataReceived, AddressOf Process_OutputDataReceived

            End Using

        Catch ex As System.ComponentModel.Win32Exception
            errMsg = "Error (Win32Exception): " & ex.Message
            Debug.WriteLine(errMsg)

            'set value
            ProcessError = errMsg
            Throw ex
        Catch ex As Exception
            errMsg = "Error: " & ex.Message
            Debug.WriteLine(errMsg)

            'set value
            ProcessError = errMsg
            Throw ex
        End Try

        If Not String.IsNullOrEmpty(ProcessError) Then
            'raise event
            RaiseEvent ProcessCompleted(Me, False)

            Return "Error: " & ProcessError
        Else
            'raise event
            RaiseEvent ProcessCompleted(Me, True)
        End If

        Return "Success"
    End Function


    Private Sub Process_ErrorDataReceived(sender As Object, e As DataReceivedEventArgs)
        'ToDo: add desired code

        If Not String.IsNullOrEmpty(e.Data) Then
            Debug.WriteLine("Process_ErrorDataReceived: " & e.Data)

            If Not String.IsNullOrEmpty(ProcessError) Then
                'add space
                ProcessError += " "
            End If

            'append
            ProcessError += e.Data

            'raise event
            RaiseEvent ErrorDataReceived(Me, e.Data)

        End If
    End Sub

    Private Sub Process_OutputDataReceived(sender As Object, e As DataReceivedEventArgs)
        'ToDo: add desired code

        If Not String.IsNullOrEmpty(e.Data) Then
            Debug.WriteLine("Process_OutputDataReceived: " & e.Data)

            'raise event
            RaiseEvent OutputDataReceived(Me, e.Data)
        End If
    End Sub
End Class

Open Properties Window

  • In VS menu, select View
  • Select Properties Window

Open Solution Explorer

  • In VS menu, select View
  • Select Solution Explorer
  • In Solution Explorer, double-click Form1.vb to open the designer.

Add Buttons to Form1

enter image description here

Add "Run" button to Form1

  • In VS menu, select View
  • Select Toolbox
  • Select Button
  • Click on Form1 to add the button to the form
  • In Properties Window, for "button1", set (name): btnRun; set Text: Connect
  • In Properties Window, click enter image description here (Events). Double-click Click to add event handler to Form1.vb

Add "Button1" button to Form1

  • In VS menu, select View
  • Select Toolbox
  • Select Button
  • Click on Form1 to add the button to the form

Add "Load" event handler to Form1

  • In Properties Window, for "Form1"", click enter image description here (Events). Double-click Load to add event handler to Form1.vb

Modify Form1.vb code

  • In Solution Explorer, right-click Form1.vb
  • Select View Code

Form1.vb

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'set value
        Button1.Enabled = False
    End Sub

    Private Sub btnRun_Click(sender As Object, e As EventArgs) Handles btnRun.Click

        'set value
        Button1.Enabled = False

        Dim helper As New HelperProcess

        'subscribe to events (add event handlers)
        AddHandler helper.ErrorDataReceived, AddressOf Helper_ErrorDataReceived
        AddHandler helper.OutputDataReceived, AddressOf Helper_OutputDataReceived
        AddHandler helper.ProcessCompleted, AddressOf Helper_ProcessCompleted

        'set value
        Dim folderName As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
        Dim filename As String = System.IO.Path.Combine(folderName, "element.docx")

        Dim arguments As String = String.Format("/c dir ""{0}"" | find /v ""Volume"" | find /v ""Directory of"" | find /v ""bytes"" ", filename)

        'execute
        Dim result As String = helper.RunCmd("cmd", arguments)

        'unsubscribe from events (remove event handlers)
        RemoveHandler helper.ErrorDataReceived, AddressOf Helper_ErrorDataReceived
        RemoveHandler helper.OutputDataReceived, AddressOf Helper_OutputDataReceived
        RemoveHandler helper.ProcessCompleted, AddressOf Helper_ProcessCompleted

        'set value
        helper = Nothing

        Debug.WriteLine("result: " & result)

        If result = "Success" Then
            Button1.Enabled = True
        Else
            Button1.Enabled = False
        End If
    End Sub

    Private Sub Helper_ErrorDataReceived(sender As Object, data As String)
        'ToDo: add desired code

        If Not String.IsNullOrEmpty(data) Then
            Debug.WriteLine("Helper_ErrorDataReceived: " & data)
        End If
    End Sub

    Private Sub Helper_OutputDataReceived(sender As Object, data As String)
        'ToDo: add desired code

        If Not String.IsNullOrEmpty(data) Then
            Debug.WriteLine("Helper_OutputDataReceived: " & data)
        End If
    End Sub

    Private Sub Helper_ProcessCompleted(sender As Object, hasSuccessfullyCompleted As Boolean)

        'ToDo: add desired code and/or uncomment desired code below

        Debug.WriteLine("hasSuccessFullyCompleted: " & hasSuccessfullyCompleted.ToString())

        If hasSuccessfullyCompleted Then
            'Button1.Invoke(New MethodInvoker(Sub()
            'Button1.Enabled = True
            'End Sub))

            'Button1.Invoke(New Action(Sub()
            'Button1.Enabled = True
            'End Sub))
        Else
            'Button1.Invoke(New MethodInvoker(Sub()
            'Button1.Enabled = False
            'End Sub))

            'Button1.Invoke(New Action(Sub()
            'Button1.Enabled = False
            'End Sub))
        End If
    End Sub
End Class
Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24