Click here to Skip to main content
15,867,141 members
Articles / Desktop Programming / Universal Windows Platform

Programming Windows 10 Desktop: UWP Focus (7 of N)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
29 Nov 2017CPOL20 min read 14.4K   719   6   2
Get Started in UWP (moving away from WinForm) Chapter 7 Saving journal entries in the file system

Introduction

Please check out the previous entries to get up to speed with our ongoing project to create Daily Journaling app via UWP.

Programming Windows 10: UWP Focus (1 of N)[^]

Programming Windows 10: UWP Focus (2 of N)[^]

Programming Windows 10: UWP Focus (3 of N)[^]

Programming Windows 10 Desktop: UWP Focus (4 of N)[^]

Programming Windows 10 Desktop: UWP Focus (5 of N)[^]

Programming Windows 10 Desktop: UWP Focus (6 of N)[^]

Background

This is my continuing attempt to tell the story of Windows 10 Desktop Development via the UWP (Universal Windows Platform).  This time, after we overcome some XAML challenges, we delve into saving our files into the file system.

Now that we can create new journal entries for each day, we need to provide a way to save the data that the user types or pastes into the RichEditBox.  

 

Organizing Our Journal Entry Files

I’d like to organize the files so that each month’s entries are in the same folder.  In the past I’ve created what I call the Y-M directory Year-Month which looks like 2017-11. This format sorts nicely in File Explorer and allows us to find the physical file entries later very easily.

 

Inside each month folder we will need a scheme for naming each file that is created.  This time we will use Y-M-D-N  which will represent Year-Month-Day-EntryNumber. Since there can be one or more entries each day, we need a way to differentiate them and keeping a counter number at the end of the file name makes things very easy and keeps everything organized.

Now all we need to do is :

  • Provide a way for user can Save an entry (add a button to CommandBar)

  • Provide a way for the user to Delete an entry (add a button to CommandBar)

 

After we provide a way for the user to launch the action she wants to take we’ll need to write the code which actually saves or deletes the associated file.  If you’ve the File System in the past under WinForm development and this is your first time using it under the UWP paradigm you will learn (as I did) that everything is different.  

 

First, let’s update our XAML to add our new AppBarButtons.

 

DailyJournal_v006.zip

If you don’t already have the code from the last article, go ahead and download it and open it in Visual Studio.

Adding New Buttons: Copying Previous Code

To add the new Save and Delete buttons I’m simply going to copy the code from the previous button we added (CreateNewEntryButton) and altering it.

However, we will have to be careful to not reuse the x:Name or Click values, of course, because those have to be unique to every button.  

 

Image 1

 

I’ll add the new Save button first and to do that, I’ll:

  1. Copy the entire AppBarButton tag

  2. Paste right after the closing tag of our existing AppBarButton

  3. Change the name to SaveEntryButton

  4. Delete the Click attribute and its value entirely

  5. Change the icon to Save (a disk icon - please refer to https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.symbol for all icons)

  6. Change the Label to “Save”

  7. Change Tooltip (more about tooltips later) to "Save (Ctrl+S)"

 

The final XAML that you can copy / paste into your file is:

XML
<AppBarButton

x:Name="SaveEntryButton"

Icon="Save"

Label="Save"

ToolTipService.ToolTip="Save (Ctrl+S)" >

</AppBarButton>

When you add that XAML you will see the button appear in the DesignView.

 

Image 2

 

After you add the XAML and see the Save (disk) button appear, go ahead and double-click it to add a new Click event handler to your MainPage.xaml.cs.

When you do, Visual Studio will add the handler and open up MainPage.xaml.cs for you.

 

Determine Which RichEditBox Is Selected

The first thing we need to know so we can save the data is which RichEditBox is currently displayed?  However, to determine which RichEditBox is currently selected we need to add some event handlers so when certain actions occur we store the current visible RichEditBox so we can access it and use it.

XAML / User Interface Clean Up

To do that work we need to clean up a few things in our XAML because currently we have an extra PivotItem we do not need.  We need to get rid of the second PivotItem in our XAML: the one which contains the MainRichEdit2.

 

Our final app will initially display only Entry1 each day (not Entry2) so this PivotItem is not needed and is only going to confuse us so let’s get rid of it.

Delete PivotItem XAML

Let’s do that now. Select it and hit your [Delete] key.

 

Image 3


Setting Focus

