diff --git a/Chapter03/Complete/Ch3-MyMediaCollection.sln b/Chapter03/Complete/Ch3-MyMediaCollection.sln
new file mode 100644
index 0000000..d74113c
--- /dev/null
+++ b/Chapter03/Complete/Ch3-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/Chapter03/Complete/MyMediaCollection/App.xaml b/Chapter03/Complete/MyMediaCollection/App.xaml
new file mode 100644
index 0000000..eeb9e6e
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/App.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter03/Complete/MyMediaCollection/App.xaml.cs b/Chapter03/Complete/MyMediaCollection/App.xaml.cs
new file mode 100644
index 0000000..0f9914f
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/App.xaml.cs
@@ -0,0 +1,34 @@
+using Microsoft.UI.Xaml;
+using MyMediaCollection.ViewModels;
+
+namespace MyMediaCollection
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : Application
+ {
+ ///
+ /// 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();
+ }
+
+ public static MainViewModel ViewModel { get; } = new MainViewModel();
+
+ ///
+ /// 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();
+ m_window.Activate();
+ }
+
+ private Window m_window;
+ }
+}
\ No newline at end of file
diff --git a/Chapter03/Complete/MyMediaCollection/Assets/LockScreenLogo.scale-200.png b/Chapter03/Complete/MyMediaCollection/Assets/LockScreenLogo.scale-200.png
new file mode 100644
index 0000000..7440f0d
Binary files /dev/null and b/Chapter03/Complete/MyMediaCollection/Assets/LockScreenLogo.scale-200.png differ
diff --git a/Chapter03/Complete/MyMediaCollection/Assets/SplashScreen.scale-200.png b/Chapter03/Complete/MyMediaCollection/Assets/SplashScreen.scale-200.png
new file mode 100644
index 0000000..32f486a
Binary files /dev/null and b/Chapter03/Complete/MyMediaCollection/Assets/SplashScreen.scale-200.png differ
diff --git a/Chapter03/Complete/MyMediaCollection/Assets/Square150x150Logo.scale-200.png b/Chapter03/Complete/MyMediaCollection/Assets/Square150x150Logo.scale-200.png
new file mode 100644
index 0000000..53ee377
Binary files /dev/null and b/Chapter03/Complete/MyMediaCollection/Assets/Square150x150Logo.scale-200.png differ
diff --git a/Chapter03/Complete/MyMediaCollection/Assets/Square44x44Logo.scale-200.png b/Chapter03/Complete/MyMediaCollection/Assets/Square44x44Logo.scale-200.png
new file mode 100644
index 0000000..f713bba
Binary files /dev/null and b/Chapter03/Complete/MyMediaCollection/Assets/Square44x44Logo.scale-200.png differ
diff --git a/Chapter03/Complete/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Chapter03/Complete/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
new file mode 100644
index 0000000..dc9f5be
Binary files /dev/null and b/Chapter03/Complete/MyMediaCollection/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ
diff --git a/Chapter03/Complete/MyMediaCollection/Assets/StoreLogo.png b/Chapter03/Complete/MyMediaCollection/Assets/StoreLogo.png
new file mode 100644
index 0000000..a4586f2
Binary files /dev/null and b/Chapter03/Complete/MyMediaCollection/Assets/StoreLogo.png differ
diff --git a/Chapter03/Complete/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png b/Chapter03/Complete/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png
new file mode 100644
index 0000000..8b4a5d0
Binary files /dev/null and b/Chapter03/Complete/MyMediaCollection/Assets/Wide310x150Logo.scale-200.png differ
diff --git a/Chapter03/Complete/MyMediaCollection/Enums/ItemType.cs b/Chapter03/Complete/MyMediaCollection/Enums/ItemType.cs
new file mode 100644
index 0000000..2e50873
--- /dev/null
+++ b/Chapter03/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/Chapter03/Complete/MyMediaCollection/Enums/LocationType.cs b/Chapter03/Complete/MyMediaCollection/Enums/LocationType.cs
new file mode 100644
index 0000000..a8d19aa
--- /dev/null
+++ b/Chapter03/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/Chapter03/Complete/MyMediaCollection/MainWindow.xaml b/Chapter03/Complete/MyMediaCollection/MainWindow.xaml
new file mode 100644
index 0000000..6373b23
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/MainWindow.xaml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter03/Complete/MyMediaCollection/MainWindow.xaml.cs b/Chapter03/Complete/MyMediaCollection/MainWindow.xaml.cs
new file mode 100644
index 0000000..002b7e8
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/MainWindow.xaml.cs
@@ -0,0 +1,18 @@
+using Microsoft.UI.Xaml;
+using MyMediaCollection.ViewModels;
+
+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();
+ }
+
+ public MainViewModel ViewModel => App.ViewModel;
+ }
+}
\ No newline at end of file
diff --git a/Chapter03/Complete/MyMediaCollection/Model/MediaItem.cs b/Chapter03/Complete/MyMediaCollection/Model/MediaItem.cs
new file mode 100644
index 0000000..5ab161a
--- /dev/null
+++ b/Chapter03/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/Chapter03/Complete/MyMediaCollection/Model/Medium.cs b/Chapter03/Complete/MyMediaCollection/Model/Medium.cs
new file mode 100644
index 0000000..f22fac4
--- /dev/null
+++ b/Chapter03/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/Chapter03/Complete/MyMediaCollection/MyMediaCollection.csproj b/Chapter03/Complete/MyMediaCollection/MyMediaCollection.csproj
new file mode 100644
index 0000000..cd7767a
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/MyMediaCollection.csproj
@@ -0,0 +1,49 @@
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
diff --git a/Chapter03/Complete/MyMediaCollection/Package.appxmanifest b/Chapter03/Complete/MyMediaCollection/Package.appxmanifest
new file mode 100644
index 0000000..11029a8
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/Package.appxmanifest
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ MyMediaCollection
+ alash
+ Assets\StoreLogo.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Chapter03/Complete/MyMediaCollection/Properties/launchSettings.json b/Chapter03/Complete/MyMediaCollection/Properties/launchSettings.json
new file mode 100644
index 0000000..b7ae329
--- /dev/null
+++ b/Chapter03/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/Chapter03/Complete/MyMediaCollection/ViewModels/BindableBase.cs b/Chapter03/Complete/MyMediaCollection/ViewModels/BindableBase.cs
new file mode 100644
index 0000000..1bc9889
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/ViewModels/BindableBase.cs
@@ -0,0 +1,28 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace MyMediaCollection.ViewModels
+{
+ public class BindableBase : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ protected bool SetProperty(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null)
+ {
+ if (Equals(originalValue, newValue))
+ {
+ return false;
+ }
+
+ originalValue = newValue;
+ OnPropertyChanged(propertyName);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel.cs b/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..72d6842
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel.cs
@@ -0,0 +1,123 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MyMediaCollection.Enums;
+using MyMediaCollection.Model;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+
+namespace MyMediaCollection.ViewModels
+{
+ public partial class MainViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ private string selectedMedium;
+ [ObservableProperty]
+ private ObservableCollection items;
+ [ObservableProperty]
+ private IList mediums;
+
+ [ObservableProperty]
+ [NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
+ private MediaItem selectedMediaItem;
+
+ private ObservableCollection allItems;
+ private int additionalItemCount = 1;
+
+ public MainViewModel()
+ {
+ PopulateData();
+ }
+
+ public void PopulateData()
+ {
+ var cd = new MediaItem
+ {
+ Id = 1,
+ Name = "Classical Favorites",
+ MediaType = ItemType.Music,
+ MediumInfo = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" }
+ };
+
+ var book = new MediaItem
+ {
+ Id = 2,
+ Name = "Classic Fairy Tales",
+ MediaType = ItemType.Book,
+ MediumInfo = new Medium { Id = 2, MediaType = ItemType.Book, Name = "Book" }
+ };
+
+ var bluRay = new MediaItem
+ {
+ Id = 3,
+ Name = "The Mummy",
+ MediaType = ItemType.Video,
+ MediumInfo = new Medium { Id = 3, MediaType = ItemType.Video, Name = "Blu Ray" }
+ };
+
+ Items = new ObservableCollection
+ {
+ cd,
+ book,
+ bluRay
+ };
+
+ allItems = new ObservableCollection(Items);
+
+ Mediums = new List
+ {
+ "All",
+ nameof(ItemType.Book),
+ nameof(ItemType.Music),
+ nameof(ItemType.Video)
+ };
+
+ 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]
+ public void AddEdit()
+ {
+ // Note this is temporary until
+ // we use a real data source for items.
+ const int startingItemCount = 3;
+
+ var newItem = new MediaItem
+ {
+ Id = startingItemCount + additionalItemCount,
+ Location = LocationType.InCollection,
+ MediaType = ItemType.Music,
+ MediumInfo = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" },
+ Name = $"CD {additionalItemCount}"
+ };
+
+ allItems.Add(newItem);
+ Items.Add(newItem);
+ additionalItemCount++;
+ }
+
+ [RelayCommand(CanExecute = nameof(CanDeleteItem))]
+ public void Delete()
+ {
+ allItems.Remove(SelectedMediaItem);
+ Items.Remove(SelectedMediaItem);
+ }
+
+ private bool CanDeleteItem() => SelectedMediaItem != null;
+ }
+}
\ No newline at end of file
diff --git a/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel.txt b/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel.txt
new file mode 100644
index 0000000..e65c035
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel.txt
@@ -0,0 +1,148 @@
+using MyMediaCollection.Enums;
+using MyMediaCollection.Model;
+using System.Collections.Generic;
+
+namespace MyMediaCollection.ViewModels
+{
+ public class MainViewModel : BindableBase
+ {
+ private string selectedMedium;
+ private ObservableCollection items;
+ private ObservableCollection allItems;
+ private IList mediums;
+ private MediaItem selectedMediaItem;
+ private int additionalItemCount = 1;
+
+ public MainViewModel()
+ {
+ PopulateData();
+ }
+
+ public void PopulateData()
+ {
+ var cd = new MediaItem
+ {
+ Id = 1,
+ Name = "Classical Favorites",
+ MediaType = ItemType.Music,
+ MediumInfo = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" }
+ };
+
+ var book = new MediaItem
+ {
+ Id = 2,
+ Name = "Classic Fairy Tales",
+ MediaType = ItemType.Book,
+ MediumInfo = new Medium { Id = 2, MediaType = ItemType.Book, Name = "Book" }
+ };
+
+ var bluRay = new MediaItem
+ {
+ Id = 3,
+ Name = "The Mummy",
+ MediaType = ItemType.Video,
+ MediumInfo = new Medium { Id = 3, MediaType = ItemType.Video, Name = "Blu Ray" }
+ };
+
+ items = new ObservableCollection
+ {
+ cd,
+ book,
+ bluRay
+ };
+
+ allItems = new ObservableCollection(Items);
+
+ mediums = new List
+ {
+ "All",
+ nameof(ItemType.Book),
+ nameof(ItemType.Music),
+ nameof(ItemType.Video)
+ };
+
+ selectedMedium = Mediums[0];
+ }
+
+ public ObservableCollection Items
+ {
+ get
+ {
+ return items;
+ }
+ set
+ {
+ SetProperty(ref items, value);
+ }
+ }
+
+ public IList Mediums
+ {
+ get
+ {
+ return mediums;
+ }
+ set
+ {
+ SetProperty(ref mediums, value);
+ }
+ }
+
+ public string SelectedMedium
+ {
+ get
+ {
+ return selectedMedium;
+ }
+ set
+ {
+ SetProperty(ref selectedMedium, value);
+
+ Items.Clear();
+
+ foreach (var item in allItems)
+ {
+ if (string.IsNullOrWhiteSpace(selectedMedium) ||
+ selectedMedium == "All" ||
+ selectedMedium == item.MediaType.ToString())
+ {
+ Items.Add(item);
+ }
+ }
+ }
+ }
+
+ public MediaItem SelectedMediaItem
+ {
+ get => selectedMediaItem;
+ set
+ {
+ SetProperty(ref selectedMediaItem, value);
+ }
+ }
+
+ public void AddOrEditItem()
+ {
+ // Note this is temporary until
+ // we use a real data source for items.
+ const int startingItemCount = 3;
+
+ var newItem = new MediaItem
+ {
+ Id = startingItemCount + additionalItemCount,
+ Location = LocationType.InCollection,
+ MediaType = ItemType.Music,
+ MediumInfo = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" },
+ Name = $"CD {additionalItemCount}"
+ };
+
+ Items.Add(newItem);
+ additionalItemCount++;
+ }
+
+ public void DeleteItem()
+ {
+ Items.Remove(SelectedMediaItem);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel2.txt b/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel2.txt
new file mode 100644
index 0000000..2cd8c38
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/ViewModels/MainViewModel2.txt
@@ -0,0 +1,165 @@
+using MyMediaCollection.Enums;
+using MyMediaCollection.Model;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+
+namespace MyMediaCollection.ViewModels
+{
+ public class MainViewModel : BindableBase
+ {
+ private string selectedMedium;
+ private ObservableCollection items;
+ private ObservableCollection allItems;
+ private IList mediums;
+ private MediaItem selectedMediaItem;
+ private int additionalItemCount = 1;
+
+ public MainViewModel()
+ {
+ PopulateData();
+
+ DeleteCommand = new RelayCommand(DeleteItem, CanDeleteItem);
+
+ // No CanExecute param is needed for this command
+ // because you can always add or edit items.
+ AddEditCommand = new RelayCommand(AddOrEditItem);
+ }
+
+ public void PopulateData()
+ {
+ var cd = new MediaItem
+ {
+ Id = 1,
+ Name = "Classical Favorites",
+ MediaType = ItemType.Music,
+ MediumInfo = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" }
+ };
+
+ var book = new MediaItem
+ {
+ Id = 2,
+ Name = "Classic Fairy Tales",
+ MediaType = ItemType.Book,
+ MediumInfo = new Medium { Id = 2, MediaType = ItemType.Book, Name = "Book" }
+ };
+
+ var bluRay = new MediaItem
+ {
+ Id = 3,
+ Name = "The Mummy",
+ MediaType = ItemType.Video,
+ MediumInfo = new Medium { Id = 3, MediaType = ItemType.Video, Name = "Blu Ray" }
+ };
+
+ items = new ObservableCollection
+ {
+ cd,
+ book,
+ bluRay
+ };
+
+ allItems = new ObservableCollection(Items);
+
+ mediums = new List
+ {
+ "All",
+ nameof(ItemType.Book),
+ nameof(ItemType.Music),
+ nameof(ItemType.Video)
+ };
+
+ selectedMedium = Mediums[0];
+ }
+
+ public ObservableCollection Items
+ {
+ get
+ {
+ return items;
+ }
+ set
+ {
+ SetProperty(ref items, value);
+ }
+ }
+
+ public IList Mediums
+ {
+ get
+ {
+ return mediums;
+ }
+ set
+ {
+ SetProperty(ref mediums, value);
+ }
+ }
+
+ public string SelectedMedium
+ {
+ get
+ {
+ return selectedMedium;
+ }
+ set
+ {
+ SetProperty(ref selectedMedium, value);
+
+ Items.Clear();
+
+ foreach (var item in allItems)
+ {
+ if (string.IsNullOrWhiteSpace(selectedMedium) ||
+ selectedMedium == "All" ||
+ selectedMedium == item.MediaType.ToString())
+ {
+ Items.Add(item);
+ }
+ }
+ }
+ }
+
+ public MediaItem SelectedMediaItem
+ {
+ get => selectedMediaItem;
+ set
+ {
+ SetProperty(ref selectedMediaItem, value);
+ ((RelayCommand)DeleteCommand).RaiseCanExecuteChanged();
+ }
+ }
+
+ public ICommand AddEditCommand { get; set; }
+
+ public void AddOrEditItem()
+ {
+ // Note this is temporary until
+ // we use a real data source for items.
+ const int startingItemCount = 3;
+
+ var newItem = new MediaItem
+ {
+ Id = startingItemCount + additionalItemCount,
+ Location = LocationType.InCollection,
+ MediaType = ItemType.Music,
+ MediumInfo = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" },
+ Name = $"CD {additionalItemCount}"
+ };
+
+ allItems.Add(newItem);
+ Items.Add(newItem);
+ additionalItemCount++;
+ }
+
+ public ICommand DeleteCommand { get; set; }
+
+ public void DeleteItem()
+ {
+ allItems.Remove(SelectedMediaItem);
+ Items.Remove(SelectedMediaItem);
+ }
+
+ private bool CanDeleteItem() => selectedMediaItem != null;
+ }
+}
\ No newline at end of file
diff --git a/Chapter03/Complete/MyMediaCollection/ViewModels/RelayCommand.cs b/Chapter03/Complete/MyMediaCollection/ViewModels/RelayCommand.cs
new file mode 100644
index 0000000..0f6278b
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/ViewModels/RelayCommand.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Windows.Input;
+
+namespace MyMediaCollection.ViewModels
+{
+ public class RelayCommand : ICommand
+ {
+ private readonly Action action;
+ private readonly Func canExecute;
+
+ public RelayCommand(Action action)
+ : this(action, null)
+ {
+ }
+
+ public RelayCommand(Action action, Func canExecute)
+ {
+ if (action == null)
+ throw new ArgumentNullException(nameof(action));
+
+ this.action = action;
+ this.canExecute = canExecute;
+ }
+
+ public bool CanExecute(object parameter) => canExecute == null || canExecute();
+
+ public void Execute(object parameter) => action();
+
+ public event EventHandler CanExecuteChanged;
+
+ public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+ }
+}
\ No newline at end of file
diff --git a/Chapter03/Complete/MyMediaCollection/app.manifest b/Chapter03/Complete/MyMediaCollection/app.manifest
new file mode 100644
index 0000000..abc9f68
--- /dev/null
+++ b/Chapter03/Complete/MyMediaCollection/app.manifest
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
\ No newline at end of file