Page view counter

November 2008 - Posts

SandDock for Silverlight – Commercial floating windows

Every once in a while you see something so promising you just have to pass it along.  The folks at divElements are developing SandDock for Silverlight; a floating windows management toolkit that looks very sweet indeed.

SandDockFoating

I can't say I've really put it through its paces, or pushed its limits, but there is a very professional feel about the work they've done so far, and I suspect this is a product worth keeping an eye on --  especially if you happen to need what they have on offer. From what I can tell, they're in (free) Beta now, with a product on the near horizon.

There are of course a number of great third party tool makers for Silverlight, the interesting question over the coming months will be whether folks will turn to buying their tools or creating them from the fundamentals provided by Microsoft and the Toolkit project.

On C# and VB

I've noticed that there is a bit of (understandable) confusion about what we publish in C# and what we publish in VB, and I thought I'd write a brief not to try to sort it out.

I believe Tim and I are in agreement that:

  • Our blog samples will be in whichever language we happen to be most comfortable in, with some variety now and again (and I further suspect that will usually be C#)
  • The videos that we create will (nearly) always provide source code in both C# and VB, but the video itself will be in our language of comfort
  • At the moment, I'm writing all the tutorials, and I'm doing so in both C# and VB.NET --- that each tutorial is published in a C# version and a VB version, in both HTML and in a pdf format, and of course the code is available in both.

TutorialLanguages

The goal is to provide access to all of this information at a very high level of comfort for both VB and C# programmers. 

My personal opinion is that it is important to be able to read programs in either language; they are incredibly similar, and translating from one to the other is, for the most part, mechanical. 

Instant Maven

One wonderful resource, both for saving time and for learning whichever language you are less comfortable in, is InstantC# or InstantVB

 InstantVBCompleted2

It is not uncommon for me to put in an entire solution developed as an example for a tutorial or video and have it converted in seconds, requiring no hand changes whatsoever

(Full disclosure: as noted  previously, I've been receiving free evaluation copies). 

Notice in the illustration above that I've circled the link to "Compare converted to original code. This little feature can be very useful when you want to learn a bit of VB/C# syntax,

InstantVBComparison

though the truth is that the limitations in this window mean that you can only go so far. If I were truly interested in spending time looking at the two versions I'd put the files (or the entire folders) through Exam Diff Pro – my favorite utility for comparing files or directories,

ExamDiff

What is interesting here, though, is not the quality of the comparison utility but rather that such a comparison is possible and straight forward.  You can practically map from the C# to the VB, word by word, line by line.

VBCSharp

You are very much entitled to love one and hate the other, but I would suggest it is the familiarity that breeds the contempt; the true hatred one can only feel for members of one's own Klan.

In any case, next stop: Iron Ruby.

Hope your holiday was splendid.

Podcast on DataBinding

Sparkling

Happy to return to my monthly interview on the Sparkling Client podcast; this month discussing the fundamentals of data- binding.

This podcast is supported by the following links

Posted by jesseliberty | 1 comment(s)
Filed under:

Graphing – Silverlight Toolkit

Okay, thus begins random excursions into aspects of the Silverlight Toolkit, supplementing a more systematic set of videos….

The first thing you need to know about the Silverlight Toolkit is that there are two greatly under-utilized resources available: The Silverlight Toolkit home on Codeplex (where you can get the source and samples) and the dedicated Silverlight Toolkit Controls forum on Silverlight.net (forum 35).

Today I'd like to start looking at some of the graphs. 

One thing you immediately notice when you look at all the graphs at once, is that there is a good deal of commonality in how most work: they want a set of data that can be identified as the IndependentValue and the DependentValue (more on this in a moment) and they want the ItemSource that supplies these values. They would like a title and  most take a width and a height and a name.

Yes, there's a good bit more, but you can go pretty far with that.

Independent vs Dependent Value

Without getting into the technical definition of these terms at all, you can quickly see by looking at the graphs, that the IndependentValues  (IVs) are the names of the things you want to measure,  while the DependentValues (DVs) are the numeric quantities for each of the IVs (this is not a precise definition but a good enough working one.)

You see this immediately when you look at a column series

DVIV

In this graph, Activity is the independent value and Time is the dependent value.

You can just as easily re-label this graph with different independent and dependent variables and it will still make sense,

DVIV2

With these new labels we could well be looking at my monthly budget from 1973.

Creating this chart with the Chart Controls is surprisingly easy.  We start by creating the data, which I prefer to delegate to a class; a better analog to getting the data from outside the program (e.g., from a web service),

Thus, I'll add Expense.cs to the project,

using System.Collections.Generic;

namespace SimpleCharts
{
  public class Expense
  {
    public string Name { get; set; }
    public double Value { get; set; }

    public static List<Expense> GetExpenses()
    {
      List<Expense> expenses = new List<Expense>()
      {
        new Expense() { Name = "Rent",   Value = 175.63 },
        new Expense() { Name = "Books",  Value = 102.77 },
        new Expense() { Name = "Food",   Value = 49.33 },
        new Expense() { Name = "Music",  Value = 75 },
        new Expense() { Name = "Else",   Value = 22 }
      };
      return expenses; 
    }
  }
}

This simple class has two public properties and a static method for generating some values.

Creating the Chart in Xaml

In my first version, I'll simply create a column graph in Xaml and then in the code, set a collection of expenses as its itemSource.  Here's the Xaml,

<UserControl x:Class="SimpleCharts.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:controls="clr-namespace:Microsoft.Windows.Controls;
assembly=Microsoft.Windows.Controls" xmlns:charting="clr-namespace:Microsoft.Windows.Controls.DataVisualization.Charting;
assembly=Microsoft.Windows.Controls.DataVisualization" Width="800" Height="500">
    <Grid x:Name="LayoutRoot" Background="White">
    <Grid.RowDefinitions>
      <RowDefinition Height="8*" />
      <RowDefinition Height="2*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="1*" />
      <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>

      <charting:Chart x:Name="Budget" Margin="10">
      <charting:Chart.Series>
        <charting:ColumnSeries 
          Title="1973" 
          IndependentValueBinding="{Binding Name}"
          DependentValueBinding="{Binding Value}" />
      </charting:Chart.Series>
    </charting:Chart>
      
   </Grid>
</UserControl>

Note that we're adding two namespaces that depend on your adding references for the two assemblies from the Toolkit: DataVisualization.dll and Controls.dll.

The Grid is divided into two rows (80/20) and two columns (50/50) though for now we'll use only the upper left corner. The chart is given a name, and then a series, though in this case we'll have only one series: the columns. We set the IV to bind to the Name of each object and the DV to bind to the value of each object. We'll supply the object (through the ItemSource) in code.

Binding Data To the Chart

The code to bind data to this chart is in Page.xaml.cs,

public Page()
{
  InitializeComponent();
  ColumnSeries cs = Budget.Series[0] as ColumnSeries;
  cs.ItemsSource = Expense.GetExpenses();
}

We are reaching into the control we've named Budget and asking for the first member in its series collection, which we know to be of type ColumnSeries. We can then set the ItemSource on that ColumnSeries to what we get back from the static method GetExpenses, which you will remember is a list of Expense objects, each of which has two public properties: Name and Value, which is just what the control is looking for. The result is a graph based on the data,

FirstChart

Adding A Pie Chart

Because all the charts work the same way, adding a pie chart is trivial. We can cut and paste the column chart and just change the name, and the charting series from ColumnSeries to PieSeries. Other than that, there is nothing to change but the Grid.Column.

<charting:Chart x:Name="BudgetAsPieChart" Grid.Column="1" 
      Margin="10">
  <charting:Chart.Series>
    <charting:PieSeries
      Title="1973" 
      IndependentValueBinding="{Binding Name}"
      DependentValueBinding="{Binding Value}" />
  </charting:Chart.Series>
</charting:Chart>

The code to bind the data is also nearly identical,

 

public Page()
{
  InitializeComponent();
  ColumnSeries cs = Budget.Series[0] as ColumnSeries;
  PieSeries ps = BudgetAsPieChart.Series[0] as PieSeries; 
  ps.ItemsSource = cs.ItemsSource = Expense.GetExpenses();
}

This puts the columns in the upper left and the pie in the upper right,

SecondChart

Evolving Data

Finally, to make this slightly more interesting, let's add a second static method to Expense that returns the next year's data, and a button to our page that will cause us to tell the charts to change their ItemSource to the new data. The effect is very gratifying; the charts morph from one year to the next.

Here's the method to add to Expense.cs

 public static List<Expense> GetNewExpenses()
 {
   List<Expense> expenses = new List<Expense>()
   {
     new Expense() { Name = "Rent",   Value = 375.63 },
     new Expense() { Name = "Books",  Value = 102.77 },
     new Expense() { Name = "Food",   Value = 149.33 },
     new Expense() { Name = "Music",  Value = 45 },
     new Expense() { Name = "Else",   Value = 222.56 }
   };
   return expenses;
 }

Here's the change to the Xaml (added just before the closing tag for the Grid).

<Button x:Name="Change" Content="Change" 
    FontSize="18" Width="120" Height="30" 
    Grid.Row="1"/>

And finally, here is the change to Page.xaml.cs,

public partial class Page : UserControl
{
  public Page()
  {
    InitializeComponent();
    ColumnSeries cs = Budget.Series[0] as ColumnSeries;
    PieSeries ps = BudgetAsPieChart.Series[0] as PieSeries; 
    ps.ItemsSource = cs.ItemsSource = Expense.GetExpenses();
    Change.Click += new RoutedEventHandler( Change_Click );
  }

  void Change_Click( object sender, RoutedEventArgs e )
  {
    ColumnSeries cs = Budget.Series[0] as ColumnSeries;
    cs.Title = "1974";
    cs.ItemsSource = Expense.GetNewExpenses();

    PieSeries ps = BudgetAsPieChart.Series[0] as PieSeries;
    ps.Title = "1974";
    ps.ItemsSource = Expense.GetNewExpenses();
  }
}

Charts3

[I apologize for the color corruption caused by the gif file]

Cross Platform

Finally, we can open the application on the Mac, and see the same charts, behaving the same way and looking exactly as if they belong on the Mac,

ChartsOnMac

Posted by jesseliberty | 17 comment(s)
Filed under: , ,

A Better Multi-Page Solution

Switch I've posted on multi-page Silverlight applications, and in fact have two videos (here and here) that show an approach that works quite well and that I was happy to steal at the time.

I recently received email from Lucas Stark (Senior Web Developer at Delta College) who suggested (quite correctly) that the pages should not have to find the PageSwitcher each time they want to navigate to another page, but rather they ought to be able to call a static method. 

I've tinkered with the code he provided, factoring out a few things and simplifying, and I now have a version that works as follows:

  • Any page wishing to participate in the switching may do so
  • If the page wishes to send data to the page it is calling, it must implement ISwitchable (defined below)

To make this work, I added ISwitchable.cs that defines the interface:

public interface ISwitchable
{
   void UtilizeState( object state );
}

I also added a new class, Switcher, in the file Switcher.cs

using System;
using System.Windows.Controls;

public static class Switcher
{
  public static PageSwitcher pageSwitcher;

  public static void Switch( UserControl newPage )
  {
    pageSwitcher.Navigate( newPage );
  }

  public static void Switch( UserControl newPage, 
                               object state )
  {
    pageSwitcher.Navigate( newPage, state );
  }

}
[code abridged leaving out test for pageSwitcher != null ]

This static class' PageSwitcher instance is set in the startup code in App.xaml.cs (where the root.visual is set as well)

 private void Application_Startup( object sender, StartupEventArgs e )
 {
    PageSwitcher pageSwitcher = new PageSwitcher();
    this.RootVisual = pageSwitcher;
    Switcher.pageSwitcher = pageSwitcher;
    Switcher.Switch( new Page() );
 }

Let's pause here and consider… when the application starts up, a new PageSwitcher (not seen yet) is created. This class, you may remember from previous coverage, is responsible for acting as the shell that holds the current page.  This instance of PageSwitcher is assigned

We'll look at it in just a moment. It is then assigned as the RootVisual (this value can only be set here and not changed while the program is running).  In addition, that same value is assigned to the static property pageSwitcher in the Switcher class.

This happens before anything else. Thus, upon start up that static class has a PageSwitcher instance available to it.

Finally, in the startup code, the static method Switch is called passing in a new instance of the Page user control.   We see above, that Switch is overloaded. The overload that takes a single argument takes a user control and calls Navigate on the newly instantiated PageSwitcher, passing along the user control. (There is a test to make sure that the PageSwitcher isn't null, but it really can't be, and so I've left that out here).

Let's look at PageSwitcher. The xaml has just an empty user control (as in previous versions), here is the .cs file:

public partial class PageSwitcher : UserControl
{
  public PageSwitcher()
  {
    InitializeComponent();
  }

  public void Navigate( UserControl nextPage )
  {
    this.Content = nextPage;
  }

  public void Navigate( UserControl nextPage, object state )
  {
    this.Content = nextPage;
    ISwitchable s = nextPage as ISwitchable;

     // test that s is not null or throw exception
     s.UtilizeState( state );
  }
}

 

When we call Navigate we pass in a new instance of Page so the first overload is called, and the content of the PageSwitcher class is filled with that user control, and the user sees the page as intended:

Page1
[this image has been cropped to save room]

When you click on switch, the event handler for that button is called and this is where the "great improvement" comes. In the previous version, each page had to look for the PageSwitcher (which was its parent), cast that value and then directly call the navigation. Now, all the page has to do is call a static method on the Switcher, passing in either a new page, or a new page and a value for the new page.

void SwitchToPage2_Click( object sender, RoutedEventArgs e )
{
  Switcher.Switch( new Page2(), YourName.Text );
}

As you can see, in this case, we call Page2 and pass in the text taken from the textBox.  Let's look at the code in Page2.xaml.cs,

using System.Windows;
using System.Windows.Controls;

namespace SimplePageSwitcherForBlog
{
  public partial class Page2 : UserControl, ISwitchable
  {
    public Page2()
    {
      InitializeComponent();
      Loaded += new RoutedEventHandler( Page2_Loaded );
    }

    void Page2_Loaded( object sender, RoutedEventArgs e )
    {
      SwitchToPage2.Click += new RoutedEventHandler( SwitchToPage2_Click );
    }

    public void UtilizeState( object state )
    {
      Message.Text = state as string;
    }

    void SwitchToPage2_Click( object sender, RoutedEventArgs e )
    {
      Switcher.Switch( new Page(), Age.Text );
    }
  }
}

The first thing to notice is that Page2 implements ISwitchable. And sure enough, we see that it does, by implementing the method UtilizeState. The implementation of that is to take the value we passed in, cast it to the string that it is, and assign that string to the Text property of the TextBlock named message in the first row (in red).

I've included the entire file because it is critical to note that this method is never called in this class, yet if you run the program you'll find that in fact the value is displayed! 

This "magic" is accomplished by delegating the responsibility to calling UtilizeState to the second overload of Navigate in PageSwitcher,

 public void Navigate( UserControl nextPage, object state )
 {
   this.Content = nextPage;
   ISwitchable s = nextPage as ISwitchable;
   if ( s != null )
   {
     s.UtilizeState( state );
   }
   else
   {
     throw new ArgumentException( "nextPage is not ISwitchable! "
       + nextPage.Name.ToString() );
   }
 }

When this method is called with two arguments, we know that the second argument is an object to be used in UtilizeState, so we call UtilizeState. To be careful, however, we first make sure that the userControl we've been given does in fact implement ISwitchable (as is required if you're going to use this method); if not we throw an exception.

This is very clean and works extremely well. The UserControl (page) only needs to know about calling the static Switch methods on a static class, the internals are entirely encapsulated and hidden from the consuming user controls, and on the flip side, the internal mechanism (the SearchPage and the Switcher, never need to know any of the semantics of any of the pages.

All of this is covered in detail, and placed in the context of a much more realistic scenario in my forthcoming tutorial (should be published in less than 2 weeks in both C# and VB). For now, the code used in this blog entry is available here.

Posted by jesseliberty | 15 comment(s)
Filed under:

Becoming a Silverlight Programmer

I received a wonderful and very kind email from saleh alabbas who asked this canonical question: "imagine you have good knowledge in C#, what plan you would draw for yourself to become an expert in developing web and Silverlight applications?"

I'm asked this so often, in so many ways, I thought I'd devote a quick column to trying to answer this unanswerable questions (or rather, this question with so many answers!)

One question is how you like to learn. Some folks do best with classes; they like to have an instructor walk them through material and they enjoy (need?) the interaction. Other folks would much rather read a book and still others find a video to be a good compromise. Some folks like to tinker and go to documentation only after burning a hole in the counter, others won't touch the keyboard until the entire program is designed on paper.

With all of that in mind, here would be my plan of action. I'd start by installing Visual Studio 2008 SP1 (or, if I couldn't afford to do that, Visual Web Developer).  I'd then go to  the Get Started page on Silverlight.net and download and install the Tools for Visual Studio 2008 SP1. I'd also download and install Expression Blend 2 and the Blend 2 Service Pack 1 and the Silverlight Toolkit (that is, numbers 1, 2 and 4 in the Get Started Box)

GetStarted

Once this is installed, I'd watch the  Getting Started video and then I'd read the following tutorials

Tutorials

With these two introductory tutorials under your belt, you'll be ready to really dive into Silverlight in a big way. If you like learning by videos, here is the order I'd watch our videos:

Video28 

After reading the tutorial on controls, I'd jump over and watch the video on Grids and Stack Panels and then the video17video on graphics and brushes and perhaps some of the other videos listed under Silverlight 2 Basics and/or Controls though these can get a bit advanced.

You might try out the video34Tab Control Introduction

 

 

 

 

Beyond the Basics

After watching a few of these videos  go on to the tutorial on Data-binding and then come back for some of the related videos such as these

Video43 Video44 Video45 Video46

Video47

 

 

 

 

 

 

 

 

 

 

 

(Each of these images is a link to the corresponding video.)

These should almost certainly be watched in numeric order.

Other Topics

Once you've explored data binding, you might want to go back and spend some time on other topics, a couple favorites include multi-page applications (we have a two-part series)

video2 video18

 

or you might decide to follow up on the Open File dialog or on using Custom Fonts

video14 video15

Other Resources

 

In addition to the tutorials and the videos, there are a host of other resources on Silverlight.net, including Quickstarts, Hands on Labs, White papers and a good bit more.

One on't cross beams gone owt askew on treadle.

There are two external resources to consider: Books, Conferences and Podcasts.

Sorry, there are three external resources: Books, Conferences, Podcasts and Magazines.

There are four – no – AMONGST  the resources are : Books, Conferences, Podcasts, Magazines and other web sites

I'll come in again.

No One Expects the VAST array of Additional Resources including  Books, Conferences, Podcasts, Magazines, other web sites, RSS and pretty red uniforms.

Books

There are a number of excellent books in the beginner to intermediate range for Silverlight 2. I even know of one that the authors worked so long and hard to make right that it has turned into a Silverlight 3 book!   I highly recommend spending some time wandering the aisle of your favorite (virtual?) bookstore as a good book on Silverlight and make the process of learning much easier and can organize the material into a coherent storyline for you.

Conferences

There are a number of excellent conferences, world wide, at which Silverlight is a major topic, with presentations both by Microsoft geeks and others. This can be a great way to learn about the technology and to meet others who are working with Silverlight and gain those critical insights simply not available otherwise.

Podcasts

There is a burgeoning, might I say plethora of podcasts that are dedicated to or at least occasionally touch upon Silverlight. In addition to searching through your favorite podcast finder, you might want to take a look at my guest podcasts as a starting point.

Other Media

There certainly are articles in various magazines and on other web sites, but if we're doing our job right, Silverlight.net should be your first stop. That said, I heartily recommend subscribing to SilverlightCream – a clipping service of the best of each day's blog entries relating to Silverlight.

I hope this helps you get started; I realize that I've just scratched the surface.

Posted by jesseliberty | 7 comment(s)
Filed under:

Silverlight Related Skills – Post 2

Yesterday, I wrote the first of a series of posts on skills related to effective Silverlight programming; specifically an introduction to LINQ. Today, with that material fresh, I'd like to take a giant leap forward and consider a number of advanced techniques that are common with Linq programmers.

To do this, I've extended and modified the Presidents class we were using, and created a second related class PresidentDetail.  For convenience, I've placed both in the file President.cs,

using System;
using System.Collections.Generic;

namespace LinqDemo2
{
  public class President
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public static List<President> GetPresidents()
    {
      List<President> presidents = new List<President>
          {
            new President() { FirstName = "George", LastName = "Washington" },
            new President() { FirstName = "John",   LastName = "Adams" },
            new President() { FirstName = "Thomas", LastName = "Jefferson" },
            new President() { FirstName = "James",  LastName = "Madison" },
            new President() { FirstName = "James",  LastName = "Monroe" }
          };
      return presidents;
    }
  }



  public class PresidentDetail
  {
    public string Name { get; set; }
    public string Portrait { get; set; }
    public string Birth { get; set; }
    public string Death { get; set; }
    public string PortraitPrefix = 
"http://www.whitehouse.gov/history/presidents/images/"; public static List<PresidentDetail> GetPresidentialDetail() { List<PresidentDetail> details = new List<PresidentDetail> { new PresidentDetail() { Name = "George Washington", Birth = "Feb 22, 1732", Death = "Dec 14, 1799", Portrait = "georgewashington.jpg"}, new PresidentDetail() { Name = "John Adams", Birth = "Oct. 30, 1735", Death = "July 4, 1826", Portrait = "johnadams.jpg"}, new PresidentDetail() { Name = "Thomas Jefferson", Birth = "April 13 1743", Death = "July 4, 1826", Portrait = "thomasjefferson.jpg"}, new PresidentDetail() { Name = "James Madison", Birth = "March 15, 1761", Death = "June 28, 1836", Portrait = "jamesmadison.jpg"}, new PresidentDetail() { Name = "James Monroe", Birth = "April 28, 1758", Death = "July 4, 1831", Portrait = "jamesmonroe.jpg"} }; return details; } } }

As you can see, each class comes with a static method that returns a typed List populated with instances of the class.

 

Page.xaml once again contains a TextBlock to display the LinqStatement and a DataGrid to display the data from the selection. Our selection, however, will consist of data from the two tables, based on a join. We'll join the two tables based on the combined FirstName and LastName in the President table matching the Name in the PresidentDetail table. This done, it is time to return the results, but that raises a problem, only one object can be returned.

You could write

select President

or you could write

select detail

but you are not allowed to write

Select President, Detail

One solution is to create a temporary new class that combines the two,

 

 

 

public class PresidentAndDetails
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string Name { get; set; }
  public string Birth { get; set; }
  public string Death { get; set; }
  public string Portrait { get; set; }
}

You can then write your LinqStatement as follows:

PresidentAndDetails result =
  from president in presidents
    join detail in details on
      string.Format("{0} {1}", president.FirstName, 
        president.LastName)
      equals detail.Name
      orderby president.LastName, detail.Birth
      select new PresidentAndDetails { President = president, Detail = detail };

The syntax is somewhat similar to yesterday's example except that we've added a join (based on the concatenation of first and last name from one object with the Name from the second) and, in the projection we're actually instantiating a new object (of type PresidentAndDetails, and initializing the members.

Anonymous Types

It gives me the heebie jeebies to create a class, PresidentAndDetails just to have a return type for a single query and fortunately, C# 3 offers the alternative of anonymous types, which allow you to eschew creating the class before you need it, and allow you to avoid naming the class, but instead to create an implicit class whose type will be inferred by its use. Thus, the Linq statement above can be rewritten as follows (and the class PresidentAndDetails can be tossed):

var result =
  from president in presidents
    join detail in details on
     string.Format("{0} {1}", president.FirstName,
       president.LastName)
     equals detail.Name
     orderby president.LastName, detail.Birth
     select new { President = president, Detail = detail }

 

The significant change is that we've gotten rid of the definition of the class, and the select statement does not name the class; it is inferred by the naming of the members. Notice also that the return value is now assigned to a variable whose type is identified as var; the actual type is assigned when the result is generated, but it is still strongly typed.

Lambda Expressions

All of this is good, but not terribly radical, but when we add lambda expressions, C# as we know it suddenly takes a dramatic change. There is much to say about lambda expressions but the essential thing to know is this: delegates are a way to indirectly refer to methods. Lambda expressions are a way to write short in-line substitutions for the methods that delegates refer to.

Lambda expressions consist of three parts:

  • The Parameters
  • The Operator ( => )
  • The Result

Putting this together you might have a delegate that looks like this,

int someDelegate(int, int);

and you might use that delegate to refer to, for example, this method:

int myMethod(int a, int b)
{
  return a * b;
}

 

You could also use that delegate to refer to this lambda expression:

(a,b) => a*b

You read this aloud "a and b goto a times b" or, to be more explicit, "given the parameters a and b, you will get back the product of a and b"

If you have more than one parameter, you must enclose them in parentheses and separate them by commas, and the types of the parameters and the return type must match the delegate that supports the lambda expression.

Lambda expressions are incredibly powerful and useful in many situations, not the least of which is LINQ. They can greatly simplify our Linq statement. For example, our most recent query now becomes,

var result  = presidents.Join( details,
president => string.Format( "{0} {1}", 
  president.FirstName, president.LastName ),
detail => detail.Name,
( president, detail ) => new { 
  President = president, Detail = detail } )
.OrderBy( ca => ca.Detail.Name )
.ThenBy( ca => ca.Detail.Birth );

At first glance, this hardly looks like C# at all, but if we take it apart, it becomes much more reasonable.

var result indicates that the type of result will be decided at run time based on the strongly typed value inferred by running the query.

presidents.Join(details,    the beginning of a join between the presidents object and the details object

president => string.Format( "{0} {1}, president.FirstName, president.LastName },

a lambda expression indicating that given a president object, you'll get back the string consisting of the first name and the last name

detail => detail.Name  similarly, a lambda expression indicating that given the detail object, you'll get back the Name property of that object

put that together and your join says to join the president object and the detail object on the concatenated string and the Name property. If that matches, you now have the president object and the detail object and it is time to create your new anonymous type, which is what the next lambda expression does,

(president, detail) => new { President = president, Detail = detail }

read this as "given a president and a detail instance get back a new anonymous object initialized with two members. The president member is initialized to the value of the president object you have, the Detail member is initialized to the value of the detail object you have.

.OrderBy ( ca => ca.Detail.Name )  This order by phrase lends the anonymous object the temporary name ca (you can use any name). Within that temp object are two member objects, President and Detail and here you set Detail.Name as the property to order by.

Intellisense recognizes the anonymous type and its internal members,

IntellisenseAndAnonType2

of course, there's nothing magical about the name "ca"….

IntellisenseAndAnonType3

Setting the Item Source

 

 

 

I'll have to follow up, but for now it seems the ItemSource for the DataGrid is much happier with a non-anonymous type, thus I've modified the search and created a temporary class to hold the fields that I want to display,

public class PresResult
{
  public string Name { get; set; }
  public string Birth { get; set; }
  public string Death { get; set; }
}

 

The search and assignment to ItemsSource looks like this,

 var result  = presidents.Join( details,
 president => string.Format( "{0} {1}", 
   president.FirstName, president.LastName ),
 detail => detail.Name,
 ( president, detail ) => new PresResult()
 {
   Name = detail.Name,
   Birth = detail.Birth,
   Death = detail.Death
 } )
 .OrderBy( ca => ca.Name )
 .ThenBy( ca => ca.Birth );

 Results.ItemsSource = result;

and the DataGrid in Page.xaml looks like this,

<Data:DataGrid  x:Name="Results" 
  AutoGenerateColumns="False"
  Margin="5,0,0,0" 
  Width="400"
  Height="Auto"
  Grid.Row="2" 
  FontFamily="Georgia" 
  FontSize="12"
  Background="#FF8BB8F8" 
  AlternatingRowBackground="#009DF2ED">
  <Data:DataGrid.Columns>
    <Data:DataGridTextColumn FontSize="12" Width="120"  Header="Name" Binding="{Binding Name }" />
    <Data:DataGridTextColumn FontSize="12" Width="120" Header="Birth" Binding="{Binding Birth }" />
    <Data:DataGridTextColumn FontSize="12" Width="120" Header="Death" Binding="{Binding Death }" />
  </Data:DataGrid.Columns>
</Data:DataGrid>

When run, the application looks like this (red circle added)

RunningLinqApp

It is pretty amazing that 3 of the first five presidents died on July 4, two within hours of one another.

For more on Linq I highly recommend Professional Linq by Joseph Rattz and of course the Linq Project.

Silverlight Related Skills – Post 1

Recently I wrote that there are a number of readers who are running into difficulty with skills needed for intermediate or advanced Silverlight development that are not strictly part of Silverlight.  This is the first of an occasional series that will demonstrate these skills and techniques. 

The usual caveats apply: nothing substitutes for a good book. 

Syllogism
Major Premise: Nothing is better than a good book. 
Minor Premise: This column is better than nothing.
Conclusion: This column is better than a good book (?)

The two technologies that I've been running into the most as a Silverlight programmer that seem to be causing the most confusion are REST and Linq. The former is a software architecture that is loosely (some would say incorrectly) used to describe a simple web service interface over HTTP that does not have a messaging layer such as SOAP. I'll be returning to this, but if you can't wait, do check out John Papa's blog entry which is short but very good (his book is great).

Linq is a more natural place for me to start as it is part of C# 3 (and I have two books on the topic <shameless plug> Learning C# 3 (this month) and Programming C# 3 and of course there are a number of excellent other books on the market that cover this material as well.

Preface

The key things to know about Linq are that it

  • is a part of the C# language, not an add-on
  • is fully object oriented and type safe
  • allows you to query any type of collection (not just databases)
  • is pronounced like a link in a chain, and stands for Language INtegrated Query.

In addition to the fundamentals of Linq, the .NET 3.5 framework has added four important class libraries:

If you want in-depth coverage of Linq, you'll want to bookmark the LINQ project page.

Linq To Objects is the general term for using LINQ to query collections that support IEnumerable or IEnumerable<T>. We'll start there.

Linq To SQL is a framework that supports using LINQ to query databases and we'll look at a subset of this for querying SQLServer in coming blog entries.

The Linq To XML Class Hierarchy supports using LINQ to interact with XML elements and allows you to expand your data interaction well beyond traditional relational databases.

Linq To Entities supports using LINQ to query the Entity Data Model produced by the ADO.NET Entity Framework. I don't expect to cover this for quite a while, but we will return to it.

Linq 101

To get started, we'll create a very simple class that we can place in collections and then search for instances of,

 public class President
 {
   public string FirstName { get; set; } 
   public string LastName { get; set; } 
   public string TookOffice { get; set; }
 }

We begin the program by creating a collection of the first five presidents, using C# 3's new object initialization (we could just as easily have given the class a constructor).

private List<President> presidents = new List<President>
{
   new President() { FirstName = "George", 
        LastName = "Washington", TookOffice="1789"},
   new President() { FirstName = "John",   
        LastName = "Adams",      TookOffice="1797"},
   new President() { FirstName = "Thomas", 
        LastName = "Jefferson",  TookOffice="1801"},
   new President() { FirstName = "James",  
        LastName = "Madison",    TookOffice="1809"},
   new President() { FirstName = "James",  
        LastName = "Monroe",     TookOffice="1817"}
};

The next step is to create the Linq statement, which consists of the from clause, the filtering clause and the projection.

The From Clause

The From Clause specifies the data source (in this case the presidents collection) and the range variable. A range variable is very much like an iteration variable in a foreach loop, iterating over the data source. The common idiom would be to write

from president in presidents

and the type of president would be inferred from the fact that it will be ranging over the collection presidents which is of type List<Presidents> and thus will be assigned the type President.

Note

If you find this use of the singular, plural and type name confusing, you of course are free to change the collection definition to

private List<President> chiefExecutives = new <President>
{ //...}

from thisLeader in chiefExecutives 

Some would find this less confusing, some more. The compiler doesn't much care either way.

The Filtering Clause

The filtering clause is the where clause and is nearly identical to the where clause in Sql and is entirely optional.  The filter is a Boolean expression, but need not be a simple equality statement, you are free to set up as complex a statement as you choose. We'll look for presidents whose first name begins with the letter J and so our filter looks like this:

where president.FirstName.StartsWith( "J" )

The Projection

Database geeks call the select statement the "projection" and it is here that you designate which properties you want to select and which objects you want to select them from. In our case, we'll select the entire president object, and so we don't need to designate specific properties.

select president.

Our complete Linq statement is thus

from president in presidents
where president.FirstName.StartsWith( "J" )
select president

This returns an IEnumerable<President> and thus serves well as the ItemSource for our datagrid. 

The entire source for Page.xaml.cs follows,

using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Linq;

namespace LinqDemo1
{
  public partial class Page : UserControl
  {
    private List<President> presidents = new List<President> { 
      new President() { FirstName = "George", 
        LastName = "Washington", TookOffice = "1789" }, 
      new President() { FirstName = "John", 
        LastName = "Adams", TookOffice = "1797" }, 
      new President() { FirstName = "Thomas", 
        LastName = "Jefferson", TookOffice = "1801" }, 
      new President() { FirstName = "James", 
        LastName = "Madison", TookOffice = "1809" }, 
      new President() { FirstName = "James", 
        LastName = "Monroe", TookOffice = "1817" } }; 
    
    public Page() 
    { 
      InitializeComponent(); 
      Loaded += new RoutedEventHandler( Page_Loaded ); 
    }

    void Page_Loaded( object sender, RoutedEventArgs e )
    {
      LinqStatement.Text = @"from president in presidents            
        where president.FirstName.StartsWith( ""J"" )            
        select president"; 
      
      Results.ItemsSource = from president in presidents 
                            where president.FirstName.StartsWith( "J" ) 
                            select president;
    }
  }
}

The display is equally simple, offering a text box to show the Linq statement and a DataGrid for the results,

LinqDemo1

Speaking at Code Mash 2009

CodeMashVery proud to say that I will have two presentations at CodeMash 2009

  • Skinning Silverlight Controls In Silverlight (which I will extend, time allowing to Creating Skinnable Custom Controls and dive into Dependency Properties and the Parts and States Model and more) and
  • Dynamic Hyper Video in Silverlight (which I believe is one of the more exciting and under-utilized aspects of Silverlight 2)

You can read about all the sessions here (announced yesterday) and follow CodeMash on Twitter  if you like (my Twitter is here) and generally immerse yourself in all things CodeMash from this link

Topics this year include Ruby, SwingBuilder, Robotics, Virtual Earth, Linq, Extension Methods, Java, JRuby, Functional Concepts, UI/WPF, iPhone SDK, Drupal, Grails, Silverlight Best Practices, DDD, Scala, Web Apps with .NET, Multithreaded F#, Grease, Agile, Spatial, CI, Photosynth, Deep Zoom Mesh, Surface, Griffon, Threads, Rails, UX, SOA, WCF, MVC, Scriptaculous, JVM, Thrashing, IronRuby, JVM, Flex, Python, Cloud Computing with .NET, Python and much more. 

People include a lot of folks I've wanted to meet for a long time, include some I've talked with or written to for years (uncle Bob will be there!)

All that and the largest indoor water park in Ohio. What more could you want?

Please Don’t Read This Blog Post

Scott Hanselman, who has the most popular blog in the world, will tell you that if you want to have a popular blog you must never make promises – you’re certain to disappoint. I’m sure he’s right, as I’ve done so in the past (made promises and disappointed), so please don’t read this blog post because I’m about to tell you what videos and tutorials I expect to create in the next few months and we all know that the unexpected will change my plans.

Isn’t that a bit of a tautology? If it were expected, then I’d be a fool not to have planned for it. So clearly it must be the unexpected that changes my plans, even if it is just that I turn out to be unexpectedly stupid and lazy. Can  you imagine if the expected came up and changed my plans? How would I explain that?  “Sorry this is late, but just what I thought would happen did, and so I couldn’t get to it. Hate when that happens.”

The reason I have to ignore (or at least bend) Scott’s guidance is this: I’m finding there are quite a few developers who want to learn about (for example) data binding and other Silverlight topics but who would like a bit of help with some of the more advanced and/or newer topics in C# (/VB), including:

  •  Anonymous Types
  • Linq
    • Linq to SQL
    • Linq to Entities
    • Linq to Objects
    • Linq to XML
  • The var keyword
  • Lamda Expressions
  • Extension Methods
  • Web Services
    • ASMX
    • WCF
    • JSON
    • REST
  • Object Initialization
  • Exchanging data to and from ASPX pages

This Ain’t No Bell Curve

All my unscientific surveys indicate that the folks who are reading this blog and coming to this site do spread out along the spectrum of experience, but there is a noticeable clustering at two distinct points: “getting started” and “getting serious” 

I will readily admit that we (I?) have been a lot better at talking about meeting the needs of the latter group than actually doing so. 

The Next Three to Six Months (or less) (or more)

Clearly we need more videos and tutorials on…

  • Templates and Custom controls including exploration of Dependency Properties and the Parts and States Model
  • The Data Grid (and Data Binding) including interacting with Data Sources, Data Services, and the artist formerly known as Astoria
  • The Graph Controls
  • Interacting with ASP.net Pages
  • Streaming
  • Isolated Storage 

Because these topics will use many of the techniques and skills listed above (Linq, web services, etc.) I plan to create mini-tutorials in my blog covering this background material with 27  8 by 10 color glossy pictures with circles and arrows and a paragraph on the back of each one explaining what each one is, to be used as evidence against us.  

VB As A First Class Citizen

As part of my vow never to say anything political on this blog again, I will not even go near the VB/;C# debate discussion except to say that my VB book tanked, but I do like the language and I’m committed to making sure all my tutorials are in both VB and C# and that my videos have their code available in both languages. That said, the blog is in C# because, to be totally honest, it is the language that I’m more fluent in. (unlike English as you can see). (Kurt Vonnegut said “English is my second language; unfortunately I don’t have a first language.”)

I apologize to those of you who prefer VB and I’ll do what I can to make myself equally obscure in both languages.

Schedule

The developers at Silverlight Geek™ have not yet announced a release schedule, but I understand they are working hard and expect to have something for you “Real Soon Now.”  We’ll be on a RSN schedule for the foreseeable future, what has the incredible advantage of always being on time. (Have you noticed that the airlines are now 97% on time through the expedient of increasing the estimated time by 25%?)

Thank you for not reading this and I look forward to actually having something to say very soon.

-jesse

Obtaining and Caching a Lot of Words

list of A words

It is surprising how often I wish I had a lot of words handy.  This week it has been because I've wanted to play with the AutoCompleteBox (you just set the list of words as the ItemSource for the control and voila!

In previous posts I demonstrated how I obtained these from a book through Project Gutenberg and how I used a background worker thread to keep the UI up to date. Today I'll show how to use Isolated Storage to stash the words locally to dramatically improve performance, and then after I show this nifty trick at DevConnections I'll write up how to obtain the words on one page and then use them in a AutoCompleteBox on a second page (ok, it's not that hard).

Isolated Storage

Isolated storage really works well here, because once you go to the bother of getting and sorting these words, it is rather silly to go get them again the next time you run the program. The trick, of course is just to check to see if you've already saved them in Iso Storage and then if so, just reconstitute them. If not, then when you are done using them, stash them away in isolated storage for next time.

You can get all sorts of fancy saving away complex data structures and saving different lists, but to keep things simple, let's just… well, keep things simple.

When we're about to get ask the user what file to open to grab words from, we'll do a quick "look aside" to see if we already have words saved,

void Page_Loaded( object sender, RoutedEventArgs e )
{
  worker.WorkerReportsProgress = true;
  worker.DoWork += new DoWorkEventHandler( worker_DoWork );
  worker.ProgressChanged += new ProgressChangedEventHandler( worker_ProgressChanged );
  worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler( worker_RunWorkerCompleted );
  if ( TestIsoStorage() )
  {
    FilePicker.IsEnabled = false;
    if ( worker.IsBusy != true )
      worker.RunWorkerAsync( null );
  }
  else
  {
    FilePicker.Click += new RoutedEventHandler( FilePicker_Click );
  }
}

 

This takes a bit of explanation. I'm still setting up my worker thread, because i'm going to use it whether or not i Have the words. It will be the worker thread that take s the single string of words and rebuilds my list of strings that the application expects. And why not? That part is already working?  The only change I wanted to make was either to get the file and parse it or not.

Let's look at TestIsoStorage(),

The logic here is that I call GetUserStoreForApplication which returns an IsolatedStorageFile at the application level (and since this is a resource I want to make sure is given up as quickly as possible I take advantage of C#'s using construct) . With that, I can test if my isolated storage file exists and if it does, I open a StreamReader and in one line I open the file for reading and suck the entire contents out as a single string, which  I place into a string builder.

NB: I'm of two minds about my ambivalence about having a single return point. One argument is that it is less confusing if you use a flag (retVal) and always exit at the end, the other responds with a word I'm not allowed to write here. Most of the time I would rewrite this as

private bool TestIsoStorage()
{
  bool retVal = false;
  using ( var store = IsolatedStorageFile.GetUserStoreForApplication() )
  {
    if ( store.FileExists( "SortedWords" ) )
    {
      using ( StreamReader reader =
        new StreamReader( store.OpenFile( "SortedWords", FileMode.Open ) ) )
      {
        sb = new StringBuilder();
        sb.Append( reader.ReadToEnd() );
        retVal = true;
      }
    }
    return retVal;
  }
}

but I don't get too excited about it.

The key to note (and I admit it is almost a hack) is that if we get the words from the file, we never call the dialog box (in fact we disable the open file button) and kick off the background thread with a null file

if ( TestIsoStorage() )
{   
  FilePicker.IsEnabled = false;   
  if ( worker.IsBusy != true )      
    worker.RunWorkerAsync( null );
}

 

The first half of DoWork is encased in a big if statement that basically turns it into a noop if we have obtained the words from isolated storage.  I kinda' hate this because the connection is not obvious, but it works, its late and I swear I'll come back and fix it… really.

void worker_DoWork( object sender, DoWorkEventArgs e )
{
  const long MAXBYTES = 200000;
  BackgroundWorker workerRef = sender as BackgroundWorker;
  if ( workerRef != null )
  {    // begin massive ugly hack      
    if ( e.Argument != null )
    {
      System.IO.FileInfo file = e.Argument as System.IO.FileInfo;
      if ( file != null )
      {
        System.IO.Stream fileStream = file.OpenRead();
        using ( System.IO.StreamReader reader = new System.IO.StreamReader( fileStream 
        {
          string temp = string.Empty;
          try
          {
            do
            {
              temp = reader.ReadLine();
              sb.Append( temp );
            } while ( temp != null && sb.Length < MAXBYTES );
          }
          catch { }
        }     // end using             
        fileStream.Close();
      }        // end if file != null      
    }           // end if argument is null       
    string pattern = "\\b";
    allWords = System.Text.RegularExpressions.Regex.Split( sb.ToString(), pattern );
    long total = allWords.Length / 100;
    long soFar = 0;
    int newPctg = 0;
    int pctg = 0;
    foreach ( string word in allWords )
    {
      newPctg = (int) ( ( ++soFar ) / total );
      if ( newPctg != pctg )
      {
        pctg = newPctg;
        workerRef.ReportProgress( pctg );
      }
      if ( words.Contains( word ) == false )
      {
        if ( word.Length > 0 && !IsJunk( word ) )
        {
          words.Add( word );
        }     
      }       
    }        
  }                      
}

 

Finally, when the thread ends we make sure to go save the words for next tmie if we've not done so yet,

private void StoreWords()
{   
  Message.Text = "Storing Words in Isolated Storage...";    
  using ( var store = IsolatedStorageFile.GetUserStoreForApplication() )   
  {      
    if ( ! store.FileExists( "SortedWords" ) )      
    {         
      StringBuilder sb = new StringBuilder();         
      foreach ( string s in words )         
      {            
        sb.Append( s + " " );         
      }         
      using ( StreamWriter writer = 
        new StreamWriter( store.OpenFile( "SortedWords", FileMode.Create ) ) )         
        { 
          writer.Write( sb.ToString() ); 
        }
    }
  }
}

 

The result, not surprisingly is a much faster start up to the program.    I do worry just a bit about the detritus of long forgotten isolated storage files cluttering up the disk. I wonder if we can put in a self-destruct timer?  I'll have to look into that.

 

-j

Using Blend with Visual Studio

For the past six months or so I've been experimenting with and then "preaching" the idea of developing with two tools: Expression Blend for the UI and Visual Studio for the code.  This has paid big dividends for me in productivity and it fits in well with the Parts and States Model and with keeping a clear separation of visuals form logic.

PartsAndStates

I'm about to give a pair of presentation at DevConnections (hence my total lack of blogging!) and I thought it would be fun to see if I just perceive  an increase in productivity or I can actually measure that increase.  Thus, I decided to put together a simple form, using a number of controls placed in a grid, including a control from the new Silverlight Toolkit (specifically the AutoCompleteBox) and measure the time it took to create the form from scratch in Visual Studio and in Blend.

Here is what the final product looks like:

FormForBlendCompare

 

I created an entirely non-scientific test in which I filmed and timed myself creating this form first in Blend and then again in Xaml using Visual Studio.

My not very accurate timing is that it took about 40 minutes in Blend and 30 in Xaml, but I think if you eliminate the overhead of filming, it is more like 30 in Xaml and 15 in Blend. Note also that I have about 15 years experience with Visual Studio and about 6 months with Blend

In any case, this may well be a case of "Draw Curve, Plot Points," but it was interesting and fun to do and seemed to validate my impression that Blend is not only easier, it is faster. I'm off to DevConnections and will try to post from there, but will resume what I hope will be much more frequent (and entirely technical!) blogging once I get back, along with the long promised series of deeply technical videos and tutorials.

2009 will be a very interesting year.

Thanks. -j

AutoCompleteBox control / Worker Threads

I recently began a discussion of the Silverlight Toolkit and on the way towards explaining the AutoCompleteBox I became distracted by creating a list of words to use as our datasource.

I've actually reworked that example, slightly to build the list using a worker thread (to explore threading and to improve the UI) but I have broken through and actually managed to get to the point, which is adding an AutoCompleteBox to the page, and while I was at it, I included (per one of the examples provided) a slider to set the minimum number of characters you must put in before the box begins to show you matches

autocomplete3

What we are seeing here, is the user is typing in letters and the autoCompleteBox is offering choices from our data source (the words retrieved from Swan's Way) that match what has been typed so far.  The slider lets us set how many letters must be typed before choices are offered. Increasing the minimum prefix length cuts down on the clutter but offers less help (though it can vastly improve performance if the data is not local).

(Complete source code here)

Coding With the Auto Complete Box

Let's start with coding the auto-complete box and then circle back to the changes I made to gathering the data

The first step is to add the Toolkit library to your references

AddingRefToAutoFill 
(image cropped)

With that we can add the namespace to the top of Page. xaml (the last name space in the UserControl)

<UserControl x:Class="AutoFill2.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="800" Height="601" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" 
    xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls">

Finally, we'll add a grid within our outer grid to place our new controls. We want to add a prompt, an auto complete box, a second prompt for the prefix-length, the current value of the prefix length, two text boxes to indicate the range, and the slider itself,

SearchBoxAnnotated

Here's the Xaml,

<Grid x:Name="InnerGrid" Height="Auto" Margin="0,0,0,0"  
      VerticalAlignment="Stretch" Grid.Column="1" Grid.Row="1">
    <Grid.RowDefinitions>
        <RowDefinition Height="0.385*"/>
        <RowDefinition Height="41"/>
        <RowDefinition Height="11"/>
        <RowDefinition Height="15"/>
        <RowDefinition Height="25"/>
        <RowDefinition Height="59"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.442*"/>
        <ColumnDefinition Width="0.558*"/>
    </Grid.ColumnDefinitions>
    
    <TextBlock x:Name="searchPrompt" Text="Search for: "
        HorizontalAlignment="Right" 
        Margin="0,12,5,0"  Grid.Row="1"
        FontFamily="Verdana" FontSize="24"  TextWrapping="Wrap"  />
    
    <controls:AutoCompleteBox x:Name="myAutoComplete"
        Margin="5,0,0,0" Grid.Column="1" Grid.RowSpan="1" Grid.Row="1" 
        HorizontalAlignment="Left" Height="30" Width="195" 
        FontFamily="Verdana" FontSize="14" />
 
    <TextBlock x:Name="minPrefix" Text="Minimum Prefix Length:"
        Padding="5" FontFamily="Verdana"  
        Margin="0,0,0,0" Grid.Row="3"  
        HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
    
    <TextBlock x:Name="negOne"  
        HorizontalAlignment="Left"  VerticalAlignment="Bottom" 
        Grid.Column="0" Grid.Row="4" FontFamily="Verdana" Text="-1" 
        Margin="5,0,0,0"/>
    
    <TextBlock x:Name="eight" 
        Margin="0,0,5,0" 
        HorizontalAlignment="Right" VerticalAlignment="Bottom" 
        Grid.Column="0" Grid.Row="4" 
        FontFamily="Verdana" Text="8" />
    
    <TextBlock x:Name="CurrentValue" Text="2"
               HorizontalAlignment="Right" VerticalAlignment="Bottom"
               Margin="5,0,0,0" Width="20" 
               Grid.Column="0" Grid.Row="3"  
               TextWrapping="Wrap"  FontFamily="Verdana"  
               Foreground="#FFF6300B" FontSize="14"/>            
    
    <Slider x:Name="SetPrefixLength" Minimum="-1" Value="2" Maximum="8" 
            SmallChange="1" LargeChange="2" Width="160" 
            Grid.Row="4" Grid.Column="0" Margin="5,0,5,0" />
    
    <Border Height="Auto" x:Name="Boundary" 
            HorizontalAlignment="Stretch"  VerticalAlignment="Stretch" 
            Width="Auto" Margin="0,0,5,0"
            Grid.Row="1"  Grid.RowSpan="4" Grid.ColumnSpan="2" 
            Canvas.ZIndex="-1" Background="#FF73B8F2"/>
</Grid>

The Xaml declares an inner grid (making it easier to divide up the space for our search box). You can tell by the funky values that this grid was created in Blend,

BlendInnerGridForAutoComplete2

A couple quick things to notice here… the trick to placing objects inside the inner grid is to make it the current container. You do that by double clicking on it. It will be surrounded by a yellow rectangle both in the Objects and Timeline window and in the art board. This gives the inner grid the same ability to draw rows and columns from the margins that you had with the outer grid.

Note also that we use a border control, this time not to draw a border around the other controls, but to provide a background color.

<Border Height="Auto" x:Name="Border" 
        HorizontalAlignment="Stretch"  VerticalAlignment="Stretch" 
        Width="Auto" Margin="0,0,5,0"
        Grid.Row="1"  Grid.RowSpan="4" Grid.ColumnSpan="2" 
        Canvas.ZIndex="-1" Background="#FF73B8F2"/>

The zIndex="-1" ensures that the border will be behind all the other controls.

The AutoCompleteBox

Don't let the AutoCompleteBox get lost in all this discussion of set up (Remember the AutoCompleteBox? This is a posting about the AutoCompleteBox [ with apologies to Arlo Guthrie] )…

<controls:AutoCompleteBox x:Name="myAutoComplete"
    Margin="5,0,0,0" Grid.Column="1" Grid.RowSpan="1" Grid.Row="1" 
    HorizontalAlignment="Left" Height="30" Width="195" 
    FontFamily="Verdana" FontSize="14" />

The Supporting Code for the AutoComplete box is in the Page.xaml.cs file. There are two steps here for supporting our control.

  • After the WorkerThread runs to gather the data (covered below) we need to set that data as the ItemSource for the AutoCompleteBox
myAutoComplete.ItemsSource = SortedWords;

That is really the key and essence of setting up the AutoCompleteBox. However, we also want to remember to set up the event handler for the slider,

SetPrefixLength.ValueChanged += 
   new RoutedPropertyChangedEventHandler<double>( SetPrefixLength_ValueChanged );
  • When the user moves the slider, we will set the appropriate property on the AutoCompleteBox,
void SetPrefixLength_ValueChanged( 
   object sender, RoutedPropertyChangedEventArgs<double> e )
{
   myAutoComplete.MinimumPrefixLength = 
      (int) Math.Floor(SetPrefixLength.Value);
   CurrentValue.Text = 
      myAutoComplete.MinimumPrefixLength.ToString();
}

To set the value in the AutoCompleteBox (an integer) we must cast the double we retrieve from the the slider – making sure to truncate, not round.  One way to do so is to use the Math.Floor function, which returns the highest integer value, as a double,  that is less than the value of the argument. Eh?  An example helps: if SetPrefixLength.value is equal to 7.3, 7.9 or 8.1 the value returned will be 7.0, 7.0 or 8.0 respectively.  

We then cast that Floored double to an integer and assign it to the MinimumPrefixLength property of the AutoCompleteBox. The possible values in this example are –1 through 8.  Note that a value of –1 turns off Autocompletion.  Interestingly, the values of 0 and 1 have the same effect.

Getting The Data – In the Background

As promised, I reworked the code to obtain the data so that it is a bit more factored, and more important, the bulk of the work is done in a background tread making for a more rewarding UI.  I won't review what I covered in the previous article, but I will show the changes.

The key is to initialize a private member variable of type BackgroundWorker, and to set the property WorkerReportsProgress in the constructor. You'll also need event handlers for:

  • Thread starts (DoWork)
  • Thread has progress to report (ProgressChanged)
  • Thread completes (RunWorkerCompleted)

(NB: you can also choose to handle cancellation)

private BackgroundWorker worker = new BackgroundWorker();
//...
 
public Page()
{
   InitializeComponent();
   worker.WorkerReportsProgress = true;
   worker.DoWork += new DoWorkEventHandler( worker_DoWork );
   worker.ProgressChanged += 
       new ProgressChangedEventHandler( worker_ProgressChanged );
   worker.RunWorkerCompleted += 
       new RunWorkerCompletedEventHandler( worker_RunWorkerCompleted );
//...
}

We're going to kick off the thread when we determine that the user has chosen a file to open.  We'll make sure the thread isn't already running and then call RunWorkerAsync (which will fire teh DoWork event) and we'll pass in the FileInfo object for the thread's edification.

void DataButton_Click( object sender, RoutedEventArgs e )
{
    OpenFileDialog openFileDialog1 = new OpenFileDialog();
   openFileDialog1.Filter = "Text Files (.txt)|*.txt|All Files (*.*)|*.*";
   openFileDialog1.FilterIndex = 1;
   openFileDialog1.Multiselect = false;
   bool? userClickedOK = openFileDialog1.ShowDialog();
   
   if ( userClickedOK == true )
   {
      // *** NEW ***
      if ( worker.IsBusy != true )
         worker.RunWorkerAsync(openFileDialog1.File);
   }
}

This method is identical to the button click handler in the previous article, except that once the user identifies the file, we hand the fileInfo object to the worker thread and our job is done!  We can now go eat lunch.

The Dowork method is called through the event delegate, passing in the sender (which you can safely cast to the BackgroundWorker) and a DoWorkEventArgs which contains, among other things, an argument property which in this case contains the FileInfo we passed in when we started the thread

   1: void worker_DoWork( object sender, DoWorkEventArgs e )
   2: {
   3:    const long MAXBYTES = 200000;
   4:    BackgroundWorker workerRef = sender as BackgroundWorker;
   5:  
   6:    if ( workerRef != null )
   7:    {
   8:       System.IO.FileInfo file = e.Argument as System.IO.FileInfo;
   9:  
  10:       if ( file != null )
  11:       {
  12:          System.IO.Stream fileStream = file.OpenRead();
  13:          using ( System.IO.StreamReader reader = 
  14:                new System.IO.StreamReader( fileStream ) )
  15:          {
  16:             string temp = string.Empty;
  17:             try
  18:             {
  19:                do
  20:                {
  21:                   temp = reader.ReadLine();
  22:                   sb.Append( temp );
  23:                } while ( temp != null  && sb.Length < MAXBYTES );
  24:             }  
  25:             catch {}
  26:          }     // end using 
  27:          fileStream.Close();
  28:          string pattern = "\\b";
  29:          string[] allWords = 
  30:          System.Text.RegularExpressions.Regex.Split(
  31:                                 sb.ToString(), pattern );
  32:  
  33:          long total = allWords.Length / 100;
  34:          long soFar = 0;
  35:          int newPctg = 0;
  36:          int pctg = 0;
  37:  
  38:          foreach ( string word in allWords )
  39:          {
  40:             newPctg = (int) ( (++soFar) / total );
  41:             if ( newPctg != pctg )
  42:             {
  43:                pctg = newPctg;
  44:                workerRef.ReportProgress( pctg );
  45:             }
  46:  
  47:             if ( words.Contains( word ) == false )
  48:             {
  49:                if ( word.Length > 0 && !IsJunk( word ) )
  50:                {
  51:                   words.Add( word );
  52:                }     // end if not junk
  53:             }        // end if unique
  54:          }           // end for each word in all words
  55:       }              // end if file is not null
  56:    }                 // end if workerRef is not null
  57: }                    // end method DoWork

Background Thread Processing

The method begins by casting the sender argument to the BackgroundWorker thread and making sure that the cast was successful (and not null).  It then casts e.argument to be the FileInfo object (as described above) and again makes sure the cast is successful.

The next 20 lines are right out of the previous example, however starting on line 33 we begin to compute how far we've come in our work.

A true reading of our progress would take into account three stages:

  1. Reading the file
  2. Creating the collection of unique words
  3. Sorting the words

Since the first is very fast, and the third is instantaneous, and since this blog entry has gone on long enough, we'll constrain ourselves to reporting on progress on the second. We know how many words we have, and we know how many words we've processed as we iterate through the foreach loop so it is a simple matter to see when we've increased by a percentage. Each time we do, we call ReportProgress, passing in the new percentage figure.

foreach ( string word in allWords )
{
   newPctg = (int) ( (++soFar) / total );
   if ( newPctg != pctg )
   {
      pctg = newPctg;
      workerRef.ReportProgress( pctg );
   }

This fires an event that is caught in our UI thread,

void worker_ProgressChanged( object sender, ProgressChangedEventArgs e )
{
   Message.Text = ( e.ProgressPercentage.ToString() + "%" );
}

The fact that this is caught in the main (UI) thread while the work is happening in the background worker thread, means that the UI is free to update,

 

autocompleteThread

When the thread completes it automagically calls the RunWorkerCompleted method, giving you a chance to clean up and to do any other work that can only be done once the thread finishes (in our case, setting the ItemSource for the AutoCompleteBox)

void worker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
   Message.Text = words.Count + " unique words added. ";
   Display();
   myAutoComplete.ItemsSource = SortedWords;
}

 

More on the AutoCompleteBox when I cover DataGrids and more on the Silverlight Toolkit very soon.

Thanks.

(If you like this article, you may wan to consider RSS subscribing. And it makes my boss happy).

 

 

=============

Special note: the animated gifs are an experiment. I think they convey a lot of information but they also make the page a lot bigger. If that is burdensome, let me know in the comments  Thanks!

=============

Posted by jesseliberty | 15 comment(s)
Filed under: ,

My Favorite Utilities…

About twice a  year I try to publish a list of the utilities that I use all the time… not the ones I like in theory, but the ones that I use – that make programming or computing easier, faster, better.  Here's my list for the end of 2008.

Programming

Vista Exam Diff Pro - best I know for examining differences in files and directories
Vista Fiddler – debugger for http / IE
VistaAppleLogo FireBug – Required plug-in for anyone engaged in web-programming.
Vista

Instant C# / Instant VB (Full Dislosure: They give me free copies) I am blown away by how well this product works. Give it a C# program and seconds later you have a VB program – often requiring little or no additional adjustment. Amazing.

Vista Kaxaml – lightweight Xaml editor that is just amazingly useful
Vista Tortoise / Subversion  – the easiest and most useful source control for small groups of developers that I know of. Incredibly easy to set up and host the subversion for Windows database and ridiculously easy to keep files synchronized.
Vista XmlSpy  Edit XML and much more

Communication & Blogging

AppleLogo Adium - Messaging on the mac and mroe (AIM, MSN, Jabber, Yahoo, more)
Vista GoToMyPC - access your pc securely from anywhere.
Vista Live Writer - Best blog authoring software I know of
VistaAppleLogo

ooVoo - High quality video conferencing free or very inexpensive

VistaAppleLogo Skype - Skype has gotten so much better it is now absolutely indispensible – check out their amazing packages of skype-in, voice mail and more.
Vista SpeedFiler - Organizes Outlook better than anything else I've seen.
Vista Trillian – One of the best integrated IM program I know of for the PC

 

Social Networking & Bookmarking

Vista Plaxo – Beautiful interface with your address book
VistaAppleLogo Facebook – Seems to me to be the emerging favorite for pure social networking
VistaAppleLogo LinkedIn – Hands down favorite for business networking
VistaAppleLogo

Technoratti – One of the more definitive guides to blogs on the web

VistaAppleLogo Flikr – A different kind of networking, but indispensible
VistaAppleLogo Twitter – There is something about instant gratification and a limit of 140 characters that is just terribly compelling
VistaAppleLogo Del.ic.ious – The best bookmarking service that I know of

 

Utilities

AppleLogo 1Password - Roboform for the Mac. Tracks passwords, credit card info, personal info, fills in forms and works beautifully. 
Vista Cliptrak Pro -  Manages the clipboard better than anything I've ever seen, but that doesn't come close to all this does. Organizes the clipping for retrieval, and allows you to copy to clipboard and print (formatted) directly to printer. Amazing.
AppleLogo DevMode – pull dashboard widgets out onto your desktop!  Truly great.
Vista

 FinePrint - print pages 2-up and much more.

Vista Hypersnap DX - My screen capture of choice. There are many good choices, but this work horse has more options,  is more flexible and is easier to use than any I've seen. Truly great.
AppleLogo iStat Menus Everything going on inside your mac, on your menu all the time.
VistaAppleLogo iTunes- I use this for books and podcasts and lectures, for listening to music (it is my jukebox). I buy digital music on Amazon where it is 256K, no DRM and cheaper but I love iTunes and I confess, I love my iPod.
Vista OnTime – (Single license is free!), incredibly powerful project management & bug tracker with incredible support. Best of breed.  PC or Web-based
VistaAppleLogo RescueTime Passively watches where you spend your time and then reports back to you. Very useful for getting yourself back on track!
Vista Roboform– 1Password for the PC – the single most useful utility I know of for creating and tracking passwords and for filling in forms. Incredibly useful and constantly improved.
AppleLogo

Stuffit – the cannonical compression and decompression software for the Mac

Vista TimeSnapper Takes a picture of your current application or screen every n seconds.  Incredibly useful. An automated lab notebook that serves two very valuable purposes: find out what went wrong by looking back, and find out where you spent your time.
Vista Winzip - Sometimes a product is so essential you almost forget it isn't built into the operating system. I've been using WinZip forever and it just keeps getting better
Vista WSFTP Pro - the best ftp program I've ever worked with

 

Absurdly Useful WebSites As Utilities

VistaAppleLogo Amazon's Media Library - one of the better ways to share what you're reading and listening to
VistaAppleLogo Pandora -Single best music site on the net, but well beyond that, the Music Genome Project is truly fascinating; be sure to read about what they are doing.
VistaAppleLogo Tinyurl.com – take a very long url and make it short and useful
VistaAppleLogo

TransferBigFile – Files too big to mail? Install this tiny utility  then drag your file to the utility drop spot (Windows only – for Mac you use a web site); your file is uploaded and an email is generated with a link to the file (encrypted if you like). The file goes away in 10 days. Lots of options such as email on receipt, etc.

VistaAppleLogo Tripit – If you travel for business, this site is indispensible – total itinerary manager – just email confirmations as they arrive and Tripit organizes it all. More useful than you'd expect.

On Learning New Technology – Controls Toolkit & Examples

 ScottPDC I had a great PDC.  I arrived with a schedule filled with sessions I intended to go to, and (shhhhh!) I skipped them all (except, ScottGu’s keynote and Scott Hanselman’s BabySmash both of which were (predictably) great). 

The reason I skipped all the others (and there were a lot of excellent talks that I will now have to settle for seeing the recordings)  is that I had the singular opportunity to not only talk with, but truly engage with some amazing developers. 

Microsoft puts on 3 major shows a year, and PDC is the most “forward looking” of the three, which means, ultimately, that a lot of what you see at PDC you probably can’t put into production code today.  Given that and the “all in” cost of attending (admission, transportation, accommodations and missed work) it tends to attract some very serious geeks. And that can make for some very interesting conversations on any number of topics.

Terse, Complex or Dead Simple?

One topic that came up a lot was the universal sense of being overwhelmed by the sheer volume of new technology, and how each developer coped with learning all the new material. This lead to some fascinating discussions about how technology is taught, documented, and explained. It was very clear that there would never be consensus on the "best" method; there can be no one best way to teach because there  is no single learning style. That said, there did seem to be two schools of thought about examples. One group seemed to strongly favor real-world examples even at the cost of a little complexity;  the other to strongly favor incredibly simple examples even at the cost of being overtly artificial.

It is clear that I favor dead-simple examples, and I can trace that directly to learning to program what turned out to be a terrific ISAM package in C in the mid 1980s. The problem, though was in their documentation. Each of their examples was incredibly cool, but amazingly opaque. It was only after I stripped away all the indirection and programming elegance that I realized that there were just half a dozen relatively straight-forward function calls to manage. I ended up creating my own set of examples that were 1/10 the size of theirs and 10 times easier to grok. It was a lesson that is seared into my writing center.

Revisiting the Controls Toolkit Examples

The incredible folks in Shawn Burke's group have released the Silverlight Control Toolkit and it comes with what I consider to be really terrific documentation, plus source code plus examples and so Bob's Your Uncle and you're done.  But while I really like what they've done, I'm going to take a shot at revisiting some of the samples to really tear it down to the simplest and most visible moving parts, and along the way I'll also take a look at using the controls in both Blend and Visual Studio.  This will be an on-going effort, over a few weeks and depending on your comments and what else comes up, we'll take some detours and delve into some of the nuances of how these controls can be used in unexpected ways.

Please click here to download the Controls Toolkit

I set out today to begin work on the AutoComplete control, which has all sorts of interesting nooks and crannies, but I quickly discovered that I wanted to create my own data to explore this control (and others), and this is where ADD is either a curse or a blessing, because I spent quite a bit of time spelunking through the open file dialog, and the scroll viewer (not to mention deciding on the right kind of generic collection.  So, since it is the weekend, let's actually start there and we'll get to the AutoComplete control (part 1 of many) in the next entry.

Creating A Data Set of Words

What I wanted was a very large set of words (so that when we're working with the auto-complete you can start typing (e.g., ) acc and get quite a few words that match (accent, accompany, accordand, accordingly, accosted, account, accounted, accumulate, accurate, accustomed)  That lets you continue to narrow, adding an o knocks out accent and accumulate, accurate and accustomed. adding an r gets you down to just two words: accordand and accordingly.  You see the point.

On the other hand, I did not want you to have to download a large file just to make this work, and besides, it is more interesting if you can put in your own set.  So! it doesn't take much to write a program that reads a file from your disk (Silverlight doesn't mind reading from your disk, it is writing to your disk where it is very careful).  We take the file and read in all the words and after a bit of hocus pocus create a collection of unique words (that is, we eliminate all the duplicates and all the punctuation).  Sort the collection and we have just what we need.

Here is how I chose to do it.

First, i needed a file, so I went to Project Gutenberg (a wonderful project well worth your time) where I was able to download a text version of Swan's way by Marcel Proust (public domain).  That will serve nicely as a source of words (and some interesting words at that!)

Second, I created a very simple UI as this is really just a utility. The UI has a button to open the file dialog, a TextBlock to let me know what is happening, and then two large TextBlocks encased inside ScrollViewers to show me the words I've retrieved. The left ScrollViewer displays the words in the order they were retrieved, the right displays them in alphabetical order,

AutoFill
(Figure cropped)

Notice that I've only captured 4,298 unique words. That is because I told the code to stop retrieving words once it hit 100,000 characters – why sit around all day on a weekend and 4K words is plenty.

Here's the code walkthrough; nothing terribly complicated, though with luck you'll find something new or useful…

<UserControl x:Class="AutoFill1.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="600">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="5*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>
        <Button x:Name="OpenFile" Content="Open File"
                 Height="30" Width="60" Margin="10"
                 HorizontalAlignment="Left" VerticalAlignment="Top"  />
        <TextBlock x:Name="Totals" Width="200" Height="30" 
            Text="Waiting..."  Grid.Column="1" VerticalAlignment="Top" 
            Margin="0,20,0,0"/>

        <ScrollViewer x:Name="WordDisplayViewer"  BorderBrush="Black" 
          BorderThickness="1"  Grid.Row="1" Grid.Column="0" Margin="5" 
          Background="Bisque" VerticalScrollBarVisibility="Auto" 
          HorizontalScrollBarVisibility="Hidden" VerticalAlignment="Stretch" 
          Width="190" >
            <TextBlock x:Name="WordDisplay" TextWrapping="Wrap" Text="Words" Width="160" />
        </ScrollViewer>
        <ScrollViewer BorderBrush="Black" BorderThickness="1" Grid.Row="1" 
            Grid.Column="1" Margin="5" Width="190" Background="Wheat" 
            VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden"
            VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
             <TextBlock x:Name="SortDisplay" TextWrapping="Wrap" Width="160"  
               Text="SortDisplay" />
        </ScrollViewer>
    </Grid>
</UserControl>

The key to having a nice scrolling block of text is to have the width of the text box be less than the width of the scrollViewer – enough to allow for the scroll bars.

In Page.xaml.cs I declare two private members, a StringBuilder and a List<String> and in Page_Loaded I provide the button's event handler, which opens the file dialog,

public partial class Page : UserControl
{
   private StringBuilder sb = new StringBuilder();
   private List<string> words = new List<string>();

   public Page()
   {
      InitializeComponent();
      Loaded += new RoutedEventHandler( Page_Loaded );
   }

   void Page_Loaded( object sender, RoutedEventArgs e )
   {
      OpenFile.Click += new RoutedEventHandler( OpenFile_Click );
   }

The job of the event handler is to present the file dialog and then if hte user picks a file to open that file as a file stream, read through each line and using the string builder create a nice long string of up to 100,000 characters. 

void OpenFile_Click( object sender, RoutedEventArgs e )
{
   OpenFileDialog openFileDialog1 = new OpenFileDialog();

   // Set filter options and filter index.
   openFileDialog1.Filter = "Text Files (.txt)|*.txt|All Files (*.*)|*.*";
   openFileDialog1.FilterIndex = 1;

   openFileDialog1.Multiselect = false;

   // Call the ShowDialog method to show the dialog box.
   bool? userClickedOK = openFileDialog1.ShowDialog();

   // Process input if the user clicked OK.
   if ( userClickedOK == true )
   {
      // Open the selected file to read.
      System.IO.Stream fileStream = openFileDialog1.File.OpenRead();

      using ( System.IO.StreamReader reader = new System.IO.StreamReader( fileStream ) )
      {
         string temp = string.Empty;
         try
         {
            do
            {
               temp = reader.ReadLine();
               sb.Append( temp );
            } while ( temp != null && sb.Length < 100000 ) ;
         }
         catch
         {
         }
      }
      fileStream.Close();

(Notice that this is not exactly industrial-strength code. )

SwanBook 

In any case, once I have my string I break it into words by using the regular expression "\b" which separates the string into words which I put in an array of strings called AllWords.

If you are interested in regular expressions but you find books and articles on them too abstract, I can't recommend RegEx Buddy highly enough.  You put in some text, and it (a) helps you build up your regular expression and (b) lets you test and (c) explains what works, what doesn't and why.  Caveat: it is not free.

 

RegExBuddy

(Regex Buddy at work – from their site)

 

In any case, once my string is broken into words, I then iterate through each word to see if I have it already (I want only one copy of each word) and if not, I check to see if it is "junk" (has punctuation, numerals, etc.).  If it passes muster, I add it to my list<string> and once done I display the results,

fileStream.Close();
string pattern = "\\b";
string[] allWords = 
System.Text.RegularExpressions.Regex.Split( 
                  sb.ToString(), pattern );
foreach ( string word in allWords )
{
   if ( words.Contains( word ) == false )
   {
      if ( word.Length > 0 &&  ! IsJunk( word ) )
      {
         words.Add( word );
      }     // end if not junk
   }        // end if unique
}           // end for each word in all words
Totals.Text = words.Count + " unique words added.";
Display();
}           // end if user clicked OK in dialog
             // end method

To be able to display and use the words, I've created two properties,

public List<string> Words
{
   get { return this.words; }
}
 
public List<string> SortedWords
{
   get 
   { 
      List<string> temp = this.words;
      temp.Sort();
      return temp;
   }
}

Here are the two missing helper methods:

private void Display()
{
   WordDisplay.Text = string.Empty;
   SortDisplay.Text = string.Empty;
   foreach ( string s in Words )
   {
      WordDisplay.Text += " " + s;
   }
   foreach ( string s2 in SortedWords )
   {
      SortDisplay.Text += " " + s2;
   }
}
 
private bool IsJunk( string theWord )
{
   foreach ( char c in theWord.ToCharArray() )
   {
      if ( char.IsPunctuation( c ) || char.IsDigit( c ) || char.IsSymbol( c ) || char.IsSeparator( c ) )
         return true;
   }
   return false;
}

The very next thing to do (now that this works) is to refactor into three classes: the original page, the code that opens the dialog box and the code that creates the data structure of words.  I'll do that next time and use that data structure to illustrate how to use the autocomplete box.

Posted by jesseliberty | 10 comment(s)
Filed under: