diff --git a/History.txt b/History.txt index f790d49..3e3735f 100644 --- a/History.txt +++ b/History.txt @@ -3,7 +3,10 @@ - ComponentModel enhancements: Tuple, ErrorEventArgs, Async - Control additions: - LinkLabel, WrapPanel, ActivityControl + LinkLabel, WrapPanel, ActivityControl, ObjectDataSource +- Removed ElementProperty, replaced with BoundParameter +- Removed Parameter property on SetProperty, and replaced with ValueBinding + property 0.3.2 Release diff --git a/samples/Experiments/DataSourcePage.xaml b/samples/Experiments/DataSourcePage.xaml new file mode 100644 index 0000000..2275570 --- /dev/null +++ b/samples/Experiments/DataSourcePage.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/TwitFaves/MainWindow.xaml.cs b/samples/TwitFaves/MainWindow.xaml.cs new file mode 100644 index 0000000..5dd558c --- /dev/null +++ b/samples/TwitFaves/MainWindow.xaml.cs @@ -0,0 +1,17 @@ +// MainPage.xaml.cs +// + +using System; +using System.Windows; +using System.Windows.Controls; +using SilverlightFX.UserInterface; + +namespace TwitFaves { + + public partial class MainWindow : Window { + + public MainWindow() { + InitializeComponent(); + } + } +} diff --git a/samples/TwitFaves/MainWindowModel.cs b/samples/TwitFaves/MainWindowModel.cs new file mode 100644 index 0000000..530b24f --- /dev/null +++ b/samples/TwitFaves/MainWindowModel.cs @@ -0,0 +1,59 @@ +// MainWindowModel.cs +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using TwitFaves.Data; + +namespace TwitFaves { + + public class MainWindowModel : ViewModel { + + private ITwitterService _twitterService; + + public MainWindowModel(ITwitterService twitterService) { + _twitterService = twitterService; + } + + public Async GetTweets(string userName) { + Async asyncTweets = new Async(); + + _twitterService.GetTweets(userName, delegate(IEnumerable tweets) { + if (tweets == null) { + asyncTweets.Complete(new Exception("Favorites for '" + userName + "' could not be loaded.")); + } + else { + IEnumerable groupedTweets = + tweets.AsQueryable(). + OrderByDescending(t => t.Date). + GroupByContiguous( + t => TweetGroup.GetDaysGroupValue(t), + EqualityComparer.Default, + (t, d) => new TweetGroup(t, d)); + + asyncTweets.Complete(groupedTweets); + } + }); + + return asyncTweets; + } + + public Async GetProfile(string userName) { + Async asyncProfile = new Async(); + + _twitterService.GetProfile(userName, delegate(Profile profile) { + if (profile == null) { + asyncProfile.Complete(new Exception("The profile for '" + userName + "' could not be loaded.")); + } + else { + asyncProfile.Complete(profile); + } + }); + + return asyncProfile; + } + } +} diff --git a/samples/TwitFaves/Properties/AppManifest.xml b/samples/TwitFaves/Properties/AppManifest.xml new file mode 100644 index 0000000..6712a11 --- /dev/null +++ b/samples/TwitFaves/Properties/AppManifest.xml @@ -0,0 +1,6 @@ + + + + diff --git a/samples/TwitFaves/Properties/AssemblyInfo.cs b/samples/TwitFaves/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..eb9b568 --- /dev/null +++ b/samples/TwitFaves/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TwitFaves")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TwitFaves")] +[assembly: AssemblyCopyright("Copyright © 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4ead5dc3-866d-44d0-9e5a-dfc856679489")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/TwitFaves/TwitFaves.csproj b/samples/TwitFaves/TwitFaves.csproj new file mode 100644 index 0000000..0cbf9df --- /dev/null +++ b/samples/TwitFaves/TwitFaves.csproj @@ -0,0 +1,122 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {DB259CE6-96A8-4F59-A0D5-A3F6815F4732} + {A1591282-1198-4647-A2B1-27E5FF5F6F3B};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + TwitFaves + TwitFaves + v3.5 + false + + + true + true + TwitFaves.xap + Properties\AppManifest.xml + TwitFaves.App + TestPage.html + false + true + false + + + 3.5 + + + 3.0.1927.0 + + + true + full + false + bin\Debug + DEBUG;TRACE;SILVERLIGHT + true + true + prompt + 4 + + + pdbonly + true + bin\Release + TRACE;SILVERLIGHT + true + true + prompt + 4 + + + + False + ..\..\binaries\Debug\Silverlight\SilverlightFX.dll + + + + + + + + + + + + + App.xaml + + + + + + + + + MainWindow.xaml + + + + + + MSBuild:Compile + Designer + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:MarkupCompilePass1 + Designer + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Web/Experiments/DataSourceSample.aspx b/samples/Web/Experiments/DataSourceSample.aspx new file mode 100644 index 0000000..a95afff --- /dev/null +++ b/samples/Web/Experiments/DataSourceSample.aspx @@ -0,0 +1,16 @@ +<%@ Page Language="C#" EnableViewState="false" EnableEventValidation="false" %> + + + + DataSource Sample + + +
+ + + + + +
+ + diff --git a/samples/Web/TwitFaves/TwitFaves.aspx b/samples/Web/TwitFaves/TwitFaves.aspx new file mode 100644 index 0000000..abd1b1e --- /dev/null +++ b/samples/Web/TwitFaves/TwitFaves.aspx @@ -0,0 +1,17 @@ +<%@ Page Language="C#" EnableViewState="false" EnableEventValidation="false" %> + + + + Twitter Favorites + + +
+ + + + + + +
+ + diff --git a/samples/Web/TwitFaves/TwitFaves.xap b/samples/Web/TwitFaves/TwitFaves.xap new file mode 100644 index 0000000..1ae1c3d Binary files /dev/null and b/samples/Web/TwitFaves/TwitFaves.xap differ diff --git a/samples/Web/Web.csproj b/samples/Web/Web.csproj index 37c29f1..3c3bcfe 100644 --- a/samples/Web/Web.csproj +++ b/samples/Web/Web.csproj @@ -11,7 +11,7 @@ Web Web v3.5 - {AC2DE467-846C-4621-AFEC-FF35DA1F8062}|..\EffectsSample\EffectsSample.csproj|Effects|False,{E333259B-1F2E-473E-90D4-7306D3E283B4}|..\EffectControl\EffectControl.csproj|Effects|False,{750026A3-3EA6-4D5F-B6DB-EBECDD3A8D3A}|..\TaskList\TaskList.csproj|TaskList|False,{783F6A15-E472-41A9-903C-105DC10F9BF0}|..\ThemeSample\ThemeSample.csproj|Themes|False,{39685CDF-0CB4-424F-BD0A-100AA220CB79}|..\Experiments\Experiments.csproj|Experiments|False,{A9A30F66-5957-4467-B206-3AC96714EAB8}|..\WeatherWidget\WeatherWidget.csproj|Weather|False,{3C606440-9DF0-4A72-A08D-23B3F20F67C4}|..\FlickrTiles\FlickrTiles.csproj|Flickr|False,{6F2D06F0-B548-4D89-8628-A7EB3BE4A8BE}|..\AmazonSearch\AmazonSearch.csproj|Amazon|False,{A8C08DAF-477C-4CEB-9AA8-21C412A06452}|..\AmazonStore\AmazonStore.csproj|Amazon|False,{750026A4-3EA6-4D5F-B6DB-EBECDD3A8D3A}|..\News\News.csproj|News|False + {AC2DE467-846C-4621-AFEC-FF35DA1F8062}|..\EffectsSample\EffectsSample.csproj|Effects|False,{E333259B-1F2E-473E-90D4-7306D3E283B4}|..\EffectControl\EffectControl.csproj|Effects|False,{750026A3-3EA6-4D5F-B6DB-EBECDD3A8D3A}|..\TaskList\TaskList.csproj|TaskList|False,{783F6A15-E472-41A9-903C-105DC10F9BF0}|..\ThemeSample\ThemeSample.csproj|Themes|False,{39685CDF-0CB4-424F-BD0A-100AA220CB79}|..\Experiments\Experiments.csproj|Experiments|False,{A9A30F66-5957-4467-B206-3AC96714EAB8}|..\WeatherWidget\WeatherWidget.csproj|Weather|False,{3C606440-9DF0-4A72-A08D-23B3F20F67C4}|..\FlickrTiles\FlickrTiles.csproj|Flickr|False,{6F2D06F0-B548-4D89-8628-A7EB3BE4A8BE}|..\AmazonSearch\AmazonSearch.csproj|Amazon|False,{A8C08DAF-477C-4CEB-9AA8-21C412A06452}|..\AmazonStore\AmazonStore.csproj|Amazon|False,{750026A4-3EA6-4D5F-B6DB-EBECDD3A8D3A}|..\News\News.csproj|News|False,{DB259CE6-96A8-4F59-A0D5-A3F6815F4732}|..\TwitFaves\TwitFaves.csproj|TwitFaves|False true @@ -83,6 +83,8 @@ + + @@ -93,6 +95,8 @@ + + diff --git a/src/Client/Core/Core.csproj b/src/Client/Core/Core.csproj index d556176..8fac439 100644 --- a/src/Client/Core/Core.csproj +++ b/src/Client/Core/Core.csproj @@ -74,7 +74,13 @@ Code + + + + + + @@ -169,6 +175,8 @@ + + @@ -187,6 +195,8 @@ + + @@ -236,9 +246,6 @@ Code - - Code - Code @@ -408,9 +415,6 @@ Code - - Code - Code diff --git a/src/Client/Core/Data/BoundParameter.cs b/src/Client/Core/Data/BoundParameter.cs new file mode 100644 index 0000000..627f76c --- /dev/null +++ b/src/Client/Core/Data/BoundParameter.cs @@ -0,0 +1,61 @@ +// BoundParameter.cs +// Copyright (c) Nikhil Kothari, 2008. All Rights Reserved. +// http://www.nikhilk.net +// +// Silverlight.FX is an application framework for building RIAs with Silverlight. +// This project is licensed under the BSD license. See the accompanying License.txt +// file for more information. +// For updated project information please visit http://projects.nikhilk.net/SilverlightFX. +// + +using System; +using System.Windows; +using System.Windows.Data; + +namespace SilverlightFX.Data { + + /// + /// Represents a parameter whose value is specified using a binding. + /// + public sealed class BoundParameter : Parameter { + + private Binding _valueBinding; + private BindingShim _binder; + + /// + /// Gets or sets the binding used to determine the value of the parameter. + /// + public Binding ValueBinding { + get { + return _valueBinding; + } + set { + _valueBinding = value; + } + } + + /// + protected override void Activate() { + if (_valueBinding == null) { + throw new InvalidOperationException("The ValueBinding property on BoundParameter must be set."); + } + + _binder = new BindingShim(AssociatedElement, _valueBinding, OnValueBindingChanged); + } + + /// + protected override void Deactivate() { + _binder.Dispose(); + _binder = null; + } + + /// + public override object GetValue() { + return _binder.Value; + } + + private void OnValueBindingChanged() { + OnValueChanged(); + } + } +} diff --git a/src/Client/Core/Data/DataSource.cs b/src/Client/Core/Data/DataSource.cs new file mode 100644 index 0000000..0d24224 --- /dev/null +++ b/src/Client/Core/Data/DataSource.cs @@ -0,0 +1,433 @@ +// DataSource.cs +// Copyright (c) Nikhil Kothari, 2008. All Rights Reserved. +// http://www.nikhilk.net +// +// Silverlight.FX is an application framework for building RIAs with Silverlight. +// This project is licensed under the BSD license. See the accompanying License.txt +// file for more information. +// For updated project information please visit http://projects.nikhilk.net/SilverlightFX. +// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Threading; + +namespace SilverlightFX.Data { + + // TODO: Add support for paging + + /// + /// A base class for data source controls. A data source control is responsible + /// for managing loading of data into the presentation. + /// + [TemplateVisualState(Name = DataSource.IdleActivityState, GroupName = "ActivityStates")] + [TemplateVisualState(Name = DataSource.LoadingActivityState, GroupName = "ActivityStates")] + public abstract class DataSource : ContentControl, IAsyncControl { + + private const string IdleActivityState = "Idle"; + private const string LoadingActivityState = "Loading"; + + private static readonly DependencyProperty AsyncDataProperty = + DependencyProperty.Register("AsyncData", typeof(Async), typeof(ObjectDataSource), null); + + private static readonly DependencyProperty DataProperty = + DependencyProperty.Register("Data", typeof(IEnumerable), typeof(ObjectDataSource), null); + + private static readonly DependencyProperty DataItemProperty = + DependencyProperty.Register("DataItem", typeof(object), typeof(ObjectDataSource), null); + + private static readonly DependencyProperty IsLoadingAsyncDataProperty = + DependencyProperty.Register("IsLoadingAsyncData", typeof(bool), typeof(ObjectDataSource), + new PropertyMetadata(false)); + + private bool _autoLoadData; + private DispatcherTimer _dataLoadTimer; + private DispatcherTimer _refreshTimer; + private string _dataLoadStatusText; + + private EventHandler _loadingDataEventHandler; + private EventHandler _dataLoadedEventHandler; + private EventHandler _errorEventHandler; + private EventHandler _asyncActivityChangedHandler; + + private DelegateCommand _loadDataCommand; + + /// + /// Initializes an instance of a DataSource control. + /// + protected DataSource() { + DefaultStyleKey = typeof(DataSource); + + _dataLoadTimer = new DispatcherTimer(); + _dataLoadTimer.Interval = TimeSpan.FromSeconds(1); + _dataLoadTimer.Tick += OnDataLoadTimerTick; + + _refreshTimer = new DispatcherTimer(); + _refreshTimer.Interval = TimeSpan.Zero; + _refreshTimer.Tick += OnRefreshTimerTick; + + Loaded += OnLoaded; + } + + /// + /// Gets the current asynchronously loading data. This is valid when + /// IsLoadingAsyncData is true. + /// + public Async AsyncData { + get { + return (Async)GetValue(AsyncDataProperty); + } + private set { + SetValue(AsyncDataProperty, value); + if (_asyncActivityChangedHandler != null) { + _asyncActivityChangedHandler(this, EventArgs.Empty); + } + } + } + + /// + /// Whether the data source should automatically load data upon startup. + /// + public bool AutoLoadData { + get { + return _autoLoadData; + } + set { + _autoLoadData = value; + } + } + + /// + /// Gets the current data loaded by the data source. + /// + public IEnumerable Data { + get { + return (IEnumerable)GetValue(DataProperty); + } + private set { + SetValue(DataProperty, value); + } + } + + /// + /// Gets the first item within the current data loaded by the data source. + /// + public object DataItem { + get { + return GetValue(DataItemProperty); + } + set { + SetValue(DataItemProperty, value); + } + } + + /// + /// Gets or sets the delay that is used when a load operation needs to + /// be performed. This allows batching multiple changes into a single + /// attempt to load data. + /// + public TimeSpan DataLoadDelay { + get { + return _dataLoadTimer.Interval; + } + set { + _dataLoadTimer.Interval = value; + } + } + + /// + /// Gets or sets the status text to use as a message when the data source + /// is loading data. + /// + public string DataLoadStatusText { + get { + return _dataLoadStatusText; + } + set { + _dataLoadStatusText = value; + } + } + + /// + /// Gets or sets the duration after which the data loaded by the data source + /// is refreshed. + /// + public TimeSpan DataRefreshInterval { + get { + return _refreshTimer.Interval; + } + set { + _refreshTimer.Interval = value; + if (_refreshTimer.Interval == TimeSpan.Zero) { + _refreshTimer.Stop(); + } + } + } + + /// + /// Whether the data source is performing an asynchronous load operation. + /// + public bool IsLoadingAsyncData { + get { + return (bool)GetValue(IsLoadingAsyncDataProperty); + } + private set { + SetValue(IsLoadingAsyncDataProperty, value); + + string visualState = value ? DataSource.LoadingActivityState : DataSource.IdleActivityState; + VisualStateManager.GoToState(this, visualState, /* useTransitions */ true); + } + } + + /// + /// Raised when data has been loaded by the data source. + /// + public event EventHandler DataLoaded { + add { + _dataLoadedEventHandler = (EventHandler)Delegate.Combine(_dataLoadedEventHandler, value); + } + remove { + _dataLoadedEventHandler = (EventHandler)Delegate.Remove(_dataLoadedEventHandler, value); + } + } + + /// + /// Raised when there is an error during data loading. + /// + public event EventHandler Error { + add { + _errorEventHandler = (EventHandler)Delegate.Combine(_errorEventHandler, value); + } + remove { + _errorEventHandler = (EventHandler)Delegate.Remove(_errorEventHandler, value); + } + } + + /// + /// Raised when data is starting to be loaded by the data source. + /// + public event EventHandler LoadingData { + add { + _loadingDataEventHandler = (EventHandler)Delegate.Combine(_loadingDataEventHandler, value); + } + remove { + _loadingDataEventHandler = (EventHandler)Delegate.Remove(_loadingDataEventHandler, value); + } + } + + /// + /// Clears the currently loaded data. + /// + public void ClearData() { + Data = null; + } + + /// + /// Notifies the data source to load data. + /// + public void LoadData() { + if (_dataLoadTimer.IsEnabled) { + _dataLoadTimer.Stop(); + } + + _dataLoadTimer.Start(); + } + + /// + /// Performs the work needed to actually load the data from the underlying source + /// represented by this data source. + /// + /// Whether to try and retrieve the estimated total count. + /// Whether the loading has been canceled. + /// The estimate total count of items in the underlying source. -1 if not available. + /// The data represented by an IEnumerable or an Async of IEnumerble. + protected abstract object LoadDataCore(bool retrieveEstimatedTotalCount, out bool canceled, out int estimatedTotalCount); + + private void LoadDataNow() { + if (_loadingDataEventHandler != null) { + CancelEventArgs ce = new CancelEventArgs(); + _loadingDataEventHandler(this, ce); + + if (ce.Cancel) { + return; + } + } + + Async currentAsyncData = AsyncData; + if (currentAsyncData != null) { + AsyncData = null; + IsLoadingAsyncData = false; + if (currentAsyncData.CanCancel) { + currentAsyncData.Cancel(); + } + } + + bool canceled; + int estimatedTotalCount; + + // TODO: Pass in whether we care about estimated total count or not + + object data = LoadDataCore(/* retrieveEstimatedTotalCount */ false, out canceled, out estimatedTotalCount); + + if (canceled) { + return; + } + + if (data is Async) { + Async asyncData = (Async)data; + asyncData.Completed += OnAsyncDataLoaded; + if (String.IsNullOrEmpty(asyncData.Message)) { + asyncData.Message = DataLoadStatusText; + } + + IsLoadingAsyncData = true; + AsyncData = asyncData; + } + else { + IEnumerable enumerableData = data as IEnumerable; + if ((enumerableData == null) && (data != null)) { + enumerableData = new object[] { data }; + } + + OnDataLoaded(enumerableData, estimatedTotalCount); + } + } + + private void OnAsyncDataLoaded(object sender, EventArgs e) { + Async asyncData = AsyncData; + + if (asyncData == sender) { + IEnumerable enumerableData = null; + int estimatedTotalCount = -1; + + if (asyncData.IsCanceled == false) { + if (asyncData.HasError) { + if ((asyncData.IsErrorHandled == false) && (_errorEventHandler != null)) { + ErrorEventArgs errorEventArgs = new ErrorEventArgs(asyncData.Error); + _errorEventHandler(this, errorEventArgs); + + if (errorEventArgs.IsHandled) { + asyncData.MarkErrorAsHandled(); + } + } + } + else { + Async asyncEnumerable = asyncData as Async; + if (asyncEnumerable != null) { + enumerableData = asyncEnumerable.Result; + } + else if (asyncData is Async>) { + Async> asyncEnumerableAndCount = asyncData as Async>; + enumerableData = asyncEnumerableAndCount.Result.First; + estimatedTotalCount = asyncEnumerableAndCount.Result.Second; + } + else { + if (asyncData.Result != null) { + enumerableData = new object[] { asyncData.Result }; + } + } + } + } + + IsLoadingAsyncData = false; + AsyncData = null; + OnDataLoaded(enumerableData, estimatedTotalCount); + } + } + + private void OnDataLoaded(IEnumerable data, int estimatedTotalCount) { + Data = data; + if (data != null) { + foreach (object o in data) { + DataItem = o; + break; + } + } + + // TODO: Do something with estimatedTotalCount + + if (_dataLoadedEventHandler != null) { + _dataLoadedEventHandler(this, EventArgs.Empty); + } + + if (_refreshTimer.Interval != TimeSpan.Zero) { + _refreshTimer.Start(); + } + } + + /// + /// Performs initialization work once the control has been loaded. + /// + protected virtual void OnLoaded() { + if (_autoLoadData) { + LoadDataNow(); + } + } + + private void OnLoaded(object sender, EventArgs e) { + _loadDataCommand = new DelegateCommand(LoadData); + Resources.Add("LoadDataCommand", _loadDataCommand); + + Dispatcher.BeginInvoke(delegate() { + OnLoaded(); + }); + } + + private void OnDataLoadTimerTick(object sender, EventArgs e) { + _dataLoadTimer.Stop(); + LoadDataNow(); + } + + private void OnRefreshTimerTick(object sender, EventArgs e) { + _refreshTimer.Stop(); + + if (IsLoadingAsyncData == false) { + _dataLoadTimer.Stop(); + LoadDataNow(); + } + } + + /// + /// Raises the Error event. + /// + /// The error message. + /// Any exception associated with the error. + protected void RaiseError(string message, Exception e) { + Exception error = new Exception(message, e); + bool handled = false; + + if (_errorEventHandler != null) { + ErrorEventArgs errorEventArgs = new ErrorEventArgs(error); + + _errorEventHandler(this, errorEventArgs); + handled = errorEventArgs.IsHandled; + } + + if (handled == false) { + throw error; + } + } + + #region Implementation of IAsyncControl + Async IAsyncControl.AsyncActivity { + get { + return AsyncData; + } + } + + event EventHandler IAsyncControl.AsyncActivityChanged { + add { + _asyncActivityChangedHandler = (EventHandler)Delegate.Combine(_asyncActivityChangedHandler, value); + } + remove { + _asyncActivityChangedHandler = (EventHandler)Delegate.Remove(_asyncActivityChangedHandler, value); + } + } + #endregion + } +} diff --git a/src/Client/Core/Data/ObjectDataSource.cs b/src/Client/Core/Data/ObjectDataSource.cs new file mode 100644 index 0000000..76db883 --- /dev/null +++ b/src/Client/Core/Data/ObjectDataSource.cs @@ -0,0 +1,122 @@ +// ObjectDataSource.cs +// Copyright (c) Nikhil Kothari, 2008. All Rights Reserved. +// http://www.nikhilk.net +// +// Silverlight.FX is an application framework for building RIAs with Silverlight. +// This project is licensed under the BSD license. See the accompanying License.txt +// file for more information. +// For updated project information please visit http://projects.nikhilk.net/SilverlightFX. +// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Markup; + +namespace SilverlightFX.Data { + + /// + /// An object that provides data to controls within the user interface. It relies + /// on the associated view model to perform data loading. + /// + public class ObjectDataSource : DataSource { + + private string _queryName; + private ParameterCollection _queryParameters; + + /// + /// Initializes an instance of an ObjectDataSource. + /// + public ObjectDataSource() { + _queryParameters = new ParameterCollection(); + _queryParameters.ParametersChanged += OnQueryParametersChanged; + } + + /// + /// The name of the method implemented by the view model that is invoked + /// to load the data. + /// + public string QueryName { + get { + return _queryName ?? String.Empty; + } + set { + _queryName = value; + } + } + + /// + /// The list of parameters used in invoking the query method. + /// + public ParameterCollection QueryParameters { + get { + return _queryParameters; + } + } + + /// + protected override object LoadDataCore(bool retrieveEstimatedTotalCount, out bool canceled, out int estimatedTotalCount) { + canceled = false; + estimatedTotalCount = -1; + + if (QueryName.Length == 0) { + return null; + } + + object model = View.GetModel(this); + if (model == null) { + return null; + } + + MethodInfo queryMethod = null; + try { + queryMethod = model.GetType().GetMethod(QueryName); + } + catch (Exception e) { + RaiseError("Could not find a method named '" + QueryName + "' on the associated model.", e); + return null; + } + + if (queryMethod.GetParameters().Length != QueryParameters.Count) { + RaiseError("There is a mismatch in paramters on '" + QueryName + "' and the QueryParameters collection.", null); + return null; + } + + object[] parameterValues = null; + int outParameterIndex = -1; + if (queryMethod.GetParameters().Length != 0) { + bool ignoreParameters; + + parameterValues = _queryParameters.GetParameterValues(queryMethod, /* honorIgnoredValues */ true, + out ignoreParameters, + out outParameterIndex); + if (ignoreParameters) { + canceled = true; + return null; + } + } + + object queryResult = queryMethod.Invoke(model, parameterValues); + if ((outParameterIndex != -1) && (parameterValues[outParameterIndex] is int)) { + estimatedTotalCount = (int)parameterValues[outParameterIndex]; + } + + return queryResult; + } + + /// + protected override void OnLoaded() { + _queryParameters.Initialize(this); + base.OnLoaded(); + } + + private void OnQueryParametersChanged(object sender, EventArgs e) { + if (AutoLoadData) { + LoadData(); + } + } + } +} diff --git a/src/Client/Core/Themes/generic.xaml b/src/Client/Core/Themes/generic.xaml index 7ba5a7b..61308a9 100644 --- a/src/Client/Core/Themes/generic.xaml +++ b/src/Client/Core/Themes/generic.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" xmlns:fxui="clr-namespace:SilverlightFX.UserInterface;assembly=SilverlightFX" + xmlns:fxdata="clr-namespace:SilverlightFX.Data;assembly=SilverlightFX" xmlns:fxnav="clr-namespace:SilverlightFX.UserInterface.Navigation;assembly=SilverlightFX"> @@ -36,6 +37,16 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +