June 2005 - Posts
Microsoft has included a Reporting Services report viewer with Visual Studio 2005, which is apparently freely distributable. If I understand this correctly, this means that Visual Studio 2005 gives you the ability to author reports, create applications that render them, and distribute the whole thing without any need for client licensing. I found the concept of Reporting Services 2000 somewhat compelling until I found that there was no way to render a report on the client side. It appears that Reporting Services 2005 closes this gap, as well as eliminating the need for a SQL Server license in order for clients to run reports. Crystal keeps disappointing me - this would be rather cool if it works decently...
Does anyone know if reports can truly be distributed freely this way, or if there are hidden licensing gotchas somewhere in the mix?
As noted in my
previous post, DataRow.ClearErrors isn't as useful as it could be, but seems to handle clearing errors quite reasonably as long as you can loop down to the DataRows in error. It's actually worse than I thought - ClearErrors will take the errors away from your row, but it doesn't trigger any events to tell anyone that the row has changed! So if you have UI bound to your DataSet, showing errors to the user, and then you call ClearErrors on the row, the component doesn't know and won't refresh itself unless you do something else. Reflector to the rescue... interestingly the property setter for DataRow.RowError and the DataRow.SetColumnError method both handle the events. Setting either a column or row error to null does remove the error. So my method to reset DataSet errors has been slightly expanded:
.csharpcode
{
font-size: 10pt;
color: black;
font-family: Courier New , Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0px; }
.rem { color: #008000; }
.kwrd { color: #0000ff; }
.str { color: #006080; }
.op { color: #0000c0; }
.preproc { color: #cc6633; }
.asp { background-color: #ffff00; }
.html { color: #800000; }
.attr { color: #ff0000; }
.alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0px;
}
.lnum { color: #606060; }
public static void ClearDataSetErrors(DataSet data)
{
if (data.HasErrors)
{
foreach (DataTable table in data.Tables)
{
foreach (DataRow row in table.GetErrors())
{
foreach (DataColumn column in row.GetColumnsInError())
row.SetColumnError(column, null);
row.RowError = null;
}
}
}
}
Quiz time: what is the value of test after the following code runs:
.csharpcode
{
font-size: 10pt;
color: black;
font-family: Courier New , Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0px; }
.rem { color: #008000; }
.kwrd { color: #0000ff; }
.str { color: #006080; }
.op { color: #0000c0; }
.preproc { color: #cc6633; }
.asp { background-color: #ffff00; }
.html { color: #800000; }
.attr { color: #ff0000; }
.alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0px;
}
.lnum { color: #606060; }
.csharpcode
{
font-size: 10pt;
color: black;
font-family: Courier New , Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0px; }
.rem { color: #008000; }
.kwrd { color: #0000ff; }
.str { color: #006080; }
.op { color: #0000c0; }
.preproc { color: #cc6633; }
.asp { background-color: #ffff00; }
.html { color: #800000; }
.attr { color: #ff0000; }
.alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0px;
}
.lnum { color: #606060; }
decimal test = decimal.Round(12.345m, 2);
12.35, right? No - imagine my surprise to find that it returns 12.34!! It turns out that .Net employs a type of rounding called "banker's rounding". When the rounding logic encounters a midpoint (like the 5 in my example), rather than always rounding up, it rounds to then nearest even number. Of course, in my situation, I needed the traditional "arithmetic rounding" which always goes up (or away from zero) when a midpoint is encountered. Finding no such feature in .Net, I wrote my own. If you see any bugs in it, let me know! (Note: code updated based on input from Ramon and Edward)
public static decimal ArithmeticRound (decimal d, int decimals)
{
decimal power = (decimal)Math.Pow(10, decimals);
// Note: converting to positive number before calculating rounding, so that we don't need separate logic for negative number handling
decimal result = decimal.Floor((Math.Abs(d) * power) + 0.5m) / power;
return result * Math.Sign(d);
}
BTW, .Net 2 provides an option on the Round function to use either type of rounding. See
http://msdn2.microsoft.com/library/wxhaazw6(en-us,vs.80).aspx.
Welcome to my blog! I've been blogging occasionally on http://joshualangemann.blogspot.com regarding my experiences with the .Net framework (particularly C#), but have been somewhat unhappy with Blogger for technical blogs. The nasty that finally drove me away was how Blogger automatically removes any characters, making it really hard to post readable code. BloggingAbout.Net graciously provided me with a spot on their site, and I'm looking forward to posting here with the rest of the bloggingabout community.
Over the next little while, I'll move the posts that seem to have value from blogspot onto this site. It doesn't look like the web interface provides a way to set the date of a post, so I guess these will come up as new posts on the dates that I move them over.
Hopefully some of this material will have value to you as you work with Microsoft.Net - I have found the blogs of others to be indispensable in solving .Net problems, and hope to provide some value back to the community with this site. Feel free to post comments if you find a post valuable, or have something to challenge or add.
I currently make heavy use of .Net typed datasets to handle the communication of data transactions between my business and UI layers. Recently we started getting into some serious application testing, and discovered various bugs where the application allowed bad data to be submitted and processed. Ooops - time for some validation checks! Hmmm... .Net datasets don't provide much validation out of the box! Some of the validations were clearly business rules that had to be verified on a case-base-case basis. However, I still wanted to provide the user with an adequate error message, and ensure that keyboard focus was placed on the column in question, so that the user could easily correct it. I really didn't want the forms displaying the data to need to be aware of the specific business rules - there were already several instances in the application where business rules were getting checked in the form, resulting in duplicated code that was next-to-impossible to verify with automated unit tests. So, how can I perform rules validation outside of the form and yet still provide enough information to the form for an optimal user experience? I needed some way to indicate the error message as well as the dataset table/row/column on which the error was occurring back to the UI layer, so that it could deal with the error display and set focus to the appropriate control on the form. DataSet errors quickly came to mind. DataTables have a feature that allows you to "set" an error on a DataRow and/or DataColumn, identifying that the row or column is in error. The interface is quite simple: just call the DataTable.SetColumnError function, identifying error text and a column. If you want to mark a row as having an error, but don't want to identify a specific column, set the RowError string property on the DataRow. OK - so it's easy enough to identify a row or column as having an error; what about making use of that information? Yes, you can programmatically identify the rows/columns in error on a dataset. In my case I needed to locate the errors and set focus to the appropriate cell in a C1TrueDBGrid. It's not as straight-forward as I would like, but here's the code that I used:
if (data.HasErrors)
{
// Loop through data tables, determine if we have a table bound to this grid
foreach (DataTable table in data.Tables)
{
if (table.HasErrors)
{
if (this.DataMember == table.TableName this.DataMember.EndsWith("." + table.TableName))
{
int i = 0;
foreach (DataRow row in table.Rows)
{
errorProcessed = this.setFocusToErrorOnRow(row, i);
if (errorProcessed)
break;
i++;
}
if (errorProcessed)
break;
}
}
}
}
Ideally the DataSet would provide a method to enumerate all errors, but no such luck. At least it tells us whether there are errors or not, so we can decide whether to go any further. For each DataTable, we check whether it has errors or not, and if so, loop through the rows and process each row. The row processing is hidden in setFocusToErrorOnRow, which loops through the results of DataRow.GetColumnsInError, a method that returns any columns on the row that have errors. Now that we have the rows and columns, we can do some magic to find them on the grid (sigh...), and finally set the focus! Another feature of DataSet errors is the integration with ErrorProviders. C1TrueDBGrid automatically detects the error and puts a nice little red icon on the column or row in error, making it obvious to the user where the problem is. One more thing: when revalidating a dataset, it is important to clear any errors previously added to it. You can do that by calling ClearErrors on the row. Would be nice to have that at the dataset level, but oh well; a few lines of code does the trick:
// Clear all existing errors
if (data.HasErrors)
foreach (DataTable table in data.Tables)
foreach (DataRow row in table.GetErrors())
row.ClearErrors();
Problem is, ClearErrors doesn't trigger anything that I can find back to the dataset to tell databound components to remove UI indications of the error. One more problem to figure out...
Here's something I posted on Mar 4, 2005
Recently I implemented a crash reporting capability into a .Net application. When an unhandled exceptions occurs (or application ThreadException), the user is presented with a dialog prompting them to submit it as a crash report. The dialog in turn calls a web service on my web server to accept the crash data.
This worked just great, so long as the user had direct HTTP access to my web server. However, it didn't work so well when they were behind a proxy server. The specific problem was with a Squid proxy server with authentication requirements. In this case, the web service call resulted in the error: "System.Net.WebException: The request failed with HTTP status 407: Proxy Authentication Required."
It turned out that the solution was relatively straight-forward. The web service wrapper provided by Visual Studio creates a class inherited from SoapHttpClientProtocol. WebProxy.GetDefaultProxy will retrieve default IE proxy settings for use in web commands. You can then set the user's default credentials on the proxy instances using CredentialCache.DefaultCredentials. Here is some example code:
CrashWebServices webServices = new CrashWebServices ();
// Set the proxy settings to the default IE settings
webServices.Proxy = System.Net.WebProxy.GetDefaultProxy();
// In case proxy authentication is required,
ensure that we make the call with the user's current credentials
webServices.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
string message = webServices.SubmitCrash(email,
crashData);
Hope this helps!
And another post from Jan 19, 2005:
Recently I had a need to switch focus to the next control on a form when the Enter key was pressed, under certain conditions. It seemed easy enough - just call SelectNextControl to have .Net read the tab order and select the next one in order, right? Yes, it was that easy, but it took a few attempts before I finally figured out how the SelectNextControl method worked!
SelectNextControl is a Control instance method, however, it also takes a Control as its first parameter. Initially I attempted to run it on the control that currently had focus, but couldn't figure out what to pass as the first parameter! Finally I realized that the method was intended to be run on the parent control, and the first parameter was the Control after which to select the next one. In my case, I needed to call the method on the Form, and pass it the control that had current focus.
To summarize, SelectNextControl is called on a parent control, such as a form. The first parameter is the control that you want to move away from, such as the currenntly selected control.
Here's a post from Dec. 11, 2004:
Recently I encountered a challenging situation in Windows Forms where I needed to sort the contents of a ComboBox involved in the middle of multiple levels of Data Binding. The combobox was on a window that provided a list of items on the top of the window, and details beneath. In the details section is a tab that provides an "Audit History" of changes to the items. Rather than displaying the entire audit history on the page, a combobox allows the user to select which change to view the audit for. Of course, it made sense that the combobox should be sorted in reverse chronological order, with the most recent changes at the top of the list, and the most recent change selected.
Sorting the combobox proved a real challenge. All of the information is managed by a dataset with datatables/datarelations joining the data elements together. I couldn't bind it to a DataView, because it had to participate as a child of the list as well as the parent of the audit details. But without a DataView, how could I sort the contents of the combobox?
First of all, I attempted to set the ComboBox.Sorted property, thinking it would handle the sorting automatically. To my chagrin, an error was promptly raised, stating that the property could not be used if the combobox was data bound. Argh! I suppose this wouldn't have done the trick anyways, since I wanted the contents sorted in reverse.
OK, what about the DataTable.DefaultView property? Surely I could set the DefaultView on the audit datatable to sort appropriately, and the sort would apply wherever that table was data bound, right? Not so! Setting properties of the DefaultView didn't help the sort at all.
Upon further research, I found that new DataViews were being created every time the current list row changed, to manage the data bindings of the child tables. In my Audit record situation, this meant that each time the user selected a new value in the list, a DataView was created in the background for the audit records, containing DataViewRows representing the audit records specifically for the selected item in the list.
By programmatically setting the Sort property of the generated DataView, its rows are automatically sorted. This rearranging of the rows plays nice with the data binding and currency management of the all of the child data tables under the Audit table. As well, the first record in the sorted list is automatically the current record for the table. This met my goal of automatically selecting the most recent audit record in the combobox.
The trick to using this, however, was to insert some code that would fire each time the Audit DataView was generated, to set the Sort property on it. Here is the code that I used. Note that Payment is the DataTable for the list, and PaymentAudit is the child table. PaymentAudit records need to be appear reverse sorted in the combobox.
In the form Load event, I bound up the CurrentChanged and PositionChanged events, as follows:
this.BindingContext[this.paymentListData, "Payment"].CurrentChanged +=new EventHandler(PaymentListTabs_PaymentChanged);
this.BindingContext[this.paymentListData, "Payment"].PositionChanged +=new EventHandler(PaymentListTabs_PaymentChanged);
And then, in the event handler for these events, I got a hold of the generated DataView and set the Sort property on it:
// Event handler that will run with the Payment Position or Currentitem changes
private void PaymentListTabs_PaymentChanged(object sender, EventArgs e)
{
// Sort the Payment Audit records if there are any
if (this.BindingContext[this.paymentListData,"Payment.PaymentPaymentAudit"].Count > 0)
((DataRowView)this.BindingContext[this.paymentListData,"Payment.PaymentPaymentAudit"].Current).DataView.Sort = "PaymentAuditId DESC";
}
This way, all of my controls could be bound up using standard .Net data binding, including the ComboBox. The audit list in the ComboBox is sorted in reverse chronilogical order, and properly re-evaluates whenever the list position is changed, data changes, or new data is merged into the dataset.
Happy coding!
Josh