Dynamic UserControl AddHandler - vb.net

I am trying to understand why my AddHandler isn't working.
I have found a workaround if buttons always on same form but they may not be in the future.
I am also creating these buttons so I can add several variables for later use
Any have a simple answer for me please?
Thanks
Mark
FORM
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
For n = 0 To 3
Dim ctl As New item_button
AddHandler ctl.Click, AddressOf Me.ClickMe
ctl.Name = "btn" & n
ctl.btn.Text = "Button " & n
ctl.btnID = n
ctl.Location = New Point(10, n * 50)
Me.Controls.Add(ctl)
Next
End Sub
Public Sub ClickMe(ByVal s As Object, ByVal e As EventArgs)
'do something
Dim btn As item_button
btn = CType(s, item_button)
TextBox1.Text = "Button " & s.btnID & " was pressed"
End Sub
End Class
ITEM_BUTTON
Public Class item_button
Public btnID As Integer
Public btnColor As System.Drawing.Color
Public Function ClickIt() As Integer
Return btnID
End Function
End Class

Your "Button" does not inherit from Button:
Public Class ItemButton ' Naming-Conventions: http://msdn.microsoft.com/en-us/library/ms229040(v=vs.110).aspx
Inherits Button
Public Property BtnID As Integer
Public Property BtnColor As System.Drawing.Color
Public Function ClickIt() As Integer
Return btnID
End Function
End Class
Since i'm not sure what you're actually trying to achieve i show you an example with a custom event that is raised in the custom button and handled in the form:
Public Class ItemButton
Inherits Button
Public Property BtnID As Integer
Public Property BtnColor As System.Drawing.Color
Public Event ButtonClicked(sender As ItemButton, buttonID As Int32)
Private Sub clicked(sender As Object, e As EventArgs) Handles Me.Click
RaiseEvent ButtonClicked(Me, BtnID)
End Sub
End Class
in the form:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
For n = 0 To 3
Dim ctl As New ItemButton
AddHandler ctl.ButtonClicked, AddressOf Me.ItemButtonClicked
ctl.Name = "btn" & n
ctl.Name = "Button " & n.ToString()
ctl.btnID = n
ctl.Location = New Point(10, n * 50)
Me.Controls.Add(ctl)
Next
End Sub
Public Sub ItemButtonClicked(ByVal btn As ItemButton, ByVal buttonID As Int32)
TextBox1.Text = "Button " & buttonID & " was pressed"
End Sub

Sorted
Private Sub btn_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btn.Click
RaiseEvent ButtonClicked(Me, btnID)
End Sub
Thanks Tim, your code helped

Related

Error : reference to a non-shared member requires an object reference

The game i am making is connect 4 , the form I am calling from is here :
Public Class Form1
Public players(1) As IPlayer
Public playerturn As IPlayer
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
buttons()
init()
End Sub
Public Sub init()
playerturn = players(0)
Board1.Init()
currentaturn.Text = String.Format("{0}'s turn", playerturn.name)
End Sub
Sub buttons()
For i As Integer = 0 To 7
Dim btn As New Button()
btn.Text = "drop"
btn.BackColor = Color.Pink
btn.Size = New Size(40, 40)
btn.Location = New Point(i * 48, 0)
btn.Tag = i
AddHandler btn.Click, AddressOf clickbutton
Panel1.Controls.Add(btn)
Next
End Sub
Sub clickbutton(ByVal sender As Object, ByVal e As EventArgs)
Dim clickedbutton As Button = CType(sender, Button)
Dim clickedcolumn As Integer = clickedbutton.Tag
If (Board1.addpiecetocolumn(clickedcolumn, Color.Red) = False) Then
Else
End If
End Sub
Private Sub Panel1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Panel1.Paint
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles LDBTN.Click
End Sub
Private Sub Label2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles currentaturn.Click
End Sub
Private Sub RESETBTN_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RESETBTN.Click
loginsys.Show()
Me.close
End Sub
End Class
and the form I am calling to :
Public Class board
Public board(8, 8) As panelbox
Public columns As Integer = 7
Public rows As Integer = 7
Public Sub New()
InitializeComponent()
Init()
End Sub
Public Sub Init()
Me.Controls.Clear()
For i As Integer = 0 To columns - 1
For j As Integer = 0 To rows - 1
board(i, j) = New panelbox()
board(i, j).Size = New Size(50, 50)
board(i, j).Location = New Point((i * 45), (j * 45))
board(i, j).BackColor = Color.DodgerBlue
Me.Controls.Add(board(i, j))
Next
Next
End Sub
Public Function addpiecetocolumn(ByVal x As Integer, ByVal colo As Color) As Boolean
For y As Integer = rows - 1 To 0 Step -1
If (board(x, y).Used = False) Then
board(x, y).Used = True
board(x, y).BackColor = colo
Return True
End If
Next
Return False
End Function
End Class
it should be noted that I am naming the second form board and the part that's calling the error is from the first form :
Public Sub init()
playerturn = players(0)
Board1.Init()
currentaturn.Text = String.Format("{0}'s turn", playerturn.name)
End Sub
Any help would be great
Use Public Shared Function function_name, in this case, you have to call it like board.function_name

