Friday, October 31, 2008

The Visual Studio Start Page

Have you ever wondered why on earth there is a start page in Visual Studio? I know I have. Usually I only click a recent solution here, but today my eye stuck on some info in the news presented here. You can now get a free copy of CodeRush and Refactor! Pro (with limited features of course).

You can find more info here, where Charlie Calvert writes about it, and you can download it here.

So now we all have a good reason to stroll trough the news on the Visual Studio start page every now and then.

Thursday, October 30, 2008

Lookup Combobox in Silverlight 2

I ran into this problem while building a proof of concept to introduce SilverLight 2. I needed a datagrid, with several columns where the values of these columns are determined by a single combobox. Putting the combobox in place was easy enough, but getting the other columns to react instantly got me googling for quite a bit (altough in the end the solution is obvious).

As an example for this post, we will use a car database (cause I love cars). A car has a brand, a type and an engine. An engine consists of a type, a fuel and a number of cillinders. I've placed a datagrid in my page.xaml and configured it a bit. It then looks like this:


The Engine column contains a combobox that appears whenever you edit a row. The Fuel and Cillinders columns are actually databound against the engine class, like this:


<?xml:namespace prefix = data /><data:datagridtemplatecolumn header="Engine">
<data:datagridtemplatecolumn.celltemplate>
<datatemplate>
<textblock margin="4" text="{Binding Engine.TypeCode}">
</datatemplate>
</data:datagridtemplatecolumn.celltemplate>
<data:datagridtemplatecolumn.celleditingtemplate>
<datatemplate>
<?xml:namespace prefix = cardemo /><cardemo:fixedcombobox selecteditem="{Binding Engine, Mode=TwoWay}" itemssource="{StaticResource Engines}">
<combobox.itemtemplate>
<datatemplate>
<stackpanel orientation="Horizontal">
<textblock width="40" margin="4" text="{Binding TypeCode}">
<textblock width="40" margin="4" text="{Binding Fuel}">
<textblock width="20" margin="4" text="{Binding NumberOfCillinders}">
</stackpanel>
</datatemplate>
</combobox.itemtemplate>
</cardemo:fixedcombobox>
</datatemplate>
</data:datagridtemplatecolumn.celleditingtemplate>
</data:datagridtemplatecolumn>
<data:datagridtextcolumn header="Fuel" isreadonly="True" binding="{Binding Engine.Fuel}">
<data:datagridtextcolumn header="Cillinders" isreadonly="True" binding="{Binding Engine.NumberOfCillinders}">
</data:datagridtextcolumn></data:datagridtextcolumn>
<data:datagridtextcolumn header="Fuel" isreadonly="True" binding="{Binding Engine.Fuel}"><data:datagridtextcolumn header="Cillinders" isreadonly="True" binding="{Binding Engine.NumberOfCillinders}"></data:datagridtextcolumn></data:datagridtextcolumn>

But like this it won't update the read-only columns for fuel and cillinders whenever I choose a different engine. To do this, we need to implement the INotifyPropertyChanged interface on the car class (in fact I would advise implementing it on all your databound classes):

public class Car : INotifyPropertyChanged
{
private Engine _engine;
private string _brand;
private string _type;

public Engine Engine
{
get
{
return _engine;
}
set
{
_engine = value;
RaisePropertyChanged("Engine");
}
}

public string Brand
{
get
{
return _brand;
}
set
{
_brand = value;
RaisePropertyChanged("Brand");
}
}

public string Type
{
get
{
return _type;
}
set
{
_type = value;
RaisePropertyChanged("Type");
}
}
#region INotifyPropertyChanged Members

private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

public event PropertyChangedEventHandler PropertyChanged;

#endregion

Now, the datagrid will subscribe to the PropertyChanged Event of your class as it binds to it. Then, whenever you choose a different item in the combobox, this changes the engine property and triggers the event. This in turn triggers the datagrid in updating the other columns.

Update: As of the December '08 release of the datagrid, a fix on the combobox is no longer needed.

Wednesday, October 15, 2008

Getting to know your software

I've spend the last two days on a course for basic use of the software I'm going to improve. Seems like a waste of time? Read on.

Getting to know your software as a user
Developers of software are used to looking at software as a developer. There are very few times a developer looks at software as a user. What is the difference, I here you ask?

A user looks at software and sees what work is needed to reach his or her goals. The user values software based on how much work is needed to reach the goal and how much of it is tedious in the software.

A developer looks at software and thinks about the technology behind it and how much work it was to do this particular feature that he or she thinks is so great.

The fact of the matter is that a lot of the times, developers spend a lot of time building features that the user doesn't use very often. This sounds like it's frustrating for the developer, but he or she will hardly ever know this (thinking the lack of support calls is a testament to the greatness of the code and not to the lack of usage :-) ).

