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

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

Rate me:
Please Sign up or sign in to vote.
4.68/5 (7 votes)
1 Dec 2017CPOL22 min read 8.4K   268   5  
Get Started in UWP (moving away from WinForm) Chapter 8 Applied OOP, Designing Objects, SoC and going deeper into designing the DailyJournal app.

Introduction

Please check out the previous entries to get up to speed with our ongoing project to create  the 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)[^]
Programming Windows 10 Desktop: UWP Focus (7 of N)[^]

To move forward with the functionality we need in our DailyJournal app we need to get the file system set up properly for our use.

Last time we left off with an app that was able to save any of the Entries as an RTF (rich text file), but every entry was saved to the same file so we need to fix that.

We also need to insure that each file is saved in its associated Y-M (Year-Month) directory.

Once we get these two things fixed up, we will then be able to add the entries to our ListView so users can see a list of available entries.  We’ll also be able to load all associated entries when a particular date on the CalendarView is selected.

Background

As I wrote and worked through this chapter I discovered that I needed to talk some things through to design the app properly.  As I did that I captured the "discussion" so that you could see what it's really like to work through software design.  I mention this because the article gets a stretched out a bit I believe (and hope) that seeing how design has to be incorporated into writing software will help and be valuable to readers. 

We will discuss why we use OOP (Object Oriented Programming) and what indicates that a new class should be created and more.

A Bit of Code Planning / Design

It seems to me that a domain object is arising from talking about our problem.  What I mean is that as we talk about our problem we may find ourselves mentioning things (nouns) in the system such as JournalEntry.  I believe that JournalEntry is a candidate class in our app’s domain (“a specified sphere of activity or knowledge” from Google’s dictionary).

JournalEntry Class

Let’s add a class to our project named JournalEntry.  As we design this class I believe you’ll also see how that OOP (Object Oriented Programming) will help us organize our code so that it is easier to maintain and extend.

 

As the code currently stands, I’m attempting to manage a number of things in code so I can tell which JournalEntry should be displayed to the user at any time while the app executes.  All of the code we’ve written so far has been just to get things working but now as the app begins to grow we need to take a step back and make sure our code makes sense and has some basic organization to it.  If you don’t do this now then later when you want to extend the app or use pieces of the app in another app you will find it cumbersome or impossible to do so.

Talking About Our App Helps Us Discover How It Should be Designed

Actually, if we think about what our app does and use a natural language sentence to explain it then we begin to see where we have some mixed up code.

Here’s how I might explain what the DailyJournal does.  

“It allows users to CRUD (Create, Read, Update, Delete) journal entries which are organized by date.”  Create means “Save the first time”.  Update means “Save changes to a previously created journal entry”.

 

SoC (Separation of Concerns)

One of most basic things that OOP (Object Oriented Programming) helps with is allowing developers to concentrate on one thing at a time.   This concept is known as SoC or Separation of Concerns in the software dev world.

We need to design our code so that when you touch one part of the code it does not break or affect things in unrelated parts of the code.  That sounds obvious when you say it out loud, but the history of software development is one of devs “getting things done” only to later find out that everything is lumped together.  No one notices until later when some poor sap (a maintenance programmer) has to fix some obscure bug and then learns that the original programmer basically dumped all of his code in one bucket and mixed it all up.

OOP Object Oriented Programming

This is simply a method that allows developers to organize their code by separating it into encapsulated units known as classes.  From those classes (templates) the system can create (instantiate) objects.  When one class has an error it is far easier to fix it in one place and know you’ve fixed it than it is when the code does not have this system of organization.  Of course there is more to OOP but this is a recurring theme and if you will accept that for now it will help a lot as you continue on your journey as a developer.

Code Organization Sniff Test

With those basics in mind, we can now identify some bad mixing of concerns by simply looking at the SaveEntryButton_Click method in MainPage.xaml.cs.

How Easily Can You Share Code?

The code in that method does all the work of saving a journal entry to a file.  Now, imagine if you wanted to share code that could save journal entries with another programmer on your team.  You’d have to copy it and paste it into his Page class and then you’d each have a copy of the same code.  Then, later if one of you noticed a bug in that code you’d have to tell the other to go and fix the code wherever the other dev had used it.  That’s not great.

