Making The WPF Slider Control Behave

December 18 2008

I recently burnt a little more time than I would have liked making the WPF Slider Control do something simple. Basically, I wanted a slider that would have a minimum value of 0 and a maximum value of 1 with increments of .01.  The Value property of a slider is of type double and when you use the slider it increments/decrements with quite a bit of granularity, to like .0000001 or more.  At first, you'd think setting the SmallChange and LargeChange would fix this, but these properties only affect when the user uses arrow keys or page up/page down keys to change the slider. Direct mouse manipulation doesn't respect these values. So what to do?  Well, you can set the TickFrequency to the value of change you want (in my case .01) and then set the IsSnapToTickEnabled to true. Finally, I set the TickPlacement to "none" because 100 tick marks didn't look so hot. Here's the final XAML:

<Slider
      x:Name="SliderFrom"
      Width="230"
      Height="21"
      TickFrequency=".01"
      IsSnapToTickEnabled="true"
      TickPlacement="none"
      Value="{Binding Path=From, Mode=Default, UpdateSourceTrigger=PropertyChanged}"
      Maximum="1"
      Minimum="0"

/>                          

Oh, and you'll notice that to make any changes on the slider propagate immediately to my data object, I also changed the UpdateSourceTrigger to PropertyChanged.

Notes On Doing Development For Windows Live Writer for Oomph

December 10 2008

When we decided to tackle the project of making Microformats easier to consume, we were naturally drawn to Windows Live Writer (WLW) as a content publishing mechanism.  By targeting WLW, we were able to target all blogging platforms, as opposed to writing specific add-ins for each different blogging platform. It is a great piece of software that comes highly recommended.

I found writing the Windows Live Writer Plug-in to be quite simple and straight forward.  I was impressed with their plug-in model in terms of placing a .dll in a directory and having WLW load that .dll on your behalf. Very elegant. 

To get started, I thought this video on Channel9 was informative and gives a good overview of doing WLW plug-in development. It’s pretty straightforward. You just need to add a reference to the WindowsLive.Writer.Api.dll. Then, once I got into actually doing development, I found the the Windows Live Writer plug-ins project indispensable in terms of getting started. There’s some great projects in there.  I also found myself turning to the WLW SDK quite a bit, especially for dealing with deployment of the plug-in, since we install it ourselves as part of our installer. 

Here’s a gotcha I hit: if you’ve installed the latest beta of Windows Live Writer, your WindowsLive.Writer.Api.dll will be a later version than folks who haven’t installed the beta. This will cause problems. (Who says .net is free from dll hell?) Be sure to add a reference to the older version (12.0.1367.1128) and not the newer version (14.0.5025.904).

One thing to keep in mind when doing development is that you have to attach to the WLW process from Visual Studio if you want to debug into your plug-in. 

Another thing that someone had done in the codeplex project, which I learned from, was to add a post-build step to my plug-in which automatically copied it to the plugins directory for WLW. That way, you can immediately see reference the latest

XCOPY /D /Y /R "$(TargetPath)" "C:\Program Files\Windows Live\Writer\Plugins\"

Of course, if your program files is on a different drive, this won’t work.  Also, it is a little confusing, because the beta of Windows Live Writer installed into C:\Program Files\Windows Live Writer but later versions installed to C:\Program Files\Windows Live\Writer. So, if you are still using the beta, you’ll need to modify this.

Similarly confusing is the way to get your plug-in up on the Windows Live Writer Gallery.  First off, realize that you have to create an .msi, which is relatively easy using the Setup and Deployment template in Visual Studio or using the WIX installer.  I used the Setup and Deployment template as opposed to WIX (more on WIX to come in a future post).  Ironically, we used WIX to build the installation for Oomph overall, which, in retrospect, I wish I’d have gone ahead and used, but that was another learning curve that I just didn’t feel like hitting. A couple gotchas here withthe Setup and Deployment template in VS: first, be sure to remove the dependency on the WindowsLive.Writer.Api.dll, as there is no need for you to be redistributing that. Second, you’ll want to make sure that the user installs the plug-in to the WLW plug-ins directory.  To do so, modify the DefaultLocation in the Application Folder to the following: [ProgramFilesFolder]\Windows Live\Writer\Plugins.  The other option here would be to  set a reg key in

HKCU\Software\Windows Live Writer\PluginAssemblies

With the string value of “Oomph.ContactsPlugin=[the installed location to the ContactsPlugin.dll file]”

Then, you can stick the .dll where ever you want to on the user’s file system.

Once you create the .msi, you should be good to go; you can upload the .msi to the WLW gallery and then, after it is reviewed, see it live!

One final thing: thanks to Tim Heuer for doing some clean up on the plug-in, which shipped with Oomph 1.1.

BlendSense: XAML Intellisense for Expression Blend

December 10 2008

So, there’s an add-in for Blend that supports Intellisense in XAML.  Pretty nifty!  However, to get it working in Blend 2 SP1, requires recompilation of the .dll. I went ahead and made a package that supports Blend2 SP1. Of course, you’ll need Blend 2 - available for trial download here. Additionally, if you are doing Silverlight development, you’ll need to install Service Pack 1 for Blend 2. The installation directions are as follows:

