How to create a new event for my own custom control? - vb.net

I have a class which inherit from Panel, and below are some member in this class
Public ItemName As String
Public Quantity As Integer
Public Price As Decimal
Public DiscountAmount As Decimal
How can I create a event when Quantity or DiscountAmount changed then run a function?
I try to write in this way but I get error:-
Private Sub info_Changed(sender As Object, e As EventArgs) Handles Quantity.Changed, DiscountAmount.Changed
myFunction()
End Sub
Error:
Handles clause requires a WithEvents variable defined in the
containing type or one of its base types.

You need to declare the events in user control and then consume those. See below code. I have created a user control UserControl1. This control raises events when Price or DiscountAmount is changed. The usercontrol is then used in Form1. You can use the same approach to change the Quantity.
Public Class Form1
Private WithEvents userCntrl As New UserControl1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ChangeValues()
End Sub
Private Sub ChangeValues()
userCntrl.Price = 100
userCntrl.DiscountAmount = 12
End Sub
Private Sub userCntrl_Price_Changed(newValue As Decimal) Handles userCntrl.Price_Changed, userCntrl.DiscountAmount_Changed
MessageBox.Show("New value = " & newValue.ToString)
End Sub
End Class
Public Class UserControl1
Public Event Price_Changed(ByVal newValue As Decimal)
Public Event DiscountAmount_Changed(ByVal newValue As Decimal)
Public ItemName As String
Public Quantity As Integer
Private Price_ As Decimal
Public Property Price() As Decimal
Get
Return Price_
End Get
Set(ByVal value As Decimal)
If value <> Price_ Then
RaiseEvent Price_Changed(value)
End If
Price_ = value
End Set
End Property
Private DiscountAmount_ As Decimal
Public Property DiscountAmount() As Decimal
Get
Return DiscountAmount_
End Get
Set(ByVal value As Decimal)
If value <> DiscountAmount_ Then
RaiseEvent DiscountAmount_Changed(value)
End If
DiscountAmount_ = value
End Set
End Property
End Class

Related

I am trying to create a base class and derived class that displays functions on a form and need help to fix code

