Showing the Sort Direction in a DataGridView

I know that Windows Forms are considered old fashioned, but I've got clients with applications that still use them. As I noted in my previous tip, I recently had to support letting the user sort  by any column in a grid. If I was going to do that, I felt I should let the user know what column and direction they'd just sorted the grid in. The DataGridView lets you do that through the column's SortGlyphDirection property: just set the property to one of SortOrder.Ascending (little arrow in the column header pointing up), SortOrder.Descending (little arrow pointing down), or SortOrder.None (no arrow in the header).

When the user clicks on a column the first time, I sort the column in ascending order; with a second click on the column, I reverse the order. If the user changes columns, I erase the glyph in the previous column. To keep track of the necessary information, I declare two variables outside of any method: The Headers collection keeps track of what direction each column is sorted in, and the OldColumn variable keeps track of the last column sorted:

Private Headers As New Dictionary(Of String, SortOrder)
Private OldColumn As DataGridViewColumn
I put the actual sort code in the DataGridView's ColumnHeaderMouseClick event, whose parameter includes the column position the user's clicked on (the ColumnIndex property). I use that parameter to get a reference to the column the user's selected:

Private Sub CustomersView_ColumnHeaderMouseClick(…    
   Dim col As DataGridViewColumn
   col = Me.CustomersView.Columns(e.ColumnIndex)
In the event handler, I check to see if there's a setting for the current column in the Headers collection. If there isn't, I just sort in ascending order; if there is, I use that value to decide what order to sort the column in:

If Headers.ContainsKey(e.ColumnIndex) = False OrElse
   Headers(e.ColumnIndex) = SortOrder.Descending Then
      Headers(e.ColumnIndex) = SortOrder.Ascending
      Customers = Customers.OrderBy(
            Function(c) prop.GetValue(c, Nothing)).ToList
     Headers(e.ColumnIndex) = SortOrderDescending
     filteredInvoices = filteredInvoices.OrderByDescending(
             Function(c) prop.GetValue(c, Nothing)).ToList
End If

After I finish sorting, I refresh my grid's DataSource with the new collection, clear the sorting glyph in the previous column, set the glyph in the column I'd just sorted, and keep track of which column I've just sorted for next time:

CustomersView.DataSource = Customers
If OldColumn IsNot Nothing Then
   OldColumn.HeaderCell.SortGlyphDirection = Windows.Forms.SortOrder.None
End If
col.HeaderCell.SortGlyphDirection = Headers(e.ColumnIndex)
OldColumn = col

Now, by glancing at the grid's column headers, the user knows which column was sorted last, and in what order.

Posted by Peter Vogel on 10/17/2013 at 11:59 AM0 comments

Sorting with LINQ, Dynamically

In the bad old days (before LINQ), I used to concatenate together SQL statements. That left me open to SQL injection attacks, of course, but it was so convenient: I could dynamically put together whatever SQL I needed at runtime. With LINQ, however, not so much (though I understand that there is a version of Dynamic LINQ out there). Even with ordinary LINQ, though, you can select which properties in an object you want to use at runtime; at least when you're sorting (and, probably, elsewhere but I haven't tried it yet).

