Speech Recognition and Speech Synthesis in Universal Windows Apps 21 September 2014 Diederik-Krols Universal Windows Apps, Windows Phone, WinRT This article introduces you to speech recognition (speech-to-text, STT) and speech synthesis (text-to-speech, TTS) in a Universal Windows XAML app. It comes with a sample app – code attached at the end- that has buttons for opening the standard UI for speech recognition, opening a custom UI for speech recognition, speaking a text in a system-provided voice of your choice, and speaking a text from a SSML document. On top of that, the Windows Phone version of the sample app comes with a custom control for speech and keyboard input, based on the Cortana look-and-feel. Here are screenshots from the Windows and Phone versions of the sample app: Speech Recognition For speech recognition there’s a huge difference between a Universal Windows app and a Universal Windows Phone app: the latter has everything built in, the former requires some extra downloading, installation, and registration. But after that, the experience is more or less the same. Windows Phone For speech-to-text, Windows Phone comes with the SpeechRecognizer class, just create an instance of it: it will listen to you, think for a while, and then come up with a text string. You can make the recognition easier by giving the control some context in the form of Constraints, like a predefined or custom Grammar – words and phrases that the control understands, or a topic, or just a list of words. Contradictory to the documentation, you *have* to provide constraints, and a call to CompileConstraintsAsync is mandatory. You can trigger the default UI experience with RecognizeWithUIAsync. This opens the UI and starts waiting for you to speak. Eventually it returns the result as a SpeechRecognitionResult that holds the recognized text, together with extra information such as the confidence of the answer (as a level in an enumeration, and as a percentage). Here’s the full use case:/// <summary> /// Opens the speech recognizer UI. /// </summary> private async void OpenUI_Click(object sender, RoutedEventArgs e) { SpeechRecognizer recognizer = new SpeechRecognizer(); SpeechRecognitionTopicConstraint topicConstraint = new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario.Dictation, "Development"); recognizer.Constraints.Add(topicConstraint); await recognizer.CompileConstraintsAsync(); // Required // Open the UI. var results = await recognizer.RecognizeWithUIAsync(); if (results.Confidence != SpeechRecognitionConfidence.Rejected) { this.Result.Text = results.Text; // No need to call 'Voice.Say'. The control speaks itself. } else { this.Result.Text = "Sorry, I did not get that."; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } This is how the default UI looks like. It takes the top half of the screen: If you don’t like this UI, then you can start a speech recognition session using RecognizeAsync. The recognized text is revealed in the Completed event of the resulting IAsyncOperation. Here’s the whole UI-less story:/// <summary> /// Starts a speech recognition session. /// </summary> private async void Listen_Click(object sender, RoutedEventArgs e) { this.Result.Text = "Listening..."; SpeechRecognizer recognizer = new SpeechRecognizer(); SpeechRecognitionTopicConstraint topicConstraint = new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario.Dictation, "Development"); recognizer.Constraints.Add(topicConstraint); await recognizer.CompileConstraintsAsync(); // Required var recognition = recognizer.RecognizeAsync(); recognition.Completed += this.Recognition_Completed; } /// <summary> /// Speech recognition completed. /// </summary> private async void Recognition_Completed(IAsyncOperation<SpeechRecognitionResult> asyncInfo, AsyncStatus asyncStatus) { var results = asyncInfo.GetResults(); if (results.Confidence != SpeechRecognitionConfidence.Rejected) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, new DispatchedHandler( () => { this.Result.Text = results.Text; ; })); } else { this.Result.Text = "Sorry, I did not get that."; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } This was just an introduction, there’s a lot more in the Windows.Media.SpeechRecognition namespace. Windows App The SpeechRecognizer class is only native to Windows Phone apps. But don’t worry: if you download the Bing Speech Recognition Control for Windows 8.1, you get roughly the same experience. Before you can actually use the control, you need to register an application in the Azure Market Place to get the necessary credentials. Under the hood the control delegates the processing to a web service that relies on OAUTH. Here’s a screenshot of the application registration pages: If you’re happy with the standard UI then you just drop the control on a page, like this:<sp:SpeechRecognizerUx x:Name="SpeechControl" /> Before starting a speech recognition session, you have to provide your credentials:var credentials = new SpeechAuthorizationParameters(); credentials.ClientId = "YourClientIdHere"; credentials.ClientSecret = "YourClientSecretHere"; this.SpeechControl.SpeechRecognizer = new SpeechRecognizer("en-US", credentials); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The rest of the story is similar to the Windows Phone code. Here’s how to open the standard UI:/// <summary> /// Activates the speech control. /// </summary> private async void OpenUI_Click(object sender, RoutedEventArgs e) { // Always call RecognizeSpeechToTextAsync from inside // a try block because it calls a web service. try { var result = await this.SpeechControl.SpeechRecognizer.RecognizeSpeechToTextAsync(); if (result.TextConfidence != SpeechRecognitionConfidence.Rejected) { ResultText.Text = result.Text; var voice = new Voice(); voice.Say(result.Text); } else { ResultText.Text = "Sorry, I did not get that."; } } catch (Exception ex) { // Put error handling here. } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } This is how the UI looks like. I put a a red box around it: If you want to skip (or replace) the UI part, then you don’t need the control in your XAML (but mind that you still have to download and install it). Just prepare a SpeechRecognizer instance:// The custom speech recognizer UI. SR = new SpeechRecognizer("en-US", credentials); SR.AudioCaptureStateChanged += SR_AudioCaptureStateChanged; SR.RecognizerResultReceived += SR_RecognizerResultReceived; .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } And call RecognizeSpeechToTextAsync when you’re ready:/// <summary> /// Starts a speech recognition session through the custom UI. /// </summary> private async void ListenButton_Click(object sender, RoutedEventArgs e) { // Always call RecognizeSpeechToTextAsync from inside // a try block because it calls a web service. try { // Start speech recognition. var result = await SR.RecognizeSpeechToTextAsync(); // Write the result to the TextBlock. if (result.TextConfidence != SpeechRecognitionConfidence.Rejected) { ResultText.Text = result.Text; } else { ResultText.Text = "Sorry, I did not get that."; } } catch (Exception ex) { // If there's an exception, show the Type and Message. ResultText.Text = string.Format("{0}: {1}", ex.GetType().ToString(), ex.Message); } } /// <summary> /// Cancels the current speech recognition session. /// </summary> private void CancelButton_Click(object sender, RoutedEventArgs e) { SR.RequestCancelOperation(); } /// <summary> /// Stop listening and start thinking. /// </summary> private void StopButton_Click(object sender, RoutedEventArgs e) { SR.StopListeningAndProcessAudio(); } /// <summary> /// Update the speech recognition audio capture state. /// </summary> private void SR_AudioCaptureStateChanged(SpeechRecognizer sender, SpeechRecognitionAudioCaptureStateChangedEventArgs args) { this.Status.Text = args.State.ToString(); } /// <summary> /// A result was received. Whether or not it is intermediary depends on the capture state. /// </summary> private void SR_RecognizerResultReceived(SpeechRecognizer sender, SpeechRecognitionResultReceivedEventArgs args) { if (args.Text != null) { this.ResultText.Text = args.Text; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } For Windows as well as Phone projects that require speech-to-text, don’t forget to enable the ‘Microphone’ capability. That also means that the user will have to give consent (only once): Speech Synthesis You now know how to let your device listen to you, it’s time to give it a voice. For speech synthesis (text-to-speech) both Phone and Windows apps have access to a Universal SpeechSynthesizer class. The SynthesizeTextToStreamAsync method transforms text into an audio stream (like a *.wav file). You can optionally provide the voice to be used; by selecting from the list of voices (SpeechSynthesizer.AllVoices) that are installed on the device. Each of these voices has it own gender and language. The voices depend on the device and your cultural settings: my laptop only speaks US and UK English, but my phone seems to be fluent in French and German too. When the speech synthesizer's audio stream is complete, you can play it via a MediaElement on the page. Note that Silverlight 8.1 apps do not need a MediaElement, they can call the SpeakTextAsync method, which is not available for Universal apps. Here’s the full flow. I wrapped it in a Voice class in the shared project (in an MVVM app this would be a 'Service'):/// <summary> /// Creates a text stream from a string. /// </summary> public void Say(string text, int voice = 0) { var synthesizer = new SpeechSynthesizer(); var voices = SpeechSynthesizer.AllVoices; synthesizer.Voice = voices[voice]; var spokenStream = synthesizer.SynthesizeTextToStreamAsync(text); spokenStream.Completed += this.SpokenStreamCompleted; } /// <summary> /// The spoken stream is ready. /// </summary> private async void SpokenStreamCompleted(IAsyncOperation<SpeechSynthesisStream> asyncInfo, AsyncStatus asyncStatus) { var mediaElement = this.MediaElement; // Make sure to be on the UI Thread. var results = asyncInfo.GetResults(); await mediaElement.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, new DispatchedHandler( () => { mediaElement.SetSource(results, results.ContentType); })); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In a multi-page app, you must make sure that there is a MediaElement on each page. In the sample app, I reused a technique from a previous blog post. I created a custom template for the root Frame:<Application.Resources> <!-- Injecting a media player on each page --> <Style x:Key="RootFrameStyle" TargetType="Frame"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Frame"> <Grid> <!-- Voice --> <MediaElement IsLooping="False" /> <ContentPresenter /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </Application.Resources> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } And applied it in app.xaml.cs:// Create a Frame to act as the navigation context and navigate to the first page rootFrame = new Frame(); // Injecting media player on each page. rootFrame.Style = this.Resources["RootFrameStyle"] as Style; // Place the frame in the current Window Window.Current.Content = rootFrame; .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The voice class can now easily access the MediaElement from the current page:public Voice() { DependencyObject rootGrid = VisualTreeHelper.GetChild(Window.Current.Content, 0); this.mediaElement = (MediaElement)VisualTreeHelper.GetChild(rootGrid, 0) as MediaElement; } /// <summary> /// Gets the MediaElement that was injected into the page. /// </summary> private MediaElement MediaElement { get { return this.mediaElement; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The speech synthesizer can also generate an audio stream from Speech Synthesis Markup Language (SSML). That’s an XML format in which you can not only write the text to be spoken, but also the pauses, the changes in pitch, language, or gender, and how to deal with dates, times, and numbers, and even detailed pronunciation through phonemes. Here’s the document from the sample app:<?xml version="1.0" encoding="utf-8" ?> <speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-US'> Your reservation for <say-as interpret-as="cardinal"> 2 </say-as> rooms on the <say-as interpret-as="ordinal"> 4th </say-as> floor of the hotel on <say-as interpret-as="date" format="mdy"> 3/21/2012 </say-as>, with early arrival at <say-as interpret-as="time" format="hms12"> 12:35pm </say-as> has been confirmed. Please call <say-as interpret-as="telephone" format="1"> (888) 555-1212 </say-as> with any questions. <voice gender='male'> <prosody pitch='x-high'> This is extra high pitch. </prosody > <prosody rate='slow'> This is the slow speaking rate. </prosody> </voice> <voice gender='female'> <s>Today we preview the latest romantic music from Blablabla.</s> </voice> This is an example of how to speak the word <phoneme alphabet='x-microsoft-ups' ph='S1 W AA T . CH AX . M AX . S2 K AA L . IH T'> whatchamacallit </phoneme>. <voice gender='male'> For English, press 1. </voice> <!-- Language switch: does not work if you do not have a french voice. --> <voice gender='female' xml:lang='fr-FR'> Pour le français, appuyez sur 2 </voice> </speak> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s how to generate the audio stream from it:/// <summary> /// Creates a text stream from an SSML string. /// </summary> public void SaySSML(string text, int voice = 0) { var synthesizer = new SpeechSynthesizer(); var voices = SpeechSynthesizer.AllVoices; synthesizer.Voice = voices[voice]; var spokenStream = synthesizer.SynthesizeSsmlToStreamAsync(text); spokenStream.Completed += this.SpokenStreamCompleted; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Speech Recognition User Control The Windows Phone project in the sample app contains a SpeechInputBox. It’s a user control for speech input through voice or keyboard that is inspired by the Cortana look-and-feel. It comes with dependency properties for the text and a highlight color, and it raises an event when the input is processed: The control behaves like the Cortana one: it starts listening when you tap on the microphone icon, it opens the onscreen keyboard if you tap on the text box, it notifies you when it listens to voice input, the arrow button puts the control in thinking mode, and the control says the result out loud when the input came from voice (not from typed input). The implementation is very straightforward: depending on the state the control shows some UI elements, and calls the API’s that were already discussed in this article. There’s definitely room for improvement: you could add styling, animations, sound effects, and localization. Here’s how to use the control in a XAML page. I did not use data binding in the sample, the main page hooked an event handler to TextChanged:<Controls:SpeechInputBox x:Name="SpeechInputBox" Highlight="Cyan" /> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here are some screenshot of the SpeechInputBox in action, next to its Cortana inspiration: If you intend to build a Silverlight 8.1 version of this control, you might want to start from the code in the MSDN Voice Search for Windows Phone 8.1 sample (there are quite some differences with Universal apps: in the XAML as well as in the API calls). If you want to roll your own UI, make sure you follow the Speech Design Guidelines. Code Here’s the code, it was written in Visual Studio 2013 Update 3. Remember that you need to register an app to get the necessary credentials to run the Bing Speech Recognition control: U2UC.WinUni.SpeechSample.zip (661.3KB). Enjoy! XAML Brewer
Universal App with Lex.DB 26 June 2014 Diederik-Krols Universal Windows Apps, Windows Phone, WinRT This article shows how to create a Universal Windows App that stores its local data in Lex.DB. This is a lightweight, developer-friendly in-process database engine, completely written in C#. For an introduction to building Store Apps on top of Lex.DB, please check this article of mine. For a more advanced dive into performance tuning, check this one – and make sure you don’t skip the valuable comments from Lex Lavnikov, the author of Lex.DB, at the end. Lex.DB can be used on .NET 4+, Silverlight 5+, Windows Phone 8+, WinRT 8+, and Xamarin. Recently this alternative for SQLite was upgraded to support Universal Windows Apps. I created a straightforward sample app, based on the Universal App with SQLite blog post by Nicolò Carandini. I added a tiny MVVM Framework with BindableBase and RelayCommand just for fun. The sample app manages a list of Person instances. This is the Person class, as simple as can be:public class Person { public int Id { get; set; } public string Name { get; set; } public string Degree { get; set; } public string Occupation { get; set; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The sample app comes with commands to add a Person, delete the selected Person, and reset the database to its default content. Here’s how it looks like in the emulator and the simulator: You just need to add the Lex.DB Nuget package in your solution: Each of the platform-specific projects will reference its own Lex.DB dll. That’s a lot simpler than SQLite, where you need to install an SDK, reference the C++ runtime *and* integrate some extra source code into your projects. The following screenshots illustrate the impact of both databases to your Visual Studio solution, with SQLite on the left, and Lex.DB on the right: Here’s how the data access layer creates a reference to the database – in a static constructor:private static DbInstance db; static Dal() { // Create database db = new DbInstance("Storage", ApplicationData.Current.RoamingFolder); // Define table mapping // * 1st parameter is primary key // * 2nd parameter is autoGen e.g. auto-increment db.Map<Person>().Automap(p => p.Id, true); // Initialize database db.Initialize(); } The following method returns the content of the Person table:public static IEnumerable<Person> GetPeople() { return db.Table<Person>(); } I defined two methods to insert/update Person instances: one for a single instance (it returns the generated primary key) and another one to save a list (in one transaction – read the already mentioned perfomance tuning article for more details):public static int SavePerson(Person person) { db.Table<Person>().Save(person); return person.Id; } public static Task SavePeople(IEnumerable<Person> people) { return db.Table<Person>().SaveAsync(people); } Here’s how to give your database some initial (default) content:public static Task ResetPeople() { // Clear db.Purge<Person>(); // Repopulate return Dal.SavePeople( new List<Person>() { new Person() { Name="Leonard Leakey Hofstadter", Degree="Ph.D.", Occupation="Experimental physicist"}, new Person() {Name="Sheldon Lee Cooper", Degree="Ph.D.", Occupation="Theoretical physicist"}, new Person() {Name="Howard Joel Wolowitz", Degree="M.Eng.", Occupation="Aerospace engineer"}, new Person() {Name="Rajesh Ramayan Koothrappali", Degree="Ph.D.", Occupation="Astrophysicist"} }); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } For the sake of completeness, here’s the delete method:public static Task DeletePeople(IEnumerable<Person> people) { return db.Table<Person>().DeleteAsync(people); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s the full source code of the sample app, it was written in Visual Studio 2013 Update 2: U2UC.WinUni.LexDBSample.zip (1.2MB) Enjoy! XAML Brewer
A Marching Ants Animation for Universal Windows Apps 17 June 2014 Diederik-Krols Universal Windows Apps, Windows Phone, WinRT In a lot of apps we need to draw lines on some kind of map to display a route. If you want such line to also indicate the driving direction and speed, then you could apply the Marching Ants Effect, where you represent the route as a dotted or dashed line and let the dashes walk slowly sideways and up and down. In the XAML world, this is remarkably easy. All you need to do is apply a dash pattern to the line (or PolyLine, or any other Shape) through the Shape.StrokeDashArray property and then animate its Shape.StrokeDashOffset. Here’s an example of the effect – since a screenshot would be rather silly, I created a movie where you see the marching ants (well, in this case they might be orcs) in the attached sample project: MarchingAnts.wmv (1.4MB) As mentioned, you have to first make the line look as an ants line, so use the appropriate values for the Shape.StrokeDashCap and Shape.StrokeLineJoin properties:Polyline line = new Polyline(); // Add Points // line.Points.Add(new Point(...)); line.Stroke = new SolidColorBrush(Colors.OrangeRed); line.StrokeThickness = 18; line.StrokeDashArray = new DoubleCollection() { 4, 2 }; line.StrokeDashCap = PenLineCap.Round; line.StrokeLineJoin = PenLineJoin.Round; .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } For the animation I use a Storyboard with nothing but a DoubleAnimation on the Shape.StrokeDashOffset property. That offset moves from 0 to the total length of the dash pattern, which can be conveniently calculated with a LINQ Sum operator. I implemented it as an extension method to the Shape class. It only takes the duration of the animation as a parameter:public static void ApplyMarchingAntsAnimation(this Shape shape, TimeSpan duration) { Storyboard storyboard = new Storyboard(); DoubleAnimation doubleAnimation = new DoubleAnimation(); doubleAnimation.From = 0.0; doubleAnimation.To = -shape.StrokeDashArray.Sum(); doubleAnimation.Duration = new Duration(duration); doubleAnimation.AutoReverse = false; doubleAnimation.RepeatBehavior = RepeatBehavior.Forever; doubleAnimation.EnableDependentAnimation = true; // Don't forget storyboard.Children.Add(doubleAnimation); Storyboard.SetTarget(doubleAnimation, shape); Storyboard.SetTargetProperty(doubleAnimation, "StrokeDashOffset"); storyboard.Begin(); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } You can apply the animation to the Stroke of any Shape with the following one-liner:line.ApplyMarchingAntsAnimation(TimeSpan.FromSeconds(1)); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Everything is implemented in the shared part of a universal app, so it works on the desktop, the tablet and the phone too. This is just a screenshot, but I assure you “it’s alive…”: Here’s the whole source, it was written in Visual Studio 2013 Update 2: U2UC.WinUni.MarchingAnts.zip (555.9KB) Enjoy! XAML Brewer
Tracking with Tiles and Toasts 14 May 2014 Diederik-Krols WinRT This article shows a way to implement multiple alarm clock functionality in a Windows (Store or Enterprise) MVVM XAML App. We’ll use locally scheduled Toast and Tile Notifications to track the progress of a workflow - like a workout scheme or a cooking recipe. The workflow can be started, paused, restarted, and canceled by the user. The app will schedule, reschedule, and unschedule the appropriate Toast and Tile Notifications so that the process can be monitored by the end user on the machine, even when the app gets suspended. Here’s a screenshot of the sample app, it monitors a classic cooking recipe: When the app is started for the first time, it creates the workflow steps, and welcomes you with a regular toast. I use a helper class that can spawn three different toasts. These toasts are a replacement for the classic Info, Warning, and Error message boxes. Here’s the whole class, I already use it in several published Store apps: /// <summary> /// Issues Toast Notifications. /// </summary> public static class Toast { /// <summary> /// Shows the specified text in a toast. /// </summary> public static void Show(string title, string text) { Toast.Show(title, text, null); } /// <summary> /// Shows a toast with an info icon. /// </summary> public static void ShowInfo(string title, string text) { Toast.Show(title, text, "ms-appx:///Assets/Toasts/Wink.png"); } /// <summary> /// Shows a toast with a warning icon. /// </summary> public static void ShowWarning(string title, string text) { Toast.Show(title, text, "ms-appx:///Assets/Toasts/Worried.png"); } /// <summary> /// Shows a toast with an error icon. /// </summary> public static void ShowError(string title, string content) { Toast.Show(title, content, "ms-appx:///Assets/Toasts/Confused.png"); } /// <summary> /// Shows a toast with the specified text and icon. /// </summary> private static void Show(string title, string content, string imagePath) { XmlDocument toastXml = GetToast(title, content, imagePath); ToastNotification toast = new ToastNotification(toastXml); ToastNotificationManager.CreateToastNotifier().Show(toast); } /// <summary> /// Gets the toast. /// </summary> private static XmlDocument GetToast(string title, string content, string imagePath) { string toastXmlString = "<toast>\n" + "<visual>\n" + "<binding template=\"ToastImageAndText02\">\n" + "<image id=\"1\" src=\"" + imagePath + "\"/>\n" + "<text id=\"1\">" + title + "</text>\n" + "<text id=\"2\">" + content + "</text>\n" + "</binding>\n" + "</visual>\n" + "</toast>\n"; XmlDocument toastXml = new XmlDocument(); toastXml.LoadXml(toastXmlString); return toastXml; } } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } Here’s the welcome toast call:Toast.ShowInfo("Welcome", "I created some default alarms for you."); .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } And this is how the result looks like: Toast Notifications can be displayed immediately, but they can also be scheduled for the future. Let’s take a look at that, and dive into some other helper classes. Each step in the chicken recipe workflow is represented by an instance of the Alarm class – the ViewModel to a Toast Notification. An alarm is in one of these states: /// <summary> /// Alarm States. /// </summary> public enum AlarmStates { New, Scheduled, Paused, Canceled, Delivered } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } The Alarm helper class comes with expected properties such as an Identifier, Title, Content, State, and TimeSpan. The TimeLeft is a calculated property: /// <summary> /// Gets the time left. /// </summary> public TimeSpan TimeLeft { get { switch (this.State) { case AlarmStates.New: return this.timeSpan; case AlarmStates.Scheduled: return this.timeSpan.Add(this.enabledAt - DateTime.Now); case AlarmStates.Paused: return this.timeSpan; case AlarmStates.Canceled: return TimeSpan.FromSeconds(0); case AlarmStates.Delivered: return TimeSpan.FromSeconds(0); default: return TimeSpan.FromSeconds(0); } } } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } Each Alarm is scheduled when the workflow starts, and has to notify the user when its associated step starts. Here’s the code that schedules an alarm; we update its state and schedule a Toast Notification: /// <summary> /// Schedules this instance. /// </summary> public async Task Schedule() { if (this.state == AlarmStates.Scheduled || this.state == AlarmStates.Delivered) { // No action. return; } if (this.state == AlarmStates.Paused) { this.TimeSpan = this.TimeLeft; } this.enabledAt = DateTime.Now; this.ScheduleToast(); this.State = AlarmStates.Scheduled; await RemovePersistedAlarm(this); } The Toast Notification is scheduled through a ToastNotifier that registers a ScheduledToastNotification by calling AddToSchedule. For a introduction to all of these, check the alarm toast notifications sample on MSDN, which also demonstrates Snoozing and Dismiss functionality. Here’s the scheduling source code: /// <summary> /// The toast notifier /// </summary> private static ToastNotifier toastNotifier = ToastNotificationManager.CreateToastNotifier(); /// <summary> /// Schedules the toast. /// </summary> private void ScheduleToast() { if (this.TimeSpan <= TimeSpan.FromSeconds(0)) { return; } XmlDocument toastXml = GetToast(); var toast = new ScheduledToastNotification(toastXml, DateTime.Now.Add(this.TimeSpan)); toast.Id = this.Id; toastNotifier.AddToSchedule(toast); } /// <summary> /// Gets the toast. /// </summary> private XmlDocument GetToast() { string toastXmlString = "<toast duration=\"long\">\n" + "<visual>\n" + "<binding template=\"ToastImageAndText02\">\n" + "<image id=\"1\" src=\"ms-appx:///Assets/Toasts/AlarmClock.png\"/>\n" + "<text id=\"1\">" + this.title + "</text>\n" + "<text id=\"2\">" + this.content + "</text>\n" + "</binding>\n" + "</visual>\n" + "<audio src=\"ms-winsoundevent:Notification.Looping.Alarm2\" loop=\"true\" />\n" + "</toast>\n"; XmlDocument toastXml = new XmlDocument(); toastXml.LoadXml(toastXmlString); return toastXml; } If you don’t like working with the raw XML version of the toast content, then I suggest you take a look at the NotificationsExtensions project. That project contains a more developer friendly wrapper around all of this. A scheduled toast notification appears (and sounds) right on time, whether the app is active or not: By the way: scheduled toast notifications –as well as scheduled tile notifications, see further- nicely survive restarts of your machine. Back to the sample app. The entire workflow as such is not remembered. When the user stops and restarts the app, we recreate the list of alarms based on the scheduled toasts – that’s what GetScheduledToastNotifications does: /// <summary> /// Returns the scheduled alarms. /// </summary> public static IEnumerable<Alarm> ScheduledAlarms() { var toasts = toastNotifier.GetScheduledToastNotifications(); return from t in toasts select (Alarm)t; } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } All we have to do is convert each native ScheduledToastNotification back to an instance of our Alarm ViewModel class. I used a conversion operator for that. If you’re dealing with the raw XML representation for the toasts, then you need to apply some XPATH magic (in SelectSingleNode) to get some of the data back: /// <summary> /// Performs an implicit conversion from <see cref="ScheduledToastNotification"/> to <see cref="Alarm"/>. /// </summary> public static implicit operator Alarm(ScheduledToastNotification toast) { Alarm result = new Alarm(); result.Id = toast.Id; result.TimeSpan = toast.DeliveryTime - DateTime.Now; result.enabledAt = DateTime.Now; result.State = AlarmStates.Scheduled; var node = toast.Content.SelectSingleNode("//text[@id=1]"); if (node != null) { result.Title = node.InnerText; } node = toast.Content.SelectSingleNode("//text[@id=2]"); if (node != null) { result.Content = node.InnerText; } return result; } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } The sample app allows an alarm to be paused, which is something that a native scheduled toast notification can’t handle. When an alarm is paused, we upgrade its state and remove the corresponding toast notification from the schedule: /// <summary> /// Pauses this instance. /// </summary> public async Task Pause() { if (this.state != AlarmStates.Scheduled) { // No action. return; } this.TimeSpan = this.TimeLeft; this.State = AlarmStates.Paused; this.UnscheduleToast(); await AddPersistedAlarm(this); } For unscheduling the toast, we look it up by its identifier through a LINQ query against GetScheduledToastNotifications, and remove it from the list with RemoveFromSchedule: /// <summary> /// Unschedules the toast. /// </summary> private void UnscheduleToast() { var toasts = toastNotifier.GetScheduledToastNotifications(); var found = (from t in toasts where t.Id == this.Id select t).FirstOrDefault(); if (found != null) { toastNotifier.RemoveFromSchedule(found); } }.csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } For a paused alarm there’s no corresponding ScheduledToastNotification anymore, so when the app is suspended, it would be lost. To prevent this, we serialize the paused alarms as a list in the local folder so we can deserialize it when the app restarts. Here’s the corresponding code for all of this: /// <summary> /// The serializer /// </summary> private static AbstractSerializationBase<List<Alarm>> serializer = new XmlSerialization<List<Alarm>>() { FileName = "Alarms.xml", Folder = ApplicationData.Current.LocalFolder }; /// <summary> /// Returns the persisted alarms. /// </summary> public static async Task<List<Alarm>> PersistedAlarms() { return await serializer.Deserialize(); } /// <summary> /// Adds a persisted alarm. /// </summary> private static async Task AddPersistedAlarm(Alarm alarm) { var alarms = await PersistedAlarms(); await RemovePersistedAlarm(alarm); alarm.TimeSpan = alarm.TimeLeft; alarms.Add(alarm); await serializer.Serialize(alarms); } I reused the XML Serializer from this article. Because the native XML Serializer doesn’t handle the TimeSpan data type very well, I applied a little trick to serialize and deserialize the TimeSpan property. I created (serializable) shadow property that holds the number of Ticks in it: /// <summary> /// Gets or sets the time span. /// </summary> /// <remarks>Not XML serializable.</remarks> [XmlIgnore] public TimeSpan TimeSpan { get { return this.timeSpan; } set { this.SetProperty(ref this.timeSpan, value); } } /// <summary> /// Gets or sets the time span ticks. /// </summary> /// <remarks>Pretended property for serialization</remarks> [XmlElement("TimeSpan")] public long TimeSpanTicks { get { return this.timeSpan.Ticks; } set { this.timeSpan = new TimeSpan(value); } } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } When an individual alarm is disabled, then we unschedule the corresponding toast notification, and remove it from the list of persisted alarms: /// <summary> /// Disables this instance. /// </summary> public async Task Disable() { if (this.state != AlarmStates.Scheduled && this.state != AlarmStates.Paused) { // No action. return; } this.UnscheduleToast(); this.State = AlarmStates.Canceled; await RemovePersistedAlarm(this); } /// <summary> /// Unschedules the toast. /// </summary> private void UnscheduleToast() { var toasts = toastNotifier.GetScheduledToastNotifications(); var found = (from t in toasts where t.Id == this.Id select t).FirstOrDefault(); if (found != null) { toastNotifier.RemoveFromSchedule(found); } } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } /// <summary> /// Removes a persisted alarm. /// </summary> private static async Task RemovePersistedAlarm(Alarm alarm) { var alarms = await PersistedAlarms(); alarms.RemoveAll(a => a.Id == alarm.Id); await serializer.Serialize(alarms); } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } When the user starts the app by tapping on the tile, or on a toast, the app reassembles the (remaining) workflow by combining the scheduled toast notifications with the serialized paused alarms (we deliberately forget the steps for which the alarms were delivered). So this is how the app looks like after a restart: This is the code that brings back the list of alarms, it comes from the constructor of the main view model – which actually represents the workflow: // Create an alarm for all scheduled notifications from previous sessions that are still running. var scheduled = Alarm.ScheduledAlarms(); if (scheduled.Count() > 0) { foreach (var alarm in scheduled) { this.alarms.Add(alarm); } } CoreWindow.GetForCurrentThread().Dispatcher.RunAsync ( CoreDispatcherPriority.Normal, async () => { // Rehydrate paused alarms. var persisted = await Alarm.PersistedAlarms(); if (persisted.Count() > 0) { foreach (var alarm in persisted) { this.alarms.Add(alarm); } } // Create default alarms. if (this.alarms.Count == 0) { Toast.ShowInfo("Welcome", "I created some default alarms for you."); this.CreateDefaultAlarms(); } this.RescheduleTiles(); } ); DispatcherTimer toastTimer = new DispatcherTimer(); toastTimer.Tick += this.ToastTimer_Tick; toastTimer.Interval = TimeSpan.FromSeconds(1); toastTimer.Start(); .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } At the end of the previous code snippet, you see that we fire up a timer with a short interval (1 second). This timer updates the UI so the user has a detailed view on the workflow status through the changing TimeLeft fields. But it doesn’t stop here: we also want to give the user a high level –but less accurate- overview of the status of the running workflow. Therefor we decorate the app’s live tile with a message that contains the remaining time to the next notification and the remaining time for the entire flow. For that we schedule a regular tile update; every minute for the sample app, but I can imagine you would want a larger interval for a production app. Here’s the code to schedule these tile notifications, it’s very similar to the toast scheduling. This code is executed when the app restarts, when user modifies the flow, and also on a slow(er) moving timer. For each minute between the current moment and the end of the workflow we lookup the next upcoming toast, and create the corresponding title and message on the live tile. The sample app notifies only the TimeLeft values, but you have access to all properties of the associated alarms (including Title and Content) if you want. Just remember that you only have three short lines of text for the tile: /// <summary> /// Reschedules the tiles. /// </summary> private void RescheduleTiles() { var scheduledAlarms = this.ScheduledAlarms; var nextAlarm = scheduledAlarms.FirstOrDefault(); var lastAlarm = scheduledAlarms.LastOrDefault(); if (nextAlarm == null) { // No alarms UnscheduleTileNotifications(); return; } var next = (DateTime.Now + nextAlarm.TimeLeft) - nextAlarmTime; var nextMinutes = next.TotalMinutes; var last = (DateTime.Now + lastAlarm.TimeLeft) - lastAlarmTime; var lastMinutes = last.TotalMinutes; if ((Math.Abs(nextMinutes) < 2) && Math.Abs(lastMinutes) < 2) { // Nothing changed since the previous check. return; } nextAlarmTime = DateTime.Now.Add(nextAlarm.TimeLeft); lastAlarmTime = DateTime.Now.Add(lastAlarm.TimeLeft); UnscheduleTileNotifications(); DateTime dateTime = DateTime.Now.AddSeconds(5); while (dateTime < lastAlarmTime) { string title = "Cooking"; var alarm = (from a in scheduledAlarms where a.TimeLeft > dateTime - DateTime.Now select a).FirstOrDefault(); if (alarm != null) { string content; if (alarm != lastAlarm) { content = String.Format( "Notifies in {0} min.\nEnds in {1} min.", (alarm.TimeLeft - (dateTime - DateTime.Now)).Minutes + 1, (lastAlarm.TimeLeft - (dateTime - DateTime.Now)).Minutes + 1); } else { content = String.Format( "Ends in {0} min.", (lastAlarm.TimeLeft - (dateTime - DateTime.Now)).Minutes + 1); } tileUpdater.AddToSchedule(new ScheduledTileNotification(this.GetTile(title, content), dateTime)); Debug.WriteLine("Scheduled Tile Notification for {0}.", dateTime); } dateTime = dateTime.Add(TimeSpan.FromMinutes(1)); } // Done. tileUpdater.AddToSchedule(new ScheduledTileNotification(this.GetTile("Job done", string.Empty), dateTime)); } Here’s an MS-Paintoshopped image of the evolution of the sample app's live tile throughout the workflow: .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } The ‘Job Done’ tile notification is the last one. The tile remains like this until the app is restarted, since there seems to be no way to bring back the default tile, at least not through the scheduling infrastructure. If you really need this functionality, you could start a background job. Anyway, here’s the call to bring back the default tile://Reset to default tile. tileUpdater.Clear(); .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } The sample app uses a strictly sequential workflow, but the Alarm ViewModel class is reusable in more complex scenario’s with e.g. parallel tasks or tasks that can be paused individually. Here’s the full source code, it was built with Visual Studio 2013 Update 2: U2UC.Win8.Alarms.zip (314.3KB) Enjoy! Diederik
Using OneDrive files in Windows Platform Apps – Part Deux 09 April 2014 Diederik-Krols WinRT This article presents a handful of building blocks for some more advanced OneDrive use cases in Windows 8 apps, like: transparently switching between a local folder and a OneDrive working folder, synchronizing a local folder with a OneDrive folder, sharing a OneDrive folder across devices, sharing a OneDrive folder across apps, or even sharing a OneDrive folder across platforms (Store app - Phone app - side loaded Enterprise app). It elaborates on my previous article that showed how to access a OneDrive folder from a Windows 8 Store app from a purely technical point. This code is now hardened, and refactored into a more reusable model. Although all building blocks are implemented and tested, not all of the mentioned scenarios are implemented in the attached sample app. The sample app simulates a Store app that has OneDrive capability as an option (e.g. as an in-app purchase) and that can switch back to the local folder whenever the user feels to (e.g. to work offline). This is how it looks like: The sample app allows you to do some basic file operations, and comes with a manual switch to toggle between the local folder and a working folder on your OneDrive. File and folder comparison and synchronization are not elaborated, but all constituents are in the source code. The object model contains the following classes: FileSystemBase: a base class that encapsulates the basic file system operations, regardless of where the working folder lives: enumerating the files in the working folder, saving a file, reading a file, and deleting a file. IFile: an interface that contains the file properties that the app is interested in: name of the file, size of the file (useful for synchronization), modification date of the file (useful for synchronization) Device: a class that represents the physical device: hardware identifier, and internet connectivity. Here’s the UML class diagram of the API: The FileSystemBase class abstracts the file manipulations. It’s an abstract class with concrete virtual asynchronous methods that each throw an exception. I know that looks weird, but it’s the best way to enforce child classes to implement an expected asynchronous behavior (static, abstract, interface, and async don’t really work together in a class definition):/// <summary> /// Base Class for File Systems. /// </summary> public abstract class FileSystemBase { /// <summary> /// Returns the list of Files in the Working Folder. /// </summary> public async virtual Task<List<IFile>> Files() { throw new NotImplementedException(); } /// <summary> /// Saves the specified content into the Working Folder. /// </summary> public async virtual Task Save(string content, string fileName) { throw new NotImplementedException(); } /// <summary> /// Returns the content of the specified file in the Working Folder. /// </summary> public async virtual Task<string> Read(string fileName) { throw new NotImplementedException(); } /// <summary> /// Deletes the specified file from the Working Folder. /// </summary> public async virtual Task Delete(string fileName) { throw new NotImplementedException(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Both concrete child classes –LocalDrive and OneDrive- have their own implementation of the virtual methods. The OneDrive class is of course a bit more complex than the LocalDrive class, since it needs a login procedure and it requires and extra identifier for the working folder and its files. Check the sample app for the source code, I'm not repeating it in this article since it’s just a rehash of my previous blog post. The main viewmodel of the app uses a field to refer to the file system:private FileSystemBase currentDrive; .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The app will not show command buttons as long as it’s not connected to a file system. So I added an IsReady property to the viewmodel:/// <summary> /// Gets a value indicating whether this instance is ready (i.e. connected to a file system). /// </summary> public bool IsReady { get { return this.currentDrive != null; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s the app in its waiting mode - it may take some time to connect to OneDrive the first time: The last used file system is stored in the roaming settings, so it can be shared between devices:/// <summary> /// Gets or sets a value indicating whether we're using OneDrive or Local Folder. /// </summary> public bool UseOneDrive { get { return this.useOneDrive; } set { if (value != this.useOneDrive) { if (value) { this.TryEnableOneDrive(); } else { this.currentDrive = LocalDrive.Current; this.SetProperty(ref this.useOneDrive, value); ApplicationData.Current.RoamingSettings.Values["UseOneDrive"] = value; this.OnPropertyChanged("IsReady"); } } } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } When the user switches to OneDrive mode, we try to activate it. In case of a problem (e.g. the user did not consent, or the drive cannot be accessed), we switch back to local mode. The OneDrive initialization code is called from strictly synchronous code - a property setter and a constructor. It executes asynchronously and finishes with property change notifications. The XAML bindings will do the rest: private void TryEnableOneDrive() { bool success = true; CoreWindow.GetForCurrentThread().Dispatcher.RunAsync ( CoreDispatcherPriority.Normal, async () => { FileSystemBase fileSystem = null; try { fileSystem = await OneDrive.GetCurrent(); } catch (Exception) { success = false; } finally { if (fileSystem == null || !OneDrive.IsLoggedIn) { // Something went wrong, switch to local. success = false; this.currentDrive = LocalDrive.Current; } else { this.currentDrive = fileSystem; } this.useOneDrive = success; ApplicationData.Current.RoamingSettings.Values["UseOneDrive"] = success; // Need to explicitly notify to reset toggle button on error. this.OnPropertyChanged("UseOneDrive"); this.OnPropertyChanged("IsReady"); } } ); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s how the code is called from the constructor of the viewmodel:this.useOneDrive = (bool)ApplicationData.Current.RoamingSettings.Values["UseOneDrive"]; if (this.useOneDrive) { this.TryEnableOneDrive(); } else { this.currentDrive = LocalDrive.Current; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In that same constructor we also check if the app has been used from another device lately, because this might trigger a synchronization routine. The use case that I have in mind here, is an app that always saves locally but uploads at regular intervals to the user’s OneDrive. Such an app would want to be informed that the OneDrive folder was updated by another device:if (ApplicationData.Current.RoamingSettings.Values["HardwareId"] != null) { string previous = (string)ApplicationData.Current.RoamingSettings.Values["HardwareId"]; if (previous != Device.Ashwid) { this.ShowToast("You seem to have used this app from another machine!", "ms-appx:///Assets/Warning.png"); } } ApplicationData.Current.RoamingSettings.Values["HardwareId"] = Device.Ashwid; .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s that code in action: For checking the device id, a GUID in local settings would do the job in most scenarios. But the ASHWID allows you to share the working folder between apps on the same device (you would just have to override the default working folder name for this):/// <summary> /// Gets the Application Specific Hardware Identifier. /// </summary> /// <remarks> /// Due to hardware drift, the returned value may change over time). /// See http://msdn.microsoft.com/en-us/library/windows/apps/jj553431.aspx. /// </remarks> public static string Ashwid { get { HardwareToken hwToken = HardwareIdentification.GetPackageSpecificToken(null); IBuffer hwID = hwToken.Id; byte[] hwIDBytes = hwID.ToArray(); return hwIDBytes.Select(b => b.ToString()).Aggregate((b, next) => b + "," + next); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Most of the file operations are ignorant of the whereabouts of the working folder:private async void ReadFolder_Executed() { this.files.Clear(); foreach (var file in await this.currentDrive.Files()) { this.files.Add(file); } } private async void ReadFile_Executed() { if (this.selectedFile != null) { this.SelectedText = await this.currentDrive.Read(this.selectedFile.Name); } } private async void DeleteFile_Executed() { if (this.selectedFile != null) { var task = this.currentDrive.Delete(this.selectedFile.Name); try { await task; this.ShowToast("File deleted."); } catch (Exception ex) { this.ShowToast("There was an error while deleting.", "ms-appx:///Assets/Warning.png"); } this.ReadFolderCommand.Execute(null); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } While saving a file on OneDrive you can easily pull the internet cable or disable the Wifi, so the Save method was the ideal candidate to test some extra exception handling. If something goes wrong while saving, you may want to check the connection to the Internet. Unfortunately there is no way to ask the LiveConnectClient nor the LiveConnectSession whether the connection is still available. Actually it’s even worse: you can re-login successfully without a connection, you end up with a "false positive". Fortunately you can examine your Internet connectivity in other ways:/// <summary> /// Gets a value indicating whether this device is currently connected to the Internet. /// </summary> public static bool IsConnected { get { try { return NetworkInformation.GetInternetConnectionProfile().GetNetworkConnectivityLevel() >= NetworkConnectivityLevel.InternetAccess; } catch (Exception) { return false; } } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In a production app, it would make sense to automatically switch to local mode after a failed Save operation against OneDrive. Apparently the LiveConnectClient enters an undetectable corrupt state after an unsuccessful BackgroundUploadAsync. The next time you try to save a file –and there’s still no Internet connection- the Live client throws an exception that you can’t catch. I’m sorry I didn’t find a workaround for this yet. Anyway, the app will die on you ungracefully in this particular scenario :-( Here’s the whole Save method (the one in the main view model):private async void SaveFile_Executed() { if (this.selectedFile != null) { var task = this.currentDrive.Save(this.selectedText, this.selectedFile.Name); try { await task; this.ShowToast("File saved."); } catch (Exception ex) { if (this.useOneDrive && !Device.IsConnected) { this.ShowToast("Internet connection is lost. You may want to switch to local storage.", "ms-appx:///Assets/Warning.png"); } else { this.ShowToast("There was an error while saving.", "ms-appx:///Assets/Warning.png"); } } } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s one of the toasts that notify the user of a problem: Here’s the sample app; it has the “Transparent FileSystem API” in its Models folder. The code was written in Visual Studio 2013 Update 2 RC, but I did not use any of the new Shared Project/Universal App features yet: U2UC.WinRT.OneDriveSync.zip (1.4MB) Don’t forget to associate the project with an app of yours, or it won’t work: Enjoy! Diederik
Using OneDrive files from a Windows 8 Store app 26 March 2014 Diederik-Krols WinRT This article explains how to let a Windows Store app manage a list of files in a working folder on the user’s OneDrive, without continuously opening file pickers. Some apps need to store more personal user data than the roaming folder can handle. A folder on the user’s OneDrive is a nice place to store that data -e.g. as flat files containing serialized business objects- and share it across his devices. Your users installed your app through the Store, so they all have a Microsoft account. And every Microsoft account comes with a OneDrive folder somewhere in the Cloud. So why not make use of it? I made a little app that shows you how to log on to OneDrive, create a working folder for your app, upload a file, enumerate the files in the folder, and read the content of a file. Here’s how it looks like: An HTTP client and some JSON magic would suffice to communicate with the OneDrive services directly, but the Live SDK for Windows, Windows Phone and .NET comes with an object model that does this for you. You can either locally install it, and then make a reference to it in your project: Or you can get it all through Nuget: Under the hood this SDK of course still calls the REST service through HTTP, so you have to activate the Internet capability for your app (and make sure you publish a privacy policy): Before you can use the API, your project needs to be associated with an app that is defined (not necessarily published) in the Store. This will update the manifest, and add an association file: If you don’t associate your project with a Store app, then you may expect an exception: Before your user can access his OneDrive through your app, he or she needs to be authenticated. The following code calls the LiveAuthClient.LoginAsync method, which takes the list of scopes as a parameter. The scopes for this particular call include single sign-on, and read and write access to the OneDrive: private async void SignIn_Executed() { if (!this.isSignedIn) { try { LiveAuthClient auth = new LiveAuthClient(); var loginResult = await auth.LoginAsync(new string[] { "wl.signin", "wl.skydrive", "wl.skydrive_update" }); this.client = new LiveConnectClient(loginResult.Session); this.isSignedIn = (loginResult.Status == LiveConnectSessionStatus.Connected); await this.FetchfolderId(); } catch (LiveAuthException ex) { Debug.WriteLine("Exception during sign-in: {0}", ex.Message); } catch (Exception ex) { // Get the code monkey's attention. Debugger.Break(); } } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } When the call executes, the system sign-in UI opens, unless the user has already signed into his Microsoft account and given consent for the app to use the requested scopes. In most situations, your end user is already logged in and will never have to type his user id and password, and he would see this consent screen only once: The app then needs to create a working folder. I decided to just use the full name of the app itself as the name of the folder: public string FolderName { get { return Package.Current.Id.Name; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s how to create the folder -in the root of the user’s OneDrive- using a LiveConnectClient.PostAsync call: private async void CreateFolder_Executed() { try { // The overload with a String expects JSON, so this does not work: // LiveOperationResult lor = await client.PostAsync("me/skydrive", Package.Current.Id.Name); // The overload with a Dictionary accepts initializers: LiveOperationResult lor = await client.PostAsync("me/skydrive", new Dictionary<string, object>() { { "name", this.FolderName } }); dynamic result = lor.Result; string name = result.name; string id = result.id; this.FolderId = id; Debug.WriteLine("Created '{0}' with id '{1}'", name, id); } catch (LiveConnectException ex) { if (ex.HResult == -2146233088) { Debug.WriteLine("The folder already existed."); } else { Debug.WriteLine("Exception during folder creation: {0}", ex.Message); } } catch (Exception ex) { // Get the code monkey's attention. Debugger.Break(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The app needs to remember the id of the folder, because it is needed in the further calls. Therefore we store it in the roaming settings: ApplicationDataContainer settings = ApplicationData.Current.RoamingSettings; public string FolderId { get { return this.settings.Values["FolderId"].ToString(); } private set { this.settings.Values["FolderId"] = value; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In case you lose the folder id, you can fetch it with a LiveConnectClient.GetAsync call: private async Task FetchfolderId() { LiveOperationResult lor = await client.GetAsync("me/skydrive/files"); dynamic result = lor.Result; this.FolderId = string.Empty; foreach (dynamic file in result.data) { if (file.type == "folder" && file.name == this.FolderName) { this.FolderId = file.id; } } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } You can add files to the working folder with LiveConnectClient.BackGroundUploadAsync: private async Task SaveAsFile(string content, string fileName) { // String to UTF-8 Array byte[] byteArray = Encoding.UTF8.GetBytes(content); // Array to Stream MemoryStream stream = new MemoryStream(byteArray); // Managed Stream to Store Stream to File await client.BackgroundUploadAsync( this.FolderId, fileName, stream.AsInputStream(), OverwriteOption.Overwrite); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .Net developers would be tempted to apply Unicode encoding. Just hold your horses and stick to UTF-8. To convince you that it covers your needs, I added some French (containing accents and other decorations) and Chinese (containing whatever the Bing translator gave me) texts in the source code. After clicking the ‘Save Files’ button in the sample app, the folder and its content become visible in the user’s File Explorer: Using LiveConnectClient.GetAsync you can read the folder’s content to enumerate the list of files in it: private async void OpenFolder_Executed() { try { // Don't forget '/files' at the end. LiveOperationResult lor = await client.GetAsync(this.FolderId + @"/files"); dynamic result = lor.Result; this.files.Clear(); foreach (dynamic file in result.data) { if (file.type == "file") { string name = file.name; string id = file.id; this.files.Add(new OneDriveFile() { Name = name, Id = id }); Debug.WriteLine("Detected a file with name '{0}' and id '{1}'.", name, id); } } } catch (LiveConnectException ex) { Debug.WriteLine("Exception during folder opening: {0}", ex.Message); } catch (Exception ex) { // Get the code monkey's attention. Debugger.Break(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The OneDriveFile class in this code snippet does not come from the API, but is just a lightweight custom class. My sample app is only interested in the name and id of each file, but the API has a lot more to offer: /// <summary> /// Represents a File on my OneDrive. /// </summary> public class OneDriveFile { public string Name { get; set; } public string Id { get; set; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } After that OpenFolder call, the sample app displays the list of files: With a file’s id, we can fetch its content through a LiveConnectClient.BackgroundDownloadAsync call: private async void ReadFile_Executed() { if (this.selectedFile != null) { // Don't forget '/content' at the end. LiveDownloadOperationResult ldor = await client.BackgroundDownloadAsync(this.selectedFile.Id + @"/content"); // Store Stream to Managed Stream. var stream = ldor.Stream.AsStreamForRead(0); StreamReader reader = new StreamReader(stream); // Stream to UTF-8 string. this.SelectedText = reader.ReadToEnd(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s the result in the sample app: For the sake of completeness: LiveConnectClient also hosts asynchronous methods to move, copy, and delete files and folders. This solution has many advantages: You can store more data that the app’s roaming folder can handle. The data is accessible across devices. The end user is not confronted with logon screens or file pickers. You don’t have to provide Cloud storage of your own. There are some drawbacks too: You’ll have to deal with the occasional latency, especially when uploading files to OneDrive. If the client is not always connected, you might need a fallback mechanism to local storage (which uses a different file API). Here’s the full source code of the sample app, it was created with Visual Studio 2013 for Windows 8.1. I cleared the app store association, so you’ll have to hook it to your own account: U2UC.WinRT.OneDriveSample.zip (5.1MB). Enjoy! Diederik
A Floating Behavior for Windows 8 Store apps 06 March 2014 Diederik-Krols WinRT In my previous article I introduced a Floating Control for Windows Store apps, and hinted that it could be rewritten as a Behavior. Well, that’s exactly what I did. This article describes the Floating Behavior: it allows a ContentControl to be dragged around the screen through mouse or touch, while optionally keeping it within its parent and/or on screen. Here’s the Behavior in action; any similarity with the app from my previous article *is* intended: Blend Behaviors are very popular in WPF and Silverlight. They are classes that encapsulate interactive behavior that can be attached to visual elements and that is generally implemented by registering event handlers to that associated element. Behaviors were missing in WinRT until they were introduced with Windows 8.1 and Visual Studio 2013. For a more detailed introduction to Behaviors in WinRT 8.1 I refer to this article by Mark Smith. In WinRT a Behavior is a class that implements the IBehavior interface. The classic Behavior<T> does not exist on this platform. But don’t worry: if you want to upgrade one of your legacy behaviors you can easily resurrect Behavior<T> yourself. There’s an example by Fons Sonnemans right here. Here’s the class declaration for FloatingBehavior:/// <summary> /// Adds Floating Behavior to a ContentControl. /// </summary> public class FloatingBehavior : DependencyObject, IBehavior { // ... } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } Apart from the mandatory interface implementations, the class comes with the exact same dependency properties (IsBoundByParent and IsBoundByScreen) and position calculations as the Floating Control. Here’s how to connect the Behavior to a ContentControl in XAML:<ContentControl> <interactivity:Interaction.Behaviors> <behaviors:FloatingBehavior IsBoundByParent="True" /> </interactivity:Interaction.Behaviors> <!-- Content Here --> </ContentControl> .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } Before you can define or use a Behavior, you need to reference the Behaviors SDK that comes with Blend: When a Behavior is attached to a XAML element, the Attach method is called. Herein we check if the Behavior is attached to a ContentControl. If that’s the case, we register an event handler for the Loaded event; if not, we complain: private ContentControl contentControl; /// <summary> /// Attaches to the specified object. /// </summary> public void Attach(DependencyObject associatedObject) { this.contentControl = associatedObject as ContentControl; if (this.contentControl == null) { throw new Exception("Floating Behavior only applies to ContentControl."); } else { this.contentControl.Loaded += ContentControl_Loaded; } } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } It’s in that Loaded event that we register the core handlers for ManipulationDelta and SizeChanged. These two actually define the Floating behavior, and are explained the previous article. Before we hook these event handlers, we need to cram a Canvas and a Border between the ContentControl and its parent. These are necessary for the position adjustment calculations. The FloatingControl carries these decorations in its style template, but the FloatingBehavior needs to do the plumbing in C#. And it’s actually a tedious operation: you have to pull the ContentControl out of its parent, wrap it in a Border in a Canvas, and then plug it back into the parent - which may retrigger the Loaded event. The pull-out and plug-in operations unconveniently depend on the parent’s type. I added implementations for Panel, ContentControl, and Border [Why is Border not a ContentControl?] but I may be missing some potential host controls here:/// <summary> /// Handles the Loaded event of the ContentControl. /// </summary> private void ContentControl_Loaded(object sender, RoutedEventArgs e) { // Make sure this only runs once. // The Loaded event is also fired when the contentcontrol is moved in the Visual Tree this.contentControl.Loaded -= ContentControl_Loaded; var parent = this.contentControl.Parent; if (parent is Panel) { var panel = parent as Panel; int i = panel.Children.IndexOf(this.contentControl); panel.Children.Remove(this.contentControl); panel.Children.Insert(i, this.Decorated); } else if (parent is ContentControl) { var cc = parent as ContentControl; cc.Content = null; cc.Content = this.Decorated; ; } else if (parent is Border) { var border = parent as Border; border.Child = null; border.Child = this.Decorated; } else { throw new Exception("Unexpected parent, please call a code monkey."); } this.frame = GetClosestParentWithSize(this.border); // No parent. if (this.frame == null) { // We probably never get here. return; } this.frame.SizeChanged += Floating_SizeChanged; } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } Wrapping the control is simple. The Decorated property returns a Canvas:/// <summary> /// Initializes and returns the decorated control. /// </summary> private Canvas Decorated { get { // Canvas var canvas = new Canvas(); canvas.Height = 0; canvas.Width = 0; canvas.VerticalAlignment = VerticalAlignment.Top; canvas.HorizontalAlignment = HorizontalAlignment.Left; // Border this.border = new Border(); this.border.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY | ManipulationModes.TranslateInertia; this.border.ManipulationDelta += this.Border_ManipulationDelta; // Move Canvas properties from control to border. Canvas.SetLeft(border, Canvas.GetLeft(this.contentControl)); Canvas.SetLeft(this.contentControl, 0); Canvas.SetTop(border, Canvas.GetTop(this.contentControl)); Canvas.SetTop(this.contentControl, 0); // Move Margin to border. this.border.Padding = this.contentControl.Margin; this.contentControl.Margin = new Thickness(0); // Connect the dots this.border.Child = this.contentControl as UIElement; canvas.Children.Add(this.border); return canvas; } } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } When the Behavior is removed from the ContentControl, the Detach method from IBehavior is called. That’s where we remove the event handlers:/// <summary> /// Detaches this instance from its associated object. /// </summary> public void Detach() { this.contentControl.Loaded -= ContentControl_Loaded; this.frame.SizeChanged -= Floating_SizeChanged; } .csharpcode { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { font-size: small; font-family: consolas, "Courier New", courier, monospace; color: black; background-color: #ffffff } .csharpcode pre { margin: 0em } .csharpcode .rem { color: #008000 } .csharpcode .kwrd { color: #0000ff } .csharpcode .str { color: #006080 } .csharpcode .op { color: #0000c0 } .csharpcode .preproc { color: #cc6633 } .csharpcode .asp { background-color: #ffff00 } .csharpcode .html { color: #800000 } .csharpcode .attr { color: #ff0000 } .csharpcode .alt { width: 100%; margin: 0em; background-color: #f4f4f4 } .csharpcode .lnum { color: #606060 } The floating behavior only applies to ContentControl so I’m ignoring the AssociatedObject property in my code since it is not strongly typed. Here’s the implementation, just for the sake of completeness:/// <summary> /// Gets the <see cref="T:Windows.UI.Xaml.DependencyObject" /> to which the <seealso cref="T:Microsoft.Xaml.Interactivity.IBehavior" /> is attached. /// </summary> /// <remarks>Not used. We prefer the strongly type contentControl field.</remarks> public DependencyObject AssociatedObject { get { return this.contentControl; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s the Behavior in action when the app is resized. the restrained controls nicely stay within the green rectangle, or on screen: I personally prefer the clean implementation of ‘floating’ as a Control over this Behavior version. But this example certainly proves that the new Windows 8.1 Store app Behaviors are capable of doing more complex things than you might have expected. Here’s the source code, it was written in Visual Studio 2013 for Windows 8.1. The Behavior is in its own project: U2UC.WinRT.FloatingBehaviorSample.zip (736.5KB) Enjoy! Diederik
A Floating Control for Windows 8 Store apps 23 February 2014 Diederik-Krols WinRT Windows 8 Store apps need to run on a huge number of screen resolutions. That makes positioning your controls not always an easy task. So why not let the end user decide where a control should be placed? This article describes how to build a XAML and C# ContentControl that can be dragged around (and off) the screen by using the mouse or touch. The control comes with dependency properties to optionally keep it on the screen or within the rectangle occupied by its parent control. These boundaries apply not only when the control is manipulated, but also when its parent resizes (e.g. when you open multiple apps or when the screen is rotated). Here’s a screenshot of the attached sample app. The main page contains some instances of the so-called Floating control. Two of these are bound to their parent in the visual tree -the yellow rectangle-, one is bound by the screen, and the remaining one is entirely free to go where you send it: I created the Floating control as a custom control that inherits from ContentControl, but I think it could be built as a behavior too. Here’s how to use it in your XAML:<controls:Floating IsBoundByParent="True"> <!-- Your content comes here --> </controls:Floating> <controls:Floating IsBoundByScreen="True"> <!-- Your content comes here --> </controls:Floating> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The default style of the Floating control is defined in the Themes\Generic.xaml file:<!-- Floating Control Style --> <Style TargetType="local:Floating"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:Floating"> <!-- This Canvas never covers other controls --> <Canvas Background="Transparent" Height="0" Width="0" VerticalAlignment="Top" HorizontalAlignment="Left"> <!-- This Border handles the dragging --> <Border x:Name="PART_Border" ManipulationMode="TranslateX, TranslateY, TranslateInertia" > <ContentPresenter /> </Border> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The heart of the control is a ContentPresenter that will contain whatever you put in it. It is hosted in a Border that responds to translation manipulations with inertia. That Border moves around within a Canvas that constitutes the outside of the Floating control. That Canvas has a zero height and width, so that it doesn’t cover other controls (e.g. other Floating controls within the same parent). A Canvas doesn’t clip its content to its bounds, so the Border doesn’t really care about its parent being sizeless: it can be dragged around to everywhere. Unless we apply some restrictions: the Floating control comes with the boundary properties IsBoundByParent and IsBoundByScreen. These are defined as dependency properties:/// <summary> /// A Content Control that can be dragged around. /// </summary> [TemplatePart(Name = BorderPartName, Type = typeof(Border))] public class Floating : ContentControl { private const string BorderPartName = "PART_Border"; public static readonly DependencyProperty IsBoundByParentProperty = DependencyProperty.Register("IsBoundByParent", typeof(bool), typeof(Floating), new PropertyMetadata(false)); public static readonly DependencyProperty IsBoundByScreenProperty = DependencyProperty.Register("IsBoundByScreen", typeof(bool), typeof(Floating), new PropertyMetadata(false)); private Border border; /// <summary> /// Initializes a new instance of the <see cref="Floating"/> class. /// </summary> public Floating() { this.DefaultStyleKey = typeof(Floating); } /// <summary> /// Gets or sets a value indicating whether the control is bound by its parent size. /// </summary> public bool IsBoundByParent { get { return (bool)GetValue(IsBoundByParentProperty); } set { SetValue(IsBoundByParentProperty, value); } } /// <summary> /// Gets or sets a value indicating whether the control is bound by the screen size. /// </summary> public bool IsBoundByScreen { get { return (bool)GetValue(IsBoundByScreenProperty); } set { SetValue(IsBoundByScreenProperty, value); } } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The control’s main job is to calculate the physical position of the Border against its parent Canvas. When the control is moved or when the parent resizes, it will adjust the Canvas.Left and Canvas.Top attached properties of the Border. An alternative approach would be to apply and configure a translation to that Border. In the OnApplyTemplate we look for the Border in the style template, and register an event handler for ManipulationDelta:protected override void OnApplyTemplate() { // Border this.border = this.GetTemplateChild(BorderPartName) as Border; if (this.border != null) { this.border.ManipulationDelta += this.Border_ManipulationDelta; } else { // Exception throw new Exception("Floating Control Style has no Border."); } this.Loaded += Floating_Loaded; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In that same method we also apply some adjustments to drastically simplify the calculations. The Floating control may be hosted in a Canvas with a Top and Left, or it may be defined with a Margin around it. Since we’re controlling the position of the Border, not the Floating, I decided to let the Border take over these settings. Canvas properties are stolen from the Floating control, and the Margin outside the Floating is transformed into a Padding inside the Border:// Move Canvas properties from control to border. Canvas.SetLeft(this.border, Canvas.GetLeft(this)); Canvas.SetLeft(this, 0); Canvas.SetTop(this.border, Canvas.GetTop(this)); Canvas.SetTop(this, 0); // Move Margin to border. this.border.Padding = this.Margin; this.Margin = new Thickness(0); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } When the control is loaded, we look up the parent to hook an event handler for the SizeChanged event:private void Floating_Loaded(object sender, RoutedEventArgs e) { FrameworkElement el = GetClosestParentWithSize(this); if (el == null) { return; } el.SizeChanged += Floating_SizeChanged; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Observe that we don’t just look for a resize of the control itself or its direct parent. That’s because these could have an actual height and width of zero – and hence would be ignored by SizeChanged. That seems to happen to Grid and Canvas controls –typical parents for a Floating- very often. So we’re actually looking for the closest parent with a real size:/// <summary> /// Gets the closest parent with a real size. /// </summary> private FrameworkElement GetClosestParentWithSize(FrameworkElement element) { while (element != null && (element.ActualHeight == 0 || element.ActualWidth == 0)) { element = element.Parent as FrameworkElement; } return element; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } When the Border is moved around, we calculate its desired position, and then adjust it so it stays within the boundaries:private void Border_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) { var left = Canvas.GetLeft(this.border) + e.Delta.Translation.X; var top = Canvas.GetTop(this.border) + e.Delta.Translation.Y; Rect rect = new Rect(left, top, this.border.ActualWidth, this.border.ActualHeight); AdjustCanvasPosition(rect); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } When the parent is resized, we apply the same logic to the current position of the Border:private void Floating_SizeChanged(object sender, SizeChangedEventArgs e) { var left = Canvas.GetLeft(this.border); var top = Canvas.GetTop(this.border); Rect rect = new Rect(left, top, this.border.ActualWidth, this.border.ActualHeight); AdjustCanvasPosition(rect); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } If one or both of the boundary properties is set, then we may need to apply a correction to the Canvas.Top and Canvas.Left of the Border. That’s what the following methods do:/// <summary> /// Adjusts the canvas position according to the IsBoundBy* properties. /// </summary> private void AdjustCanvasPosition(Rect rect) { // No boundaries if (!this.IsBoundByParent && !this.IsBoundByScreen) { Canvas.SetLeft(this.border, rect.Left); Canvas.SetTop(this.border, rect.Top); return; } FrameworkElement el = GetClosestParentWithSize(this); // No parent if (el == null) { // We probably never get here. return; } var position = new Point(rect.Left, rect.Top); ; if (this.IsBoundByParent) { Rect parentRect = new Rect(0, 0, el.ActualWidth, el.ActualHeight); position = AdjustedPosition(rect, parentRect); } if (this.IsBoundByScreen) { var ttv = el.TransformToVisual(Window.Current.Content); var topLeft = ttv.TransformPoint(new Point(0, 0)); Rect parentRect = new Rect(topLeft.X, topLeft.Y, Window.Current.Bounds.Width - topLeft.X, Window.Current.Bounds.Height - topLeft.Y); position = AdjustedPosition(rect, parentRect); } // Set new position Canvas.SetLeft(this.border, position.X); Canvas.SetTop(this.border, position.Y); } /// <summary> /// Returns the adjusted the topleft position of a rectangle so that is stays within a parent rectangle. /// </summary> private Point AdjustedPosition(Rect rect, Rect parentRect) { var left = rect.Left; var top = rect.Top; if (left < -parentRect.Left) { left = -parentRect.Left; } else if (left + rect.Width > parentRect.Width) { left = parentRect.Width - rect.Width; } if (top < -parentRect.Top) { top = -parentRect.Top; } else if (top + rect.Height > parentRect.Height) { top = parentRect.Height - rect.Height; } return new Point(left, top); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s what happens when the app is resized or rotated, the bound Floating controls remain inside the box and/or on screen: Here’s the full source code. The Floating control is immediately reusable, since it lives in its own project: U2UC.WinRT.FloatingSample.zip (1.9MB) Enjoy! Diederik
LexDB performance tuning in a Windows 8 Store app 09 February 2014 Diederik-Krols WinRT This article explains how to create fast queries against a LexDB database in a Windows 8 Store app, and how to keep these queries fast. LexDB is a lightweight, in-process object database engine. It is written in C# and can be used in .NET, Silverlight, Windows Phone, Windows Store, and Xamarin projects. For an introduction to using the engine in a Windows 8 Store app, I refer to a previous blog article of mine. LexDB is an object database, it is not as relational as e.g. SQLite. But it still comes with the possibility of indexing, and it has commands to reorganize the stored data and release storage. This article zooms in on these features. Here’s a screenshot of the attached sample app. It requests for the size of the Person table to measure, and it comes with buttons to trigger a database reorganization (when you notice that the queries’ performance goes down) and to start measuring a SELECT statement using different indexes: The app is of course a port of my previous blog post on SQLite. It stores the same business object (Person):/// <summary> /// Represents a person. /// </summary> internal class Person { /// <summary> /// Gets or sets the identifier. /// </summary> public int Id { get; set; } /// <summary> /// Gets or sets the name. /// </summary> public string Name { get; set; } /// <summary> /// Gets or sets the description. /// </summary> public string Description { get; set; } /// <summary> /// Gets or sets the status. /// </summary> public int Status { get; set; } /// <summary> /// Gets or sets the day of birth. /// </summary> public DateTime BirthDay { get; set; } /// <summary> /// Gets or sets the picture. /// </summary> public byte[] Picture { get; set; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } When I migrated the database code from SQLite to LexDB I immediately noticed that SELECT statements in LexDB are pretty fast, but INSERT and DELETE statements are an order of magnitude slower, and run slower as the table size increases. That’s definitely something to consider, but as long as you stick to asynchronous calls, and as long as your app does not do any bulk operations, it’s nothing to really worry about. The performance of INSERT and DELETE statements surely had an impact on my sample app: the original app simply recreated the table when the target size was changed. With LexDB this isn’t an option anymore: the app now adds only the missing objects, or removes them. This allows you to gradually build up a large test table. So when you set a new size for the table, please use relatively small increments (depending on your hardware): you’d be amazed how long it takes to insert 1000 new objects into the table! Here’s the initialization of the database. It logs the whereabouts of the physical files so you can monitor these. And it creates a table with two indexes: an index only on name, and a ‘covering’ index on name and status:static Dal() { // Reveal the location of the database folder Debug.WriteLine(string.Format(@"Databases are stored at {0}\Lex.Db\.", ApplicationData.Current.LocalFolder.Path)); // Create database db = new DbInstance("storage"); // Define table mapping db.Map<Person>().Automap(p => p.Id, true).WithIndex("ix_Person_Name", p => p.Name).WithIndex("ix_Person_Name_Status", p => p.Name, p => p.Status); // Initialize database db.Initialize(); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s the benchmark query that retrieves a filtered list of persons:public static List<Person> GetPersons() { return db.Table<Person>().Where(p => p.Name == "James" && p.Status == 2).ToList(); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Since LexDB is an object database, any query against the Person table will return a list of fully populated Person instances. There’s no way to do any projection to return only Id, Name, and Status. [Maybe there is a way, but I didn’t find it. After all, the official documentation is a short series of blog posts and the source code.] All query plans will eventually end up in the base table. If you want to use an index for a query, then you have to tell it to the engine upfront:public static List<Person> GetPersonsIndex() { // Horrible performance // return db.Table<Person>().IndexQueryByKey("ix_Person_Name", "James").ToLazyList().Where(p => p.Value.Status == 2).Select(p => p.Value).ToList(); return db.Table<Person>().IndexQueryByKey("ix_Person_Name", "James").ToList().Where(p => p.Status == 2).ToList(); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The IndexQueryByKey returns the primary keys of the requested objects (WHERE name=?), and the query plan continues in the base table (file) to filter out the remaining objects (WHERE status=?). That’s why I didn’t notice any performance improvements: in most cases, the raw query ran even faster. So unless you’re looking for a very scarce value, a regular index on a LexDB table will NOT be very helpful. The same is true of course in SQL Server: indexes with a low density will be ignored. But in a relational database it’s the engine that decides whether or not to use an index, here the decision is up to you. So let’s verify the impact of a covering index. Mind that the term ‘covering’ here only applies to the WHERE-clause, since there’s no way to skip the pass through the base table:public static List<Person> GetPersonsCoveringIndex() { return db.Table<Person>().IndexQueryByKey("ix_Person_Name_Status", "James", 2).ToList(); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } As you see in the above app screenshots, the ‘covered’ query runs faster than the original query in all cases. But on the other hand: the difference is not that significant and will probably not be noticed by the end users. I can imagine that the real added value of these indexes will appear in more complex queries (e.g. when joining multiple tables). Let’s jump to the administrative part. The fragmentation caused by INSERT, UPDATE and DELETE statements has a bad influence on indexes: they get gradually slower over time. In most relational databases this is a relatively smooth process. It can be stopped by reorganizing or rebuilding the index. In LexDB this also happens, but the degradation is a less than smooth process. If you add and remove a couple of times a block of let’s say 1000 objects, then you’ll observe only subtle changes in the response time of the SELECT statements. Very suddenly the basic SELECT statement runs about ten times slower, while the indexed queries continue to do their job with the same response time. At that moment, it’s time to rebuild the base table, and optionally release storage:public static void Reorganize() { Debug.WriteLine("Before compacting:"); LogPersonInfo(); // Reorganizes the file (huge impact on performance). db.Table<Person>().Compact(); // For the sake of completeness. db.Flush<Person>(); db.Flush(); Debug.WriteLine("After compacting:"); LogPersonInfo(); } private static void LogPersonInfo() { var info = db.Table<Person>().GetInfo(); Debug.WriteLine(string.Format("* Person Data Size: {0}", info.DataSize)); Debug.WriteLine(string.Format("* Person Effective Data Size: {0}", info.EffectiveDataSize)); Debug.WriteLine(string.Format("* Person Index Size: {0}", info.IndexSize)); } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Immediately, everything is back to normal: Compacting a fragmented table is a very rapid operation (less than a second) and it has an immediate result on the queries. Compacting the data is something you may want to do on start-up, e.g. in an extended splash screen routine. Even though LexDB is not as advanced as SQLite, it comes with the necessary infrastructure to get some speed and maintain it. That’s all for today. Here’s the code, it was written in Visual Studio 2013 for Windows 8.1: U2UC.WinRT.LexDbIndexing.zip (852.7KB) Enjoy! Diederik
SQLite performance tuning in a Windows 8 Store app 03 February 2014 Diederik-Krols WinRT This article explains how to monitor and optimize a SQLite query in a Windows 8 Store app by adding indexes and/or rewriting the query. I’ll be using the WinRT SQLite wrapper from the Visual Studio Gallery. I assume that you know how to install and use it, but feel free to check a previous blog post of mine for an introduction. As usual I created a small app. It allows you to select the number of records to insert in a table of Persons. The app then measures the execution time of a SELECT statement against that table, using four different strategies. Here’s how the app looks like: This is the definition of Person, a more or less representative business model class:/// <summary> /// Represents a person. /// </summary> internal class Person { /// <summary> /// Gets or sets the identifier. /// </summary> [PrimaryKey, AutoIncrement] public int Id { get; set; } /// <summary> /// Gets or sets the name. /// </summary> [MaxLength(64)] public string Name { get; set; } /// <summary> /// Gets or sets the description. /// </summary> public string Description { get; set; } /// <summary> /// Gets or sets the status. /// </summary> /// <remarks>Is an enum in the viewmodel.</remarks> public int Status { get; set; } /// <summary> /// Gets or sets the day of birth. /// </summary> public DateTime BirthDay { get; set; } /// <summary> /// Gets or sets the picture. /// </summary> /// <remarks>Is a blob in the database.</remarks> public byte[] Picture { get; set; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The query that we’re going to monitor uses projection (not all columns are fetched – no SELECT *) as well as filtering (not all rows are fetched – there’s a WHERE clause). We’re only interested in the Id, Name, and Status columns of the persons named James with a status of 2. I started with the following query: public static List<Person> GetPersonsOriginal() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; var result = from p in db.Table<Person>() where p.Name == "James" && p.Status == 2 select new Person() { Id = p.Id, Name = p.Name, Status = p.Status }; return result.ToList(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } To my big surprise I noticed that the non-selected fields (e.g. Description and Image) were filled too in the result set. The projection step was clearly not executed: Next, I tried to return an anonymous type: public static List<object> GetPersonsAnonymous() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; var result = from p in db.Table<Person>() where p.Name == "James" && p.Status == 2 select new { Id = p.Id, Name = p.Name, Status = p.Status }; return result.ToList<object>(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Unfortunately it threw an exception: I should have known: the WinRT wrapper relies heavily on LINQ but the SQLite runtime itself is written in unmanaged C, so it’s allergic to anonymous .NET classes. It turns out that when using the SQLite wrapper to run a query that returns a result set, you have to provide the signature of that result. The LINQ query calls Query<T> in the SQLite class and needs to provide the type parameter. Since I just needed a couple of columns, I created a Result class to host the query’s return values: internal class Result { /// <summary> /// Gets or sets the identifier. /// </summary> public int Id { get; set; } /// <summary> /// Gets or sets the name. /// </summary> public string Name { get; set; } /// <summary> /// Gets or sets the status. /// </summary> public int Status { get; set; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s how the second version of the query looks like: public static List<Result> GetPersons() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; var result = from p in db.Table<Person>() where p.Name == "James" && p.Status == 2 select new Result() { Id = p.Id, Name = p.Name, Status = p.Status }; return result.ToList(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } I must admit that -even for a very large table- the results come back very rapidly. I guess that most apps do not require any SQLite performance tuning. Asynchronous SELECT statements will suffice, and these are provided by the WinRT wrapper. SQLite is so fast because it is a genuine relational database: its data is not simply serialized, but stored and (proactively) cached in fixed-size pages that are hooked into B-trees (for indexes) or B+-trees (for tables). Here’s an overview of the internal mechanisms, the illustration comes out of the definitive guide to SQLite. As a first optimization, I decorated the database with an index on the Name column:public static void CreateIndex() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; db.Execute("CREATE INDEX `ix_person_name` ON `Person` (`Name` ASC)"); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The query ran about three times faster. That’s not bad. Then I added an index on all requested fields, a so-called covering index. Theoretically this is the fastest way to get the data: everything is found in the index itself, there’s no need to read the table. Here’s the covering index definition: public static void CreateCoveringIndex() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; db.Execute("CREATE INDEX `ix_person_name_status` ON `Person` (`Name` ASC, 'Status' ASC)"); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } On average, this query runs four times faster than the original. It’s getting better. Then I activated the tracing and started to add the extra measurements, e.g. on the creation of the indexes. The database trace revealed the query for the so-called covering index: select * from "Person" where (("Name" = ?) and ("Status" = ?)) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } That’s still a SELECT *, so the LINQ-based query was actually selecting ALL of the columns. It turns out that the SQLite wrapper calls Query<T> with the table type (Person) as T. I expected it would use the projected type (Result). That’s the reason why all fields were populated in the very first query. I wanted to measure a real covering index, so I decided to call Query<T> myself, with a custom query (SQL-based rather than LINQ-based) that fetches only the projected columns: public static List<Result> GetPersons2() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; var result = db.Query<Result>("select Id, Name, Status from Person where ((Name = ?) and (Status = ?))", "James", 2); return result.ToList(); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } [Instead of providing the Result type, I think I could have created a TableMapping programmatically. TableMapping smells like SQLite’s version of a strongly typed ADO.NET dataset. When I come to think of it: it would be nice to have a designer for this in Visual Studio.] Anyway, this last query runs about five times faster than the original one, and the trace was not showing a SELECT * anymore: select Id, Name, Status from Person where ((Name = ?) and (Status = ?)) .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } But still I wanted to double check the used strategy: SQLite has the “EXPLAIN QUERY PLAN” syntax to reveal the actual query plan. After some vain calls, I decided to apply the same approach as for the custom SELECT query: define a type for the result set, and call Query<T>. Neither in SQLite nor in the wrapper did I find a way to discover the signature of the result. It would be nice to have something like the SET FMTONLY from SQL Server. Fortunately I found a description of the results in the official SQLite documentation. Here’s the structure of a SQLite query plan line: public class QueryPlanLine { public int selectid { get; set; } public int order { get; set; } public int from { get; set; } public string detail { get; set; } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s a method to log a query plan: public static void GetQueryPlan(string query) { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; // Get query plan List<QueryPlanLine> queryPlan = db.Query<QueryPlanLine>(string.Format("explain query plan {0}", query)); foreach (var line in queryPlan) { Debug.WriteLine(line.detail); } }; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Here’s the list of different query plans for the four tested strategies: it ranges from a table scan to a covering index: Achievement unlocked! :-) Here’s the app again after it went through the different optimizations. It is clearly getting faster at each step: Just remember that in a busy database the indexes need to be rebuilt/reorganized from time to time. That can be done in SQLite with the REINDEX statement. Here’s how to rebuild the covering index: public static void ReorganizeCoveringIndex() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; db.Execute("REINDEX `ix_person_name_status`"); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } I guess you can build an auto-tuning mechanism to discover slow indexes with the ANALYZE statement, and then rebuild these. But it’s probably easier rebuild the whole database from time to time *and* release disk space whilst doing that. The magic word in SQLite for that is: VACUUM.public static void RebuildDatabase() { using (var db = new SQLiteConnection(DbPath)) { // Activate Tracing db.Trace = true; db.Execute("VACUUM"); } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Although SQLite is impressively fast, I was able to make a simple query on a single table more than 5 times faster with just a small effort. I assume that you could achieve even better results on a complex join. You can easily performance tune SQLite queries by creating indexes and/or taking control of the SELECT statement yourself. Here’s the code, it was written in Visual Studio 2013 for Windows 8.1: U2UC.WinRT.SQLiteIndexing.zip (257.7KB) Enjoy! Diederik