search
Japanese Chinese Nederlands Espanol Italiano Deutsch Francais Twitter Rss Feeds
MicrosoftArticlesForumsFAQs
C# .NET
VB.NET
Visual Studio .NET
ADO.NET
Xml / Xslt
VB 6.0
.NET CF
GDI+
LINQ
Deployment
Security
FoxPro
Silverlight / WPF
Entity Framework
RIA Services

Web ProgrammingArticlesForumsFAQs
JavaScript
ASP
ASP.NET
Web Services

Non-MicrosoftArticlesForumsFAQs
NHibernate
Perl
PHP
Ruby
Java
Linux / Unix
Apple
Open Source

DatabasesArticlesForumsFAQs
SQL Server
Access
Oracle
MySQL
Other Databases

OfficeArticlesForumsFAQs
Excel
Word
Powerpoint
Outlook
Publisher
Money

Operating SystemsArticlesForumsFAQs
Windows 7
Windows Server
Windows Vista
Windows XP
Windows Update
MAC
Linux / UNIX

Server PlatformsArticlesForumsFAQs
BizTalk
Site Server
Exhange Server
IIS

Graphic DesignArticlesForumsFAQs
Macromedia Flash
Adobe PhotoShop
Expression Blend
Expression Design
Expression Web

OtherArticlesForumsFAQs
Subversion / CVS
Ask Dr. Dotnetsky
Active Directory
Networking
Uninstall Virus
Job Openings
Product Reviews
Search Engines
Resumes

 

Silverlight Toolkit: Autocomplete TextBox Stock Symbols and Chart


By Peter Bromberg
Printer Friendly Version
View My Articles
55 Views
    

In a previous article, I presented an example of creating stock charts using the Silverlight Toolkit Charting control, getting the price history data "on demand" from Yahoo Finance. At that time I mentioned that one of the nice features for enhancement would be to provide an autocomplete search box to provide the correct symbol for the stock you want to chart. In this article, I show two different ways this can be done. I also provide a CSV format text file of over 20,000 current stock symbols


The Silverlight Toolkit is a work in progress, with controls listed in three different categories of "feature completion". The Autocomplete TextBox is listed in the "Preview" category, and my understanding is that in the December release, which is not yet available at this writing, there will be more fixes and features, plus support for Observable Collections. Currently the Autocomplete isn't smart enough for that; it's really expecting something like an array of strings. However, with a custom ItemFilter, it is possible to feed it an ObservableCollection of say a "StockSymbol" class containing symbol and description, and still accomplish the objective.

There is some excellent documentation in the standalone CHM help file for the Toolkit that details all the methods, properties and events of the Autocomplete control. However, Jeff Wilcox, who is one of the folks who wrote the code, has provided what I consider an excellent guide that he calls "The missing guide". If you intend to learn to use this control, I highly recommend downloading this page and printing it out for reference. Jeff did a great job of detailing the "ins and outs".

As mentioned, I have two versions of the application. The first does filtering "on the server" - in other words, we send the search string (once it is at least 3 characters) via a WCF service call, and the service returns us the matching ObservableCollection of StockSymbol objects to populate the control with. In the other version, I actually get the entire list of StockSymbols in the Page constructor, and we do all the filtering at the client.  I've provided a "loose" Page.xaml.cs.alternate file in the root of the downloadable solution so you can easily rename files and switch out to either version. So let's take a look at some sample code with comments:

As in the original article, we must first have a service to provide our data. Here is the new code in the added Global.asax.cs class  that handles the symbols:

public static List<stocks.StockSymbol> Symbols;


protected
void Application_Start(object sender, EventArgs e) { Symbols = new List<stocks.StockSymbol>(); string s = File.ReadAllText(Server.MapPath("Symbols.txt")); s = s.Replace("\r", ""); // split to array on end of line string[] rows = s.Split('\n'); // split to the 2 needed columns in each row try { foreach (string s2 in rows) { string[] cols = s2.Split(','); var sym = new stocks.StockSymbol(cols[0], cols[1]); Symbols.Add(sym); } // It's now in Global.Symbols so we don't need to load it again until the app recycles } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } }
When the application starts (on the first request) the above code loads the textfile and splits it into string arrays on the linefeed character, then again on the comma delimiters, in order to populate our List of type StockSymbol, which looks like so:
        /// <summary>
        /// Class to hold a stock symbol and description
        /// </summary>
        public class StockSymbol
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="StockSymbol"/> class.
            /// </summary>
            /// <param name="symbol">The symbol.</param>
            /// <param name="description">The description.</param>
            public StockSymbol(string symbol, string description)
            {
                Symbol = symbol;
                Description = description;
            }

            public StockSymbol()
            {
            }

            public string Symbol { get; set; }
            public string Description { get; set; }
        }
