Using VS2012 (.NET) I am developing a ribbon bar style add-in for PowerPoint (2010) where I want the incoming serial port values to be passed through an algorithm and, depending on the output, perform an action such as next slide or previous slide. I am using the SerialPort.DataReceived event handler.
My problem is simpler explained using an iterating variable j which increases in value by 1 each time the SerialPort.DataReceived event handler is called. I will detail the problem more after the code but, in short, once my code is called j increases in value as expected prior to a slideshow presentation being started and for the first 30 seconds or so of the slideshow presentation. After about 30 seconds of being in presentation mode, j stops iterating. I am watching the value of j using Breakpoint When Hit and the debugging Output window.
In one class (class2), associated with a form launched by clicking a ribbon button, a button click in said form sets the SerialPort.DataReceived handler and opens the serial port (abbreviated relevant code):
Public Class class2 'called by clicking ribbon button
Public sp As New SerialPort
Public Baud As Integer = 9600
Public Port As String
Public c1 As class1
Public Sub New(oParent As class1) 'get reference to parent class
c1= oParent
InitializeComponent()
End Sub
....
Public Sub btn1_Click(sender As Object, e As EventArgs) _
Handles btn1.Click
Port = lbCom.SelectedItem 'Port selected from form listbox
Try
With sp
.BaudRate = Baud
.PortName = Port
.ReadTimeout = 5000 'Give serial port 5sto open
.RtsEnable = True
End With
c1.mySP = sp
AddHandler sp.DataReceived, AddressOf c1.spDataReceivedHandler
sp.Open()
Me.Hide()
Catch ex As Exception
MsgBox("Error", vbOKOnly, "Connection Error")
End Try
End Sub
End Class
In the main class (class1) I have the DataReceived Handler and iterating variable j (again, abbreviated relevant code):
Public Class class1
...
Public Sub spDataReceivedHandler(ByVal sender As Object, _
e As SerialDataReceivedEventArgs)
j = j + 1 ' value monitored using Breakpoint When Hit
End Sub
End Class
More details about the problem. Once I start my code, if I don't start a slideshow presentation j will iterate without any problem (5+minutes). If I start a slideshow presentation but immediately hit alt+tab to give focus to another application, say VS, j will iterate without any problem (with PowerPoint still being in presentation mode). If I start a slideshow presentation and let it have the focus j will stop iterating after ~30 seconds, regardless of how long I let j iterate prior to starting the slideshow presentation.
I have also tried using a background worker instead of serial port event handler where I use a do loop to get data from the serial port but I run into the same issue; works as expected until slideshow presentation has been running for ~30 seconds.
I have already written two separate Windows Form style applications using the same serial port parameters and same device which work fine. The issue, as far as I can tell, is with the slideshow presentation having the focus (and I'm guessing resource availability?).
I'm going to try my luck using the Task Parallel library, but if anyone has any insight as to why the aforementioned maddening problem exists do please enlighten me (Note: relatively novice programmer, so if there is a glaring error in my approach please let me know as well). Thanks.
Okay, so no idea why the program was behaving that way but a work around was indeed to put the "open and monitor" serial port code into a sub in the ThisAddIn class and call it as a new task when appropriate (clicking a button).
Clicking a button on my custom ribbon:
Public Class myribbon
Private Sub btn_Click(sender As Object, e As RibbonControlEventArgs) _
Handles btn.Click
' Define new task spTask (sub located in ThisAddIn)
Dim spTask = New Task(Sub() Globals.ThisAddIn.readToSP())
End Sub
End Class
Public Class ThisAddIn
...' other stuff
Dim mySP As New SerialPort
Public Sub readToSP()
...' serial port params
Try
mySP.Open()
While (mySP.IsOpen)
Dim analogV As String = mySP.ReadTo(delimStr)
...' do something with analogV
End While
Catch ex as Exception 'appropriate catches...
End Try
End Sub
End Class
Related
I have a class that gets the internet time and displays it in a text box. This is that class name InternetTimeGet
I have added a "progress meter" in that class that will show how far through the list of IPs the program is. Right now the Debug Console is displaying these IPs and updates fine as is shown here: IPs and Progress
Everything is already multithreaded and nothing is hanging my UI
However, I'm having trouble getting the program to display the progress on a label named ProgLbl in my main form named MainForm. Here is the code of my InternetTimeGet class:
Public Class InternetTimeGet
'Irrelevant variables omitted...
'Used to update progress
Public Shared IPCount As Integer
Public Shared ProgDivision As Single
Public Shared ProgBar As Single
Public Shared FullBar As Integer = 100
IPCount = My.Settings.ServerIPList.Count
ProgDivision = FullBar / IPCount
For Each IP As String In ServArray
Try
'Shows each IP and the Percentage completion in Debug console
Debug.WriteLine(IP, "IP Addresses")
ProgBar = ProgBar + ProgDivision
Debug.WriteLine(Math.Round(ProgBar, 1) & "%", "Progress")
MainForm.ProgLbl.Text = "Progress: " & Math.Round(ProgBar, 1) & "%"
If My.Computer.Network.Ping(IP) Then
LastHost = IP
Result = GetNISTTime(IP)
End If
Catch ex As Exception
'Return "Sync Error 0x01"
MsgBox("There was a sync error while retrieving the updated internet time. Please try again.", MsgBoxStyle.Critical, "Sync Error 0x01")
Debug.WriteLine("There was a sync error while retrieving the updated internet time. Please try again.", "Sync Error 0x01")
End Try
Next
The Debug console shows each IP and the percentage of completion fine and does not not show intermediate updates or hang the UI, but the label ProgLbl in my main form MainForm does not show these updates at all and just remains saying "Progress: ". Once again my main form MainForm's UI DOES NOT hang.
The problem is updating the label, ProgLbl, in my main form, MainForm, from the InternetTimeGet class shown above. That's what I'd appreciate help with.
I have been looking for an answer and tried several things but finally gave up and decided to ask those who know better.
Thank you for your help
Well, it is not clear where your code is implemented but if you want to make some operation (such as getting time and going through a list of IPs), as a general rule, you'll want to avoid to do it in the main thread to avoid freezing the UI.
The background worker is perfect for that kind of purpose, as it will allow you to run code Async while reporting progress so you can update your main form.
Here is a simple example using a background worker
that you will have to rework to fit your need.
Private WithEvents _GetTimeWorker As BackgroundWorker
Somewhere in your form, you need to initialize your worker and set the WorkerReportProgress to true. You can then start the worker when you need.
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
_GetTimeWorker = New BackgroundWorker With {.WorkerReportsProgress = True}
_GetTimeWorker.RunWorkerAsync()
End Sub
DoWork is actually the async method that you put your logic in so it does not block the UI. When you want to update the progress bar, you call ReportProgress on your background worker.
Private Sub _GetTimeWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles _GetTimeWorker.DoWork
Dim watch As New Stopwatch
watch.Start()
'Just simulating some work.
While watch.ElapsedMilliseconds < 10000
System.Threading.Thread.Sleep(100)
_GetTimeWorker.ReportProgress(CInt(watch.ElapsedMilliseconds / 10000 * 100))
End While
End Sub
The progress bar value is updated from the progress changed function.
Private Sub _GetTimeWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles _GetTimeWorker.ProgressChanged
'There we change the progress bar value
prog1.Value = e.ProgressPercentage
End Sub
I have a simple WinForm application. The main entry point of the application is mainForm. I am using a Timer on the form and the timer interval is being set to 2000ms. The Tick event of the Timer is as below,
Public myValue as Integer = 100
Private Sub myTimer_Tick(sender As Object, e As EventArgs) Handles myTimer.Tick
If myValue = 0 Then
myTimer.Enabled = False
Else
myValue = myValue -1
End If
End Sub
The timer is being called at the start of the application when mainForm is loaded. Now myValue is a global variable and here for the purpose of simplicity I have used this otherwise it is replaced by some process count mechanism which is not required to be explained here.
I am able to use this approach as long as I am using Windows.Forms.Timer placed on some specific Form. I have two more scenarios in which this approach fails.
1 - I have to use the same functionality on some other form and for this currently I am using a separate Timer on another Form and it has its own Tick event.
2 - I have to use the same functionality from another module/class and I am unable to achieve this because for this to work I require a Form.
Now for a start I have looked into Threading.Timer. The problem I am facing is that I don't know how to wait for Threading.Timer to finish as the control goes to next line after Threading.Timer is called. I am not sure whether this can be done with the help of WaitHandle or not. Also I have read that Threading.Timer creates a separate Thread for each of its Tick. This seems like an overkill in my simple scenario.
I just want to use the Timer functionality without the need of Form. Also I could create the similar functionality using a Do Loop with Thread.Sleep inside it but unless I am sure that my Timer functionality is not going to work in other situations I am going to stick to my Timer approach.
I see ... If thats the case, you should really create a second thread that runs a loop. That thread has some exiting parameters that indicates that operation is completed and the Thread itself is set to Isbackground = false.
However, you could also do this ...
Imports System.Timers
Public Class Main
Private Shared WithEvents m_oTimer As Timers.Timer = Nothing
Private Shared m_oWaitHandle_TimerHasCompleted As System.Threading.AutoResetEvent = Nothing
Public Shared Sub Main()
Try
'Application Entry point ...
'Create the global timer
m_oTimer = New Timers.Timer
With m_oTimer
.AutoReset = True
.Interval = 2000
.Start()
End With
'Create the WaitHandle
m_oWaitHandle_TimerHasCompleted = New System.Threading.AutoResetEvent(False)
'Show your form
Dim oFrm As New Form1
Application.Run(oFrm)
'Wait for the timer to also indicate that it has finished before exiting
m_oWaitHandle_TimerHasCompleted.WaitOne()
Catch ex As Exception
'Error Handling here ...
End Try
End Sub
Private Shared Sub m_oTimer_Elapsed(sender As Object, e As ElapsedEventArgs) Handles m_oTimer.Elapsed
'Timer will fire here ...
Try
If 1 = 2 Then
m_oWaitHandle_TimerHasCompleted.Set()
End If
Catch ex As Exception
'Error Handling ...
End Try
End Sub
End Class
Please note that 'm_oWaitHandle_TimerHasCompleted.Set()' will never run, you'll have to add a condition ... however, once run, the WaitOne will complete and the application will exit as required.
Hows zat?
Sounds to me like you want to create a single instance of a timer, that does not need to be instantiated via a form?
If so ... Create a new class called 'Main' and copy the following into it.
Imports System.Timers
Public Class Main
Private Shared WithEvents m_oTimer As Timers.Timer = Nothing
Public Shared Sub Main()
Try
'Application Entry point ...
'Create the global timer
m_oTimer = New Timers.Timer
With m_oTimer
.AutoReset = True
.Interval = 2000
.Start()
End With
'Show your form
Dim oFrm As New Form1
Application.Run(oFrm)
Catch ex As Exception
'Error Handling here ...
End Try
End Sub
Private Shared Sub m_oTimer_Elapsed(sender As Object, e As ElapsedEventArgs) Handles m_oTimer.Elapsed
'Timer will fire here ...
Try
Catch ex As Exception
'Error Handling ...
End Try
End Sub
End Class
Once done, right click on your project and select 'Properties'. In the Application tab you'll see a checkbox called 'Enable Application framework'. Uncheck this box. Now, in the dropdown called 'Startup Object' you should now see 'Sub Main' .... Select that.
When the application runs, Sub Main will now run instead of your form.
This will create the Timer that will fire outside of your form. Please note, as you're not syncing it, I believe it'll run inside a thread so be a little careful there :)
I'm working on a simple VB.NET program (just using winforms) and am really terrible at UI management. I'd like to have a single button that starts a process, and then have that same button stop the process.
I'm thinking about having the main form initiate a counter, and the Click event iterate the counter. Then it does a simple check, and if the counter is even it will do thing A and odd does thing B.
Is there a better way, aside from using two buttons or stop/start radio buttons?
I've done that exact thing one of two ways. You can use a static variable or toggle the text of the button.
Since your button has two functions, Good design requires you to indicate that to the user. The following code assumes the Button's text is set in Design Mode to "Start", and the code to start and stop your process is in the Subs StartProcess and EndProcess.
Public Sub Button1_Click(ByVal Sender as Object, ByVal e as System.EventArgs)
If Button1.Text ="Start" Then
StartProcess()
Button1.Text="End"
Else
EndProcess()
Button1.Text="Start"
End IF
End Sub
EDIT
The above solution is fine for a single-language application developed by a small number of developers.
To support multiple languages, developers typically assign all text literals from supporting files or databases. In larger development shops, with multiple programmers, using a display feature of the control for flow-control may cause confusion and regression errors. In those cass, the above technique wouldn't work.
Instead, you could use the Tag property of the button, which holds an object. I would typically use a Boolean, but I used a string just to make more clear as to what's going on.
Public Sub New()
'Initialize the Tag
Button1.Tag="Start"
End Sub
Public Sub Button1_Click(ByVal Sender as Object, ByVal e as System.EventArgs)
If Button1.Tag.ToString="Start" Then
StartProcess()
Button1.Tag="End"
Else
EndProcess()
Button1.Tag="Start"
End IF
End Sub
This is example in pseudo-code. I don't guarantee that names of methods and event are exactly match real names. But this should provide you a design that you could use for responsive form.
Lets say, your process is running on separate tread, using BackgroundWorker.
You setup your worker and start process
Class MyForm
private _isRunning as boolean
private _bgWorker as BackgroundWorker
sub buton_click()
If Not _isRunning Then
_isRunning = true;
StartProcess()
Else
StopProcess()
End if
end sub
sub StartProcess()
' Setup your worker
' Wire DoWork
' Wire on Progress
' wire on End
_bgWorker.RunWorkerAsync()
End sub
sub StopProcess()
if _isRunning andAlso _bgWorker.IsBusy then
' Send signal to worker to end processed
_bgWorker.CancelAsync()
end if
end sub
sub DoWork()
worker.ReportProgress(data) ' report progress with status like <Started>
' periodically check if process canceled
if worker.canceled then
worker.ReportProgress(data) ' report progress with status like <Cancelling>
return
end if
' Do your process and report more progress here with status like <In Progress>
' and again periodically check if process canceled
if worker.canceled then
worker.ReportProgress(data) ' report progress with status like <Cancelling>
return
end if
worker.ReportProgress(data) ' report progress with status like <Ending>
end sub
sub ReportProgress(data)
if data = <some process state, like "Started"> then
btnProcess.Text = "End Process"
end if
End sub
sub ReportEndOfProcess
btnProcess.Text = "Start Process"
_isRunning = false
end sub
End Class
Here you can pinpoint the names of methods and events
You have to substitute identifiers with real names and create you state or data object, which will carry information from background thread to UI thread, and also an Enum Status that can be part of your custom state object. This should work once translated into real code
Just want to show another approach for this task
Use .Tag property for your own purpose
If .Tag Is Nothing (by default in designer) then start process
If not Nothing -> stop process
Public Sub Button1_Click(ByVal Sender as Object, ByVal e as System.EventArgs)
If Me.Button1.Tag Is Nothing Then
StartProcess()
Me.Button1.Tag = New Object()
Me.Button1.Text = "End"
Else
EndProcess()
Me.Button1.Tag = Nothing
Me.Button1.Text = "Start"
End
End Sub
I have written an application with the following sub main:
Public Sub Main()
Dim Value As String() = Environment.GetCommandLineArgs
Dim F As Form
Select Case Value.Last.ToLower
Case "-character"
F = New frmCharacterSheet
Case "-viewer"
F = New frmClient
Case Else
F = New frmCombat
End Select
Application.Run(F)
End Sub
This is because I want to be able to install my app with three different startup modes based on the command line. I did have a form that did this, but this has made error trapping very hard because the main form just reports the error.
This console seems to work well but I don't want the user to see the black console screen at startup.
I have searched for the answer but most solutions are 'switch back to a windows forms application'. I don't want to do this though for the above reason. (I cannot use application.run(f) in a winforms start situation because I get a threading error.
I need to know either how to hide the console window, or alternatively how to code a main menu that will launch one of the other three forms (but making them the startup form).
Any help would be appreciated....
Try:
Private Declare Auto Function ShowWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As Boolean
Private Declare Auto Function GetConsoleWindow Lib "kernel32.dll" () As IntPtr
Private Const SW_HIDE As Integer = 0
Sub Main()
Dim hWndConsole As IntPtr
hWndConsole = GetConsoleWindow()
ShowWindow(hWndConsole, SW_HIDE)
'continue your code
End Sub
It has a side effect that the window will be shown and then immediately hidden
valter
"or alternatively how to code a main menu that will launch one of the other three forms (but making them the startup form)."
Start with a standard WinForms Project and use the Application.Startup() event. From there you can check your startup parameters and then dynamically change the Startup form by assigning your desired instance to "My.Application.MainForm". This will cause that form to load as if it was the one originally assigned to the "Startup Form" entry.
Click on Project --> Properties --> Application Tab --> "View Application Events" Button (bottom right; scroll down).
Change the Left dropdown from "(General)" to "(MyApplication Events)".
Change the Right dropdown from "Declarations" to "Startup".
Simplified code:
Namespace My
' The following events are available for MyApplication:
'
' Startup: Raised when the application starts, before the startup form is created.
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
' UnhandledException: Raised if the application encounters an unhandled exception.
' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.
Partial Friend Class MyApplication
Private Sub MyApplication_Startup(sender As Object, e As ApplicationServices.StartupEventArgs) Handles Me.Startup
If True Then
My.Application.MainForm = New Form1 ' <-- pass your desired instance to MainForm
End If
End Sub
End Class
End Namespace
Just go to Project Properties> Application> Application Type> and select Windows Forms Application
At this point your ConsoleApplication turns totally invisible, with no User-Interface.
I just want to add another solution although Idle_Mind has already provided an excellent one. This demonstrates that you can use Application.Run(Form) inside a WinForms app.
Public Class Form1
Private Shared applicationThread As New Threading.Thread(AddressOf Main)
Private Shared Sub Main()
Dim myForm As Form
Dim config = 2 ' if 3, will run Form3
Select Case config
Case 2
myForm = New Form2
Case 3
myForm = New Form3
Case Else
MessageBox.Show("Bad config!")
Exit Sub
End Select
Application.Run(myForm)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
applicationThread.Start()
' immediately dispose Form1 so it's not even shown
Dispose()
End Sub
End Class
I have a main form wich is expected to perfom some long operations. In parallel, I'm trying to display the percentage of the executed actions.
So I created a second form like this:
Private Delegate Sub DoubleFunction(ByVal D as Double)
Private Delegate Sub EmptyFunction()
Public Class LoaderClass
Inherits Form
'Some properties here
Public Sub DisplayPercentage(Value as Double)
If Me.InvokeRequired then
dim TempFunction as New DoubleFunction(addressof DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End if
End sub
Public Sub CloseForm()
If Me.InvokeRequired Then
Dim CloseFunction As New EmptyFunction(AddressOf CloseForm)
Me.Invoke(CloseFunction)
Else
Me.Close()
End If
FormClosed = True
End Sub
End class
My main sub, the one which is expected to perform the long operations is in another form as follows:
Private Sub InitApplication
Dim Loader as new LoaderClass
Dim LoaderThread as new thread(Sub()
Loader.ShowDialog()
End sub)
LoaderThread.start()
Loader.DisplayPercentage(1/10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2/10)
ConnectToDataBase()
Loader.DisplayPercentage(3/10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4/10)
LoadClients()
...
Loader.CloseForm()
End sub
The code works almost 95% of the time but sometimes I'm getting a thread exception somewhere in the sub DisplayPercentage. I change absolutely nothing, I just hit the start button again and the debugger continues the execution without any problem.
The exception I get is: Cross-thread operation not valid: Control 'LoaderClass' accessed from a thread other than the thread it was created on event though I'm using : if InvokeRequired
Does anyone know what is wrong with that code please ?
Thank you.
This is a standard threading bug, called a "race condition". The fundamental problem with your code is that the InvokeRequired property can only be accurate after the native window for the dialog is created. The problem is that you don't wait for that. The thread you started needs time to create the dialog. It blows up when InvokeRequired still returns false but a fraction of a second later the window is created and Invoke() now objects loudly against being called on a worker thread.
This requires interlocking, you must use an AutoResetEvent. Call its Set() method in the Load event handler for the dialog. Call its WaitOne() method in InitApplication().
This is not the only problem with this code. Your dialog also doesn't have a Z-order relationship with the rest of the windows in your app. Non-zero odds that it will show behind another window.
And an especially nasty kind of problem caused by the SystemEvents class. Which needs to fire events on the UI thread. It doesn't know what thread is the UI thread, it guesses that the first one that subscribes an event is that UI thread. That turns out very poorly if that's your dialog when it uses, say, a ProgressBar. Which uses SystemEvents to know when to repaint itself. Your program will crash and burn long after the dialog is closed when one of the SystemEvents now is raised on the wrong thread.
Scared you enough? Don't do it. Only display UI on the UI thread, only execute slow non-UI code on worker threads.
Thank you for your proposal. How to do that please ? Where should I
add Invoke ?
Assuming you've opted to leave the "loading" code of the main form in the main UI thread (probably called from the Load() event), AND you've set LoaderClass() as the "Splash screen" in Project --> Properties...
Here is what LoaderClass() would look like:
Public Class LoaderClass
Private Delegate Sub DoubleFunction(ByVal D As Double)
Public Sub DisplayPercentage(Value As Double)
If Me.InvokeRequired Then
Dim TempFunction As New DoubleFunction(AddressOf DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End If
End Sub
End Class
*This is the same as what you had but I moved the delegate into the class.
*Note that you do NOT need the CloseForm() method as the framework will automatically close your splash screen once the main form is completely loaded.
Now, over in the main form, you can grab the displayed instance of the splash screen with My.Application.SplashScreen and cast it back to LoaderClass(). Then simply call your DisplayPercentage() method at the appropriate times with appropriate values:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitApplication()
End Sub
Private Sub InitApplication()
Dim Loader As LoaderClass = DirectCast(My.Application.SplashScreen, LoaderClass)
Loader.DisplayPercentage(1 / 10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2 / 10)
ConnectToDataBase()
Loader.DisplayPercentage(3 / 10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4 / 10)
LoadClients()
' Loader.CloseForm() <-- This is no longer needed..."Loader" will be closed automatically!
End Sub
Private Sub LoadLocalConfiguration()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub ConnectToDataBase()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadInterfaceObjects()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadClients()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
End Class
If all goes well, your splash screen should automatically display, update with progress, then automatically close when your main form has finished loading and displayed itself.
Me.Invoke(TempFunction, Value)
Should be:
Me.Invoke(TempFunction, new Object(){Value})
because the overload with parameters takes an array of parameters.
Value is on the stack of the function in the current thread. You need to allocate memory on the GC heap and copy the value to that memory so that it is available to the other thread even after the local stack has been destroyed.