Regular Expressions

Regular expressions are a very powerful tool for validating or parsing a string. I don't claim to be an expert in the use of Regular expressions by any means. But I have used them with great success on a number of occasions. They are very useful. I have used them to validate a special URL that is used to create hyperlinks to forms within a smart client application, or to parse the syntax of a Powerbuilder DataWindow to name just two.

In ASP.NET there is a Regular Expression validator control. This control lets you validate input based on Regular Expressions. The nice thing about this control is the hard work is done for you for a variety of different strings. For example there is a sample expression for internet email addresses. \w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* and one for an Internet URL http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?

If you want to use Regex in .NET (C# or VB.NET) getting the expression right is the tough part, as you can see from the expressions above. The rest is pretty easy. To help you out there is a tool written by Eric Gunnerson called Regular Expression Workbench that I have found very useful on many occasions. You can down load it from gotdotnet. This wonderful tool will help you format your expression, interpret or execute your expression on a sample string, even tell you how efficient the operation is.

For those of you who don't really know how to use Regular Expressions here is a simple example to get you started. Using this example you can test a string for swear words.

First: to use Regular Expressions you must use the System.Text.RegularExpressions namespace.

Then create a regular expression and call the match method to search your string.

 using System.Text.RegularExpressions;
 Regex r;                                                     //Declare Regular expression
 r = new Regex("hell");                                //Create expression
 Match m = r.Match(this.textBox1.Text);    //Call match method

 if (m.Success)                                           //Check to see if a match was made
   {
      MessageBox.Show("You can't say that.");
   }
 else
   {
      MessageBox.Show("nothing found.");
   }

The above example works to find the word hell but it will also find it inside the word hello. You can add the escape character /b to denote word boundaries. For example if your expression is /bhell/b it will only match on the word hell. /b has two uses in a regular expression it's a word boundary. When used within a [ ] character class it refers to the backspace character.

Fine so you can now validate a string to keep users from entering a swear word. "But Dave there are many words I need to check for.", you say. Not to worry this can be done in the same expression using a pipe. If you separate each word you want to find with a pipe Match( ) will look for all the words listed. See the example below.

using System.Text.RegularExpressions;
Regex r;                                                    //Declare Regular expression
   
 r = new Regex("
\\bhell\\b|\\bfrig\\b");        //Create expression
 Match m = r.Match(this.textBox1.Text);  //Call match

 if (m.Success)                                         //Check to see if a match was made
 {
  MessageBox.Show("You can't say that.");
 }
 else
 {
  MessageBox.Show("nothing found.");
 }

Closing a form before it shows

I'm working on a smart client application at the moment. One of the forms, under certain conditions, launches a wizard to gather information from the user.

From the users perspective they open the form and the wizard displays. If the user hits cancel, I want the wizard to close and the form not to show.

So what is the best way to do this. There is no cancel in the FormLoad event. If you try to close in formload an exception is raised:

Cannot call Close() while doing CreateHandle().

I asked Google and found one solution. Controls have a Public Event, VisibleChanged that fires when the visible property is changed. In this event you can call the forms Close method.

This works fine with one side effect, the form shows for a split second then closes.

This will suffice, but if anyone knows a better way to do this please let me know.

 

 

DataTable.Rows

I have an issue with the DataTable object. Although I think it's a cultural thing. Some people I talk to don't seem to bothered by it. I find it very annoying, mostly because it would have been so easily avoided.

So whats the problem? When a DataRow is flagged for deletion it's still part of the row collection in the DataTable. So you may now be wondering what the big deal is.

You see I spent 10 years in the PowerBuilder world. In a PowerBuilder DataWindow (Similar to the objects in ADO.NET) when a row is flagged for deletion it's stored in a separate collection. Therefore when you are processing the rows The Deleted rows are not in the collection of current rows. If you want to access the deleted rows you can do that also. Also when you get a count of rows it's the count of rows not including the deleted rows.

In a DataTable when you delete a row it remains in the rows Collection and you have to deal with them. This also means that DataTable.Rows.Count or DataTable.Count is a count of all the rows including deleted rows.

Why do I say it's cultural? My esteemed colleague Bruce Johnson and I had a brief discussion and he almost convinced me this is how it should be. Bruce said "It's only flagged as deleted why shouldn't it be in the Rows collection?"  I have to say this is a fair statement. However it would make life easier for the developer if there was a DeletedRows collection. Therefore a corresponding DeletedRows.Count. This feels more natural to me.

When do you ever want to iterate through all the rows in a DataTable both deleted and not and preform the same action on them? I could make something up but it would be bogus. I have never wanted to do this. Even if you can come up with a good reason it's not going the be the common scenario. When you write a framework you should take into account the 80/20 rule. Make it easier for 80 percent of the cases and let the 20 percent do extra work.

This means that when you iterate through a collection of rows in a table you have to be sensitive to the fact that some of them may have been deleted.

There are ways to make life easier, and when .net 2.0 (Whidbey) comes along there will be even more.

What can you do about it?

You could check the row state inside your iterator like this.

For each row as dataRow in DataTable
          if Datarow.RowState <> dataRowState.Delete then
                ...
          end if
Next

Or you could get a subset of the collection like this.

For each row as dataRow in dataTable.Select("", "", DataViewRowState.CurrentRows)
               ...
next

Keep in mind with this solution if you are using a typed DataSet you will have to cast the select for the typed row.

For each row as OrderRow in Ctype(OrderTable.Select("","",DataViewRowState.CurrentRows), OrderRow())
             ...
next

I recommend wrapping up the select in a DataSetHelper method, so it looks like this.

For each row as OrderRow in Ctype(DataSetHelper.GetCurrentRows( OrderTable ), OrderRow())
              ...
next

What about getting the count of Current rows in the DataTable. You could wrap this little code segment up in a method in your DataSetHelper also. Just pass in a DataTable.

Public Function GetRowCount( dt as DataTable ) as integer
              Dim dataView As New dataView

             dataView.Table = dt
             dataView.RowStateFilter = DataViewRowState.CurrentRows
             return = dataView.Count
End Function

I mentioned above .net 2.0 (Whidbey) will help. How is that you ask.
There are a couple of solutions.

  1. Using Partial types you could extend the DataSet Class to include a method that returns a collection of Current Rows. This way it's more natural to the developer.             OrderTable.CurrentOrderRows
  2. Using Iterators you could write your own iterator that only iterates thought the current rows.

 

 

 

 

Rename a XSD

Yesterday I tried to rename a DataSet. To lazy to recreate it. Have you ever tried to rename a DataSet?

It seems easy enough. Press F2 in the Solution explorer and type in a new name. Open the XSD and do a find and replace of the old name with the new name, there done right?

Not exactly when you try to generate the class it may get generated with the old name. Your solution ends up looking like this:

  • MyNewDataSetName.xsd
    • MyOldDataSetName.vb
    • MyNewDataSetName.xsx

So what is the solution? The problem is in the Project File. There's an attribute on the file node called LastGenOutput it is holding the old DataSetName. See below.

Delete this attribute and you will be fine.


 
          RelPath = "MyNewDataSet.xsd"
      BuildAction = "Content"
      Generator = "MSDataSetGenerator"
      LastGenOutput = "MyOldDataSet.cs"
    />
 


           

Visual Studio Command Prompt

Here is another useful tip from Mark Comeau, this guy needs his own Blog. Then again I'm glad he hasn't got one. What would I write about. :)

Do you find yourself in the Visual Studio Command Prompt from time to time? Of course you do. Do you get to it via:

Start->Programs->Visual Studio .Net 2003->Visual Studio .Net Tools->Visual Studio .NET 2003 Command Prompt

Hopefully you have a shortcut that is closer to your desktop or start menu.

When you open it what directory are you in? Do you have to then use your vast knowledge of command line directory traversal to get to your source code or whatever it is your looking for?

Would you like to right click on a directory in windows explorer and open a Visual Studio Command Prompt already set to that directory?

Just add these entries to your registry:

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shell\cmd_vs]
@="Open VS Command Prompt Here"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shell\cmd_vs\command]
@="cmd.exe /k \"C:\\Program Files\\Microsoft Visual Studio .NET 2003\\Common7\\Tools\\vsvars32.bat\""