Code Is In Wrong Place

This happens because the code is in the wrong place (inside our Page object).  Since we are not saving out a Page object to a file, but instead are saving out a journal entry, the code indicates that we have another domain object in our system (JournalEntry) that needs to be created and should be able to Save itself to a file.

 

If we really do that and then the other developer wants to save a JournalEntry also, then you simply share the JournalEntry class with the other dev and he can create a JournalEntry object and it will know how to save itself because all of the code it needs is encapsulated within itself.  Also, he will not have to understand the specifics of how the Save works but instead he will simply create a new JournalEntry and call its Save method and everything will work.

Other Indications That Something Isn’t Right

There are other indications that things aren’t quite right in the code too.  Those things are places where we have to think, “wait, which JournalEntry currently displayed on the screen?”  These kinds of things are subtle and they are not hard rules.  They are things which indicate to you as a developer that you have another thing (another domain object) in your system that needs to be separated out into its own class.  

User Stories May Help

We can often expose these domain objects more by talking about how the app works in a manner that creates a kind of a story.

  • When the app starts, as a user, I want it to default to the current date.  

  • When the app starts and defaults to the current date, as a user I want the app to display the first journal entry which exists for the current date.

  • As a user I want to be able to Save an Entry I have created.

Elaboration On How DailyJournal Should Work

Here’s a bit of elaboration on how that might work:

App Loaded

When the app loads, or when a user clicks on a date in the CalendarView, we need to :

  1. check the date

  2. open the appropriate date folder (if it exists)

  3. Determine if there are any JournalEntries for the current date

  4. Display Entry1 for the current date (if it exists) -- display an empty Entry1 if no entry already exists.

User Clicks Save Button

When a user clicks the Save button the app must:

  1. Get the date

  2. Create the Y-M directory if it does not exist

  3. Create RTF file (if it doesn’t exist)

  4. Save the RTF into the Y-M directory.

Add A New Class In Visual Studio

To add a new class:

  1. Go to Solution Explorer

  2. Right-click your Project

  3. A menu will appear -- slide down to the [Add] menu item

  4. Another menu will appear -- slide down to the [Class…] menu item and click it

Image 1


 

When you click the [Class…] menu item another window will pop up.

Make sure you choose [Code] on the left side of that window.  When you do, the appropriate choices will appear on the right side.

Select the Class choice.

Type the name of our new class (JournalEntry) in the [Name:] edit box at the bottom of this window.

Click the [Add] button.

Image 2

 

When you click the [Add] button, Visual Studio will:

  1. create the new file

  2. Add it to the current project

  3. Open it up and display it so you can edit it.

Image 3

 

You can see that Visual Studio has created a new class named JournalEntry and it has added it to our default namespace (DailyJournal).  At this point you have an empty class which will do nothing.

 

The first thing I’m going to do is cut all of the code out of the SaveEntryButton_Click method (in MainPage.xaml.cs) and paste it into a new Save method in our JournalEntry class.  

Cut all the code shown highlighted in blue:

 

Image 4

 

Here’s the code you can paste into your JournalEntry class to create the new Save method:

 

C#
public async void Save()

       {

           Windows.Storage.StorageFolder storageFolder =

               Windows.Storage.ApplicationData.Current.LocalFolder;

           Windows.Storage.StorageFile sampleFile =

               await storageFolder.CreateFileAsync("FirstRichEdit.rtf",

                   Windows.Storage.CreationCollisionOption.ReplaceExisting);

           IRandomAccessStream documentStream =

               await sampleFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);

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

           documentStream.Dispose();

       }

Notice that the method is marked as public. That is so that the Page class can use a JournalEntry and call the Save() method. If it were marked private we would not be able to call the method.

You can see that Visual Studio is warning us about some code issues when we paste that code into the class.

 

Image 5

 

That’s simply because the libraries we need are not referenced (no using statements) in the new JournalEntry class.

If you add the two following using statements to the top of the class file then two of the errors will go away:

C#
using Windows.Storage.Streams;

using Windows.UI.Text;

 

