Monday, December 17, 2007

Application Launch Directory

A lot of Windows based applications generally require one or more files that are consumed by the application to be placed in the same folder hierarchy as the application. For example, if I need a couple of XML files, I generally create a folder called XMLData and place the files inside it.

Although accessing these file paths in code is pretty straightforward, there is a small caveat to it. A lot of users use the System.Environment.CurrentDirectory


This works well in most situations. Note how the tooltip states - "the directory from which this process starts".

However, if your application contains any piece of code that can alter the current working directory - say, you use a OpenFileDialog control to pick a file on the file system but do not restore the working directory by setting the RestoreDirectory property, this results in changing the current working directory and the quoted portion of the tooltip no longer holds good.

I would recommend using the Application.StartupPath instead of the System.Environment.CurrentDirectory. The folder path returned by this function is not altered during the course of execution of the application.



Console applications do not have access to the Application object and hence you might need to use the System.Reflection.Assembly.GetExecutingAssembly.Location method to get the full path of the console application. You can then use the IO.Path.GetDirectoryName method to extract the application launch directory.

Sunday, December 16, 2007

Dynamic ComboBox in a DataGridView

And here is the second installment. It is generally convenient to bind the grid column to a single datasource such that all dropdowns have identical items. However, certain applications require that the items in the dropdown depend upon other attributes of the row.

For this example, I will create a three column grid with the first column containing the Employee Name, the second one containing the Department and third one being the dynamic combobox column that lists the positions for the particular department.

Once again, I have created the datasources at runtime to provide a complete example. To add that dynamic feel, I have decided to handle a DataGridView event - CellValueChanged. You can of course set this value after binding the original gridview source as well.

The Setup code that declares two dummy datasources and binds the Employee datasource with the gridview.

    'Create the sample list of names

    Dim dtNames As DataTable = New DataTable

    dtNames.Columns.Add("Name", GetType(String))

    dtNames.Columns.Add("Department", GetType(String))

 

    Dim dr As DataRow

    dr = dtNames.NewRow : dr.Item("Name") = "Alvin Menezes" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

    dr = dtNames.NewRow : dr.Item("Name") = "Saikumar Chettiar" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

    dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 1" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

    dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 2" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

 

    'create the sample department - position choices

    dtChoices = New DataTable

    dtChoices.Columns.Add("Department", GetType(String))

    dtChoices.Columns.Add("Position", GetType(String))

 

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Tech Architect" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Project Manager" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Project Leader" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Developer" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "HR" : dr.Item("Position") = "Manager" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "HR" : dr.Item("Position") = "Executive" : dtChoices.Rows.Add(dr)

 

 

    'Bind the datagridview

    dgvSampleEx.AutoGenerateColumns = False

    dgvSampleEx.DataSource = dtNames



The code listing for the dynamic ComboBox rendering

  Private Sub dgvSampleEx_CellValueChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvSampleEx.CellValueChanged

    If e.RowIndex < 0 Then

      Exit Sub

    End If

 

    If e.ColumnIndex = 1 Then

      Dim Department As String = CStr(dgvSampleEx.Rows(e.RowIndex).Cells(1).Value)

      Dim dv As DataView = New DataView(dtChoices, "Department = '" & Department & "'", "", DataViewRowState.CurrentRows)

 

      Dim dgvComboCell As DataGridViewComboBoxCell = CType(dgvSampleEx.Rows(e.RowIndex).Cells(2), DataGridViewComboBoxCell)

      With dgvComboCell

        .DisplayMember = "Position"

        .ValueMember = "Position"

        .DataSource = dv

      End With

    End If

  End Sub


Guess the trick is to know that binding a DataGridView column sets all dropdown elements to be identical whereas binding the DataGridViewCell gives you more control over the exact elements that need to be displayed in the dropdown choices.

And the results for all to see


Note how the dropdown values differ - depending on the Department that has been entered. Ideally, the Department could also be rendered as a combobox. I decided to go with plain text to keep this example simple.

ComboBox in a DataGridView

