VB WindowsForm Custom Class Event Handler Issue - vb.net

I created a custom class (DataGroupBoxControl) that is basically a GroupBox with one or more Panels inside.
Each Panel holds two Labels side by side as pictured below.
enter image description here
The object allows a DataTable to be passed into it, which is what determines how many rows of Panels are created and displayed.
I would like the user to be able to click on one of the Panel Labels and do 'something'.
Here is the procedure inside the class that creates the Labels.
Public Class DataGroupBoxControl
Inherits GroupBox
Public DataLabels As New List(Of DataLabelControl)
Public Sub BindData(ByVal ds As DataTable)
Dim colItem As Integer = 0
For Each col As DataColumn In ds.Columns
DataLabels.Add(New DataLabelControl)
For Each row As DataRow In ds.Rows
DataLabels(colItem).TitleLabel.Text = col.ColumnName & " " & _Separator
DataLabels(colItem).TitleLabel.Name = col.ColumnName
If IsNumeric(row.Item(colItem)) Then
If row.Item(colItem).ToString.IndexOf(".") <> -1 Then
DataLabels(colItem).ValueLabel.Text = Decimal.Parse(row.Item(colItem)).ToString("c")
Else
DataLabels(colItem).ValueLabel.Text = row.Item(colItem)
End If
End If
DataLabels(colItem).ValueLabel.Name = row.Item(colItem)
DataLabels(colItem).Top = (colItem * DataLabels(colItem).Height) + 20
DataLabels(colItem).Left = 5
Me.Controls.Add(DataLabels(colItem))
Me.Height = Me.Height + DataLabels(colItem).Height
DataLabels(colItem).Show()
Next
colItem += 1
Next
End Sub
Where and how do I create a Label Click Event Handler?
And then access that event from my main form with the following object:
Public AccountsGroupBox As New DataGroupBoxControl

Before creating DataLabelControl use AddHandler to attach event handler eg. to TitleLabel control, then you can listen for events and raise new event that can be handled in parent control.
Public Class SomeForm
Private WithEvents AccountsGroupBox As New DataGroupBoxControl
Private Sub AccountsGroupBox_ItemClick(
sender As Object,
args As ItemClickEventArgs) Handles AccountsGroupBox.ItemClick
End Sub
End Class
Public Class ItemClickEventArgs
Inherits EventArgs
Public Property Control As Object
End Class
Public Class DataGroupBoxControl
Inherits GroupBox
Public Event ItemClick(sender As Object, args As ItemClickEventArgs)
Public DataLabels As New List(Of DataLabelControl)
Private Sub OnLabelClick(sender As Object, args As EventArgs)
RaiseEvent ItemClick(Me, New ItemClickEventArgs With {.Control = object})
End Sub
Public Sub BindData(ByVal ds As DataTable)
For Each col As DataColumn In ds.Columns
Dim control = New DataLabelControl
AddHandler control.TitleLabel.Click, AddressOf OnLabelClick
DataLabels.Add(control)
' ...
Next
End Sub
End Class

Related

add button to TreeNode

