I've received a project from another developer. Long story short, the program pulls several thousand entries from an SQL Database hosted on our internal network. The program displays the entries and allows you to filter them for convenience.
Recently we had an issue in which a table in our SQL Database was cleared (It's normally regenerated each day, but for several days it was blank.) Found the issue and solved it (Made no changes to the VB project) to repopulate the table; but since that point the VB project would no longer fire events.
The program is several thousand lines of code long, so I can not post the entire thing; but I will try my best to give symptoms:
The form object can fire events (Form_Closing, Form_Closed, etc.)
The existing controls (Radio button, buttons, picturebox, data grid view, etc) will not fire any events.
If I add a new control (such as a button), it will not fire events.
If a put a debug breakpoint at the sub that should be fired, it will not break.
Here's an example of a method that should be fired but is not fired:
`Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
MsgBox("GOT IT!")
End Sub`
Here's the Form_Load sub:
<DllImport("user32.dll")> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitializeComponent()
Try
DataGridView_Items.RowsDefaultCellStyle.SelectionBackColor = Color.Yellow
DataGridView_Items.RowsDefaultCellStyle.SelectionForeColor = Color.Black
CheckBox_Highlight.DataBindings.Add("Visible", RadioButton_BD, "Checked")
Try
'Populates the DGV
LoadTable()
TableLayoutPanel_BD_Parts.Visible = True
TableLayoutPanel_PF_Parts.Visible = False
'Exits if no data was pulled from the database
If dbDataSet.Rows.Count = 0 Or pfDataSet.Rows.Count = 0 Then Application.ExitThread()
Catch ex As Exception
Using w As StreamWriter = File.AppendText(logFile)
Log("Function Form1_Load says " & ex.Message & " # " & DateTime.Now.ToString("yyyy-MM-dd") & "_" & DateTime.Now.ToString("HH-mm-ss"), w)
End Using
End Try
BackgroundWorker1.RunWorkerAsync()
formLoaded = True
Catch exx As Exception
MsgBox(exx.ToString())
End Try
End Sub
There is a backgroundworker, but it appears to work correctly and exit out.
All the forms can be interacted with; but do not fire events. (I can change the selection of the radio button, click the button, type into text boxes, etc.)
I know this is a little vague, I'm just hoping someone can give suggestions as to things that could cause this that I can look into. I can provide specifics; but I can't copy the entire code here.
Thanks for any help!
A very strange thing in your code is that you call InitializeComponent() from Form_Load.
Usually this method is called in Form constructor so you can remove it from Form_Load.
I made some test on my PC: if you called twice InitializeComponent() you duplicate every controls in the form and their events doesn't fire anymore maybe because you have two controls with the same name.
Related
(I'm using VS2008 with EMDK v2.9 with Fix1)
I have a form, where I declare my reader:
Private WithEvents barcodeReader As Barcode.Barcode = New Barcode.Barcode
I want it to be active only in one of the controls on the form, so I do this:
Private Sub txbAccount_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txbAccount.GotFocus
barcodeReader.EnableScanner = True
End Sub
And turn it off the same way in the Lost Focus event of that textbox.
Here is the OnRead sub:
Private Sub barcodeReader_OnRead(ByVal sender As Object, ByVal readerData as Symbol.Barcode.ReaderData) Handles barcodeReader.OnRead
If (readerData.HasReader) Then
Try
Dim ctrl As TextBox = Ctype(GetActiveControl(), TextBox)
If (ctrl.Name = "txbAccount") Then
ctrl.Text = readerData.Text
End If
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1)
End If
End Sub
The problem is: as soon as I enable scanner in the GotFocus event of the textbox, the OnRead event will fire over and over (with empty data) until I actually press the scan key, scan actual data - then it stops.
I've read that maybe the Handled property is not getting set properly, however I don't see property like that for this.
Quick question to answer is the handledis usualy within the e eventargs (although I dont see any in that routine just readerdata it may be in there!?
On a side, with barcode scanners they usualy use the sendkeys commands to simply pass string through to the system. These can be trapped easily by using any of the OnKeyPress / OnKeyDown... etc. If you wanted to go down this route, you'll need to take each keypress / down as aan individual character, whereas your barcode.OnRead might do all that for you. (Again I havent used the EMDK you reference).
Lastly 'usualy' barcode scanners end with a cr (carriage return) some barcode scanners can turn this off, or change these in settings. If not this might be where the e.handled referenced elsewhere is talking about. This would be something like ..
if e.KeyChar = Chr(Keys.Enter) then
e.handled = true
end if
This would stop the send going to the object (textbox) and therfore not losing focus (as the enter key wasn't passed)
Hope this helps.. a bit :)
Chicken
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 am writing a script / program to log into SAP then just grab a few bits of data from a employee then close SAP.
This I can do already but am trying to make a fancy GUI just for that instead of clicking many windows in SAP to get it...mainly because im lazy cant be bothered clicking a million things... Some call this innovation i think :)
I have been researching BackGroundWorker in vb.net but wouldnt this still load the window and just keep the form active and responsive while running the program?
I dont have 'admin' rights (can create and modify user accounts su01,pa30 etc) to the server etc so cant log into the server\database...hence running script to obtain result..
Does anyone know how Ican log SAP and have it hidden while running ?
You can check this post about how to hide an external application window.
http://www.vbforums.com/showthread.php?669210-RESOLVED-Hiding-Window-of-external-process
In this example, guys trying to start calc.exe in hidden mode.
First off all insert at the beggining of your progect
Imports System.Runtime.InteropServices
Then you have to enum flags for ShowWindow (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548%28v=vs.85%29.aspx
Private Enum ShowWindowCommand As Integer
Hide = 0
Show = 5
Minimize = 6
Restore = 9
End Enum
Set a specified window's show state
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function ShowWindow(ByVal hwnd As IntPtr, ByVal nCmdShow As ShowWindowCommand) As Boolean
End Function
Determine whether the specified window handle identifies an existing window
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function IsWindow(ByVal hWnd As IntPtr) As Boolean
End Function
Determines whether a specified window is minimized.
Private Declare Auto Function IsIconic Lib "user32.dll" (ByVal hwnd As IntPtr) As Boolean
variable to save window handle (you can choise your own name, but rename in other part of code)
Private calc_hWnd As IntPtr
launch windows calculator (in your case saplogon.exe) minimized and hidden, while loading form
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim test As New Process
Try
' file to launch
test.StartInfo.FileName = "calc.exe" ' note: full path not needed for windows calc.
' Note: This next line has no effect on WinXP "calc.exe" and some other apps like FireFox.
'test.StartInfo.WindowStyle = ProcessWindowStyle.Minimized
' start the app
test.Start()
' wait until app is in idle state
test.WaitForInputIdle(-1)
' get app main window handle
Dim tmp_hWnd As IntPtr = test.MainWindowHandle
' make sure handle is valid (non zero)
' try up to 10 times within one second
' do a re-try loop that runs for a second or two
For i As Integer = 1 To 10
tmp_hWnd = test.MainWindowHandle
If Not tmp_hWnd.Equals(IntPtr.Zero) Then Exit For ' got handle so exit loop
Threading.Thread.Sleep(100) ' wait 100ms
Next '- try again
If Not tmp_hWnd.Equals(IntPtr.Zero) Then
' use ShowWindow to change app window state (minimize and hide it).
ShowWindow(tmp_hWnd, ShowWindowCommand.Minimize)
ShowWindow(tmp_hWnd, ShowWindowCommand.Hide)
' save handle for later use.
calc_hWnd = tmp_hWnd
Else
' no window handle?
MessageBox.Show("Unable to get a window handle!")
End If
Catch ex As Exception
' error !
MessageBox.Show(ex.Message)
End Try
End Sub
on exit restore/unhide app if found running.
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
' is our variable set to non-zero?
If Not calc_hWnd.Equals(IntPtr.Zero) Then
' is app window found?
If IsWindow(calc_hWnd) = True Then
' if app is minimized then restore it
If IsIconic(calc_hWnd) Then
ShowWindow(calc_hWnd, ShowWindowCommand.Restore)
End If
' make sure window is seen incase it was hidden.
ShowWindow(calc_hWnd, ShowWindowCommand.Show)
End If
End If
End Sub
But you can write another code in and kill saplogon.exe process.
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
For Each p As Process In System.Diagnostics.Process.GetProcessesByName("saplogon.exe")
Try
p.Kill()
' possibly with a timeout
p.WaitForExit()
' process has already exited - might be able to let this one go
Catch ex As Exception
MessageBox.Show(ex.toString)
End Try
Next
End Sub
frmMain has a button that kicks off a background worker
Private Sub btnProcessITN_Click(sender As Object, e As EventArgs) Handles btnProcessITN.Click
Me.frmMainProgressBar.Visible = True
Me.btnProcessITN.Text = "working..." & Me.frmMainProgressBar.Value & "%"
Me.btnProcessITN.Enabled = False
backgroundWorker2.WorkerReportsProgress = True
BackgroundWorker2.RunWorkerAsync()
End Sub
Private Sub backgroundWorker2_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker2.DoWork
ProcessITN()
End Sub
ProcessITN lives in modITN. This runs, does its thing, then PROBLEM
Public Sub ProcessITN()
...doing work here....
frmMain.updateProgress(current, Max)
End Sub
and back in form main I have a function that tries to update the progress of the background worker, and update a progress bar on the form
Public Function updateProgress(current As Integer, Max As Integer) As Boolean
BackgroundWorker2.ReportProgress((current / Max) * 100)
Return True
End Function
Private Sub backgroundWorker2_ProgressChanged( _
ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles BackgroundWorker2.ProgressChanged
Me.frmMainProgressBar.Value = e.ProgressPercentage
Me.btnProcessITN.Text = "working..." & Me.frmMainProgressBar.Value & "%"
'Me.txtProgress.Text = Me.txtProgress.Text & vbNewLine & strMessage
Me.txtProgress.AppendText(vbNewLine & strMessage)
End Sub
Now, the problem is, that although the code is running, and the values are being passed across correctly from modITN to frmMain, the form itself is not refreshing. the progress bar does not change. txtProgress does not change.
N.B this code was working perfectly when the stuff within modITN was within frmMain. I moved it to try and tidy my code.
You may still have a look here, I had the same question - and there are some more issues to it.
Multithreading for a progressbar and code locations (vb.net)?
VB.net's 'Default Instance' of Forms is a hideous trap in my opinion.
I've sorted the problem!
I moved the backgroundworker blocks to modITN, out frmMain. The whole thing runs in modITN now, and updates the progress bar from there. frmMain simply calls the sub in modITN to start the whole thing off.
Simple!
I've been reading around trying to find out why I'd be getting this exception to no avail. I hope someone has seen this before:
I'm using Visual Basic 2010.
Briefly, I have a "Settings Panel" form which takes a while to create (it contains a lot of labels and textboxes), so I create it in another thread.
After it's loaded, it can be viewed by clicking a button which changes the form's visibility to True. I use the following subroutine to handle invokes for my controls:
Public Sub InvokeControl(Of T As Control)(ByVal Control As T, ByVal Action As Action(Of T))
If Control.InvokeRequired Then
Control.Invoke(New Action(Of T, Action(Of T))(AddressOf InvokeControl), New Object() {Control, Action})
Else
Action(Control)
End If
End Sub
Here's the relevant part of my main code (SettingsTable inherits TableLayoutPanel and HelperForm inherits Form):
Public Class ch4cp
Public RecipeTable As SettingsTable
Public WithEvents SettingsWindow As HelperForm
Private Sub ch4cp_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
PanelCreatorThread = New Threading.Thread(AddressOf CreateStartupPanels)
PanelCreatorThread.Start()
End Sub
Private Sub CreateStartupPanels()
SettingsWindow = New HelperForm("Settings Panel")
SettingsTable = New SettingsTable
SettingsTable.Create()
SettingsWindow.Controls.Add(SettingsTable)
End Sub
Private Sub ViewSettingsPanel_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ViewSettingsPanel.CheckedChanged
InvokeControl(SettingsWindow, Sub(x) x.Visible = ViewSettingsPanel.Checked)
End Sub
The SettingsTable.Create() method generates a bunch of Labels and TextBoxes based on the contents of the application settings and adds them to the SettingsTable.
When I click on the ViewSettingsPanel checkbox, I get a cross-thread violation error. Any ideas? I would really appreciate it.
I figured it out. In case anyone else might be running into a similar issue, here was the secret:
In the SettingsTable class, I have a MakeTable method which looks like this:
Private Sub MakeTable()
Me.Visible = False
Me.Controls.Clear()
... add some controls ...
Me.Visible = True
End Sub
I did this so that the control wouldn't flicker if the table was remade while visible. I don't entirely understand why (from reading, I'm guessing it's something like the handles for the child controls weren't being created because they weren't shown after being created, so IsInvokeRequired evaluated to False when it should have been True). The fix was to do this:
Private Sub MakeTable()
If Not IsNothing(Me.Parent) Then If Me.Parent.Visible Then Me.Visible = False
Me.Controls.Clear()
... add some controls ...
Me.Visible = True
End Sub
This way, the child controls are "shown" on the invisible SettingsWindow form and their handles are therefore created. Works just fine now!
Better way to this in VB.NET is to use a Extension it makes very nice looking code for cross-threading GUI Control Calls.
Just add this line of code to any Module you have.
<System.Runtime.CompilerServices.Extension()> _
Public Sub Invoke(ByVal control As Control, ByVal action As Action)
If control.InvokeRequired Then
control.Invoke(New MethodInvoker(Sub() action()), Nothing)
Else
action.Invoke()
End If
End Sub
Now you can write Cross-Thread Control code that's only 1 line long for any control call.
Like this, lets say you want to clear a ComboBox and it's called from threads or without threads you can just use do this now
cboServerList.Invoke(Sub() cboServerList.Items.Clear())
Want to add something after you clear it?
cboServerList.Invoke(Sub() cboServerList.Items.Add("Hello World"))