The next thing we want to do is insure that whenever our first PivotItem is Loaded or obtains focus (user is interacting with it) then we set focus onto the RichEditBox.  We want to set focus to the RichEditBox because that is how the user actually wants to interact with our app.  He doesn’t actually care about the PivotItem, because the PivotItem is simply a means to get to the RichEditBox.  Since this is true, we need to insure that whenever the user clicks a PivotItem header focus gets set to the associated RichEditBox.

Outer Container

Also, keep in mind that when you click on a PivotItem’s Header you are actually first clicking on the outer container : the Pivot.  The Operating System propagates your click on the Pivot down into the PivotItem (inner container) but the way to set the focus on the associated RichEditBox is to add a handler so that any time the outer Pivot is clicked we check which PivotItem is clicked and then set the focus to its associated RichEditBox.  

 

When the outer Pivot item is clicked the GotFocus event handler is actually called.  It is not, as I first assumed also, the Click event.

This is a lot of work isn’t it?  This is what developers do.  This is why we have to understand all these crazy little details.

Add GotFocus Event Handler

Let’s Add the GotFocus handler to our Pivot.

We’ve added events to XAML elements before but let’s go through it again.

  1. Click the Pivot element in the XAML (while viewing MainPage.xaml)

  2. In the Properties window (usually underneath the Solution Explorer on the right), click the [Lightning bolt] icon to display a list of Event Handlers that this control supports.

  3. Scroll down in the list until you see the GotFocus Event.

  4. Double-click on the TextBox next to the GotFocus event.

 

Image 4

 

When you double-click on the GotFocus TextBox, Visual Studio will add the Event handler to your XAML and add the new method to MainPage.xaml.cs.

 

Image 5

 

This is the easy way to add an event handler because Visual Studio helps you by doing most of the work for you.  In a moment we’ll see how we can add our own event handlers via C# code.

Add Code To Event Handler: Set Focus On RichEditBox

Let’s think about what we want the app to do when the user clicks on the PivotItem (via clicking the Pivot).

  • Get the associated PivotItem

  • Set keyboard focus on the associated RichEditBox

 

Here’s the code to do that work.  It requires that we obtain references to all of the affected objects (Pivot, PivotItem, RichEditBox).

C#
Pivot p = sender as Pivot;

PivotItem pi = p.SelectedItem as PivotItem;

RichEditBox reb = pi.Content as RichEditBox;

reb.Focus(FocusState.Keyboard);

 

Image 6

 

I’ll use the line numbers from Visual Studio to explain what the code is doing.

 

When the rootPivot_GotFocus event fires it provides two arguments.  In our code we don’t need to use the second argument but we do use the first one (sender).  Since we know that GotFocus was called from a Pivot element we know that the sender object will be a Pivot, however, we need to convert the generic sender object to the type we know we need to use.  We can do this easily with the as keyword and that is what we are doing here on line 65.

 

Now that we have a valid Pivot we can use it to get to the PivotItem that it contains on line 66.

The currently selected Pivot contains a SelectedItem property which is the associated PivotItem so we get that and store it in our local PivotItem (pi) variable.

All For RichEditBox Reference

We do all this work just so we can get to the RichEditBox that is contained inside the PivotItem.  We know that we originally set our PivotItem’s Content property to b a RichEditBox so once again we use the as keyword to get to the associated RichEditBox on line 67.

 

Finally, we have a valid RichEditBox.  This was the whole reason we went through all of this so that on line 68 we can set the keyboard focus to the RichEditBox.  

Download Code and Try It

Now, if we run the app and click on the Entry1 (Pivot) then the I-beam cursor will automatically appear in the RichEditBox and the user can begin typing immediately.

 

Get the DailyJournal_v007.zip and build the code, run it and click on Entry1 and you will see that is true.  Also, now when you add a New Entry, that Entry will receive focus.  

 

Image 7

 

Did You Recognize the Existing Problem?

However, you should’ve noticed there is still a problem.

The app should probably go ahead and set focus to the RichEditBox immediately when it loads up.  To fix that however, we need to add a Loaded event handler to the PivotItem.  

A Bit Confusing

This is a bit confusing because we’ve been working with the Pivot, but now we need to add an event to the PivotItem.  

You may have thought (as I previously did) that you could just set focus to the RichEditBox when the MainPage has loaded.  However, you cannot be sure the PivotItem itself has been loaded and if it has not then the RichEditBox would not be loaded and that would cause the app to crash.

 

