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)[^]
Print 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 :
- Find all existing entries (based on selected month)
-
Count the existing entries found per day
-
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.
<ListView Name="EntriesListView" Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
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.
We want to add two things to the ListView
:
-
A header
-
Data
If we wanted to add static data we could just add some string elements and the items would appear in the list.
<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>
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.
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:
<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.
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.
<DataTemplate>
</DataTemplate>
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:
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold">1st Header</TextBlock>
<TextBlock FontWeight="Bold">2nd Header</TextBlock>
</StackPanel>
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
.
<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>
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.
<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.
Now we can define our Grid
layout for the Items
which get added to the EntriesListView
.
Here’s our initial Grid
layout:
<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>
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.
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.
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
).
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.
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:
Text="{Binding}"
For now, we’ll continue to use the hard-coded Entry Count of 3.
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.
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.
-
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.
-
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.
Things To Change
There are a few things we should fix before moving on.
-
Remove the test hard-coded items so they no longer appear.
-
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)
-
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.
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.
EntriesListView.Items.Add(MainCalendar.SelectedDates[0].ToString("yyyy-MM-dd"));
Add Margin To HeaderTemplate
I altered the Margins
on both of the HeaderTemplate TextBlock
items so they now look like the following:
<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.
############################################################################
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.
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.
<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>
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.
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
.
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:
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:
var thing = new {count = 5, name="flintstone"};
After you create your anonymous type, you can access its properties by name.
Console.WriteLine(thing.count);
Console.WriteLine(thing.name);
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
.
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.
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:
{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.
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:
<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}" />
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.
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:
-
Remove the line of code which adds the Item into the ListView from the LoadEntriesByDate
method
-
We will remove that line since we only had it there to test our ListView
.
-
Write the code to count the number of entries which exist for each day in the current month.
-
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:
InitializeEntriesListView();
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:
private void InitializeEntriesListView()
{
}
private Dictionary<string,int> CalculateEntryCount()
{
}
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
-
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.
-
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.
-
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
)
-
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.
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;
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;
}
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:
-
Key you are searching for in the Dictionary
-- this is our String
date like 2017-12-05
-
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:
-
Clear the EntriesListView
-- since it runs every time you need to clear old items
-
Iterate through the list of items returned by CalculateEntryCount
()
-
Create an anonymous object from each returned item
-
Add each anonymous object to the EntriesListView
Here’s the code:
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
}
);
}
}
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
.
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
-
go to MainPage.xaml
and select the EntriesListView
in the XAML
-
Click on the [Lightning bolt] icon in the Properties
dialog box.
-
Scroll down to the SelectionChanged
event
-
Double-click inside the TextBox
next to the SelectionChanged
event
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.
if (e.AddedItems.Count > 0) {
dynamic currentAnon = e.AddedItems[0];
MainCalendar.SelectedDates.Clear();
DateTime dt = DateTime.Parse(currentAnon.date);
MainCalendar.SelectedDates.Add(dt);
}
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