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/Experiments/DataSourcePage.xaml.cs b/samples/Experiments/DataSourcePage.xaml.cs
new file mode 100644
index 0000000..9ccb319
--- /dev/null
+++ b/samples/Experiments/DataSourcePage.xaml.cs
@@ -0,0 +1,50 @@
+// DataSourcePage.xaml.cs
+//
+
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Linq;
+using SilverlightFX.UserInterface;
+
+namespace Experiments {
+
+ public class DataItem {
+
+ public string Name { get; set; }
+
+ public int Value { get; set; }
+
+ public override string ToString() {
+ return Name + " (" + Value + ")";
+ }
+ }
+
+ public class DataViewModel : ViewModel {
+
+ private List Items = new List() {
+ new DataItem() { Name = "Foo" },
+ new DataItem() { Name = "FooBar" },
+ new DataItem() { Name = "Baz" },
+ new DataItem() { Name = "FooBarBaz" }
+ };
+
+ public IEnumerable GetItems(string prefix) {
+ List items = Items.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
+ foreach (DataItem dataItem in items) {
+ dataItem.Value++;
+ }
+
+ return items;
+ }
+ }
+
+ public partial class DataSourcePage : ViewUserControl {
+
+ public DataSourcePage() {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/samples/Experiments/Experiments.csproj b/samples/Experiments/Experiments.csproj
index 4d5473c..34fa782 100644
--- a/samples/Experiments/Experiments.csproj
+++ b/samples/Experiments/Experiments.csproj
@@ -73,6 +73,12 @@
BouncePage.xaml
+
+ ActivityPage.xaml
+
+
+ DataSourcePage.xaml
+
NumberEditForm.xaml
@@ -158,6 +164,14 @@
Designer
MSBuild:Compile
+
+ MSBuild:MarkupCompilePass1
+ Designer
+
+
+ MSBuild:MarkupCompilePass1
+ Designer
+
MSBuild:MarkupCompilePass1
Designer
diff --git a/samples/Experiments/ViewModelPage.xaml b/samples/Experiments/ViewModelPage.xaml
index 75bf52b..67be324 100644
--- a/samples/Experiments/ViewModelPage.xaml
+++ b/samples/Experiments/ViewModelPage.xaml
@@ -16,9 +16,7 @@
Content="Initialize">
-
-
-
+
diff --git a/samples/Samples.sln b/samples/Samples.sln
index 60df508..b1a3dba 100644
--- a/samples/Samples.sln
+++ b/samples/Samples.sln
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmazonStore", "AmazonStore\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "News", "News\News.csproj", "{750026A4-3EA6-4D5F-B6DB-EBECDD3A8D3A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwitFaves", "TwitFaves\TwitFaves.csproj", "{DB259CE6-96A8-4F59-A0D5-A3F6815F4732}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -77,6 +79,10 @@ Global
{750026A4-3EA6-4D5F-B6DB-EBECDD3A8D3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{750026A4-3EA6-4D5F-B6DB-EBECDD3A8D3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{750026A4-3EA6-4D5F-B6DB-EBECDD3A8D3A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB259CE6-96A8-4F59-A0D5-A3F6815F4732}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB259CE6-96A8-4F59-A0D5-A3F6815F4732}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB259CE6-96A8-4F59-A0D5-A3F6815F4732}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB259CE6-96A8-4F59-A0D5-A3F6815F4732}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -92,5 +98,6 @@ Global
{6F2D06F0-B548-4D89-8628-A7EB3BE4A8BE} = {38EE0482-5653-49CE-9C94-EBF490218D23}
{A8C08DAF-477C-4CEB-9AA8-21C412A06452} = {38EE0482-5653-49CE-9C94-EBF490218D23}
{750026A4-3EA6-4D5F-B6DB-EBECDD3A8D3A} = {38EE0482-5653-49CE-9C94-EBF490218D23}
+ {DB259CE6-96A8-4F59-A0D5-A3F6815F4732} = {38EE0482-5653-49CE-9C94-EBF490218D23}
EndGlobalSection
EndGlobal
diff --git a/samples/TwitFaves/App.xaml b/samples/TwitFaves/App.xaml
new file mode 100644
index 0000000..dd7d582
--- /dev/null
+++ b/samples/TwitFaves/App.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/TwitFaves/App.xaml.cs b/samples/TwitFaves/App.xaml.cs
new file mode 100644
index 0000000..c682255
--- /dev/null
+++ b/samples/TwitFaves/App.xaml.cs
@@ -0,0 +1,15 @@
+// App.xaml.cs
+//
+
+using System;
+using System.Windows;
+
+namespace TwitFaves {
+
+ public partial class App : Application {
+
+ public App() {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/samples/TwitFaves/Assets/Bird.png b/samples/TwitFaves/Assets/Bird.png
new file mode 100644
index 0000000..c95693e
Binary files /dev/null and b/samples/TwitFaves/Assets/Bird.png differ
diff --git a/samples/TwitFaves/Assets/Clouds.jpg b/samples/TwitFaves/Assets/Clouds.jpg
new file mode 100644
index 0000000..1f27f33
Binary files /dev/null and b/samples/TwitFaves/Assets/Clouds.jpg differ
diff --git a/samples/TwitFaves/Assets/Logo.png b/samples/TwitFaves/Assets/Logo.png
new file mode 100644
index 0000000..45da117
Binary files /dev/null and b/samples/TwitFaves/Assets/Logo.png differ
diff --git a/samples/TwitFaves/Assets/Theme.xaml b/samples/TwitFaves/Assets/Theme.xaml
new file mode 100644
index 0000000..4c7460e
--- /dev/null
+++ b/samples/TwitFaves/Assets/Theme.xaml
@@ -0,0 +1,703 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/TwitFaves/Data/ITwitterService.cs b/samples/TwitFaves/Data/ITwitterService.cs
new file mode 100644
index 0000000..0f9b037
--- /dev/null
+++ b/samples/TwitFaves/Data/ITwitterService.cs
@@ -0,0 +1,15 @@
+// ITwitterService.cs
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace TwitFaves.Data {
+
+ public interface ITwitterService {
+
+ void GetProfile(string userName, Action profileCallback);
+
+ void GetTweets(string userName, Action> tweetsCallback);
+ }
+}
diff --git a/samples/TwitFaves/Data/LinqExtensions.cs b/samples/TwitFaves/Data/LinqExtensions.cs
new file mode 100644
index 0000000..488b52b
--- /dev/null
+++ b/samples/TwitFaves/Data/LinqExtensions.cs
@@ -0,0 +1,73 @@
+// LinqExtensions.cs
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace TwitFaves.Data {
+
+ public class Group : IGrouping {
+
+ private TKey _key;
+ private List _elements;
+
+ public Group(TElement element, TKey key) {
+ _key = key;
+ _elements = new List() { element };
+ }
+
+ public TKey Key {
+ get {
+ return _key;
+ }
+ }
+
+ internal void AddElement(TElement element) {
+ _elements.Add(element);
+ }
+
+ #region IEnumerable Members
+ IEnumerator IEnumerable.GetEnumerator() {
+ return ((IEnumerable)this).GetEnumerator();
+ }
+ #endregion
+
+ #region IEnumerable Members
+ IEnumerator IEnumerable.GetEnumerator() {
+ return _elements.GetEnumerator();
+ }
+ #endregion
+ }
+
+ public static class LinqExtensions {
+
+ public static IEnumerable GroupByContiguous(this IQueryable source, Expression> keySelector, IEqualityComparer keyComparer, Expression> groupCreator) where TGroup : Group {
+ Func keyFunction = keySelector.Compile();
+ Func groupFunction = groupCreator.Compile();
+
+ TGroup currentGroup = null;
+ foreach (TSource sourceElement in source) {
+ TKey key = keyFunction(sourceElement);
+
+ if ((currentGroup != null) && (keyComparer.Equals(currentGroup.Key, key) == false)) {
+ yield return currentGroup;
+ currentGroup = null;
+ }
+
+ if (currentGroup == null) {
+ currentGroup = groupFunction(sourceElement, key);
+ }
+ else {
+ currentGroup.AddElement(sourceElement);
+ }
+ }
+
+ if (currentGroup != null) {
+ yield return currentGroup;
+ }
+ }
+ }
+}
diff --git a/samples/TwitFaves/Data/Profile.cs b/samples/TwitFaves/Data/Profile.cs
new file mode 100644
index 0000000..7e054e2
--- /dev/null
+++ b/samples/TwitFaves/Data/Profile.cs
@@ -0,0 +1,45 @@
+// Profile.cs
+//
+
+using System;
+
+namespace TwitFaves.Data {
+
+ public class Profile {
+
+ public string ImageUrl {
+ get;
+ set;
+ }
+
+ public int Friends {
+ get;
+ set;
+ }
+
+ public int Followers {
+ get;
+ set;
+ }
+
+ public int Updates {
+ get;
+ set;
+ }
+
+ public string Name {
+ get;
+ set;
+ }
+
+ public string ScreenName {
+ get;
+ set;
+ }
+
+ public string Status {
+ get;
+ set;
+ }
+ }
+}
diff --git a/samples/TwitFaves/Data/Tweet.cs b/samples/TwitFaves/Data/Tweet.cs
new file mode 100644
index 0000000..ef2d579
--- /dev/null
+++ b/samples/TwitFaves/Data/Tweet.cs
@@ -0,0 +1,30 @@
+// Tweet.cs
+//
+
+using System;
+
+namespace TwitFaves.Data {
+
+ public class Tweet {
+
+ public DateTime Date {
+ get;
+ set;
+ }
+
+ public string Text {
+ get;
+ set;
+ }
+
+ public string ScreenName {
+ get;
+ set;
+ }
+
+ public string ImageUrl {
+ get;
+ set;
+ }
+ }
+}
diff --git a/samples/TwitFaves/Data/TweetGroup.cs b/samples/TwitFaves/Data/TweetGroup.cs
new file mode 100644
index 0000000..7507d3b
--- /dev/null
+++ b/samples/TwitFaves/Data/TweetGroup.cs
@@ -0,0 +1,59 @@
+// Tweet.cs
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace TwitFaves.Data {
+
+ public class TweetGroup : Group {
+
+ private string _imageUrl;
+
+ public TweetGroup(Tweet tweet, int daysOld)
+ : base(tweet, daysOld) {
+ _imageUrl = tweet.ImageUrl;
+ }
+
+ public string GroupName {
+ get {
+ if (DaysOld == 0) {
+ return "Today";
+ }
+ if (DaysOld == 1) {
+ return "Yesterday";
+ }
+ if (DaysOld <= 7) {
+ return "This Week";
+ }
+ return "Older";
+ }
+ }
+
+ public int DaysOld {
+ get {
+ return Key;
+ }
+ }
+
+ public IEnumerable Tweets {
+ get {
+ return this;
+ }
+ }
+
+ public static int GetDaysGroupValue(Tweet tweet) {
+ int days = (DateTime.UtcNow - tweet.Date).Days;
+ if (days <= 0) {
+ return 0;
+ }
+ if (days == 1) {
+ return days;
+ }
+ if (days <= 7) {
+ return 7;
+ }
+ return Int32.MaxValue;
+ }
+ }
+}
diff --git a/samples/TwitFaves/Data/TwitterService.cs b/samples/TwitFaves/Data/TwitterService.cs
new file mode 100644
index 0000000..b3b3315
--- /dev/null
+++ b/samples/TwitFaves/Data/TwitterService.cs
@@ -0,0 +1,122 @@
+// TwitterService.cs
+//
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Json;
+using System.Linq;
+using System.Net;
+
+namespace TwitFaves.Data {
+
+ [Service(typeof(ITwitterService))]
+ public sealed class TwitterService : ITwitterService {
+
+ private const string ProfileUriFormat = "http://pipes.yahooapis.com/pipes/pipe.run?_id=33459206c20c11fadeeb777d88e6d54d&_render=json&userName={0}&r={1}";
+ private const string TweetsUriFormat = "http://pipes.yahooapis.com/pipes/pipe.run?_id=b9d8434a09e96ff3a46c7466e37b1298&_render=json&userName={0}&r={1}";
+
+ public void GetProfile(string userName, Action profileCallback) {
+ WebClient webClient = new WebClient();
+ webClient.DownloadStringCompleted += delegate(object sender, DownloadStringCompletedEventArgs e) {
+ Profile profile = null;
+
+ if (e.Error == null) {
+ try {
+ JsonValue json = JsonValue.Parse(e.Result);
+ JsonValue jsonProfile = json["value"]["items"][0];
+
+ profile = new Profile();
+ profile.Name = jsonProfile["name"];
+ profile.ScreenName = jsonProfile["screen_name"];
+ profile.ImageUrl = ((string)jsonProfile["profile_image_url"]).Replace("_normal", "_bigger");
+ profile.Status = jsonProfile["status"]["text"];
+ profile.Friends = Int32.Parse(jsonProfile["friends_count"]);
+ profile.Followers = Int32.Parse(jsonProfile["followers_count"]);
+ profile.Updates = Int32.Parse(jsonProfile["statuses_count"]);
+ }
+ catch {
+ profile = null;
+ }
+ }
+
+ profileCallback(profile);
+ };
+
+ Uri requestUri = new Uri(String.Format(ProfileUriFormat, userName, new Random().Next()), UriKind.Absolute);
+ webClient.DownloadStringAsync(requestUri);
+ }
+
+ public void GetTweets(string userName, Action> tweetsCallback) {
+ WebClient webClient = new WebClient();
+ webClient.DownloadStringCompleted += delegate(object sender, DownloadStringCompletedEventArgs e) {
+ List tweets = null;
+
+ if (e.Error == null) {
+ try {
+ tweets = new List();
+
+ JsonValue json = JsonValue.Parse(e.Result);
+ JsonArray jsonTweets = (JsonArray)json["value"]["items"];
+
+ foreach (JsonValue jsonTweet in jsonTweets) {
+ JsonValue jsonUser = jsonTweet["user"];
+
+ Tweet tweet = new Tweet();
+ tweet.ScreenName = jsonUser["screen_name"];
+ tweet.ImageUrl = ((string)jsonUser["profile_image_url"]).Replace("_normal", "_bigger");
+ tweet.Text = ProcessHashTags(jsonTweet["text"]);
+ tweet.Date = ParseDateTime(jsonTweet["created_at"]);
+
+ tweets.Add(tweet);
+ }
+ }
+ catch {
+ tweets = null;
+ }
+ }
+
+ tweetsCallback(tweets);
+ };
+
+ Uri requestUri = new Uri(String.Format(TweetsUriFormat, userName, new Random().Next()), UriKind.Absolute);
+ webClient.DownloadStringAsync(requestUri);
+ }
+
+ private static DateTime ParseDateTime(string date) {
+ string dayOfWeek = date.Substring(0, 3).Trim();
+ string month = date.Substring(4, 3).Trim();
+ string dayInMonth = date.Substring(8, 2).Trim();
+ string time = date.Substring(11, 9).Trim();
+ string offset = date.Substring(20, 5).Trim();
+ string year = date.Substring(25, 5).Trim();
+
+ date = String.Format("{0}-{1}-{2} {3}", dayInMonth, month, year, time);
+ return DateTime.SpecifyKind(DateTime.Parse(date), DateTimeKind.Utc);
+ }
+
+ private static string ProcessHashTags(string text) {
+ int hashTagIndex = 0;
+
+ while ((hashTagIndex >= 0) && (hashTagIndex < text.Length)) {
+ hashTagIndex = text.IndexOf('#', hashTagIndex);
+ if (hashTagIndex >= 0) {
+ int endHashTagIndex = text.IndexOf(' ', hashTagIndex);
+ if (endHashTagIndex < 0) {
+ endHashTagIndex = text.Length;
+ }
+
+ string hashTag = text.Substring(hashTagIndex, endHashTagIndex - hashTagIndex);
+ string link = "http://twitter.com/search?q=%23" + hashTag.Substring(1) + "|" + hashTag;
+
+ text = text.Replace(hashTag, link);
+
+ hashTagIndex += link.Length;
+ }
+ }
+
+ return text;
+ }
+ }
+}
diff --git a/samples/TwitFaves/MainWindow.xaml b/samples/TwitFaves/MainWindow.xaml
new file mode 100644
index 0000000..c87041e
--- /dev/null
+++ b/samples/TwitFaves/MainWindow.xaml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Client/Core/UserInterface/Actions/InvokeMethod.cs b/src/Client/Core/UserInterface/Actions/InvokeMethod.cs
index aad171a..c699b18 100644
--- a/src/Client/Core/UserInterface/Actions/InvokeMethod.cs
+++ b/src/Client/Core/UserInterface/Actions/InvokeMethod.cs
@@ -25,6 +25,7 @@ public sealed class InvokeMethod : InvokeMemberAction {
private string _methodName;
private ParameterCollection _parameters;
+ private bool _parametersInitialized;
///
/// Gets or sets the name of the method to invoke when this action is triggered.
@@ -70,12 +71,12 @@ public sealed class InvokeMethod : InvokeMemberAction {
object[] parameters = null;
if ((_parameters != null) && (_parameters.Count != 0)) {
- parameters = new object[_parameters.Count];
-
- for (int i = 0; i < _parameters.Count; i++) {
- object value = _parameters[i].GetValue(AssociatedObject);
- parameters[i] = Convert.ChangeType(value, targetParameters[i].ParameterType, CultureInfo.CurrentCulture);
+ if (_parametersInitialized == false) {
+ _parameters.Initialize(AssociatedObject);
+ _parametersInitialized = true;
}
+
+ parameters = _parameters.GetParameterValues(targetMethod);
}
targetMethod.Invoke(target, parameters);
diff --git a/src/Client/Core/UserInterface/Actions/SetProperty.cs b/src/Client/Core/UserInterface/Actions/SetProperty.cs
index a8c255e..e483f6d 100644
--- a/src/Client/Core/UserInterface/Actions/SetProperty.cs
+++ b/src/Client/Core/UserInterface/Actions/SetProperty.cs
@@ -12,6 +12,7 @@
using System.Globalization;
using System.Reflection;
using System.Windows;
+using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Markup;
@@ -24,8 +25,9 @@ namespace SilverlightFX.UserInterface.Actions {
[ContentProperty("Value")]
public sealed class SetProperty : InvokeMemberAction {
+ private Binding _valueBinding;
+ private BindingShim _binder;
private string _propertyName;
- private Parameter _value;
///
/// Gets or sets the name of the property to set when this action is triggered.
@@ -40,22 +42,35 @@ public sealed class SetProperty : InvokeMemberAction {
}
///
- /// Gets or sets the Value as an ActionParameter that will supply the actual value to set.
+ /// Gets or sets the binding that is used to indicate the value that will be used
+ /// to set the property.
///
- public Parameter Value {
+ public Binding ValueBinding {
get {
- return _value;
+ return _valueBinding;
}
set {
- _value = value;
+ _valueBinding = value;
}
}
+ ///
+ protected override void OnDetach() {
+ if (_binder != null) {
+ _binder.Dispose();
+ _binder = null;
+ }
+ base.OnDetach();
+ }
+
///
protected override void InvokeAction(EventArgs e) {
if (String.IsNullOrEmpty(_propertyName)) {
throw new InvalidOperationException("The PropertyName property must be set on a SetProperty action.");
}
+ if (_valueBinding == null) {
+ throw new InvalidOperationException("The ValueBinding property must be set on a SetProperty action.");
+ }
object target = GetTarget();
if (target == null) {
@@ -67,10 +82,11 @@ public sealed class SetProperty : InvokeMemberAction {
throw new InvalidOperationException("The specified property '" + _propertyName + "' was not found on an object of type '" + target.GetType().FullName + "'");
}
- object value = AssociatedObject.DataContext;
- if (_value != null) {
- value = _value.GetValue(AssociatedObject);
+ if (_binder == null) {
+ _binder = new BindingShim(AssociatedObject, _valueBinding, null);
}
+
+ object value = _binder.Value;
if (targetProperty.PropertyType != typeof(Object)) {
value = Convert.ChangeType(value, targetProperty.PropertyType, CultureInfo.CurrentCulture);
}
diff --git a/src/Client/Core/UserInterface/ElementProperty.cs b/src/Client/Core/UserInterface/ElementProperty.cs
deleted file mode 100644
index 96302dc..0000000
--- a/src/Client/Core/UserInterface/ElementProperty.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-// ElementProperty.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.Reflection;
-using System.Windows;
-
-namespace SilverlightFX.UserInterface {
-
- ///
- /// An ActionParameter that provides access to a property off a specified element.
- ///
- public sealed class ElementProperty : Parameter {
-
- private string _elementName;
- private string _propertyName;
-
- ///
- /// Gets or sets the name of the element to retrieve the parameter value.
- ///
- public string ElementName {
- get {
- return _elementName;
- }
- set {
- _elementName = value;
- }
- }
-
- ///
- /// Gets or sets the name of the property to retrieve the parameter value.
- ///
- public string PropertyName {
- get {
- return _propertyName;
- }
- set {
- _propertyName = value;
- }
- }
-
- ///
- public override object GetValue(FrameworkElement element) {
- if (String.IsNullOrEmpty(_propertyName)) {
- throw new InvalidOperationException("The PropertyName on an ElementProperty must be specified.");
- }
-
- object source = null;
-
- if (String.IsNullOrEmpty(_elementName)) {
- source = element.DataContext;
- }
- else if (String.CompareOrdinal(_elementName, "$self") == 0) {
- source = element;
- }
- else {
- source = element.FindName(_elementName);
- }
-
- if (source == null) {
- throw new InvalidOperationException("The ElementProperty could not find the specified element to use as the source of its value.");
- }
-
- PropertyInfo sourceProperty = source.GetType().GetProperty(_propertyName);
- if (sourceProperty == null) {
- throw new InvalidOperationException("The specified property '" + _propertyName + "' was not found on an object of type '" + source.GetType().FullName + "'");
- }
-
- return sourceProperty.GetValue(source, null);
- }
- }
-}
diff --git a/src/Client/Core/_System/Presentation/BindingShim.cs b/src/Client/Core/_System/Presentation/BindingShim.cs
new file mode 100644
index 0000000..faa8c38
--- /dev/null
+++ b/src/Client/Core/_System/Presentation/BindingShim.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.Data;
+
+namespace System.Windows {
+
+ internal sealed class BindingShim : IDisposable {
+
+ private static int _instanceCount;
+
+ private FrameworkElement _element;
+ private Binding _binding;
+ private object _value;
+ private DependencyProperty _attachedDP;
+ private Action _changedHandler;
+
+ public BindingShim(FrameworkElement element, Binding binding, Action changedHandler) {
+ _element = element;
+ _binding = binding;
+
+ _instanceCount++;
+ _attachedDP = DependencyProperty.RegisterAttached("BoundParameter" + _instanceCount,
+ typeof(object), typeof(BindingShim),
+ new PropertyMetadata(OnChanged));
+
+ _element.SetBinding(_attachedDP, _binding);
+ _value = _element.GetValue(_attachedDP);
+
+ // Initialize _changedHandler after setting up the binding so we don't invoke
+ // it when the OnChanged handled gets called as a side-effect of setting the binding!
+ _changedHandler = changedHandler;
+ }
+
+ public object Value {
+ get {
+ return _value;
+ }
+ }
+
+ public void Dispose() {
+ _element.ClearValue(_attachedDP);
+ _element = null;
+ }
+
+ private void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
+ _value = e.NewValue;
+ if (_changedHandler != null) {
+ _changedHandler();
+ }
+ }
+ }
+}
diff --git a/src/Client/Core/_System/Presentation/Data/IDataSource.cs b/src/Client/Core/_System/Presentation/Data/IDataSource.cs
deleted file mode 100644
index 478cc10..0000000
--- a/src/Client/Core/_System/Presentation/Data/IDataSource.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// DataboundControl.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.ComponentModel;
-
-namespace System.Windows.Data {
-
- ///
- /// Represents an object or component that is able to provide
- /// data source functionality, i.e. a collection of items.
- ///
- public interface IDataSource : INotifyPropertyChanged {
-
- ///
- /// Gets the collection of items contained within underlying collection.
- ///
- object Data {
- get;
- }
- }
-}
diff --git a/src/Client/Core/_System/Presentation/Parameter.cs b/src/Client/Core/_System/Presentation/Parameter.cs
index 6a648dc..dbee463 100644
--- a/src/Client/Core/_System/Presentation/Parameter.cs
+++ b/src/Client/Core/_System/Presentation/Parameter.cs
@@ -9,6 +9,7 @@
//
using System;
+using System.Windows.Data;
namespace System.Windows {
@@ -17,11 +18,84 @@ namespace System.Windows {
///
public abstract class Parameter {
+ private ParameterCollection _owner;
+ private string _parameterName;
+ private object _ignoredValue;
+
+ ///
+ /// Gets the element that owns this parameter.
+ ///
+ protected FrameworkElement AssociatedElement {
+ get {
+ if (_owner == null) {
+ return null;
+ }
+ return _owner.AssociatedElement;
+ }
+ }
+
+ ///
+ /// Gets or sets the value to be considered as the ignored or not-set
+ /// value.
+ ///
+ public object IgnoredValue {
+ get {
+ return _ignoredValue;
+ }
+ set {
+ _ignoredValue = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the name of the paramter.
+ ///
+ public string ParameterName {
+ get {
+ return _parameterName ?? String.Empty;
+ }
+ set {
+ if (_owner != null) {
+ throw new InvalidOperationException("ParameterName can only be set before it is added to a ParameterCollection.");
+ }
+ _parameterName = value;
+ }
+ }
+
+ ///
+ /// Activates the parameter with the specified element as visual tree context.
+ ///
+ protected abstract void Activate();
+
+ ///
+ /// Deactivates a parameter, when it is no longer in use.
+ ///
+ protected abstract void Deactivate();
+
///
/// Gets the value of the parameter.
///
- /// The element that the action is associated with.
/// The value of the parameter.
- public abstract object GetValue(FrameworkElement element);
+ public abstract object GetValue();
+
+ ///
+ /// Notifies that this parameter's value has changed.
+ ///
+ protected void OnValueChanged() {
+ if (_owner != null) {
+ _owner.OnParameterChanged(this);
+ }
+ }
+
+ internal void SetOwner(ParameterCollection owner) {
+ if (owner != null) {
+ _owner = owner;
+ Activate();
+ }
+ else {
+ Deactivate();
+ _owner = null;
+ }
+ }
}
}
diff --git a/src/Client/Core/_System/Presentation/ParameterCollection.cs b/src/Client/Core/_System/Presentation/ParameterCollection.cs
index 27a5856..0ba9e2f 100644
--- a/src/Client/Core/_System/Presentation/ParameterCollection.cs
+++ b/src/Client/Core/_System/Presentation/ParameterCollection.cs
@@ -11,6 +11,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.Reflection;
namespace System.Windows {
@@ -18,5 +21,155 @@ namespace System.Windows {
/// Represents a collection of Parameter objects.
///
public sealed class ParameterCollection : ObservableCollection {
+
+ private FrameworkElement _owner;
+ private Dictionary _parameterMap;
+ private EventHandler _parametersChangedHandler;
+
+ internal FrameworkElement AssociatedElement {
+ get {
+ return _owner;
+ }
+ }
+
+ ///
+ /// Raised when the list of parameters changes or the value of a parameter has changed.
+ ///
+ public event EventHandler ParametersChanged {
+ add {
+ _parametersChangedHandler = (EventHandler)Delegate.Combine(_parametersChangedHandler, value);
+ }
+ remove {
+ _parametersChangedHandler = (EventHandler)Delegate.Remove(_parametersChangedHandler, value);
+ }
+ }
+
+ ///
+ /// Gets the list of parameter values corresponding to the specified method signature.
+ ///
+ /// The method whose parameters are to be looked up.
+ /// The list of parameter values for the specified method.
+ public object[] GetParameterValues(MethodInfo method) {
+ bool dummyHasIgnoredValues;
+ int dummyOutParameterIndex;
+ return GetParameterValues(method, /* honorIgnoreValues */ false,
+ out dummyHasIgnoredValues, out dummyOutParameterIndex);
+ }
+
+ ///
+ /// Gets the list of parameter values corresponding to the specified method signature.
+ ///
+ /// The method whose parameters are to be looked up.
+ /// Whether to compare values against the Parameter's IgnoredValue property.
+ /// Whether any of the returned parameter values matches an ignored value.
+ /// The index of the last out parameter if one exists.
+ /// The list of parameter values for the specified method.
+ public object[] GetParameterValues(MethodInfo method, bool honorIgnoreValues, out bool hasIgnoredValues, out int outParameterIndex) {
+ hasIgnoredValues = false;
+ outParameterIndex = -1;
+
+ if (_parameterMap == null) {
+ // Not yet initialized...
+ return null;
+ }
+
+ ParameterInfo[] parameterInfos = method.GetParameters();
+ if (parameterInfos.Length != Count) {
+ return null;
+ }
+
+ List