It is time now to set concepts aside for some more practical problems!
Last days, through the work on two different projects, I encountered DBNull twice, and faced a funny problem about locating the target TreeNode of a contextual menu!
Let us start by the first one:
DBNull in Silverlight and Windows Forms
I encountered a DBNull problem two times (in three days… that seems a bit much). The first time was in the context of a WCF service intended to feed a Silverlight application, and the second was in the context of a GridView control in a Windows Forms application.
For your information, DBNull is a .NET Type defined in namespace System (mscorlib.dll).
The first issue emerged with what appeared to be a communication problem between a Silverlight application and one related WCF service. After some research, DBNull was reported to be an unknown Type in XML serialization. The service was transmitting data read through a SQL Server database… We had a Cell class. Its Data member was an object.
A bit more research made it visible that the following code as the source of the annoyance:
cell.Data = data_reader.GetValue(cell_index)
|
In fact, when the field at cell_index was null, data_reader.GetValue() returned DBNull. Which was assigned as the cell’s Data. Serialized by WCF, DBNull was not recognized by the receiving Silverlight application.
Though the time needed to discover the source of the problem was quite long, the solution was quite simple:
cell.Data = data_reader.IsDBNull(cell_index)
? null :data_reader.GetValue(cell_index)
|
// Which clearly means:
if(data_reader.IsDBNull(cell_index))
cell.Data = null;
else
cell.Data = data_reader.GetValue(cell_index)
|
Now that the cell content is null, everything goes right (because null is a recognize Type).
It will be a great day when DBNull (which implements the ISerializable Interface) will simply be serialized as null… let us just wait.
Yet another DBNull problem!
Once the Silverlight DBNull problem solved… precisely: the next day J, I just found myself again confronted by another DBNull. This time in a Windows Forms application.
In this new case, I had a DataGridView control which was filled using a DataTable and I wanted to programmatically add a row to those displayed in the DataGridView control.
Obtaining the row’s data was simple (calling a web service or by directly querying a database).
The task was then to add the obtained data as a new row of the DataTable used as the source of the control. The problem was again related to the cells containing null values in the new added row.
In fact, if you assign the row’s cells data through a DataRow object you may not have a particular issue. Issues appear when you try to assign an object’s properties values to the row cells.
In this particular case, we had an object with some properties of nullable types: for instance int? parent_id. The code below generated an error:
DataRow row = table.NewRow()
row["parent_id"] = obj.parent_id; // an error here when obj.parent_id == null
|
Some solutions are proposed in several forums. They mainly propose to handle some special events of the DataGridView (like DataError event) and/or play with custom interpretation of DBNull…
The solution I used seems more simple. It is based on the fact that when the row was created (using table.NewRow() method) it contained all required cells… each of which, a priori, filled with something (a sort of ‘null’ thing… we don’t really care about its type). So, we simply may assign cell values only if the data to be assigned is NOT null:
DataRow row = table.NewRow()
if( obj.parent_id != null)
row["parent_id"] = obj.parent_id;
|
That seems to work!
Contextual menus… yes! but where is the target node?
TreeView control is a nice and useful control to show many data domains.
One nice and efficient thing of TreeView control is that it allows the developer to handle many aspects of tree nodes by their level in the tree hierarchy. You can for instance choose the icon of items according to their nest level in the tree hierarchy
Among the customizations you can do is to choose a specific contextual menu for each hierarchy level… very cool and simple to do.
When the user right clicks a node in the tree, he or she would see the assigned contextual menu and be able to execute operations in relation to the type of the node.
The thing is, when a menu is clicked, you developer should find out which node was selected when this menu was clicked… Curiously, this is not as simple as we may expect.
I naively thought, a contextual menu object will contain some relevant information about its Target object (in our case: the target Node)… unfortunately this was not true!
So, you simply find yourself with a command to execute (the menu clicked)… but don’t know on which object of the tree should you execute this command!
Few solutions are proposed to solve this problem, and none, of what I read, seemed sustainable.
Here again, we should ‘reinvent the wheel’…
Reinventing the contextual wheel!
The problem seems to be: “Locating the target tree node”. The problem’s definition starts by “Locate”… so let us think “location”.
I first started by trying to locate the mouse cursor at the moment of the click event (using MousePosition which returns the point of the current mouse position in screen coordinates)… but that seemed random, not always related to the node position (and also quite difficult to debugJ).
The next step was to think “location of controls”.
In fact, a contextual menu is a control and as such, it should have a location… hopefully relative to its parent control (which is the TreeView).
Our Click event is received from a contextual menu item (ContextMenuStrip object) which is located into a parent control (the contextual menu box), itself is a child control of the TreeView control containing the node.
The following figure illustrates this disposition
The Owner property of the menu strip contains its parent menu control information. Through this information, we are able to obtain the location of the menu box, and thus, the location of the first menu strip relative to the TreeView control. With some more simple arithmetic, we can finally locate the right-clicked node (the target node) using the TreeView control GetNodeAt(Point point) method :
static TreeNode LocateContextMenuTargetNode(
TreeView tree_view,
ToolStripMenuItem menu_item)
{
// check entries
if( tree_view == null || menu_item == null || menu_item.Owner == null)
return null;
ToolStrip menu = menu_item.Owner;
// top most menu item
ToolStripMenuItem first = (ToolStripMenuItem) menu.Items[0];
// obtain the enclosing rectangle of the menu box
Rectangle menu_rect = menu.RectangleToScreen(menu.ClientRectangle);
// obtain the rectangle relative to the TreeView control
Rectangle tv_rect = tree_view.RectangleToClient(menu_rect);
// obtain the optimal point that corresponds to the right-clicked node
Point point = newPoint(
first.ContentRectangle.X + tv_rect.X,
first.ContentRectangle.Y + tv_rect.Y);
// finally, get the right-clicked node
return tree_view.GetNodeAt(point);
}
|
That works fine for contextual menus with few elements and in a ‘normal’ screen resolution.
If the contextual menu contains many items, the first item may be quite far away from the target node!...So, what?
Let us look for a more sustainable way to locate our item… we may again, rethink ‘mouse location’!
In this solution, we will keep track of the current ‘right-clicked node by responding to the MouseDown event of the TreeView.
TreeNode m_context_menu_target_node;
void treeView1_MouseDown(object sender, MouseEventArgs e) { // if this is not a right-click: do nothing if (e.Button != System.Windows.Forms.MouseButtons.Right) { m_context_menu_target_node = null; return; } Point point = e.Location;
m_context_menu_target_node = treeView1.GetNodeAt(point); }
|
|
Now, when a contextual menu is clicked, we can first see if the target node is not null… execute the command and reset the target node to null (until the next time):
TreeNode node = m_context_menu_target_node;
if (node == null) return;
// reset the target node to null m_context_menu_target_node = null;
// execute the command on the node …
|