Where to declare a public object which refer to a VB object? - vb.net

First time that I use Visual Studio and VB.net.
Could someone explain to me where to declare a public object which refers to a VB object ?
This code works fine :
Public Class Form1
Private ThePen As New System.Drawing.Pen(Color.Red)
Private Sub Line(A As Point, y As Point)
Dim NewGraphic As Graphics = PictureBox1.CreateGraphics()
NewGraphic.DrawLine(ThePen, A, B)
NewGraphic.Dispose()
End Sub
End Class
But I would like to declare only one time in public
Dim NewGraphic As Graphics = PictureBox1.CreateGraphics()
I tried to declare it at the beginning, but it seem that my object PictureBox1 is not yet loaded (so, can't access PictureBox1.CreateGraphics())
So I tried in
Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
But I can't declare public variable inside :(

You should pretty much NEVER call CreateGraphics. Draw on a control in its Paint event handler or, if appropriate, create a custom control and override the OnPaint method. Store the data that represents the drawing in one or more fields and, whenever you want to change the drawing, set those fields and call Invalidate on the control.
Private lineStart As Point
Private lineEnd As Point
Private Sub DrawLine(start As Point, [end] as Point)
lineStart = start
lineEnd = [end]
PictureBox1.Invalidate()
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As EventArgs) Handles PictureBox1.Paint
e.Graphics.DrawLine(Pens.Red, lineStart, lineEnd)
End Sub
Generally speaking, it is preferable to specify the area to invalidate rather than not specifying an argument and invalidating the entire control. It is actually painting the pixels to the screen that is the slow part so it is preferable to keep that to a minimum. I'll leave that part to you but you may like to check this out for more info. Note that, if you're moving a line, you'd need to invalidate the area that contained the old line and the area that will contain the new line. You can call Invalidate multiple times with different areas in such cases, or you can combine the areas into one Region and call it once.

So different view but interesting !
I'm unable to test it :(
There's a problem with e. object in
e.Graphics.DrawLine(Pens.Red, lineStart, lineEnd)
BC30456 Visual Basic 'Graphics' is not a member of 'EventArgs'.

Related

Better solution, timer, stopwatch, timespan