Installation

  • Extract the contents of .zip in your Blend 2 installation folder (typically %ProgramFiles%\Microsoft Expression\Blend 2)
  • Run Blend from the Blend.bat file that was extracted into Blend's installation folder. (The only way to run an add-in in Blend is to use a command line argument: -addin: pathtothe_addin - in our case -addin:Addins\Expression.Blend.IntelliSense.dll. You can also create a shortcut to Blend and pass this argument.)

Limitations

  • No support for custom namespace types and custom attached properties.
  • Currently the Silverlight support is not accurate, because it is using the WPF XML schema for IntelliSense information.

Virtual Earth, GeoCoding on the fly and Oomph

December 10 2008

One big chunk of work during Oomph development was getting the Virtual Earth aspect of the code working.  I ended up combining two features of Virtual Earth. On the one hand, I might have a lat/long, from the Geo Microformat. In that case, I could just pass the lat/long to Virtual Earth to plot the point. But, on the other hand, I might need to geo-code the location on the fly if all I have to work with is an address.   I ended up writing some recursive code to support geocoding on the fly.  Here’s the nuts and bolts of the code:

First, I load the map. 

try {
    mainMap = new VEMap('iwmf_mapFrame');
    mainMap.LoadMap();
}
catch (err){
    setTimeout(buildMap,1000);
    return;
}
//seems the only way to force the size we want for the map                       
mainMap.Resize(1024,175);

//firefox hack because it always places the map over about 350 px
if (oomph.browser.mozilla)
    mainMap.Pan(-350,0);

Note that before even trying to load the map, I do some checking to make sure the script finished loading. I then position it inside Oomph.  Lastly, for some reason, I was having issues in Firefox getting my pushpins to show correctly, thus the pan.

Next, I do the geocoding. I already have an array of addresses, culled from the hCards, to work with:

if (addresses.length > 0)
    geoCode(addresses[0]);

 

Note how I pass only the first address to the geoCode method. That’s because the call to the Virtual Earth geocoding service (which is Find()) is async.  Notice how I provide a callback method as the last parameter to the geoCode method.  Also, notice how I ask the method to only return one result.  By default, it returns multiple results, allowing the user to determine the best one. But for the purpose of Oomph, we just want to make the call for the user and hope that it geocodes correctly, which it usually does:

 

function geoCode(address){
    //because we might be mapping multiple locations, we aren't going to zoom in 
    //unless there is only one location microformatTotal
    //hmm, VE doesn't seem to be zooming even if there is only one
    
    var zoom = true;
    if (addresses.length > 1 || geoinfos.length > 0)
    {
        zoom = false; 
    }         
    mainMap.Find( null,
          address.location,
          null,
          null,
          null,
          null,
          true,
          true,
          false, //only return one geocode
          zoom,
          GetCoordinates);    //provide the GetCoordinates callback for the response
}

In the GetCoordinates callback, I actually push the location onto the map and then call geoCode again, with the next address:

function GetCoordinates(layer, resultsArray, places, hasMore, veErrorMessage)
     {
        if (places == null)
            return;
        geoinfos.push(places[0].LatLong);
        var myShape = new VEShape(VEShapeType.Pushpin, places[0].LatLong);
        geoinfos2.push(new VEShape(VEShapeType.Pushpin, places[0].LatLong))
        myShape.SetTitle(addresses[addressCounter].name);
        mainMap.AddShape(myShape);
        if (geoinfos.length == 1)
            mainMap.SetCenterAndZoom(places[0].LatLong, 13);
        //we can only do another geocode after the first one completes, thus the recursion
        addressCounter++;
                       
        if (addressCounter < addresses.length)
            geoCode(addresses[addressCounter]);
         //if we've got them all, reset map view
         if (addressCounter == addresses.length && addresses.length > 1)
             mainMap.SetMapView(geoinfos2);

     } 

(In looking at this code, I just noticed that I bail if null – I should probably fix this because, if for some reason I get null, I’ll end up exiting out of the whole loop and never get to the other addresses – just made myself a workitem to do this!)

Once I get a place back from Virtual Earth, I can put it on the map.  I also put the location into my other array, which I walk later. I then go ahead and center/zoom the map if I only had one.  I then call geoCode again after incrementing my address counter.  Lastly, if I have everything, I call SetMapView to center and zoom the map in the event I have more than one location on the map.

After dealing with geoCoding, I then go and parse any lat/longs I received through the geo Microformat.  (Again, looking at the code, there’s some redundancy – the lot of it could probably be cleaned up, but hey, it is eXtreme Programming methodology at work (been reading Extreme Programming Adventures in C# incidentally…)

All of this code can be found here in oomph.js, lines 215 – 295. Overall, working with Virtual Earth was pretty easy, once I figured out how to do async geocoding.

WPF Boot Camp Content Ported To Silverlight RTM

December 5 2008

With Yet Another Carousel (YAC) ported to Silverlight RTM, I was finally able to port the WPF Boot Camp 2008 content to Silverlight RTM.  Here’s the code if you are interested in creating your own video portal with a carousel.  If you are just looking for the videos and want to skip the glitz, here’s links to all the videos and presentations – some great stuff if you are learning WPF.