Managing Dynamically created User Controls events

I have a User Control that is Dynamically created. It has to raise a Mouse_Move event & Mouse_Down event.
How to manage events for Multiple User Control that are created dynamically. I was considering using a list of user controls to track the controls. But I do not know how to setup the events properly.
Public Class UserControl1
Public Structure Porportions
Dim width_Percent As Double
Dim Height_percent As Double
Dim X_Location_Percent As Double
Dim Y_Location_Percent As Double
End Structure
Dim Pipe As Porportions
Dim guage1 As Porportions
Dim guage2 As Porportions
Public start_pos As Point
Public move_offset As Point
Public Client_Point As Point
Public Pipe_Source As Excel
Public Pipe_Data As DataSet
Public Pipe_Properties As Pipe
Private Pipe_ID As String
' Public Event Pipe_MouseMove(ByVal sender As Object, ByVal e As System.EventArgs)
Public Event Pipe_MouseMove1(ByVal sender As Object, ByVal e As System.EventArgs)
Public Event Pipe_MouseDown1(ByVal sender As Object, ByVal e As System.EventArgs)
Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
RaiseEvent Pipe_MouseMove1(sender, e)
End Sub
Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
RaiseEvent Pipe_MouseDown1(sender, e)
End Sub
Public Class Form1
Private pipe_cnt As Integer = 0
Private start_position As Point
Private MoveOffset As Point
Private Mouse_Position As Point
Private WithEvents pp As UserControl1
Private Sub Pipe_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles pp.Pipe_MouseMove1
Dim dx As Integer
Dim dy As Integer
Dim m_loc As Point
Dim scrn As Point
m_loc = New Point(e.Location)
Mouse_Position = New Point(e.X, e.Y)
scrn = PointToScreen(Mouse_Position)
Mouse_Position = PointToClient(Mouse_Position)
dx = start_position.X - Mouse_Position.X
dy = start_position.Y - Mouse_Position.Y
MoveOffset = New Point(dx, dy)
If e.Button = MouseButtons.Left Then
Try
pp.Location = New Point(pp.Left + e.X, pp.Top + e.Y)
pp.Location = New Point(pp.Left + e.X, pp.Top + e.Y)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End If
End Sub
Private Sub Pipe_MouseDown1(ByVal sender As Object, ByVal e As System.EventArgs) Handles pp.Pipe_MouseDown1
start_position = New Point(pp.Location)
End Sub
What I understand that you want to use an same event for multiple user controls. There are many methods to achieve this.
Method 1 (Easiest):
Just put handler events after Handles clause and separate them by commas ,. See example:
Private Sub MouseMove_Event(sender As Object, e As MouseEventArgs) Handles Pipe.MouseMove, PictureBox1.MouseMove
MsgBox("MouseMove")
End Sub
Private Sub Click_Event(sender As Object, e As MouseEventArgs) Handles Pipe.Click, PictureBox1.Click
MsgBox("Click")
End Sub
Private Sub MouseDown_Event(sender As Object, e As MouseEventArgs) Handles Pipe.MouseDown, PictureBox1.MouseDown
MsgBox("MouseDown")
End Sub
Method 2 (burden):
Create and collect all controls in a array of controls and then create events in a foreach loop.
Create Sub that gets array of controls and add handlers using foreach loop.
Private Sub CreateHandlers(Controls() As Control)
For Each control As Control In Controls
Me.Controls.Add(control)
AddHandler control.Click, AddressOf Click_Event
AddHandler control.MouseMove, AddressOf MouseMove_Event
AddHandler control.MouseDown, AddressOf MouseDown_Event
Next
End Sub
Your events
Private Sub Click_Event(sender As Object, e As EventArgs)
'Handle Click events here
End Sub
Private Sub MouseMove_Event(sender As Object, e As EventArgs)
'Handle MouseMove events here
End Sub
Private Sub MouseDown_Event(sender As Object, e As EventArgs)
'Handle MouseDown events here
End Sub
Create controls dynamically and just call CreateHandlers(controls) at end
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim pictureBox1 As PictureBox = New PictureBox _
With {
.Size = New Size(100, 100),
.Location = New Point(0, 0),
.BackColor = Color.Black
}
Dim panel1 As Panel = New Panel _
With {
.Size = New Size(100, 100),
.Location = New Point(100, 0),
.BackColor = Color.Red
}
Dim tableLayoutPanel1 As TableLayoutPanel = New TableLayoutPanel _
With {
.Size = New Size(100, 100),
.Location = New Point(200, 0),
.BackColor = Color.Green
}
Dim controls() As Control = {pictureBox1, panel1, tableLayoutPanel1}
CreateHandlers(controls)
End Sub
End Class