To improve this, playing around with your own software in a course can be very helpful. It's usually more productive than doing this at your usual desk, because of the distractions that are around. Also, a good trainer encourages you to work with the software and provide some practice examples.

Improving user awareness
The traditional way of improving on this would be to interview key users, but this is very unusual in a product development setting. Requirements and functional specifications are written by a department inside the company and developers just need to build according to these specifications.

So another way to get to know your users is to go to a user course of your own software.
This not only puts you in contact with some users, but it can also point out weaknesses in the functionality (or even technical issues, but then something is wrong in the testing process). A user course can do that trough the training material. Another thing to look for is explanations of the instructor. Ask yourself "why does this need explaining?".

And last but not least, questions of the users themselves are a valuable input. It may very well be that you would consider a screen perfectly intuitive, but your user may not understand it at all.

Conclusion
A basic user course a waist of time? I'd say no and encourage all developers to give this a try.

Monday, October 13, 2008

Resizing images

It was one of those days again. You build someone a .NET library to convert WMF images inside an OLE package to a clean JPEG file from MS Access and the first thing they say is, "Can it resize the image?". I'll spare you the why and skip right to the how :-).

Requirements
First thing to think about is how you want this resizing to behave. You could resize just for size, but in our case we wanted to save space. Also we wanted to keep the aspect ratio and we only wanted to shrink images, not make them larger.

Keeping aspect ratio meens that only one of the dimensions can be specified. In our case this was always going to be the height, which would be specified in millimeters.

The process
It takes the following steps to resize an image based on a new height:
  1. Calculate the vertical resolution of the image in pixels/mm
  2. Calculate the new height
  3. Calculate the factor by which the image is being sized
  4. Calculate the horizontal resluotion in pixels/mm
  5. Calculate the new width based on the old one, the horizontal resolution and the sizing factor

The code
So let's dive into the code. First thing we need is a field and a constant to store some values in:

private const double mmPerInch = 25.4;
private double _sizingFactor;


The first step is to calculate the vertical resolution of the image in pixels/mm:

double pixelsPerMm = image.VerticalResolution / mmPerInch;


Then we can calculate the new height, taking into account that we don't want to change heights that are smaller then the preferred height:

if (image.Height / pixelsPerMm <= height)
{
_sizingFactor = 1;
return image.Height;
}
double newHeight = pixelsPerMm * height;


After this we can calculate the factor by which the image is being sized:

_sizingFactor = height / (image.Height / pixelsPerMm);


Then we calculate the horizontal resluotion in pixels/mm:

if (_sizingFactor == 1)
{
return image.Width;
}
double pixelsPerMm = image.HorizontalResolution / mmPerInch;


And we calculate the new width based on the old one, the horizontal resolution and the sizing factor:

double newWidthMm = (image.Width / pixelsPerMm) * _sizingFactor;
double newWidth = newWidthMm * pixelsPerMm;


Finally, resizing the image is easy. I've wrapped the code in some methods and constructed a Size construct:

int newHeight = CalculateHeight(image, height);
int newWidth = CalculateAspectRatioWidth(image);
return new Size(newWidth, newHeight);


Then we can resize an image like this:

Bitmap outputBitmap = new Bitmap(bitmap, CalculateNewSizeByHeight(bitmap, height));

Thursday, October 9, 2008

MS Reporting Services 2005 non-standard layout tip

One of my collegues asked me to find a way to display data in a SSRS 05 report in three columns, much like an address cards page. The obvious way would be to just have three datasets, but this is not an ideal approach.

I've been checking out some different possibilities and came up with more then one solution (which is always a good thing, right?)
A common factor in all this is that you need row numbers. Alltough SSRS supplies rownumbers trough the RowNumber() function, you can not use this function in either grouping or filtering, so I needed an alternative.
Looking into getting the rownumber from the database, I decided to have a go with the Row_Number() statement in T-SQL. This proved to be a winner.

I've created two solutions by using the row number. One approach is to assign each row a number according to the column that the row should be displayed in, simply by deviding the row_number by three and using the remainder as my group number.
Then I used three lists that filter the data on the group number. Inside the list I use a rectangle as a container for the data, so each row takes the same physical space in the report.

Another way of doing things is using a matrix and calculation two numbers, one group number, like with the lists approach, and one number that is calculated by adding 0.5 (this is needed for rounding purposed) dividing by three and rounding the result. This results in each set of three records having a number (so record 1 trough 3 have number 1 and records 4 trough 6 have number 2, etc.). Now you can use the first field as a groupfield for the columns and the second field as a groupfield for the rows.

Another way that might work (haven't tested this one) is to use a table.