I am working on small tool for tracking duration of various activities.
In this example we have 3 activities, Drive, Walk and Wait.
Each activitiy is a button on Form1
Example:
Click on button Drive, stopwatch "SW" and timer "Tmr" are started and counting "Drive" time.
After 5 seconds I click on button Wait, SW and Tmr are stopped, SW1 and Tmr1 are started and counting time for "Wait" activity.
Click again on button Drive, SW1 and Tmr1 as stopped, SW and Tmr started and time is resumed from 5th second
And so on, can be one or more activities included. At the end of measuring I have total duration for each activity.
This Code below is actually working well. Function is called from the Form1, measuring is started and later I have values in public variables available.
Module:
Dim SW, SW1, SW2 As New Stopwatch
Dim WithEvents Tmr, Tmr1, Tmr2 As New Timer
Dim stws() = {SW, SW1, SW2}
Dim tmrs() = {Tmr, Tmr1, Tmr2}
Public Drive, Walk, Wait As String
Public Function WhichButton(btn As Button)
WhichButton = btn.Text
Select Case WhichButton
Case "Drive"
For Each s As Stopwatch In stws
s.Stop()
Next
For Each t As Timer In tmrs
t.Stop()
Next
SW.Start()
Tmr.Start()
Case "Wait"
For Each s As Stopwatch In stws
s.Stop()
Next
For Each t As Timer In tmrs
t.Stop()
Next
SW.Start()
Tmr1.Start()
Case "Walk"
For Each s As Stopwatch In stws
s.Stop()
Next
For Each t As Timer In tmrs
t.Stop()
Next
SW2.Start()
Tmr2.Start()
End Select
End Function
Private Sub Tmr_Tick(sender As Object, e As System.EventArgs) Handles Tmr.Tick
Dim elapsed As TimeSpan = SW.Elapsed
Drive = $"{elapsed.Hours:00}:{elapsed.Minutes:00}.{elapsed.Seconds:00}"
End Sub
Private Sub Tmr1_Tick(sender As Object, e As System.EventArgs) Handles Tmr1.Tick
Dim elapsed As TimeSpan = SW1.Elapsed
Walk = $"{elapsed.Hours:00}:{elapsed.Minutes:00}.{elapsed.Seconds:00}"
End Sub
Private Sub Tmr2_Tick(sender As Object, e As System.EventArgs) Handles Tmr2.Tick
Dim elapsed As TimeSpan = SW2.Elapsed
Wait = $"{elapsed.Hours:00}:{elapsed.Minutes:00}.{elapsed.Seconds:00}"
End Sub
Reason im here is because I'm not happy with this solution and I don't have a knoweledge for advanced one. The probem here is that I can have X number of Buttons, can add new or remove few, it depends on situation, and I don't want to write block of Code for each. Also if I Change a text property of the button, Select Case will not work.
So I want to create timers and stopwatches dynamically for each button.
I would like to start with this:
Dim timers As List(Of Timer) = New List(Of Timer)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For Each btn As Button In Panel1.Controls.OfType(Of Button)
timers.Add(New Timer() With {.Tag = btn.Name})
AddHandler btn.Click, AddressOf Something
Next
End Sub
Public Sub Something(sender As Object, e As EventArgs)
Dim btn = DirectCast(sender, Button)
Dim tmr As Timer = timers.SingleOrDefault(Function(t) t.Tag IsNot Nothing AndAlso t.Tag.ToString = btn.Name)
End Sub
Here I can refer to Timer over the Tag property but I have no idea how to implement stopwatch and timespan.
Thanks for reading and any help is appreciated, suggestions, pseudocode, code examples.
Firstly, there's no point using three Timers. A single Timer can handle all three times. Secondly, based on what you've posted, there's no point using any Timer. The only reason I could see that a Timer would be useful would be to display the current elapsed time in the UI constantly, but you're not doing that. Repeatedly setting those String variables is pointless if you're not going to display them. Just get the Elapsed value from the appropriate Stopwatch if and when you need it.
As for your Buttons' Click event handler, it's terrible too. The whole point of a common event handler is because you want to do the same thing for each object so you only have to write the code once. If you end up writing separate code for each object in that common event handler then that defeats the point and makes your code more complex instead of less. You should be using separate event handlers for each Button.
If you were going to go with a common event handler though, at least extact out the common code. You have the same two For Each loops in all three Case blocks. That should be done before the Select Case and then only start the appropriate Stopwatch in each Case.
I don't think that you should be using Buttons though. You should actually be using RadioButtons. You can set their Appearance property to Button and then they look just like regular Buttons but still behave like RadioButtons. When you click one, it retains the depressed appearnce to indicate that it is checked and clicking a different one will release the previously-depressed one. In that case, your code might look like this:
Private ReadOnly driveStopwatch As New Stopwatch
Private ReadOnly waitStopwatch As New Stopwatch
Private ReadOnly walkStopwatch As New Stopwatch
Private Sub driveRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles driveRadioButton.CheckedChanged
If driveRadioButton.Checked Then
driveStopwatch.Start()
Else
driveStopwatch.Stop()
End If
End Sub
Private Sub waitRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles waitRadioButton.CheckedChanged
If waitRadioButton.Checked Then
waitStopwatch.Start()
Else
waitStopwatch.Stop()
End If
End Sub
Private Sub walkRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles walkRadioButton.CheckedChanged
If walkRadioButton.Checked Then
walkStopwatch.Start()
Else
walkStopwatch.Stop()
End If
End Sub
Because checking a RadioButton automatically unchecks any other, each CheckedChanged event handler only has to worry about its own Stopwatch.
If you wanted to display the elapsed time for a particular Stopwatch when it stops, you do that when it stops, e.g.
Private Sub driveRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles driveRadioButton.CheckedChanged
If driveRadioButton.Checked Then
driveStopwatch.Start()
Else
driveStopwatch.Stop()
driveLabel.Text = driveStopwatch.Elapsed.ToString("hh\:mm\:ss")
End If
End Sub
That overload of TimeSpan.ToString was first available in .NET 4.5 I think, so you should use it unless you're targeting .NET 4.0 or earlier.
If you did want to display the current elapsed time constantly then, as I said, you only need one Timer. You would just let it run all the time and update appropriately based on the Stopwatch that is currently running, e.g.
Private Sub displayTimer_Tick(sender As Object, e As EventArgs) Handles displayTimer.Tick
If driveStopwatch.IsRunning Then
driveLabel.Text = driveStopwatch.Elapsed.ToString("hh\:mm\:ss")
ElseIf waitStopwatch.IsRunning Then
waitLabel.Text = waitStopwatch.Elapsed.ToString("hh\:mm\:ss")
ElseIf walkStopwatch.IsRunning Then
walkLabel.Text = walkStopwatch.Elapsed.ToString("hh\:mm\:ss")
End If
End Sub
You haven't shown us how you're displaying the elapsed time so that's a bit of a guess. In this scvenario, you should definitely still update the Label when a Stopwatch stops, because the Timer won't update that Label on the next Tick.
You would presumably want a Button somewhere that could stop and/or reset all three Stopwatches. That would mean setting Checked to False on all three RadioButtons and then calling Reset on all three Stopwatches. You'll probably want to clear/reset the Labels too.
There's also a potential gotcha using RadioButtons like this. If one of your RadioButtons is first in the Tab order then it will recieve focus by default when you load the form. Focusing a RadioButton will check it, so that would mean that you'd start a Stopwatch by default. If that's not what you want, make sure that some other control is first in the Tab order. If you can't do that for some reason, handle the Shown event of the form, set ActiveControl to Nothing, uncheck that RadioButton and reset the corresponding Stopwatch and Label.
As a final, general message, notice that I have named everything so that even someone with no prior knowledge of the project would have no doubt what everything was and what it was for. Names like SW, SW1 and SW2 are bad. Even if you realised that SW meant Stopwatch, you have no idea what each one is actually for. In this day of Intellisense, it's just lazy use names like that. Every experienced developer can tell you a story about going back to read their own code some time later and having no idea what they meant by various things. Don't fall into that trap and make sure that you get into good habits early.
EDIT:
As a bonus, here's a way that you can use a common event handler properly. Firstly, define a custom Stopwatch class that has an associated Label:
Public Class StopwatchEx
Inherits Stopwatch
Public Property Label As Label
End Class
Once you make that association, you automatically know which Label to use to display the elapsed time for a Stopwatch. Next, define a custom RadioButton class that has an associated Stopwatch:
Public Class RadioButtonEx
Inherits RadioButton
Public Property Stopwatch As StopwatchEx
End Class
Next, use that custom class on your form instead of standard RadioButtons. You can add them directly from the Toolbox (your custom control will be added automatically after building your project) or you can edit the designer code file and change the type of your controls in code. There is a certain amount of risk in the latter option so be sure to create a backup beforehand. Once that's all done, change the type of your Stopwatches and handle the Load event of the form to create the associations:
Private ReadOnly driveStopwatch As New StopwatchEx
Private ReadOnly waitStopwatch As New StopwatchEx
Private ReadOnly walkStopwatch As New StopwatchEx
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Associate Stopwatches with RadioButtons
driveRadioButton.Stopwatch = driveStopwatch
waitRadioButton.Stopwatch = waitStopwatch
walkRadioButton.Stopwatch = walkStopwatch
'Associate Labels with Stopwatches
driveStopwatch.Label = driveLabel
waitStopwatch.Label = waitLabel
walkStopwatch.Label = walkLabel
End Sub
You can now use a single method to handle the CheckedChanged event for all three RadioButtons because you can now do the exact same thing for all three of them:
Private Sub RadioButtons_CheckedChanged(sender As Object, e As EventArgs) Handles driveRadioButton.CheckedChanged,
waitRadioButton.CheckedChanged,
walkRadioButton.CheckedChanged
Dim rb = DirectCast(sender, RadioButtonEx)
Dim sw = rb.Stopwatch
If rb.Checked Then
sw.Start()
Else
sw.Stop()
sw.Label.Text = sw.Elapsed.ToString("hh\:mm\:ss")
End If
End Sub
The RadioButton that raised the event tells you which Stopwatch to use and that tells you which Label to use, so there's no need to write different code for each one.
The Tick event handler of the Timer can also treate each Stopwatch with common code:
Private Sub displayTimer_Tick(sender As Object, e As EventArgs) Handles displayTimer.Tick
For Each sw In {driveStopwatch, waitStopwatch, walkStopwatch}
If sw.IsRunning Then
sw.Label.Text = sw.Elapsed.ToString("hh\:mm\:ss")
Exit For
End If
Next
End Sub
You can create the array atthe class level but, as it's only being used in this one place, it makes sense to create it here. The performance hit is insignificant and it makes the code more readable by creating things where they are used.
Note that I did use abbreviations for variable names in this code. That's for two reasons. Firstly, they are variables that will refer to different objects at different times. That means that using a name specific to the purpose of the object is not possible. You could use a context-based name, e.g. currentRadioButton, but I don't do that here because of the second reason.
That second reason is that they are local variables used in a very limited scope. The rb and sw variables are not used more than a few lines from where they are declared so it's hard to not understand what they are. If you name a field like that then, when you see it in code, you have to look elsewhere to find out what it is. In this code, if you're looking at a usage of one of those variables then the declaration is in eyeshot too, so you'd have to be blind not to see what type you're dealing with. Basically, if a variable is used a long way from its declaration then I suggest a meaningful, descriptive name. If it is only used within a few lines of its declaration though, a brief name is OK. I generally tend to use the initials of the type, as I have done here. If you need multiple local variables of that type, I generally prefer to use descriptive names to disambiguate them rather than using numbers. Sometimes, though, there's really no purpose-specific way to do that, in which case numbers are OK, e.g. comparing two Strings without context might use s1 and s2 as variable names.

