Showing posts with label Silverlight 3. Show all posts
Showing posts with label Silverlight 3. Show all posts

Monday, August 31, 2009

M-V-VM Product Configuration

Deb and I have created many solutions that support sales or engineering efforts. Each application is a little different, driven by the unique aspects of the product we are selling/designing. We have built systems to sell cars, to quote multi-million dollar communications infrastructure projects, to design chemical plants and gear boxes.



You do enough of these applications and you start to see a software design pattern. You also realize just how important the role of the subject matter expert is in helping you create a successful application.


Our first Silverlight product configuration solution is a Cookie Designer. With a lifetime of experience eating cookies and a trip to the store to read the packaging, I now consider myself a subject matter expert. You can test drive the program HERE.

The Cookie Designer is an entry in the Summer Silverlight Coding Competition -- please try it and vote for our solution. Community points are an important part of the judging in this contest.

Our cookie product configuration solution is composed of a set of ingredients that can be combined without any mutually exclusive constraints. Anyone for cinnamon oatmeal dark chocolate chunk?

The secret to creating a convincing picture is to have each ingredient on its own separate layer and to make visible the layers that are involved -- enter the M-V-VM pattern, and in this case used as an adapter / translator between the composition of the model and what is shown.


<Canvas x:Name="CookieRoot" Height="400" HorizontalAlignment="Center" VerticalAlignment="Center" Width="400" Clip="M0,0L400,0 400,400 0,400z" RenderTransformOrigin="0.5,0.5">



<Image Height="381" Width="404" Canvas.Left="-9" Canvas.Top="8" Source="Cookie_Images/Base Cookie.png"/>

<Image x:Name="Cinnamon" Height="382" Width="390" Opacity="0.651" Canvas.Left="6" Canvas.Top="13" Source="Cookie_Images/Cinnamon.png" Visibility="Collapsed">

</Image>

<Image x:Name="PeanutButter" Height="379" Width="385" Opacity="0.478" Canvas.Left="7" Canvas.Top="9" Source="Cookie_Images/Peanut Butter.png" Visibility="Collapsed"/>

<Image x:Name="ReesesPeanutButter" Height="379" Width="385" Opacity="0.478" Canvas.Left="7" Canvas.Top="9" Source="Cookie_Images/Peanut Butter.png" Visibility="Collapsed"/>

<Image x:Name="Cocoa" Height="387" Width="389" Opacity="0.678" Canvas.Left="5" Canvas.Top="5" Source="Cookie_Images/Cocoa.png" Visibility="Collapsed">

</Image>

<Image x:Name="Oatmeal" Height="380" Width="387" Opacity="0.412" Canvas.Left="6" Canvas.Top="7" Source="Cookie_Images/Oatmeal.png" Visibility="Collapsed"/>

<Image x:Name="Peanuts" Height="377" Width="381" Canvas.Left="9" Canvas.Top="10" Source="Cookie_Images/Peanuts.png" Visibility="Collapsed"/>

<Image x:Name="Raisins" Height="373" Width="370" Canvas.Left="15" Canvas.Top="10" Source="Cookie_Images/Raisins.png" Visibility="Collapsed"/>

<Image x:Name="Pecans" Height="313" Width="302" Canvas.Left="56" Canvas.Top="56" Source="Cookie_Images/Pecans.png" Visibility="Collapsed"/>

<Image x:Name="Almonds" Height="286" Width="333" Canvas.Left="14" Canvas.Top="59" Source="Cookie_Images/Almonds.png" Visibility="Collapsed"/>

<Image x:Name="WhiteChocolateChips" Height="351" Width="353" Canvas.Left="18" Canvas.Top="45" Source="Cookie_Images/White Chocolate Chips.png" Visibility="Collapsed"/>

<Image x:Name="SemisweetChocolateChips" Height="400" Width="400" Canvas.Left="0" Canvas.Top="0" Source="Cookie_Images/Dark Chocolate Chips.png" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">

</Image>

<Image x:Name="MilkChocolateChips" Height="351" Width="380" Canvas.Left="7" Canvas.Top="45" Source="Cookie_Images/Milk Chocolate Chips.png" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">

</Image>

<Image x:Name="MacadamiaNuts" Height="377" Width="354" Canvas.Left="38" Canvas.Top="12" Source="Cookie_Images/Macadamia Nuts.png" Visibility="Collapsed"/>

<Image x:Name="DarkChocolateChunks" Height="546" Width="456" Canvas.Left="-27" Canvas.Top="-88" Source="Cookie_Images/Dark Chocolate Chunks.png" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">

</Image>

<Image x:Name="MilkChocolateChunks" Height="546" Width="456" Canvas.Left="-27" Canvas.Top="-88" Source="Cookie_Images/Milk Chocolate Chunks.png" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">

</Image>

<Image x:Name="WhiteChocolateChunks" Height="546" Width="456" Canvas.Left="-27" Canvas.Top="-88" Source="Cookie_Images/White Chocolate Chunks.png" RenderTransformOrigin="0.5,0.5" Visibility="Collapsed">

</Image>

<Image x:Name="HersheyMiniKisses" Height="134" Width="134" Canvas.Left="134" Canvas.Top="126" Source="Cookie_Images/Hershey Kiss.png" Visibility="Collapsed"/>

</Canvas>





We were careful to name the XAML elements so they could be controlled through an automated binding statement that is generated when the picture is loaded.


void CookieCanvas_Loaded(object sender, RoutedEventArgs e)
{
foreach (Image oItem in CookieRoot.Children)
{
string sName = oItem.Name;
if (string.IsNullOrEmpty(sName))
continue;
var oBinding = new Binding(sName);
oBinding.Mode = BindingMode.OneWay;
oItem.SetBinding(Button.VisibilityProperty, oBinding);
}
}

With all this binding now in place the final step is the view model.

public class CookieVM : ViewModel

{

public CookieVM(Instance oSource)

: base(oSource)

{

}

public override void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

{

if (e.NewItems.IsNotNull())

foreach (Component oNote in e.NewItems.OfType<Component>())

NotifyPropertyChange(oNote.ClassName);



if (e.OldItems.IsNotNull())

foreach (Component oNote in e.OldItems.OfType<Component>())

NotifyPropertyChange(oNote.ClassName);

}

public Visibility Cinnamon

{

get { return DoesChildOfClassExist("Cinnamon") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility PeanutButter

{

get { return DoesChildOfClassExist("PeanutButter") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility ReesesPeanutButter

{

get { return DoesChildOfClassExist("ReesesPeanutButter") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility Cocoa

{

get { return DoesChildOfClassExist("Cocoa") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility Oatmeal

{

get { return DoesChildOfClassExist("Oatmeal") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility Peanuts

{

get { return DoesChildOfClassExist("Peanuts") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility Raisins

{

get { return DoesChildOfClassExist("Raisins") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility HersheyMiniKisses

{

get { return DoesChildOfClassExist("HersheyMiniKisses") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility Pecans

{

get { return DoesChildOfClassExist("Pecans") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility Almonds

{

get { return DoesChildOfClassExist("Almonds") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility DarkChocolateChunks

{

get { return DoesChildOfClassExist("DarkChocolateChunks") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility MilkChocolateChunks

{

get { return DoesChildOfClassExist("MilkChocolateChunks") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility WhiteChocolateChunks

{

get { return DoesChildOfClassExist("WhiteChocolateChunks") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility WhiteChocolateChips

{

get { return DoesChildOfClassExist("WhiteChocolateChips") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility SemisweetChocolateChips

{

get { return DoesChildOfClassExist("SemisweetChocolateChips") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility MilkChocolateChips

{

get { return DoesChildOfClassExist("MilkChocolateChips") ? Visibility.Visible : Visibility.Collapsed; }

}

public Visibility MacadamiaNuts

{

get { return DoesChildOfClassExist("MacadamiaNuts") ? Visibility.Visible : Visibility.Collapsed; }

}
}

Thursday, July 16, 2009

Autosizing of Grids, Canvasses and StackPanels

Deb: Moving from a relatively static environment, like Photoshop, to a dynamic environment like Silverlight/Blend, requires learning about autosizing and how visuals will behave when the browser is resized. One of the most challenging things for me to learn was the differences in behaviors between grids, canvasses and stackpanels.

I started with grids, the workhorse of the layout panels, and I got comfortable with setting the height and width to 'auto', and the alignment to stretch, and setting the margins to achieve the effect I was looking for. I learned how to set column and row sizes, locking certain ones and leaving others to size dynamically as the space changed.

I moved on to canvasses, learning how to place objects on the panel in a different way, but the autosizing aspect of the layout panel worked the same as the grid. I figured I had this dynamic sizing thing figured out. Then I got cocky and started sticking stackpanels into my grids, and suddenly things weren't working the way I expected. This video explains why:

I am not a developer, but I do have a strong intuitive understanding of hierarchies and trees. I had developed a mental model that said that if I picked 'auto' and 'stretch', my object would size to match the parent it was embedded in. Working with stackpanels forced me to reevaluate my mental model, to acknowledge that sometimes the sizing was driven by the children embedded in the object, and margins - as seen in the video - seem to be able to refer to both (in the example, the top and left margins are relative to the parent canvas but the right and bottom margins are relative to the 'children' buttons).

This is where trial and error has led me. If there is an explanation for these behaviors, a simplifying principle that can be applied in all cases, I would love to learn it -- I really like to understand why things are the way they are. Post a comment and let me know what you think!

Tuesday, July 14, 2009

The Designer's Perspective: Working with Silverlight and Expression Blend

Deb: For too long now, I've been sitting on the sidelines, content to edit Steve's posts. But it's time to start representing the designer's viewpoint when it comes to using Silverlight 3 and Expression Blend 3.

I was first attracted to Silverlight for two reasons: 1) visualization is very important to me and Silverlight lets me design some very compelling visualizations, and 2) Expression Blend promised me significantly more autonomy, and the ability to do whatever I could imagine without relying on a developer to translate my vision for me.

While I haven't become as autonomous as I would like, Blend keeps improving and for the most part, Blend 3 is a joy to work with. I have my moments of frustration, sure, but they are more than offset by the power and independence I've been given. The goal of my posts will be to reduce the frustration factor even further for fellow designers.

I can summarize my attitude toward XAML by saying this: in general, the only legitimate reason for me to go to the XAML is to finetune the sizing of rows and columns. Other than that, I shouldn't need to even look at it. I think this should be the ultimate test of Blend's ability to meet the needs of designers: no XAML coding required.

The point of view I represent is that of someone who has graphic arts training, who is adept at Adobe tools such as Photoshop, who is an extremely heavy user of the Internet, but who knows nothing about C# or .NET. Perhaps it may be difficult for some readers to understand someone with this background, but there are a lot of us out here. There are many things that may be obvious to a developer that can only be painfully learned through trial and error by a designer. Given the relative 'youth' of the Silverlight tools and their accompanying documentation (sparse), only the Internet, powerful search engines and the sharing of the Silverlight community make solving the problems we confront relatively painless. And it always helps to know that the pain of being an early adopter of Silverlight will be compensated for by the advantages that come from being among the first to learn these skills.

Saturday, July 4, 2009

Learning RIA Services: It's Great to be a Silverlight Developer

I have built WCF integrations to SQL Server as part of building Silverlight applications. Having created the basic elements of a CRUD (Create, Read, Update, Delete) using WCF (see Tim Heuer's blog on how to do this ), I had to ask myself if there wasn't a way to achieve the same result that required less code.

During MIX09 I saw Brad Adams' talks on .NET RIA Services (watch Brad Adams talk). On 3 separate occasions I worked through these examples until I began to understand how all the parts fit together. My basic feeling was that although these services are very powerful, they made a lot of assumptions about how I wanted to collect and consume the data. Too much magic going on behind the scenes for my purposes. Using the beta release of RIA Services, according to all the demos I could find on line, there was no way to loop through the returned records without binding it to the control.

The Problem Statement

I wanted to find out what I was doing wrong. Is there a way to just loop over the records returned from RIA services without binding to a control? I asked the question on Stack Overflow and in the Silverlight forums.




The Community to the Rescue

If I knew the answer, I would have asked a better question. Isn't that always the case? The members of the developer community offered answers to my ill-formed question:



No one gave me the exact answer. There was no code provided that I could just cut and paste, but I got great response in hours and their insight led me to the answer I was looking for:



Not long after I starting working on this problem, Silverlight 3 was released. RIA services have changed and I have not had time to investigate, but once I do I will add a new post.

It is Great to be a Silverlight Developer

I have been writing software since I was 18; I would have started sooner if I had access to a computer. Looking back over my 30+ years of programming, I am now having more fun than ever, and I think it is because of the times we live in -- Silverlight, the Internet, and the community of people who use them both. Silverlight has turned web applications into a rich and now, with OOB, sometimes even unconnected experience. Today's development culture is one of generosity and sharing. I am in contact with people around the world, Silverlight gurus and novices alike, and they all love to share. You get the sense that we are all looking to see how much we can accomplish together with Silverlight.

Friday, July 3, 2009

Bing Maps Silverlight Control Part 5: Calculating Distance

My dad was the first person to show me how to measure distances on a map using a pair of dividers and the scale printed on the map key. There is a much better way to do this using the Bing Maps Silverlight control -- it just takes a little programming and finding the right equation.



Here is the Bobcat Video I told you about:



Back to Programming...

Here is how you calculate the distance between points on the earth. You also need to know EarthRadiusInMiles = 3956.0;


/// <summary>
/// Calculate the distance between two geocodes. Defaults to using Miles.
/// </summary>
public static double CalcDistance(double lat1, double lng1, double lat2, double lng2)
{
return CalcDistance(lat1, lng1, lat2, lng2, GeoCodeCalcMeasurement.Miles);
}
/// <summary>
/// Calculate the distance between two geocodes.
/// </summary>
public static double CalcDistance(double lat1, double lng1, double lat2, double lng2, GeoCodeCalcMeasurement m)
{
double radius = GeoCodeCalc.EarthRadiusInMiles;
if (m == GeoCodeCalcMeasurement.Kilometers) { radius = GeoCodeCalc.EarthRadiusInKilometers; }
return radius * 2 * Math.Asin(Math.Min(1, Math.Sqrt((Math.Pow(Math.Sin((DiffRadian(lat1, lat2)) / 2.0), 2.0) + Math.Cos(ToRadian(lat1)) * Math.Cos(ToRadian(lat2)) * Math.Pow(Math.Sin((DiffRadian(lng1, lng2)) / 2.0), 2.0)))));
}

I created a Marker class to mark the end points of the distance calculation. To show the user that they are in distance calculation mode, I have also changed the cursor on the map control. In this case, I alternate between the arrow and the pointing finger.

PointsOfInterest m_oMarkers = new PointsOfInterest();

void cbMeasure_Checked(object sender, RoutedEventArgs e)
{
Map1.Cursor = Cursors.Hand;
m_oMarkers.Clear();
}
void cbMeasure_Unchecked(object sender, RoutedEventArgs e)
{
Map1.Cursor = Cursors.Arrow;
if ( m_oMarkers.Count > 1 )
AddLine(m_oMarkers,.5);
}

The code for collecting the user's selection of points is here:


void Map1_MouseClick(object sender, MapMouseEventArgs e)
{
var oLocation = Map1.ViewportPointToLocation(e.ViewportPoint);

PointOfInterest oPOI = null;
if (cbMeasure.IsChecked == true)
{
oPOI = new Marker() { Loc = oLocation };
m_oMarkers.Add(oPOI);
}
else
{
oPOI = new PointOfInterest() { Loc = oLocation };
m_oList.Add(oPOI);
}

RenderPoint(oPOI);

if (cbDraw.IsChecked == true)
try
{
double dRadius = double.Parse(txtRadius.Text);
AddCircle(oLocation, dRadius, .5);
}
catch( Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Using a polyline and a tool tip is great way to report the distance:


double DistanceInMiles(LocationCollection oLocs)
{
double dDistance = 0.0;
for (int i = 0; i < oLocs.Count() - 1; i++)
dDistance += DistanceInMiles(oLocs[i], oLocs[i + 1]);

return dDistance;
}

double DistanceInMiles(Location Loc1, Location Loc2)
{
double dMiles = GeoCodeCalc.CalcDistance(Loc1.Latitude, Loc1.Longitude, Loc2.Latitude, Loc2.Longitude);
return dMiles;
}

LocationCollection RenderLine(List<PointOfInterest> oList)
{

var oPOIs = (from oPOI in oList
where oPOI is Marker
select oPOI).ToList<PointOfInterest>();

var oLocs = new LocationCollection();
foreach (var oPOI in oPOIs)
oLocs.Add(oPOI.Loc);

return oLocs;
}


void AddLine(List<PointOfInterest> oList, double dOpacity)
{
MapPolyline polyline = new MapPolyline();
polyline.Stroke = new SolidColorBrush(Colors.Red);
polyline.StrokeThickness = 1;
polyline.Opacity = dOpacity;

//this works in miles
LocationCollection oLocs = RenderLine(oList);
polyline.Locations = oLocs;
Map1.AddChild(polyline);

double dDist = DistanceInMiles(oLocs);

ToolTipService.SetToolTip(polyline, string.Format("{0:0.0} mi \n {1:0.00} yd", dDist, dDist * 5280.0 / 3.0));
}

Sunday, June 21, 2009

Bing Maps Silverlight Control Part 4: Drawing Circles To Scale

When you add scaled geometry to the Virtual Earth application you need to remember 2 things:

1) The earth is not flat, so your math needs to account for the curvature of the earth.

2) The Virtual Earth API has, among other things, MapPolygon objects, which are treated as scaled geometry. In my example, I wanted to draw a circle around a point that showed what was on the map 10 miles in any direction. This video shows my reasons why:



I pulled all the parts to build this application from the web. You can find the formulas to calculate distances on a curved surface in Wikipedia: Great Circle Distance
Tim Heuer had a post on calculating the points in a circle using Java: Drawing a Radius
In my project I converted all the code to C# and separated the calculation of locations and the generation of a MapPolygon into two functions.

void AddCircle(Location oLoc, double dRadius, double dOpacity)
{
MapPolygon polygon = new MapPolygon();
polygon.Fill = new SolidColorBrush(Colors.Green);
polygon.Stroke = new SolidColorBrush(Colors.Blue);
polygon.StrokeThickness = 5;
polygon.Opacity = dOpacity;

//this works in miles
polygon.Locations = DrawACircle(oLoc, dRadius);
Map1.AddChild(polygon);
}

public LocationCollection DrawACircle(Location oLoc, double dRadius)
{
var oLocs = new LocationCollection();

var earthRadius = GeoCodeCalc.EarthRadiusInMiles;
var lat = GeoCodeCalc.ToRadian(oLoc.Latitude); //radians
var lon = GeoCodeCalc.ToRadian(oLoc.Longitude); //radians
var d = dRadius / earthRadius; // d = angular distance covered on earths surface

for (int x = 0; x<= 360; x++)
{
var brng = GeoCodeCalc.ToRadian(x); //radians
var latRadians = Math.Asin(Math.Sin(lat) * Math.Cos(d) + Math.Cos(lat) * Math.Sin(d) * Math.Cos(brng));
var lngRadians = lon + Math.Atan2(Math.Sin(brng) * Math.Sin(d) * Math.Cos(lat), Math.Cos(d) - Math.Sin(lat) * Math.Sin(latRadians));

var pt = new Location(180.0 * latRadians / Math.PI, 180.0 * lngRadians / Math.PI);
oLocs.Add(pt);
}
return oLocs;
}

Integrating this with the current UI was straight-forward -- I just added a little more code to the actions the application was already doing. So drawing circles around the current location became this:

void btnCircle_Click(object sender, RoutedEventArgs e)
{
var oMyLoc = from oItem in m_oList
where oItem is MyLocation
select oItem;

PointOfInterest oWhereIAm = oMyLoc.First<PointOfInterest>();
if (oWhereIAm == null)
{
MessageBox.Show("Add Where Am I to the Map");
return;
}

for (int i = 1; i <= 5; i++)
{
double dRadius = i * 10.0;
AddCircle(oWhereIAm.Loc, dRadius, .15);
}
}


And drawing a circle of a given radius about any mouse click became this:



void Map1_MouseClick(object sender, MapMouseEventArgs e)
{
var oLocation = Map1.ViewportPointToLocation(e.ViewportPoint);

var oPOI = new PointOfInterest() { Loc = oLocation };
m_oList.Add(oPOI);

RenderPoint(oPOI);

if (cbDraw.IsChecked == true)
try
{
double dRadius = double.Parse(txtRadius.Text);
AddCircle(oPOI.Loc, dRadius, .5);
}
catch( Exception ex)
{
MessageBox.Show(ex.Message);
}
}


In the next post I will show how to measure between two user-selected points.

Tuesday, June 16, 2009

Virtual Earth Silverlight Control Part 3: Locating Best Buy Stores

I was searching the web for sources of Lat/Long data I could mash-up to in a Virtual Earth application and I ran across Best Buy ReMix. The API to collect store location information was easy to use and returned Lat/Long info, so I integrated it into the application. Watch this video to see how I did it:



I like to start by testing the URL in the browser:


http://api.remix.bestbuy.com/v1/stores(area(44236,20))?apiKey=[YourKey]


The Remix API returned XML describing the stores within 20 miles of my zipcode. Here is some of what I got back:


<stores currentPage="1" totalPages="1" from="1" to="6" total="6" queryTime="0.004" totalTime="0.013" canonicalUrl="/v1/stores(area("44236",20))?apiKey=fbsn9dsqvgnm42vyk4x5hman">
<store>
<storeId>758</storeId>
<name>Macedonia OH</name>
<address>470 East Aurora Road</address>
<city>Macedonia</city>
<region>OH</region>
<postalCode>44056</postalCode>
<fullPostalCode>44056-1834</fullPostalCode>
<country>US</country>
<lat>41.313461</lat>
<lng>-81.521233</lng>
<phone>330-468-6850</phone>
<hours>10:00am - 9:00pm Monday - Friday, 11:00am - 6:00pm Saturday, 11:00am - 6:00pm Sunday</hours>
<distance>6.0</distance>
</store>


The next step is to verify that Silverlight can consume this data using the WebClient Class. The code is simple but it does not always work, based on permissions (see Tim Heuer's explanation of this). I test it with this code:


void LoadBestBuyStores()
{
WebClient oClient = new WebClient();

string sUri = string.Format("http://api.remix.bestbuy.com/v1/stores(area({0},20))?apiKey=fbsn9dsqvgnm42vyk4x5hman", txtZipCode.Text);
var oAddress = new Uri(sUri);
oClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(BestBuyStores_DownloadStringCompleted);
oClient.DownloadStringAsync(oAddress);
}

void BestBuyStores_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
string sResult = e.Result;
MessageBox.Show(sResult);
}



To finish the application, I needed to parse the XML that was returned and map it into an object instance, so I defined a class 'BestBuyStores':



public class BestBuyStore : PointOfInterest
{
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string Phone { get; set; }
public string Hours { get; set; }
}


and then added LinqToXML code to build my objects:


void BestBuyStores_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null )
return;

string sResult = e.Result;

var doc = XDocument.Parse(sResult);

var oStores = from element in doc.Descendants("store")
select new BestBuyStore
{
Name = element.Element("name").Value,
Address = element.Element("address").Value,
City = element.Element("city").Value,
Region = element.Element("region").Value,
Phone = element.Element("phone").Value,
Hours = element.Element("hours").Value,
Loc = new Location(double.Parse(element.Element("lat").Value), double.Parse(element.Element("lng").Value))
};

foreach (BestBuyStore oStore in oStores.ToList<BestBuyStore>())
{
RenderPoint(oStore);
m_oList.Add(oStore);
}

frmBestBuy.Visibility = Visibility.Visible;
frmBestBuy.ItemsSource = oStores;
}


Finally by extending the RenderPoint method I was able to mark each location with a custom Best Buy graphic:


void RenderPoint(PointOfInterest oPOI)
{
if (oPOI is MyLocation)
{
Uri oSource = new Uri(@"Pushpin.png", UriKind.Relative);
var oBitmap = new BitmapImage(oSource);
var oImage = new Image() { Source = oBitmap, Width=23, Height=27 };
m_oLayer.AddChild(oImage, oPOI.Loc, PositionMethod.BottomCenter);
}
else if (oPOI is BestBuyStore)
{
Uri oSource = new Uri(@"BestBuy.png", UriKind.Relative);
var oBitmap = new BitmapImage(oSource);
var oImage = new Image() { Source = oBitmap, Width = 23, Height = 27 };
m_oLayer.AddChild(oImage, oPOI.Loc, PositionMethod.BottomLeft);
}
else
{
var oRect = new System.Windows.Shapes.Rectangle() { Fill = new SolidColorBrush(Colors.Red), Width = 10, Height = 10 };
m_oLayer.AddChild(oRect, oPOI.Loc, PositionMethod.Center);
}
}

Tuesday, June 9, 2009

Generic Type Conversion

While working on the Virtual Earth map application, I discovered the need to parse data from a string of characters. So I searched the net and found this answer on stack overflow: generic-type-conversion-from-string . I like this code example because it looks like the simplest and most general approach:

public class TypedProperty<T> : Property
{
public T TypedValue
{
get { return (T)Convert.ChangeType(base.Value, typeof(T)); }
set { base.Value = value.ToString();}
}
}
But if you cut and paste the code into a Silverlight 2 or Silverlight 3 application it does not run. This is because the two argument version of the ChangeType method is not part of the Silverlight runtime. I guess I could have used the three argument version, but instead I implemented a function the uses reflection:


public bool TryParse<T>(string sValue, out T oValue) where T : IConvertible
{
oValue = default(T);

try
{
string[] parts = sValue.Split(':');
MethodInfo oMethod = typeof(T).GetMethod("Parse", new Type[] { typeof(string) } );
oValue = (T)oMethod.Invoke(null, new object[] { parts[1].Trim() });
return true;
}
catch { }
return false;
}
Silverlight Size vs. Functionality

You find this all the time in Silverlight development -- some of the tools, functions and features you have come to rely on in .NET are just not part of the lightweight Silverlight runtime. I read on one blog that programming with Silverlight is like going camping with .NET. In fact, I first learned this the hard way trying to recompile some previous .NET code. I couldn't include the existing library -- it had to be rebuilt using the Silverlight libraries. When I attempted to recompile it, I found that it needed to be reengineered to use functionality available in the Silverlight runtime.

I have always been able to find a creative solution to simple programming issues like the one above, and I think the ability to deliver my solution over the web to any machine, without creating a custom install, makes it worth the effort to find these alternative approaches.

Sunday, June 7, 2009

Virtual Earth Silverlight Control Part 2: Locate Me

It was a post by Nikhil Kothari that inspired me to create a method to report my current location on the Virtual Earth Silverlight control.

http://www.nikhilk.net/Silverlight-Locate-Me.aspx

While I was traveling around the country (see http://www.wiredwalkabout.com/), I would run Nikhil's application to see the result. For the most part, it always reported my location about 20 miles from where I really was, which I think is a problem with the web service used and not the code calling it.

http://api.hostip.info/get_html.php?position=true




Nikhil's current live sample has been updated, but it is still not at good as a GPS. The application source code is worth looking at in detail. He uses techniques that are more advanced than calling WebClient, and as a programmer it is interesting to know how to do same thing many different ways (viewing this link requires you have Silverlight 3 installed):

http://www.nikhilk.net/Content/Posts/SilverlightLocateMe/LocateMe.htm

This post is about integrating this functionality into the mapping application I am currently developing. I have decided to create a separate class of objects to mark my current location. I did this mainly because I wanted to use a different symbol to mark myself on the map -- like a "You Are Here" marker. I have extended this class to collect a time stamp, and this -- in combination with persistence -- can be used to track my movements.


public class MyLocation : PointOfInterest



{



public DateTime TimeStamp { get; set; }



public MyLocation()



: base()



{



TimeStamp = DateTime.Now;



}







#region Serializer Methods



public override XElement PersistTo(ModelSerializer oSerializer, XElement oSelf)



{



XElement oXElement = base.PersistTo(oSerializer, oSelf);







oXElement.Add(new XAttribute("TimeStamp", TimeStamp.ToString()));







return oXElement;



}



public override IMapModel RecoverFrom(ModelSerializer oSerializer, XElement oElement)



{



var oResult = base.RecoverFrom(oSerializer, oElement);







string sTimeStamp = oElement.Attribute("TimeStamp").Value;



TimeStamp = DateTime.Parse(sTimeStamp);



return oResult;



}



#endregion



}
I needed to modify the rendering code in order to display a different picture for this class. With this quick change, objects of type MyLocation now show a pushpin instead of a red rectangle.


void RenderPoint(PointOfInterest oPOI)



{







if (oPOI is MyLocation)



{



Uri oSource = new Uri(@"Pushpin.png", UriKind.Relative);



var oBitmap = new BitmapImage(oSource);



var oImage = new Image() { Source = oBitmap, Width=23, Height=27 };



m_oLayer.AddChild(oImage, oPOI.Loc, PositionMethod.BottomCenter);



}



else



{



// Create a Rectangle Shape to Show as the "Pushpin"



// Set it's Size to 10x10



var oRect = new System.Windows.Shapes.Rectangle() { Fill = new SolidColorBrush(Colors.Red), Width = 10, Height = 10 };







// Add Rectangle to MapLayer



m_oLayer.AddChild(oRect, oPOI.Loc, PositionMethod.Center);



}



}
The code to query the web service using WebClient follows. This is an asynchronous call that returns a string delimited by line feeds, so I used Nikhil's trick of reading from a stream to parse out and convert the Lat/Long information.

void WhereAmI()



{



WebClient oClient = new WebClient();



var oAddress = new Uri(@"http://api.hostip.info/get_html.php?position=true");



oClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(oClient_DownloadStringCompleted);



oClient.DownloadStringAsync(oAddress);



}







void oClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)



{



string sResult = e.Result;







using (StreamReader sr = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(sResult))))



{



try



{



sr.ReadLine();



sr.ReadLine();







double lat = 0;



TryParse<Double>(sr.ReadLine(), out lat);







double lng = 0;



TryParse<Double>(sr.ReadLine(), out lng);







var oPOI = new MyLocation() { Loc = new Location(lat, lng) };



m_oList.Add(oPOI);







RenderPoint(oPOI);







}



catch { }



}



MessageBox.Show(sResult);



}


Monday, June 1, 2009

Using the Virtual Earth Silverlight Control - Part One

Debra and I took a trip around the country last month, 7500 miles of driving over 25 days. You can read about it at http://www.wiredwalkabout.com/.

One of the goals of the trip was to fieldtest technology, including the Virtual Earth Silverlight control. You can see the MIX09 presentation here at http://videos.visitmix.com/MIX09/T34F.

So you might be thinking, why not just do this from home? I believe that you can see the issues more clearly if you put yourself into the situations in which you expect the software to be used. After all, Pixar animators traveled to South America as inspiration for the movie "UP", and traveled along Route 66 in preparation for the movie "Cars".

Along the way we were using a Garmin to plan our trip. This is an extremely valuable tool, but it was missing some features that would have been nice to have, so I decided to investigate the Virtual Earth Silverlight control to see what it would take to reproduce/extend the features in the Garmin. I have also done Telco applications in the past and I wanted to investigate how the mapping requirement in these applications could be accomplished in Silverlight.



Since this investigation was going to be more like building an application and less like an experiment, I am starting by writing code to persist my POCO domain model (Plain Old CLR Objects). I am using the OpenFileSave feature in Silverlight 3 to save my domain model as XML. The rest of this blog entry explains my model for persistence.

For this application I am implementing my own XML serializer. The result (so far) is as follows. As the implementation grows I expect this file format to change. XML gives me some flexibility in reusing data from older files.


<?xml version="1.0" encoding="utf-8"?>
<ModelSerializer>
<PointsOfInterest>
<PointOfInterest Latitude="39.730288305639185" Longitude="-83.310517678613749" />
<PointOfInterest Latitude="34.004710072445405" Longitude="-89.814423928613749" />
<PointOfInterest Latitude="32.572126585547366" Longitude="-96.669892678613763" />
<PointOfInterest Latitude="30.436679980300028" Longitude="-98.032197366113763" />
<PointOfInterest Latitude="31.304215774927229" Longitude="-103.74508799111376" />
<PointOfInterest Latitude="33.272989725895094" Longitude="-111.96286142861376" />
<PointOfInterest Latitude="36.445446009018042" Longitude="-118.24704111611376" />
<PointOfInterest Latitude="38.845986120977358" Longitude="-120.35641611611376" />
<PointOfInterest Latitude="37.463816739355629" Longitude="-122.11422861611376" />
<PointOfInterest Latitude="41.82646221909387" Longitude="-121.71872080361376" />
<PointOfInterest Latitude="45.511996049055" Longitude="-122.24606455361376" />
<PointOfInterest Latitude="47.478111391249712" Longitude="-122.20211924111376" />
<PointOfInterest Latitude="46.3676556511152" Longitude="-117.06051767861376" />
<PointOfInterest Latitude="45.604305565410648" Longitude="-110.68844736611376" />
<PointOfInterest Latitude="44.07759180028043" Longitude="-103.52536142861376" />
<PointOfInterest Latitude="43.410903675485677" Longitude="-96.582002053613763" />
<PointOfInterest Latitude="41.924626769221476" Longitude="-89.902314553613749" />
</PointsOfInterest>
</ModelSerializer>



The PointsOfInterest and PointOfInterest classes both implement the PersistTo XML and RecoverFrom XML methods defined in the IMapModel interface. Here is the code:



public interface IMapModel
{
XElement PersistTo(ModelSerializer oSerializer, XElement oSelf);
IMapModel RecoverFrom(ModelSerializer oSerializer, XElement oElement);
}

public class PointsOfInterest : List<PointOfInterest>, IMapModel
{
#region Serializer Methods
public virtual XElement PersistTo(ModelSerializer oSerializer, XElement oSelf)
{
XElement oXElement = new XElement(GetType().Name);
oSelf.Add(oXElement);

foreach (IMapModel oIMap in this)
oIMap.PersistTo(oSerializer, oXElement);

return oSelf;
}
public virtual IMapModel RecoverFrom(ModelSerializer oSerializer, XElement oElement)
{
string sType = GetType().Name;
XElement oRefElement = oElement.Element(sType);
if (oElement.HasElements)
foreach (XElement oSubElement in oRefElement.Elements())
{
IMapModel oChild = oSerializer.Import(this, oSubElement);

PointOfInterest oPOI = oChild as PointOfInterest;
if (oPOI != null)
this.Add(oPOI);
}

return this;
}
#endregion
}

public class PointOfInterest : IMapModel
{
public Location Loc { get; set; }

#region Serializer Methods
public virtual XElement PersistTo(ModelSerializer oSerializer, XElement oSelf)
{
XElement oXElement = new XElement(GetType().Name);
oSelf.Add(oXElement);

oXElement.Add(new XAttribute("Latitude",Loc.Latitude));
oXElement.Add(new XAttribute("Longitude", Loc.Longitude));

return oXElement;
}
public virtual IMapModel RecoverFrom(ModelSerializer oSerializer, XElement oElement)
{
Loc = new Location(double.Parse(oElement.Attribute("Latitude").Value), double.Parse(oElement.Attribute("Longitude").Value));
return this;
}
#endregion
}


This is not the first time I have implemented my own serializer. Most of my applications implement a highly interrelated domain model, combining objects sourced from many data sources into a domain model with many pointers and references. I have found the additional control I get over the serialization implementation to outweigh the effort of implementation. However, before this series of posts is finished, I plan on investigating the Silverlight 3 tools for persistence in XAML, XML and JSON.
Here is the code for ModelSerializer and the save and restore methods that start the process.


public class ModelSerializer
{

#region Compute Type

public Type ComputeType(XElement oElement)
{
string sType = oElement.Name.ToString();
return ComputeType(sType);
}

public Type ComputeType(object oObject)
{
if (oObject.GetType().IsSubclassOf(typeof(Type)))
return ComputeType(oObject as Type);
else
return ComputeType(oObject.ToString());
}

private Dictionary<String, Type> m_oTDictionary = null;
public Dictionary<String, Type> TypeHash
{
get
{
if (m_oTDictionary == null)
{
m_oTDictionary = new Dictionary<String, Type>();
m_oTDictionary.Add("double", typeof(Double));
m_oTDictionary.Add("string", typeof(String));
m_oTDictionary.Add("boolean", typeof(Boolean));
m_oTDictionary.Add("bool", typeof(Boolean));
m_oTDictionary.Add("integer", typeof(Int32));
m_oTDictionary.Add("int32", typeof(Int32));
m_oTDictionary.Add("object", typeof(object));
}
return m_oTDictionary;
}
set
{
m_oTDictionary = value;
}
}
#endregion


private XDocument m_oDocument = null;
public XDocument Document
{
get
{
if (m_oDocument == null)
m_oDocument = new XDocument();

return m_oDocument;
}
set
{
m_oDocument = value;
}
}

public void Write(StreamWriter oStream)
{
Document.Save(oStream);
}

public XDocument Read(StreamReader oStream)
{
Document = XDocument.Parse(oStream.ReadToEnd());
return Document;
}


public virtual IMapModel CreateObject(XElement oElement)
{
Type oType = ComputeType(oElement);
try
{
return Activator.CreateInstance(oType) as IMapModel;
}
catch (Exception ex)
{
oType = ComputeType(oElement);
string sType = oElement.Name.ToString();
string sMessage = string.Format("Type {0} not created. {1}", sType, ex.Message);
MessageBox.Show(sMessage);
}
return null;
}

public XElement Export(IMapModel oObject, XElement oParent)
{
return SerializeObject(oObject, oParent);
}


public IMapModel Import(IMapModel oObject, XElement oElement)
{
IMapModel oResult = CreateObject(oElement);
if (oResult != null)
DeSerializeObject(oResult, oElement);

return oResult;
}

public virtual void Serialize(IMapModel oObject)
{
Serialize(oObject, Document);
}
public virtual void Serialize(IMapModel oObject, XDocument oDocument)
{
XElement oElement = new XElement(GetType().Name);
oDocument.Add(oElement);
SerializeObject(oObject, oElement);
}


public virtual IMapModel DeSerialize(IMapModel oObject)
{
IMapModel oResult = DeSerialize(Document, oObject);
return oResult;
}

public virtual IMapModel DeSerialize(XDocument oDocument, IMapModel oTarget)
{
XElement oRoot = oDocument.Root;
IMapModel oResult = DeSerializeObject(oTarget, oRoot);
return oResult;
}

public virtual XElement SerializeObject(IMapModel oObject, XElement oElement)
{
return oObject.PersistTo(this, oElement);
}

public virtual IMapModel DeSerializeObject(IMapModel oObject, XElement oElement)
{
oObject.RecoverFrom(this, oElement);
return oObject;
}

}



void btnSave_Click(object sender, RoutedEventArgs e)
{

Button oButton = sender as Button;

SaveFileDialog sfd = new SaveFileDialog()
{
DefaultExt = "xml",
Filter = "XML files (*.xml)*.xmlAll files (*.*)*.*",
FilterIndex = 1
};

bool? result = sfd.ShowDialog();
if (result == true)
{
try
{
using (StreamWriter oStream = new StreamWriter(sfd.OpenFile()))
{
ModelSerializer oSerializer = new ModelSerializer();
oSerializer.Serialize(m_oList);
oSerializer.Write(oStream);
oStream.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}

void btnLoad_Click(object sender, RoutedEventArgs e)
{
Button oButton = sender as Button;

OpenFileDialog ofd = new OpenFileDialog()
{
Filter = "XML files (*.xml)*.xmlAll files (*.*)*.*",
FilterIndex = 1
};

bool? result = ofd.ShowDialog();
if (result == true)
{
try
{
using (StreamReader oStream = ofd.File.OpenText())
{
ModelSerializer oSerializer = new ModelSerializer();
oSerializer.Read(oStream);
oSerializer.DeSerialize(m_oList);
oStream.Close();
}

foreach (PointOfInterest oPOI in m_oList)
RenderPoint(oPOI);

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}


All the source code is on Codeplex:

http://vearthmappingexample.codeplex.com/

Thursday, May 14, 2009

Error Message: The directory name is invalid

Over the last week I was working on serialization of a generic domain model to XML. I had seen a few examples of using Silverlight 3's new feature SaveFileDialog(), so I implemented it in order to save the XML to my local hard drive. I got this error - the directory name is invalid.



I asked a friend who is learning Silverlight to create a small project using SaveFileDialog() command, and it worked for him without a problem. He sent me the project, I ran it unchanged, and I go the same error. So I ran this Google search:

Silverlight "The directory name is invalid"

and found some helpful entries about Protected Mode in IE. I followed the instructions to turn off Protected Mode:

Tool .. Internet Options .. Security. Here is the picture.



I checked MSN to see what I was giving up by unchecking the box. This is what it said:

Understanding Protected Mode

Protected Mode is an important step forward in security for Internet Explorer (IE); it helps protect users from attack by running an IE process with greatly restricted privileges on Windows Vista. While Protected Mode does not protect against all forms of attack, it significantly reduces the ability of an attack to write, alter, or destroy data on the user's machine or to install malicious code.

Disabling this would sound bad to any user. Furthermore, being in the reduced security mode affected my Google home page and my Twitter login.

What looked like this:



Now looks like this:



As you can see, this would be very confusing for someone using your application, who is expecting the File...Save option to act like it would in any other desktop application. In fact, turning off Protected Mode is likely to scare users away. For this reason I have decided to add other options for the user to persist information, like saving to an account on the server or in isolated storage.

To finish on a positive note the blogs say that Microsoft will be fixing this issue before the final release of Silverlight 3.

Tuesday, April 28, 2009

Element Binding That Acts Like Visio Glue

In previous posts I discussed drag and drop techniques, inspired by my years of work with Visio. Now that Silverlight 3 has element to element binding, I was inspired to investigate ways to implement Visio glue behaviors in Silverlight.




The following is the XAML shown in the video:


<UserControl x:Class="Sketch.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:ApprenticeView;assembly=ApprenticeView"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="gray">
<v:SketchPage x:Name="MyPage">
<v:OneDLink
X1="{Binding PinX, ElementName=RedSquare}"
Y1="{Binding PinY, ElementName=RedSquare}"
X2="{Binding PinX, ElementName=BlueSquare}"
Y2="{Binding PinY, ElementName=BlueSquare}"
>
</v:OneDLink>
<v:SketchShape x:Name="RedSquare"
v:SketchPanel.PinX="100"
v:SketchPanel.PinY="100"
Background="Red"
Width="40"
Height="30" >

</v:SketchShape>
<v:SketchShape x:Name="BlueSquare"
v:SketchPanel.PinX="300"
v:SketchPanel.PinY="140"
Background="Blue"
Width="40"
Height="30" >

</v:SketchShape>
</v:SketchPage>
</Grid>
</UserControl>

Dependency properties do all the heavy lifting in this example. This is because they send and receive notifications when their values change. To take advantage of this you need to program your custom XAML controls in a very declarative way, reacting to the events that occur naturally as Silverlight renders the screen.


PinX and PinY are attached properties of a SketchPage, and are used to position SketchShapes on the page (see the entry: Replicating a Visio Page Using a Silverlight Panel).

The OneDLink shape has four dependency properties -- X1,Y1,X2,Y2 -- that represent the start and finish of a line. These properties are bound to the PinX and PinY of the shape using the element binding syntax:


<v:OneDLink
X1="{Binding PinX, ElementName=RedSquare}"
Y1="{Binding PinY, ElementName=RedSquare}"
X2="{Binding PinX, ElementName=BlueSquare}"
Y2="{Binding PinY, ElementName=BlueSquare}"
>
</v:OneDLink>

The last bit of magic is in the code of the OneDLink class, because X1,Y1,X2,Y2 call InvalidateRouting(); when their value changes.


        public double Y2
{
get { return (double)GetValue(Y2Property); }
set { SetValue(Y2Property, value); }
}

public static readonly DependencyProperty Y2Property =
DependencyProperty.Register("Y2",
typeof(double),
typeof(OneDLink),
new PropertyMetadata(0.0, new PropertyChangedCallback(OnRouteChanged)));


protected static void OnRouteChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
OneDLink oPanel = obj as OneDLink;
if (oPanel != null)
oPanel.InvalidateRouting();
}

public override void InvalidateRouting()
{
CreateRouting();
InvalidateArrange();
}

CreateRouting() does the math that generates a line between the two end points.



public virtual void CreateRouting()
{
Children.Clear();
AddChild(SmartRouting(X1, Y1, X2, Y2));
}

protected Polyline SmartRouting(double dX1, double dY1, double dX2, double dY2)
{
Point oUpperLeft = new Point(Math.Min(dX1, dX2), Math.Min(dY1, dY2));
Point oLowerRight = new Point(Math.Max(dX1, dX2), Math.Max(dY1, dY2));

//Make sure these is at least 3.0 to render the line, just in case
//it is vertical or horizontal
Width = Math.Abs(oLowerRight.X - oUpperLeft.X) + 3.0;
Height = Math.Abs(oLowerRight.Y - oUpperLeft.Y) + 3.0;

PointCollection oColl = new PointCollection();
if (dX1 <= dX2 && dY1 <= dY2)
{
oColl.Add(new Point(0, 0));
oColl.Add(new Point(Width, Height));
}
else if (dX1 > dX2 && dY1 > dY2)
{
oColl.Add(new Point(0, 0));
oColl.Add(new Point(Width, Height));
}
else
{
oColl.Add(new Point(0, Height));
oColl.Add(new Point(Width, 0));
}

Polyline oPoly = new Polyline();
oPoly.SetValue(Polyline.PointsProperty, oColl);
oPoly.StrokeThickness = 2.0;
oPoly.Stroke = new SolidColorBrush(Colors.Blue);
return oPoly;
}
}

I know that the code samples I posted are not complete enough to reproduce the example shown in the video simply by cutting and pasting the code into a Silverlight 3 project. I decided to remove the complex issues associated with building a full-featured 2D drawing system, and focus on what is needed to make element binding work automatically in custom user controls.

In the future there will be more posts on creating a full-featured 2D drawing system in Silverlight.