This list is a public static field in Global, so getting at it is as simple as "Global.Symbols". The method that returns the matching List follows:
        /// <summary>
        /// Gets the stock symbols.
        /// </summary>
        /// <param name="searchTerm">The search term.</param>
        /// <returns></returns>
        [OperationContract]
        public List<StockSymbol> GetStockSymbols(string searchTerm)
        {
            List<StockSymbol> symbols = Global.Symbols;
            if (String.IsNullOrEmpty(searchTerm))
                return symbols;
            else
            {
                List<StockSymbol> retList = Contains(symbols, searchTerm);
                return retList;
            }
        }

        /// <summary>
        /// Determines whether List<StockSymbol> contains the specified target search term.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="srch">The Search string.</param>
        /// <returns></returns>
        private List<StockSymbol> Contains(List<StockSymbol> target, string srch)
        {
            return target.FindAll(delegate(StockSymbol sym) { return sym.Description.Contains(srch); });
        }
The "Contains" method above is our predicate for the filtering process.  Now let's switch over to the Silverlight client and see what happens "under the hood" when you start typing in the Autocomplete Textbox:

using System;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using Microsoft.Windows.Controls;
using StockCharts.StockService;

namespace StockCharts
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
            // In this version we filter at the server, so Mode=None.
            autoComplete1.SearchMode = AutoCompleteSearchMode.None;
          
        }

        private void c_GetStockSymbolsCompleted(object sender, GetStockSymbolsCompletedEventArgs e)
        {
            autoComplete1.ItemsSource = e.Result;
            autoComplete1.MinimumPrefixLength = 3;
            autoComplete1.IsTextCompletionEnabled = false;
            // No itemfilter here since we are filtering at the server.
            autoComplete1.PopulateComplete();
        }

        private void c_GetStockDataCompleted(object sender, GetStockDataCompletedEventArgs e)
        {
            ObservableCollection<stocksStockData> stockDatas = e.Result;
            Chart1.LegendTitle = null;
            Chart1.Title = ((stocksStockSymbol) autoComplete1.SelectedItem).Description;
            Dispatcher.BeginInvoke(() => Chart1.DataContext = stockDatas);
            // CLEAR OUT so user can search again
            Dispatcher.BeginInvoke(() => autoComplete1.Text = "");
        }

        private void autoComplete1_SelectedItemChanged(object sender, SelectionChangedEventArgs e)
        {
            if (autoComplete1.SelectedItem == null) return;
            string symbol = ((stocksStockSymbol) autoComplete1.SelectedItem).Symbol;
            var c = new stocksClient();
            c.GetStockDataCompleted += c_GetStockDataCompleted;
            c.GetStockDataAsync(symbol, DateTime.Now.AddYears(-2), DateTime.Now);
        }

        private void autoComplete1_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if(autoComplete1.Text.Length <3) return;
            autoComplete1.Populating += (s, args) =>
                                            {
                                                args.Cancel = true;
                                                var c = new stocksClient();
                                                c.GetStockSymbolsCompleted += c_GetStockSymbolsCompleted;
                                                c.GetStockSymbolsAsync(autoComplete1.Text);
                                            };

        }
    }
}
The KeyUp handler is where I do the logic here. You can also use "TextChanged", but I found KeyUp to have more usable behavior in this approach. If the user hasn't typed at least 3 characters, we bail. The Populating event is provided with a delegate that goes out to the service, returns the appropriate list of symbols, and the c_GetStockSymbolsCompleted callback handler hands off the data to the control and calls its PopulateComplete method, which basically says "I'm ready to provide these selections to the user to choose from".

And here is a screenshot of everything "in action":



This is a really useful control, and it's going to get better very shortly. In fact, they are going to add it to WPF next. Yep, Silverlight first, WPF later. Go Figure!

You can download the full Visual Studio 2008 solution here, including the list of 20,000 plus symbols, descriptions and exchanges. The file has all NYSE, AMEX, NASDAQ, OTCBB ("Pink Sheet") stocks as well as a number of indexes. Not all these symbols will work with Yahoo finance, so be prepared to handle the occasional boo-boo!

Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites.
Please post questions at forums, not via email!

button
Article Discussion: Silverlight Toolkit: Autocomplete TextBox Stock Symbols and Chart
Peter Bromberg posted at Saturday, November 29, 2008 8:28 AM
Original Article