Tuesday, April 29, 2008

How to Display Row Icon in DataGridView

Level: Intermediate
Knowledge Required:
  • DataGridView Control
  • Data Bindings
  • DataGridView Columns

Description:

We usually use DataGridView control to display contents of a DataTable. For a good impression and User Friendly Interface, we display an Icon with each Row in the DataGridView. For Example we can have a DataTable to store Tasks. Each task can have Status = Done / NOT Done. So we want that when we display these tasks in the DataGridView control there should be an Icon representing the Status of Task. As shown in the following figure.


DataGridView Control with Row Status Icon

To get the above result we will add an Unbound Column in the DataGridView control of Type DataGridViewImageColumn and set the VirtualMode property of DataGridView control to True. This will cause the DataGridView to fire the CellValueNeeded event. This Event triggers whenever the Cell is going to be rendered. Note that only the Unbound Cell will cause this event to be triggered. The bound columns will render themselve automatically.

For this purpose we first have created a Typed DataSet i.e. TaskDataSet containing a DataTable Task as,

Task DataTable

In the above table Task_Status is an Int32 field. We have decided that when this field is 0 (zero) then it means task is NOT Done yet and if this field contains 1 then it means task is Done.

Next we will create a Form and will put the following things:

  • TaskDataSet
  • BindingSource
  • DataGridView Control

Then we will bind the DataGridView to the BindingSource.

After this we will setup our DataGridView control by removing the Columns: Task_ID and Task_Status then we will add an Unbound Column TaskStatusIconColumn of type DataGridViewImageColumn. This column should be the first column of DataGridView set its properties as,

Properties of TaskStatusIconColumn:

  • DefaultCellStyle:
    • BackColor = White
    • ForeColor = Black
    • SelectionBackColor = White
    • SelectedForeColor = Black
  • Resizable = False
  • Width = 32

Now set the properties of Task_Description column which should have the Name TaskDescriptionDataGridViewTextBoxColumn.

Properties of TaskDescriptionDataGridViewTextBoxColumn:

  • AutoSizeMode = Fill

Properties of DataGridView Control:

  • RowHeadersVisible = False
  • SelectionMode = FullRowSelect
  • VirtualMode = True

VirtualMode is an important property here which must be True otherwise the CellValueNeeded event will NOT be triggered. And finally in the CellValueNeeded Event Handler we will use the following code:

Private Sub TaskDataGridView_CellValueNeeded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) Handles TaskDataGridView.CellValueNeeded
    If e.RowIndex >= 0 AndAlso e.ColumnIndex = Me.TaskStatusIconColumn.Index Then
        Dim drvTask As DataRowView
        Dim rowTask As TaskDataSet.TaskRow
        drvTask = Me.TaskDataGridView.Rows(e.RowIndex).DataBoundItem
        rowTask = CType(drvTask.Row, TaskDataSet.TaskRow)
        Select Case rowTask.Task_Status
            Case 0 ' NOT Done
                e.Value = My.Resources.Resources.Blank16
            Case 1 ' Done
                e.Value = My.Resources.Resources.OK
        End Select
    End If
End Sub

In above code I have used a technique to get the DataRow of DataTable from DataGridView's Row. I have discussed this technique in my previous post How to Get the Table Row from DataGridView Row

Note that I have created 2 PNG images (Blank16 and OK) and have added them in my Resources. Blank16 is a Blank PNG if we dont use this, then DataGridView will render its default image i.e. a Red Cross

You can download the source from here:

DataGridViewRowStatusIcon.rar

Friday, April 25, 2008

Using Data Across Multiple Windows Forms with OK and Cancel Feature

Issue: Cannot add OK and Cancel button on other forms if DataSet Contains multiple child tables, other forms change the data directly from main DataSet
Level: Advanced
Knowledge Required:

  • Typed DataSet
  • Data Bindings


Description:

Before you continue with this article I recommend to see the Beth Massi’s article because it will clear the basic idea. My article covers a little advanced topic.

As we all have used a Typed DataSet (with multiple related tables) on a single form for Data Entry Process. Which is easy to create using Visual Studio 2005.