how to safe call a control from another thread using Timers.Timer

I read various posts, and made a practice project, but it does not works.
The form have a button and a text box with a default text 'Updated 0 times'. On button click starts the timer and each time update the text with the number of times the text was updated.
The exception of cross thread calls is not thrown, but when calling the text box, its .Text = "", the text is updated but not the text box on the form. And InvokeRequired is always false.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Here the textBox.Text = "Updated 0 times."
Dim checking_text As String = Me.TextBox1.Text
TimerTest.StartTimer()
End Sub
Delegate Sub UpdateTextInvoke(ByVal new_text As String)
Public Sub UpdateText(ByVal new_text As String)
'Here the textBox.Text = ""
Dim txtB As TextBox = Me.TextBox1
'InvokeRequired always = False.
If txtB.InvokeRequired Then
Dim invk As New UpdateTextInvoke(AddressOf UpdateText)
txtB.Invoke(invk, New Object() {new_text})
Else
'The value of this text box is updated, but the text on the form TextBox1 never changes
txtB.Text = new_text
End If
End Sub
End Class
Public Class TimerTest
Private Shared tmr As New System.Timers.Timer
Private Shared counter As Integer
Public Shared Sub StartTimer()
tmr.Interval = 5000
AddHandler tmr.Elapsed, AddressOf UdpateText
tmr.Enabled = True
End Sub
Public Shared Sub UdpateText(ByVal sender As Object, ByVal e As System.EventArgs)
counter += 1
Form1.UpdateText(String.Format("Updated {0} time(s).", counter))
End Sub
End Class
SOLVED
In the Class TimerTest added this code 'Private Shared myform As Form1 = Form1'
then changed 'Form1.UpdateText' To 'myform.UpdateText'
As indicated in the comments, you are using the default form instance feature of VB.Net. You could pass an instance of the form to the TimerTest class, and replace the reference to Form1 with the instance.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim checking_text As String = Me.TextBox1.Text
TimerTest.StartTimer(Me)
End Sub
Public Sub UpdateText(new_text As String)
If TextBox1.InvokeRequired Then
Dim invk As New Action(Of String)(AddressOf UpdateText)
TextBox1.Invoke(invk, {new_text})
Else
TextBox1.Text = new_text
End If
End Sub
End Class
Public Class TimerTest
Private Shared tmr As New System.Timers.Timer()
Private Shared counter As Integer
Private Shared instance As Form1
Public Shared Sub StartTimer(formInstance As Form1)
instance = formInstance
tmr.Interval = 5000
AddHandler tmr.Elapsed, AddressOf UdpateText
tmr.Enabled = True
End Sub
Public Shared Sub UdpateText(ByVal sender As Object, ByVal e As System.EventArgs)
counter += 1
instance.UpdateText(String.Format("Updated {0} time(s).", counter))
End Sub
End Class

Overriding a derived Event

