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

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

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
12 Dec 2017CPOL29 min read 13.3K   622   4  
Get Started in UWP (moving away from WinForm) Chapter 12 Working with the ListView - Setting up ListView style via XAML, using data binding and anonymous types in C#.

Introduction

This is the continuing saga of UWP (Universal Windows Platform) development for the desktop. 

Chapter 12 is a heavy-weight weighing in at 31 pages and 35 screenshots.

It all started with the following chapters:

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)[^]
Programming Windows 10 Desktop: UWP Focus (8 of N)[^]
Programming Windows 10 Desktop: UWP Focus (9 of N)[^]
Programming Windows 10 Desktop: UWP Focus (10 of N)[^]
Programming Windows 10 Desktop: UWP Focus (11 of N)[^]

Image 1Print or Kindle Available at Amazon

You can also read the first 8 chapters of the book as a print or kindle book from amazon:

Programming Windows 10 Via UWP: Learn To Program Universal Windows Apps For the Desktop (Program Win10) [^]

 

 

Background

As promised, in this chapter we will begin to set up the ListView so that it will :

  1. Find all existing entries (based on selected month)
  2. Count the existing entries found per day

  3. Add a date item and a count into the ListView for each day which has 1 or more entries

ListView XAML Design Research

When I began to working with the ListView so I could write this chapter, I looked around on the Internet.  I could not find any good simple examples of how to set the ListView up with a simply header and how to format ListView items so they will look nice.  

I found the Microsoft Github examples, but I couldn’t get them to build due to another issue with Visual Studio 2017 and the ListView example is mixed in with numerous other examples.  That seems to be the issue with learning technical things at times: you can’t find a simple example just to get your started.

I like knowledge to build as I go, but often you can only see a very specific example of how someone solved a particular thing (ListView in our case).  

No Snapshots Shown of Graphic Element

To my dismay I would often find some example which had no snapshot of what it would end up looking like.  I couldn’t tell, just by looking at the XAML if the example was what I wanted or not.  It’s all a bit frustrating.

With all of that in mind, our ListView example will build from the very basic control we dropped on our Page to the final result. I will also provide snapshots of the control as it changes in each step so you can know what it is supposed to look like.

This will allow you to understand how it works and then extend your knowledge so you can extend the control as you learn more.  This will be much better than just dropping an example on a Page and hoping it works.

Get The Current Code

If you need the code as we left it in the last article then get the DailyJournal_v023 and build it and you'll be up to speed.

Current State of EntriesListView

Because we haven’t done much with our ListView in the DailyJournal app, the control XAML is very basic.

XML
<ListView Name="EntriesListView" Grid.Row="2" Grid.Column="0"
           HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />

Image 2

 

We are going to add some child elements (nodes) to the ListView so the first thing we want to do is make sure the ListView element is a block type of element -- has a start node <ListView> and an end node </ListView> which we will use to contain the child elements.

 

Image 3

 

We want to add two things to the ListView:

  1. A header

  2. Data

If we wanted to add static data we could just add some string elements and the items would appear in the list.

XML
<x:String>Item 1</x:String>
<x:String>Item 2</x:String>
<x:String>Item 3</x:String>
<x:String>Item 4</x:String>
<x:String>Item 5</x:String>

Image 4

 

Of course, we want our items to be added programmatically as the app finds valid entries which exist for the month.

We also want our ListView to display two columns of data.  One for the entry date and one for the count of entries found on that date.  

It will look something like what I had previously created in my legacy WinForms app.

Image 5

 

Note: As we continue through the following section I will leave two of the static items in the control so we can see how things look in relation to those items.  Later we will remove those items.

The first thing we will add is the header portion which will display “Date” and “Entry Count”

ListView provides a way to add a HeaderTemplate so we can define what the header should look like.  The HeaderTemplate is a property of the ListView class so we can easily add the new XAML like the following:

