Thursday, April 10, 2008

My Startup Script

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

 

I don't like re-starting my machine because I need to re-open all the misc windows and files I have loaded. However, because I still need to reboot, I wrote a simple script to open common programs and files for me.

REM: Open Windows Explorer

start explorer
start explorer

REM: Open NotePad

start notepad
start notepad "C:\MyDocuments\temp.txt"

REM: Open the DOS command window, default to a certain folder

start cmd "/Kcd C:\Utils\MyTasks"

REM: Open Internet Explorer

start /B /D"C:\Program Files\Internet Explorer" iexplore.exe

REM: Open a Visual Studio solution

start /B /D"C:\MySolutions\StandardFiles" StandardFiles.sln

This opens five main things:

  • Windows Explorer

  • Notepad - a blank version, and one that stores a simple scratchpad of notes. You could also open other scratchpads.

  • Internet Explorer (could just as easily be firefox). There are also ways to pre-populate the tabs.

  • Cmd window - I set it to a directory that has a bunch of misc scripts (using the "/K" switch to call the CD command)

  • Visual Studio - In this case I open a solution folder with a bunch of misc xml files that I use.

Of course you can add other programs to the list too.

 

Lastly, I made a shortcut of this batch script on my desktop, so I just click it when Windows loads up. (You could probably automate the startup tasks if you want).

Wednesday, April 9, 2008

Silverlight Xaml error: "Length cannot be less than zero. Parameter name: length"

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

I was getting a strange Silverlight compile error in my Page.xaml the other day (while migrating stuff from 1.1 Alpha to 2.0 Beta):

Length cannot be less than zero.
Parameter name: length

At first it sounds like I was setting a wrong value - like trying to reference the "-1" position on a string. But, it actually was a constraint in what namespaces Xaml allows.

 

In 1.1 Alpha, this would be ok:

xmlns:Tank.Core="clr-namespace:Tank.Core;assembly=Tank.Core"

Which you could then reference like so:

However, that line kept throwing the error when I tried to compile.

 

It seems like if I remove the period ".", then it works again, like so:

xmlns:Tank1="clr-namespace:Tank.Core;assembly=Tank.Core"

Strange. I'm not fully sure why, maybe some parsing thing with periods "." in beta, but it was good to have a work-around.

Tuesday, April 8, 2008

Excuses for installing a game on your work laptop

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

Of course work laptops are for work, and should only be used for "company purposes". So, how do some people justify installing games on their work machines:
  • "It's part of the operating system - Windows won't run unless I install these games. Really."

  • "By installing on my main work laptop, I'm using that laptop more, so I'm more familiar with how it works and I can more easily check work-related tasks (like email and IM, and things that require the VPN)."

  • "These games have technical educational value, and are therefore not really 'games', but rather learning tutorials that benefit the company."

  • "I'm not 'playing the game', I'm studying it to better understand how to make functional user interfaces."

  • "I'm just analyzing the deployment experience so I can glean from it and apply it to our own product."

  • "This isn't really a game, it's actually a test project that I wrote myself (such as with XNA or Silverlight)."

  • "This game was built by one of our clients, so I'm just studying their products to help me conduct better client outreach."

I'm sure there's more.

Thursday, March 27, 2008

Fast cloning of dropdowns

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

There are times that you'll want to dynamically add items to an html dropdown using JavaScript. It can be a huge performance gain, even with Ajax and update panels. You can easily do this like so:

 

      function DoStuff()
      {
        var selectbox = document.getElementById("Select1");
        addOption(selectbox, "AAA", "1");
        addOption(selectbox, "BBBBB", "2");
        addOption(selectbox, "CC", "3");
       
      }
   
      function addOption(selectbox, strText, strValue )
      {
        var optn2 = document.createElement("OPTION");
        optn2.text = strText;
        optn2.value = strValue;
        selectbox.options.add(optn2);    //This line is very slow
      }

 

The problem I wanted to solve was how to do this when adding 4000 items? It could take several seconds, and be very slow. (Yes, ideally a simple dropdown by definition doesn't have that many items, but let's say there are legacy constraints there, and the client wants a dropdown). For example, say you have a grid, each row has a dropdown, and that dropdown may be huge. A much faster way is to load only 1 dropdown, make it hidden (via JS on the client), and then clone it's nodes.

 