However, you will still have a third error, because the currentRichEditBox is no longer available.  That is a member of the Page class and is not a member of our new JournalEntry class.

We have to figure out a solution for this.

Moving Code To Correct Place (Class)

This is actually an example of one of the three tenets of OOP, known as encapsulation and related to scope.  Objects in OOP should only be accessible by the things which use them.  

In other words the Page object no longer really needs to deal with the RichEditBox, but instead the JournalEntry does need to know about it.  That means we will remove the currentRichEditBox from the Page and give the JournalEntry a member that is a RichEditBox.

To do that, all we have to do is go to the top of the class and add a line which looks like:

C#
private RichEditBox _richEditBox;

We are telling the class that it will have a private member (inaccessible outside of this class) which is a RichEditBox.

When we add the RichEditBox to our JournalEntry class the type is unknown becuase we need another using statement to reference the library where the type is defined:

C#
using Windows.UI.Xaml.Controls;

 

However, adding a member variable does not initialize the RichEditBox.  Also, we need this to be equal to the RichEditBox which is displayed on our MainPage.  

Class Constructor

Fortunately, a class allows us to add a special method called a constructor which will run first when an object is instantiated (when an object is generated from our class template).

We can add a constructor to our JournalEntry class so that when the Page object creates a new JournalEntry it can pass in the RichEditBox reference.

Here’s the code that defines our constructor:

C#
public JournalEntry(RichEditBox richEditBox)

       {

           _richEditBox = richEditBox;

       }

Here’s what the entire JournalEntry looks like at this point:

Image 6

 

Code Builds, But Doesn’t Work Properly

The interesting thing is that the code will now build but it doesn’t work properly.

The list of problems:

  1. The Save button will no longer function -- it has no implementation code at all now

  2. The JournalEntry class is not being used

Quick Fix : Shows Us a Level of Indirection

Let’s do a quick fix on our SaveEntryButton_Click method to show how we can get our app to work again and to see how it leads us to our changes.

Right now our SaveEntryButton_Click method is empty.  We moved the code to our JournalEntry class.

So, let’s new take the following steps:

  1. New up a JournalEntry object in the method

  2. Call the Save method on the JournalEntry


It’s just two lines of code we need to add:

C#
JournalEntry je = new JournalEntry(currentRichEditBox);

je.Save();

 

Image 7

 

Build, Run, Test

Now the app will behave exactly as it previously did and you can save your document to FirstRichEdit.rtf just as we did in our last chapter.

Download The Code

You can download the DailyJournal_v011.zip at the top of this article and try it out if you haven’t been following along.

A Lot of Work To Get To The Same Place

I understand if you feel like that was a lot of work to get to the same code.  However, we are attempting to learn not only how to develop apps but what experienced devs do to create robust, extendable code that is far easier to work with.

Also, we need to change a few more things which will help make it more apparent why moving the code out into its own class will make things better.

What Do We Need To Change?

We now need to change our MainPage class so that it uses a JournalEntry instead of using the the RichEditBox.  Let’s delete the member variable currentRichEditBox from the top of our class and rebuild, because we will get some errors and they will indicate what we need to move to our JournalEntry class.

Go to the top of the MainPage.xaml.cs file inside the class and delete the line which contains the member variable, currentRichTextBox.

Image 8

 

After that, go ahead and add a new member variable that is a JournalEntry.

C#
private JournalEntry journalEntry;

 

After you make those two changes, go ahead and rebuild the project.  

Cleaning A Solution

Note: If you ever have difficulties rebuilding the project, go to the [Build…] menu (top of Visual Studio ) and choose the [Clean Solution] menu item.  That will reset the solution so it can be built properly.

 

#####################################################################

SideBar : Visual Studio 2017 Problems

At this point when I tried to rebuild I got stuck on an error and my project would not rebuild.

Image 9

 

The additional issue is that this is just a warning so the app should’ve built properly but it doesn’t.  The real issue is that I know there are Errors but as you can see it is reporting 0 errors.

Something very odd is going on.  

I finally right-clicked on the project and opened up the properties window and unchecked these two boxes.

Image 10

After that, I could finally see the errors that we are expecting.

#####################################################################

 

Expected Errors After Removing currentRichEditBox

When you build in Visual Studio and there are errors they will be listed in the ErrorList Window (usually at the bottom of Visual Studio).

Image 11

 

We have two errors.  I highlighted the bottom one so you can tell where one stops and another begins.

Both errors give us the same description but each one comes from a different line in our source code (one at line 82 and the other at line 87).

To go to the location where the error is occurring, go ahead and double-click the first error and it will move you to that line in MainPage.xaml.cs.

Since the other one is only 5 lines beneath that we can see it too.

Image 12

 

You can see that Visual Studio also warns you by using the red squiggly line also.

These errors make sense to us because we know that we deleted the member variable.

Fixing Problems With One Line of Code

We can actually fix this problem by:

  1. adding one line of new code.

  2. delete both those lines with the squiggly lines

  3. Altering the je.Save() line to use our member : journalEntry.Save()

 

That will fix all the problems and the code will work again.

Here’s the line of code we’ll add:

C#
journalEntry = new JournalEntry(reb);

 

That line of code stores the current RichEditBox in the current JournalEntry (journalEntry) member variable.   Every time a RichEditBox gets its focus set it will create a new JournalEntry (using the currently selected RichEditBox) and store a reference to it in our member variable journalEntry (which represents the current journal entry).  

Note: We can do this a bit better, but for now it is good enough.  We are not generating (instantiating) thousands of objects so it is okay but there is a better way to do this and we’ll look into it later.

Here’s what the updated code looks like:

Image 13

 

Build and Run

You can download the DailyJournal_v012.zip from the top of this article and try it out.

Everything Works The Same

Everything works the same, but we are beginning to get some separation in the code which will help us with the functionality that we are going to add.  

What Other Things Do We Need?

What are we driving toward?  What am I attempting to lead us to?  

Here Are Some Things We Know

  • The JournalEntry already manages the RichEditBox that is associated with the entry.

  • We know that we need to generate a file name to save the user’s entry into.

  • That file name is associated with the JournalEntry so we want it to do that work.

  • We know that the JournalEntry will create the file name using the the DateCalendar value and an Entry count number.

  • We know that we need to save the file into a Y-M (Year-Month) directory and we want the JournalEntry to manage that since it is the one that is saving the file.

The Very Lightest Introduction to UML

Here’s a little model of of the things our JournalEntry class needs to have in it:

Image 14

That is a very rough example of what a UML (Universal Modeling Language) class might look like.  It’s just a quick way to communicate what a class contains.  

  • The top part of the box is the Class name.
  • The bottom part is what members the class contains.
  • The dashes represent that the items are all private to the class.  
  • The plus sign represents that the item is public and you can see that the Save() method is public.

That’s a nice little summary of what we need.

Keep It Simple

Don’t make more of this than is necessary.  Diagrams are just a way to communicate things more clearly and quicker and that is why we used it.  Also, it will surely change.  This is just a quick way to remind us of what we are initially targeting.

 

Since we want the Entry to be created according the the date value that the CalendarView is currently sitting on, we are going to pass that value into the JournalEntry’s constructor.

We also need to know which Entry number the JournalEntry should be created for so we’ll pass that into the constructor too.

I believe with those two items we can generate our file name properly so that we can make the app store each entry in its own file instead of that generic named (FirstRichEdit.rtf)) one.

CreateNewEntryButton_Click Is Wrong

We’ve actually been cheating because we were still letting the CreateNewEntryButton_Click do too much work.  The work it does to create a new entry is actually wrong and needs to be moved to our JournalEntry so we’ll have to look at how we can refactor that code to do that, before moving on.   

However, this is related to the work we’re doing in the JournalEntry constructor also so it should all work out fine, but it will feel like quite a bit of work.

I noticed this because I noticed that I needed to send in the EntryNumber value to the JournalEntry constructor and that it is currently being calculated in the CreateNewEntryButton_Click method.  

A Collection of JournalEntry

As I examine that code I see that I need to know how many JournalEntry objects there are.  

