Add chapter 10 sample project

This commit is contained in:
Alvin Ashcraft
2023-08-13 13:40:04 -04:00
parent f9e4d407b9
commit 90f935f4b2
91 changed files with 4110 additions and 0 deletions
@@ -0,0 +1,38 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace TemplateStudioSampleApp.Helpers;
public class EnumToBooleanConverter : IValueConverter
{
public EnumToBooleanConverter()
{
}
public object Convert(object value, Type targetType, object parameter, string language)
{
if (parameter is string enumString)
{
if (!Enum.IsDefined(typeof(ElementTheme), value))
{
throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum");
}
var enumValue = Enum.Parse(typeof(ElementTheme), enumString);
return enumValue.Equals(value);
}
throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (parameter is string enumString)
{
return Enum.Parse(typeof(ElementTheme), enumString);
}
throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
}
}
@@ -0,0 +1,8 @@
using Microsoft.UI.Xaml.Controls;
namespace TemplateStudioSampleApp.Helpers;
public static class FrameExtensions
{
public static object? GetPageViewModel(this Frame frame) => frame?.Content?.GetType().GetProperty("ViewModel")?.GetValue(frame.Content, null);
}
@@ -0,0 +1,21 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace TemplateStudioSampleApp.Helpers;
// Helper class to set the navigation target for a NavigationViewItem.
//
// Usage in XAML:
// <NavigationViewItem x:Uid="Shell_Main" Icon="Document" helpers:NavigationHelper.NavigateTo="AppName.ViewModels.MainViewModel" />
//
// Usage in code:
// NavigationHelper.SetNavigateTo(navigationViewItem, typeof(MainViewModel).FullName);
public class NavigationHelper
{
public static string GetNavigateTo(NavigationViewItem item) => (string)item.GetValue(NavigateToProperty);
public static void SetNavigateTo(NavigationViewItem item, string value) => item.SetValue(NavigateToProperty, value);
public static readonly DependencyProperty NavigateToProperty =
DependencyProperty.RegisterAttached("NavigateTo", typeof(string), typeof(NavigationHelper), new PropertyMetadata(null));
}
@@ -0,0 +1,10 @@
using Microsoft.Windows.ApplicationModel.Resources;
namespace TemplateStudioSampleApp.Helpers;
public static class ResourceExtensions
{
private static readonly ResourceLoader _resourceLoader = new();
public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey);
}
@@ -0,0 +1,20 @@
using System.Runtime.InteropServices;
using System.Text;
namespace TemplateStudioSampleApp.Helpers;
public class RuntimeHelper
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder? packageFullName);
public static bool IsMSIX
{
get
{
var length = 0;
return GetCurrentPackageFullName(ref length, null) != 15700L;
}
}
}
@@ -0,0 +1,112 @@
using TemplateStudioSampleApp.Core.Helpers;
using Windows.Storage;
using Windows.Storage.Streams;
namespace TemplateStudioSampleApp.Helpers;
// Use these extension methods to store and retrieve local and roaming app data
// More details regarding storing and retrieving app data at https://docs.microsoft.com/windows/apps/design/app-settings/store-and-retrieve-app-data
public static class SettingsStorageExtensions
{
private const string FileExtension = ".json";
public static bool IsRoamingStorageAvailable(this ApplicationData appData)
{
return appData.RoamingStorageQuota == 0;
}
public static async Task SaveAsync<T>(this StorageFolder folder, string name, T content)
{
var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting);
var fileContent = await Json.StringifyAsync(content);
await FileIO.WriteTextAsync(file, fileContent);
}
public static async Task<T?> ReadAsync<T>(this StorageFolder folder, string name)
{
if (!File.Exists(Path.Combine(folder.Path, GetFileName(name))))
{
return default;
}
var file = await folder.GetFileAsync($"{name}.json");
var fileContent = await FileIO.ReadTextAsync(file);
return await Json.ToObjectAsync<T>(fileContent);
}
public static async Task SaveAsync<T>(this ApplicationDataContainer settings, string key, T value)
{
settings.SaveString(key, await Json.StringifyAsync(value));
}
public static void SaveString(this ApplicationDataContainer settings, string key, string value)
{
settings.Values[key] = value;
}
public static async Task<T?> ReadAsync<T>(this ApplicationDataContainer settings, string key)
{
object? obj;
if (settings.Values.TryGetValue(key, out obj))
{
return await Json.ToObjectAsync<T>((string)obj);
}
return default;
}
public static async Task<StorageFile> SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentException("File name is null or empty. Specify a valid file name", nameof(fileName));
}
var storageFile = await folder.CreateFileAsync(fileName, options);
await FileIO.WriteBytesAsync(storageFile, content);
return storageFile;
}
public static async Task<byte[]?> ReadFileAsync(this StorageFolder folder, string fileName)
{
var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
if ((item != null) && item.IsOfType(StorageItemTypes.File))
{
var storageFile = await folder.GetFileAsync(fileName);
var content = await storageFile.ReadBytesAsync();
return content;
}
return null;
}
public static async Task<byte[]?> ReadBytesAsync(this StorageFile file)
{
if (file != null)
{
using IRandomAccessStream stream = await file.OpenReadAsync();
using var reader = new DataReader(stream.GetInputStreamAt(0));
await reader.LoadAsync((uint)stream.Size);
var bytes = new byte[stream.Size];
reader.ReadBytes(bytes);
return bytes;
}
return null;
}
private static string GetFileName(string name)
{
return string.Concat(name, FileExtension);
}
}
@@ -0,0 +1,121 @@
using System.Runtime.InteropServices;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
using Windows.UI.ViewManagement;
namespace TemplateStudioSampleApp.Helpers;
// Helper class to workaround custom title bar bugs.
// DISCLAIMER: The resource key names and color values used below are subject to change. Do not depend on them.
// https://github.com/microsoft/TemplateStudio/issues/4516
internal class TitleBarHelper
{
private const int WAINACTIVE = 0x00;
private const int WAACTIVE = 0x01;
private const int WMACTIVATE = 0x0006;
[DllImport("user32.dll")]
private static extern IntPtr GetActiveWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
public static void UpdateTitleBar(ElementTheme theme)
{
if (App.MainWindow.ExtendsContentIntoTitleBar)
{
if (theme == ElementTheme.Default)
{
var uiSettings = new UISettings();
var background = uiSettings.GetColorValue(UIColorType.Background);
theme = background == Colors.White ? ElementTheme.Light : ElementTheme.Dark;
}
if (theme == ElementTheme.Default)
{
theme = Application.Current.RequestedTheme == ApplicationTheme.Light ? ElementTheme.Light : ElementTheme.Dark;
}
Application.Current.Resources["WindowCaptionForeground"] = theme switch
{
ElementTheme.Dark => new SolidColorBrush(Colors.White),
ElementTheme.Light => new SolidColorBrush(Colors.Black),
_ => new SolidColorBrush(Colors.Transparent)
};
Application.Current.Resources["WindowCaptionForegroundDisabled"] = theme switch
{
ElementTheme.Dark => new SolidColorBrush(Color.FromArgb(0x66, 0xFF, 0xFF, 0xFF)),
ElementTheme.Light => new SolidColorBrush(Color.FromArgb(0x66, 0x00, 0x00, 0x00)),
_ => new SolidColorBrush(Colors.Transparent)
};
Application.Current.Resources["WindowCaptionButtonBackgroundPointerOver"] = theme switch
{
ElementTheme.Dark => new SolidColorBrush(Color.FromArgb(0x33, 0xFF, 0xFF, 0xFF)),
ElementTheme.Light => new SolidColorBrush(Color.FromArgb(0x33, 0x00, 0x00, 0x00)),
_ => new SolidColorBrush(Colors.Transparent)
};
Application.Current.Resources["WindowCaptionButtonBackgroundPressed"] = theme switch
{
ElementTheme.Dark => new SolidColorBrush(Color.FromArgb(0x66, 0xFF, 0xFF, 0xFF)),
ElementTheme.Light => new SolidColorBrush(Color.FromArgb(0x66, 0x00, 0x00, 0x00)),
_ => new SolidColorBrush(Colors.Transparent)
};
Application.Current.Resources["WindowCaptionButtonStrokePointerOver"] = theme switch
{
ElementTheme.Dark => new SolidColorBrush(Colors.White),
ElementTheme.Light => new SolidColorBrush(Colors.Black),
_ => new SolidColorBrush(Colors.Transparent)
};
Application.Current.Resources["WindowCaptionButtonStrokePressed"] = theme switch
{
ElementTheme.Dark => new SolidColorBrush(Colors.White),
ElementTheme.Light => new SolidColorBrush(Colors.Black),
_ => new SolidColorBrush(Colors.Transparent)
};
Application.Current.Resources["WindowCaptionBackground"] = new SolidColorBrush(Colors.Transparent);
Application.Current.Resources["WindowCaptionBackgroundDisabled"] = new SolidColorBrush(Colors.Transparent);
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
if (hwnd == GetActiveWindow())
{
SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero);
SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero);
}
else
{
SendMessage(hwnd, WMACTIVATE, WAACTIVE, IntPtr.Zero);
SendMessage(hwnd, WMACTIVATE, WAINACTIVE, IntPtr.Zero);
}
}
}
public static void ApplySystemThemeToCaptionButtons()
{
var res = Application.Current.Resources;
var frame = App.AppTitlebar as FrameworkElement;
if (frame != null)
{
if (frame.ActualTheme == ElementTheme.Dark)
{
res["WindowCaptionForeground"] = Colors.White;
}
else
{
res["WindowCaptionForeground"] = Colors.Black;
}
UpdateTitleBar(frame.ActualTheme);
}
}
}