Asynchronous Tasks in Silverlight
by Andrew Shough on Jul.15, 2010, under Silverlight
Anyone who has developed in Silverlight knows that you can’t block the UI, silverlight actually throws an exception if you block the UI. So most tasks need to be done asynchronously, but for some strange reason I can’t find this type of functionality built into the silverlight framework, one would think that Microsoft would supply a way of doing this without having to write your own threading, something like the following:
public void LoadData()
{
int result = 0;
Task.Execute( () => result = SomeMethod(), (e) => taskComplete(e, result));
}
public int SomeMethod()
{
return 2 * (100 / 5) + 1000;
}
public void taskComplete(Exception e, int result)
{
if(e != null)
// do something with the exception.
else
// do something with the result.
}
But since I couldn’t find one in the silverlight framework, I wrote one for my own use:
public class Task<T>
{
public Task(Func<T> function, Action<T> completed)
{
_func = function;
_completed = completed;
}
private Func<T> _func;
private Action<T> _completed;
private Dispatcher _dispatcher;
public void Execute()
{
Thread thread = new Thread(Start);
thread.Start();
}
private void Start()
{
var result = _func();
_dispatcher.BeginInvoke(_completed, result);
}
}
public class Task
{
public Task(Dispatcher dispatcher, Action action, Action<Exception> completed)
{
_action = action;
_completed = completed;
_dispatcher = dispatcher;
}
public Task(Action action, Action<Exception> completed)
{
_action = action;
_completed = completed;
}
private Dispatcher _dispatcher;
private Action _action;
private Action<Exception> _completed;
private Exception _exception;
public void Execute()
{
Thread thread = new Thread(Start);
thread.Start();
}
public static void Execute(Dispatcher dispatcher, Action action, Action<Exception> completed)
{
new Task(dispatcher, action, completed).Execute();
}
public static void Execute(Action action, Action<Exception> completed)
{
new Task(action, completed).Execute();
}
public static void Execute<T>(Func<T> action, Action<T> completed)
{
new Task<T>(action, completed).Execute();
}
private void Start()
{
try
{
_action();
}
catch (Exception ex)
{
_exception = ex;
}
_dispatcher.BeginInvoke(_completed, _exception);
}
}
All you need to do to use it, is the same as I did right at the top:
public void LoadData()
{
int result = 0;
Task.Execute( () => result = SomeMethod(), (e) => taskComplete(e, result));
}
public int SomeMethod()
{
return 2 * (100 / 5) + 1000;
}
public void taskComplete(Exception e, int result)
{
if(e != null)
// do something with the exception.
else
// do something with the result.
}
or you can do it without the complete method:
public void LoadData()
{
int result = 0;
Task.Execute(
() => result = SomeMethod(),
(e) =>
{
if(e != null) // do something with the error.
else // do something with the result.
});
}
public int SomeMethod()
{
return 2 * (100 / 5) + 1000;
}
Hope this helps someone with their silverlight UI development.
Custom Columns for the DataGrid in Silverlight
by Andrew Shough on Jul.05, 2010, under Silverlight
We are developing a silverlight front-end for a client, and hit a problem where we didn’t want to specify the columns or write custom converters to get the text descriptions from lists for ID’s etc. We just wanted to say to the grid, here is your data, now bind. So a custom columns class was written.
Our UI makes calls to an web service which returns xml based messages, the message coming back, has all data for that call, so the actual records as well as any look up lists that may be needed to display meaningful data. Each call to get data has a corresponding init call, this call basically gets all fields that will be returned by the call. So we wanted to be able to say our columns come from the “response” and the data comes from the “response.message”. So what was born from this is below:
public class Columns
{
private static Dictionary<DependencyObject, Dictionary<string, DataGridColumn>> columns = new Dictionary<DependencyObject, Dictionary<string, DataGridColumn>>();
private static List<string> filter = new List<string>();
public static readonly DependencyProperty BindingProperty = DependencyProperty.RegisterAttached("Binding", typeof(object), typeof(Columns), new PropertyMetadata(BindingChanged));
public static readonly DependencyProperty FilterProperty = DependencyProperty.RegisterAttached("VisibleColumns", typeof(List<string>), typeof(Columns), new PropertyMetadata(VisibleColummnsChanged));
public static object GetBinding(DependencyObject obj)
{
return (object)obj.GetValue(BindingProperty);
}
public static void SetBinding(DependencyObject obj, object value)
{
obj.SetValue(BindingProperty, value);
}
public static List<string> GetVisibleColumns(DependencyObject obj)
{
return (List<string>)obj.GetValue(FilterProperty);
}
public static void SetVisibleColumns(DependencyObject obj, List<string> value)
{
obj.SetValue(FilterProperty, value);
}
private static void BindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var grid = sender as DataGrid;
var response = e.NewValue as IEnvelope;
grid.AutoGeneratingColumn += (s, ea) =>
{
if (!response.Definitions.ContainsKey(ea.PropertyName))
{
ea.Cancel = true;
return;
}
if (response.Definitions[ea.PropertyName].FieldType is SingleSelect)
{
ea.Column = new DataGridSingleSelectColumn(ea.PropertyName, response);
if (!columns.ContainsKey(sender))
columns.Add(sender, new Dictionary<string, DataGridColumn>());
if (columns[sender].ContainsKey(ea.PropertyName))
{
grid.Columns.Remove(columns[sender][ea.PropertyName]);
columns[sender][ea.PropertyName] = ea.Column; ;
}
else
{
columns[sender].Add(ea.PropertyName, ea.Column);
}
}
ea.Column.Header = response.Definitions[ea.PropertyName].Description;
ea.Column.SortMemberPath = ea.PropertyName;
if (filter.Count > 0)
ea.Column.Visibility = filter.Contains(ea.PropertyName) ? Visibility.Visible : Visibility.Collapsed;
};
grid.ColumnWidth = DataGridLength.Auto;
}
private static void VisibleColummnsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
filter.Clear();
filter.AddRange(e.NewValue as List<string>);
}
}
There are some custom columns in there such as the DataGridSingleSelectColumn, FieldType, and IEnvelope that are specific to this solution, but the idea here is a column collection is created and if the data coming back indicates that the value is a look up value type, then the code gets the description for that id from the correct list.
To use this in XAML is pretty simple:
xmlns:cols="clr-namespace:Your.Namespace;assembly=Your.Assembly"
xmlns:swcd="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
....
<swcd:DataGrid asp:Columns.VisibleColumns="{Binding ListBinding}" asp:Columns.Binding="{Binding Response, Mode=TwoWay}" Margin="-1" ItemsSource="{Binding Response.Message, Mode=TwoWay}" IsReadOnly="True" SelectionMode="Single">
....
I know that this solution is pretty specific to how we retrieve data from our service but the principles are the same, for anyone else who wishes to create custom columns for the DataGrid.