Prevent control disabling in vb.net - vb.net

is there a way to make a control always enabled, so that if you set Enabled = False it has no effect?
I tried to add a handler to the EnabledChange event in this way:
AddHandler mybutton.EnabledChange, Sub()
mybutton.EnabledChange = True
End Sub
but it causes a stackoverflow exception in a lot of situations, for example when you try to disable the control that contains the button. So is there another way to do this?

Try change like this .. it's worked for me
Private Sub myButton_EnabledChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles myButton.EnabledChanged
If Not myButton.Enabled Then myButton.Enabled = True
End Sub

No
Not within any realm of good coding practice
if you change it to enabled = false, it changes the property setting with that event.
What is the code issue, there will be a solution?
In the properties of the control, enabled =true. You don't need to write code for it. It's in the form design.
In response to your comment:
You can use the patch that matzone has selected.. personally, I am in favor of removing that particular control from the loop, and I've had issues with nested and endless loops in vb.net...I just prefer to have less unnecessary changes, within my program, just helps keep out the bugs. It means you've got to go through and ensure the code re-enabling the control is always called, whenever it is changed in the loop.. asking for errors if you ask me.. It may be more work in the short run, but would be better to select the control out in the first place. I've faced similar quandaries.
See link to MSDN
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.enabled.aspx

This worked for me
Protected Overridable Sub cmdCancel_EnabledChanged(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles cmdCancel.EnabledChanged
If DirectCast(eventSender, TPDotnet.GUIControls.WinButtonEx).Enabled = False Then
cmdCancel.Enabled = True
End If
End Sub
The condition is handled correctly.

Related

Most efficient way to programmatically update and configure forms and controls

I am looking for a way to prevent user form controls from appearing one by one when I'm programmatically adding them and for ways to enhance application performance and visual appeal.
Say, I have a Panel_Top in which I programmatically add comboboxes. What is happening is that they appear one by one as they are created and I am looking for a way to suspend the refreshing of the panel and or user form to make all of those programmatically added comboboxes to appear at the same time and faster than it happens right now.
I've tried suspendlayout which doesn't do anything for me or maybe I'm doing it wrong.
MyForm.PanelTop.SuspendLayout = true
And also I've tried to set the Panel_Top to invisible like:
MyForm.Top_Panel.visible = false
Which kind of sorta looks and performs better, or it might be a placebo.
What is the correct approach to this problem?
PS: I do have form set to doublebuffer = true, if that matters
What I tend to do is create a loading modal to appear on top of the form rendering the controls that need to be created/made visible, this can optionally have a progress bar that gets incremented as the control is created/shown. With the loading modal running, the container that needs to add the controls starts with SuspendLayout, adds the controls, and then finished with ResumeLayout.
This makes it so that controls are added/shown while giving the user a visual indicator that something is going on behind the scenes.
Here is a phenomenal example of a loading modal: https://www.vbforums.com/showthread.php?869567-Modal-Wait-Dialogue-with-BackgroundWorker and here is an example of using it:
Private ReadOnly _controlsToAdd As New List(Of Control)()
Private Sub MyForm_Show(sender As Object, e As EventArgs) Handles MyBase.Shown
Using waitModal = New BackgroundWorkerForm(AddressOf backgroundWorker_DoWork,
AddressOf backgroundWorker_ProgressChanged,
AddressOf backgroundWorker_RunWorkerCompleted)
waitModal.ShowDialog()
End Using
End Sub
Private Sub backgroundWorker_DoWork(sender As Object, e As DoWorkEventArgs)
Dim worker = DirectCast(sender, BackgroundWorker)
For index = 1 To 100
_controlsToAdd.Add(New ComboBox() With {.Name = $"ComboBox{index}"})
worker.ReportProgress(index)
Threading.Thread.Sleep(100) ' Zzz to simulate a long running process
Next
End Sub
Private Sub backgroundWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
Dim percentageCompleted = e.ProgressPercentage / 100
' do something with the percentageCompleted value
End Sub
Private Sub backgroundWorker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
PanelTop.SuspendLayout()
PanelTop.Controls.AddRange(_controlsToAdd.ToArray())
PanelTop.ResumeLayout()
End Sub
SuspendLayout() is the correct way to handle this with WinForms.
But first of all, this is a function you call, and not a flag you set.
Secondly, don't forget to call ResumeLayout() at the end of the changes.
Finally, you need to ensure you only call them once when you start to change around the controls in the panel and then again at the very end. If you use them with every control you won't get any benefit.
So the pattern might look something like this:
Public Sub SomeMethod()
PanelTop.SuspendLayout() ' Prevent the panel from updating until you've finished
' Make a bunch of changes
PanelTop.Controls.Clear()
For Each ...
PanelTop.Controls.Add( ... )
Next
PanelTop.ResumeLayout() ' Allow the panel to show all the changes in the same WM_PAINT event
End Sub
You also need to ensure you don't have anything in there like DoEvents()/Invalidate() that might invoke the windows message loop and cause the form to redraw itself.

Making sure async tasks complete before vb.net application terminates

I'm creating a vb.net desktop application. This application includes some asynchronous functions. When the user closes the application via the red X in the upper-right corner, there is some logic to possibly run one or more of these async functions. The problem is, the program terminates before they are complete. I figured using "Await" in my call would do that, but apparently not.
I found this thread that talks about using ManualResetEvent, but I'm having trouble understanding all of it, especially since the question is in the context of a console app, and the MSDN documentation the answer links to is about specifying threads, not simply using async tasks. As an attempt at using it anyway, I tried adding this to my main form:
Public resetEvent As ManualResetEvent = New ManualResetEvent(False)
And immediately after the call to one of these functions, I added this (quote includes the call):
Await activeCount.SerializeAsync(activeCount)
resetEvent.WaitOne()
And at the end of my async function itself, before returning the Task, added this:
frmMain.resetEvent.Set()
I don't think I'm using that right, though. The program still terminates before it's complete anyway.
Even before that, I figured the best place for such a thing would be in ApplicationEvents MyApplication_Shutdown, but I'm not sure how to know if such a function is still running at that point.
So what is the best way to make sure all my async functions complete before the application terminates in this situation?
Thank you!
UPDATE AFTER ACCEPTED ANSWER:
Though F0r3v3r-A-N00b's answer worked, I realized I need to use a dialog in certain cases. I couldn't call that within the background worker because the dialog is on the GUI thread, not the background thread. I tried moving things around so I'd call the dialog first, then make the background worker and all that, but for whatever reason I couldn't get it to work.
Long story short, I got around it by simply making a synchronous version of my functions, and so I could say 'if the user terminated the program and I need to call any of these functions before closing, call the synchronous versions instead'. That works. Thanks!
Try this. Create a new project. Add 1 label and backgroundworker to your form. Paste this in your form's code area:
Public Class Form1
Dim taskCompleted As Boolean = False
Dim taskIsrunning As Boolean = False
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Threading.Thread.Sleep(5000)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
taskCompleted = True
taskIsRunning = False
Label1.Text = "Background task completed."
Me.Close()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If taskIsRunning Then
e.Cancel = True
Exit Sub
End If
If Not taskCompleted Then
taskIsRunning = True
Label1.Text = "Starting background task."
BackgroundWorker1.RunWorkerAsync()
Label1.Text = "Background task is running."
e.Cancel = True
End If
End Sub
End Class

Winforms button click mousedown/mouseup

I experienced some strange thing in someone else's application that I am making myself familiar with: A button that on a click starts or stops a plc (TwinCat from Beckhoff) like this:
(Just a remark: the hbool1 variable is just an integer handle so that the plc knows which internal variable I want to change: here: switch on/off)
Private Sub Button1_MouseDown(sender As Object, e As MouseEventArgs) Handles Button1.MouseDown
'Switch off
adsClient.WriteAny(hbool1, False)
End Sub
Private Sub Button1_MouseUp(sender As Object, e As MouseEventArgs) Handles Button1.MouseUp
'Switch on
adsClient.WriteAny(hbool1, True)
End Sub
I wasn't familiar with MouseUp/MouseDowns on a button and expected just a Click.
So out of curiosity I tried it with the Click Event:
Dim state_isOn As Bool
Private Sub Button1_Click(sender As Object, e As MouseEventArgs) Handles Button1.Click
If state_isOn Then
'Switch off
adsClient.WriteAny(hbool1, False)
Else
'Switch on
adsClient.WriteAny(hbool1, True)
End If
End Sub
The variable state_isOn is set to true or false when the plc responds with an answer.
I do not understand 2 things:
Why does the 1st code snippet work? On the first click it switches on, on the next click it switches off. As far as I see, both events are called in a row: first mouseDown, afterwards mouseUp. So the Plc should be switched on always, but I can switch it off as well.
Why does my own idea not work? Isn't it more logical?!
What I did not try yet is to check the MouseClick Event. Then perhaps my own idea would work, but still there is the question, why the 1st code does work at all.
EDIT:
I figured out another thing:
Consider the first snippet, how it was done initially (working): I put 2 message boxes in the code and noticed a different behaviour: With the message boxes, it does not work any more. When I click the button, I only get to the point showing "Switch off", and no following "Switch on". While this might be related to the interrupting nature of a message box, still it is interesting.
Private Sub Button1_MouseDown(sender As Object, e As MouseEventArgs) Handles Button1.MouseDown
'Switch off
adsClient.WriteAny(hbool1, False)
MsgBox("Switch off")
End Sub
Private Sub Button1_MouseUp(sender As Object, e As MouseEventArgs) Handles Button1.MouseUp
'Switch on
adsClient.WriteAny(hbool1, True)
MsgBox("Switch on")
End Sub
Another note: With the MouseClick-event it does not work, either.
I have the feeling there is something wrong in the code of the plc and the former author figured out this mousebuttonup/down hack somehow to make it work, instead of fixing the plc. Is this possible?
To really start or stop the plc with the TcAdsClient you should use the WriteControl Methods available like:
tcClient.WriteControl(newStateInfo(AdsState.Run,tcClient.ReadState().DeviceState));
tcClient.WriteControl(newStateInfo(AdsState.Stop,tcClient.ReadState().DeviceState));
You are probably not really stopping the PLC. Instead you are just writing to a BOOL variable on your plc, which happens to change the control flow of your PLC program. It is often normal programming style on a PLC that you will trigger an action whith a rising or falling edge on a BOOL variable. To create this edge it makes perfect sense to use the MouseUp/MouseDown event. But there is the possibility that when you click too fast that the rising edge is not recognized. This can happen if the variable gets updated to FALSE and then TRUE in the same PLC cycle. You should probably use just the ClickEvent to set to FALSE and trigger a timer to set the variable to TRUE again so that you ensure that you are not in the same PLC cycle.

Why does my image not show properly after clicking? VB.NET

I'm having this code:
Private Sub FormatMouseOver_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles FormatMouseOver.MouseClick
FormatClicked.Enabled = True
FormatClicked.Visible = True
BigLettersNoClicked.Enabled = True
BigLettersNoClicked.Visible = True
FormatMouseOver.Visible = False
FormatMouseOver.Enabled = False
End Sub
all the variables are images stored in pictureBoxes. The first one mentioned (FormatClicked) does show and enables itself properly, however the secound one (BigLettersNoClicked) doesn't do anything. The two pictureBoxes overlap, the BigLettersNoClicked is exactly one layer above FormatClicked. I tried putting it on any other place in the project and it still doesn't work.
Just to clarify: FormatMouseOver disabling works fine, and there are no errors during debuging
Subsequent question: since i'm new to the vb.net, can anyone explain to me, what is the difference between MouseClick and Click events?
Thanks
As always, MSDN provides a good summary about that topic:
Maybe this helps you: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.click%28v=vs.110%29.aspx

Not checking for control key pressed properly

I got this timer tick function:
Private Sub controlTick(ByVal sender As Object, ByVal e As EventArgs)
Label2.Text = (Control.ModifierKeys = Keys.Control)
End Sub
That is supposed to make my label say "True" if I am currently holding down the Control key, and "False" if I am not.
But, how come my label is always "False"? What is interesting is that if I press the Control key at lighting speed a bunch of times I can see for a fraction of a second "True", but immediately turns to "False".
Timer ticks every 50ms.
I do not understand.... any ideas?
I can't reproduce the behavior you describe... I tried creating a new WinForms project, placed a Label control on the middle of the form, and added a Timer control.
Whenever I press the Ctrl key, the label reads True. Otherwise, it reads False. Exactly the behavior you would expect to see. I don't have to press anything at lightning speed.
(Edit: It doesn't break when more controls are placed on the form either. What are you doing differently?)
My code looks like this:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
' Start the timer
Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
' Update the label
Label1.Text = (Control.ModifierKeys = Keys.Control).ToString
End Sub
Only difference is that you're apparently compiling without type checking enabled (Option Strict Off).
I always prefer to code in VB.NET with this turned on (check your project's Properties window), in which case you have to explicitly convert the boolean type to a string type using ToString.
I have created a winform application to prove this.. I am using the form and I have set the "KeyPreview" property to true and for every key pressed I get the code correctly.
Please check again using the way I mentioned and let me know if it resolves.
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show(e.KeyCode.ToString());
}
Also for Control key the code is (e.KeyCode == Keys.ControlKey)....
I'm not sure this will help, but try using HasFlag, because maybe there is some other flag in ModifierKeys which is also on:
http://msdn.microsoft.com/en-us/library/system.enum.hasflag.aspx