diff --git a/Chapter05/Complete/Ch5-MyMediaCollection.sln b/Chapter05/Complete/Ch5-MyMediaCollection.sln
new file mode 100644
index 0000000..d74113c
--- /dev/null
+++ b/Chapter05/Complete/Ch5-MyMediaCollection.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33530.505
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyMediaCollection", "MyMediaCollection\MyMediaCollection.csproj", "{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Build.0 = Debug|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.ActiveCfg = Debug|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Build.0 = Debug|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Deploy.0 = Debug|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.ActiveCfg = Debug|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Build.0 = Debug|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Deploy.0 = Debug|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.ActiveCfg = Release|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Build.0 = Release|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Deploy.0 = Release|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.ActiveCfg = Release|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Build.0 = Release|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Deploy.0 = Release|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.ActiveCfg = Release|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Build.0 = Release|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Deploy.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C9277197-C949-4948-80CD-0A685EE6DCBB}
+ EndGlobalSection
+EndGlobal
diff --git a/Chapter05/Complete/MyMediaCollection/App.xaml b/Chapter05/Complete/MyMediaCollection/App.xaml
new file mode 100644
index 0000000..eeb9e6e
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/App.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Complete/MyMediaCollection/App.xaml.cs b/Chapter05/Complete/MyMediaCollection/App.xaml.cs
new file mode 100644
index 0000000..3eb7fec
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/App.xaml.cs
@@ -0,0 +1,70 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Services;
+using MyMediaCollection.ViewModels;
+using MyMediaCollection.Views;
+using System;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace MyMediaCollection
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : Application
+ {
+ public static IHost HostContainer { get; private set; }
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// Invoked when the application is launched.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ {
+ m_window = new MainWindow();
+ var rootFrame = new Frame();
+ RegisterComponents(rootFrame);
+ rootFrame.NavigationFailed += RootFrame_NavigationFailed;
+ rootFrame.Navigate(typeof(MainPage), args);
+ m_window.Content = rootFrame;
+ m_window.Activate();
+ }
+
+ private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception($"Error loading page {e.SourcePageType.FullName}");
+ }
+
+ private Window m_window;
+
+ private void RegisterComponents(Frame rootFrame)
+ {
+ var navigationService = new NavigationService(rootFrame);
+ navigationService.Configure(nameof(MainPage), typeof(MainPage));
+ navigationService.Configure(nameof(ItemDetailsPage), typeof(ItemDetailsPage));
+
+ HostContainer = Host.CreateDefaultBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddSingleton(navigationService);
+ services.AddSingleton();
+ services.AddTransient();
+ services.AddTransient();
+ }).Build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Assets/LockScreenLogo.scale-200.png b/Chapter05/Complete/MyMediaCollection/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..7440f0d
Binary files /dev/null and b/Chapter05/Complete/MyMediaCollection/Assets/LockScreenLogo.scale-200.png differ
diff --git a/Chapter05/Complete/MyMediaCollection/Assets/SplashScreen.scale-200.png b/Chapter05/Complete/MyMediaCollection/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..32f486a
Binary files /dev/null and b/Chapter05/Complete/MyMediaCollection/Assets/SplashScreen.scale-200.png differ
diff --git a/Chapter05/Complete/MyMediaCollection/Assets/Square150x150Logo.scale-200.png b/Chapter05/Complete/MyMediaCollection/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..53ee377
Binary files /dev/null and b/Chapter05/Complete/MyMediaCollection/Assets/Square150x150Logo.scale-200.png differ
diff --git a/Chapter05/Complete/MyMediaCollection/Assets/Square44x44Logo.scale-200.png b/Chapter05/Complete/MyMediaCollection/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..f713bba
Binary files /dev/null and b/Chapter05/Complete/MyMediaCollection/Assets/Square44x44Logo.scale-200.png differ
diff --git a/Chapter05/Complete/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Chapter05/Complete/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..dc9f5be
Binary files /dev/null and b/Chapter05/Complete/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Chapter05/Complete/MyMediaCollection/Assets/StoreLogo.png b/Chapter05/Complete/MyMediaCollection/Assets/StoreLogo.png
new file mode 100644
index 0000000..a4586f2
Binary files /dev/null and b/Chapter05/Complete/MyMediaCollection/Assets/StoreLogo.png differ
diff --git a/Chapter05/Complete/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png b/Chapter05/Complete/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..8b4a5d0
Binary files /dev/null and b/Chapter05/Complete/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/Chapter05/Complete/MyMediaCollection/Enums/ItemType.cs b/Chapter05/Complete/MyMediaCollection/Enums/ItemType.cs
new file mode 100644
index 0000000..2e50873
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Enums/ItemType.cs
@@ -0,0 +1,9 @@
+namespace MyMediaCollection.Enums
+{
+ public enum ItemType
+ {
+ Music,
+ Video,
+ Book
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Enums/LocationType.cs b/Chapter05/Complete/MyMediaCollection/Enums/LocationType.cs
new file mode 100644
index 0000000..a8d19aa
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Enums/LocationType.cs
@@ -0,0 +1,8 @@
+namespace MyMediaCollection.Enums
+{
+ public enum LocationType
+ {
+ InCollection,
+ Loaned
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Interfaces/IDataService.cs b/Chapter05/Complete/MyMediaCollection/Interfaces/IDataService.cs
new file mode 100644
index 0000000..e53eff7
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Interfaces/IDataService.cs
@@ -0,0 +1,19 @@
+using MyMediaCollection.Enums;
+using MyMediaCollection.Model;
+using System.Collections.Generic;
+
+namespace MyMediaCollection.Interfaces
+{
+ public interface IDataService
+ {
+ IList GetItems();
+ MediaItem GetItem(int id);
+ int AddItem(MediaItem item);
+ void UpdateItem(MediaItem item);
+ IList GetItemTypes();
+ Medium GetMedium(string name);
+ IList GetMediums();
+ IList GetMediums(ItemType itemType);
+ IList GetLocationTypes();
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Interfaces/INavigationService.cs b/Chapter05/Complete/MyMediaCollection/Interfaces/INavigationService.cs
new file mode 100644
index 0000000..c8ccee6
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Interfaces/INavigationService.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace MyMediaCollection.Interfaces
+{
+ public interface INavigationService
+ {
+ string CurrentPage { get; }
+ void NavigateTo(string page);
+ void NavigateTo(string page, object parameter);
+ void GoBack();
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/MainWindow.xaml b/Chapter05/Complete/MyMediaCollection/MainWindow.xaml
new file mode 100644
index 0000000..7ebe85e
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/MainWindow.xaml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/MainWindow.xaml.cs b/Chapter05/Complete/MyMediaCollection/MainWindow.xaml.cs
new file mode 100644
index 0000000..b955d8d
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/MainWindow.xaml.cs
@@ -0,0 +1,15 @@
+using Microsoft.UI.Xaml;
+
+namespace MyMediaCollection
+{
+ ///
+ /// An empty window that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Model/MediaItem.cs b/Chapter05/Complete/MyMediaCollection/Model/MediaItem.cs
new file mode 100644
index 0000000..5ab161a
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Model/MediaItem.cs
@@ -0,0 +1,13 @@
+using MyMediaCollection.Enums;
+
+namespace MyMediaCollection.Model
+{
+ public class MediaItem
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public ItemType MediaType { get; set; }
+ public Medium MediumInfo { get; set; }
+ public LocationType Location { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Model/Medium.cs b/Chapter05/Complete/MyMediaCollection/Model/Medium.cs
new file mode 100644
index 0000000..f22fac4
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Model/Medium.cs
@@ -0,0 +1,11 @@
+using MyMediaCollection.Enums;
+
+namespace MyMediaCollection.Model
+{
+ public class Medium
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public ItemType MediaType { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/MyMediaCollection.csproj b/Chapter05/Complete/MyMediaCollection/MyMediaCollection.csproj
new file mode 100644
index 0000000..2d334d5
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/MyMediaCollection.csproj
@@ -0,0 +1,65 @@
+
+
+ WinExe
+ net6.0-windows10.0.19041.0
+ 10.0.17763.0
+ MyMediaCollection
+ app.manifest
+ x86;x64;ARM64
+ win10-x86;win10-x64;win10-arm64
+ win10-$(Platform).pubxml
+ true
+ true
+ 10.0.18362.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+
+
+ MSBuild:Compile
+
+
+
+
+
+ true
+
+
diff --git a/Chapter05/Complete/MyMediaCollection/Package.appxmanifest b/Chapter05/Complete/MyMediaCollection/Package.appxmanifest
new file mode 100644
index 0000000..11029a8
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Package.appxmanifest
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ MyMediaCollection
+ alash
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Complete/MyMediaCollection/Properties/launchSettings.json b/Chapter05/Complete/MyMediaCollection/Properties/launchSettings.json
new file mode 100644
index 0000000..b7ae329
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "MyMediaCollection (Package)": {
+ "commandName": "MsixPackage"
+ },
+ "MyMediaCollection (Unpackaged)": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Services/DataService.cs b/Chapter05/Complete/MyMediaCollection/Services/DataService.cs
new file mode 100644
index 0000000..2f57701
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Services/DataService.cs
@@ -0,0 +1,163 @@
+using MyMediaCollection.Enums;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MyMediaCollection.Services
+{
+ public class DataService : IDataService
+ {
+ private IList _items;
+ private IList _itemTypes;
+ private IList _mediums;
+ private IList _locationTypes;
+
+ public DataService()
+ {
+ PopulateItemTypes();
+ PopulateMediums();
+ PopulateLocationTypes();
+ PopulateItems();
+ }
+
+ private void PopulateItems()
+ {
+ var cd = new MediaItem
+ {
+ Id = 1,
+ Name = "Classical Favorites",
+ MediaType = ItemType.Music,
+ MediumInfo = _mediums.FirstOrDefault(m => m.Name == "CD"),
+ Location = LocationType.InCollection
+ };
+
+ var book = new MediaItem
+ {
+ Id = 2,
+ Name = "Classic Fairy Tales",
+ MediaType = ItemType.Book,
+ MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Hardcover"),
+ Location = LocationType.InCollection
+ };
+
+ var bluRay = new MediaItem
+ {
+ Id = 3,
+ Name = "The Mummy",
+ MediaType = ItemType.Video,
+ MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Blu Ray"),
+ Location = LocationType.InCollection
+ };
+
+ _items = new List
+ {
+ cd,
+ book,
+ bluRay
+ };
+ }
+
+ private void PopulateMediums()
+ {
+ var cd = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" };
+ var vinyl = new Medium { Id = 2, MediaType = ItemType.Music, Name = "Vinyl" };
+ var hardcover = new Medium { Id = 3, MediaType = ItemType.Book, Name = "Hardcover" };
+ var paperback = new Medium { Id = 4, MediaType = ItemType.Book, Name = "Paperback" };
+ var dvd = new Medium { Id = 5, MediaType = ItemType.Video, Name = "DVD" };
+ var bluRay = new Medium { Id = 6, MediaType = ItemType.Video, Name = "Blu Ray" };
+
+ _mediums = new List
+ {
+ cd,
+ vinyl,
+ hardcover,
+ paperback,
+ dvd,
+ bluRay
+ };
+ }
+
+ private void PopulateItemTypes()
+ {
+ _itemTypes = new List
+ {
+ ItemType.Book,
+ ItemType.Music,
+ ItemType.Video
+ };
+ }
+
+ private void PopulateLocationTypes()
+ {
+ _locationTypes = new List
+ {
+ LocationType.InCollection,
+ LocationType.Loaned
+ };
+ }
+
+ public int AddItem(MediaItem item)
+ {
+ item.Id = _items.Max(i => i.Id) + 1;
+ _items.Add(item);
+
+ return item.Id;
+ }
+
+ public MediaItem GetItem(int id)
+ {
+ return _items.FirstOrDefault(i => i.Id == id);
+ }
+
+ public IList GetItems()
+ {
+ return _items;
+ }
+
+ public IList GetItemTypes()
+ {
+ return _itemTypes;
+ }
+
+ public IList GetMediums()
+ {
+ return _mediums;
+ }
+
+ public IList GetMediums(ItemType itemType)
+ {
+ return _mediums
+ .Where(m => m.MediaType == itemType)
+ .ToList();
+ }
+
+ public IList GetLocationTypes()
+ {
+ return _locationTypes;
+ }
+
+ public void UpdateItem(MediaItem item)
+ {
+ var idx = -1;
+ var matchedItem =
+ (from x in _items
+ let ind = idx++
+ where x.Id == item.Id
+ select ind).FirstOrDefault();
+
+ if (idx == -1)
+ {
+ throw new Exception("Unable to update item. Item not found in collection.");
+ }
+
+ _items[idx] = item;
+ }
+
+ public Medium GetMedium(string name)
+ {
+ return _mediums.FirstOrDefault(m => m.Name == name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Services/NavigationService.cs b/Chapter05/Complete/MyMediaCollection/Services/NavigationService.cs
new file mode 100644
index 0000000..63f8c95
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Services/NavigationService.cs
@@ -0,0 +1,84 @@
+using Microsoft.UI.Xaml.Controls;
+using MyMediaCollection.Interfaces;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MyMediaCollection.Services
+{
+ public class NavigationService : INavigationService
+ {
+ public NavigationService(Frame rootFrame)
+ {
+ AppFrame = rootFrame;
+ }
+
+ private readonly IDictionary _pages = new ConcurrentDictionary();
+
+ public const string RootPage = "(Root)";
+
+ public const string UnknownPage = "(Unknown)";
+
+ private static Frame AppFrame;
+
+ public void Configure(string page, Type type)
+ {
+ if (_pages.Values.Any(v => v == type))
+ {
+ throw new ArgumentException($"The {type.Name} view has already been registered under another name.");
+ }
+
+ _pages[page] = type;
+ }
+
+ ///
+ /// Gets the name of the currently displayed page.
+ ///
+ public string CurrentPage
+ {
+ get
+ {
+ var frame = AppFrame;
+
+ if (frame.BackStackDepth == 0)
+ return RootPage;
+
+ if (frame.Content == null)
+ return UnknownPage;
+
+ var type = frame.Content.GetType();
+
+ if (_pages.Values.All(v => v != type))
+ return UnknownPage;
+
+ var item = _pages.Single(i => i.Value == type);
+
+ return item.Key;
+ }
+ }
+
+ public void NavigateTo(string page)
+ {
+ NavigateTo(page, null);
+ }
+
+ public void NavigateTo(string page, object parameter)
+ {
+ if (!_pages.ContainsKey(page))
+ {
+ throw new ArgumentException($"Unable to find a page registered with the name {page}.");
+ }
+
+ AppFrame.Navigate(_pages[page], parameter);
+ }
+
+ public void GoBack()
+ {
+ if (AppFrame?.CanGoBack == true)
+ {
+ AppFrame.GoBack();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/ViewModels/ItemDetailsViewModel.cs b/Chapter05/Complete/MyMediaCollection/ViewModels/ItemDetailsViewModel.cs
new file mode 100644
index 0000000..28db7ed
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/ViewModels/ItemDetailsViewModel.cs
@@ -0,0 +1,161 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MyMediaCollection.Enums;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Model;
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace MyMediaCollection.ViewModels
+{
+ public partial class ItemDetailsViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private ObservableCollection locationTypes = new();
+ [ObservableProperty]
+ private ObservableCollection mediums = new();
+ [ObservableProperty]
+ private ObservableCollection itemTypes = new();
+ private int _itemId;
+ [ObservableProperty]
+ private string itemName;
+ [ObservableProperty]
+ private string selectedMedium;
+ [ObservableProperty]
+ private string selectedItemType;
+ [ObservableProperty]
+ private string selectedLocation;
+ [ObservableProperty]
+ private bool isDirty;
+ private int _selectedItemId = -1;
+ protected INavigationService _navigationService;
+ protected IDataService _dataService;
+
+ public ItemDetailsViewModel(INavigationService navigationService, IDataService dataService)
+ {
+ _navigationService = navigationService;
+ _dataService = dataService;
+
+ PopulateLists();
+ }
+
+ public void InitializeItemDetailData(int itemId)
+ {
+ _selectedItemId = itemId;
+
+ PopulateExistingItem(_dataService);
+ IsDirty = false;
+ }
+
+ private void PopulateExistingItem(IDataService dataService)
+ {
+ if (_selectedItemId > 0)
+ {
+ var item = _dataService.GetItem(_selectedItemId);
+ Mediums.Clear();
+
+ foreach (string medium in dataService.GetMediums(item.MediaType).Select(m => m.Name))
+ Mediums.Add(medium);
+
+ _itemId = item.Id;
+ ItemName = item.Name;
+ SelectedMedium = item.MediumInfo.Name;
+ SelectedLocation = item.Location.ToString();
+ SelectedItemType = item.MediaType.ToString();
+ }
+ }
+
+ private void PopulateLists()
+ {
+ ItemTypes.Clear();
+ foreach (string iType in Enum.GetNames(typeof(ItemType)))
+ ItemTypes.Add(iType);
+
+ LocationTypes.Clear();
+ foreach (string lType in Enum.GetNames(typeof(LocationType)))
+ LocationTypes.Add(lType);
+
+ Mediums = new ObservableCollection();
+ }
+
+ private void Save()
+ {
+ MediaItem item;
+
+ if (_itemId > 0)
+ {
+ item = _dataService.GetItem(_itemId);
+
+ item.Name = ItemName;
+ item.Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation);
+ item.MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType);
+ item.MediumInfo = _dataService.GetMedium(SelectedMedium);
+
+ _dataService.UpdateItem(item);
+ }
+ else
+ {
+ item = new MediaItem
+ {
+ Name = ItemName,
+ Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation),
+ MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType),
+ MediumInfo = _dataService.GetMedium(SelectedMedium)
+ };
+
+ _dataService.AddItem(item);
+ }
+ }
+
+ public void SaveItemAndContinue()
+ {
+ Save();
+ _itemId = 0;
+ ItemName = string.Empty;
+ SelectedMedium = null;
+ SelectedLocation = null;
+ SelectedItemType = null;
+ IsDirty = false;
+ }
+
+ public void SaveItemAndReturn()
+ {
+ Save();
+ _navigationService.GoBack();
+ }
+
+ partial void OnItemNameChanged(string value)
+ {
+ IsDirty = true;
+ }
+
+ partial void OnSelectedMediumChanged(string value)
+ {
+ IsDirty = true;
+ }
+
+ partial void OnSelectedItemTypeChanged(string value)
+ {
+ IsDirty = true;
+ Mediums.Clear();
+
+ if (!string.IsNullOrWhiteSpace(value))
+ {
+ foreach (string med in _dataService.GetMediums((ItemType)Enum.Parse(typeof(ItemType), SelectedItemType)).Select(m => m.Name))
+ Mediums.Add(med);
+ }
+ }
+
+ partial void OnSelectedLocationChanged(string value)
+ {
+ IsDirty = true;
+ }
+
+ [RelayCommand]
+ private void Cancel()
+ {
+ _navigationService.GoBack();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/ViewModels/MainViewModel.cs b/Chapter05/Complete/MyMediaCollection/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..c819bb3
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/ViewModels/MainViewModel.cs
@@ -0,0 +1,100 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.UI.Xaml.Input;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Model;
+using System.Collections.ObjectModel;
+
+namespace MyMediaCollection.ViewModels
+{
+ public partial class MainViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string selectedMedium;
+ [ObservableProperty]
+ private ObservableCollection items = new ObservableCollection();
+ private ObservableCollection allItems;
+ [ObservableProperty]
+ private ObservableCollection mediums;
+ [ObservableProperty]
+ [NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
+ private MediaItem selectedMediaItem;
+ private INavigationService _navigationService;
+ private IDataService _dataService;
+ private const string AllMediums = "All";
+
+ public MainViewModel(INavigationService navigationService, IDataService dataService)
+ {
+ _navigationService = navigationService;
+ _dataService = dataService;
+
+ PopulateData();
+ }
+
+ public void PopulateData()
+ {
+ Items.Clear();
+
+ foreach (var item in _dataService.GetItems())
+ {
+ Items.Add(item);
+ }
+
+ allItems = new ObservableCollection(Items);
+
+ Mediums = new ObservableCollection
+ {
+ AllMediums
+ };
+
+ foreach (var itemType in _dataService.GetItemTypes())
+ {
+ Mediums.Add(itemType.ToString());
+ }
+
+ SelectedMedium = Mediums[0];
+ }
+
+ partial void OnSelectedMediumChanged(string value)
+ {
+ Items.Clear();
+
+ foreach (var item in allItems)
+ {
+ if (string.IsNullOrWhiteSpace(value)
+ || value == "All"
+ || value == item.MediaType.ToString())
+ {
+ Items.Add(item);
+ }
+ }
+ }
+
+ [RelayCommand]
+ private void AddEdit()
+ {
+ var selectedItemId = -1;
+
+ if (SelectedMediaItem != null)
+ {
+ selectedItemId = SelectedMediaItem.Id;
+ }
+
+ _navigationService.NavigateTo("ItemDetailsPage", selectedItemId);
+ }
+
+ public void ListViewDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
+ {
+ AddEdit();
+ }
+
+ [RelayCommand(CanExecute = nameof(CanDeleteItem))]
+ private void Delete()
+ {
+ allItems.Remove(SelectedMediaItem);
+ Items.Remove(SelectedMediaItem);
+ }
+
+ private bool CanDeleteItem() => SelectedMediaItem != null;
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Views/ItemDetailsPage.xaml b/Chapter05/Complete/MyMediaCollection/Views/ItemDetailsPage.xaml
new file mode 100644
index 0000000..be08f04
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Views/ItemDetailsPage.xaml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Complete/MyMediaCollection/Views/ItemDetailsPage.xaml.cs b/Chapter05/Complete/MyMediaCollection/Views/ItemDetailsPage.xaml.cs
new file mode 100644
index 0000000..629d419
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Views/ItemDetailsPage.xaml.cs
@@ -0,0 +1,49 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+using MyMediaCollection.ViewModels;
+
+namespace MyMediaCollection.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class ItemDetailsPage : Page
+ {
+ public ItemDetailsPage()
+ {
+ ViewModel = App.HostContainer.Services.GetService();
+
+ this.InitializeComponent();
+
+ Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
+
+ // Load the user setting
+ string haveExplainedSaveSetting = localSettings.Values[nameof(SavingTip)] as string;
+
+ // If the user has not seen the save tip, display it
+
+ if (!bool.TryParse(haveExplainedSaveSetting, out bool result) || !result)
+ {
+ SavingTip.IsOpen = true;
+
+ // Save the teaching tip setting
+ localSettings.Values[nameof(SavingTip)] = "true";
+ }
+ }
+
+ public ItemDetailsViewModel ViewModel;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ var itemId = (int)e.Parameter;
+
+ if (itemId > 0)
+ {
+ ViewModel.InitializeItemDetailData(itemId);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/Views/MainPage.xaml b/Chapter05/Complete/MyMediaCollection/Views/MainPage.xaml
new file mode 100644
index 0000000..9659b71
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Views/MainPage.xaml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Complete/MyMediaCollection/Views/MainPage.xaml.cs b/Chapter05/Complete/MyMediaCollection/Views/MainPage.xaml.cs
new file mode 100644
index 0000000..5182eb2
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/Views/MainPage.xaml.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.UI.Xaml.Controls;
+using MyMediaCollection.ViewModels;
+
+namespace MyMediaCollection.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainPage : Page
+ {
+ public MainPage()
+ {
+ ViewModel = App.HostContainer.Services.GetService();
+ this.InitializeComponent();
+ }
+
+ public MainViewModel ViewModel;
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Complete/MyMediaCollection/app.manifest b/Chapter05/Complete/MyMediaCollection/app.manifest
new file mode 100644
index 0000000..abc9f68
--- /dev/null
+++ b/Chapter05/Complete/MyMediaCollection/app.manifest
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
\ No newline at end of file
diff --git a/Chapter05/Start/Ch5-MyMediaCollection.sln b/Chapter05/Start/Ch5-MyMediaCollection.sln
new file mode 100644
index 0000000..d74113c
--- /dev/null
+++ b/Chapter05/Start/Ch5-MyMediaCollection.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33530.505
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyMediaCollection", "MyMediaCollection\MyMediaCollection.csproj", "{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Build.0 = Debug|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Deploy.0 = Debug|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.ActiveCfg = Debug|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Build.0 = Debug|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Deploy.0 = Debug|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.ActiveCfg = Debug|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Build.0 = Debug|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Deploy.0 = Debug|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.ActiveCfg = Release|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Build.0 = Release|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Deploy.0 = Release|ARM64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.ActiveCfg = Release|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Build.0 = Release|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Deploy.0 = Release|x64
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.ActiveCfg = Release|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Build.0 = Release|x86
+ {972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Deploy.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C9277197-C949-4948-80CD-0A685EE6DCBB}
+ EndGlobalSection
+EndGlobal
diff --git a/Chapter05/Start/MyMediaCollection/App.xaml b/Chapter05/Start/MyMediaCollection/App.xaml
new file mode 100644
index 0000000..eeb9e6e
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/App.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Start/MyMediaCollection/App.xaml.cs b/Chapter05/Start/MyMediaCollection/App.xaml.cs
new file mode 100644
index 0000000..3eb7fec
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/App.xaml.cs
@@ -0,0 +1,70 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Services;
+using MyMediaCollection.ViewModels;
+using MyMediaCollection.Views;
+using System;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace MyMediaCollection
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : Application
+ {
+ public static IHost HostContainer { get; private set; }
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// Invoked when the application is launched.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ {
+ m_window = new MainWindow();
+ var rootFrame = new Frame();
+ RegisterComponents(rootFrame);
+ rootFrame.NavigationFailed += RootFrame_NavigationFailed;
+ rootFrame.Navigate(typeof(MainPage), args);
+ m_window.Content = rootFrame;
+ m_window.Activate();
+ }
+
+ private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
+ {
+ throw new Exception($"Error loading page {e.SourcePageType.FullName}");
+ }
+
+ private Window m_window;
+
+ private void RegisterComponents(Frame rootFrame)
+ {
+ var navigationService = new NavigationService(rootFrame);
+ navigationService.Configure(nameof(MainPage), typeof(MainPage));
+ navigationService.Configure(nameof(ItemDetailsPage), typeof(ItemDetailsPage));
+
+ HostContainer = Host.CreateDefaultBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddSingleton(navigationService);
+ services.AddSingleton();
+ services.AddTransient();
+ services.AddTransient();
+ }).Build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Assets/LockScreenLogo.scale-200.png b/Chapter05/Start/MyMediaCollection/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..7440f0d
Binary files /dev/null and b/Chapter05/Start/MyMediaCollection/Assets/LockScreenLogo.scale-200.png differ
diff --git a/Chapter05/Start/MyMediaCollection/Assets/SplashScreen.scale-200.png b/Chapter05/Start/MyMediaCollection/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..32f486a
Binary files /dev/null and b/Chapter05/Start/MyMediaCollection/Assets/SplashScreen.scale-200.png differ
diff --git a/Chapter05/Start/MyMediaCollection/Assets/Square150x150Logo.scale-200.png b/Chapter05/Start/MyMediaCollection/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..53ee377
Binary files /dev/null and b/Chapter05/Start/MyMediaCollection/Assets/Square150x150Logo.scale-200.png differ
diff --git a/Chapter05/Start/MyMediaCollection/Assets/Square44x44Logo.scale-200.png b/Chapter05/Start/MyMediaCollection/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..f713bba
Binary files /dev/null and b/Chapter05/Start/MyMediaCollection/Assets/Square44x44Logo.scale-200.png differ
diff --git a/Chapter05/Start/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Chapter05/Start/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..dc9f5be
Binary files /dev/null and b/Chapter05/Start/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Chapter05/Start/MyMediaCollection/Assets/StoreLogo.png b/Chapter05/Start/MyMediaCollection/Assets/StoreLogo.png
new file mode 100644
index 0000000..a4586f2
Binary files /dev/null and b/Chapter05/Start/MyMediaCollection/Assets/StoreLogo.png differ
diff --git a/Chapter05/Start/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png b/Chapter05/Start/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..8b4a5d0
Binary files /dev/null and b/Chapter05/Start/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/Chapter05/Start/MyMediaCollection/Enums/ItemType.cs b/Chapter05/Start/MyMediaCollection/Enums/ItemType.cs
new file mode 100644
index 0000000..2e50873
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Enums/ItemType.cs
@@ -0,0 +1,9 @@
+namespace MyMediaCollection.Enums
+{
+ public enum ItemType
+ {
+ Music,
+ Video,
+ Book
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Enums/LocationType.cs b/Chapter05/Start/MyMediaCollection/Enums/LocationType.cs
new file mode 100644
index 0000000..a8d19aa
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Enums/LocationType.cs
@@ -0,0 +1,8 @@
+namespace MyMediaCollection.Enums
+{
+ public enum LocationType
+ {
+ InCollection,
+ Loaned
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Interfaces/IDataService.cs b/Chapter05/Start/MyMediaCollection/Interfaces/IDataService.cs
new file mode 100644
index 0000000..e53eff7
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Interfaces/IDataService.cs
@@ -0,0 +1,19 @@
+using MyMediaCollection.Enums;
+using MyMediaCollection.Model;
+using System.Collections.Generic;
+
+namespace MyMediaCollection.Interfaces
+{
+ public interface IDataService
+ {
+ IList GetItems();
+ MediaItem GetItem(int id);
+ int AddItem(MediaItem item);
+ void UpdateItem(MediaItem item);
+ IList GetItemTypes();
+ Medium GetMedium(string name);
+ IList GetMediums();
+ IList GetMediums(ItemType itemType);
+ IList GetLocationTypes();
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Interfaces/INavigationService.cs b/Chapter05/Start/MyMediaCollection/Interfaces/INavigationService.cs
new file mode 100644
index 0000000..c8ccee6
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Interfaces/INavigationService.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace MyMediaCollection.Interfaces
+{
+ public interface INavigationService
+ {
+ string CurrentPage { get; }
+ void NavigateTo(string page);
+ void NavigateTo(string page, object parameter);
+ void GoBack();
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/MainWindow.xaml b/Chapter05/Start/MyMediaCollection/MainWindow.xaml
new file mode 100644
index 0000000..7ebe85e
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/MainWindow.xaml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/MainWindow.xaml.cs b/Chapter05/Start/MyMediaCollection/MainWindow.xaml.cs
new file mode 100644
index 0000000..b955d8d
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/MainWindow.xaml.cs
@@ -0,0 +1,15 @@
+using Microsoft.UI.Xaml;
+
+namespace MyMediaCollection
+{
+ ///
+ /// An empty window that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Model/MediaItem.cs b/Chapter05/Start/MyMediaCollection/Model/MediaItem.cs
new file mode 100644
index 0000000..5ab161a
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Model/MediaItem.cs
@@ -0,0 +1,13 @@
+using MyMediaCollection.Enums;
+
+namespace MyMediaCollection.Model
+{
+ public class MediaItem
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public ItemType MediaType { get; set; }
+ public Medium MediumInfo { get; set; }
+ public LocationType Location { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Model/Medium.cs b/Chapter05/Start/MyMediaCollection/Model/Medium.cs
new file mode 100644
index 0000000..f22fac4
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Model/Medium.cs
@@ -0,0 +1,11 @@
+using MyMediaCollection.Enums;
+
+namespace MyMediaCollection.Model
+{
+ public class Medium
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public ItemType MediaType { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/MyMediaCollection.csproj b/Chapter05/Start/MyMediaCollection/MyMediaCollection.csproj
new file mode 100644
index 0000000..2d334d5
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/MyMediaCollection.csproj
@@ -0,0 +1,65 @@
+
+
+ WinExe
+ net6.0-windows10.0.19041.0
+ 10.0.17763.0
+ MyMediaCollection
+ app.manifest
+ x86;x64;ARM64
+ win10-x86;win10-x64;win10-arm64
+ win10-$(Platform).pubxml
+ true
+ true
+ 10.0.18362.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+
+
+ MSBuild:Compile
+
+
+
+
+
+ true
+
+
diff --git a/Chapter05/Start/MyMediaCollection/Package.appxmanifest b/Chapter05/Start/MyMediaCollection/Package.appxmanifest
new file mode 100644
index 0000000..11029a8
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Package.appxmanifest
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ MyMediaCollection
+ alash
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Start/MyMediaCollection/Properties/launchSettings.json b/Chapter05/Start/MyMediaCollection/Properties/launchSettings.json
new file mode 100644
index 0000000..b7ae329
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "MyMediaCollection (Package)": {
+ "commandName": "MsixPackage"
+ },
+ "MyMediaCollection (Unpackaged)": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Services/DataService.cs b/Chapter05/Start/MyMediaCollection/Services/DataService.cs
new file mode 100644
index 0000000..2f57701
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Services/DataService.cs
@@ -0,0 +1,163 @@
+using MyMediaCollection.Enums;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Model;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MyMediaCollection.Services
+{
+ public class DataService : IDataService
+ {
+ private IList _items;
+ private IList _itemTypes;
+ private IList _mediums;
+ private IList _locationTypes;
+
+ public DataService()
+ {
+ PopulateItemTypes();
+ PopulateMediums();
+ PopulateLocationTypes();
+ PopulateItems();
+ }
+
+ private void PopulateItems()
+ {
+ var cd = new MediaItem
+ {
+ Id = 1,
+ Name = "Classical Favorites",
+ MediaType = ItemType.Music,
+ MediumInfo = _mediums.FirstOrDefault(m => m.Name == "CD"),
+ Location = LocationType.InCollection
+ };
+
+ var book = new MediaItem
+ {
+ Id = 2,
+ Name = "Classic Fairy Tales",
+ MediaType = ItemType.Book,
+ MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Hardcover"),
+ Location = LocationType.InCollection
+ };
+
+ var bluRay = new MediaItem
+ {
+ Id = 3,
+ Name = "The Mummy",
+ MediaType = ItemType.Video,
+ MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Blu Ray"),
+ Location = LocationType.InCollection
+ };
+
+ _items = new List
+ {
+ cd,
+ book,
+ bluRay
+ };
+ }
+
+ private void PopulateMediums()
+ {
+ var cd = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" };
+ var vinyl = new Medium { Id = 2, MediaType = ItemType.Music, Name = "Vinyl" };
+ var hardcover = new Medium { Id = 3, MediaType = ItemType.Book, Name = "Hardcover" };
+ var paperback = new Medium { Id = 4, MediaType = ItemType.Book, Name = "Paperback" };
+ var dvd = new Medium { Id = 5, MediaType = ItemType.Video, Name = "DVD" };
+ var bluRay = new Medium { Id = 6, MediaType = ItemType.Video, Name = "Blu Ray" };
+
+ _mediums = new List
+ {
+ cd,
+ vinyl,
+ hardcover,
+ paperback,
+ dvd,
+ bluRay
+ };
+ }
+
+ private void PopulateItemTypes()
+ {
+ _itemTypes = new List
+ {
+ ItemType.Book,
+ ItemType.Music,
+ ItemType.Video
+ };
+ }
+
+ private void PopulateLocationTypes()
+ {
+ _locationTypes = new List
+ {
+ LocationType.InCollection,
+ LocationType.Loaned
+ };
+ }
+
+ public int AddItem(MediaItem item)
+ {
+ item.Id = _items.Max(i => i.Id) + 1;
+ _items.Add(item);
+
+ return item.Id;
+ }
+
+ public MediaItem GetItem(int id)
+ {
+ return _items.FirstOrDefault(i => i.Id == id);
+ }
+
+ public IList GetItems()
+ {
+ return _items;
+ }
+
+ public IList GetItemTypes()
+ {
+ return _itemTypes;
+ }
+
+ public IList GetMediums()
+ {
+ return _mediums;
+ }
+
+ public IList GetMediums(ItemType itemType)
+ {
+ return _mediums
+ .Where(m => m.MediaType == itemType)
+ .ToList();
+ }
+
+ public IList GetLocationTypes()
+ {
+ return _locationTypes;
+ }
+
+ public void UpdateItem(MediaItem item)
+ {
+ var idx = -1;
+ var matchedItem =
+ (from x in _items
+ let ind = idx++
+ where x.Id == item.Id
+ select ind).FirstOrDefault();
+
+ if (idx == -1)
+ {
+ throw new Exception("Unable to update item. Item not found in collection.");
+ }
+
+ _items[idx] = item;
+ }
+
+ public Medium GetMedium(string name)
+ {
+ return _mediums.FirstOrDefault(m => m.Name == name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Services/NavigationService.cs b/Chapter05/Start/MyMediaCollection/Services/NavigationService.cs
new file mode 100644
index 0000000..63f8c95
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Services/NavigationService.cs
@@ -0,0 +1,84 @@
+using Microsoft.UI.Xaml.Controls;
+using MyMediaCollection.Interfaces;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MyMediaCollection.Services
+{
+ public class NavigationService : INavigationService
+ {
+ public NavigationService(Frame rootFrame)
+ {
+ AppFrame = rootFrame;
+ }
+
+ private readonly IDictionary _pages = new ConcurrentDictionary();
+
+ public const string RootPage = "(Root)";
+
+ public const string UnknownPage = "(Unknown)";
+
+ private static Frame AppFrame;
+
+ public void Configure(string page, Type type)
+ {
+ if (_pages.Values.Any(v => v == type))
+ {
+ throw new ArgumentException($"The {type.Name} view has already been registered under another name.");
+ }
+
+ _pages[page] = type;
+ }
+
+ ///
+ /// Gets the name of the currently displayed page.
+ ///
+ public string CurrentPage
+ {
+ get
+ {
+ var frame = AppFrame;
+
+ if (frame.BackStackDepth == 0)
+ return RootPage;
+
+ if (frame.Content == null)
+ return UnknownPage;
+
+ var type = frame.Content.GetType();
+
+ if (_pages.Values.All(v => v != type))
+ return UnknownPage;
+
+ var item = _pages.Single(i => i.Value == type);
+
+ return item.Key;
+ }
+ }
+
+ public void NavigateTo(string page)
+ {
+ NavigateTo(page, null);
+ }
+
+ public void NavigateTo(string page, object parameter)
+ {
+ if (!_pages.ContainsKey(page))
+ {
+ throw new ArgumentException($"Unable to find a page registered with the name {page}.");
+ }
+
+ AppFrame.Navigate(_pages[page], parameter);
+ }
+
+ public void GoBack()
+ {
+ if (AppFrame?.CanGoBack == true)
+ {
+ AppFrame.GoBack();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/ViewModels/ItemDetailsViewModel.cs b/Chapter05/Start/MyMediaCollection/ViewModels/ItemDetailsViewModel.cs
new file mode 100644
index 0000000..fb33773
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/ViewModels/ItemDetailsViewModel.cs
@@ -0,0 +1,154 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MyMediaCollection.Enums;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Model;
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace MyMediaCollection.ViewModels
+{
+ public partial class ItemDetailsViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private ObservableCollection locationTypes = new();
+ [ObservableProperty]
+ private ObservableCollection mediums = new();
+ [ObservableProperty]
+ private ObservableCollection itemTypes = new();
+ private int _itemId;
+ [ObservableProperty]
+ private string itemName;
+ [ObservableProperty]
+ private string selectedMedium;
+ [ObservableProperty]
+ private string selectedItemType;
+ [ObservableProperty]
+ private string selectedLocation;
+
+ [ObservableProperty]
+ [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
+ private bool isDirty;
+ private int _selectedItemId = -1;
+ protected INavigationService _navigationService;
+ protected IDataService _dataService;
+
+ public ItemDetailsViewModel(INavigationService navigationService, IDataService dataService)
+ {
+ _navigationService = navigationService;
+ _dataService = dataService;
+
+ PopulateLists();
+ }
+
+ public void InitializeItemDetailData(int itemId)
+ {
+ _selectedItemId = itemId;
+
+ PopulateExistingItem(_dataService);
+ IsDirty = false;
+ }
+
+ private void PopulateExistingItem(IDataService dataService)
+ {
+ if (_selectedItemId > 0)
+ {
+ var item = _dataService.GetItem(_selectedItemId);
+ Mediums.Clear();
+
+ foreach (string medium in dataService.GetMediums(item.MediaType).Select(m => m.Name))
+ Mediums.Add(medium);
+
+ _itemId = item.Id;
+ ItemName = item.Name;
+ SelectedMedium = item.MediumInfo.Name;
+ SelectedLocation = item.Location.ToString();
+ SelectedItemType = item.MediaType.ToString();
+ }
+ }
+
+ private void PopulateLists()
+ {
+ ItemTypes.Clear();
+ foreach (string iType in Enum.GetNames(typeof(ItemType)))
+ ItemTypes.Add(iType);
+
+ LocationTypes.Clear();
+ foreach (string lType in Enum.GetNames(typeof(LocationType)))
+ LocationTypes.Add(lType);
+
+ Mediums = new ObservableCollection();
+ }
+
+ [RelayCommand(CanExecute = nameof(CanSaveItem))]
+ private void Save()
+ {
+ MediaItem item;
+
+ if (_itemId > 0)
+ {
+ item = _dataService.GetItem(_itemId);
+
+ item.Name = ItemName;
+ item.Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation);
+ item.MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType);
+ item.MediumInfo = _dataService.GetMedium(SelectedMedium);
+
+ _dataService.UpdateItem(item);
+ }
+ else
+ {
+ item = new MediaItem
+ {
+ Name = ItemName,
+ Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation),
+ MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType),
+ MediumInfo = _dataService.GetMedium(SelectedMedium)
+ };
+
+ _dataService.AddItem(item);
+ }
+
+ _navigationService.GoBack();
+ }
+
+ private bool CanSaveItem()
+ {
+ return IsDirty;
+ }
+
+ partial void OnItemNameChanged(string value)
+ {
+ IsDirty = true;
+ }
+
+ partial void OnSelectedMediumChanged(string value)
+ {
+ IsDirty = true;
+ }
+
+ partial void OnSelectedItemTypeChanged(string value)
+ {
+ IsDirty = true;
+ Mediums.Clear();
+
+ if (!string.IsNullOrWhiteSpace(value))
+ {
+ foreach (string med in _dataService.GetMediums((ItemType)Enum.Parse(typeof(ItemType), SelectedItemType)).Select(m => m.Name))
+ Mediums.Add(med);
+ }
+ }
+
+ partial void OnSelectedLocationChanged(string value)
+ {
+ IsDirty = true;
+ }
+
+ [RelayCommand]
+ private void Cancel()
+ {
+ _navigationService.GoBack();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/ViewModels/MainViewModel.cs b/Chapter05/Start/MyMediaCollection/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..c819bb3
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/ViewModels/MainViewModel.cs
@@ -0,0 +1,100 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.UI.Xaml.Input;
+using MyMediaCollection.Interfaces;
+using MyMediaCollection.Model;
+using System.Collections.ObjectModel;
+
+namespace MyMediaCollection.ViewModels
+{
+ public partial class MainViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string selectedMedium;
+ [ObservableProperty]
+ private ObservableCollection items = new ObservableCollection();
+ private ObservableCollection allItems;
+ [ObservableProperty]
+ private ObservableCollection mediums;
+ [ObservableProperty]
+ [NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
+ private MediaItem selectedMediaItem;
+ private INavigationService _navigationService;
+ private IDataService _dataService;
+ private const string AllMediums = "All";
+
+ public MainViewModel(INavigationService navigationService, IDataService dataService)
+ {
+ _navigationService = navigationService;
+ _dataService = dataService;
+
+ PopulateData();
+ }
+
+ public void PopulateData()
+ {
+ Items.Clear();
+
+ foreach (var item in _dataService.GetItems())
+ {
+ Items.Add(item);
+ }
+
+ allItems = new ObservableCollection(Items);
+
+ Mediums = new ObservableCollection
+ {
+ AllMediums
+ };
+
+ foreach (var itemType in _dataService.GetItemTypes())
+ {
+ Mediums.Add(itemType.ToString());
+ }
+
+ SelectedMedium = Mediums[0];
+ }
+
+ partial void OnSelectedMediumChanged(string value)
+ {
+ Items.Clear();
+
+ foreach (var item in allItems)
+ {
+ if (string.IsNullOrWhiteSpace(value)
+ || value == "All"
+ || value == item.MediaType.ToString())
+ {
+ Items.Add(item);
+ }
+ }
+ }
+
+ [RelayCommand]
+ private void AddEdit()
+ {
+ var selectedItemId = -1;
+
+ if (SelectedMediaItem != null)
+ {
+ selectedItemId = SelectedMediaItem.Id;
+ }
+
+ _navigationService.NavigateTo("ItemDetailsPage", selectedItemId);
+ }
+
+ public void ListViewDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
+ {
+ AddEdit();
+ }
+
+ [RelayCommand(CanExecute = nameof(CanDeleteItem))]
+ private void Delete()
+ {
+ allItems.Remove(SelectedMediaItem);
+ Items.Remove(SelectedMediaItem);
+ }
+
+ private bool CanDeleteItem() => SelectedMediaItem != null;
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Views/ItemDetailsPage.xaml b/Chapter05/Start/MyMediaCollection/Views/ItemDetailsPage.xaml
new file mode 100644
index 0000000..3f2db14
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Views/ItemDetailsPage.xaml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Start/MyMediaCollection/Views/ItemDetailsPage.xaml.cs b/Chapter05/Start/MyMediaCollection/Views/ItemDetailsPage.xaml.cs
new file mode 100644
index 0000000..de201f3
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Views/ItemDetailsPage.xaml.cs
@@ -0,0 +1,34 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+using MyMediaCollection.ViewModels;
+
+namespace MyMediaCollection.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class ItemDetailsPage : Page
+ {
+ public ItemDetailsPage()
+ {
+ ViewModel = App.HostContainer.Services.GetService();
+
+ this.InitializeComponent();
+ }
+
+ public ItemDetailsViewModel ViewModel;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ var itemId = (int)e.Parameter;
+
+ if (itemId > 0)
+ {
+ ViewModel.InitializeItemDetailData(itemId);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/Views/MainPage.xaml b/Chapter05/Start/MyMediaCollection/Views/MainPage.xaml
new file mode 100644
index 0000000..9659b71
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Views/MainPage.xaml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter05/Start/MyMediaCollection/Views/MainPage.xaml.cs b/Chapter05/Start/MyMediaCollection/Views/MainPage.xaml.cs
new file mode 100644
index 0000000..5182eb2
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/Views/MainPage.xaml.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.UI.Xaml.Controls;
+using MyMediaCollection.ViewModels;
+
+namespace MyMediaCollection.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainPage : Page
+ {
+ public MainPage()
+ {
+ ViewModel = App.HostContainer.Services.GetService();
+ this.InitializeComponent();
+ }
+
+ public MainViewModel ViewModel;
+ }
+}
\ No newline at end of file
diff --git a/Chapter05/Start/MyMediaCollection/app.manifest b/Chapter05/Start/MyMediaCollection/app.manifest
new file mode 100644
index 0000000..abc9f68
--- /dev/null
+++ b/Chapter05/Start/MyMediaCollection/app.manifest
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
\ No newline at end of file