I want to "override" an Event from a derived class - for example from a Forms-Control.
My actual state is, that the overriding (performed by the Command "Shadows") is working when I use the Handler of this Control directly.
Is the Control a member of a Collection it is only working with such Events which I have created by myself - if I try to use the overridden Event it isn't working. I suppose that the Collection uses the Event from the Base-Class.
Is that possible ?
And if "Yes" - what could I do ?
Code-Snippets from the described "Problem" :
This part collects the Event-Handler inside the Custom Control :
Private KalenderElemente As New Collection
Private Sub CreateElements()
KalenderElemente.Clear()
For i As Integer = 1 To 42
Dim myKalenderTag As New PP_Monatskalender_Tag
myKalenderTag.Name = "Tag_" + i.ToString("00")
myKalenderTag.ForeColor = my_ForeColor_Days
myKalenderTag.BackColor = my_BackColor_Days
myKalenderTag.Parent = Me
AddHandler myKalenderTag.Click, AddressOf KalenderTag_Click
AddHandler myKalenderTag.MouseMove, AddressOf KalenderTag_MouseMove
AddHandler myKalenderTag.MouseEnter, AddressOf KalenderTag_MouseEnter
AddHandler myKalenderTag.MouseLeave, AddressOf KalenderTag_MouseLeave
KalenderElemente.Add(myKalenderTag)
Next
End Sub
Private Sub Kalender_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles MyBase.MouseMove
If Not KalenderElemente.Item(0).Visible Then
KalenderElemente.Item(0).DatumsTag = 0
RaiseEvent MouseMove(KalenderElemente.Item(0), e)
Else
KalenderElemente.Item(41).DatumsTag = 0
RaiseEvent MouseMove(KalenderElemente.Item(41), e)
End If
End Sub
Private Sub KalenderTag_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
RaiseEvent MouseMove(sender, e)
End Sub
Shadows Event MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
Private Sub KalenderTag_MouseEnter(ByVal sender As Object, ByVal e As EventArgs)
RaiseEvent MouseEnter(sender, e)
End Sub
Shadows Event MouseEnter(ByVal sender As Object, ByVal e As EventArgs)
Private Sub KalenderTag_MouseLeave(ByVal sender As Object, ByVal e As EventArgs)
RaiseEvent MouseLeave(sender, e)
End Sub
Shadows Event MouseLeave(ByVal sender As Object, ByVal e As EventArgs)
Now each of the internal Controls deliver it's Mouse-Event to outside.
If I put it on a Form and write a script which takes the Event I could see that all works fine (and as expected).
In the following you see the part of the collection which should manage this Control (and others) :
Public Class MessageDefinition
Public WithEvents Control As Control
Public HeaderText As String
Public MessageText As String
Public DisplayShadow As Boolean
Public ToolTipLocation As ToolTipLocationDefintion
Public Location As Point
End Class
Public Class Message_Collection
Inherits CollectionBase
Public Shadows Sub Clear()
Dim myItem As MessageDefinition
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
RemoveHandler myItem.Control.MouseEnter, AddressOf Item_MouseEnter
RemoveHandler myItem.Control.MouseMove, AddressOf Item_MouseMove
RemoveHandler myItem.Control.MouseLeave, AddressOf Item_MouseLeave
Next
List.Clear()
End Sub
Overrides Function ToString() As String
Return "[...]"
End Function
Public Sub Dispose()
Clear()
End Sub
' ================================
Public Sub SetMessage(item As MessageDefinition)
Dim myItem As MessageDefinition
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control.GetType Is item.Control.GetType _
AndAlso myItem.Control.Name = item.Control.Name Then
'List.Item(i - 1) = item
'RaiseEvent MouseEnter(item, System.EventArgs.Empty)
Exit Sub
End If
Next
AddHandler item.Control.MouseEnter, AddressOf Item_MouseEnter
AddHandler item.Control.MouseMove, AddressOf Item_MouseMove
AddHandler item.Control.MouseLeave, AddressOf Item_MouseLeave
List.Add(item)
RaiseEvent MouseEnter(item, System.EventArgs.Empty)
End Sub
Private Sub Item_MouseEnter(sender As Object, e As System.EventArgs)
Dim myItem As MessageDefinition
Dim mySender As Control = sender
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control Is mySender Then
RaiseEvent MouseEnter(myItem, e)
Exit Sub
End If
Next
End Sub
Private Sub Item_MouseMove(sender As Object, e As System.EventArgs)
Dim myItem As MessageDefinition
Dim mySender As Control = sender
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control Is mySender Then
RaiseEvent MouseMove(myItem, e)
Exit Sub
End If
Next
End Sub
Private Sub Item_MouseLeave(sender As Object, e As System.EventArgs)
Dim myItem As MessageDefinition
Dim mySender As Control = sender
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control Is mySender Then
RaiseEvent MouseLeave(myItem, e)
Exit Sub
End If
Next
End Sub
Public Event MouseEnter(sender As Object, e As System.EventArgs)
Public Event MouseMove(sender As Object, e As System.EventArgs)
Public Event MouseLeave(sender As Object, e As System.EventArgs)
End Class
As described (on Top) the catched Events are fired with "Standard"-Controls but not with the "Customized" Control.
If I change it and build up my own Events (with different names), which are not shadowing the Events from the derived control, it is also working as expected.
You don't override events in .NET - you can only override inherited event handlers, if any.
The event pattern in .NET is to create a public event in the base class and a protected virtual (VB Protected Overridable) method that raises that event and that can be overridden by a derived class. This method should be named OnEventName.
The Windows Forms controls follow this pattern, so to e.g. override when the Click event happens, you override the OnClick method:
Public Class MyTextBox
Inherits TextBox
Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
If SomeCondition() Then
MyBase.OnClick(e)
Else
Return 'Do not click
End If
End Sub
End Class
Obviously you can fiddle with the e argument as well.