XML
<ListView.HeaderTemplate>
</ListView.HeaderTemplate>

When you type the opening tag in Visual Studio and press <ENTER> the closing tag will be automatically created for you.  However, you will also see that Visual Studio warns you that there it believes there is a problem with the new XAML.

 

Image 6

 

The HeaderTemplate is a container type of element and it requires that you add a child node (element) to it.

It is specifically looking for the DataTemplate element so let’s add that element now.

 

XML
<DataTemplate>
</DataTemplate>

Image 7

 

Once you add the new element, Visual Studio will stop complaining about the missing item.

Name of DataTemplate Element Is Somewhat Odd

The name DataTemplate feels a bit odd to me, because it makes me think we are going to add a template that will style the data that the ListView contains (such as Item1 and Item2). However, the data the template will be styling in the HeaderTemplate is the data (text) that shows up as the headers at the top of the ListView.  This will make a bit more sense when you see that we add a ListView.ItemTemplate element to style the items and that template will also contain a DataTemplate element.

More Definition of Our ListView Design

Our header will be one row with two columns.  The first column will need more space since it is displaying more data (a date string) while the second column only needs to be about 3 characters wide to display up to 999.

Grid or StackPanel

At this point we are going to add our elements which will create the layout of our header.

The two most obvious choices are a Grid or a StackPanel.

We could use a StackPanel with it’s orientation set to horizontal and then we could add two TextBox elements, each of which would add its associated header text to the ListView.

The XAML would look like:

XML
<StackPanel Orientation="Horizontal">
    <TextBlock  FontWeight="Bold">1st Header</TextBlock>
    <TextBlock FontWeight="Bold">2nd Header</TextBlock>
</StackPanel>

Image 8

 

However, the problem is that it’s a bit more difficult to get things to use up available space using the StackPanel.  You can see in the image that the two items aren’t spaced properly.

StackPanels are appropriate for a number of uses but in this case I think the Grid is going to be easier for us to get the layout right.  And, of course, we’ve already used a Grid for the main layout so the work we do will be familiar to you.

However, I wanted to show this to you because so you’d have the StackPanel in mind for future use and because there is quite a bit of debate about using StackPanel versus Grid.

NOTE: If you pasted the StackPanel into your layout, go ahead and delete it since we won’t use it.

Let’s add the initial Grid XAML into our DataTemplate.

XML
<Grid>
   <Grid.RowDefinitions>
         <RowDefinition Height="*" />
   </Grid.RowDefinitions>
   <Grid.ColumnDefinitions>
         <ColumnDefinition Width="*" />
         <ColumnDefinition Width="Auto" />
   </Grid.ColumnDefinitions>
          <TextBlock Grid.Row="0" Grid.Column="0" Foreground="#ff0000" Text="Date"  />
          <TextBlock Grid.Row="1" Grid.Column="1" Foreground="#ff0000" Text="Entry Count" />
</Grid>

Image 9

 

You can see that we now have a header with two columns each with its own red header text.

That is because we created a Grid that has one Grid.RowDefinition and two Grid.ColumnDefinitions.

The row definition is simple and has its Height property set to take up the available space.

Next, the first column definition has its Width property set to take up available space.  That will be most of the space leftover since the second column’s Width property is set to Auto -- which will set the column to the width that the text (Entry Count) takes up.

TextBlock Styles

Of course, it is on the TextBlocks themselves where we define which Grid.Row and Grid.Column the TextBlock should show up.  We also define the Text that will show up in the TextBlock by setting the Text property.  Setting the color of the text is an easy matter of setting the Foreground color to a valid RGB color.  In our case we set it to red.

Basic Layout Works, But Has A Bug

That is a basic layout and it will work for now, but we will see a problem that occurs when we run the program in a little bit.

Before we run the app, let’s define a ListView.ItemTemplate also so we’ll get more bang for our buck once we build and run.  

ListView.ItemTemplate