For example, have a hidden dropdown, which you populate from whatever server values:

 

<span id="Span1">span

And then, call this function to clone those values. It will clone the entire dropdown (sparing you from calling the very slow options.add method 4000 times), and then dump it in some nested container (like a span):

 

      function cloneDropdowns_fast(strSourceId, strContainerId)
      {
        o = document.getElementById(strSourceId).cloneNode(true);
        o.style.visibility = "";
        var container = document.getElementById(strContainerId);
        container.appendChild(o);
      }

 

      function PopulateDropdown()
      {
        cloneDropdowns_fast("Select2", "Span1");
      }

 

So, calling the method "PopulateDropdown" will copy the entire dropdown from "Select2", and dump a whole new dropdown in the "Span1" container. It works much faster (and appears to work in both IE and FF).

Monday, March 17, 2008

Tips for converting from Silverlight 1.1 Alpha to 2.0 Beta

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



As I convert some Silverlight 1.1 Alpha apps to the new 2.0 Beta, I'm running across a lot of issues. So far, all have been solvable. Besides the breaking changes documented on MSDN, here are some others. Also, check the Silverlight Bugs forum if something just doesn't appear to be working right.

Silverlight app does not appear to automatically clip an object moving outside the canvas. I.e. if you define a canvas at 100 x 100, and place an image at (200,200), it will still show up. This can be a problem for widgets with moving parts, especially if the location for those parts are based on complex rules (i..e. the physics engine for a game). You can solve this by having your html host page explicitly set the Silverlight apps' boundaries (instead of using percentages):

            <object data="data:application/x-silverlight," type="application/x-silverlight-2-b1" width="300" height="400">

Image source uses strongly typed bitmap instead of a string url source. You can still dynamically load images.

Downloading xml files by default is now asynchronous. This quickstart tutorial explains how to download files. Sure, it is often a good practice to download the file asycnh so that you do not block the main UI thread. But, for small "legacy" apps that you just want to convert quickly, which download small files from the same server as the silverlight app (i.e. no cross domain), where security is not an issue - it's nice to just have a quick synch method to download files. JackBond wrote a good wrapper utility to get around this, re-pasted here for convenience:
//Method from JackBond at: http://silverlight.net/forums/t/11508.aspx
public static string DownloadXmlStringSync(string url)
{
  ScriptObject request;
  request = HtmlPage.Window.CreateInstance("XMLHttpRequest");

  request.Invoke("open", "GET", url, false);
  request.Invoke("send", "");

  return (string)request.GetProperty("responseText");
}
The source for relative paths has changed, especially when referenced from a user control. Before, relative paths were referenced from root directly. Now, they appear to be referenced from ClientBin (I think). One easy work around is to always just convert your relative paths to absolute ones.

UserControls are no longer just Canvases. This is good and bad.
  • The good is that you now have strongly-typed names for all your controls. No more calling this.GetType().Assembly.GetManifestResourceStream, and then doing (root.FindName("TxtMessage") as TextBlock).Text = strText. Rather, you can just say this.TxtMessage.
  • The Bad is that (at least to my understanding) you cannot make a base class that all your UserControls inherit from. Normally (like in ASP.Net or with 1.1 Alpha Canvases), you could make MyBase which derives from Control, and then have all your custom controls inherit MyBase. But now in Silverlight, you cannot do this because the generated partial class from the Xaml hard-codes a reference to UserControl, and that locks you in. Although, I hope I missed something there, or it's changed in the next beta, because using abstract base classes obviously has its benefits.
Practically, I found it easiest to just delete my Canvas files, and then re-create a UC with the same name, and then manually copy the old canvas code back in.

Reminder: Storyboard needs to be started from Page_Load, not the constructor. Thanks to Bill Reiss for pointing this out. While not a breaking change, it's a good reminder. Before, a Silverlight User Control (i.e. based on the Canvas element) had a Page_Load event, so you could start your StoryBoard from that. But now, the UserControls have a constructor. So, you'll need to add the Load event, and then start your story board from that. Else, you'll waste time like I did wondering "why doesn't this storyboard start?"

