Use Paint.NET to Create a Lake

by Bobbi Perreault 16. January 2009 20:05
Share on Facebook

Paint.NET is free software for digital photo editing.  I use it for 85% of my bitmap image processing needs - about the only time I switch to Photoshop is when I need to remove backgrounds because the Polygonal lasso tool makes it so easy. 

I had some fun this morning by putting a lake where none existed.  I started with a picture of FlatIrons that was taken by me when I worked in Longmont. 

I first increased the size of my canvas by adding 120 or so pixels onto the bottom. (Ctrl-R, or Image-Resize)

I used the Rectangle Select tool to select the mountain piece, then Ctrl-C to copy it.  Ctrl-Shift-V pastes my selection into a new layer.

From the Layers menu, select "Flip Vertical" and this will turn it upside down.  Then I type 'M' to get the 'Move Selected Pixels' tool, and move the flipped layer down to the bottom of my canvas.  This lines up the old bottom of the picture with a flipped copy of it's self.

 

 

Keep this layer selected. 

Use the Effects-Noise-Add Noise menu item to litter up the selection with a bunch of white specs.

Use the Effects-Blur-Motion Blur menu item to make the water 'wavy' - Do that by setting the angle to 180 and extending the blur to lengthen the lines.  Keep this selection active.

Once again, copy then paste into a new layer. 

Set the layer properties to Transparency of 70.  (F4 to access, or Layers-Properties)

Keeping this area selected, bump up the contrast to lighten this layer.

 

Move your layer selection back down to the first layer, the original picture - but keep your 'water' selection active.  Copy once again.

Move your layer selection up to the layer that is lightened and made transparent.  Then once again, paste your clipboard into a new layer.

For this third layer - once again set the transparency to 90.  This time use the Contrast tool (Ctrl-Shift-C, or Adjustments-Brightness/Contrast) to Darken the layer.

Now here's the fun piece.  Grab the Lasso select tool and with your mouse down for the whole time - drag your cursor back and forth and back and forth in a wave pattern across the darkened layer you just added.  Keep the lines you're drawing very skinny at the top of your "water" and far apart.  By the time you get to the bottom of your "water" your polygons should be wider - but closer to each other - think wave.

When you've filled the water with your lines, let up the mouse this will select portions of the transparent darker layer.  DELETE your selection.  And magic waves appear.

Here's a link to the final product - I like it for the background of my Twitter page.

NEXT - I'm going to take my waves into Expression Design.  These guys are going to ripple across the screen in the background of my new toy web site, Dont Call Me Late For Supper.

 

ReMix, a new API from Best Buy

by Bobbi Perreault 11. January 2009 13:17
Share on Facebook

When I heard of this API of Best Buy's fom the Minnov8 Gang's podcast,  Mashup Best Buy, I thought I just had to give it a try.  I got an API key at that time, but it took me four long months try it out.  (Let's not talk about time management - not a good subject for me.)

I found it very simple to use, if a bit limited in functionality.  (For example, you can't pull a list of categories from the API, but you can query the products by category once you have that information) 

I wanted to try this in a Silverlight application.  I decided to structure the application so as to keep my API key as part of the server app, so the requests back to the API are done by my web server - not the Silverlight app.

Getting data back from the Best Buy servers is all done with REST requests, like this:

http://api.remix.bestbuy.com/v1/products?sort=name.desc&apiKey=YOURKEYHERE

http://remix.bestbuy.com/docs/types/Products

http://api.remix.bestbuy.com/v1/products(manufacturer='canon'&salePrice%3C33)?apiKey=YOURKEYHERE

http://api.remix.bestbuy.com/v1/products(categoryPath.name='audio'&salePrice%3C33)?apiKey=YOURKEYHERE

http://api.remix.bestbuy.com/v1/products(categoryPath.name=' Blu-ray & DVD Players ')?apiKey=YOURKEYHERE

I couldn't find documentation on what parameters you can pass, but when you send an invalid request ( an incorrect name= value), you receive back all the valid attributes in the error message.  So that was fine.

This silverlight ReMix demo application is written using the Silverlight Islands method of putting together a web app with Silverlight sprinkled through it.  That would be for SEO if this were going to be a real site.  I've written about the Islands method before, you can refer here, and here.

 Source Code (only SL app, not server side.) here   and you need this:  Wrap Panel

Anyway, have fun.  Here's how you can get your own API key to play with this great new source of content for the web.  http://remix.bestbuy.com/

 

Silverlight-Capture and Define Rectangles/Regions and Wire Events on them

by Bobbi Perreault 29. October 2008 12:46
Share on Facebook

This is a piece of an ecommerce tool I built, I call it the Room Builder. It was what I learned Silverlight on - so this project has been brought up from Silverlight 1.1 Alpha version. In other words, you may see some bad practices in there. But it's still useful. Or could be to the right situation.

Specifically today I want to talk about one of the features of the tool, that is the part that allows me to designate a region of a photograph, name it, and wire events to it for use in the application.

There is a link to the finished tool, so you can see what I mean on the left side of this blog, under the section titled "links to some of my silverlight controls".   If you hold your mouse over that link there should be a window pop up with instructions on it's use.

Regions of the photograph are targeted by clicking at key points along the perimeter of a path.  In the case of our Room Builder, it would be the outline of a couch.  After the path is defined, it is finalized with the click of a button.  At that time, the defined region is turned into an object and added to the children of the canvas along with a click event so that object may be selected again.

You can download the source code for this Room Builder application here.  It won't run for you, though, because there is no server side application with the correct web services.  SORRY.  If anyone really needs that piece, I'll be happy to send it.  It's just time I didn't have tonight to spend.  Sorry.

Anyway,

Here's the key points of this region building operation:

Click the Get Started Button, Cursor changes to a pencil.

public void StartMappingClick(object sender, RoutedEventArgs e)
{
//if it's not visible (Opacity), don't execute it.  Stray Clicks.
if ( StartButton.Opacity < 1)
return;
if (_room == null || string.IsNullOrEmpty(_room.ProductID))
{
message.Text = "Please setup your room first.";
return;
}
_gettingRoom = false;
SBHideStartButton.Begin();
SBShowCancelButton.Begin();
SBShowEndButton.Begin();
//for our cursor
ImageMapperCanvas.Cursor = Cursors.None;
LayoutRoot.Cursor = Cursors.None;
///////////////////////
_points = new List();
// Capture mouse and update stat
CaptureMouse();
if (pencil != null)
{
pencil.Visibility = Visibility.Visible;
if (_positionLast != null)
movePencilToCurrentPosition((Point)_positionLast);
}
lastRectangle = "";
_bIn = true;
this.message.Text = "";
}

Click at each turning point of your region

private void HandleMouseLeftButtonDown(object sender, MouseEventArgs e)
{
// Capture mouse and update stat
Point pos = e.GetPosition(this);
if (_bIn)
{
//highlight the point and add it to the list 
registerPoint( pos );
}
_positionLast = pos; //this is so we can track for edits.
}
private void registerPoint(Point pos)
{
//only register the point if it's inside the image.
if (!CheckCollision(pencil, ImageMapperCanvas))
return;
_points.Add(pos);
if (_points.Count == 1)
{
//mark the point
Ellipse el = new Ellipse();
el.SetValue(Canvas.LeftProperty, pos.X+20);
el.SetValue(Canvas.TopProperty, pos.Y+20 );
el.Width=2;
el.Height=2;
el.Stroke = new SolidColorBrush(Color);
el.StrokeThickness = 1;
el.SetValue(Line.NameProperty, lineName(_points.Count));
LayoutRoot.Children.Add(el);
}
else
{
//draw the polyline and add the line to the list.
//string of points
//
// Create new line
var line1 = new Line
{
X1 = _positionLast.Value.X,
Y1 = _positionLast.Value.Y,
X2 = pos.X,
Y2 = pos.Y,
Stroke = new SolidColorBrush(Color),
StrokeThickness = 1
};
line1.SetValue(Line.NameProperty, lineName(_points.Count));
LayoutRoot.Children.Add(line1);
}
}

Click the End Mapping Button

public void EndMappingClick(object sender, RoutedEventArgs e)
{
//if it's not visible (Opacity), don't execute it.  Stray Clicks.
if (this.EndButton.Opacity < 1)
return;
SBHideStartButton.Begin();
SBHideEndButton.Begin();            
SBShowcontrolgrid.Begin();
// Release mouse and update stat
ReleaseMouseCapture();
ImageMapperCanvas.Cursor = Cursors.Stylus;
LayoutRoot.Cursor = Cursors.Arrow;
if (pencil != null)
{
pencil.Visibility = Visibility.Collapsed;
}
if (_bIn)
{
_ictr++;
//we could switch this out based on a selected tool.
// MapItemBO newMapItem = drawRectangle();
MapItemBO newMapItem = drawPolygon();
if (newMapItem != null)
persistMapItem( newMapItem );
_bIn = false;
_points.Clear();
}
this.message.Text = "Search for the outlined item";
}

During this end mapping process, a path object is created, a click event attached, and that object is added to the children of the canvas.

code to get this path added to the canvas, by the way, if you download the sample code, this is line 509 in page.xaml file.

private void createPolygonPath(string dataline, string shapeName, Color border)
{
//to get this I used code from CreateWPFShape.  I also brought up Blend to
//help me with the Xaml for the path, expecially fill and opacity.
//also, in my diagram maker is code which created path geometries programatically
//this came from scribbler.
//
//extract the point array from this dataline. remove markup from the dataline.
dataline = dataline.ToLower();
int ilen = dataline.IndexOf("coords=");
if (ilen < 7)
return; 
dataline = dataline.Substring(ilen + 7);
ilen = dataline.IndexOf("href");
if (ilen < 1)
return;
dataline = dataline.Substring(0,ilen);
dataline = dataline.Replace("\"", "");
//message.Text = dataline;  //for debugging.
string[] pointlist = dataline.Split(','); //split the dataline on commas into an array.
if (pointlist.Count() < 2)
return;
// Create a new geometry.
PathGeometry geometry = new PathGeometry();
// Create a new path figure.
PathFigure figure = new PathFigure();
figure.Segments = new PathSegmentCollection();
// Add figures to the geometry.
for (int i = 0; i < pointlist.Count(); i++)
{
// Determine the starting index and the end index
// into the points array that defines the figure.
int ptx = -1;
int.TryParse( pointlist[i], out ptx);
int pty = -1;
if ( pointlist.Count() > 1 && i != (pointlist.Count() - 1) )
int.TryParse( pointlist[i + 1], out pty);
else
int.TryParse( pointlist[ pointlist.Count()-1 ], out pty);
System.Windows.Point pt = new Point( ptx, pty);
if (i == 0)
figure.StartPoint = pt;
else
{
LineSegment linesegment1 = new LineSegment();
linesegment1.Point = pt;
figure.Segments.Add(linesegment1);
}
i++; //move to the next x
}
if (geometry.Figures == null)
{
PathFigureCollection pathFigures = new PathFigureCollection();
pathFigures.Add(figure);
geometry.Figures = pathFigures;
}
else
{
// Add the new figure to the geometry.
geometry.Figures.Add(figure);
}
// Add the geometry to a new Path and set path properties.
System.Windows.Shapes.Path path = new System.Windows.Shapes.Path();
path.Data = geometry;
path.SetValue(Canvas.NameProperty, shapeName);
SolidColorBrush brw = new SolidColorBrush(Colors.White);           
path.Fill = brw;
path.Opacity = .40;
SolidColorBrush brb = new SolidColorBrush( border ); 
path.Stroke = brb;
path.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(editItemClick);
LayoutRoot.Children.Add(path);
}

Happy SILVERLIGHT! 

Maintaining Browser History in a Silverlight Application

by Bobbi Perreault 10. October 2008 23:37
Share on Facebook

The purpose of maintaining browser history is to give you the ability to enable the browser back button and code for deep linking which improves your discoverability.

This blog post is part four of my four part series on SEO for Silverlight Applications. I put this content together for my talk I gave at the Minnesota Developer's Conference which was last September. Fun Times. ( :-) )

The other posts in this series can be found here: Part I, Menu in Html and Silverlight, this is the Sitemap demo
Part II, Show Multiple Silverlight Controls in the Same Page with jQuery
Part III, service layer communicatioons - the Glue between the Silverlight and the WebApp

Maintaining history and deep linking ability

There are other ways to do this, maintaining browser history for back button and deep linking. In these early days of MVC, though, it’s a little bit to me like the Wild Wild West. What I mean is sometimes it just feels like every man for himself and the man with the biggest gun is the law. So, I brought out the big gun for my browser history and found myself a jquery plugin that is supposed to do the job.

Using this jQuery plugin, jquery.history - I’ve linked to the file menu.js which we’ve looked at before. In the function that sets up all the actions I need to complete when the page loads, ( that’s the $(document).ready function), I've placed a function call that initializes the history object.

 
function pageload(hash) {
// hash doesn't contain the first # character.
if(hash) {
// restore ajax loaded state
$("#load").load(hash + ".html");
} else {
// start page
$("#load").empty();
}
}

Every time a Silverlight object invokes a script that loads a new Island, it also invokes this Function silverlightHistory with the parameter (detailsurl) . This is the script which sets my new url up in the address bar, and adds me to history.

   
// set onlick event for buttons
function silverlightHistory( detailsurl ){
// 
var hash = detailsurl;
hash = hash.replace(/^.*#/, '');
// moves to a new page. 
// pageload is called at once. 
$.historyLoad(hash);
return false;
}
function setProductDetails( detailsurl )
{
//setup for browser history
silverlightHistory( detailsurl );
$('#sl-productListDisplay').html(" ").fadeOut();
//fetch the silverlight product details and plug it in, it's controlid #2
$.get('/Content/sl.htm', function(data){ 
setSilverlight(data, 'ControlId', 2, 'sl-productListDisplay', detailsurl);})
}

service layer communications - the Glue between the Silverlight and the WebApp

by Bobbi Perreault 5. October 2008 12:54
Share on Facebook

The way that we obtain our data from our web app.

This blog post is part three of my four part series on SEO for Silverlight Applications. I put this content together for my talk I gave at the Minnesota Developer's Conference which was last September. Fun Times. ( :-) )

The other posts in this series can be found here: Part I, Menu in Html and Silverlight, this is the Sitemap demo
Part II, Show Multiple Silverlight Controls in the Same Page with jQuery

The problem is how to transport objects and data from server to silverlight and back and do the least amount of typing. The databinding features of a Silverlight application make transporting the contents of an object from code to UI so simple. That's how I want to be able to send the contents of the object to the server and how I want to be able to retrieve the contents of that object on the server side.

In a monolithic web application this is simple, serialize from object to xml, do your transport, and serialize back to the object again.

In a silverlight project, it's not so simple for these reasons:

  1. Silverlight doesn't natively support serialization
  2. Silverlight objects in binary form must be in a separate runtime. (double the fun)
  3. You cannot share the same binary business class between the server app and the Silverlight app.- thus Each class in a Silverlight project must be echoed as a class on the server

One way to resolve these problems and add a service layer to your .aspx/mvc application is to use a code generator and run codegen for the xml views. Then when the Silverlight control requests data, redirect the request from an html based view to an xml based view. That’s how the addition I made to the MVC Store application is built.

There are a few aspects of this this blog post won’t cover today. When you use XML, you increase the size of your request object which slows down your silverlight app and pushes up against built in request limits. Normally, in a production application, to avoid these difficulties, I would add a compression /decompression step in the middle of the communication. This saves on bandwidth. Today’s demo doesn’t use compression – but you need to be aware of the need for it. Because Silverlight doesn’t natively support compression, you’ll need to take advantage of other add-in’s that do. (modification: 10/26/2008 - you can find a compression library in this control set: Silverlight Contrib)

Ok, let’s take a look at the parts of this program which are responsible for the communication. On the MVC application side, in global.asax – we’ve got a controller wired up for ajax style communications. This controller is the CatalogController and it has an Action inside that goes into play when a url beginning with ‘async’ is requested. This action is going to get the requested data from the database, and send it to an xml view for formatting.

In the xml view, there’s one thing to note here and that is the >xml opening tag is up on the top line to eliminate whitespace which causes errors in xml parsing in silverlight. But apparently there isn’t a way to eliminate enough of it so I do also trim the returned xml string from inside of Silverlight. – Before I load it into XDocument, I must trim it. So, anyway, let’s look at the Silverlight control and walk through how the product list is pushed out to the user. Appl.xaml.cs receives the incoming request to start the application.

if (!string.IsNullOrEmpty(controlid)){switch (controlid){case "1":this.RootVisual = new menu();break;case "2":try {string param = e.InitParams["requested"];this.RootVisual = new ProductDetails( param );}

It receives a controlid valued at “2”. When this code receives a #2 for controlid value in InitParams, This code reads the InitParams “requested” value. Requested InitParams value is a URL.

If one exists, the ProductDetails.xaml is then created with the value of the URL passed into the constructor.

In the constructor of ProductDetails,

  1. The constructor hooks up the page loaded event and sets a member var to the value received for requested into the _url member.
  2. In the pageload, if _url isn’t empty – then we call a function called initiateValuesLoad and this is where the request will be made back to the server for this object’s contents.
       void initiateValuesLoad(){
try{string URIvalue = getBaseUrl();       
string serviceUrl = URIvalue + "async" + _url;       
WebClient client = new WebClient();       
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(DownloadStringCompletedEvent);       
client.DownloadStringAsync(new Uri(serviceUrl));       
}
catch (Exception) { }
}
void DownloadStringCompletedEvent(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null){this.sitemapXML = e.Result;       
if (e.Result.Length > 0)Dispatcher.BeginInvoke(delegate(){setProduct();      
 });}

Function initiateValuesLoad is just very simply a WebClient request. It’s asyncronous, so we set our DownloadStringCompletedEventHandler to a value of a function that will parse our received data. If we get data back. So in DownLoadStringCompleted, if the value of the DownloadStringCompletedEventArgs is not an error condition – the member variable called sitemapXML (a string var) is set to the Result of the request and we call function setProduct which is going to finish up here. Now, Dispatcher.BeginInvoke is used here to launch a thread within which the setProduct function will do it’s job. We need to use Dispatcher.BeginInvoke because we are not allowed to touch the UI from a background thread. We must get on this Special Plane of Existence which allows us to touch the UI.

        private void setProduct()
{
sitemapXML = sitemapXML.Trim();         
//removing spaces at the beginning, the spaces seem unavoidable with this approach.
XDocument xdoc = XDocument.Parse(sitemapXML);
_Cat = from productitem in xdoc.Descendants("productMapNode")
select new CatItem{
CatItemName = (string) productitem.Attribute("name"),
CatItemPageUri = (string) productitem.Attribute("pageurl"),
CatItemImage = (string) productitem.Attribute("imageurl"),
Productcode = (string) productitem.Attribute("productcode"),
Price = (string)productitem.Attribute("price")};        
 if (_Cat.Count() >          0)
{
fillWrappable();          
//the object is ready to show itself - this is javascript call.
HtmlPage.Window.Invoke("          showProductList");}
}

In setProduct we trim our white space, parse our XDocument, load our IEnumerable as we saw in the Sitemap Menu example – same code that’s used in the menu loading a different business object class. Same XLinq. And in the end we go to our fillWrappable function which will create the XAML for our category list display.

What I’ve done here in this function is, I created a button template, which is XAML. That I’m going to load from a resource. I placed this XAML snippet into a resource because I wanted to have some experience of using a resource – it could just as easily have been a string variable within this function.

<button xmlns="http://schemas.microsoft.com/client/2007 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" style="{2}" x:name="Cat{5}">
<button.template>
<controltemplate targettype="Button">
<grid>
<grid.background>
<lineargradientbrush endpoint="0.5,1" startpoint="0.5,0">
<gradientstop color="#FF000000" />
<gradientstop color="#FF0B123C" offset="1" />
</lineargradientbrush>
</grid.background>
<grid.rowdefinitions>
<rowdefinition height="*" />
<rowdefinition height="Auto" minheight="35.772" />
<rowdefinition height="Auto" minheight="74" />
</grid.rowdefinitions>
<grid.rendertransform>
<transformgroup>
<translatetransform x:name="MainButtonTranslate" x="0.0" y="0.0" />
<scaletransform x:name="MainButtonScale" scalex="1.0" scaley="1.0" />
</transformgroup>
</grid.rendertransform>
<vsm:visualstatemanager.visualstategroups>
<vsm:visualstategroup x:name="CommonStates">
<vsm:visualstategroup.transitions>
<vsm:visualtransition duration="0:0:0.5" to="MouseOver">
<storyboard>
<doubleanimation storyboard.targetname="MainButtonScale" storyboard.targetproperty="ScaleX" to="1.1" duration="0:0:0.05" />
<doubleanimation storyboard.targetname="MainButtonScale" storyboard.targetproperty="ScaleY" to="1.1" duration="0:0:0.05" />
</storyboard>
</vsm:visualtransition>
<vsm:visualtransition duration="0:0:0.5" to="Pressed">
<storyboard>
<doubleanimation storyboard.targetname="MainButtonTranslate" storyboard.targetproperty="X" to="1.1" duration="0:0:0.05" />
<doubleanimation storyboard.targetname="MainButtonTranslate" storyboard.targetproperty="Y" to="1.1" duration="0:0:0.05" />
</storyboard>
</vsm:visualtransition>
</vsm:visualstategroup.transitions>
<vsm:visualstate x:name="Normal" />
<vsm:visualstate x:name="MouseOver"></vsm:visualstate>
<vsm:visualstate x:name="Pressed"></vsm:visualstate>
<vsm:visualstate x:name="Disabled">
</vsm:visualstate>
</vsm:visualstategroup>
<vsm:visualstategroup x:name="FocusStates">
<vsm:visualstate x:name="Focused"></vsm:visualstate>
<vsm:visualstate x:name="Unfocused"></vsm:visualstate>
</vsm:visualstategroup>
</vsm:visualstatemanager.visualstategroups>
<image 'image{5}' Source='{1}' Style='{4}' Grid.Row='0' Margin="6,4,6,0" Grid.RowSpan="1" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<textblock style="{3}" text="{0}" grid.row="1" verticalalignment="Bottom" foreground="#FFFFFFFF" x:name="title" />
<textblock horizontalalignment="Left" verticalalignment="Top" text="{6}" textwrapping="Wrap" x:name="Desc" grid.row="2" foreground="#FFFFFFFF" /></grid>
</controltemplate>
</button.template>
</button>

You can see here where the one button for each category item in the list, we take our button xaml string and we format it together with our product data and some style information using String.Format. The xaml control is then created using XamlReader.Load( onebutton )

The new button object receives it’s click event, And the button is appended to this Wrappable object which exists in the XAML and is going to present my buttons side by side then down.

      private void fillWrappable(){
StringBuilder sb = new StringBuilder();     
 //I don't think you can use a Border in here when you're using a wrap panel. I couldn't take the time to see why though
byte[] bytes = ReadBytesFromStream("MDCSilverlightDemo.BL.buttontemplate.xml");      
string buttonTemplate = "";      
UTF8Encoding encoding = new UTF8Encoding();      
buttonTemplate = encoding.GetString(bytes.ToArray(), 0, (int)bytes.Length);      
int ictr = 0;     
foreach (CatItem mnu in _Cat)
{
string onebutton = string.Format(buttonTemplate, mnu.CatItemName, mnu.CatItemImage,"      {StaticResource buttonStyle1}",
"{StaticResource CatItemNameBlock}", "{StaticResource ThumbNailPreview}",ictr.ToString());     
 ictr += 1;      
Button bt = (Button)XamlReader.Load(onebutton);      
sb.Append(onebutton).Append("\r\n");      
bt.Click += new RoutedEventHandler(bt_Click);      
Wrappable.Children.Add(bt);      
}
string allbuttons = sb.ToString();       
//debug.   !!!}

So, basically – that’s it for our Service Layer Communciation.

menu in html and silverlight

by Bobbi Perreault 12. September 2008 22:27
Share on FacebookDemo source links (big download warning)

Links to this demo project, which is created from MVC Storefront -  (Updated:  link to the demonstration page)

MVC Storefront. is an ecommerce demo and is a perfect starting place for Silverlight enhancement - you can get help for MVC Storefront here:  http://www.codeplex.com/mvcsamples

This html/silverlight demo is built as it is to enable the site to conform with a Web standards-based approach. On the UI layer, conforming to Web standards means 100% separation of presentation from content and structure, as well as the scripting behavior of UI elements. On the back end, this means limiting the mixing of UI code in the Web applications and CMS code that may need periodic updates, it means applying the same strict separation as to any other static screen.

There’s a fine line here, at the same time we need to avoid the appearance of "cloaking".  "Cloaking" is the tactic of detecting search engine spiders when they visit and varying the HTML code specifically for the spiders in order to improve rankings. This is only acceptable in a very limited use: namely, as a way of simplifying search engine unfriendly links. If you are in any way selectively modifying the page content, this is nothing less than a bait-and-switch. Search engines have undercover spiders that masquerade as regular visitors to detect such unscrupulous behavior.

In this demo, The Silverlight control will be responsible for both retrieving it ‘s content from the server as well as hiding the indexable content using CSS. By having the Silverlight menu hide the indexable menu we have just solved the problem of how to know if the user has Silverlight Installed and how to know if the user has Javascript enabled.

Let’s look at the Silverlight project: I’m going to start off by opening an mvc project that I downloaded from Codeplex, the MVC Storefront. This is an ecommerce demo and is a perfect starting place for Silverlight enhancement.

The requirements for the menu are this:   If the client doesn't support Silverlight ( bots, clients who  haven't installed it yet, older browsers, etc.)  - keep the page in pure html. If the client does support Silverlight, load this menu from the exact same source that the MVC view menu is using.

As stated previously, the Silverlight control is constructed in such a way that it will – if created – retrieve it’s contents from the server and after formatting it’s self – hide the html version. Once we have our Silverlight, we don’t really give control back to the html and that avoids any further delays to the user.

Let’s look at how this is setup. Here is the MVC Storefront application. Let’s have a really quick overview of how an MVC app is constructed. First, Global.asax file has code in application startup to assign controller classes to specific urls. All the controller files are located in folder: Controllers. When a request for one of the assigned urls is received at the server, that controller is called along with the default action. In our case, the MVC storefront default page goes into the CatalogController,and uses the function of Index. Index method will gather the menu details into a list of type CatalogData and pass that list over to the View also called Index - which will use that data to present the user page. All the view files are located in the folder called View. So, we can find our Index view in folder View/Catalog/Index.aspx. Index view renders a control called CategoryList.ascx which presents the CatalogData list in the form of an html menu. Here’s what this page looks like.

<div id="sl-categoryNavigation" ></div>
<div id="categoryNavigation" class="categoryNavigation">
    <%foreach (Category parent in ViewData) { %>
    <h3><%=parent.Name %></h3>
    <ul>
        <%foreach (Category child in parent.SubCategories) { %>
        <li>
        <%=Html.ActionLink<CatalogController>(x=>x.Index(parent.Name,child.Name),child.Name) %>
        </li>
        <%} %>
    </ul>
    <%} %>
</div>

View the source of this MVC page, and you’ll see clear, clean html. No mangled ID’s. No ViewState.  When this page loads, this Index.aspx view, there is some javascript contained in an included file that comes into play. The javascript is contained in an included file to preserve the separation of presentation from content and structure. This script is is executed when the page is ready. The file is called menu.js, and it uses JQuery to simplify scripting.

We’ll see that in great detail in our next demo. For this demo, we’re going to skip that part and get right to the Silverlight.

What we’re interested in here is in how this Silverlight menu can be created from the same source as the html menu. That saves me as the programmer from a lot of maintenance during the life of this application.

Let’s look at the Silverlight control first. In our Silverlight project there’s a control called menu.xaml. In menu.xaml we have a Grid which contains a StackPanel which contains a ListBox. There is a template applied to the ListBox which enables another ListBox to be embedded within. All this xaml is written in such a way so as to make it skinnable.

We’re going to skip over the part where this Silverlight control contacts the server for it’s menu details and save that for later. Right now, we’ll look at what happens when fully formatted web.sitemap xml is ready to be turned into a menu. Here’s what the web.sitemap this control works with looks like.

<?xml version="1.0" encoding="utf-8" ?>
<siteMap >
<siteMapNode>
    <siteMapNode url="" title="Apparel" description="Apparel" menugroup="Apparel" itemType="menuTitle"  >
          <siteMapNode url="/store/Apparel/Boots" title="Boots" description="Boots" menugroup="Apparel" itemType="menuItem"  />
           <siteMapNode url="/store/Apparel/Hats" title="Hats" description="Hats" menugroup="Apparel" itemType="menuItem"  />
           <siteMapNode url="/store/Apparel/Sunglasses" title="Sunglasses" description="Sunglasses" menugroup="Apparel" itemType="menuItem"  />
         </siteMapNode>
...
    <siteMapNode url="" title="Featured Items" description="Featured Items" menugroup="Featured Items" itemType="menuTitle"  >
          <siteMapNode url="/store/Featured Items/All Items" title="All Items" description="All Items" menugroup="Featured Items" itemType="menuItem"  />
         </siteMapNode>
</siteMapNode>
</siteMap>

This data is passed into the code behind of menu.xaml first as a string. (open the menu.xaml.cs file and move to method setMenu())

In the setMenu method,

I’m going to build some business objects to represent the Silverlight menu and I’m going to use  LINQ over Xml ( Xlinq  )

One thing I ran into while working with LINQ over Xml that I’ll just mention real quick and that is: namespaces can complicate the code. And in my case, since it was my site map, talking to my server, I just removed the namespace which was in the first node.   This saved me from writing some code to match namespaces.

To get the Xdocument to load up in my Silverlight control from my Sitemap XML,  I created two new Business Object classes in my project.  The first class represents a SiteMapNode.  The second class represents a MenuGroup.

Here’s what the LINQ looks like which is going to parse the XML into an IEnumerable list of my sitemap objects, which are the menu items. Use the XDocument.Parse to get my xml string into an XDocument. And then I’m going to select from xdoc.Decendentants(“siteMapNode”) new SitemapBO objects – and load em up right there. The url property is assigned from the sitemap attribute “url”. The title is assigned from attribute title, and so on.

The sitemapbo class, which we have just made a list of is just a property bag which stores and organizes my XML for me.

SiteMapBO.cs:

public class SiteMapBO
    {
        public string url { get; set; }
        public string title { get; set; }
        public string description { get; set; }
        public string menugroup { get; set; }
        public string itemType { get; set; }
    }

XLINQ:

xdoc = XDocument.Parse(sitemapXML);

IEnumerable<SiteMapBO> menuitems = from menuitem in xdoc.Descendants("siteMapNode")
               select new SiteMapBO
               {
                   url = (string)menuitem.Attribute("url"),
                   title = (string)menuitem.Attribute("title"),
                   description = (string)menuitem.Attribute("description"),
                   menugroup = (string)menuitem.Attribute("menugroup"),
                   itemType = (string)menuitem.Attribute("itemType")
               };

Once I have this list of sitemapbo objects, I still need to organize it into a list of MenuGroup objects.

How I format the menu using "menuItem" and "menuTitle" of the sitemap.

What I want is to have my menu titles to be independently skinnable from my menu items.  It gives me freedom, I can make my menu look special because I can touch each type of object within it with a skin. I have the ability to make this Silverlight something special and That's what the MenuGroups class is for.  After getting a list of SiteMapBO's from the Xdocument, in the olden days, I would then re-traverse the list to load a new IEnumerable list of MenuGroups - and this is the list that gets bound to my ListBox.

Today, I’m able to do this using LINQ to Objects.

MenuGroup class consists of a Title property , and a list of SiteMapBO’s.

MenuGroups.cs:

public class MenuGroups
   {
       public string title { get; set; }
       public List<SiteMapBO> itemsList { get; set; }

       public MenuGroups()
       {
           itemsList = new List<SiteMapBO>();
       }
   }

 

Here’s the code that will load the list which will become my ItemsSource of the menu ListBox  using LINQ to Objects, Framework V3.5:

List<MenuGroups> menus = new List<MenuGroups>();
if (menuitems.Count() > 0)
{
    foreach (var menugroup in menuitems.Where(grp => grp.itemType == "menuTitle"))
    {
        MenuGroups mnu = new MenuGroups();
        mnu.title = menugroup.title;
        mnu.itemsList = menuitems.Where(p => p.itemType != "menuTitle" && p.menugroup == menugroup.menugroup).ToList();
        menus.Add(mnu);
    }
    this.MenuList.ItemsSource = menus;

    //the object is ready to show itself - this is javascript call.
    HtmlPage.Window.Invoke("showSlMenu");
    //here, we'll show ourself with an animation
    this.showMyself.Begin();
}

Ok, at the end, then, when I’ve finished building the list of MenuGroup objects, I just set the ItemsSource of the MenuGrid Listbox to this list, menus. And the Styling takes care of the rest of it – that’s it, we’re done.

This is how I build this Silverlight menu control so that it is skinnable and that way I can re-use it in different projects.

In the XAML, notice that this solution allows for individual formatting of the header and the items.

This is the menu: (a stackpanel which contains an image and a listbox.)

<StackPanel x:Name="MenuGrid" >
<Image Source="images/bdlogo.png" Stretch="Fill"/>
<ListBox x:Name="MenuList" Style="{StaticResource menuTitleStyle}" >
</ListBox>
</StackPanel>

 

The listbox name is Menulist, and it’s styled with a StaticResource, menuTitleStyle. The menuTitleStyle is contained in the top of the XAML file, inside the UserControl.Resources section.

Here is the menuTitleStyle:

   It has an Item Template which contains a TextBlock and a ListBox. The TextBlock is using databinding to set it to the MenuGroup property, Title. And the ListBox is databound to the itemsList property of that menugroup.

<Style TargetType="ListBox" x:Key="menuTitleStyle" >
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding title}"
Style="{StaticResource menuTitle}" />
<ListBox ItemsSource="{Binding itemsList}" SelectionChanged="MenuList_SelectionChanged"
Style="{StaticResource menuItemsStyle}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>

So, to finish this piece I need to discuss how the Silverlight menu replaces the html menu. While the Silverlight menu is getting it’s xml, formatting it’s xml, putting it’s face on – it’s not visible on the web page. Once it’s ready there is a line of code here that runs a javascript which is linked to the web page. Here’s the line of code that executes the javascript, which will take care of sizing the div which contains our Silverlight control to place it over the top of the Html control (this is a modified version of CSS Image replacement technique)

//the object is ready to show itself - this is javascript call.
HtmlPage.Window.Invoke("showSlMenu");

Following this line is the animation (Silverlight) which fades in the new menu.

//here, we'll show ourself with an animation
this.showMyself.Begin();

Let’s look at showSLMenu in the file menu.js file

First, in this SLMenu function we get the width and height of the html menu.

Then, The div which holds the Silverlight control is then resized to match the original menu. In our css, we’ve set the z-index property of this div very high which will position it over any html content with a lower z-index.

And lastly, the Silverlight contol’s opacity is brought from 0 to 100 which fades it into place.

 

RSS Feed FriendFeed