It is often useful to add a dropdown box in a DataGridView column. I will list a couple of methods that can be used to achieve this. Of course, your real world applications will differ in certain areas related to the source of data. For the sake of this example, I have created these datasources at runtime.
The first example deals with displaying a two column grid with the first column containing the Employee Name and the second column that can be used to collect the Employee Gender
  1. Drag a DataGridView on the form

  2. Expand the DataGridView tasks and add a couple of columns


  3. Set the DataPropertyName for the first column appropriately, Since my data source deals with Employee Names, I have selected 'Name' which corresponds to the column name in my datasource.


  4. Finally, bind the data source with the grid and another datasource with the second column so that the dropdown choices are rendered correctly.

        'Create the sample list of names

        Dim dtNames As DataTable = New DataTable

        dtNames.Columns.Add("Name", GetType(String))

     

        Dim dr As DataRow

        dr = dtNames.NewRow : dr.Item("Name") = "Alvin Menezes" : dtNames.Rows.Add(dr)

        dr = dtNames.NewRow : dr.Item("Name") = "Saikumar Chettiar" : dtNames.Rows.Add(dr)

        dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 1" : dtNames.Rows.Add(dr)

        dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 2" : dtNames.Rows.Add(dr)

     

        'Create the sample list of dropdown choices

        Dim dtGenders As DataTable = New DataTable

        dtGenders.Columns.Add("Gender", GetType(String))

        dtGenders.Columns.Add("GenderId", GetType(Integer))

     

        dr = dtGenders.NewRow : dr.Item("GenderId") = 1 : dr.Item("Gender") = "Male" : dtGenders.Rows.Add(dr)

        dr = dtGenders.NewRow : dr.Item("GenderId") = 2 : dr.Item("Gender") = "Female" : dtGenders.Rows.Add(dr)

     

        'Bind the datagridview

        dgvSample.AutoGenerateColumns = False

        dgvSample.DataSource = dtNames

     

        'Setup the Combobox column

        Dim dgvComboCol As DataGridViewComboBoxColumn = CType(dgvSample.Columns(1), DataGridViewComboBoxColumn)

        With dgvComboCol

          .DisplayMember = "Gender"

          .ValueMember = "GenderId"

          .DataSource = dtGenders

        End With



And that's it, we are good to go

Saturday, December 15, 2007

Scrollbar on a PictureBox, Nah!

The quickest way to add a scrollbar to a PictureBox is not to add one to it. Instead drag a Panel on the Form. Set its AutoScroll property to True.



Now, drag a PictureBox on this panel, such that the panel becomes the parent container of this PictureBox control. Set the SizeMode property of the PictureBox to AutoSize.



Ensure that you set the location of the PictureBox to 0,0. This will align the PictureBox to the top left of the Panel.

And... well nothing. That's it. You can load any image in the PictureBox at Design / Run time and the panel will do the rest for you.

Basic XPath Query

Consider the following the XML segment

<tests>

  <test title="Sample Test 1">

    <result type="Passed" status="0"/>

    <result type="Failed" status="0"/>

  </test>

  <test title="Sample Test 2">

    <result type="Passed" status="0"/>

    <result type="Failed" status="0"/>

  </test>

  <test title="Sample Test 3">

    <result type="Passed" status="1"/>

    <result type="Failed" status="0"/>

  </test>

  <test title="Sample Test 4">

    <result type="Passed" status="0"/>

    <result type="Failed" status="1"/>

  </test>

</tests>



Note: xdoc in the following example refers to an instance of Xml.XmlDocument that contains this XML document
  • To retrieve all test nodes from this XML Block, we can use the basic XPath query -

    xDoc.SelectNodes("/tests/test")


  • To retrieve all result nodes that have the type attribute set to "Passed",

    xDoc.SelectNodes("/tests/test/result[@type='Passed'")


  • To retrieve all result nodes that have the type attribute set to "Failed" and having a status of "0",

    xDoc.SelectNodes("/tests/test/result[@type='Failed' and @status='0'")


  • To retrieve the second test node, we can use the following query

    xDoc.SelectNodes("/tests/test[position()=2]")




The last one is especially useful if you do not have unique node identifier attributes and need to access the nth item from the node list. Of course, the other alternative is to use a for each loop and skip n-1 items from the top. But this is a far more elegant solution.

Friday, December 14, 2007