Recently, I needed to support a grid for a client who wanted to be able to sort on any column. I was setting the grid's DataSource to a collection of objects. While this simplified getting the objects displayed, it also turned off the grid's automatic sorting capability. To get around that, I added some code to the grid's ColumnHeaderMouseClick event to sort the grid. What I didn't want to do was write a separate method to sort on each grid column (especially because this client kept changing his/hers/its mind about what columns should or shouldn't be in the grid).

Fortunately, each grid column held the name of the property that the column was responsible for displaying from the objects in the grid's DataSource. Given a property's name, I retrieved the PropertyInfo for that property and used its GetValue method in my LINQ code.

First, I retrieve a sample object from the collection of objects (Customers, in this case):

Dim cust As Customer
cust = Customers.First

Then I retrieve the PropertyInfo object for the property whose name is in the PropertyName string variable:

Dim prop As PropertyInfo
prop = cust.GetType.GetProperties.First(
           Function(pr) pr.Name = propertyName)

Now I use the PropertyInfo object's GetValue method in my LINQ statement to sort my list:

Customers = Customers.OrderBy(
      Function(c) prop.GetValue(c, Nothing)).ToList

I'm looking forward to finding out how many places I can use this technique.

Posted by Peter Vogel on 10/07/2013 at 5:13 AM0 comments

Free Tool: Making Life Easier with Web Essentials

I mentioned this tool in one of my Practical JavaScript columns, but Web Essentials 2010/2012 are so useful to ASP.NET developers that I wanted to cover it here for everyone not reading that column. The primary difference between the 2010 and the 2012 versions is that the 2012 version does more.

First and foremost, Web Essentials will create bundles for you (unlike Chirpy, a previous tool from this column which will only minify your files). But Web Essentials also includes tons of support for creating CSS (I keep discovering more features, but the inline URL picker will probably remain my favorite). There are fewer features for HTML and JavaScript files (though Web Essential's support for TypeScript is essential).

Having said that, I do like regions and outlining; Web Essentials gives me both for JavaScript. I'm not a great JavaScript programmer (yet), so I also like Web Essentials' JSHint, which reminds me of JavaScript best practices.

You should get this tool.

Posted by Peter Vogel on 09/30/2013 at 10:23 AM0 comments

Visual Studio Tip: Use Ctrl_I for Quick Searches

This tip is mostly for Visual Studio 2010 users: stop using Ctrl_F. Ctrl_F brings up the Find dialog where you can type in your search text, press the <Enter> key to search, and (eventually) close the dialog because it's blocking your view. Instead, use Ctrl_i (for "incremental search"). After pressing Ctrl_i, just start typing and your cursor will skip to the first occurrence of whatever you've typed so far, no dialog required. Often you'll find your cursor sitting on what you were looking for before you've typed in everything you thought you'd need to find it.

After entering your search text, pressing Ctrl_i again will take you to the next matching entry, as will F3 if you'd prefer something more familiar (Ctrl_Shift_i will take you to the previous entry). When you do find what you want, just press <Esc> to end the search and go back to making changes. There is one difference between using Ctrl_i and F3 to go to the next matching entry: when you've visited all the matching entries on the page, F3 pops up a dialog box to tell you so; Ctrl_i just cycles through the entries again.

The one downside of the incremental search is that it only searches within a document. However, that means that I use my Find dialog just for "Entire Solution" searches. While the incremental search ignores the scope setting on the Find dialog, it does pick up the dialog's other settings (e.g. "Match Case").

Why is this tip "mostly for Visual Studio 2010 users"? Because, in Visual Studio 2012, this is also the way that the Find dialog works: it starts searching as soon as you start typing.

Posted by Peter Vogel on 09/25/2013 at 4:15 PM0 comments

Writing Great Error Messages

I knew this, I really did, but I had to be reminded: A really good error message doesn't tell the user that something's wrong or even what it is that's wrong. A really good error message tells the user what they need to do to solve this problem.

You don't want to have your error messages say things like "Invoices cannot be processed" or even "Invoices cannot be processed: Invoices have different due dates." Those error messages just tell the user what's gone wrong (in varying levels of detail). Instead, tell the user what to do and then, if you want, what the problem is: "Only select invoices with the same due dates -- the selected invoices have multiple due dates."

And, by the way: Always write the error message from the user's point of view and using the user's vocabulary. I remember getting one error message that ended with "…and if this is a server-side error, contact System Administration." How the users, from their point of view, were going to figure out that the problem was (or wasn't) a 'server-side error' was a mystery to me.

Posted by Peter Vogel on 09/19/2013 at 6:48 AM0 comments

Is There Anything in that String?

String variables look like value types, those variables holding integers and dates. But, under the hood, strings are more like reference types, those variables that point to objects. This means that you can set a string to Nothing/null, something you can't do with integers (though you can with a variation on some value types). While a string variable set to Nothing/null is obviously a string with 'nothing there,' you probably also consider a string set to String.Empty or "" (a zero-length string, or ZLS) as 'nothing there.' In some conditions, you may even consider a bunch of blanks ("     ") as 'nothing there.'

.NET provides a method for checking these conditions, and .NET 4 adds a new version that does a little more. The earlier method is the static/shared method IsNullOrEmpty, which returns True if a string is set to any of String.Empty, null/Nothing or a ZLS, but returns false for a string of blanks:

Dim res As Boolean
Dim tester As String
tester = Nothing
res = String.IsNullOrEmpty(tester) 'res is True
tester = String.Empty
res = String.IsNullOrEmpty(tester) 'res is True
tester = ""
res = String.IsNullOrEmpty(tester) 'res is True
tester = "    "
res = String.IsNullOrEmpty(tester) 'res is False

.NET 4 adds a new method named IsNullOrWhitespace which does the same thing as IsNullOrEmpty, but also returns True for a string of blanks or anything else that .NET classifies as whitespace. Whitespace is anything that returns True from char.IsWhiteSpace and includes, for example, tabs and carriage returns/linefeeds:

tester = vbTab
res = String.IsNullOrWhiteSpace(tester) 'res is True
The key issue in choosing between the two is whether you consider a string of blanks/tabs/carriage returns/linefeeds as 'nothing there' (use IsNullOrWhitespace) or as 'something there' (use IsNullOrEmpty).

Posted by Peter Vogel on 09/11/2013 at 9:11 AM0 comments

Working with Objects with the Window Form DataGridView

If you're using the Windows Forms DataGridView and loading data into columns by pulling that data out of properties on objects, you should know that there's an easier way. Just create a List of your objects and use that List to set the DataGridView's DataSource property. The DataGridView will generate a row for each object in the collection and column for each property on the class. Typical code would look like this:

Me.DataGridView1.DataSource = ListOfObjects

Of course, the objects you're putting in the DataGridView may have properties you don't want to display. You'll also want to control the left-to-right order that the properties are displayed in. To take control of the DataGridView's columns, select the Columns property in the Properties window and click on the builder button that appears (that's the button with the three little dots). That will bring up the Edit Columns dialog, where you can add columns to the grid and decide which property each column will display.

In code, you'll also need to set the AutoGenerateColumns property to false to prevent the DataGridView from adding all of your object's properties to the grid anyway:

Me.DataGridView1.AutoGenerateColumns = False

Generating the rows in the DataGridView from a list of objects also makes it easy to retrieve the object associated with each row. You don't pull the data from a row's columns and use those values to recreate the object; instead, you can just pull the object from the row's DataBoundItem property. This code processes all the objects for the rows that the user selected:

For Each row As DataGridViewRow In DataGridView.SelectedRows
  Dim obj As MyObject = CType(row.DataBoundItem, MyObject)
  '…process MyObject

Posted by Peter Vogel on 09/03/2013 at 7:13 AM0 comments

Free Tool: SharePoint Content Deployment Wizard

You know that when it comes to deploying your latest SharePoint solution, it's not enough just to move over your code/list/templates/WebParts/etc. Often you need to have specific content in your production SharePoint site to support your new application. The SharePoint Content Deployment wizard helps you leverage the Content Migration API to generate a cmp file holding the content needed for your deployment.

You still, of course, have to set up the content in your test site (which you probably will have done as part of testing your application). But once you've done that, you can run the wizard to select and export the content you need to move to your production site as part of deploying your solution (and the content can be anything from a list to a site collection). On your production site, you run the wizard again to import the content.

The wizard is smart enough to pick up any dependencies that your content needs (new content types, for instance). It's not smart enough, however, to handle moving content across SharePoint versions, though, so don't think of this as a migration tool.

But, think! No more staying up late banging in the content to have it ready for when the users come in (or, worse, forgetting to put in the content and having your code blow up).

Posted by Peter Vogel on 08/28/2013 at 7:50 AM0 comments

Check for Changes Between Collections with SequenceMatch

You'd prefer not to execute any update code more often than you absolutely have to. If you only have one object to deal with, checking to see if an update is required is simple: retrieve the corresponding object from the database, check to see if the two objects have different values in their properties, and update the database only if there is a difference.

However, the problem is more complicated if you have a collection of objects to update in your application and in the database. With two collections, you can check for differences between the objects that appear in both collections. But you'll also need to check if the collections have missing or added objects. Fortunately, SequenceEqual can do all those checks for you, returning false if the two sequences don't have the same elements or if matching elements are different.

There are two wrinkles here, though. The first is that the two collections must be sorted the same way. This example sorts two collections by ID and then calls the SequenceEqual method on one collection, passing the other collection to determine if an update is required:

Dim usrs = (From cu In context.CompanyUsers
            Order By cu.ID
            Select cu).ToList
Dim updatedUsrs = (From us In ChangedUsers
                   Order By us.ID
                   Select us).ToList
If Not usrs.SequenceEqual(updatedUsrs) Then
    '…do update
End If

The second wrinkle requires some code. SequenceEqual doesn't actually compare the properties in the two objects: it just checks that the lists reference the same objects. If you've retrieved the objects from two sources (for example, if your application generated one set and the other set comes from your database), SequenceEqual will always return False. Fortunately, to get SequenceEqual to check the values of your object's properties you just need to have your classes implement the IEquatable interface, which I described in an earlier tip.

Posted by Peter Vogel on 08/08/2013 at 1:36 PM0 comments

Free Tool: Don't Reset Your CSS, Normalize It

Standards must be wonderful things; after all, there are so many of them. And there's a different standard for CSS defaults for every browser. As a result, if there's any relevant CSS setting that you don't supply a value for in your CSS, your perfectly good stylesheet will cause your page to display "uniquely" in every browser (and unique is never good).

The typical solution to this problem is to use a "CSS Reset" stylesheet, which shoves a value into every CSS setting (margins, line heights, font size) that might matter to you. But CSS Resets just steamrolls over the defaults, setting all numeric values to 0 or 1. It's a "zeroing out" approach. Since those "zeroed out" settings probably aren't what you want, you have to write a lot more CSS to provide values for those defaults.

Normalize.css takes a different approach. Rather than make every browser look like nothing, Normalize tries to make every browser look the same. Normalize.css attempts to support the browser defaults that are actually common, and bring those browsers that don't use those common defaults around to those defaults. So, for example, Normalize ensures that superscripts and subscripts use the same font-size and line-height in all browsers rather than zeroing those values out and forcing you to specify them. You can argue with the defaults the authors have chosen as "common," but they have drawn on the specifications established by a number of industry groups, including WHATWG (the people who brought you HTML5).

From a user interface design point of view, this is a two-edged sword. Users value consistency in their UIs more than anything else. Normalize.css will make your pages look alike across all browsers. But it also means that when your page is displayed in Chrome, your page may not look like every other Chrome page. I'm OK with that, but you're free to disagree.

Normalize.css is up to date with HTML5, understands mobile browsers, is easy to add to your project, and is well documented so that you know what it's doing (and why). It's not even very big. It should be the start point for all of your styling.

Posted by Peter Vogel on 07/30/2013 at 1:16 PM0 comments

Visual Studio Tip: Extend Your Most Recently Used List

The most recently used (MRU) lists on Visual Studio's menu list the last 10 projects (or files) that you worked on. However, I'm sometimes working on multiple projects for multiple clients simultaneously (please don't tell them: each one thinks they're exclusive). As a result, I have many projects on the go and often have trouble remembering which one I'm working on for any particular client.

Which leads me to Microsoft Office: One of the reasons I like the latest versions of Office is that they do a great job of showing me all the files and folders I've touched in the recent past. It's a great memory jogger because it isn't limited to the last 10 items.

You can't get those Office lists with Visual Studio, but you can extend your MRU lists to something more than 10: In Visual Studio, go to Tools | Options | Environment | General and set the "items shown in recently used lists" option to a higher number. Or, to a lower number if you'd prefer to forget about some of your recent projects.

Posted by Peter Vogel on 07/24/2013 at 1:16 PM0 comments

Control Testing for Equality with the IEquatable Interface

When you use the equals sign (=) or the Equals method to compare two objects, you're probably doing a reference comparison: You're asking if the two variables being compared are pointing to the same single object in memory. That may not be what you want.

What you may want is to compare two different instances of the same object to see if the two objects represent the same business entity. As an example, you might compare the variables Customer1 and Customer2, both of which point to Customer objects, to determine if both Customer objects represent the same customer. In that case, you want to do a value comparison: You want to ask if the two objects that the variables point to have identical values in their properties.

You can take control of that comparison in your custom classes by having them implement the IEquatable interface, which contains a single method called Equals. When code compares your object to another object of the same type, the Equals method will be called and be passed the other object. In the Equals method you just need to return False if any two properties on the objects are different. So, for a Customer object, an implementation might look like this:

Class Customer
    Implements IEquatable (Of Customer)

  Public Function Equals1(
        ByVal cust As Customer) _
        As Boolean _
        Implements IEquatable(Of Customer).Equals

   If cust.ID <> this.ID OrElse
      cust.FirstName <> this.FirstName OrElse
      cust.LastName <> this.LastName Then 
            Return False 
    End If 
    Return True 
  End Function 

End Class

However, the IEquatable interface version of the Equals method is used only when the variables are declared with the same data type (i.e., both variables are declared as Customer). If you compare a Customer to a variable of some other type (e.g., compare a variable declared as Customer to a variable declared as Object), then the base Equals method (the one that your class inherits from System.Object) will be used to compare the two objects. At that point, you're probably back to doing a reference comparison.

That behavior may be what you want: Customer to Customer comparisons will be value based and any other comparison will be reference based. But if you think developers using your objects may find this behavior inconsistent, you should also override the default Equals method and call your comparison code from that override also:

Public Function Equals1(
        ByVal cust As Customer) _
        As Boolean _
        Implements IEquatable(Of Customer).Equals
  Return MyValueComparison(CType(obj, Customer));
End Function

Public Overrides Function Equals(obj As Object) As Boolean
  Return MyValueComparison(CType(obj, Customer));
End Function

Public Function MyVallueComparison(cust As Customer) As Boolean
  If cust.ID <> this.ID OrElse
     cust.FirstName <> this.FirstName OrElse
     cust.LastName <> this.LastName Then 
       Return False 
  End If 
  Return True 
End Function

If you override the Equals method, you should override the GetHashCode method so that its results are consistent with the Equals method:

Public Overrides Function GetHashCode() As Integer
  Dim hash As Integer
  '…calculate hashcode and store in hash
  Return hash.GetHashCode()
End Function

You don't need to write a complicated GetHashCode method. The critical issue in overriding the GetHashCode method is to ensure that two objects that are the same (according to the Equals methods) return the same values from GetHashCode. It's not absolutely essential that two different objects return different values from GetHashCode. If your GetHashCode method always returns the same value, you'll have a "good enough" version.

The Dictionary object provides an example of how the value returned from GetHashCode is used. The Dictionary initially uses the hash code to reduce the time to find an object when searching by key. However, if two objects have the same hash code, the Dictionary falls back on the Equals method to find the right object. Using the Equals method will take the Dictionary longer than using the hash code, but you probably won't notice unless you have a lot of objects in the Dictionary.

Posted by Peter Vogel on 07/16/2013 at 9:59 AM0 comments

Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.