Let’s go add the Loaded event handler to our PivotItem which exists in the XAML.  After that we’ll begin to see how to add events to the programmatically added elements (PivotItems and associated RichEditBoxes).

Adding the Loaded Event Handler To the PivotItem

It’s the same work on a different element to add the Loaded event handler.

 

Image 8

 

  1. Click the PivotItem element in the XAML (while viewing MainPage.xaml)

  2. In the Properties window (usually underneath the Solution Explorer on the right), click the [Lightning bolt] icon to display a list of Event Handlers that this control supports.

  3. Scroll down in the list until you see the Loaded Event.

  4. Double-click on the TextBox next to the Loaded event.

 

The code we will add to our PivotItem Loaded event will be very similar but will not require us to get our PivotItem reference from the Pivot.  Instead, we will have a reference to the associated PivotItem passed to us via the sender object (first parameter of the PivotItem_Loaded method.

Here’s the code we will add to the Loaded event:

C#
PivotItem pi = sender as PivotItem;

           RichEditBox reb = pi.Content as RichEditBox;

           reb.Focus(FocusState.Keyboard);

 

Image 9

 

This demonstrates that the sender object becomes whatever type which initiated the firing of the event. In this case, when the PivotItem Loaded event handler fires the sender is a PivotItem.  

So in this case we just need to get the PivotItem’s Content (RichEditBox) so we can set focus on it.

When A PivotItem Loads: Set Focus to RichEditBox

Now, when we run the app the RichEditBox will get focus immediately (even before the user Clicks the Pivot).  This will also insure that whenever a PivotItem loads that it’s associated RichEditBox gets the focus.  That is important since it is the way the user will actually interact with the app.

Get Code and Try It

Go ahead and build and run it if you’re following along.  Or download DailyJournal_v008.zip and build and run and try it.

Results of Loaded Event

Now, even before you click the Entry1 PivotItem header, you should see the I-beam cursor in the RichEditBox.

However, there are still issues which will cause user interaction errors.

What Problem Continues to Exist?

It’s a bit tricky because if you start the app and click the New Entry button you will see that the RichEditBox on the new Entry will get focus.  However, that is only because you had focus in the Entry1 RichEditBox and we get lucky.

Recreate The Problem

If you first click on the CalendarView control (set focus on CalendarView) and then click the New Entry button you will see that the RichEditBox does not get an I-beam cursor because it does not get focus.  This will cause us issues going forward.  

Why Does This Issue Occur?

This problem occurs because the generated PivotItem’s and associated generated RichEditBoxes do not have the event handlers activated.  Keep in mind that we added the event handlers to the specific PivotItem that exists in the XAML, but a new PivotItem and RichEditBox is generated every time we click the New Entry button.  We need to add event handlers to the generated objects so they will work properly.

 

Let’s go fix that.

Back to CreateNewEntryButton Method

To Fix this problem we need to add an event handler to every PivotItem that is generated when the user clicks the New Entry button.

It’s not difficult, but it is kind of tricky to find if you’ve never done it before.

As a matter of fact, it’s only one line of code because we’ve already actually written the method (PivotItem_Loaded).

 

First, take a look at the current CreateNewEntryButton_Click method to get our bearings:

Image 10

 

Showing All Code

I’m also showing the rest of the code because we are going to use the PivotItem_Loaded method to fix our problem and I wanted it to be visible.

Examine my listing of the CreateNewEntryButton_Click closely and you’ll see a empty line at 59 (Visual Studio line numbers shown in image).

That is where I’m going to add the line of code to fix our problem.

We need to tell every generated PivotItem (pi) that it should register the event handler for the Loaded event.  We also want to tell it that the event handler method will be the method we already wrote : PivotItem_Loaded.

 

If you begin to type on line 59 and start typing pi.L you will see that Intellisense will try to help you.

 

Image 11

 

When Intellisense pops up you’ll see a list of Event Handlers ([lightning bolt] icons) and Properties (wrench icons) and (not shown here) methods (functions) (box icons) that are supported by the object.

In our case we want the Loaded event, so if you want you can double-click that item in the list and it will be added to your code.  The value that Loaded is set to is a delegate (method that will be called when the Loaded event fires).  The syntax is a little different because we are adding our method to any other methods that might be already there so we use a += to do this.

Here’s the full line of code :

C#
pi.Loaded += PivotItem_Loaded;

Problem Fixed

That’s it.  Now the problem is fixed.  Whenever you add a new journal entry the Loaded event will set the focus on the RichEditBox.

Even if you click on another control such as the CalendarView first and then click the New Entry button you will find that the RichEditBox gets focus.

Build, Run, Try

Go ahead and build, run and try it out.  If you need the code, get the DailyJournal_v009.zip at the top of this article.

Why Have We Went So Far Afield?

All of this work has been to allow us to get to a place where we are able to:

  • determine which RichEditBox is currently in use so that we can save its data.

To do that, we have to know when the RichEditBox is activated.  To know when the RichEditBox is activate requires the code we’ve written to handle the Loaded and GotFocus events.

Capture Currently Selected RichEditBox

Now we need to capture the currently selected / activated RichEditBox so that when the user clicks the Save button we will get the data that is associated with the RichEditBox which is currently being viewed by the user and save its data.

New Member Variable

That is very easy to do simply by doing two things:

  1. adding a new private member variable to our MainPage class

  2. Setting the member variable to reference the currently activated (focused) RichEditBox

 

Since this is a RichEditBox we simply add the following code to the top of our MainPage class:

C#
private RichEditBox currentRichEditBox;

 

Image 12

 

Note: You probably already have that in your code, if you’ve downloaded previous source zip files because I had been figuring out how to do that work and I accidentally left my changes in there.  It’s fine, it won’t hurt anything, just saves you some typing.

 

Once you’ve added that line we need to set this reference any time the RichEditBox is gets focus.  Right now that happens in two different places :

  1. rootPivot_GotFocus method

  2. PivotItem_Loaded

 

Actually, now that I’m looking at those two methods I see that I’m repeating some code.  That breaks one of the SOLID software development principles (SOLID (object-oriented design) - Wikipedia[^] ) known as DRY (Don’t Repeat Yourself) so I will refactor the code out into its own method so we only have to set our currentRichEditBox reference in one place.

Here’s the changed C# code:

C#
private void rootPivot_GotFocus(object sender, RoutedEventArgs e)

       {

           Pivot p = sender as Pivot;

           PivotItem pi = p.SelectedItem as PivotItem;

           RichEditBox_SetFocus(pi);

       }


       private void PivotItem_Loaded(System.Object sender, RoutedEventArgs e)

       {

           PivotItem pi = sender as PivotItem;

           RichEditBox_SetFocus(pi);

       }


       private void RichEditBox_SetFocus(PivotItem pi)

       {

           RichEditBox reb = pi.Content as RichEditBox;

           reb.Focus(FocusState.Keyboard);

           currentRichEditBox = reb;

       }

 

Image 13

 

The first two lines in the new method were previously in both the Loaded and GetFocus method and that isn’t any good.  

Now, I’ve simply taken those lines and moved them to the new method.  Of course I also had to add the reference to the PivotItem as a parameter so I could use the object in my new method.  

Then I also added the call of the new method to both of the methods (GetFocus and Loaded) so everything will still function the same.

Added Line of Code To Capture RichEditBox

Finally I added the last line of code to the new method so no matter how the RichEditBox gets activated that we will get a reference to it so we can use it to get the data and save it to a file.

However, the code doesn’t do anything different yet, because we haven’t implemented the Save button.

Let’s go and double-click our Save button so we can add an event handler to it so we can write some code when the user clicks the Save button.

 

Add SaveButton Click Event Handler

Let’s do this the same way we added event handlers for GotFocus and Loaded.

Open up the MainPage.xaml and :

  1. Click the SaveEntryButton element in the XAML.

  2. In the Properties window (usually underneath the Solution Explorer on the right), click the [Lightning bolt] icon to display a list of Event Handlers that this control supports.

  3. Scroll down in the list until you see the Click Event.

  4. Double-click on the TextBox next to the Click event.

Image 14

 

Of course, when you double-click the TextBox Visual Studio opens MainPage.xaml.cs and adds the new method.

Long Chapter: Quick Save

Since this chapter is becoming so long we will go ahead and make the Save button save the text to one file for now.  That means whichever RichEditBox that is currently displaying will determine the data that is in the file.  If you switch to another RichEditBox and save again it will overwrite your previous data.

 

Future Functionality

Then, next time we will begin to solve the real problem storing data multiple files and then reloading the files into their associated RichEditBoxes.

 

File Storage : Saving Data

Here’s the code we are going to use in our SaveEntryButton_Click method:

       

C#
Windows.Storage.StorageFolder storageFolder =

           Windows.Storage.ApplicationData.Current.LocalFolder;

       Windows.Storage.StorageFile sampleFile =

           await storageFolder.CreateFileAsync("FirstRichEdit.rtf",

               Windows.Storage.CreationCollisionOption.ReplaceExisting);

 

I got that code from a sample at :

https://docs.microsoft.com/en-us/windows/uwp/files/quickstart-reading-and-writing-files

I changed the name of the file that it saves but the rest is the same.

When you add the code into MainPag.xaml.cs Visual Studio Intellisense is going to warn you about a problem.

 

Image 15

 

It is telling us because we are calling an asynchronous method (CreateFileAsync) from a synchronous method (SaveEntryButton_Click).

This is similar to what we saw way back in chapter 2 when we tried to pop up a dialog box. 

We can fix this without help from Intellisense.  Simply add the keyword async to the SaveEntryButton_Click method (right after the private keyword).

As soon as you do that you will see that Intellisense stops bugging you.

 

Image 16


That creates the file, but it doesn’t actually save any data.  To do that we need to :

  1. Create a IRandomAccessString when opening the File asynchronously

  2. Call the RichEditBox Document.SaveToStream

  3. Close (Dispose) of the Stream

 

Here’s the code you need to add:

C#
IRandomAccessStream documentStream = await sampleFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);

           currentRichEditBox.Document.SaveToStream(TextGetOptions.FormatRtf, documentStream);

           documentStream.Dispose();

 