Get form control using string name VB.NET

I have a control on a form UserNameCtrl and that control has a sub called LoadCtrl
I essentially have loads of these subs for clicks, so I want to put them all into one event handler
Private Sub NewsletterBtn_Click(sender As Object, e As EventArgs) Handles NewsletterBtn.Click, NewsletterImage.Click
If Not MainNewsletterCtrl.Loaded() Then MainNewsletterCtrl.Load()
End Sub
However within each of the subs the control names are hardcoded to call the .loaded and .load functionality.
I've wrote a new version of this
Private Sub GenericNavItem_Click(sender As Object, e As EventArgs)
Dim ctrl As Control = Controls.Find(sender.tag, True).FirstOrDefault
'Want to do the Controlname.Load here
End Sub
Using the tag (which I named as the control name) I got the corresponding control. But it's bringing back it as a control rather than of the type I want it to be.
I know I declare it as Control, but I don't know how I can cast it to be the ControlName.Load rather than the generic control.
If they are all the same class (or base class), then just cast to that class. If they are all different class but have the same method Load and Loaded, then I suggest you create an interface.
Interface ISomeName
Sub Load()
Function Loaded() As Boolean()
End Interface
Make sure all your class implement it and then just cast to that interface.
Private Sub GenericNavItem_Click(sender As Object, e As EventArgs)
Dim ctrl As Control = Controls.Find(sender.tag, True).FirstOrDefault
Dim ctrlInterface As ISomeName = CType(ctrl, ISomeName)
If Not ctrlInterface.Loaded() Then ctrlInterface.Load()
End Sub