Here are the project instructions:
The main form has eight label controls, two textbox controls and four buttons. Customer Name and Account are static value. You enter Starting Account Balance and Transaction Amount. Clicking on Deposit button adds the Transaction Amount to Starting Account Balance and displays in Account Balance label. Clicking on Withdraw button checks to make sure there is sufficient fund in Starting Account Balance, if not it displays a message: The amount to withdraw exceeds account balance.
Clear button clears Starting Account Balance, Transaction Amount, and Account Balance. Exit button closes the form.
Here is my code for Base Class ACCOUNT and Derived Class BANK ACCOUNT:
Public Class Account
Public StartBalance As Decimal
Public Transaction As Decimal
Public AccountBalance As Decimal
Public Property txtStartBalance() As Decimal
Get
Return StartBalance
End Get
Set(ByVal value As Decimal)
StartBalance = value
End Set
End Property
Public Sub New(ByVal txtStartBalance As Decimal, txtTransaction As Decimal, txtAccountBalance As Decimal)
StartBalance = txtStartBalance
Transaction = txtTransaction
AccountBalance = txtAccountBalance
End Sub
End Class
Public Class BankAccount
Inherits Account
Public Sub New()
MyBase.New()
End Sub
Public Function MakeDeposit(ByVal txtAccountBalance.text As Decimal)
AccountBalance = StartBalance + Transaction
txtAccountBalance.text = AccountBalance
End Function
Public Function MakeWithdraw(ByVal txtAccountBalance.text As Decimal)
If Transaction > StartBalance Then
MessageBox.Show("The amount to withdraw exceeds account balance.")
Else
AccountBalance = StartBalance - Transaction
txtAccountBalance.text = AccountBalance
End If
End Function
End Class
Here is my FORM code:
Public Class Form1
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
Me.Close()
End Sub
Private Sub btnClear_Click(sender As Object, e As EventArgs) Handles btnClear.Click
txtStartBalance.Clear()
txtTransaction.Clear()
End Sub
Private Sub btnDeposit_Click(sender As Object, e As EventArgs) Handles btnDeposit.Click
Dim MyAccount As New BankAccount()
If txtStartBalance.Text = Nothing Then
MessageBox.Show("Error! Enter Starting Account Balance")
ElseIf txtTransaction.Text = Nothing Then
MessageBox.Show("Error! Enter Transaction Amount")
Else
MakeDeposit()
End If
End Sub
Private Sub btnWithdraw_Click(sender As Object, e As EventArgs) Handles btnWithdraw.Click
Dim MyAccount As New Account(byVal txtStartBalance.Text As Decimal, byVal txtTransaction.Text As Decimal))
txtAccountBalance.txt = MakeWithdraw()
End Sub
End Class
This is my form design:
In a class variables like Public StartBalance As Decimal are called fields as opposed to Properties. Fields are normally Private and hold the data of the class. The Public Properties expose the class's data through property procedures; what you have written with the Get, Set code. Up to date versions of Visual Studio write the Property procedure, and backer field for you. All you have to write is Public Property StartBalance. The rest is hidden but it works the same.
Your problem with the BankAccount class is Sub New. For MyBase.New to function it must have the parameters that the base is looking for.
You don't want your class to be dependent upon the User Interface controls. The class may be used in other programs with a different UI, maybe a web application. Message boxes in the class wouldn't work in a web app. As long as the methods of the class receive parameters of the proper type it will function without caring where the parameters came from.
Whenever you have user input you need to validate that it is correct. I moved that to a separate Function so the event procedure wouldn't be too long.
I made a single event procedure for both transaction buttons (note the Handles clause) because most of the code is the same. A property of the class instance (BankCustomer.CurrentBalance) contains the value you want for the balance label.
Public Class Account
Public Property CurrentBalance As Decimal
Public Property StartBalance As Decimal
Public Property CustomerName As String
Public Property AccountNumber As Integer
Public Sub New(BegBalance As Decimal, AcctNum As Integer, Name As String)
StartBalance = BegBalance
CurrentBalance = StartBalance
CustomerName = Name
AccountNumber = AcctNum
End Sub
End Class
Public Class BankAccount
Inherits Account
Public Sub New(BegBalance As Decimal, AcctNum As Integer, Name As String)
MyBase.New(BegBalance, AcctNum, Name)
End Sub
Public Sub MakeDeposit(ByVal TransAmount As Decimal)
CurrentBalance += TransAmount
End Sub
Public Function MakeWithdraw(ByVal TransAmount As Decimal) As Boolean
If TransAmount > CurrentBalance Then
Return False
Else
CurrentBalance -= TransAmount
Return True
End If
End Function
End Class
'In the Form
Private BankCustomer As BankAccount
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
Me.Close()
End Sub
Private Sub btnClear_Click(sender As Object, e As EventArgs) Handles btnClear.Click
txtStartBalance.Clear()
txtTransaction.Clear()
'Clear the labels too
BankCustomer = Nothing
End Sub
Private Sub TransactionButtons_Click(sender As Object, e As EventArgs) Handles btnDeposit.Click, btnWithdraw.Click
If Not ValidateInput() Then
Exit Sub
End If
If BankCustomer Is Nothing Then
BankCustomer = New BankAccount(CDec(txtStartBalance), CInt(txtAccountNumber.Text), txtCustomerName.Text)
End If
Dim btn = DirectCast(sender, Button)
If btn.Name = "btnDeposit" Then
BankCustomer.MakeDeposit(CDec(txtTransaction.Text))
Else
If Not BankCustomer.MakeWithdraw(CDec(txtTransaction.Text)) Then
MessageBox.Show("Sorry, cannot make withdrawal, amount exceeds current balance.")
End If
End If
lblAccountBalance = BankCustomer.CurrentBalance.ToString
End Sub
Private Function ValidateInput() As Boolean
Dim bool = False
If String.IsNullOrEmpty(txtCustomerName.Text) Then
MessageBox.Show("Please fill in Customer Name")
Return False
End If
If Not Integer.TryParse(txtAccountNumber.Text, Nothing) Then
MessageBox.Show("Please enter a valid number for Account Number")
Return False
End If
If Not Decimal.TryParse(txtStartBalance.Text, Nothing) Then
MessageBox.Show("Please enter a valid Start Balance")
Return False
End If
If Not Decimal.TryParse(txtTransaction.Text, Nothing) Then
MessageBox.Show("Please enter valid Transaction amount")
Return False
End If
Return True
End Function

