Tuesday, December 11, 2007

Silverlight Image Utilities - clipping and shrinking an image to fit

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/silverlight_image_utilities__clipping_and_shrinking_an_imag.htm]

For my TruckWars game, I needed to get the profile shot for an image. For example, when you select an object, it displays an image of that object in the dashboard. The two problems are:

  1. What if the image is bigger than the dashboard's profile size? (i..e image is 96x96, but the dashboard only allows 48x48).

  2. What if the image has multiple frames for animation (i.e. image displays as 48x48, but has two frames, and therefore actual size is 96x48).

I wanted a way that given any image, it would clip the image to a single frame, and then shrink it to fit within the allowed-profile size. You can do this if you know the actual image size. In this case, the size of the profile image in the dashboard is 48 pixels. Because I make the animation frames be horizontal (i.e. something with two frames is twice as wide), I scale the image based on height. Then I clip to just the first frame.

 

    public static void MakeProfileImage(ref Image image1, Size szActual)
    {

      //Scale back the img to fit to the view size
      const double profileHeight = 48;
      double dblScale = szActual.Height / profileHeight;
      image1.Width = szActual.Width / dblScale;
      image1.Height = profileHeight;

      //Always clip the view size (in case there were multiple frames for animation)
      RectangleGeometry r = new RectangleGeometry();
      r.Rect = new Rect(0, 0, profileHeight, profileHeight);
      image1.Clip = r;
    }

 

I added this to a ImageUtilities class for reuse later.

 

Monday, December 10, 2007

Silverlight and Globalization: System.FormatException from NumberFormatInfo

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/silverlight_and_globalization_systemformatexception_from_n.htm]

Through recent error-logging that I added to TruckWars, I found out the sometimes this line would fail:

 double x = Convert.ToDouble("12.5");

 

The input string was static (not user input) as it came from an xml config file. The line threw this exception:

System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options,
    NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt)
   at System.Double.Parse(String s, NumberStyles style, NumberFormatInfo info)
   at System.Convert.ToDouble(String value)

At first I thought "of course '12.5' is a valid double." And of course it worked on my machine, and all the servers I was checking. But, the logs still showed this occasional error. Then I thought - what if it's a globalization problem? In other words, for the "en-US" culture, "12.5" is a valid number, but not for other cultures. For example, this would fail for someone in France, where "12,5" is used for a decimal place (note the comma instead of the period).

 

So, I set up a unit test to check for a different culture:

[TestMethod]
public void ParseFromString_Global_Decimal()    
{      
  System.Globalization.CultureInfo culture =
  System.Globalization.CultureInfo.CreateSpecificCulture("fr-FR");
  System.Threading.Thread.CurrentThread.CurrentCulture = culture;

  //run my parsing method here

  //ensure that culture wasn't overridden:
  Assert.AreEqual(culture.Name,
   System.Threading.Thread.CurrentThread.CurrentCulture.Name);
}

And the test failed with the exact error that I expected. I could then fix it by passing in a specific culture (like "en-US") NumberFormat:

private static System.Globalization.NumberFormatInfo _formatNumber =
System.Globalization.CultureInfo.CreateSpecificCulture("en-US").NumberFormat;

//essentially fixed by this:
double x = Double.Parse("12.5", _formatNumber);

I don't normally get this error because for our ASP.Net apps I use a set of utilities that already handled this, and I didn't need to worry about international users for the  windows forms because I only make those for internal development tools. However, it's another reminder why it's nice to have logging for even simple apps.

Sunday, December 9, 2007

Becoming a credible developer

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/becoming_a_credible_developer.htm]

Every developer wants to be taken seriously. We want to be credible. But what makes someone credible?

 

I've seen a lot of devs who think that being credible means:

  • Sending out a link to a guidelines document

  • Keep referring to your  last project (that no-one else on your current team was on) as that perfect project

  • Throwing out buzzwords

  • Never admitting that you're wrong

The problem is that all of these require little effort and don't really help anyone else. They don't distinguish the speaker - anyone can send out links, bluff about a former project, or throw out buzzwords.

 

I think a much better way to see if some is credible is if they:

  • Have experience in the problem domain

  • Have work products they can point to (i.e "I made this website", "I wrote this tool")

  • Can accurately predict what will happen (not "try this", but rather" do this and you'll get that")

  • Have a reputation of being correct (including other credible people who will back them up - i.e. "good references")

  • Can point to the official source (not "I heard on some blog that...", but rather "The MSDN reference spec for C# 2.0 says ...")

  • Are willing to invest their own resources into the approach or product (i.e. dogfood it). If someone won't even put their own resources in, then they probably don't believe in the approach.