Adding an ItemTemplate works just like adding the HeaderTemplate.

Just add the ListView.ItemTemplate and DataTempate elements in one quick shot.

XML
<ListView.ItemTemplate>
   <DataTemplate>  

   </DataTemplate>
</ListView.ItemTemplate>

As soon as you add the two elements, you will find that the two hard-coded ListView items will disappear from the preview layout.

Image 10

Now we can define our Grid layout for the Items which get added to the EntriesListView.

Here’s our initial Grid layout:

XML
<Grid>
  <Grid.RowDefinitions>
     <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
     <ColumnDefinition Width="5*" />
     <ColumnDefinition Width="Auto" />
  </Grid.ColumnDefinitions>
  <TextBlock Grid.Row="0" Grid.Column="0" Foreground="DarkBlue" Text="2017-12-11" />
  <TextBlock Grid.Row="0" Grid.Column="1" Foreground="DarkBlue" Text="3"/>
</Grid>

Image 11

The Grid is defined exactly the same way we defined the HeaderTemplate grid.  

Of course we did change the individual TextBlock items and this time we changed the Foreground color to DarkBlue (a predefined RGB color in the .NET library).  If you type a letter between the quotes of the Foreground value Visual Studio will popup an intellisense dialog which will allow you to choose a color by name from the available predefined ones.

Image 12

Layout Preview

Since no data would show up in the Layout Preview during design time I went ahead and added the Text property and set it to the current date (2017-12-11) and a random value of 3 for the second column.

There’s another problem though, because as you can see, the Entry Count value (3) gets displayed immediately after the current date and it makes it look as that it is a part of the date value.

For now, let’s add a Margin value just to separate those out.

XML
Margin="25 0 2 0"

Remember, that is four Margin values which will be set to each side of the TextBlock starting with the left side and going around clockwise (Left, Top, Right, Bottom).

Image 13

 

At least now you can tell that they are two separate columns and values.

Setting Data to Generated Value

Of course, we don’t want our data to be set to a hard-coded value so let’s see how we can fix that.

Remember How We’re Adding To The ListView?

First of all, as a reminder take a look at the first line of the LoadEntriesByDate() method in the MainPage.xaml.cs file.  

 

Image 14

 

Right now that adds a new Item to the EntriesListView any time the user clicks a date on the MainCalendar.

However, we need to tell the ListView to bind to that value.  The framework we build on is amazing and the XAML provides a way to easily bind our Text property to a value that is added programmatically.

Binding Syntax

All we have to do is use some special syntax which uses curly brackets { } and which the compiler understands.  

To simply get the ListView to bind to the values which exist, all we have to do is tell it to use Binding by setting our Text property like the following:

XML
Text="{Binding}"

For now, we’ll continue to use the hard-coded Entry Count of 3.

 

Image 15

The other benefit of adding that is that as soon as you do, the design layout will bind to the hard-coded values we have in our XAML and they will display.

Build, Run, Examine

Let’s build the app now and see how it behaves.

If you haven’t been following along get the DailyJournal_v024 source code and build it and run.

 

Image 16

When The App Starts

When you first start the app the two hard-coded items will be added and then the third item will be generated and inserted when the current date is auto-selected in the MainCalendar.

After that, if you click any other date on the MainCalendar the SelectedDateChanged event will fire which will eventually call the LoadEntriesByDate() method which will run the line of code which adds a new item to the EntriesListView so you will see more entries (you may need to scroll) appear in the ListView.

If you add some entries and scroll you will find at least two things which are a bit odd.

  1. When the scroll bar appears, part of the header becomes obscured (see next image).  We will fix this in the XAML design.  Probably with a simple addition of a margin.

  2. When you scroll the items, the header also scrolls and goes out of view.  This is designed by Microsoft to be this way by default and there are some ways to fix it but they require extensive layout changes. For now, we will not change this.  It’s possible that it makes sense on a phone or a device with little screen space, but it feels odd on a desktop app.