Clicking a button, and then making that same button invisible in its click event, causes an exception. More documented on the forums. One suggestion is to set the size to 0.

Third Party controls may mess up your references. If you were using 3rd party controls, it may mess up your name spaces and you get a "Cannot find that type" error. For example, I used a 3rd party button control, and then it could no longer find the new Beta 2.0 buttons, so I kept getting a "Cannot find class or namespace Button...." compile error. I needed to manually add this to the csproj file in the <ItemGroup> for the References:
       
    <Reference Include="System.Windows.Controls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

Application_Exit unexpectedly hit by attaching the mouseHandler to a canvas. It's hard for me to describe this one. But, if you're walking through some mouse-handling code where you attach and detach it, or call the GetPosition on a target canvas that's been removed from the Page , it may throw an exception. I know that's ambiguous, but it's something I was seeing.

Friday, March 14, 2008

Dynamically load an image in Silverlight 2.0 Beta

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

You can easily dynamically load images in Silverlight. given a relative url, you can convert that to an absolute url, and then to an ImageSource, and then use that to set an image. Here's a sample method that takes the relative path, the parent canvas that we'll add the image too, and an x-y point:

 

    private void AddImage(string strRelativeImagePath, Canvas myCanvas, Point p)
    {
      //Convert from RelativePath to AbsolutePath to Uri to ImageSource
      string strFullUrl = GetAbsoluteUrl(strRelativeImagePath);
      Uri u = new Uri(strFullUrl, UriKind.Absolute);
      System.Windows.Media.Imaging.BitmapImage b = new System.Windows.Media.Imaging.BitmapImage(u);

      //Create the image here
      Image img = new Image();
      img.Source = b;
      img.SetValue(Canvas.LeftProperty, p.X);
      img.SetValue(Canvas.TopProperty, p.Y);
      //set other properties here...

      //now add to a canvas:
      myCanvas.Children.Add(img);
    }

 

And just call it like so:

 

    AddImage("MyImage.png", this.parentCanvas, new Point(100, 50));

 

Note some changes from the 1.1 Alpha:

  • Image.Source is no longer just a string url, but rather a strongly-typed class. You need to create an ImageSource object first, such as by instantiating a Bitmap image (this allows other image types, like png, not just bmp).

  • The relative-to directory has changed, such that it's easiest to just convert to an absolute url. When you directly add this to the page, it works fine, but if you add it to a userControl from another class library, then it doesn't. Just using absolute urls makes it work (hence the GetAbsoluteUrl method I blogged about yesterday).

Thursday, March 13, 2008

Silverlight 2.0 Convert Relative Url Paths to Absolute

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

As you've probably heard, Silverlight 2.0 Beta 1 is out. You can get it from Silverlight.Net. And Bill Reiss is starting another great tutorial series on Silverlight games.

 

I see a lot of breaking changes (as documented on MSDN), and I'll blog more about my adventures upgrading. One change is that it seems like you need Absolute urls in a few more places (like for accessing xml files or images). Because I like to think in terms of relative urls, here's an easy utility to convert relative to absolute. It should handle both file paths (file:///aaa) and web (http://aaa).

 

    public static string GetAbsoluteUrl(string strRelativePath)
    {
      if (string.IsNullOrEmpty(strRelativePath))
        return strRelativePath;

      string strFullUrl;
      if (strRelativePath.StartsWith("http:", StringComparison.OrdinalIgnoreCase)
        || strRelativePath.StartsWith("https:", StringComparison.OrdinalIgnoreCase)
        || strRelativePath.StartsWith("file:", StringComparison.OrdinalIgnoreCase)
        )
      {
        //already absolute
        strFullUrl = strRelativePath;
      }
      else
      {
        //relative, need to convert to absolute
        strFullUrl = System.Windows.Application.Current.Host.Source.AbsoluteUri;
        if (strFullUrl.IndexOf("ClientBin") > 0)
          strFullUrl = strFullUrl.Substring(0, strFullUrl.IndexOf("ClientBin")) + strRelativePath;
      }

      return strFullUrl;
    }

 

More to come soon...