Add sample code for chapter 5

This commit is contained in:
Alvin Ashcraft
2023-06-10 09:00:29 -04:00
parent edbc96830c
commit 370d8a52b8
60 changed files with 2202 additions and 0 deletions
@@ -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
@@ -0,0 +1,18 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<Application
x:Class="MyMediaCollection.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
@@ -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
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
public static IHost HostContainer { get; private set; }
/// <summary>
/// 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().
/// </summary>
public App()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
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<INavigationService>(navigationService);
services.AddSingleton<IDataService, DataService>();
services.AddTransient<MainViewModel>();
services.AddTransient<ItemDetailsViewModel>();
}).Build();
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,9 @@
namespace MyMediaCollection.Enums
{
public enum ItemType
{
Music,
Video,
Book
}
}
@@ -0,0 +1,8 @@
namespace MyMediaCollection.Enums
{
public enum LocationType
{
InCollection,
Loaned
}
}
@@ -0,0 +1,19 @@
using MyMediaCollection.Enums;
using MyMediaCollection.Model;
using System.Collections.Generic;
namespace MyMediaCollection.Interfaces
{
public interface IDataService
{
IList<MediaItem> GetItems();
MediaItem GetItem(int id);
int AddItem(MediaItem item);
void UpdateItem(MediaItem item);
IList<ItemType> GetItemTypes();
Medium GetMedium(string name);
IList<Medium> GetMediums();
IList<Medium> GetMediums(ItemType itemType);
IList<LocationType> GetLocationTypes();
}
}
@@ -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();
}
}
@@ -0,0 +1,10 @@
<Window
x:Class="MyMediaCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:model="using:MyMediaCollection.Model"
mc:Ignorable="d">
</Window>
@@ -0,0 +1,15 @@
using Microsoft.UI.Xaml;
namespace MyMediaCollection
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
}
}
@@ -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; }
}
}
@@ -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; }
}
}
@@ -0,0 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>MyMediaCollection</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<SupportedOSPlatformVersion>10.0.18362.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Views\ItemDetailsPage.xaml" />
<None Remove="Views\MainPage.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221109.1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Page Update="Views\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\ItemDetailsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
Explorer "Package and Publish" context menu entry to be enabled for this project even if
the Windows App SDK Nuget package has not yet been restored.
-->
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
</Project>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="9457558b-77d0-43f9-b1e4-e6aa9aba05d6"
Publisher="CN=alash"
Version="1.0.0.0" />
<Properties>
<DisplayName>MyMediaCollection</DisplayName>
<PublisherDisplayName>alash</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="MyMediaCollection"
Description="MyMediaCollection"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
@@ -0,0 +1,10 @@
{
"profiles": {
"MyMediaCollection (Package)": {
"commandName": "MsixPackage"
},
"MyMediaCollection (Unpackaged)": {
"commandName": "Project"
}
}
}
@@ -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<MediaItem> _items;
private IList<ItemType> _itemTypes;
private IList<Medium> _mediums;
private IList<LocationType> _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<MediaItem>
{
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<Medium>
{
cd,
vinyl,
hardcover,
paperback,
dvd,
bluRay
};
}
private void PopulateItemTypes()
{
_itemTypes = new List<ItemType>
{
ItemType.Book,
ItemType.Music,
ItemType.Video
};
}
private void PopulateLocationTypes()
{
_locationTypes = new List<LocationType>
{
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<MediaItem> GetItems()
{
return _items;
}
public IList<ItemType> GetItemTypes()
{
return _itemTypes;
}
public IList<Medium> GetMediums()
{
return _mediums;
}
public IList<Medium> GetMediums(ItemType itemType)
{
return _mediums
.Where(m => m.MediaType == itemType)
.ToList();
}
public IList<LocationType> 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);
}
}
}
@@ -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<string, Type> _pages = new ConcurrentDictionary<string, Type>();
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;
}
/// <summary>
/// Gets the name of the currently displayed page.
/// </summary>
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();
}
}
}
}
@@ -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<string> locationTypes = new();
[ObservableProperty]
private ObservableCollection<string> mediums = new();
[ObservableProperty]
private ObservableCollection<string> 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<string>();
}
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();
}
}
}
@@ -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<MediaItem> items = new ObservableCollection<MediaItem>();
private ObservableCollection<MediaItem> allItems;
[ObservableProperty]
private ObservableCollection<string> 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<MediaItem>(Items);
Mediums = new ObservableCollection<string>
{
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;
}
}
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="MyMediaCollection.Views.ItemDetailsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<Style x:Key="AttributeTitleStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="AttributeValueStyle" TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="8"/>
</Style>
<Style x:Key="AttributeComboxValueStyle" TargetType="ComboBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="8"/>
</Style>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Item Details" FontSize="18" Margin="8"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Name:" Style="{StaticResource AttributeTitleStyle}"/>
<TextBox Grid.Column="1" Style="{StaticResource AttributeValueStyle}" Text="{x:Bind ViewModel.ItemName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="Media Type:" Grid.Row="1" Style="{StaticResource AttributeTitleStyle}"/>
<ComboBox Grid.Row="1" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.ItemTypes}" SelectedValue="{x:Bind ViewModel.SelectedItemType, Mode=TwoWay}"/>
<TextBlock Text="Medium:" Grid.Row="2" Style="{StaticResource AttributeTitleStyle}"/>
<ComboBox Grid.Row="2" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.Mediums}" SelectedValue="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}"/>
<TextBlock Text="Location:" Grid.Row="3" Style="{StaticResource AttributeTitleStyle}"/>
<ComboBox Grid.Row="3" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.LocationTypes}" SelectedValue="{x:Bind ViewModel.SelectedLocation, Mode=TwoWay}"/>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
<SplitButton x:Name="SaveButton"
Content="Save and Return"
Margin="8,8,0,8"
Click="{x:Bind ViewModel.SaveItemAndReturn}"
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}">
<SplitButton.Flyout>
<Flyout>
<StackPanel>
<Button Content="Save and Create New"
Click="{x:Bind ViewModel.SaveItemAndContinue}"
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}"
Background="Transparent"/>
<Button Content="Save and Return"
Click="{x:Bind ViewModel.SaveItemAndReturn}"
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}"
Background="Transparent"/>
</StackPanel>
</Flyout>
</SplitButton.Flyout>
<SplitButton.Resources>
<TeachingTip x:Name="SavingTip"
Target="{x:Bind SaveButton}"
Title="Save and create new"
Subtitle="Use the dropdown button option to save your item and create another.">
</TeachingTip>
</SplitButton.Resources>
</SplitButton>
<Button Content="Cancel" Margin="8" Command="{x:Bind ViewModel.CancelCommand}"/>
</StackPanel>
</Grid>
</Page>
@@ -0,0 +1,49 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using MyMediaCollection.ViewModels;
namespace MyMediaCollection.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class ItemDetailsPage : Page
{
public ItemDetailsPage()
{
ViewModel = App.HostContainer.Services.GetService<ItemDetailsViewModel>();
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);
}
}
}
}
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="MyMediaCollection.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:MyMediaCollection.Model"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Media Collection" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="Media Type:" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{x:Bind ViewModel.Mediums}" SelectedItem="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}" MinWidth="120" Margin="0,2,6,4"/>
</StackPanel>
</Grid>
<ListView Grid.Row="1" ItemsSource="{x:Bind ViewModel.Items}"
SelectedItem="{x:Bind ViewModel.SelectedMediaItem, Mode=TwoWay}"
DoubleTapped="{x:Bind ViewModel.ListViewDoubleTapped}">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="Medium" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
<Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="Title" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:MediaItem">
<Grid IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path=MediumInfo.Name}"/>
<TextBlock Grid.Column="1" Text="{x:Bind Path=Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Row="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button Command="{x:Bind ViewModel.AddEditCommand}"
Content="Add/Edit Item"
Margin="8,8,0,8"/>
<Button Command="{x:Bind ViewModel.DeleteCommand}"
Content="Delete Item"
Margin="8"/>
</StackPanel>
</Grid>
</Page>
@@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using MyMediaCollection.ViewModels;
namespace MyMediaCollection.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
ViewModel = App.HostContainer.Services.GetService<MainViewModel>();
this.InitializeComponent();
}
public MainViewModel ViewModel;
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyMediaCollection.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>
+43
View File
@@ -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
@@ -0,0 +1,18 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<Application
x:Class="MyMediaCollection.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
@@ -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
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
public static IHost HostContainer { get; private set; }
/// <summary>
/// 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().
/// </summary>
public App()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
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<INavigationService>(navigationService);
services.AddSingleton<IDataService, DataService>();
services.AddTransient<MainViewModel>();
services.AddTransient<ItemDetailsViewModel>();
}).Build();
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,9 @@
namespace MyMediaCollection.Enums
{
public enum ItemType
{
Music,
Video,
Book
}
}
@@ -0,0 +1,8 @@
namespace MyMediaCollection.Enums
{
public enum LocationType
{
InCollection,
Loaned
}
}
@@ -0,0 +1,19 @@
using MyMediaCollection.Enums;
using MyMediaCollection.Model;
using System.Collections.Generic;
namespace MyMediaCollection.Interfaces
{
public interface IDataService
{
IList<MediaItem> GetItems();
MediaItem GetItem(int id);
int AddItem(MediaItem item);
void UpdateItem(MediaItem item);
IList<ItemType> GetItemTypes();
Medium GetMedium(string name);
IList<Medium> GetMediums();
IList<Medium> GetMediums(ItemType itemType);
IList<LocationType> GetLocationTypes();
}
}
@@ -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();
}
}
@@ -0,0 +1,10 @@
<Window
x:Class="MyMediaCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:model="using:MyMediaCollection.Model"
mc:Ignorable="d">
</Window>
@@ -0,0 +1,15 @@
using Microsoft.UI.Xaml;
namespace MyMediaCollection
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
}
}
@@ -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; }
}
}
@@ -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; }
}
}
@@ -0,0 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>MyMediaCollection</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<SupportedOSPlatformVersion>10.0.18362.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Views\ItemDetailsPage.xaml" />
<None Remove="Views\MainPage.xaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221109.1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Page Update="Views\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Views\ItemDetailsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
Explorer "Package and Publish" context menu entry to be enabled for this project even if
the Windows App SDK Nuget package has not yet been restored.
-->
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
</Project>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="9457558b-77d0-43f9-b1e4-e6aa9aba05d6"
Publisher="CN=alash"
Version="1.0.0.0" />
<Properties>
<DisplayName>MyMediaCollection</DisplayName>
<PublisherDisplayName>alash</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="MyMediaCollection"
Description="MyMediaCollection"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
@@ -0,0 +1,10 @@
{
"profiles": {
"MyMediaCollection (Package)": {
"commandName": "MsixPackage"
},
"MyMediaCollection (Unpackaged)": {
"commandName": "Project"
}
}
}
@@ -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<MediaItem> _items;
private IList<ItemType> _itemTypes;
private IList<Medium> _mediums;
private IList<LocationType> _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<MediaItem>
{
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<Medium>
{
cd,
vinyl,
hardcover,
paperback,
dvd,
bluRay
};
}
private void PopulateItemTypes()
{
_itemTypes = new List<ItemType>
{
ItemType.Book,
ItemType.Music,
ItemType.Video
};
}
private void PopulateLocationTypes()
{
_locationTypes = new List<LocationType>
{
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<MediaItem> GetItems()
{
return _items;
}
public IList<ItemType> GetItemTypes()
{
return _itemTypes;
}
public IList<Medium> GetMediums()
{
return _mediums;
}
public IList<Medium> GetMediums(ItemType itemType)
{
return _mediums
.Where(m => m.MediaType == itemType)
.ToList();
}
public IList<LocationType> 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);
}
}
}
@@ -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<string, Type> _pages = new ConcurrentDictionary<string, Type>();
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;
}
/// <summary>
/// Gets the name of the currently displayed page.
/// </summary>
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();
}
}
}
}
@@ -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<string> locationTypes = new();
[ObservableProperty]
private ObservableCollection<string> mediums = new();
[ObservableProperty]
private ObservableCollection<string> 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<string>();
}
[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();
}
}
}
@@ -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<MediaItem> items = new ObservableCollection<MediaItem>();
private ObservableCollection<MediaItem> allItems;
[ObservableProperty]
private ObservableCollection<string> 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<MediaItem>(Items);
Mediums = new ObservableCollection<string>
{
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;
}
}
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="MyMediaCollection.Views.ItemDetailsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<Style x:Key="AttributeTitleStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="AttributeValueStyle" TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="8"/>
</Style>
<Style x:Key="AttributeComboxValueStyle" TargetType="ComboBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="8"/>
</Style>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Item Details" FontSize="18" Margin="8"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Name:" Style="{StaticResource AttributeTitleStyle}"/>
<TextBox Grid.Column="1" Style="{StaticResource AttributeValueStyle}" Text="{x:Bind ViewModel.ItemName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="Media Type:" Grid.Row="1" Style="{StaticResource AttributeTitleStyle}"/>
<ComboBox Grid.Row="1" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.ItemTypes}" SelectedValue="{x:Bind ViewModel.SelectedItemType, Mode=TwoWay}"/>
<TextBlock Text="Medium:" Grid.Row="2" Style="{StaticResource AttributeTitleStyle}"/>
<ComboBox Grid.Row="2" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.Mediums}" SelectedValue="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}"/>
<TextBlock Text="Location:" Grid.Row="3" Style="{StaticResource AttributeTitleStyle}"/>
<ComboBox Grid.Row="3" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.LocationTypes}" SelectedValue="{x:Bind ViewModel.SelectedLocation, Mode=TwoWay}"/>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
<Button Content="Save" Margin="8,8,0,8" Command="{x:Bind ViewModel.SaveCommand}"/>
<Button Content="Cancel" Margin="8" Command="{x:Bind ViewModel.CancelCommand}"/>
</StackPanel>
</Grid>
</Page>
@@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using MyMediaCollection.ViewModels;
namespace MyMediaCollection.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class ItemDetailsPage : Page
{
public ItemDetailsPage()
{
ViewModel = App.HostContainer.Services.GetService<ItemDetailsViewModel>();
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);
}
}
}
}
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="MyMediaCollection.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyMediaCollection.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:MyMediaCollection.Model"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Media Collection" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="Media Type:" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{x:Bind ViewModel.Mediums}" SelectedItem="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}" MinWidth="120" Margin="0,2,6,4"/>
</StackPanel>
</Grid>
<ListView Grid.Row="1" ItemsSource="{x:Bind ViewModel.Items}"
SelectedItem="{x:Bind ViewModel.SelectedMediaItem, Mode=TwoWay}"
DoubleTapped="{x:Bind ViewModel.ListViewDoubleTapped}">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="Medium" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
<Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="Title" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:MediaItem">
<Grid IsHitTestVisible="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path=MediumInfo.Name}"/>
<TextBlock Grid.Column="1" Text="{x:Bind Path=Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Row="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button Command="{x:Bind ViewModel.AddEditCommand}"
Content="Add/Edit Item"
Margin="8,8,0,8"/>
<Button Command="{x:Bind ViewModel.DeleteCommand}"
Content="Delete Item"
Margin="8"/>
</StackPanel>
</Grid>
</Page>
@@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using MyMediaCollection.ViewModels;
namespace MyMediaCollection.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
ViewModel = App.HostContainer.Services.GetService<MainViewModel>();
this.InitializeComponent();
}
public MainViewModel ViewModel;
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyMediaCollection.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>