How to select which object to instantiate without Select Case?

Say I have three classes. A base class Person and two other classes (Employee and Manager) each inherits from Person.
Public Class Person
Public Property Name As String
Public Overridable ReadOnly Property Salary As Decimal
Get
Return 0
End Get
End Property
Public Sub New()
End Sub
End Class
Class Employee:
Public Class Employee
Inherits Person
Overrides ReadOnly Property Salary As Decimal
Get
Return 100
End Get
End Property
Sub New()
MyBase.New()
End Sub
End Class
Class Manager:
Public Class Manager
Inherits Person
Overrides ReadOnly Property Salary As Decimal
Get
Return 1000
End Get
End Property
Sub New()
MyBase.New()
End Sub
End Class
My problem is how to create a new Person(based on a ListBox) that can be either Person/Employeeor Manager and retrieve the Salary Property without going through Select Case or If-else in the ListBox1_SelectedIndexChanged event. To do this selection, I have added another class, named Identify, that takes the selected index of the Listbox and pass it to getProsonType method and return the selected category. Please look at my code below.
The form1 code looks like:
Public Class Form1
Private P As Identify
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles
MyBase.Load
ListBox1.Items.Add("Person")
ListBox1.Items.Add("Employee")
ListBox1.Items.Add("Manager")
End Sub
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As
EventArgs) Handles ListBox1.SelectedIndexChanged
P = New Identify(CType(ListBox1.SelectedIndex, PersonType))
Dim PGeneral As Person
PGeneral = P.GeneralPerson
Label1.Text = PGeneral.Salary
End Sub
End Class
I have assigned the three types to a public Enum PersonType just to restrict the selection.
Public Enum PersonType
Person = 0
Employee = 1
Manager = 2
End Enum
The Identity Class looks like:
Public Class Identify
Public Property PType As PersonType
Public ReadOnly Property GeneralPerson As Person
Get
Return getProsonType(PType)
End Get
End Property
Private Function getProsonType(ByVal SomeOne As PersonType) As Person
Dim pp As Person
Select Case SomeOne
Case PersonType.Person
pp = New Person()
Case PersonType.Employee
pp = New Employee()
Case PersonType.Manager
pp = New Manager()
End Select
Return GeneralPerson
End Function
Sub New(ByVal PersonType As PersonType)
Me.PType = PersonType
End Sub
End Class
After running the project I get the error System.StackOverflowException. I am not sure if this is the cleanest or the correct way to do this and I looked in many places and reached this dead end!
Please help me to correct this or find a better way.
Thanks in advance.

Assign a value to a property depending on initialized Class

I try to assign a value (Test1) to a Property (Wealth) dynamically so that depending on the initialized Class the calculated value is different. But all I get as a result is 0. Could anyone explain me why and how I can solve the problem.
Public Class Class1
Private _test1 As Integer
Overridable ReadOnly Property Test1 As Integer
Get
Return _test1
End Get
End Property
Public ReadOnly Property Wealth As Integer
Get
Dim rnd As New Random
Dim val As Integer = rnd.Next(1, 6)
Return val * _test1
End Get
End Property
End Class
Public Class Class2
Inherits Class1
Public Overrides ReadOnly Property Test1 As Integer
Get
Return 3
End Get
End Property
End Class
Initialisation:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New Class2
MsgBox(t.Wealth.ToString)
End Sub
End Class
Don't use the private variable, you need to reference the property itself.
Public Class Form1
Public Class Class1
Overridable ReadOnly Property Test1 As Integer
Get
Return 0 'Default value'
End Get
End Property
Public ReadOnly Property Wealth As Integer
Get
Dim rnd As New Random
Dim val As Integer = rnd.Next(1, 6)
Return val * Test1 'Changed! Uses the Property name, so that if it is overridden it uses the new version'
End Get
End Property
End Class
Public Class Class2
Inherits Class1
Public Overrides ReadOnly Property Test1 As Integer
Get
Return 3
End Get
End Property
End Class
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New Class2
MsgBox(t.Wealth.ToString)
End Sub
End Class
Sounds like you need a constructor.
Public Class Class1
Public Sub New(int As Integer)
Me.test1 = int
End sub
...
Then when you declare it
Dim t As New Class1(5)
MsgBox(t.Wealth.ToString)