That first line is another Async call, since the app doesn’t know how long it will have to wait to get disk access and it doesn’t want to freeze up.

 

Image 17

 

Once you have a valid Stream which we can use to write the file data to, we can call the SaveToStream method.  Notice that the first parameter is an SDK (Software Dev Kit) enumeration which tells the method to save the file as standard RTF (Rich Text Format).  That way, if you have any images in there, it’ll save those too.

 

Finally, we Dispose of our stream to close the file and we are done.

Get the Code and Try It Out

Build the code, run it and type something in the RichEditBox and click the Save button.  If everything works properly you won’t really even know it saved.

 

But, Where Is The File Saved?

Ah, the $64 dollar question.  Now that we use the special Windows.Storage.ApplicationData and it’s LocalFolder value, the app lets the OS tell it where it should save the file.

 

Once again this is because the UWP architects are attempting to insure that no matter what device the app is running on it should not fail to save the data.

In our case, on the desktop, you can find the file by going to :

%localappdata%\Packages\116d1010-a1e4-452a-a1d2-c84aea07af6d_gw4zt26480tv8

You should be able to copy that last line, paste it into File Explorer and it will take you to that location.

Isn’t that bizarre?!  Well, %localappdata% is an environment variable which points to your Windows SpecialFolder LocalAppData which is generally found at: c:\users\<username>\local\AppData\local\