String Manipulation by hiding and showing

The idea is simple, if I have a string value "ABCD" then with a ButtonClick event it should randomly reveal a char while others are hidden. i.e, "B*" another click would "AB**" and so on.
So far, my I have been stuck in a for loop.
For Each c As Char In x
y = Random.Next(0, x.IndexOf(c))
Next
I'm still learning VB.NET at this phase.
Public Class Form1
Private _indexes As Integer()
Private _currentIndex As Integer
Private _chars As Char()
Private _template As String
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim rnd = New Random()
_template = "ABCD"
' Create indexes that are randomly sorted
_indexes = Enumerable _
.Range(0, _template.Length) _
.OrderBy(Function(i) rnd.Next()) _
.ToArray()
'Create an array of chars with stars '****'.
_chars = New String("*"c, _template.Length).ToCharArray()
_currentIndex = 0
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If _currentIndex < _template.Length Then
Dim index As Integer = _indexes(_currentIndex)
_currentIndex += 1
_chars(index) = _template(index)
Dim result As String = New String(_chars)
Label1.Text = result
End If
End Sub
End Class
I have already posted an answer where I focused on the algorithm. The algorithm code was directly integrated into a form. This works but is not a good practice. The code would be more reusable, more understandable and could be tested more easily if it was extracted to a separate class.
Public Class RandomTextRevealer
Private _indexes As Integer()
Private _currentIndex As Integer
Private _chars As Char()
Private _template As String
Private _result As String
Public Sub New(ByVal templateText As String)
Dim rnd = New Random()
_template = templateText
' Create indexes that are randomly sorted
_indexes = Enumerable _
.Range(0, _template.Length) _
.OrderBy(Function(i) rnd.Next()) _
.ToArray()
'Create an array of chars with stars '****'.
_chars = HiddenText.ToCharArray()
_currentIndex = 0
End Sub
Public Function RevealNext() As String
If _currentIndex < _template.Length Then
Dim index As Integer = _indexes(_currentIndex)
_currentIndex += 1
_chars(index) = _template(index)
_result = New String(_chars)
End If
Return _result
End Function
Public ReadOnly Property HiddenText() As String
Get
Return New String("*"c, _template.Length)
End Get
End Property
End Class
We can test the class like this in a little console application, without a form:
Module Programm
Public Sub Main()
Dim revealer = New RandomTextRevealer("Just a test")
Console.WriteLine(revealer.HiddenText)
For i As Integer = 1 To 12
Console.WriteLine(revealer.RevealNext())
Next
Console.ReadKey()
End Sub
End Module
Now we can integrate it in a form like this (I added a Reset-button, in order to be able to repeat the test with different random values):
Public Class Form2
Dim revealer As RandomTextRevealer
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Reset()
End Sub
Private Sub btnReveal_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReveal.Click
Label1.Text = revealer.RevealNext()
End Sub
Private Sub btnReset_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReset.Click
Reset()
End Sub
Private Sub Reset()
revealer = New RandomTextRevealer("ABCD")
Label1.Text = revealer.HiddenText
End Sub
End Class
Now our form-code looks much cleaner.