Good day.
There is a custom control that adds a button to each Node
Imports System.Windows.Forms.VisualStyles
Public Class CustomTreeView
Inherits TreeView
Private buttonRect As New Rectangle(80, 2, 50, 26)
Private ReadOnly stringFormat As StringFormat
Public Sub New()
SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
DrawMode = TreeViewDrawMode.OwnerDrawText
ShowLines = False
FullRowSelect = True
ItemHeight = 30
stringFormat = New StringFormat With {
.Alignment = StringAlignment.Near,
.LineAlignment = StringAlignment.Center
}
End Sub
Protected Overrides Sub OnDrawNode(ByVal e As DrawTreeNodeEventArgs)
e.Graphics.DrawString(e.Node.Text, Me.Font, New SolidBrush(Me.ForeColor), e.Bounds, stringFormat)
ButtonRenderer.DrawButton(e.Graphics, New Rectangle(e.Node.Bounds.Location + New Size(buttonRect.Location), buttonRect.Size), "btn", Me.Font, True, If(e.Node.Tag IsNot Nothing, CType(e.Node.Tag, PushButtonState), PushButtonState.Normal))
End Sub
Protected Overrides Sub OnNodeMouseClick(ByVal e As TreeNodeMouseClickEventArgs)
Select Case e.Node.Tag
Case Nothing, Is <> PushButtonState.Pressed
Return
End Select
e.Node.Tag = PushButtonState.Normal
MessageBox.Show(e.Node.Text & " clicked")
' force redraw
e.Node.Text = e.Node.Text
End Sub
Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
Dim tnode As TreeNode = GetNodeAt(e.Location)
If tnode Is Nothing Then
Return
End If
Dim btnRectAbsolute As New Rectangle(tnode.Bounds.Location + New Size(buttonRect.Location), buttonRect.Size)
If btnRectAbsolute.Contains(e.Location) Then
tnode.Tag = PushButtonState.Pressed
tnode.Text = tnode.Text
End If
End Sub
End Class
tell me how you can display the button only to the first (main) nod?
And how, when you click on this button, not display a message, but let's say call some procedure?
There is no built-in button tree node. But you can create a custom tree node having a button yourself. This custom tree node inherits from TreeNode. To improve extensibility, we declare an interface for tree nodes having a DrawNode method:
Imports System.Windows.Forms.VisualStyles
Public Interface ICustomDrawTreeNode
Sub DrawNode(ByVal e As DrawTreeNodeEventArgs, buttonState As PushButtonState)
End Interface
We also create a module containing some settings used in the custom tree view and in the custom tree node
Module Settings
Public ReadOnly ButtonRect As New Rectangle(80, 2, 50, 26)
Public ReadOnly TextStringFormat = New StringFormat() With {
.Alignment = StringAlignment.Near,
.LineAlignment = StringAlignment.Center,
.FormatFlags = StringFormatFlags.NoClip Or StringFormatFlags.FitBlackBox Or StringFormatFlags.LineLimit
}
End Module
We can then implement a button node like this
Imports System.Windows.Forms.VisualStyles
Public Class ButtonTreeNode
Inherits TreeNode
Implements ICustomDrawTreeNode
Private ReadOnly buttonText As String
Public Sub New(text As String, buttonText As String)
MyBase.New(text)
Me.buttonText = buttonText
End Sub
Public Sub DrawNode(e As DrawTreeNodeEventArgs, buttonState As PushButtonState) _
Implements ICustomDrawTreeNode.DrawNode
Dim font As Font = e.Node.TreeView.Font
' Draw Text to the left of the button
Dim rect As Rectangle = New Rectangle(
e.Node.Bounds.Location,
New Size(Settings.ButtonRect.Left, e.Bounds.Height))
e.Graphics.DrawString(e.Node.Text, font, Brushes.Black, rect, Settings.TextStringFormat)
' Draw the button
rect = New Rectangle(
e.Node.Bounds.Location + Settings.ButtonRect.Location,
Settings.ButtonRect.Size)
ButtonRenderer.DrawButton(e.Graphics, rect, buttonText, font, True, buttonState)
End Sub
End Class
It has a Private ReadOnly buttonText As String to store the text of the button. The normal node text and the button text are passed in the constructor of ButtonTreeNode:
Public Sub New(text As String, buttonText As String)
The DrawNode method will be called be the CustomTreeView in OnDrawNode.
In CustomTreeView I declared a NodeButtonClick event that will be raised when the button of a node is clicked. You can then handle this event in the form. When you select the CustomTreeView in the designer, this new event will appear in the "Action" section of the events.
Imports System.ComponentModel
Imports System.Windows.Forms.VisualStyles
Public Class CustomTreeView
Inherits TreeView
<Category("Action")>
Public Event NodeButtonClick(e As TreeNodeMouseClickEventArgs)
Private _isButtonPressed As Boolean
Public Sub New()
SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
DrawMode = TreeViewDrawMode.OwnerDrawText
ShowLines = False
FullRowSelect = True
ItemHeight = 30
End Sub
Protected Overrides Sub OnDrawNode(e As DrawTreeNodeEventArgs)
Dim customDrawNode As ICustomDrawTreeNode = TryCast(e.Node, ICustomDrawTreeNode)
If customDrawNode Is Nothing Then ' Normal text node.
e.Graphics.DrawString(e.Node.Text, Font, Brushes.Black, e.Node.Bounds, Settings.TextStringFormat)
Else
customDrawNode.DrawNode(e, If(_isButtonPressed, PushButtonState.Pressed, PushButtonState.Normal))
End If
End Sub
Protected Overrides Sub OnNodeMouseClick(e As TreeNodeMouseClickEventArgs)
If _isButtonPressed Then
_isButtonPressed = False
Refresh()
Dim buttonNode = TryCast(e.Node, ButtonTreeNode)
If buttonNode IsNot Nothing Then
RaiseEvent NodeButtonClick(e)
End If
End If
End Sub
Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
Dim buttonNode = TryCast(GetNodeAt(e.Location), ButtonTreeNode)
If buttonNode IsNot Nothing Then
Dim btnRectAbsolute As New Rectangle(
buttonNode.Bounds.Location + Settings.ButtonRect.Location,
Settings.ButtonRect.Size)
_isButtonPressed = btnRectAbsolute.Contains(e.Location)
If _isButtonPressed Then
Refresh()
End If
End If
End Sub
End Class
In the form you can write
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TreeView1.Nodes.Add("Text")
TreeView1.Nodes.Add(New ButtonTreeNode("Caption", "Button"))
End Sub
Private Sub TreeView1_NodeButtonClick(e As TreeNodeMouseClickEventArgs) _
Handles TreeView1.NodeButtonClick
MessageBox.Show(e.Node.Text & " clicked")
End Sub
End Class
This adds a normal text node and a custom button node to the TreeView. It also handles the NodeButtonClick of the custom TreeView.