Underneath there in the \Packages folder is where UWP apps store their data.  

Here’s the file sitting in my directory:

 

Image 18

Package.appxmanifest

The last item is a GUID (Globally Unique ID) which is generated by a Visual Studio tool to uniquely identify your app when you first created it.

As a matter of fact, if we go back to Visual Studio and double-click the Package.appxmanifest in Solution Explorer and then choose the Packaging section, you will see that the Package family name contains this value.

 

Image 19

 

If you save some data in your file, you can navigate down to the file in the file system (using the previous clues and take a look at it using MS-Word or WordPad.

Here’s the data I saved (which even includes a snapshot image).

 

Image 20

 

I then opened that document in WordPad:

NOTE: Windows 10 thinks the image is dangerous so if you save an image in there it may not want to allow you to view that data.

 

Image 21

 

I leave you with that, since this is a ridiculously long chapter.

However, I’ll be back soon to fix the file saving schema and the add the ability to delete entries (along with their files).

History

 

2017-11-29 : First publication

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) RADDev Publishing
United States United States
"Everything should be made as simple as possible, but not simpler."

Comments and Discussions

 
QuestionViewing the Saved File Pin
Bob Kaye30-Nov-17 14:07
Bob Kaye30-Nov-17 14:07 
AnswerRe: Viewing the Saved File Pin
raddevus30-Nov-17 14:44
mvaraddevus30-Nov-17 14:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.