Image 17

Things To Change

There are a few things we should fix before moving on.

  1. Remove the test hard-coded items so they no longer appear.

  2. Format the date so it matches our Entry file names more closely (yyyy-MM-dd) since we don’t need the time and it is only taking up space on the User Interface (UI)

  3. Add the Margin to the header so the second column header won’t be obscured.

Remove Hard-coded ListView Items

First, let’s delete those two lines from our XAML.

You can highlight them and hit the [delete] key.

Image 18

 

This does mean that no items will show up in the ListView any longer, but since we are moving toward a final solution that is fine.

Next, we’ll format the date so that it will match the format we use on the file entries.  Of course, since there will be a count of all files for each date, we don’t need to include the file number.

Change Date Format

Open up MainPage.xaml.cs and go to the first line of code in the LoadEntriesByDate() method.

All we have to do is add the format specifier (“yyyy-MM-DD”) to the ToString() method.

C#
EntriesListView.Items.Add(MainCalendar.SelectedDates[0].ToString("yyyy-MM-dd"));

Image 19

 

Add Margin To HeaderTemplate

I altered the Margins on both of the HeaderTemplate TextBlock items so they now look like the following:

XML
<TextBlock Margin="7 0 0 0" Grid.Row="0" Grid.Column="0" Foreground="#ff0000" Text="Date"  />
<TextBlock Margin="0 0 15 0" Grid.Row="0" Grid.Column="1" Foreground="#ff0000" Text="Entry Count" />

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

SideBar Small Bug-fix

Note: There is a slight bug-fix in TextBlock code that I show in the next sample.  That came about because I noticed that the second TextBlock element’s Grid.Row was set to 1.  However, there is no Row 1 (second row).  Fortunately, the XAML parser simply ignores that value and instead goes by the Grid.RowDefinition and understands there is only one row.

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

Image 20


Problems I Encountered : A Microsoft Bug?

At this point I thought I’d be able to run the app and things would look right, however I discovered an issue with the layout of the items in the ListView.  

Item Data Overlaps

Even though we are using a Grid definition for the ItemTemplate which matches the HeaderTemplate, the item layout does not get formatted the same.  This causes an issue so that the item data does not align with the header. It doesn’t look nice at all.

Image 21

 