Updating the text on a dynamically generated control In VB

I have a dynamically generated TabControl, and am trying to update a Combobox in the TabPage. The function that updates the Combobox is called of a click event.
I've tried to follow some guides regarding manipulating dynamically generated controls by storing the dynamically generated controls as properties on the class: How to pass value from one form to another form's dynamically created control
The controls are generated dynamically as such:
Public Class Form1
Public Sub loadForm()
Dim ctp As New CustomTabPage("Tab number" & i, ErrorList(i), myList, New Object)
Me.myTabControl.TabPages.Add(ctp)
...
End Sub
End Class
Public Class CustomTabPage
Inherits TabPage
Private m_testSelect As ComboBox
...
Public Property testSelect() As ComboBox
Get
testSelect = m_testSelect
End Get
Set(ByVal mytestSelect As ComboBox)
m_testSelect = mytestSelect
End Set
End Property
Public Sub newTab()
m_testSelect = New ComboBox
With m_testSelect
.Location = New System.Drawing.Point(locX + labelSize, locY)
End With
Me.Controls.Add(m_testSelect)
Dim ccb As New CustomCheckBox()
Me.Controls.Add(m_testSelect)
End Sub
Public Sub UpdateCBOs(ByVal i As Integer)
If i = 1 Then
testSelect.Text = "Test1"
ElseIf i = 0 Then
testSelect.Text = "Test2"
End If
...
End Sub
End Class
Public Class CustomCheckBox
Inherits CheckBox
Public Sub Clicked(sender As Object, e As EventArgs) Handles MyBase.CheckedChanged
Dim ctp = CType(Form1.myTabControl.SelectedTab, CustomTabPage)
ctp.UpdateCBOs(i)
End Sub
End Class
Currently while debugging through I stop on the line after line
errorBy.Text = "Test1"
When I mouse over errorBy.Text, and see that errorBy.Text ="" and indeed after the click event finishes, I see on the form that the combobox is not updated.

Getting Event Handler from Instance of Class

