CodeProject, WPF

WPF : How To Create Your Own Threaded DataSourceProvider

It has been a while since I posted something, but you see I have been moving house and do not have any internet at the moment, so it has been quite problematic.

After much delay I finally bit the bullet and and bought myself a mobile broadband dongle which enables me to work on the move. Hooray.

Anyway in this post I am going to show you guys how to create your own DataSourceProviders for WPF. For those that know nothing about DataSourceProviders, they are a way of fetching data directly from XAML, and you may use a sub class of DataSourceProvider to do the fetching of certain user requested data based on certain parameters. Basically it all starts by inheriting from DataSourceProvider.

Now WPF already has some very useful DataSourceProviders, such as

ObjectDataProvider : Which allows you to call methods in a certain class to obtain values. This is highly configurable through method name, parameters etc etc

XmlDataProvider : Which allows you to obtain XML data from various XML sources, such as XML file, XML data islands etc etc

These 2 classes that come with the framework are great, but occassionally it is useful to know how to write your own DataSourceProvider.

And as luck would have it it is not that hard. It basically boils down to 3 steps

  1. Inherit from DataSourceProvider
  2. Override BeginQuery()
  3. Make sure to call OnQueryFinished() at some point with the data fetched

And that is all there is to it really. I will now show you an example of what this may look like, where the example is very contrived I must say, but it does showcase how to create a simple DataSourceProvider. This example allows the user to specify (in XAML) the name of embedded resource TXT file to read out line by line, which is then exposed as the data from the subclassed DataSourceProvider. This data can then be used as the ItemSource for say a ListBox/ComboBox or something like that.

One extra thing it provides is the ability to support background loading, which it does by examining the IsAsync property (which can be set from XAML), and if that is set will do the fetch on a background worker item within the ThreadPool.

Anyway here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Threading;
 
namespace TXTDataProvider
{
    /// <summary>
    /// Very simple DataSourceProvider that shows
    /// how to read from resource files, and demonstrates
    /// how to do it Asnychrounsly
    /// </summary>
    public class TXTDataSourceProvider : DataSourceProvider
    {
        #region Data
        private Boolean isAsync = false;
        private String fileName = String.Empty;
        #endregion
 
        #region Overrides
        /// <summary>
        /// Begins the query to get the data. This examines the
        /// IsAsync flag to determine if it needs to be threaded
        /// and run in the background or not
        /// </summary>
        protected override void BeginQuery()
        {
            if (IsAsync)
                ThreadPool.QueueUserWorkItem(RunQuery, null);
            else
                RunQuery(null);
 
        }
        #endregion
 
        #region Private Methods
        /// <summary>
        /// Runs the actual Query taking a state object
        /// </summary>
        /// <param name="state">The object state thaat could be 
        /// used to pass some state to the query operation </param>
        private void RunQuery(object state)
        {
            base.BeginQuery();
 
            try
            {
                List<String> dataLocal = new List<String>();
                String[] lines = null;
 
                switch (FileName)
                {
                    case "TextFile1":
                        lines = global::TXTDataProvider.
                            Properties.Resources.TextFile1
                            .Split(new String[] { "rn" }, 
                                StringSplitOptions.None);
                        break;
                    case "TextFile2":
                        lines = global::TXTDataProvider.
                            Properties.Resources.TextFile2
                            .Split(new String[] { "rn" }, 
                                StringSplitOptions.None);
                        break;
                    default:
                        break;
                }
 
                foreach (String line in lines)
                    dataLocal.Add(line);
 
                ObjectInstance = dataLocal;
            }
            catch
            {
                ObjectInstance = null;
            }
        }
        #endregion
 
        #region Public Properties
        /// <summary>
        /// Gets or sets a value that indicates 
        /// whether to perform the fetching Asynch or not
        /// </summary>
        public Boolean IsAsync
        {
            get { return isAsync; }
            set 
            {
                isAsync = value;
                OnPropertyChanged(
                    new PropertyChangedEventArgs("IsAsync"));
 
            }
        }
 
        /// <summary>
        /// Gets or sets a reference to the data
        /// object.
        /// </summary>
        public object ObjectInstance
        {
            get { return Data; }
            set
            {
                OnQueryFinished(value, null, null, null);
                OnPropertyChanged(
                    new PropertyChangedEventArgs("ObjectInstance"));
            }
        }
 
        /// <summary>
        /// Gets or sets the name of the FileName to 
        /// read to provide data from.
        /// 
        /// This is obviously a bit contrived, but it does 
        /// assist with the demo code
        /// </summary>
        public String FileName
        {
            get { return fileName; }
            set
            {
                fileName = value;
                OnPropertyChanged(
                    new PropertyChangedEventArgs("FileName"));
 
            }
        }
        #endregion
 
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And this is the relevant bit of xaml that makes use of this custom DataSourceProvider:

<Window x:Class="TXTDataProvider.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TXTDataProvider" WindowStartupLocation="CenterScreen"
    Title="Demo : A simple demo of how to create a custom DataSourceProvider in WPF" 
    Height="600" Width="600" TextElement.FontSize="11">
    
    <Window.Resources>
        <local:TXTDataSourceProvider x:Key="txtProvider"
              IsAsync="True" FileName="TextFile1"/>
    </Window.Resources>
    
    <DockPanel LastChildFill="True">
 
        <ListBox ItemsSource="{Binding Mode=OneTime, 
                    Path=., Source={ StaticResource txtProvider}}" 
                 DockPanel.Dock="Bottom">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Label Content="Provided Value:" 
                               FontFamily="Arial" 
                               FontWeight="Bold" 
                               Margin="5,0,0,0"/>
                        <Label Content="{Binding}" 
                               FontFamily="Arial" 
                               FontWeight="Normal" 
                               Margin="5,0,0,0"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </DockPanel>
</Window>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And here is what it looks like when it runs:

As always here is a small demo project. Enjoy

http://dl.dropbox.com/u/2600965/Blogposts/2010/02/TXTDataProvider.zip

9 thoughts on “WPF : How To Create Your Own Threaded DataSourceProvider

  1. Thank you. Simple examples are the best. It is quite clear for people just learning and unfamiliar.

  2. Hi Sacha,

    thanks for this great post!
    But I have one question: Is it possible to change and SAVE(!) textfile data in this easy way, too?

    Kind regards,

    Stone

  3. Stone

    I think you could more than likely do that by adding a property to the datasource, that when set will save a file.

    Not sure you would want to though, datasources are more typically used to FETCH data.

  4. Actually , these days when looking for attractive applications made by WPF ,i just see Sasha projects with their open source codes .
    you are the best in the WPF Programming , It is the truth , i like your ideas in programming , i can say your programming philosophy …

    thank you

Leave a comment