Sometimes we require to break this data entry process into multiple Forms but the Typed DataSet should remain same. For example consider the following DataSet,

Company DataSet


Now we can create a single form containing controls that can manipulate all three tables. But the form will look like a mess. So we can split the data into multiple forms as I have created 2 forms

Company Form

Address Form



As you can see the Address form will open when User clicks the New Address or Edit Address Buttons. The logic will be very simple for sharing the Data i.e. we will pass the CompanyDataSet in the Public Constructor (New Method) of Address Form and the Address Form will use that DataSet for editing. But if we directly use the Passed DataSet in the Address Form then changes in this form will cause directly changes in the DataSet since its reference is passed. For Example:

In Address Form we replace the binding source’s DataSurce in the New Method as,

Public Sub New(ByVal Address_ID As Integer, ByRef CompDS As CompanyDataSet)
Me.InitializeComponent()
Me.AddressBindingSource.DataSource = CompDS
Me.AddressBindingSource.Filter = "Address_ID=" & Address_ID
End Sub


Now changes in this form will directly change the Main Company DataSet and the buttons we have placed at the bottom of this form i.e. OK, Cancel and Apply will have no use. So what we do here is a simple technique:

We will make a copy of the DataSet and make changes in that copy and on pressing the OK or Apply buttons we will copy the changes on the main DataSet.

Technically speaking:
1) Create Private Save Changes method in Parent form which accepts a Parameter CompanyDataSet, this method will discard its own CompanyDataSet and copy all the things from this given DataSet
2) Create a Public Delegate in Child Form for saving the changes in DataSet
3) Pass this save changes method (created on Parent Form) to the Child Form so the Child Form call this save method by passing its own CompanyDataSet


Child Form (Address Form):
We will use the following code form Child Form

' this form will execute this method to save changes
Public Delegate Sub SaveChangesDelegate(ByRef OtherDataSet As CompanyDataSet)
' declaration of Delegate
Private _SubSaveChanges As SaveChangesDelegate

Public Sub New(ByVal Address_ID As Integer, ByRef CompDS As CompanyDataSet, ByRef SubSaveChanges As SaveChangesDelegate)
Me.InitializeComponent()
' coying data from main dataset
Me.CompanyDataSet.Merge(CompDS)
Me._SubSaveChanges = SubSaveChanges
End Sub

Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOK.Click
' call the save changes method
Me._SubSaveChanges(Me.CompanyDataSet)
Me.Close()
End Sub

Private Sub btnApply_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnApply.Click
' just save changes do not close form
Me._SubSaveChanges(Me.CompanyDataSet)
End Sub

Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
Me.Close()
End Sub

Parent Form:
Following is the code for Parent Form

Private Sub btnEditAddress_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEditAddress.Click
Dim drvCurrentAddress As DataRowView
' get the current selected address
drvCurrentAddress = Me.AddressBindingSource.Current
' if NO Address is selected
If drvCurrentAddress Is Nothing Then
MsgBox("Please select an Address first", MsgBoxStyle.Exclamation, "Edit Address")
Else ' else (address is selected)
' display that address
Dim frmNew As frmAddress
frmNew = New frmAddress(drvCurrentAddress("Address_ID"), Me.CompanyDataSet, AddressOf SaveChanges)
frmNew.ShowDialog()
End If
End Sub

Public Sub SaveChanges(ByRef OtherDataSet As CompanyDataSet)
Me.CompanyDataSet.Clear()
Me.CompanyDataSet.Merge(OtherDataSet)
End Sub


Updated Log:
24-May-2008:
DataSet.Merge() method only merges the rows i.e. new rows added and old rows updated but this method does NOT remove the Rows. E.g. DS1.Merge(DS2) will NOT Delete the rows from DS1 (rows that are DELETED from DS2). Therefore the above article was NOT working for the Deleted Rows.
26-May-2008:
Changing the reference of DataSet affects the BindingSource. Since BindingSource is bound to the previous DataSet and executing Me.DataSet = OtherDataSet causes Me.DataSet to change its reference to OtherDataSet and our BindingSource still bound with the previous Reference. To overcome this issue I have changed the Logic the code in above SaveChanges() method, i.e. first clear the DataSet and then merge with the OtherDataSet.