I'm just getting started with custom classes so I wrote a button strip. It inherits a panel and populates it with buttons from strings passed to my .Add().
ButtonStrip1.Add("Button","Texts","Go Here")
I'd like for it to be able to naturally grab ButtonStrip1.Click's handler and pass it on to child buttons and delete it from ButtonStrip1.
I haven't been able to figure that out, so I've been doing
Public Class ButtonStrip
Inherits Panel
Public Property Innermargin As Integer = 5
Dim Offset As Integer = Innermargin
Dim Buttons = New List(Of ButtonStrip_Button)
Dim StoredFN As EventHandler
Public Sub New()
End Sub
Function Add(fn As EventHandler, ParamArray ByVal Values As String())
StoredFN = fn
For Each V In Values
Dim B As New ButtonStrip_Button
Buttons.Add(B)
Me.Controls.Add(B)
B.Text = V
B.Left = Offset + Innermargin
B.Top = Innermargin
Offset = B.Left + B.Width
AddHandler B.Click, fn
Next
RemoveHandler Me.Click, fn
Me.Width = Offset + Innermargin
Me.Height = Buttons(0).height + Innermargin * 2
End Function
Function Add(ParamArray ByVal Values As String())
If StoredFN Is Nothing Then
Throw New Exception("First Add() must supply a function")
End If
Me.Add(StoredFN, Values)
End Function
End Class
Public Class ButtonStrip_Button
Inherits System.Windows.Forms.Button
Public Sub New()
AutoSize = True
AutoSizeMode = AutoSizeMode.GrowAndShrink
End Sub
End Class
which is called by
ButtonStrip1.Add(AddressOf ButtonStrip1_Click,"Button","Texts","Go Here")
What I'd basically like to do is (psuedo-code)
Function Add(fn As EventHandler, ParamArray ByVal Values As String())
If StoredFN is Nothing Then StoredFN = Me.Click
...
AddHandler B.Click, Me.Click
Next
RemoveHandler Me.Click, Me.Click
...
End Function
I've tried changing a few things and googled a lot. I've also tried using CallByName(Me,"Click",CallType.Method) and with CallType.Get, but the error I get is Expression 'Click' is not a procedure, but occurs as the target of a procedure call. It also returns this exact same message for unhandled events, such as ButtonStrip1 has no MouseDown Event.
I've also tried using MyClass.
Not seen here is an alternative .Add() that add StoredFN to B.click
For instance, this click event works with my code
Private Sub ButtonStrip1_Click(sender As Object, e As EventArgs) Handles ButtonStrip1.Click
msgbox("You clicked " & sender.text & ".")
End Sub
What I was suggesting was something like this:
Public Class ButtonStrip
Inherits Panel
Public Event ButtonClick As EventHandler(Of ButtonClickEventArgs)
Protected Overridable Sub OnButtonClick(e As ButtonClickEventArgs)
RaiseEvent ButtonClick(Me, e)
End Sub
Private Sub Buttons_Click(sender As Object, e As EventArgs)
OnButtonClick(New ButtonClickEventArgs(DirectCast(sender, Button)))
End Sub
Public Sub Add(ParamArray values As String())
Dim btn As New Button
AddHandler btn.Click, AddressOf Buttons_Click
'...
End Sub
End Class
Public Class ButtonClickEventArgs
Inherits EventArgs
Public ReadOnly Property Button As Button
Public Sub New(button As Button)
Me.Button = button
End Sub
End Class
Now there's no need to pass event handlers around. When a Button is clicked, the ButtonStrip handles that event and then raises its own ButtonClick event. Neither the ButtonStrip nor the Buttons have to care about any methods that handle that event as it will be handled the same way any other event is. In a form, you'd then handle the ButtonClick event of the ButtonStrip and get the Button that was clicked from e.Button. You could also add an Index property if you wanted to know the position of the Button rather than the Button itself.

Pass an object from form to another in VB