VB .NET event handler of controls

I'm using VB .NET to create a planning, and I got a little problem with events.
In the main form, I put a panel in which I add programatically rows and boxes in those rows. I have inside the form a TextBox and the panel that contains all the boxes. I want to change a the text of the TextBox when I click on a box, so I use the AddHandler statement but it doesn't work. I tried to debug it and I realised that it actually calls the sub and inside it, I can see the changes it makes (TextBox.Text becomes what I want), but when it exits the sub, it is like nothing has changed.
I don't know if I was clear enough.
Thanks
Here is a simplified code (I removed all the graphics functions to resize the controls...)
Public Class frmPrinc
Public actEditing As Object
Private Class boxAct
Inherits Label
Public act As Integer
Public Sub New(ByVal a As Integer)
act = a
AddHandler Me.Click, AddressOf clickBox
End Sub
Private Sub clickBox(sender As Object, e As EventArgs)
Dim boxact As boxAct = DirectCast(sender, boxAct)
frmPrinc.actEditing = boxact
boxact.Text = "Clicked"
End Sub
End Class
Private Sub showPlanning()
pan_plan.Controls.Clear()
Dim plan As New Control ' Control that will be used as a row
For i As Integer = 0 To 10
plan.Controls.Add(New boxAct(i))
Next
Panel1.Controls.Add(plan)
End Sub
End Class
When I run that, the text of the box changes but actEditing is still Nothing...
Instead of boxAct trying to directly update frmPrinc of the current "box" being clicked, it should instead raise a Custom Event that frmPrinc subscribes to. frmPrinc can use that information as it then sees fit. Below I've added the custom event and raise it in class boxAct. The form subscribes to that event using AddHandler when each instance of boxAct is created. All together, this looks something like:
Public Class frmPrinc
Public actEditing As boxAct
Public Class boxAct
Inherits Label
Public act As Integer
Public Event BoxClicked(ByVal box As boxAct)
Public Sub New(ByVal a As Integer)
act = a
End Sub
Private Sub boxAct_Click(sender As Object, e As EventArgs) Handles Me.Click
Me.Text = "Clicked"
RaiseEvent BoxClicked(Me)
End Sub
End Class
Private Sub showPlanning()
pan_plan.Controls.Clear()
Dim plan As New Control ' Control that will be used as a row
For i As Integer = 0 To 10
Dim box As New boxAct(i)
AddHandler box.BoxClicked, AddressOf box_BoxClicked
plan.Controls.Add(box)
Next
Panel1.Controls.Add(plan)
End Sub
Private Sub box_BoxClicked(box As boxAct)
actEditing = box
Debug.Print("Box Clicked: " & actEditing.act)
End Sub
End Class
From the comments:
Thanks man, it worked! I'd like to know though why I need to make such
a structure to raise a simple event that modifies the main form...
Just to not do the same mistake again – Algor Frile
This a design decision everyone must make: "Loosely Coupled" vs. "Tightly Coupled". The approach I gave above falls into the Loosely Coupled category. The main benefit to a loosely coupled solution is re-usability. In your specific case, we have Class boxAct being reused multiple times, albeit all within the same form. But what if that wasn't the case? What if you wanted to use boxAct on multiple forms (or even have multiple "groups" of them)? With your original approach, you had this line:
frmPrinc.actEditing = boxact
which means that if wanted to use Class boxAct with a different form you'd have to make a copy of Class boxAct, give it a new name, and then manually change that one line to reference the new form:
Public Class boxAct2
Private Sub clickBox(sender As Object, e As EventArgs)
Dim boxact As boxAct = DirectCast(sender, boxAct)
frmSomeOtherForm.actEditing = boxact
boxact.Text = "Clicked"
End Sub
End Class
This shows the disadvantage of the Tightly Coupled approach, which uses references to specifics types (the Form in this case) to communicate. The Tightly coupled approach might be initially easier to implement when you're coding fast and furious, but then it suffers from re-usability down the line. There are scenarios in which a tightly coupled solution make sense, but only you can make that decision; those scenarios usually involve some kind of "sub-control" that will only ever get used within some kind of custom container/control and will never be used on its own somewhere else.
Conversely, with the loosely coupled approach, if we wanted to re-use Class boxAct in a different form, then no changes to it would be required at all (though at that point you'd probably not want it declared within your original Form!). In the new form you'd simply add a handler for the BoxClicked() event and then do what you need to do. Each form would receive the events for its respective instances of boxAct.
Final thoughts...your original approach could actually work, but most likely was failing at this line (same as above):
frmPrinc.actEditing = boxact
Here you were referencing to frmPrinc using what is known as the Default Instance of that form. This would have worked if frmPrinc was the "Startup Object" for your application. I'm guessing it wasn't, however, and you were creating an instance of frmPrinc from somewhere else. To make the original approach work you would have had to pass a reference to your ACTUAL instance of frmPrinc into Class boxAct (usually via the Constructor in tightly coupled solutions).

Embed database in vb.net app

I'm building a small VB.net app. I want to add a little database (about 5 columns, 20 records). I want to keep everything in a single exe. I think it's a bit overkill to add a 'full' database, so I'm looking for an alternative.
I could create a CSV file, and add it as a resource. Is this a good idea, or are there any other better alternatives?
Another option for such a small amount of data is to store it in ApplicationSettings. Your question implies you are using WinForms, so you can make use of the built-in features with just a small amount of work to store your own custom class.
Create a class to represent your data.
Wrap that class in a property of another class that inherits from ApplicationSettingsBase as a List(Of )
Manipulate this custom setting as needed and call Save() as needed.
Here is an example that binds to a DataGrid:
The class that represents your data:
Public Class Fruit
Public Property FruitName As String
Public Property FruitColor As String
Public Property FruitGrowsOn As String
End Class
The class that turns Fruit into a collection stored in application settings. Notice it inherits ApplicationSettingsBase. Also notice the attributes on the Fruits property that identify this as a user setting as opposed to an application setting (which cannot be modified by the user). The DefaultSettingAttribute makes sure the collection is instantiated so you don't get null reference exception until after the first time you add an item:
Imports System.Configuration
Public NotInheritable Class FruitCollection
Inherits ApplicationSettingsBase
<UserScopedSettingAttribute()>
<DefaultSettingValue("")>
Public Property Fruits() As List(Of Fruit)
Get
Fruits = Me("Fruits")
End Get
Set(ByVal value As List(Of Fruit))
Me("Fruits") = value
End Set
End Property
End Class
The Form definition. Retrieves the instance of your custom setting (FruitUserSettings), creates a binding source for a DataGridView, and provides a Save button to persist the changes made in the grid to Settings. Next time the user opens the form the changes will still be there provided they clicked the Save button:
Public Class Form1
Dim FruitUserSettings As FruitCollection
Dim GridBindingSrc As BindingSource
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
FruitUserSettings = New FruitCollection()
GridBindingSrc = New BindingSource(FruitUserSettings, "Fruits")
DataGridView1.AutoGenerateColumns = True
DataGridView1.DataSource = GridBindingSrc
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
FruitUserSettings.Save()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
GridBindingSrc.Dispose()
End Sub
End Class
Note, you don't need a binding source or grid, these were just for demonstration. You can manipulate FruitUserSettings.Fruits like any other list in any way you want. As long as Save() is called on the settings you will retain the data.
You can download/clone the working sample here: https://github.com/crowcoder/CustomSetting
i would use XML file as little database, you can query it easily with linq (Language-Integrated Query). also there are built in library's that can help you handle you records and query's. of course that you can use access, excel (you can query excel with SQL) csv or txt file . also you can create a local data base file in visual studio

VB.Net Chart created in code using WithEvents - Handler causes plot time to increase - why?

To start with I have a fairly unique situation in that I am dealing with large amounts of data - multiple series of about 500,000 points each. The typical plot time is about 1s which is perfectly adequate.
The chart is created 'WithEvents' in code and the plot time doesn't change.
However, when I add the sub with the handler for the click event ..
Private Sub Chart_Main_Click(ByVal sender As Object, _
ByVal e As MouseEventArgs) Handles Chart_Main.Click
Dim y As Integer = Chart_Main.ChartAreas(0).AxisX.PixelPositionToValue(e.X)
'MsgBox(y)
End Sub
the plot time blows out to 3min. Even having no code in the sub, the result is the same. There is no reference to the click event in any of the code so I am at a loss as to why this is occurring. I suspect it has something to do with the number of points being added but not knowing the cause is frustrating.
Is anyone able to explain what is going on?
Ok, i don't know if the explanation in the comments was sufficient, so here some example code...
Also i wanted to try this myself!
Essencially, what you do is take control on when you want Windows to check the events.
For that, i suggested two wrappers on AddHandler and RemoveHandler that can safely be called from worker threads.
So, what you have to do, is:
Initialize the Handler in the constructor
Call RemoveClickHandler on your control, each time you want it to be left alone by the EventHandler
But don't forget to reinitialize the handler afterwards via AddClickHandler
Also, your handler method should not have the 'Handles' keyword anymore...
Public Class MainForm
Public Sub New()
' This call is required by the designer.
InitializeComponent()
m_pPictureClickHandler = New MouseEventHandler(AddressOf hndPictureClick)
AddClickHandler(pbxFirst, m_pPictureClickHandler)
End Sub
' Have a persistent local instance of the delegate (for convinience)
Private m_pPictureClickHandler As MouseEventHandler
Public Sub AddClickHandler(obj As Control, target As [Delegate])
If Me.InvokeRequired Then
Me.Invoke(New Action(Of Control, [Delegate])(AddressOf AddClickHandler), obj, target)
Else
AddHandler obj.MouseClick, target
End If
End Sub
Public Sub RemoveClickHandler(obj As Control, target As [Delegate])
If Me.InvokeRequired Then
Me.Invoke(New Action(Of Control, [Delegate])(AddressOf RemoveClickHandler), obj, target)
Else
RemoveHandler obj.MouseClick, target
End If
End Sub
' Here your Plot is done
Public Sub LockedPlot()
RemoveClickHandler(pbxFirst, m_pPictureClickHandler)
' do something on your handler free control ...
AddClickHandler(pbxFirst, m_pPictureClickHandler)
End Sub
' This is your handler (note without a 'Handles' keyword)
Private Sub hndPictureClick(sender As Object, e As MouseEventArgs)
' do something with the click
MessageBox.Show(String.Format("Yeah! You clicked at: {0}x{1}", e.X.ToString(), e.Y.ToString()))
End Sub
End Class
I suppose an even better design would be to create a child class of your chart that has an LPC style method called, say 'SafePlot', with folowing features:
It accepts a pointer (delegate) to a procedure
It will remove all the event handler before invoking the procedure
Finally it would reinitialize the handlers on it's own after the job is done.
It may require a collection to all handler refering to it's events.
-> For that reason i'd let the class manage the handlers entiraly...
Alternativly you could put the 'SafePlot' idea in your main class. then you could manage the event handler there... but that is disputable
Well i can think of a few other ways to do this, but i'm cutting the brainstorming now!
If interested in one of these design solutions, give me a poke.