Databinding Not updating

I have been trying to get a label to databind to a readonly property. I have a much more complex project which I am implementing this in and it isn't working. I have been unsuccessful in getting help with this so I have created a much simpler version of the project and my databinding still isn't updating.
To replicate my issue you will need a form with a textbox, label and button, and then a class.
The code for the class is as follows
Imports System.ComponentModel
Public Class databinding
Implements INotifyPropertyChanged
Public Sub New()
numbers = New List(Of number)
End Sub
Public Property numbers As List(Of number)
Get
Return m_number
End Get
Set(value As List(Of number))
m_number = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End Set
End Property
Private m_number As List(Of number)
Public ReadOnly Property hnumber As Integer
Get
Dim list As IList(Of number) = (From t As number In numbers Select t Order By t.value Descending).ToList()
If (list.Count > 0) Then
If (IsNothing(list(0).value)) Then
Return "0"
Else
Return list(0).value
End If
End If
Return "0"
End Get
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
End Class
Public Class number
Public Property value As Integer
Get
Return t_number
End Get
Set(value As Integer)
t_number = value
End Set
End Property
Private t_number As Integer
End Class
The code for the form is as follows:
Public Class Form1
Public numberlist As New databinding
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.DataBindings.Add(New Binding("text", numberlist, "hnumber"))
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim newnum As New number
newnum.value = TextBox1.Text
numberlist.numbers.Add(newnum)
End Sub
End Class
Now based on my understanding when you click the button a number from the textbox is added to this list, which happens, and the hnumber value updates, which using a breakpoint and a watch I can see also happens. From reading I need to implement inotifypropertychanged when I set the new number to get the label to re check the databind (which has been done).
However the label will stay at 0. If I run watch through Label1 I can see that under DataBindings > List > arrayList > (0) > System.Windows.Forms.Binding>DataSource>Databinding_test.databinding the details of the class (including the correct value for hnumber) is listed, so to me that shows that the Label does in fact know about the value it should be binding to.
Could someone please fill me in on what I am missing to make this all work, as it is almost causing me to pull out all of my hair.
Thanks,
mtg
I've tried to explain this to you before, and I will again.
The reason why your binding isn't updated is because you're adding the value to a list.
numberlist.numbers.Add(newnum)
However, if you "change" the list, this will trigger the propertychanged event.
numberlist.numbers.Add(newnum)
numberlist.numbers = numberlist.numbers '<--
Instead of using an IList<T> you should use the ObservableCollection<T> which allows you to track the changes made.
Public Class databinding
Implements INotifyPropertyChanged
Public Sub New()
Me.numbers = New ObservableCollection(Of number)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property numbers As ObservableCollection(Of number)
Get
Return m_number
End Get
Set(value As ObservableCollection(Of number))
If (Not m_number Is value) Then
Unhook(m_number)
Hook(value)
m_number = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End If
End Set
End Property
Public ReadOnly Property hnumber As Integer
Get
If (Not numbers Is Nothing) Then
Dim list As IList(Of number) = (From t As number In numbers Select t Order By t.value Descending).ToList()
If (list.Count > 0) Then
If (IsNothing(list(0).value)) Then
Return 0
Else
Return list(0).value
End If
End If
End If
Return 0
End Get
End Property
Private Sub Hook(collection As ObservableCollection(Of number))
If (Not collection Is Nothing) Then
AddHandler collection.CollectionChanged, AddressOf Me.OnNumbersChanged
End If
End Sub
Private Sub OnNumbersChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End Sub
Private Sub Unhook(collection As ObservableCollection(Of number))
If (Not collection Is Nothing) Then
RemoveHandler collection.CollectionChanged, AddressOf Me.OnNumbersChanged
End If
End Sub
Private m_number As ObservableCollection(Of number)
End Class

