Monday, December 17, 2007
Application Launch Directory
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
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
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
- Drag a DataGridView on the form
- Expand the DataGridView tasks and add a couple of columns
- 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.
- 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!
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
<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%
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
- Change the DrawMode property of the Combobox to OwnerDrawVariable. The default value is Normal.
- 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
- 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
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