When do I need to know how many JournalEntry objects there are?  I need to know that when:

  1. The app loads and gets set to the current date - I need to know how many JournEntry objects there are for the current date so I can load the appropriate number of PivotItems (one for each file)

  2. I also need to know how many JournalEntry objects there are any time the user chooses a new date on the CalendarView.  But that is really the same thing as when the app starts and it automatically chooses the current date.

Good Summary of Program Functionality

I know that a JournalEntry knows nothing about any other JournalEntry.  This is part of what keeps our code Separated.  A JournalEntry simply allows me to organize the code which is specifically related to a JournalEntry.  However, I need to know the count of JournalEntry objects for each date.  I actually have to calculate the number of JournalEntry objects from the number of files found in the Y-M which match the date selected by the user (or app upon startup).

The following is a good summary of what we need:

  1. A date is selected on the CalendarView (either when app starts or user selection)

  2. The appropriate Y-M directory is calculated.

  3. If the Y-M directory exists, then there are entries already created

  4. Determine if any of the file names match the date selected.

  5. If the Y-M directory does not exist or no file names match then display one empty entry with the Header of Entry1

  6. If the Y-M directory does exist and one or more file names match, generate a PivotItem for each one and load the RTF data into the assocated RichEditBox.

Deciding Where Work Is Done

One of the main things you’ll do as a software developer is determine where the work is done -- inside of which object should you write your code to solve the problem.

 

At first we had the Page object doing everything because we were basically following the Get ‘R Dun method of coding.   Now, we are beginning to see that different objects should have different responsibilities.  This even goes back to an old way of designing classes called CRC cards.

CRC Cards

Class-Responsibility-Collaboration cards were a way to do basic design of

  1. Class - What classes your program would be divided into

  2. Responsibility - basic functionality that the class would be responsible for

  3. Collaboration - which other objects would the class interact with

It’s a nice way to think of your programs.

In our case, I believed that the Page class would collaborate with the JournalEntry, but now I see something different.

I see that really we need the Page class to interact with a collection of JournalEntry objects.  This will make it easier to manage because then the Page will simply say to the JournalEntryCollection, “do you have one or more JournalEntries?”  

If there are not one or more then the Page class will simply load the empty Entry1 Pivot and be done.  If there is one or more JournalEntries then the Page class will simply load a Pivot for each one.

Begins To Make More Sense: Developer Understanding Is Important

This makes much more sense because now the responsibility of the Page is simply display the JournalEntry objects.  Since the Page is really a view type of object that seems to make sense.  The rest of our code which determines if there are files and all that will be separated out from the page into these two other classes: JournalEntry and JournalEntryCollection.

Naming Collection-type Classes

I am going to name our collection of JournalEntry as JournalEntries.  Writing it as the plural name is a convention that will indicate to future devs that this is a collection of JournalEntry.  It’s just a convention (a way of doing things) that you can follow or not, but it does work nicely.

This Chapter : Heavy On Design / OOP Theory Discussion

Since this chapter has required a lot of code design and learning what code design is it has become quite long.  We’ll go ahead and add our new JournalEntries class now and then end the chapter so you (and I) can take a break.    Also, our last changes to the code still allow the app to build and run and our next refactoring (writing code in JournalEntries) is going to alter the code quite a bit, so now is a good time to break.

Next Article : JournalEntries Functionality

In the next article we will get all of the functionality that I mentioned previously working so the app will:

  • Determine the number of entries that exist each day

  • Display the associated entries for each day

  • Allow the user to save new entries using the new Y-M directory and correctly calculated file name.

Add the JournalEntries Class

  1. Go to Solution Explorer

  2. Right-click your Project

  3. A menu will appear -- slide down to the [Add] menu item

  4. Another menu will appear -- slide down to the [Class…] menu item and click it

Image 15

 

 

Image 16

 

Type JournalEntries.cs as the [Name:]

Click the [Add] button and the class will be added to your project and Visual Studio will open up the class and display it so we will be ready for Chapter 9.

 

Image 17


See You Next Time

See you next time in Chapter 9.

I'll provide the Code Download which will include this empty new class (JournalEntries) at the top of the next article so readers will be able to download the first zip and start into the article.

History

2017-12-01 : 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

 
-- There are no messages in this forum --