Wednesday, April 23, 2008

DataGridView Custom Percentage/Progress Bar Column


Description:
This is a smart as well as simple Custom Progress Bar Column for DataGridView Control, which is used to display the Percentage Graphically.

Here is the Source

DataGridViewPercentageColumn.zip

Saturday, April 19, 2008

How To Get the Table Row from DataGridView Row

Level: Beginner
Knowledge Required: To understand the following solution you must have knowledge of:
  • Typed DataSets
  • Data Tables
  • DataGridView Control
  • Data Binding
  • Windows Forms
Description:
We use DataGridView Control to display the contents of a DataTable. For this purpose we first bind the DataTable with DataGridView Control using a BindingSource. Sometimes it is required that we need to get the DataTable's Row from DataGridView Row.

For example, we have an event in DataGridView i.e. CellDoubleClick event. This event is triggered whenever the DataGridView Cell is double clicked. In this event we are supplied with an Event Argument of type DataGridViewCellEventArgs which contains 2 Members RowIndex and ColumnIndex.

Suppose we want that whenever the Cell is double clicked in the DataGridView, we extract out that particular row of our DataTable and then perform some action.

To fully understand this consider a Student Table having Student ID, Student Name and lots of other different fields. We have bound this Table with our DataGridView control and in DataGridView control we are only displaying the Student Name. Whenever the Student Name is double clicked we want that another Window should open i.e. Student Detail Window that contains all the Fields of Table. For this purpose we will use the following code:

Private Sub StudentDataGridView_CellDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles StudentDataGridView.CellDoubleClick

    Dim drvStudent As DataRowView
    Dim rowStudent As StudentDataSet.StudentRow
    Dim frmNew As StudentDetailForm

    drvStudent = StudentDataGridView.Rows(e.RowIndex).DataBoundItem
    rowStudent = drvStudent.Row

    frmNew = New StudentDetailForm(rowStudent.Student_ID)
    frmNew.ShowDialog()

End Sub

As you can see following code gets the DataRowView which is actually bound with DataGridView Row

drvStudent = StudentDataGridView.Rows(e.RowIndex).DataBoundItem

Then we get the actual DataTable Row as,

drvStudent.Row

Friday, April 18, 2008

How To Trim all Items in String Array

Issue: String Array Items do NOT Trim Using For Each Loop
Level: Beginner

Knowledge Required:
  • For Each Loop
  • For Next Loop
  • Arrays

Description:
It is observed that when we use For Each Loop to iterate through a String Array and perform some action with each item then this change does NOT affect the String Array itself. Consider the following code

Dim strArray() As String = {"First Item", "Second Item", "Third Item"}

' First we are trying to add some more
' text with each item in array
For Each strItem As String In strArray
    strItem &= " some data"
Next

' Now again iterate through this array to see
' whether the changes has been made or not
For Each strItem As String In strArray
    Debug.Print(strItem)
Next

The output of above code will be

First Item
Second Item
Third Item


As you can see the output, there is NO change in items since each time value is copied into the variable and that variable was changed NOT the Array item. So to make changes in each item we will be using the For Next loop as,

Dim strArray() As String = {"First Item", "Second Item", "Third Item"}

' First we are trying to add some more ' text with each item in array
For i As Integer = 0 To strArray.Length - 1
    strArray(i) &= " some data"
Next

' Now again iterate through this array to see
' whether the changes has been made or not
For Each strItem As String In strArray
    Debug.Print(strItem)
Next


The output of above code will be

First Item some data
Second Item some data
Third Item some data

Tuesday, April 15, 2008

Blog Title Changed

I have changed my Blog Title from Arsalan Tamiz to Visual Basic Development Issues.

CheckBox CheckedChanged Event Recursion