Binding Combobox to Object Datasource

I've got a form bound to an object datasource. It has one text box and one combo box. I set up one binding source for the main object and one binding source for the combo box. When I run the form, the text box is bound correctly, and the list of values in the combo box is bound correctly, but the ValueMember of the combo box isn't working correctly.
The combo box shows the correct list, but it's selected index is 0 instead of what it should be 2. When I change the value in the text box, it's bound object's Property.Set method is called correctly, but the same Property.Set method is not called for the combo box.
I know I can hack up the OnSelectedIndex change methods in the form, but I would like to know what I am doing wrong in just using the Bindings.
Here is the code on the form:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Dim NameValueBindingSource1 As New BindingSource()
Dim WorkOrderBindingSource1 As New BindingSource
'Create main object to bind to
Dim wo As New WorkOrder
wo.WOIndex = "2012-0111"
wo.WorkOrderType = 3
'Create list object for combo box
Dim NameValues As BindingList(Of NameValue)
NameValues = FillNameValueList()
'Bind Text Box to Binding Source
WorkOrderBindingSource1.DataSource = wo
WOIndexTextBox1.DataBindings.Add("Text", WorkOrderBindingSource1, "WOIndex")
'Bind Combo Box to Binding Source
NameValueBindingSource1.DataSource = NameValues
WorkOrderTypeCombo.DataSource = NameValueBindingSource1
WorkOrderTypeCombo.DisplayMember = "Value"
WorkOrderTypeCombo.ValueMember = "Code"
End Sub
Function FillNameValueList() As BindingList(Of NameValue)
Dim bl As New BindingList(Of NameValue)
Dim nv As NameValue
nv = New NameValue
bl.Add(New NameValue("Short", 0))
bl.Add(New NameValue("Middle", 1))
bl.Add(New NameValue("Long", 2))
bl.Add(New NameValue("Very Long", 3))
Return bl
End Function
End Class
Here's the code for the main object - "WorkOrder"
Imports System.ComponentModel
Public Class WorkOrder
Implements IEditableObject
Implements INotifyPropertyChanged
Private mWOIndex As String
Private mWorkOrderType As Integer
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Property WOIndex As String
Get
Return mWOIndex
End Get
Set(value As String)
mWOIndex = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("WOIndex"))
End Set
End Property
Public Property WorkOrderType As Integer
Get
Return mWorkOrderType
End Get
Set(value As Integer)
mWorkOrderType = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("WorkOrderType"))
End Set
End Property
Public Sub BeginEdit() Implements System.ComponentModel.IEditableObject.BeginEdit
End Sub
Public Sub CancelEdit() Implements System.ComponentModel.IEditableObject.CancelEdit
End Sub
Public Sub EndEdit() Implements System.ComponentModel.IEditableObject.EndEdit
End Sub
End Class
Here's the code for the object used in the combo box
Imports System.ComponentModel
Public Class NameValue
Implements IEditableObject
Implements INotifyPropertyChanged
Private mValue As String
Private mCode As Integer
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Property Code As Integer
Get
Return mCode
End Get
Set(value As Integer)
mCode = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Code"))
End Set
End Property
Public Property Value As String
Get
Return mValue
End Get
Set(value As String)
mValue = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set
End Property
Public Sub BeginEdit() Implements System.ComponentModel.IEditableObject.BeginEdit
End Sub
Public Sub CancelEdit() Implements System.ComponentModel.IEditableObject.CancelEdit
End Sub
Public Sub EndEdit() Implements System.ComponentModel.IEditableObject.EndEdit
End Sub
Public Sub New(InitValue As String, InitCode As Integer)
Value = InitValue
Code = InitCode
End Sub
End Class
In your code, you are merely assigning the DataSource to the ComboBox, but you're not establishing any DataBinding for it.
You need a line like this (using C# here):
WorkOrderTypeCombo.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", WorkOrderBindingSource1, "WorkOrderType", true));
Hope this helps