If you would like the same functionality from a Drive also add this to your registry.

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Drive\shell\cmd_vs]
@="Open VS Command Prompt Here"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Drive\shell\cmd_vs\command]
@="cmd.exe /k \"C:\\Program Files\\Microsoft Visual Studio .NET 2003\\Common7\\Tools\\vsvars32.bat\""


Thanks Mark thats a good one.


 

Source Safe Performance

Here are a couple of good tips for making source safe run a little better over the network.

I have seen these before but a client (Mark Comeau) recently reminded me of them.

  1. Change your VSS Folder for temporary files to be local. It will default to where the VSS Database is installed. Tools->Options...-> General Tab
  2. Put your ss.ini on your local machine. This file normally is in the \users\ You can move it locally and change the \users.txt file to tell Source Safe where you put it.

Less network chatter means a better response time when using Source Safe over the network.

 

 

EnforceConstraints

Have you ever tried to update a Dataset and received this message?

Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.

Not a problem in a small DataSet. But if your Dataset is more complex sometimes it's not easy to see the problem right away.

There is a property on the DataSet called EnforceConstraints. When you set this to true the DataSet will try to enforce all the constraints in the DataSet (Not null columns, Foreign keys, unique keys) The problem with this exception is, it tells you very little. You know you have violated a constraint, but which one? The way to find out is to look at the row and column errors.

Below is a method that will attempt to apply contraints and write the errors out to the output window. I hope someone out there finds it useful.

Public Sub GetDataSetErrors(ByVal ds As System.Data.DataSet)

 

Public Shared Sub GetDataSetErrors(ByVal ds As System.Data.DataSet)

 

Try

ds.EnforceConstraints =

True

Catch ex As Exception

Debug.WriteLine("DataSet errors: " & ds.DataSetName)

 

For Each table As DataTable In ds.Tables

 

Dim ErrorRows As DataRow()

ErrorRows = table.GetErrors()

 

For Each row As DataRow In ErrorRows

Debug.WriteLine("Table: " & table.TableName)

Debug.WriteLine(" Row Error: " & row.RowError)

 

Dim ErrorColumns As DataColumn()

ErrorColumns = row.GetColumnsInError()

 

For Each column As DataColumn In ErrorColumns

Debug.WriteLine("Column: " & column.ColumnName)

Debug.WriteLine(" Error: " & row.GetColumnError(column))

 

Next

 

Next

 

Next

 

End Try

 

End Sub