I have searched through the internet and couldn't find the answer to my problem, but, the issue is that I have 2 forms;
frm_bookManeger
and
frm_addBook
The first one is the main form and has a list of books (named listBook), a TreeView and a button to invoke the second form to add a new book.
After filling in all of the TextBoxes and information of a book, I press "Add". Then, the second form will be closed and all info of that book will be kept in an instance of Book class. The problem is: how can I pass this instance to the first form to store it in listBook.
For example:
If I create a constructor in form 1 to get form 2 then in form 2:
Dim f1 As form1 = New form1(me)
f1.Show()
f2.Close()
I can't do it because form 1 will start up instantly when I start program, and the default right now doesn't have any parameter in OnCreateMainForm():
Protected Overrides Sub OnCreateMainForm()
Me.MainForm = Global.WindowsApplication5.frm1
End Sub
How can I do it?
First form:
Public Class frm_bookManeger
'list of Book
Dim listBook As List(Of Book) = New List(Of Book)
Private frm_addBook As frm_addBook
Public Sub New(frm_addBook As frm_addBook) 'got error
Me.frm_addBook = frm_addBook
End Sub
Second form:
Public Class frm_addBook
Dim Public tempBook As Book = New Book()
'add book
Private Sub btn_add_Click(sender As Object, e As EventArgs) Handles btn_add.Click
tempBook.BookName1 = TextBox_name.Text
tempBook.Author1 = TextBox_author.Text
tempBook.Price1 = TextBox_price.Text
tempBook.Genre1 = TextBox_genre.Text
tempBook.EstablishedDay1 = dtp_established.Value.Date
Dim frm_Mngr As frm_bookManeger = New frm_bookManeger(Me)
End Sub
End Class
Dim frm As New form1
frm.textbox.Text = Me.passing value.Text
frm.Show()
or you can try
Public Class Form1
Private loginLabel As String
Public Sub New(ByVal loginParameter As String)
InitializeComponent()
Me.loginLabel = loginParameter
End Sub
End Class
dim frm as new Form1(label.Text)
Your frm_addBook needs a reference to the instance of frm_bookManeger so that it can use methods in the latter.
That can be done by passing a reference to the current instance of frm_bookManeger to the New constructor of frm_addBook.
Also, you probably want the book adding form to be a dialog form rather than an ordinary form.
I made a simple "Book" class and used a TextBox to display the books, so the first form is this:
Imports System.Text
Public Class frm_BookManager
Dim bookList As List(Of Book)
Public Class Book
Property Name As String
Property Author As String
End Class
Public Sub AddBook(b As Book)
If bookList Is Nothing Then
bookList = New List(Of Book)
End If
bookList.Add(b)
End Sub
Private Sub ShowBooks()
Dim sb As New StringBuilder
For Each b In bookList
sb.AppendLine(b.Name & " by " & b.Author)
Next
TextBox1.Text = sb.ToString()
End Sub
Private Sub btn_add_Click(sender As Object, e As EventArgs) Handles btn_add.Click
Using addBook As New frm_addBook(Me)
Dim result = addBook.ShowDialog()
If result = DialogResult.OK Then
ShowBooks()
End If
End Using
End Sub
Private Sub frm_BookManager_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddBook(New Book With {.Name = "Wuthering Heights", .Author = "Emily Brontë"})
ShowBooks()
End Sub
End Class
For the form to add a book, I added "Cancel" and "OK" buttons.
Public Class frm_addBook
Dim myParent As frm_BookManager
Private Sub bnOK_Click(sender As Object, e As EventArgs) Handles bnOK.Click
Dim b As New frm_BookManager.Book With {.Name = TextBox_name.Text, .Author = TextBox_author.Text}
myParent.AddBook(b)
End Sub
Public Sub New(parent As frm_BookManager)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
myParent = parent
' set the DialogResult for each button so the parent can tell what happened
bnCancel.DialogResult = DialogResult.Cancel
bnOK.DialogResult = DialogResult.OK
End Sub
End Class
Notice that a new Book can be added with myParent.AddBook(b) because myParent refers to an instance of frm_BookManager.
You could modify it so that the dialog stays open and has a button to just add a book and not close the dialog. I made the ShowBooks() method Private so you can't call it from outside the class it is in - you could modify that.
There are many possibilities for small modifications to the code I showed to achieve greater functionality. And I could not resist correcting the spelling of "Maneger" to "Manager" ;)
I think the easiest way would be to have the frm_addBook form have a property which will contain the book that was added. In the frm_bookManager form, show that form using ShowDialog and if the user clicks OK on that form, the property will contain the book added. Be sure to dispose the frm_addBook form after you get the book from the public property.
Public Class Book
Public Property Name As String
Public Property Author As String
End Class
Public Class frm_bookManager
Dim bookList As New List(Of Book)()
Private Sub btnAddBook_Click(sender As Object, e As EventArgs) Handles btnAddBook.Click
Using addBookForm As New frm_addBook()
If addBookForm.ShowDialog() = DialogResult.OK Then
bookList.Add(addBookForm.BookToAdd)
End If
End Using
End Sub
End Class
Public Class frm_addBook
Public Property BookToAdd As Book
Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles
'User filled in the fields and clicked this OK button
Me.BookToAdd = New Book()
Me.BookToAdd.Name = txtName.Text
Me.BookToAdd.Author = txtAuthor.Text
End Sub
End Class
I would not pass the main form instance into the add book form because it would create a tight coupling between the two forms and the add book form would only be usable by the main form. You might wish to use the add book form from other forms in the app.