You can't easily bluff these things. For example, your prediction either comes true or it doesn't (if it's ambiguous, then it's a bad engineering prediction); you either have a concrete product you can show people, or you don't. I think every industry leader you find does these things - they have tons of experience and products, make accurate predictions, have a reputation that precedes them, they write the official source, and they dedicate their lives to their cause. Now that's being credible.

Thursday, December 6, 2007

Create an object dynamically with CreateInstance using Reflection

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/create_an_object_dynamically_with_createinstance_using_refle.htm]

You can instantiate an object dynamically using Reflection. For example, the boards in TruckWars are stored in Xml files. Each board has a list of creatures:

 

  <Creatures>
    <Creature type="TankUnit" boardPosition="1.5, 7.5" team="Hero" />
    <Creature type="PickupTruck" boardPosition="1.5, 2" team="Hero" />
    <Creature type="PushButtonStayDown" boardPosition="13.5, 1.5" />
    <Creature type="TankUnitEnemy" boardPosition="14.2, 7.5" team="Enemy1" />
  Creatures
>

 

You could use an xml reader to cycle through this, and at each creature node, dynamically create a creature object. The "trick" is to have a base type (like "CreatureBase") that all your objects inherit from. You then specify the type in the xml file, and use the CreateInstance() method to dynamically create an object (to my knowledge, this requires that the base type at least have an empty constructor):

 

        CreatureBase c = (CreatureBase)Assembly.GetExecutingAssembly().CreateInstance(CreatureNamespace + "." + strType);
        c.Position = strPositionSerializedFromXmlAttribute;
        c.Team = (Team)Enum.Parse(typeof(Team), strTeam, true);    //makes an enum
        c.Name = strName;

 

You can then serialize the xmlNode's attributes and use them to set properties on that object. Thanks to polymorphism, the object will act as the derived type (for example, it will call the derived type's overridden methods).

 

This technique is often used in enterprise architecture for extensibility. The core system creates the base class, but then you can override it and set some xml config file to use your derived type.

 

Wednesday, December 5, 2007

Deploying Silverlight Apps

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/deploying_silverlight_apps.htm]

Silverlight is awesome to deploy. As this forum thread describes, you basically just need your web server to setup the right mime type ("Extension should be .xaml and the Content Type should be application/xaml+xml". I also had ".dll --> application/x-msdownload" because some other blogs recommended it).

 

You can then just ftp the files from your silverlight project (ClientBin, files in the root folder, and any other directories), to your server.

 

After dealing with Winform, ASP.Net, and XNA deployment problems, I was floored that it just actually worked.

 

Because Silverlight runs on the client machine, it is the client's responsibility to have the right stuff installed. However, silverlight makes that really easy by rendering as a single-click download link if the client doesn't have it installed yet.

 

Also note that Silverlight is not a server technology like SQL2005 or ASP.Net. You don't even need an ASP.Net server to run Silverlight. (This is especially practical if you're a hobbyist on a budget, and your host company charges extra to asp-enable your site.).

Tuesday, December 4, 2007

Using Path.Combine instead of string concatenation

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/using_pathcombine_instead_of_string_concatenation.htm]

I often need to work with filePaths, such as getting an absolute file name given a directory and a fileName. One way for developers to do this is simply string concatenation. A lot of devs do this because it appears so simple. But the problem is that when passing in the directory as a string, you often don't know if it will end with a final slash or not - i.e "C:\Temp" or "C:\Temp\". Merely doing string concat will screw it up, you could get a non-existent file like "C:\Temp\\MyFile.txt".

 

A much better approach is to use System.IO.Path.Combine. This allows you to have a directory (with or without the slash), and the combine method if smart enough to handle it:


    public static string GetFileName(string strDir, string strName)
    {
      //return strDir + @"\" + strName;   //BAD
      return System.IO.Path.Combine(strDir, strName); //GOOD
    }

 

What's funny is that both options are just 1 line of code and take essentially the exact same amount of time to write. However, the first is very brittle, and could likely cost you tons of time tracking down an error because someone passed in a directory with or without that extra slash. The second just works.

Monday, December 3, 2007

Silverlight 1.1 global page error handler

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/silverlight_11_global_page_error_handler.htm]

When writing Silverlight TruckWars, I was trying to figure out how to create a global error handler for a page. Eventually I saw the WebApplication.Current.ApplicationUnhandledException event, which I could hook up like so:

 

    public void Page_Loaded(object o, EventArgs e)
    {
        //....
        WebApplication.Current.ApplicationUnhandledException +=
            new EventHandler
            (Current_ApplicationUnhandledException);
    }

 

    void Current_ApplicationUnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
    {
      HandleError(e);
    }

 

Note that Silverlight handles errors differently than ASP.Net. In ASP, you get the yellow error page with a detail exception message (by default). However, in Silverlight, it just stops executing your code and returns to the caller, leading to weird behavior.