How to provide cloud-based, JSON data to Windows 8 Metro Grid Applications – Part 3
Exercise 1: Modifying our Windows 8 Metro Grid application to consume JSON data across http
Download the Windows Azure SDK and Tools - To do these posts, absolutely necessary |
How to provide cloud-based, JSON data to Windows 8 Metro Grid Applications - Part 1 | https://blogs.msdn.com/b/brunoterkaly/archive/2012/06/12/how-to-provide-cloud-based-json-data-to-windows-8-metro-grid-applications.aspx |
How to provide cloud-based, JSON data to Windows 8 Metro Grid Applications - Part 2 | https://blogs.msdn.com/b/brunoterkaly/archive/2012/06/14/how-to-provide-cloud-based-json-data-to-windows-8-metro-grid-applications-part-2.aspx |
How to provide cloud-based, JSON data to Windows 8 Metro Grid Applications - Part 3 | https://blogs.msdn.com/b/brunoterkaly/archive/2012/06/15/how-to-provide-cloud-based-json-data-to-windows-8-metro-grid-applications-part-3.aspx |
How to provide cloud-based, JSON data to Windows 8 Metro Grid Applications - Part 4 | https://blogs.msdn.com/b/brunoterkaly/archive/2012/06/17/how-to-provide-cloud-based-json-data-to-windows-8-metro-grid-applications-part-3-windows-azure-cloud.aspx |
Source Code |
This post is broken down into 3 exercises.
Exercise 1 | Add a new class, called MySampleDataSource. This will replace SampleDataSource.
|
Exercise 2 | Modify App.xaml.cs to invoke code in MySampleDataSource. This code will retrieve JSON data across http.
|
Exercise 3 | Modify the user interface (XAML) code to bind correctly to the new data source.
|
Exercise 1: Add a new class, called MySampleDataSource.
This will replace SampleDataSource. SampleDataSource has hard-code values to supply data to the user interface. MySampleDataSource will make asynchronous http calls and parse JSON data for the user interface. We will also add MyStack.cs to help with assigning items to groups.
Exercise 1: Task 1 - Introducing MySampleDataSource.cs and MyStack.cs
The next step is to add MySampleDataSource.cs module below.
This is a re-write of SampleDataSource.cs (provided by VS 2012 RC). Not many changes. I changed some of the base data elements very slightly. I also added a method to do the asynchronous http request for JSON data.
I have also added a MyStack.cs module. This is used to group items into groups. The code expects that the JSON data is ordered correctly when it is sent by the server (Windows Azure Cloud back-end).
It should be sorted by group, then by item.
There are two source files we will add.
File 1 | MySampleDataSource.cs | It will allow our Windows 8 Metro Grid app make JSON requests |
File 2 | MyStack.cs | It will allow us to group items into groups. For example, adding individual motorcycles into groups like Sport Bikes and Exotic Bikes. |
- Go back to Visual Studio 2012 RC.
- If not visible, view "Solution Explorer."
- Click on the "View" menu and select "Solution Explorer."
- Right mouse click on the "DataModel" folder and add a new class.
- Call that new class "MySampleDataSource.cs."
- Paste in the code from the snippet below. See "Code snippet 1 - MySampleDataSource.cs."
- Right mouse click on the "DataModel" folder and add a new class.
- Call that new class "MyStack.cs."
- Paste in the code from the snippet below. See "Code snippet 2 - MyStack.cs."
- Solution Explorer should look like this:
- After you’ve pasted the code in for MyStack.cs and MySampleDataSource.cs, you can try compiling your application.
- Notice that the 3 new classes replicate the structure and behavior of the built in types.
- You can see these objects after you’ve added the two modules.
- You will then rebuild your solution and switch to “Class View.”
- The red box is what we will add.
- MySampleDataCommon, MySampleDataGroup, MySampleDataItem
- The objects in the green box were provided by Visual Studio.
- SampleDataCommon, SampleDataGroup, SampleDataItem
- The red box is what we will add.
- We will add MySampleSource.cs.
- MySampleSource.cs will include the follow key classes that allow us to asynchronous call into a JSON data store using http.
- We will also add MyStack.cs
- This class will help manage individual items and their groups.
Some additional notes regarding the code
- We added some using statements.
- using System.Net.Http;
- Supports http web requests
- using Windows.Data.Json;
- Provides useful parsing functions
- using System.Threading.Tasks;
- Support for threads
- using System.Net.Http;
- I slightly changed the name conventions.
- From SampleData to MySampleData
- Not a good idea for production code, but good for a quick hack.
- From SampleData to MySampleData
- You should easily be able to see parallels between the new MySampleDataSource.cs versus the old SampleDataSource.cs.
- Changes to the base class (MySampleDataCommon)
- We parse JsonObjects in the code.
You will see code like currGroup.TryGetValue("_subTitle", out val).
?123456789101112131415161718192021222324public
MySampleDataGroup(JsonObject currGroup)
{
//
// Extract attributes from the JsonObject
//
IJsonValue val;
if
(currGroup.TryGetValue(
"_uniqueId"
,
out
val))
this
.UniqueId = val.GetString();
if
(currGroup.TryGetValue(
"_title"
,
out
val))
this
.Title = val.GetString();
if
(currGroup.TryGetValue(
"_subTitle"
,
out
val))
this
.Subtitle = val.GetString();
if
(currGroup.TryGetValue(
"_imagePath"
,
out
val))
this
.SetImage(val.GetString());
if
(currGroup.TryGetValue(
"_description"
,
out
val))
this
.Description = val.GetString();
if
(currGroup.TryGetValue(
"_content"
,
out
val))
this
.Content = val.GetString();
- This code allows you to extract strings from JSON data
- It is how we populate MySampleDataItem, and MySampleDataGroup.
- We implement a Stack object to help us group items together.
- Items belong to a group so a Stack mechanism is used to track this relationship.
Code snippet 1 - MySampleDataSource.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | using System; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using Windows.ApplicationModel.Resources.Core; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; using System.Net.Http; using Windows.Data.Json; using System.Threading.Tasks; // The data model defined by this file serves as a representative example of a strongly-typed // model that supports notification when members are added, removed, or modified. The property // names chosen coincide with data bindings in the standard item templates. // // Applications may use this model as a starting point and build on it, or discard it entirely and // replace it with something appropriate to their needs. namespace FastMotorcycle { /// <SUMMARY> /// /// This is the base class to MySampleDataGroup and MySampleDataGroup /// It provides some common data structures and some eventing mechanisms /// /// </SUMMARY> [Windows.Foundation.Metadata.WebHostHidden] public abstract class MySampleDataCommon : FastMotorcycle.Common.BindableBase { public MySampleDataCommon() { } // Constructor for the core elements of the base class. public MySampleDataCommon(String uniqueId, string title, string subTitle, string imagePath, string description, string content) { this ._uniqueId = uniqueId; this ._title = title; this ._subTitle = subTitle; this ._description = description; this ._content = content; this .SetImage(imagePath); } private string _uniqueId = string .Empty; public string UniqueId { get { return this ._uniqueId; } set { this .SetProperty( ref this ._uniqueId, value); } } private string _title = string .Empty; public string Title { get { return this ._title; } set { this .SetProperty( ref this ._title, value); } } private string _subTitle = string .Empty; public string Subtitle { get { return this ._subTitle; } set { this .SetProperty( ref this ._subTitle, value); } } private string _description = string .Empty; public string Description { get { return this ._description; } set { this .SetProperty( ref this ._description, value); } } private string _content = string .Empty; public string Content { get { return this ._content; } set { this .SetProperty( ref this ._content, value); } } private ImageSource _image = null ; private string _imagePath = null ; public string ImagePath { get { return _imagePath; } set { _imagePath = value; } } public ImageSource Image { get { if ( this ._image == null && this ._imagePath != null ) { this ._image = new BitmapImage( new Uri(MySampleDataCommon._baseUri, this ._imagePath)); } return this ._image; } set { this ._imagePath = null ; this .SetProperty( ref this ._image, value); } } // This method makes sure the image is viewable as an ImageSource public void SetImage(String path) { this ._image = null ; this ._imagePath = path; this .OnPropertyChanged( "Image" ); } } /// <SUMMARY> /// /// This is the core MySampleDataItem. It will hold information about motorcycles and the /// group to which they belong /// /// </SUMMARY> public class MySampleDataItem : MySampleDataCommon { public MySampleDataItem() { } public MySampleDataItem(String uniqueId, string title, string subTitle, string description, string imagePath, string content, MySampleDataGroup group) : base (uniqueId, title, subTitle, description, imagePath, content) { this ._group = group; } // The main constructor for an item. The code here searches for attributes // in JSON objects. It then populate relevant properties. // In this implementation, it will really hold "Fast Motorcycles" public MySampleDataItem(JsonObject currItem, MySampleDataGroup currGroup) { string uniqueId = string .Empty, title = string .Empty, subTitle = string .Empty, description = string .Empty, imagePath = string .Empty, content = string .Empty; IJsonValue val; if (currItem.TryGetValue( "_uniqueId" , out val)) uniqueId = val.GetString(); if (currItem.TryGetValue( "_title" , out val)) title = val.GetString(); if (currItem.TryGetValue( "_subTitle" , out val)) subTitle = val.GetString(); if (currItem.TryGetValue( "_imagePath" , out val)) imagePath = val.GetString(); if (currItem.TryGetValue( "_description" , out val)) description = val.GetString(); if (currItem.TryGetValue( "_content" , out val)) content = val.GetString(); // Inherited members this .UniqueId = uniqueId; this .Title = title; this .Subtitle = subTitle; this .SetImage(imagePath); this .Description = description; this .Content = content; // Additional data member (items point to their parent) this .Group = currGroup; } private MySampleDataGroup _group; public MySampleDataGroup Group { get { return this ._group; } set { this .SetProperty( ref this ._group, value); } } } /// <SUMMARY> /// /// This is the fundamental type for groups. It will be either Sport Bikes or Exotic Bikes. It will hold a /// collection of individual MySampleDataItem objects. /// /// </SUMMARY> public class MySampleDataGroup : MySampleDataCommon { public MySampleDataGroup() { } public MySampleDataGroup(String uniqueId, string title, string subTitle, string imagePath, string description, string content) : base (uniqueId, title, subTitle, imagePath, description, content) { } public MySampleDataGroup(JsonObject currGroup) { // // Extract attributes from the JsonObject // IJsonValue val; if (currGroup.TryGetValue( "_uniqueId" , out val)) this .UniqueId = val.GetString(); if (currGroup.TryGetValue( "_title" , out val)) this .Title = val.GetString(); if (currGroup.TryGetValue( "_subTitle" , out val)) this .Subtitle = val.GetString(); if (currGroup.TryGetValue( "_imagePath" , out val)) this .SetImage(val.GetString()); if (currGroup.TryGetValue( "_description" , out val)) this .Description = val.GetString(); if (currGroup.TryGetValue( "_content" , out val)) this .Content = val.GetString(); } // // MySampleDataGroup has a collection of MySampleDataItem // private ObservableCollection<MYSAMPLEDATAITEM> _items = new ObservableCollection<MYSAMPLEDATAITEM>(); public ObservableCollection<MYSAMPLEDATAITEM> Items { get { return this ._items; } } public IEnumerable<MYSAMPLEDATAITEM> TopItems { // Provides a subset of the full items collection to bind to from a GroupedItemsPage // for two reasons: GridView will not virtualize large items collections, and it // improves the user experience when browsing through groups with large numbers of // items. // // A maximum of 12 items are displayed because it results in filled grid columns // whether there are 1, 2, 3, 4, or 6 rows displayed get { return this ._items.Take(12); } } } /// <SUMMARY> /// /// This is the main container class for MySampleDataItem and MySampleDataGroup objects /// Creates a collection of groups and items with data from a an JSON/http web request. /// /// </SUMMARY> public sealed class MySampleDataSource { private static MySampleDataSource _sampleDataSource = new MySampleDataSource(); private static MyStack mystack = new MyStack(); // // This is the main entry point for all objects into the system. // Essentially, we add MySampleDataGroup objects to _allGroups. Each // MySampleDataGroup object will have a collection of MySampleDataItem objects. // private ObservableCollection<MYSAMPLEDATAGROUP> _allGroups = new ObservableCollection<MYSAMPLEDATAGROUP>(); public ObservableCollection<MYSAMPLEDATAGROUP> AllGroups { get { return this ._allGroups; } } public static IEnumerable<MYSAMPLEDATAGROUP> GetGroups( string uniqueId) { if (!uniqueId.Equals( "AllGroups" )) throw new ArgumentException( "Only 'AllGroups' is supported as a collection of groups" ); return _sampleDataSource.AllGroups; } public static MySampleDataGroup GetGroup( string uniqueId) { // Simple linear search is acceptable for small data sets var matches = _sampleDataSource.AllGroups.Where((group) => group.UniqueId.Equals(uniqueId)); if (matches.Count() == 1) return matches.First(); return null ; } public static MySampleDataItem GetItem( string uniqueId) { // Simple linear search is acceptable for small data sets var matches = _sampleDataSource.AllGroups.SelectMany(group => group.Items).Where((item) => item.UniqueId.Equals(uniqueId)); if (matches.Count() == 1) return matches.First(); return null ; } // // This is the main method that retrieves JSON data by making an asynchronous web requet. // Currently, it is calling a local emulator instance of Windows Azure. // Note the web address of "https://127.0.0.1/FastMotorcycleListService...." // public static async Task<JSONARRAY> LoadRemoteDataAsync() { // // Retrieve fastmotorcycle data from Azure // var client = new HttpClient(); client.MaxResponseContentBufferSize = 1024 * 1024; // Read up to 1 MB of data var response = await client.GetAsync( new Uri( "https://127.0.0.1:81/FastMotorcycleListService.svc/list/SampleData/1" )); var result = await response.Content.ReadAsStringAsync(); // // Parse the JSON fastmotorcycle data // var fastmotorcycles = JsonArray.Parse(result); // // Convert the JSON objects into MySampleDataItems and MySampleDataGroups // Assume that we are ordered by Group, then by Item // JsonArray array = fastmotorcycles; IJsonValue groupKey; foreach (var item in array) { var obj = item.GetObject(); if (!obj.TryGetValue( "_group" , out groupKey)) { continue ; // must have _group, skip if not } else { // // This code tracks "groups" // If a group already exists, and we have a new item to add, then add item to group // If we do find a new group, add it to our stack. The stack object is used // to add new items to a group. // var currGroup = groupKey.GetObject(); MySampleDataGroup newGroup = new MySampleDataGroup(currGroup); if (!mystack.Exists(newGroup)) { mystack.Add(newGroup); } } // // Add item to latest group // Make sure child points to its own group. // Children items point to parent (group) // MySampleDataItem newItem = new MySampleDataItem(obj, mystack.GetLatestGroup()); mystack.AddChildToLatestGroup(newItem); } // // Loop through all groups and a group to _sampleDataSource.AllGroups // This is one of the main entry points for data into the user interface. // MySampleDataGroup loadedGroup = null ; for ( int i = 0; i < mystack.stackCount; i++) { loadedGroup = mystack.GetEntry(i); // Retrieve group from bottom of stack _sampleDataSource.AllGroups.Add(loadedGroup); } return array; } public MySampleDataSource() { // Empty. Data retrieved from JSON call. } } } |
Code snippet 2 - MyStack.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FastMotorcycle { public class MyStack { public int stackCount; public MySampleDataGroup[] stack; public MyStack() { stackCount = 0; stack = new MySampleDataGroup[1000]; } internal void Add(MySampleDataGroup myNode) { stack[stackCount++] = myNode; } internal bool IsChild( int i) { return false ; // stack[i].Depth > stack[i - 1].Depth; } internal bool IsEqual( int k) { return false ; // stack[k].Depth == stack[k - 1].Depth; } internal bool IsLast( int k) { return k == stackCount; } public bool Exists(MySampleDataGroup newGroup) { if (stackCount == 0) return false ; else { return stack[stackCount - 1].UniqueId == newGroup.UniqueId; } } public void AddChildToLatestGroup(MySampleDataItem newItem) { newItem.Group = stack[stackCount - 1]; stack[stackCount - 1].Items.Add(newItem); } public MySampleDataGroup GetEntry( int i) { return stack[i]; } public MySampleDataGroup GetLatestGroup() { return stack[stackCount - 1]; } } } |
Exercise 2: Modify App.config.cs to invoke code in MySampleDataSource
There is code in MySampleDataSource that needs to be called. This code will make an asynchronous http request to retrieve JSON data.
- Open the file App.xaml.cs
- Add the code highlighted below
- Take note that we've added two lines of code here.
- We declared an object of type MySampleDataSource
- We have called LoadRemoteDataAsync() on that object
- Take note that we've added two lines of code here.
Code Snippet 3 - App.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | using FastMotorcycle.Common; using System; using System.Collections.Generic; using System.IO; using System.Linq; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; // The Grid App template is documented at https://go.microsoft.com/fwlink/?LinkId=234226 namespace FastMotorcycle { /// <SUMMARY> /// Provides application-specific behavior to supplement the default Application class. /// </SUMMARY> sealed partial class App : Application { public static MySampleDataSource sampleData = new MySampleDataSource(); /// <SUMMARY> /// Initializes the singleton Application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). /// </SUMMARY> public App() { this .InitializeComponent(); this .Suspending += OnSuspending; } /// <SUMMARY> /// Invoked when the application is launched normally by the end user. Other entry points /// will be used when the application is launched to open a specific file, to display /// search results, and so forth. /// </SUMMARY> /// <PARAM name="args">Details about the launch request and process.</PARAM> protected override async void OnLaunched(LaunchActivatedEventArgs args) { // Do not repeat app initialization when already running, just ensure that // the window is active if (args.PreviousExecutionState == ApplicationExecutionState.Running) { Window.Current.Activate(); return ; } await MySampleDataSource.LoadRemoteDataAsync(); // Create a Frame to act as the navigation context and associate it with // a SuspensionManager key var rootFrame = new Frame(); SuspensionManager.RegisterFrame(rootFrame, "AppFrame" ); if (args.PreviousExecutionState == ApplicationExecutionState.Terminated) { // Restore the saved session state only when appropriate await SuspensionManager.RestoreAsync(); } if (rootFrame.Content == null ) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter if (!rootFrame.Navigate( typeof (GroupedItemsPage), "AllGroups" )) { throw new Exception( "Failed to create initial page" ); } } // Place the frame in the current Window and ensure that it is active Window.Current.Content = rootFrame; Window.Current.Activate(); } /// <SUMMARY> /// Invoked when application execution is being suspended. Application state is saved /// without knowing whether the application will be terminated or resumed with the contents /// of memory still intact. /// </SUMMARY> /// <PARAM name="sender">The source of the suspend request.</PARAM> /// <PARAM name="e">Details about the suspend request.</PARAM> private async void OnSuspending( object sender, SuspendingEventArgs e) { var deferral = e.SuspendingOperation.GetDeferral(); await SuspensionManager.SaveAsync(); deferral.Complete(); } } } |
Exercise 3: Modify the user interface (XAML) code to bind correctly to the new data source.
Remember, we have 3 files for the user interface:
GroupDetailPage.xaml | Gives details about a group. In this case, the group can be Sport Bikes or Exotic Bikes |
GroupedItemsPage.xaml | Shows Groups and Items. This is the initial page seen by users. You can navigate to the group or to an individual item from here. |
ItemDetailPage.xaml | Provides details about and individual item. In this case, it is about an individual motorcycle. |
Modify the GroupDetailPage.xaml
- We will modify GroupDetailPage.xaml
- We'd like to bind to the "Content" property for a group.
Modify the GroupDetailPage.xaml.cs
- Open GroupDetailPage.xaml.cs
- Navigate to the method LoadState()
- Change the following line:
- var group = SampleDataSource.GetGroup((String)navigationParameter);
- Change the following line:
- Navigate to the method ItemView_ItemClick()()
- Change the following line:
- var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
- Change the following line:
- See line 42 and 57 in the code below.
GroupDetailPage.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | using System; using System.Collections.Generic; using System.IO; using System.Linq; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; // The Group Detail Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234229 namespace FastMotorcycle { /// <SUMMARY> /// A page that displays an overview of a single group, including a preview of the items /// within the group. /// </SUMMARY> public sealed partial class GroupDetailPage : FastMotorcycle.Common.LayoutAwarePage { public GroupDetailPage() { this .InitializeComponent(); } /// <SUMMARY> /// Populates the page with content passed during navigation. Any saved state is also /// provided when recreating a page from a prior session. /// </SUMMARY> /// <PARAM name="navigationParameter">The parameter value passed to /// <SEE cref="Frame.Navigate(Type, Object)" /> when this page wtas initially requested. /// </PARAM> /// <PARAM name="pageState">A dictionary of state preserved by this page during an earlier /// session. This will be null the first time a page is visited.</PARAM> protected override void LoadState(Object navigationParameter, Dictionary<STRING, Object> pageState) { // TODO: Create an appropriate data model for your problem domain to replace the sample data var group = MySampleDataSource.GetGroup((String)navigationParameter); this .DefaultViewModel[ "Group" ] = group; this .DefaultViewModel[ "Items" ] = group.Items; } /// <SUMMARY> /// Invoked when an item is clicked. /// </SUMMARY> /// <PARAM name="sender">The GridView (or ListView when the application is snapped) /// displaying the item clicked.</PARAM> /// <PARAM name="e">Event data that describes the item clicked.</PARAM> void ItemView_ItemClick( object sender, ItemClickEventArgs e) { // Navigate to the appropriate destination page, configuring the new page // by passing required information as a navigation parameter var itemId = ((MySampleDataItem)e.ClickedItem).UniqueId; this .Frame.Navigate( typeof (ItemDetailPage), itemId); } } } |
GroupedItemsPage.xaml.cs
- Modify GroupedItemsPage.xaml.cs. You will add “My” to the appropriate lines. Take note of the following lines.
- Line 41
- Line 59
- Line 72
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | using System; using System.Collections.Generic; using System.IO; using System.Linq; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; // The Grouped Items Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234231 namespace FastMotorcycle { /// <SUMMARY> /// A page that displays a grouped collection of items. /// </SUMMARY> public sealed partial class GroupedItemsPage : FastMotorcycle.Common.LayoutAwarePage { public GroupedItemsPage() { this .InitializeComponent(); } /// <SUMMARY> /// Populates the page with content passed during navigation. Any saved state is also /// provided when recreating a page from a prior session. /// </SUMMARY> /// <PARAM name="navigationParameter">The parameter value passed to /// <SEE cref="Frame.Navigate(Type, Object)" /> when this page was initially requested. /// </PARAM> /// <PARAM name="pageState">A dictionary of state preserved by this page during an earlier /// session. This will be null the first time a page is visited.</PARAM> protected override void LoadState(Object navigationParameter, Dictionary<STRING, Object> pageState) { // TODO: Create an appropriate data model for your problem domain to replace the sample data var sampleDataGroups = MySampleDataSource.GetGroups((String)navigationParameter); this .DefaultViewModel[ "Groups" ] = sampleDataGroups; } /// <SUMMARY> /// Invoked when a group header is clicked. /// </SUMMARY> /// <PARAM name="sender">The Button used as a group header for the selected group.</PARAM> /// <PARAM name="e">Event data that describes how the click was initiated.</PARAM> void Header_Click( object sender, RoutedEventArgs e) { // Determine what group the Button instance represents var group = (sender as FrameworkElement).DataContext; // Navigate to the appropriate destination page, configuring the new page // by passing required information as a navigation parameter this .Frame.Navigate( typeof (GroupDetailPage), ((MySampleDataGroup)group).UniqueId); } /// <SUMMARY> /// Invoked when an item within a group is clicked. /// </SUMMARY> /// <PARAM name="sender">The GridView (or ListView when the application is snapped) /// displaying the item clicked.</PARAM> /// <PARAM name="e">Event data that describes the item clicked.</PARAM> void ItemView_ItemClick( object sender, ItemClickEventArgs e) { // Navigate to the appropriate destination page, configuring the new page // by passing required information as a navigation parameter var itemId = ((MySampleDataItem)e.ClickedItem).UniqueId; this .Frame.Navigate( typeof (ItemDetailPage), itemId); } } } |
Code Snippet - ItemDetailPage.xaml.
- Open ItemDetailPage.xaml.
- We want to add a binding for Description
- Insert the following 3 lines on line 62, 63, 64, as seen in the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | <COMMON:LAYOUTAWAREPAGE mc:Ignorable= "d" xmlns:mc= "https://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d= "https://schemas.microsoft.com/expression/blend/2008" xmlns:common= "using:FastMotorcycle.Common" xmlns:data= "using:FastMotorcycle" xmlns:local= "using:FastMotorcycle" xmlns:x= "https://schemas.microsoft.com/winfx/2006/xaml" xmlns= "https://schemas.microsoft.com/winfx/2006/xaml/presentation" IsTabStop= "false" DataContext= "{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}" x:Class= "FastMotorcycle.ItemDetailPage" x:Name= "pageRoot" > <?XML:NAMESPACE PREFIX = "[default] https://schemas.microsoft.com/winfx/2006/xaml/presentation" NS = "https://schemas.microsoft.com/winfx/2006/xaml/presentation" /><Page.Resources> <!-- Collection of items displayed by this page --> <CollectionViewSource x:Name= "itemsViewSource" d:Source= "{Binding AllGroups[0].Items, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}" Source= "{Binding Items}" ></CollectionViewSource> </Page.Resources> <!-- This grid acts as a root panel for the page that defines two rows: * Row 0 contains the back button and page title * Row 1 contains the rest of the page layout --> <Grid DataContext= "{Binding Group}" d:DataContext= "{Binding AllGroups[0], Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}" > <Grid.RowDefinitions> <RowDefinition Height= "140" ></RowDefinition> <RowDefinition Height= "*" ></RowDefinition> </Grid.RowDefinitions> <!-- Back button and page title --> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width= "Auto" ></ColumnDefinition> <ColumnDefinition Width= "*" ></ColumnDefinition> </Grid.ColumnDefinitions> <BUTTON x:Name= "backButton" IsEnabled= "{Binding Frame.CanGoBack, ElementName=pageRoot}" Click= "GoBack" > <TextBlock Text= "{Binding Title}" x:Name= "pageTitle" Grid.Column= "1" ></TextBlock> </Grid> <!-- The remainder of the page is one large FlipView that displays details for one item at a time, allowing the user to flip through all items in the chosen group --> <FlipView tabIndex=1 Margin= "0,-3,0,0" x:Name= "flipView" ItemsSource= "{Binding Source={StaticResource itemsViewSource}}" Grid.Row= "1" AutomationProperties.Name= "Item Details" AutomationProperties.AutomationId= "ItemsFlipView" > <FlipView.ItemTemplate> <DataTemplate> <!-- UserControl chosen as the templated item because it supports visual state management Loaded/unloaded events explicitly subscribe to view state updates from the page --> <UserControl Unloaded= "StopLayoutUpdates" Loaded= "StartLayoutUpdates" > <ScrollViewer x:Name= "scrollViewer" Grid.Row= "1" > <!-- Content is allowed to flow across as many columns as needed --> <COMMON:RICHTEXTCOLUMNS Margin= "117,0,117,47" x:Name= "richTextColumns" > <RichTextBlock x:Name= "richTextBlock" Width= "560" > <Paragraph> <Run Text= "{Binding Title}" FontWeight= "Light" FontSize= "26.667" ></Run> <LineBreak></LineBreak> <LineBreak></LineBreak> <Run Text= "{Binding Subtitle}" FontWeight= "SemiBold" ></Run> </Paragraph> <Paragraph LineStackingStrategy= "MaxHeight" > <InlineUIContainer> <IMG Margin= "0,20,0,10" x:Name= "image" Source= "{Binding Image}" Stretch= "Uniform" MaxHeight= "480" > </InlineUIContainer> </Paragraph> <Paragraph> <Run Text= "{Binding Description}" FontWeight= "SemiLight" ></Run> </Paragraph> <Paragraph> <Run Text= "{Binding Content}" FontWeight= "SemiLight" ></Run> </Paragraph> </RichTextBlock> <!-- Additional columns are created from this template --> <COMMON:RICHTEXTCOLUMNS.COLUMNTEMPLATE> <DataTemplate> <RichTextBlockOverflow Margin= "80,0,0,0" Width= "560" > <RichTextBlockOverflow.RenderTransform> <TranslateTransform Y= "4" X= "-1" ></TranslateTransform> </RichTextBlockOverflow.RenderTransform> </RichTextBlockOverflow> </DataTemplate> </COMMON:RICHTEXTCOLUMNS.COLUMNTEMPLATE> </COMMON:RICHTEXTCOLUMNS> <VisualStateManager.VisualStateGroups> <!-- Visual states reflect the application's view state inside the FlipView --> <VisualStateGroup x:Name= "ApplicationViewStates" > <VisualState x:Name= "FullScreenLandscape" ></VisualState> <VisualState x:Name= "Filled" ></VisualState> <!-- Respect the narrower 100-pixel margin convention for portrait --> <VisualState x:Name= "FullScreenPortrait" > <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "Margin" Storyboard.TargetName= "richTextColumns" > <DiscreteObjectKeyFrame Value= "97,0,87,57" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "MaxHeight" Storyboard.TargetName= "image" > <DiscreteObjectKeyFrame Value= "400" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <!-- When snapped, the content is reformatted and scrolls vertically --> <VisualState x:Name= "Snapped" > <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "Margin" Storyboard.TargetName= "richTextColumns" > <DiscreteObjectKeyFrame Value= "17,0,17,57" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "Style" Storyboard.TargetName= "scrollViewer" > <DiscreteObjectKeyFrame Value= "{StaticResource VerticalScrollViewerStyle}" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "Width" Storyboard.TargetName= "richTextBlock" > <DiscreteObjectKeyFrame Value= "280" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "MaxHeight" Storyboard.TargetName= "image" > <DiscreteObjectKeyFrame Value= "160" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </ScrollViewer> </UserControl> </DataTemplate> </FlipView.ItemTemplate> </FlipView> <VisualStateManager.VisualStateGroups> <!-- Visual states reflect the application's view state --> <VisualStateGroup x:Name= "ApplicationViewStates" > <VisualState x:Name= "FullScreenLandscape" ></VisualState> <VisualState x:Name= "Filled" ></VisualState> <!-- The back button respects the narrower 100-pixel margin convention for portrait --> <VisualState x:Name= "FullScreenPortrait" > <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "Style" Storyboard.TargetName= "backButton" > <DiscreteObjectKeyFrame Value= "{StaticResource PortraitBackButtonStyle}" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <!-- The back button and title have different styles when snapped --> <VisualState x:Name= "Snapped" > <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "Style" Storyboard.TargetName= "backButton" > <DiscreteObjectKeyFrame Value= "{StaticResource SnappedBackButtonStyle}" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty= "Style" Storyboard.TargetName= "pageTitle" > <DiscreteObjectKeyFrame Value= "{StaticResource SnappedPageHeaderTextStyle}" KeyTime= "0" ></DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </COMMON:LAYOUTAWAREPAGE> <PRE></PRE><P></P><DIV></DIV><P></P><DIV></DIV> <DIV></DIV> </BUTTON> |
Code Snippet ItemDetailPage.xaml.cs
- Open ItemDetailPage.xaml.cs
- Modify the code on lines 50 and 64, similar to what we've been doing previously.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | using FastMotorcycle.Data; using System; using System.Collections.Generic; using System.IO; using System.Linq; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; // The Item Detail Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234232 namespace FastMotorcycle { /// <SUMMARY> /// A page that displays details for a single item within a group while allowing gestures to /// flip through other items belonging to the same group. /// </SUMMARY> public sealed partial class ItemDetailPage : FastMotorcycle.Common.LayoutAwarePage { public ItemDetailPage() { this .InitializeComponent(); } /// <SUMMARY> /// Populates the page with content passed during navigation. Any saved state is also /// provided when recreating a page from a prior session. /// </SUMMARY> /// <PARAM name="navigationParameter">The parameter value passed to /// <SEE cref="Frame.Navigate(Type, Object)" /> when this page was initially requested. /// </PARAM> /// <PARAM name="pageState">A dictionary of state preserved by this page during an earlier /// session. This will be null the first time a page is visited.</PARAM> protected override void LoadState(Object navigationParameter, Dictionary<STRING, Object> pageState) { // Allow saved page state to override the initial item to display if (pageState != null && pageState.ContainsKey( "SelectedItem" )) { navigationParameter = pageState[ "SelectedItem" ]; } // TODO: Create an appropriate data model for your problem domain to replace the sample data var item = MySampleDataSource.GetItem((String)navigationParameter); this .DefaultViewModel[ "Group" ] = item.Group; this .DefaultViewModel[ "Items" ] = item.Group.Items; this .flipView.SelectedItem = item; } /// <SUMMARY> /// Preserves state associated with this page in case the application is suspended or the /// page is discarded from the navigation cache. Values must conform to the serialization /// requirements of <SEE cref="SuspensionManager.SessionState" />. /// </SUMMARY> /// <PARAM name="pageState">An empty dictionary to be populated with serializable state.</PARAM> protected override void SaveState(Dictionary<STRING, Object> pageState) { var selectedItem = (MySampleDataItem) this .flipView.SelectedItem; pageState[ "SelectedItem" ] = selectedItem.UniqueId; } } } |
Conclusions – Next Steps
Note a few closing facts. First, we are not done. We need to create the cloud back end.
- You will not be able to run this code yet.
- We still need to create the Windows Azure Clod-based back end that will serve up JSON data.
- That is the next post - creating the cloud project using Windows Azure
- The key code happens in LoadRemoteDataAsync().
- Let's reveiw this piece of code.
- It creates an HttpClient object
- This allows us to connect to an http endpoint using http
- The code then performs and asynchronous call to the cloud service
- Starts by navigating to the address:
- https://127.0.0.1:81/FastMotorcycleListService.svc/list/SampleData/1
- Later, when we migrate to Azure, this url will change
- Starts by navigating to the address:
- The next piece of code, loops through all the MySampleDataItem objects.
- We parse the JSON array and they extract all the attributes, one by one.
- The remainder of the code, as previously explained, uses a stack to manage the relationship between groups and items. We essentially add all the items to a group. Next, the groups are added to the application.
- Next stop, the cloud, we will code up the Azure side of the equation.
The Next Post is about the Cloud
Although I haven't shown you how to build the cloud project, here is what it looks like from a few angles.
- Here is the running web server, ready to take http requests and return JSON data.
- Here is what the Visual Studio 2010 Azure project looks like. In the next post, we will build it from scratch.
'** Important Note ** Just to be clear, we cannot run the cloud project yet. So trying to run our Windows 8 Metro Grid client yet isn’t possible.
Step 1 – Create the cloud project (not done)
Step 2 – Run the cloud project (not done)
Step 3 – Run the Windows 8 Metro Data Grid Client Application (pending Step 1 & 2)Once you start your Windows Azure Cloud Project, you are ready to run the Windows 8 Metro Grid client.
If we are lucky, here is what we will see.
See you soon !
Download for Azure SDK |
Comments
Anonymous
January 06, 2013
Hi This is a great article, do you have an example how do connect Fast MotorCycle to Azure Services? [Thanks...Please provide a more detailed question...Bruno]Anonymous
January 06, 2013
hi, I am trying to connect the VS Store App template to Azure mobile services, I have also looked closely as the Contoso Cookbook demo. There are plenty of simple examples connecting a simple page to Azure Mobile Services, but I cannot find and struggled to connect the Grid Template or similar? Any help would be much appropriated... [see this post http://blogs.msdn.com/b/brunoterkaly/archive/2013/01/07/parsing-json-by-hand-from-azure-mobile-services.aspx ....Bruno]Anonymous
January 08, 2013
hi and thank you for parsing json by hand example. In regards to your Fast Motorcycle example above, can you give provide an example what the table / Field schema would look like in Azure Mobile Services? [At the bottom of this post, I give you some guidance, Andrew.] http://blogs.msdn.com/b/brunoterkaly/archive/2013/01/07/parsing-json-by-hand-from-azure-mobile-services.aspxAnonymous
June 04, 2013
Hi, Great article! Explanation are easy to understand. I have a question. Can I retrieve data from sql database using WCF Service in the MySampleDataSource? Instead of retrieving it from Azure and JSON. I want to do so as I'm not using Azure. If this is possible, can you provide an example or explanation for my understanding? Or are there any other ways to get data from sql database using WCF Service in order to bind the retrieved data to the xaml of each the 3 xaml pages. Thanks. When retrieving data from SQL Server you have a few options.
(1) Assuming your database is hosted in the cloud, your client can just use a connection string directly, as is true for Windows Azure SQL Database. If the DB is on-premises, it won't be visible by the outside world and you'll have to go through a service. (2) My example did use a WCF service. It is a good idea to not give clients a connection string and to use a service. Your service can be REST-based or SOAP. I used a REST-ful approach. The data coming back is in JSON or XML with REST. For SOAP, you can use a more efficient binary format.Anonymous
June 05, 2013
The comment has been removed