Thursday, 13 April 2006

I like the WinForms user interface, but not the Web UI. I know, I'm not alone with this, but please keep in mind that it's 2006 and you can conjure a full realistic 3D world on your PC with any of the trendy FPS games, but you go back to work the next day and have to work with an enterprise application with a web front end and still have to struggle with inconveniences determined by the technology you use. It's crazy, isn't it?

On the other hand (if I don’t count AJAX technologies, which are a big hope for me), you have the WinForms UI, which provides you with a useable environment for your applications. As we have mostly worked with web applications in the last two years, we looked into the ASP.NET improvements first when the new .NET framework came out. Although we found strange things there, what I have seen and tried out is really promising (I promise I'll collect our findings on this topic as well). Thus, full of enthusiasm (and knowing that many of our customers are interested in using Genome with WinForms UI), I’ve started to put together a sample that shows a few databinding scenarios using the ever famous Northwind database and Genome of course (the sample will already be online by the time you read this).

I'm a bit disappointed. I see the improvements and the fancy new stuff they have shovelled together at MS, but it cannot make me happy. Let me tell you what I've found.

The DataGridView

First I wanted to play a bit with the DataGridView, the new grid component of MS. More concretely, I wanted to display the employees in the Northwind database in a grid. With a few clicks I created a BindingSource with the designer that was based on the business class Empoyee. Although it took some time for me to understand, I now see that this BindingSource does not represent the data that will actually be displayed in the grid but rather the structure of it (at least in design time). This is an object data source (not to be confused with the ObjectDataSource class of ASP.NET 2.0, which is something completely different :-)), therefore it is somehow represents the structure of a .NET object, i.e. a .NET class. This is clearly visible in the code generated by the WinForms desinger:

this.employeeBindingSource.DataSource = typeof(Northwind.Business.Employee);

Since it represents the object structure, I can use it in design time to specify the columns of my grid with their characteristics (like special formatting) of course. That’s fine so far.

As my application must also show some data in the grid, it is obvious that typeof(Employee) as a data source won't be enough. It served design-time needs very well, but when the application starts I have to replace it with something that can provide the actual data that I want to display. It couldn’t be easier: I'll replace the data source of my BindingSource in the Form_Load event with my Genome set which will return all employees from the database:

employeeBindingSource.DataSource = dataDomain.Extent(typeof(Employee));

(Please have a look at the two cited lines and note the similarity: typeof(Employee) - extentof(Employee); it nicely shows how O/R mapping makes relational databases available in an object-oriented way.)

Well actually, you could directly set the data source of the grid as well, but the point is that the controls that act on the same data (like a grid and a navigator bar) are not directly bound together (the navigator doesn’t have to know about the grid), but they all depend on the BindingSource. A great example of IoC, fine.

However, it is not so fine that the grid-bound columns can only be bound to single properties and not to more sophisticated expressions. Therefore, if you want to display the company name of your orders in the grid, which would bind to the expression Company.Name, you have to do it all manually, unlike in ASP.NET where this is possible.

The IBindingList

The application works, displays the employees, you can control the selected item with a navigator bar (BindingNavigator), etc. The only thing that you will notice is that sorting does not work. I have looked into the documentation and decided that I'll implement an IBindingList wrapper around the Genome set that will transform the sorting intentions to the format recognised by Genome and can be further transformed to SQL. Yes, you have to look into the specs to find this out, because the type of the BindingSource property (object) does not tell you too much.

Weakly Typed vs. Strongly Typed: 15-0.

The IBindingList interface allows you to sort by a property (if you want to sort by more complex expressions, or at least more than one property, then the IBindingListView interface can be used, which is new in 2.0). The IBindingList interface extends the basic collection handling interfaces, like IList, ICollection, and IEnumerable. Unfortunately, since IBindingList already existed in .NET 1.1, it uses the non-generic old style interfaces, still providing a weakly typed interface in our new strongly typed world.

Fortunately Genome, as a decent O/R mapper, uses the business class properties in the sorting expressions (instead of the database field names), so it was quite straightforward and the rest is just plumbing, with a lot of casting from my internal List<T> source to the non-generic IList interface results.

Just one interesting point about the IBindingListView interface: it has a string property called “Filter”. Here the documentation says: “The definition of the filter string is dependent on the data-source implementation”. I’m wondering what the point is in providing a Filter property in a generic interface if the specification of the property value is implementation-specific. How can you use it then? It is as if somebody had created a common car fuelling interface for petrol and diesel fuel. Nobody wants to fill a petrol car with diesel.

Weakly Typed vs. Strongly Typed: 30-0.

The Employee Details Form

With the IBindingList wrapper, sorting works too. After that I created a detail form that pops up when you double click a row in the grid and where you can display and edit the details of an employee record. This part of my sample went quite well. Similarly as with the grid, I created a BindingSource for the Employee class which I could use to specify the binding expressions (only properties, of course) for the controls I used: TextBox, ComboBox, DateTimePicker. The only problem that I had is that my DateTime properties (BirthDate, HireDate) were actually nullable in the database, therefore I used the platform-independent NullableDateTime struct as property type for these properties. Unfortunately, the NullableDateTime properties cannot work together with the DateTimePicker directly, but with a dynamically attached TypeConverter, I was able to solve this problem.

I used Genome's long-running transaction in my application so all the property changes were collected in the memory by the Genome context. The only thing that I had to do is instruct Genome to write out or discard the in-memory changes to the database when the user clicks OK or Cancel in the detail form.

The ComboBox DataSource

Another problem that I encountered was with the ComboBox. As you probably know, the employees in the Northwind database have another associated employee that they report to. The ReportsTo references build up a tree hierarchy among the employees, where Mr. Andrew Fuller is at the top, which means he has nobody to report to (what an enviable position…). When I populated the items in the ReportsTo ComboBox, I used the employees in the database, but did not add a "nobody" entry into the list. So I added a "null" instance to the items and modified the code that provides the display label for the items so it displays a special text for null. As it turns out however, you are not allowed to add null to the list. My ComboBox was bound to a strongly typed List<Employee> array and I had not wanted to create a special employee instance that represents the "nobody" value. And if I add a non-Employee instance to the list, then the data binding to the ReportsTo property (that is a type of Employee) does not work. When I explained my problem to a colleague, saying that I had not found any solution to this simple problem in the documentation, he suggested looking into the framework code with Reflector. Finally we found the ultimate solution: DBNull.Value! You can add DBNull.Value to the ComboBox items collection; it works with data binding, so everything is fine. It’s “truly amazing”. Right. Except that my beautiful List<Employee> array has become List<Object> now.

Weakly Typed vs. Strongly Typed: 40-0.

In-place Editing

My last challenge was to get the in-place editing to work with the grid. I extended the IBindingList wrapper to support insert/delete operations for this. Although I was able to do it, it was again full of inconveniences. The first interesting thing was that there is no good way to implement row-level editing. It seems that the DataGridView was designed to support editing the data cell by cell and not row by row. It was especially bad with new objects. In the AddNew() method of the IBindingList wrapper, I created a new business object. As business objects are stored in the database, Genome tracks this operation and registers in the context that the new object has to be inserted into the table. The grid calls AddNew() when you click the empty row at the end of the list, but if you leave the row without filling in any of the cells, it threatens to act as if you've cancelled the new record creation without notifiying the data source. When you click the last row again, it again calls the AddNew(). I think you can guess what happens if you click the last row and away repeatedly. Genome on its own would just continue creating new objects and registering them in the context, so when you finally commit, a lot of uninitialised employees are inserted into the database, because the grid does not inform IBindingList about the cancelled object creation. For now, I solved the problem by subscribing to the “Validated” event of the grid, trying to capture the cancel-new situation. It is a hack. Also I'm quite sad that if you delete a record from the navigator bar (there is a button there with a red x), the UserDeletedRow event is not fired. Therefore, if you want to handle record-delete events in the UI consistently, you have to handle them both in the grid and in the navigator.

Conclusion

To my dismay, Weakly Typed won the game. However, working with strongly-typed structures in the UI has only just started. The WinForms UI became more useable with .NET 2.0, but is still far from perfect, which is good news for 3rd party UI component vendors, I guess. In the meantime, I'll see how the Weakly Typed vs. Strongly Typed set continues, and who'll win the match.

If you are interested in how to use Genome with WinForms applications, especially with data binding, please visit the Downloads section of the Genome site.

Posted by Gáspár

Tuesday, 03 October 2006 10:57:38 (W. Europe Daylight Time, UTC+02:00)
hi !
i didn't know how you did for the "The ComboBox DataSource" because we can't add new items to a bound combobox column !!!???
i still didn't find a solution for that :(
Tuesday, 03 October 2006 13:42:08 (W. Europe Daylight Time, UTC+02:00)
Hello!

Actually I bind the combobox to a list that _already_ contains this additonal item.

reportsToComboBoxDataSource = new List&lt;Object&gt;(... items from db ...);
reportsToComboBoxDataSource.Insert(0, DBNull.Value);
reportsToComboBox.DataSource = reportsToComboBoxDataSource;

With the ComboBox's Format event I can alter the text to be displayed for this additional item. You can download the evaluation version of Genome from http://genom-e.com and check the "NorthwindDataBinding" sample, that contains the full solution.
Friday, 06 October 2006 18:37:39 (W. Europe Daylight Time, UTC+02:00)
Hello!

Have you tried to implement the ICancelAddNew interface in your IBindingList wrapper?! Then you can remove the object from the collection in the CancelNew method, if the user cancels the row creation.
Alexander Ståhl
Wednesday, 11 October 2006 16:34:38 (W. Europe Daylight Time, UTC+02:00)
Hi Alexander,

Yes, you are totally right. Interestingly enough we discovered the same just a few days before your post. So we have updated the implementation to use this interface and really it is much nicer now!

Thank you for your hint anyway!

Br,
Gaspar
Comments are closed.