I had to work with the layout and I even posted a question about HeaderTemplate and ItemTemplate not matching even when the XAML is exactly the same at StackOverflow (https://stackoverflow.com/questions/47775116/why-do-listview-headertemplate-and-listview-itemtemplate-uwp-xaml-display-diff)*.

*While writing this chapter, a solution to this problem came in so I’ve come back to make this note.  The solution described requires we add a new ListView.ItemContainerStyle.  That opens up a whole new can of worms so for now, I’m going to ignore the solution and use my simpler (far less good) solution.  

I finally determined that if I make the changes shown below to the ItemTemplate column definition it will look close to what we are expecting.

Here’s the entire XAML listing of the ListView.ItemTemplate so you can make sure you have it all correct.  The main change is the ColumnDefinitions Width values.

XML
<ListView.ItemTemplate>
  <DataTemplate>
     <Grid>
        <Grid.RowDefinitions>
           <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
           <ColumnDefinition Width="200" />
           <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
       <TextBlock Margin="7 0 0 0"  Grid.Row="0" Grid.Column="0" Foreground="DarkBlue" Text="{Binding}"/>
       <TextBlock Margin="25 0 2 0" Grid.Row="0" Grid.Column="1" Foreground="DarkBlue" Text="3" />
      </Grid>
  </DataTemplate>
</ListView.ItemTemplate>

Image 22

Build, Run, Check It Out

Now if you run the ListView looks much better.  If you need the current code, grab the DailyJournal_v025 and build it and run.  

Each time you click a date, the app will add an entry to the ListView.  Even though this isn’t how the app will eventually work, it allows us to test it.

 

Image 23

Scrollbar and Alignment Fixes

The scroll bar no longer obscures the header and the items are aligned much more nicely with the header.

Currently, we are using the value of 3 which is in the XAML to represent the Entry Count.

The date value is currently bound to the Entry item with the generic {Binding} syntax set on the TextBlock.  

However, what we really want is a simple object that has those two things (Date, Entry Count) as properties so that we can easily bind the value to the ListView item.

This will make it easier to set and track the values when we do our work to calculate the number of entries for each day and insert them into the ListView.

ViewModel Object: The Basics

This object we are talking about is really just a ViewModel.  It is a Model of something (two values which are used together) that the View will use to display some information.

Anonymous Types In C#

Since this object really won’t be used anywhere else, I’m going to create an anonymous type.

This will also allow you to gain some experience with anonymous types.

An anonymous type is simply a type that is generated on the fly so it has no name.

Normally when we create a new type, we wrap it in a C# class definition which creates a type that is named (gets its type name from the class name).

If we look at the top of our Page class as an example, we can see that we named it MainPage.

Image 24

 

In this case, MainPage becomes the type name of our UDT (User Defined Type).

In contrast, the anonymous type is not defined anywhere as a class, but the compiler knows how to build it and keep track of it internally.

Here’s the syntax that we use to create an anonymous class:

C#
var myAnonymous = new {name=value, name=value};

The names are supplied by the developer.

The value types get set by the type of data that is put into it.  This is known as a dynamic type in contrast to the static types (int, String, bool, etc) that C# normally uses.

If the values are numeric there is no need to put them in quotes. If the value is a String then you put it in quotes.

Here are a few more examples:

C#
var thing = new {count = 5, name="flintstone"};

After you create your anonymous type, you can access its properties by name.

C#
Console.WriteLine(thing.count); // prints 5

Console.WriteLine(thing.name); // prints flintstone

Since we want one object to hold both our date and our entry count this will work very nicely.

If you want more info, there is a good readable reference on anonymous types with nice examples at : https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types
 

Let’s go add our new anonymous type to our LoadEntriesByDate() method where we are adding new Items to our ListView.

Previously, we were simply adding the date as a formatted String to the ListView.

Image 25

 

Now, we are going to add an object which will contain the formatted String and the Entry Count to the ListView.  I’m going to display the new line on numerous lines so you it will be a bit more clear.

 

Image 26

 

Notice that we are stilling calling the Add() method on the Items list.

However, now we are adding an anonymous object which is defined as:

C#
{date = MainCalendar.SelectedDates[0].ToString("yyyy-MM-dd"), entryCount = 12}

This anonymous object gets instantiated and added to the ListView all in one line of code but it does have two properties named date and entryCount.  Right now I’m setting the entryCount to 12 just so you know that we will no longer use the value of 3 which we were previously using.

Building and Running Now: Not Quite Right

If you build and run you will find that the code does build with no errors and does run.  However, the ListView items do not look correct.

Image 27

Default Binding Tries But Fails

The default binding that we have set up attempts to bind to any value it can find so it grabs the anonymous object contained in the ListView and the default ToString() which is added to every object seems to fire so that you can see a part of the original object definition that we used to create the anonymous object.

Of course, we still have the hard-coded value of 3 in the Entry Count TextBlock and we need to fix that too.

Fix The Binding

Fixing the binding is very easy.  Let’s switch over to the MainPage.xaml and alter the Binding values on our ItemTemplate TextBoxes.

All we have to do is add the name of the property to the Binding syntax and everything will be fixed.

Here’s what our updated TextBlocks should look like now:

XML
<TextBlock Margin="7 0 0 0"  Grid.Row="0" Grid.Column="0" Foreground="DarkBlue" Text="{Binding date}"  />
<TextBlock Margin="25 0 2 0" Grid.Row="0" Grid.Column="1" Foreground="DarkBlue" Text="{Binding entryCount}" />

 

Image 28

With that change we told the binding mechanism to look inside any attached object for a date property and a entryCount property and use it to get the Text which should be displayed.  Of course, these values are case sensitive so they have to match the names of our properties in our anonymous object exactly.

Build and Run

Let’s build this code and run it to see how our changes look.  You can get DailyJournal_v026 source if you don’t already have it.

Now, things look correct.

Image 29

Getting Closer To Final Functionality

We are getting closer to the functionality we want but we have some more things to do.

I find it helpful to make short lists of the things I’m going to work on:

  1. Remove the line of code which adds the Item into the ListView from the LoadEntriesByDate method

    1. We will remove that line since we only had it there to test our ListView.

  2. Write the code to count the number of entries which exist for each day in the current month.

  3. Add the ListView SelectedChanged event so that when the user selects one of the ListView items, the MainCalendar is moved to that date and the first entry which exists is automatically loaded.

Entry Counting Code

Where should the code which counts the entries go?  Well, first of all we will put the code that does the actual counting and sorting of the entries in its own private method.  I guess we’ll call it CalculateEntryCount().

After that we’ll call CalculateEntryCount() every time the user loads a new month or saves a new item.

However, we want that method to be called by another method to separate the work that is done.  The CalculateEntryCount() will calculate the entries it will not update the ListView.

Instead that work will be done in a separate method which will call the CalculateEntryCount().

In the long run this makes everything more clear and when you don’t look at the code for weeks or months it will still make sense.

We are going to call the method which updates the ListView UI InitializeEntriesListView().

Get ‘R Dun (Get ‘R Workin’)

The InitializeEntriesListView method will be called every time the user clicks a new date.  For now, I’m not going to worry about whether or not the month has changed. I’m just going to fire the functionality every time.  We can always make it more efficient later, but for now I just want to get it working.

The first thing I want to do is add our stub methods (methods with no implementation -- no code) into the MainPage.xaml.cs file so we can then fill them out.

First we’ll add the call of the InitializeListView() to our MainCalendar_SelectedDatesChanged method:

C#
InitializeEntriesListView();

Image 30

Now we can be assured that the method will be called every time the user selects a new date.  That method is also fired when the app loads so that means it will initialize the ListView with the list of available entries when that app starts too.

Now, I’ve opened up some space in the MainPage.xaml.cs file and added our two new empty methods:

C#
private void InitializeEntriesListView()
{

}

private Dictionary<string,int> CalculateEntryCount()
{    

}

Image 31


 

Since I’m working with these two methods at the same time, it’s nice to have them together in the file.

Of course Visual Studio knows that the CalculateEntryCount() method will return a Dictionary (more later) and it is warning that it doesn’t see a return statement in the method yet by displaying a red squiggly line.

CalculateEntryCount Implementation

I’ve took some time to figure out the code and I’ve written up the method.  First of all here is a summary of what the CalculateEntryCount() method does.

High-Level Look

  1. Determines if Y-M folder for the current Year-Month exists. If it doesn’t then there are no files for the month and nothing to calculate.

  2. If the Y-M folder does exist then there is at least one file for the month so it finds all files in current month folder.

  3. After it finds all the files, it needs to organize them for easy grouping (counting) and use (creating new anonymous objects to add to the ListView)

  4. It stores the file dates and stores them with their associated counts in a Dictionary collection and returns the collection to the calling method (InitializeEntriesListView() in our case).

Detailed Look At Code

Here’s the entire CalculateEntryCount() method.

C#
private Dictionary<string,int> CalculateEntryCount()
{
  String ymfolder = YMDDate.Substring(0, 7);
  Dictionary<string, int> allEntries = new Dictionary<string, int>();

  if (Directory.Exists(Path.Combine(appHomeFolder.Path, ymfolder)))
  {
     String[] allCurrentFiles = Directory.GetFiles(
     Path.Combine(appHomeFolder.Path, ymfolder),
     "*.rtf",
     SearchOption.TopDirectoryOnly);

     foreach (string f in allCurrentFiles)
     {
        int x = 0;
        // if the date key is already in the collection x will be > 0
        string strippedFileName = Path.GetFileName(f).Substring(0, 10);
        allEntries.TryGetValue(strippedFileName, out x);

        if (x == 0)
        {
           allEntries.Add(strippedFileName, 1);
        }
        else
        {
           allEntries[strippedFileName] = ++x;
        }
     }
  }
  return allEntries;
}

Image 32

 

The code in this method which iterates through the files is exactly like the code we’ve seen before in LoadEntriesByDate().  However, the new code starting on line 121 inside the foreach loop is new and interesting so let’s talk about it.

On line 121 I create a local temp variable (x) and set it to 0 every time through the loop because I need it to be initialized every time.

On line 123 I strip out the file name only (no path info) and then I Substring the filename so we don’t have any of the entry number info.  I get just the first 10 characters (yyyy-mm-dd).

I then use our Dictionary collection on line 124, which I’ve prepped at the top of the method.

A Dictionary is a generic collection made up of a Key and a Value. Since it is a Dictionary the Key must be unique.  It will not allow you to add an item with the same Key more than once.  

This is a generic Dictionary because you can set the type of the Key and Value that the Dictionary will contain. In our case I set the Key to be a String and the Value to be an Integer.

I’m depending upon the Dictionary Key requiring a unique key so I can create a collection of keyed (unique) File name/dates.  Then I will increment the value each time the same key is found.

I’ll explain that again and more clearly as we go.

Dictionary.TryGetValue() Method: How It Works

On line 124, I call a method called TryGetValue on the Dictionary which takes two parameters:

  1. Key you are searching for in the Dictionary -- this is our String date like 2017-12-05

  2. Value which will be returned in the out variable you pass in -- I’m passing in the integer x variable.

If TryGetValue does not find the Key in the Dictionary, it does not return anything  and x will still be its initialized value of 0.

That is why on line 125 I check to see if x is equal to 0.  If it is then I add the new item to the Dictionary with a value of 1 (on line 127).

So, the first time it finds a file, for the date of 2017-12-05, it will insert the Key “2017-12-05” with a value of 1.

That indicates that we’ve found one file which was created on 2017-12-05.  

However, if there is more than one file with that same prefix (2017-12-05) then TryGetValue will return the integer value into the out parameter x.

So, the next time through the loop when it finds a file with 2017-12-05 TryGetValue will return the x value of 1.

When it returns the x value of 1 (or any value not equal to 0) then we will reach the else block and the associated line of code at line 131.

Line 131 will get the current object with the String Key value of 2017-12-05 and set it’s Value to ++x.  That is x incremented by 1.

This builds up an entire set of entries in the allEntries Dictionary which would look something like the following:

Key = 2017-12-05, Value=3

Key = 2017-12-14, Value=1

Key = 2017-12-18, Value=1

Now that we’ve completed the hard part, all we have to do is iterate through those to add them to the ListView.

InitializeEntriesListView : Display

This method will do the following:

  1. Clear the EntriesListView -- since it runs every time you need to clear old items

  2. Iterate through the list of items returned by CalculateEntryCount()

  3. Create an anonymous object from each returned item

  4. Add each anonymous object  to the EntriesListView

Here’s the code:

C#
private void InitializeEntriesListView()
{
  EntriesListView.Items.Clear();

  foreach (var item in CalculateEntryCount().OrderBy(f => f.Key))
  {
        EntriesListView.Items.Add(
        new
        {
           date = item.Key,
           entryCount = item.Value
        }
     );
  }
}

Image 33

I’ve highlighted the anonymous object that is created and passed into the Items.Add() method just to make it stand out.

You can see that on line 104 we clear the current EntriesListView so all items will be gone since we are now going to load it again.

Next on line 105 we run a foreach loop through the items returned by the CalculateEntryCount() method.  We will refer to each object in that Dictionary as item and we call an OrderBy extension method on the Dictionary that will sort the Dictionary so that the lower date values (Dec 4 comes before Dec 5, etc) will be listed first in the EntriesListView.

Inside the foreach loop we use the item variable to create the new anonymous object that has the two named properties (date and entryCount). We get the date from the item.Key and the entryCount from the item.Value.  

Now when we run the code, all of the entries found for a month will be listed in the EntriesListView.

Build it and Fire It Up!

Build the code and try it out.  If you need all of the updates, get DailyJournal_v027.

Image 34

You can see that each date’s entry also has an Entry Count.  For example, the 2017-12-07 has an Entry Count of 3 listed.  If we click on that date in the MainCalendar (which I have done), you will see that there are actually three entries on the right.

Oh, Just One More Thing

It all works great. Except, one more thing.

You should be able to click on the ListView item and have it move to the date that you chose and load the entries on the right.  That’s very easy code so let’s do it and wrap this chapter up.

We want the code to run when the user changes the selection on the EntriesListView.  That is the SelectionChanged event of the ListView.

You can

  1. go to MainPage.xaml and select the EntriesListView in the XAML

  2. Click on the [Lightning bolt] icon in the Properties dialog box.

  3. Scroll down to the SelectionChanged event

  4. Double-click inside the TextBox next to the SelectionChanged event

Image 35


 

Visual Studio will open up the MainPage.xaml.cs and add the new method (EntriesListView_SelectionChanged) and place the cursor inside the brackets so it is ready for you to type some code.

Here’s the simple code we need.

C#
if (e.AddedItems.Count > 0) {
  dynamic currentAnon = e.AddedItems[0];
  MainCalendar.SelectedDates.Clear();
  DateTime dt = DateTime.Parse(currentAnon.date);
  MainCalendar.SelectedDates.Add(dt);
}

Image 36

When this method is called by the OS because the user has made a new selection in the ListView, the subsystem provides a parameter named e.  That parameter contains an array of items named AddedItems.  At times the method may be called even though there is no item added so we have to check that the Count is greater than 0 -- that there is an item in the collection.  Once we’ve confirmed that we only care about the first item (index 0) so we get it and we save it into a dynamic variable.  

dynamic is the keyword that tells the compiler that we have a type that we want it to figure out what it contains.   Since we’ve previously added anonymous types to our ListView, we know they are defined as <string, int> so we know how to get their values out.

On line 242 we clear all of the selected dates that may be currently chosen on the MainCalendar.  This prepares the calendar since we are going to programmatically select a date.

Next on line 242 we use a static method called Parse which is a part of the DateTime object that can easily parse the anonymous type’s date property (a string) back into a date.  We save that value in the local temp variable dt.

Finally, on line 243, we use that date to add the date to the MainCalendar collection property called SelectedDates.  When we add that value, it becomes the newly selected date -- which matches the value in the EntriesListView.  When that new date is selected in the ListView, the MainCalendar_SelectedDatesChanged event fires and runs our code to load all of the available entries.  Now the user can select an item from the Entry List view so she can easily navigate through the existing entries.

You Have To Try That Out

Build it and run it and it works like a real app now.  It’s very easy to navigate and manage your entries.  Of course, you still cannot delete them from the app.  Work to do.  :)

Get the DailyJournal_v028 source and try it out.  

Now when you click any of the EntriesListView item the associated date will be selected on the MainCalendar and the entries for that day will be loaded.

Also, if you cycle back through the months and select any day in the month then the EntriesListView will be initialized again and you’ll be able to see what days have entries in that month.

It creates a nice way to go through the available entries.

History

2017-12-12 : First published Chapter 12 on 12th day of Dec (12).  xD 

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 --