Issue: CheckBox CheckedChanged Event Recursively Called automatically, System.StackOverflowException exception occurs
Level: Intermediate
Knowledge Required: To understand the following solution you must have the knowledge of:

  • CheckBox Control
  • Recursion
  • Add Event Handler (AddHandler)
  • Remove Event Handler (RemoveHandler)

Description:

Sometimes it is observed that whenever we try to change the Checked property of CheckBox within the CheckedChanged Event Control it automatically fires the same event again in such a manner that this firing never stops. For example:


Private Sub CheckBox1_CheckedChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles CheckBox1.CheckedChanged

    CheckBox1.Checked = Not CheckBox1.Checked

End Sub

In the above example when user clicks on CheckBox1 for the first time then this event will be triggered, so in this event we are toggling the current value i.e. if first time the Checked Property was False then on Click this value is turned to True and then in the same event we are converting its value to False which will again trigger this event and then this event is again converts the value to True and this process goes on until the System.StackOverflowException exception occurs.

The process,

1) FirstTime
Checked = False
2) User Clicked on Check Box
Checked = True (Event triggered)
3) in event Checked = NOT Checked (NOT True = False)
Checked = False (Event triggered)
4) in event Checked = NOT Checked (NOT False = True)
Checked = True (Event triggered)
...
...
...

You may think why we will do this strange thing in the CheckedChanged Event, nobody ever wants to change the Checked property by himself in the CheckedChanged Event, since it is changing by clicking.
But this scenario is a real life example, we can have the situation where we dont want the user to check the checkbox until some other things are NOT full filled OR we can also have another scenario where we want the checkbox to be read only. This can also be accomplished by setting the CheckBox Enable Property to False but this will also Darken our control which we dont want to be happened. What we want is just that when user clicks on checkbox nothing should happen neither its checked is unchecked nor unchecked is checked.

To understand it more accurately, consider a scenario where we have 4 check boxes user can check any 2 of them NOT four of them,



In this scenario we can create a function CanCheckBoxSelect(byref CheckboxToCheck as CheckBox). This function will return True if CheckBox can be checked and False if it cannot (NOTE that I am NOT considering the inside logic of this function because this is NOT our topic) therefore we will add the Coding in CheckedChanged Event as
Private Sub CheckBox_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
    Dim chkClicked As CheckBox
    chkClicked = CType(sender, CheckBox) ' Get the Check Box which is Clicked
    ' If this CheckBox cannot be Checked
    If NOT CanCheckBoxSelect(chkClicked) Then
        ' first remove the event handler because we are going to change the Checked Property
        RemoveHandler chkClicked.CheckedChanged, AddressOf CheckBox_CheckedChanged
        ' now change the Checked Property and this will NOT again trigger this Event
chkClicked.Checked = Not chkClicked.Checked
        ' now again add the CheckedChanged event handler
        AddHandler chkClicked.CheckedChanged, AddressOf CheckBox_CheckedChanged
    End If
End Sub
In the above code we can understand the solution that
  1. We have first simply removed this event handler and then
  2. Again added the handler
By removing the Event Handler, this will prevent this event to be triggered again when we are changing the Checked Property manually.

Friday, April 11, 2008

How To Programmatically Select the DataGrid Row and/or Cell

Title: How To Programmatically Select the DataGrid Row and/or Cell
Issue: Cannot highlight/select the DataGrid Row and/or Cell through Code
Level: Beginner
Knowledge Required:
To understand the following solution you must have the knowledge of:


  • DataGrid View Control

Description:
To select the Particular row and/or cell of DataGrid View Control there are several techniques. Firstly you need to decide whether user can select more than one rows or cells. For this purpose you can use MultiSelect Property of DataGrid Control:

MultiSelect is a Boolean Property that can have values:
True = Enable user to select multiple rows and/or cells
False = Disable user to select only one row and/or cell at a time

To select particular Cell:
Tech #1:

myDataGrid.CurrentCell = myDataGrid(col, row)

where
col = Column Index
row = Row Index

Tech #2:
myDataGrid.Rows(row).Cells(col).Selected = True

To select particular Row:
myDataGrid.Rows(row).Selected = True

To clear previously selected Cells:
myDataGrid.ClearSelection