Multi Colored ComboboxListItems

Requirement

Consider this, a list of Students and their overall performance. For the sake of this example, I will simply rate the performance at two levels
  • 80% and above
  • below 80%
To easily discern between students at these performance levels, we can display the student listitems in different colors using the Combobox control.
A little about the Combobox Item rendering
Listitems in a combobox are rendered in one of two ways - an unselected listitem and a selected listitem.

Since we would like to render the Student Listitems based on the performance factor at two levels, we will require a total of four color combinations -
  • Students whose overall performance meets or exceeds 80%


    Normal unselected  
    Selected

  • Students whose overall performance is below 80%


    Normal unselected  
    Selected

Lets dig in

Having defined the colors that we can use for rendering the listitems, here are the steps that are required
  1. Change the DrawMode property of the Combobox to OwnerDrawVariable. The default value is Normal.


  2. Handle the MeasureItem event of the Combobox. This allows for manipulating the size of each Listitem. In this current example, I have simply set a fixed height and width, however based on the value of e.Index and its corresponding Listitem element, you can change these values if you wish to render the Listitems differently.

    Private Sub cmbStudents_MeasureItem(ByVal sender As Object, ByVal e As System.Windows.Forms.MeasureItemEventArgs) _
     Handles cmbStudents.MeasureItem

      e.ItemHeight = 18 'Pick this based on desired height of the listitem
      e.ItemWidth = cmbStudents.Width
    End Sub


  3. Handle the DrawItem event of the Combobox. Like the event name suggests, this event allows you to perform a custom draw of the Listitem every time the Listitem is displayed. For our example, it is sufficient to know that this event is fired each time a user expands the Combobox to view a list of items as well runs his mouse over the expanded list resulting in a hover selection of that listitem.

    Private Sub cmbStudents_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) _
     Handles cmbStudents.DrawItem
      If e.Index < 0 Then
        Exit Sub
      End If

      'Get the Student object that needs to be rendered using the e.Index
      Dim oStudent As Student = CType(cmbStudents.Items(e.Index), Student)
      Dim DisplayStr As String = oStudent.Name

      Dim r As RectangleF = New RectangleF(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height)

      If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
        If oStudent.OverallPerformanceScore > 80 Then
          e.Graphics.FillRectangle(Brushes.Lavender, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Blue, r)
        Else
          e.Graphics.FillRectangle(Brushes.AntiqueWhite, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Red, r)
        End If
      Else
        If oStudent.OverallPerformanceScore > 80 Then
          e.Graphics.FillRectangle(Brushes.GhostWhite, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Blue, r)
        Else
          e.Graphics.FillRectangle(Brushes.SeaShell, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Red, r)
        End If
      End If

      ' Draw the focus rectangle if mouse hovers over the item.
      e.DrawFocusRectangle()
    End Sub


The Result




Student Class Listing


Private Class Student
  Private _Name As String
  Private _OverallPerformanceScore As Double

  Public Sub New(ByVal Name As String, ByVal OverallPerformanceScore As Double)
    _Name = Name
    _OverallPerformanceScore = OverallPerformanceScore
  End Sub

  Public ReadOnly Property Name() As String
    Get
      Return _Name
    End Get
  End Property

  Public ReadOnly Property OverallPerformanceScore() As Double
    Get
      Return _OverallPerformanceScore
    End Get
  End Property

  Public Overrides Function ToString() As String
    Return Name
  End Function
End Class

Thursday, December 13, 2007

Remove Duplicate Whitespaces using Regular Expressions

A quick way to eliminate duplicate white spaces from a string using regular expressions using VB.Net


Dim DirtySample As String = "The quick brown fox " & ControlChars.CrLf & " jumped" & ControlChars.Tab & ControlChars.Tab & " over lazy dog"
Dim CleanSample As String = System.Text.RegularExpressions.Regex.Replace(DirtySample, "\s+", " ")

Debug.WriteLine("DirtySample : ")
Debug.WriteLine(DirtySample)

Debug.WriteLine("CleanSample : ")
Debug.WriteLine(CleanSample)


Output

DirtySample :
The quick brown fox
 jumped over lazy dog
CleanSample :
The quick brown fox jumped over lazy dog