Bind DataSource to new DevExpress Report Designer?

I'm trying to figure out how to set my DataSource as the default when a user clicks New Report, or for any new report, in the DevExpress User Data Report Designer.
Right now, the Blank Report I have load on Form_Load has my DataSources just fine, but anytime I hit New Report, they're gone.
I've googled and followed the docs, but they all seem to be geared towards opening a specific report (as above).
Can anyone help?
0. ICommandHandler interface
You need to handle the ReportCommand.NewReport command by implementing the ICommandHandler interface. You must pass an object that implementing this interface to the XRDesignMdiController.AddCommandHandler method. You can get XRDesignMdiController object from ReportDesignTool.DesignForm.DesignMdiController property or from ReportDesignTool.DesignRibbonForm.DesignMdiController property according to what type of form you want to use.
Here is example:
Private Sub ShowReportDesigner()
Dim tool As New ReportDesignTool(CreateReport)
Dim controller = tool.DesignRibbonForm.DesignMdiController
Dim handler As New NewCommandHandler(controller, AddressOf CreateReport)
controller.AddCommandHandler(handler)
tool.ShowRibbonDesigner()
End Sub
Private Function CreateReport() As XtraReport
Dim report As New XtraReport
report.DataSource = YourDataSourceObjectHere
Return report
End Function
Public Class NewCommandHandler
Implements ICommandHandler
Private ReadOnly _controller As XRDesignMdiController
Private ReadOnly _createReport As Func(Of XtraReport)
Public Sub New(controller As XRDesignMdiController, createReport As Func(Of XtraReport))
_controller = controller
_createReport = createReport
End Sub
Public Function CanHandleCommand(command As ReportCommand, ByRef useNextHandler As Boolean) As Boolean Implements ICommandHandler.CanHandleCommand
useNextHandler = command <> ReportCommand.NewReport
Return Not useNextHandler
End Function
Public Sub HandleCommand(command As ReportCommand, args() As Object) Implements ICommandHandler.HandleCommand
_controller.OpenReport(_createReport())
End Sub
End Class
1. DesignPanelLoaded event
The another way is to subscribe to XRDesignMdiController.DesignPanelLoaded event. In this event you can check where the DataSource of report in loaded panel is empty and set it to your data source.
Here is example:
Private Sub ShowReportDesigner()
Dim report As New XtraReport
report.DataSource = YourDataSourceObjectHere
Dim tool As New ReportDesignTool(New XtraReport)
Dim controller = tool.DesignRibbonForm.DesignMdiController
AddHandler controller.DesignPanelLoaded, AddressOf mdiController_DesignPanelLoaded
tool.ShowRibbonDesigner()
End Sub
Private Sub mdiController_DesignPanelLoaded(ByVal sender As Object, ByVal e As DesignerLoadedEventArgs)
Dim panel = DirectCast(sender, XRDesignPanel)
Dim report = panel.Report
If IsNothing(report.DataSource) Then
report.DataSource = YourDataSourceObjectHere
End If
End Sub