■ WinUI (C#) 템플리트 스튜디오를 사용해 앱을 만드는 방법을 보여준다.
※ 비주얼 스튜디오에서 TestProject(Unpackaged) 모드로 빌드한다.
※ TestProject.csproj 프로젝트 파일에서 WindowsPackageType 태그를 None으로 추가했다.
▶ NavigationViewHeaderBehavior.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; using Microsoft.Xaml.Interactivity; namespace TestProject; /// <summary> /// 네비에기션 뷰 헤더 동작 /// </summary> public class NavigationViewHeaderBehavior : Behavior<NavigationView> { //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 디폴트 헤더 속성 - DefaultHeaderProperty /// <summary> /// 디폴트 헤더 속성 /// </summary> public static readonly DependencyProperty DefaultHeaderProperty = DependencyProperty.Register ( "DefaultHeader", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _navigationViewHeaderBehavior.UpdateHeader()) ); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Attached Property ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 헤더 모드 속성 - HeaderModeProperty /// <summary> /// 헤더 모드 속성 /// </summary> public static readonly DependencyProperty HeaderModeProperty = DependencyProperty.RegisterAttached ( "HeaderMode", typeof(bool), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(NavigationViewHeaderMode.Always, (d, e) => _navigationViewHeaderBehavior.UpdateHeader()) ); #endregion #region 헤더 컨텍스트 속성 - HeaderContextProperty /// <summary> /// 헤더 컨텍스트 속성 /// </summary> public static readonly DependencyProperty HeaderContextProperty = DependencyProperty.RegisterAttached ( "HeaderContext", typeof(object), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _navigationViewHeaderBehavior.UpdateHeader()) ); #endregion #region 헤더 템플리트 속성 - HeaderTemplateProperty /// <summary> /// 헤더 템플리트 속성 /// </summary> public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.RegisterAttached ( "HeaderTemplate", typeof(DataTemplate), typeof(NavigationViewHeaderBehavior), new PropertyMetadata(null, (d, e) => _navigationViewHeaderBehavior!.UpdateHeaderTemplate()) ); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 네비게이션 뷰 헤더 동작 /// </summary> private static NavigationViewHeaderBehavior _navigationViewHeaderBehavior; #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 현재 페이지 /// </summary> private Page currentPage; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 디폴트 헤더 템플리트 - DefaultHeaderTemplate /// <summary> /// 디폴트 헤더 템플리트 /// </summary> public DataTemplate DefaultHeaderTemplate { get; set; } #endregion #region 디폴트 헤더 - 디폴트 헤더 /// <summary> /// 디폴트 헤더 /// </summary> public object DefaultHeader { get => GetValue(DefaultHeaderProperty); set => SetValue(DefaultHeaderProperty, value); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 헤더 모드 설정하기 - SetHeaderMode(page, navigationViewHeaderMode) /// <summary> /// 헤더 모드 설정하기 /// </summary> /// <param name="page"></param> /// <param name="navigationViewHeaderMode">네비게이션 뷰 헤더 모드</param> public static void SetHeaderMode(Page page, NavigationViewHeaderMode navigationViewHeaderMode) => page.SetValue(HeaderModeProperty, navigationViewHeaderMode); #endregion #region 헤더 모드 구하기 - GetHeaderMode(page) /// <summary> /// 헤더 모드 구하기 /// </summary> /// <param name="page">페이지</param> /// <returns>헤더 모드</returns> public static NavigationViewHeaderMode GetHeaderMode(Page page) => (NavigationViewHeaderMode)page.GetValue(HeaderModeProperty); #endregion #region 헤더 컨텍스트 설정하기 - SetHeaderContext(page, value) /// <summary> /// 헤더 컨텍스트 설정하기 /// </summary> /// <param name="page">페이지</param> /// <param name="value">값</param> public static void SetHeaderContext(Page page, object value) => page.SetValue(HeaderContextProperty, value); #endregion #region 헤더 컨텍스트 구하기 - GetHeaderContext(page) /// <summary> /// 헤더 컨텍스트 구하기 /// </summary> /// <param name="page">페이지</param> /// <returns>헤더 컨텍스트</returns> public static object GetHeaderContext(Page page) => page.GetValue(HeaderContextProperty); #endregion #region 헤더 템플리트 설정하기 - SetHeaderTemplate(page, dataTemplate) /// <summary> /// 헤더 템플리트 설정하기 /// </summary> /// <param name="page">페이지</param> /// <param name="dataTemplate">데이터 템플리트</param> public static void SetHeaderTemplate(Page page, DataTemplate dataTemplate) => page.SetValue(HeaderTemplateProperty, dataTemplate); #endregion #region 헤더 템플리트 구하기 - GetHeaderTemplate(page) /// <summary> /// 헤더 템플리트 구하기 /// </summary> /// <param name="page">페이지</param> /// <returns>헤더 템플리트</returns> public static DataTemplate GetHeaderTemplate(Page page) => (DataTemplate)page.GetValue(HeaderTemplateProperty); #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Protected #region 부착시 처리하기 - OnAttached() /// <summary> /// 부착시 처리하기 /// </summary> protected override void OnAttached() { base.OnAttached(); INavigationService navigationService = App.GetService<INavigationService>(); navigationService.Navigated += navigationService_Navigated; _navigationViewHeaderBehavior = this; } #endregion #region 탈착중 처리하기 - OnDetaching() /// <summary> /// 탈착중 처리하기 /// </summary> protected override void OnDetaching() { base.OnDetaching(); INavigationService navigationService = App.GetService<INavigationService>(); navigationService.Navigated -= navigationService_Navigated; } #endregion //////////////////////////////////////////////////////////////////////////////// Private ////////////////////////////////////////////////////////////////////// Event #region 네비게이션 서비스 탐색시 처리하기 - navigationService_Navigated(sender, e) /// <summary> /// 네비게이션 서비스 탐색시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void navigationService_Navigated(object sender, NavigationEventArgs e) { if(sender is Frame frame && frame.Content is Page page) { this.currentPage = page; UpdateHeader(); UpdateHeaderTemplate(); } } #endregion ////////////////////////////////////////////////////////////////////// Function #region 헤더 업데이트하기 - UpdateHeader() /// <summary> /// 헤더 업데이트하기 /// </summary> private void UpdateHeader() { if(this.currentPage != null) { NavigationViewHeaderMode navigationViewHeaderMode = GetHeaderMode(this.currentPage); if(navigationViewHeaderMode == NavigationViewHeaderMode.Never) { AssociatedObject.Header = null; AssociatedObject.AlwaysShowHeader = false; } else { object headerFromPage = GetHeaderContext(this.currentPage); if(headerFromPage != null) { AssociatedObject.Header = headerFromPage; } else { AssociatedObject.Header = DefaultHeader; } if(navigationViewHeaderMode == NavigationViewHeaderMode.Always) { AssociatedObject.AlwaysShowHeader = true; } else { AssociatedObject.AlwaysShowHeader = false; } } } } #endregion #region 헤더 템플리트 업데이트하기 - UpdateHeaderTemplate() /// <summary> /// 헤더 템플리트 업데이트하기 /// </summary> private void UpdateHeaderTemplate() { if(this.currentPage != null) { DataTemplate headerDataTemplate = GetHeaderTemplate(this.currentPage); AssociatedObject.HeaderTemplate = headerDataTemplate ?? DefaultHeaderTemplate; } } #endregion } |
▶ EnumerationToBooleanConverter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Data; namespace TestProject; /// <summary> /// 열거형↔진리 값 변환자 /// </summary> public class EnumerationToBooleanConverter : IValueConverter { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - EnumerationToBooleanConverter() /// <summary> /// 생성자 /// </summary> public EnumerationToBooleanConverter() { } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 변환하기 - Convert(sourceValue, targetType, parameter, language) /// <summary> /// 변환하기 /// </summary> /// <param name="sourceValue">소스 값</param> /// <param name="targetType">타겟 타입</param> /// <param name="parameter">매개 변수</param> /// <param name="language">언어</param> /// <returns>변환 값</returns> public object Convert(object sourceValue, Type targetType, object parameter, string language) { if(parameter is string enumerationString) { if(!Enum.IsDefined(typeof(ElementTheme), sourceValue)) { throw new ArgumentException("Exception : EnumerationToBooleanConverter value must be enumeration."); } object enumerationValue = Enum.Parse(typeof(ElementTheme), enumerationString); return enumerationValue.Equals(sourceValue); } throw new ArgumentException("Exception : EnumerationToBooleanConverter parameter must be as enumeration name."); } #endregion #region 역변환하기 - ConvertBack(sourceValue, targetType, parameter, language) /// <summary> /// 역변환하기 /// </summary> /// <param name="sourceValue">소스 값</param> /// <param name="targetType">타겟 타입</param> /// <param name="parameter">매개 변수</param> /// <param name="language">언어</param> /// <returns>역변환 값</returns> public object ConvertBack(object sourceValue, Type targetType, object parameter, string language) { if(parameter is string enumerationString) { return Enum.Parse(typeof(ElementTheme), enumerationString); } throw new ArgumentException("Exception : EnumerationToBooleanConverter parameter must be as enumeration name."); } #endregion } |
▶ NavigationViewHeaderMode.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
namespace TestProject; /// <summary> /// 네비게이션 뷰 헤더 모드 /// </summary> public enum NavigationViewHeaderMode { /// <summary> /// 항상 /// </summary> Always, /// <summary> /// 표시 안함 /// </summary> Never, /// <summary> /// 최소화 /// </summary> Minimal } |
▶ FrameExtension.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 프레임 확장 /// </summary> public static class FrameExtension { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 페이지 뷰 모델 구하기 - GetPageViewModel(frame) /// <summary> /// 페이지 뷰 모델 구하기 /// </summary> /// <param name="frame">프레임</param> /// <returns>페이지 뷰 모델</returns> public static object GetPageViewModel(this Frame frame) => frame?.Content?.GetType().GetProperty("ViewModel")?.GetValue(frame.Content, null); #endregion } |
▶ ResourceExtension.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using Microsoft.Windows.ApplicationModel.Resources; namespace TestProject; /// <summary> /// 리소스 확장 /// </summary> public static class ResourceExtension { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 리소스 로더 /// </summary> private static readonly ResourceLoader _resourceLoader = new(); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 지역 문자열 구하기 - GetLocalString(resourceKey) /// <summary> /// 지역 문자열 구하기 /// </summary> /// <param name="resourceKey">리소스 키</param> /// <returns>지역 문자열</returns> public static string GetLocalString(this string resourceKey) => _resourceLoader.GetString(resourceKey); #endregion } |
▶ SettingStorageExtension.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
using Windows.Storage; using Windows.Storage.Streams; namespace TestProject; /// <summary> /// 설정 저장소 확장 /// </summary> public static class SettingStorageExtension { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 파일 확장 /// </summary> private const string FileExtension = ".json"; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 로밍 저장소 이용 가능 여부 구하기 - IsRoamingStorageAvailable(applicationData) /// <summary> /// 로밍 저장소 이용 가능 여부 구하기 /// </summary> /// <param name="applicationData">애플리케이션 데이터</param> /// <returns>로밍 저장소 이용 가능 여부</returns> public static bool IsRoamingStorageAvailable(this ApplicationData applicationData) { return applicationData.RoamingStorageQuota == 0; } #endregion #region 저장하기 (비동기) - SaveAsync<TContent>(storageFolder, fileNameWithoutExtension, content) /// <summary> /// 저장하기 (비동기) /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="storageFolder">저장소 폴더</param> /// <param name="fileNameWithoutExtension">확장자 없는 파일명</param> /// <param name="item">컨텐트</param> /// <returns>태스크</returns> public static async Task SaveAsync<TItem>(this StorageFolder storageFolder, string fileNameWithoutExtension, TItem item) { StorageFile storageFile = await storageFolder.CreateFileAsync(GetFileName(fileNameWithoutExtension), CreationCollisionOption.ReplaceExisting); string json = await JSONHelper.SerializeAsync(item); await FileIO.WriteTextAsync(storageFile, json); } #endregion #region 읽기 (비동기) - ReadAsync<TItem>(storageFolder, fileNameWithoutExtension) /// <summary> /// 읽기 (비동기) /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="storageFolder">저장소 폴더</param> /// <param name="fileNameWithoutExtension">확장자 없는 파일명</param> /// <returns>항목 태스크</returns> public static async Task<TItem> ReadAsync<TItem>(this StorageFolder storageFolder, string fileNameWithoutExtension) { if(!File.Exists(Path.Combine(storageFolder.Path, GetFileName(fileNameWithoutExtension)))) { return default; } StorageFile storageFile = await storageFolder.GetFileAsync($"{fileNameWithoutExtension}.json"); string json = await FileIO.ReadTextAsync(storageFile); return await JSONHelper.DeserializeAsync<TItem>(json); } #endregion #region 저장하기 (비동기) - SaveAsync<TValue>(applicationDataContainer, key, value) /// <summary> /// 저장하기 (비동기) /// </summary> /// <typeparam name="TValue">값 타입</typeparam> /// <param name="applicationDataContainer">애플리케이션 데이터 컨테이너</param> /// <param name="key">키</param> /// <param name="value">값</param> /// <returns>태스크</returns> public static async Task SaveAsync<TValue>(this ApplicationDataContainer applicationDataContainer, string key, TValue value) { applicationDataContainer.SaveString(key, await JSONHelper.SerializeAsync(value)); } #endregion #region 문자열 저장하기 - SaveString(applicationDataContainer, key, value) /// <summary> /// 문자열 저장하기 /// </summary> /// <param name="applicationDataContainer">애플리케이션 데이터 컨테이너</param> /// <param name="key">키</param> /// <param name="value">값</param> public static void SaveString(this ApplicationDataContainer applicationDataContainer, string key, string value) { applicationDataContainer.Values[key] = value; } #endregion #region 읽기 (비동기) - ReadAsync<TValue>(applicationDataContainer, key) /// <summary> /// 읽기 (비동기 /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="applicationDataContainer">애플리케이션 데이터 컨테이너</param> /// <param name="key">키</param> /// <returns>항목 태스크</returns> public static async Task<TItem> ReadAsync<TItem>(this ApplicationDataContainer applicationDataContainer, string key) { object instance; if(applicationDataContainer.Values.TryGetValue(key, out instance)) { return await JSONHelper.DeserializeAsync<TItem>((string)instance); } return default; } #endregion #region 파일 저장하기 (비동기) - SaveFileAsync(storageFolder, contentByteArray, fileName, creationCollisionOption) /// <summary> /// 파일 저장하기 (비동기) /// </summary> /// <param name="storageFolder">저장소 폴더</param> /// <param name="contentByteArray">컨텐트 바이트 배열</param> /// <param name="fileName">파일명</param> /// <param name="creationCollisionOption">생성 충돌 옵션</param> /// <returns>저장소 파일 태스크</returns> public static async Task<StorageFile> SaveFileAsync(this StorageFolder storageFolder, byte[] contentByteArray, string fileName, CreationCollisionOption creationCollisionOption = CreationCollisionOption.ReplaceExisting) { if(contentByteArray == null) { throw new ArgumentNullException(nameof(contentByteArray)); } if(string.IsNullOrEmpty(fileName)) { throw new ArgumentException("File name is null or empty. Specify a valid file name", nameof(fileName)); } StorageFile storageFile = await storageFolder.CreateFileAsync(fileName, creationCollisionOption); await FileIO.WriteBytesAsync(storageFile, contentByteArray); return storageFile; } #endregion #region 파일 읽기 (비동기) - ReadFileAsync(storageFolder, filePath) /// <summary> /// 파일 읽기 (비동기) /// </summary> /// <param name="storageFolder">저장소 폴더</param> /// <param name="filePath">파일 경로</param> /// <returns>컨텐트 바이트 배열 태스크</returns> public static async Task<byte[]> ReadFileAsync(this StorageFolder storageFolder, string filePath) { IStorageItem storageItem = await storageFolder.TryGetItemAsync(filePath).AsTask().ConfigureAwait(false); if((storageItem != null) && storageItem.IsOfType(StorageItemTypes.File)) { StorageFile storageFile = await storageFolder.GetFileAsync(filePath); byte[] contentByteArray = await storageFile.ReadByteArrayAsync(); return contentByteArray; } return null; } #endregion #region 바이트 배열 읽기 - ReadByteArrayAsync(storageFile) /// <summary> /// 바이트 배열 읽기 /// </summary> /// <param name="storageFile">저장소 파일</param> /// <returns>바이트 배열 태스크</returns> public static async Task<byte[]> ReadByteArrayAsync(this StorageFile storageFile) { if(storageFile != null) { using IRandomAccessStream randomAccessStream = await storageFile.OpenReadAsync(); using DataReader dataReader = new DataReader(randomAccessStream.GetInputStreamAt(0)); await dataReader.LoadAsync((uint)randomAccessStream.Size); byte[] byteArray = new byte[randomAccessStream.Size]; dataReader.ReadBytes(byteArray); return byteArray; } return null; } #endregion //////////////////////////////////////////////////////////////////////////////// Private #region 파일명 구하기 - GetFileName(fileNameWithoutExtension) /// <summary> /// 파일명 구하기 /// </summary> /// <param name="fileNameWithoutExtension">확장자 없는 파일명</param> /// <returns>파일명</returns> private static string GetFileName(string fileNameWithoutExtension) { return string.Concat(fileNameWithoutExtension, FileExtension); } #endregion } |
▶ ActivationHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
namespace TestProject; /// <summary> /// 활성화 핸들러 /// </summary> /// <typeparam name="TActivationHandler">활성화 핸들러 타입</typeparam> public abstract class ActivationHandler<TActivationHandler> : IActivationHandler where TActivationHandler : class { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 처리 가능 여부 구하기 - CanHandle(activationHandler) /// <summary> /// 처리 가능 여부 구하기 /// </summary> /// <param name="activationHandler">활성화 핸들러</param> /// <returns>처리 가능 여부</returns> public bool CanHandle(object activationHandler) => activationHandler is TActivationHandler && CanHandleInternal(activationHandler as TActivationHandler); #endregion #region 처리하기 (비동기) - HandleAsync(activationHandler) /// <summary> /// 처리하기 (비동기) /// </summary> /// <param name="activationHandler">활성화 핸들러</param> /// <returns>태스크</returns> public async Task HandleAsync(object activationHandler) => await HandleInternalAsync(activationHandler as TActivationHandler); #endregion ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 처리 가능 여부 구하기 (내부용) - CanHandleInternal(activationHandler) /// <summary> /// 처리 가능 여부 구하기 (내부용) /// </summary> /// <param name="activationHandler">활성화 핸들러</param> /// <returns>처리 가능 여부</returns> protected virtual bool CanHandleInternal(TActivationHandler activationHandler) => true; #endregion #region 처리하기 (내부용, 비동기) - HandleInternalAsync(activationHandler) /// <summary> /// 처리하기 (내부용, 비동기) /// </summary> /// <param name="activationHandler">활성화 핸들러</param> /// <returns>태스크</returns> protected abstract Task HandleInternalAsync(TActivationHandler activationHandler); #endregion } |
▶ AppNotificationActivationHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.Windows.AppLifecycle; using Microsoft.Windows.AppNotifications; namespace TestProject; /// <summary> /// 앱 알림 활성화 핸들러 /// </summary> public class AppNotificationActivationHandler : ActivationHandler<LaunchActivatedEventArgs> { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 네비게이션 서비스 /// </summary> private readonly INavigationService navigationService; /// <summary> /// 알림 서비스 /// </summary> private readonly IAppNotificationService notificationService; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - AppNotificationActivationHandler(navigationService, appNotificationService) /// <summary> /// 생성자 /// </summary> /// <param name="navigationService">네비게이션 서비스</param> /// <param name="appNotificationService">앱 알림 서비스</param> public AppNotificationActivationHandler(INavigationService navigationService, IAppNotificationService appNotificationService) { this.navigationService = navigationService; this.notificationService = appNotificationService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 처리 가능 여부 구하기 (내부용) - CanHandleInternal(e) /// <summary> /// 처리 가능 여부 구하기 (내부용) /// </summary> /// <param name="e">이벤트 인자</param> /// <returns>처리 가능 여부</returns> protected override bool CanHandleInternal(LaunchActivatedEventArgs e) { return AppInstance.GetCurrent().GetActivatedEventArgs()?.Kind == ExtendedActivationKind.AppNotification; } #endregion #region 처리하기 (내부용, 비동기) - HandleInternalAsync(e) /// <summary> /// 처리하기 (내부용, 비동기) /// </summary> /// <param name="e">이벤트 인자</param> /// <returns>태스크</returns> protected async override Task HandleInternalAsync(LaunchActivatedEventArgs e) { AppNotificationActivatedEventArgs appNotificationActivatedEventArgs = (AppNotificationActivatedEventArgs)AppInstance.GetCurrent().GetActivatedEventArgs().Data; if(this.notificationService.ParseArgument(appNotificationActivatedEventArgs.Argument)["action"] == "Settings") { App.MainWindow.DispatcherQueue.TryEnqueue ( DispatcherQueuePriority.Low, () => { this.navigationService.NavigateTo(typeof(SettingViewModel).FullName!); } ); } App.MainWindow.DispatcherQueue.TryEnqueue ( DispatcherQueuePriority.Low, () => { App.MainWindow.ShowMessageDialogAsync("TODO : Handle notification activations.", "Notification Activation"); } ); await Task.CompletedTask; } #endregion } |
▶ DefaultActivationHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
using Microsoft.UI.Xaml; namespace TestProject; /// <summary> /// 디폴트 활성화 핸들러 /// </summary> public class DefaultActivationHandler : ActivationHandler<LaunchActivatedEventArgs> { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region FIeld /// <summary> /// 네비게이션 서비스 /// </summary> private readonly INavigationService notificationService; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - DefaultActivationHandler(navigationService) /// <summary> /// 생성자 /// </summary> /// <param name="navigationService">네비게이션 서비스 인터페이스</param> public DefaultActivationHandler(INavigationService navigationService) { this.notificationService = navigationService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 처리 가능 여부 구하기 (내부용) - CanHandleInternal(e) /// <summary> /// 처리 가능 여부 구하기 (내부용) /// </summary> /// <param name="e">이벤트 인자</param> /// <returns>처리 가능 여부</returns> protected override bool CanHandleInternal(LaunchActivatedEventArgs e) { return this.notificationService.Frame?.Content == null; } #endregion #region 처리하기 (내부용, 비동기) - HandleInternalAsync(e) /// <summary> /// 처리하기 (내부용, 비동기) /// </summary> /// <param name="e">이벤트 인자</param> /// <returns>태스크</returns> protected async override Task HandleInternalAsync(LaunchActivatedEventArgs e) { this.notificationService.NavigateTo(typeof(MainViewModel).FullName!, e.Arguments); await Task.CompletedTask; } #endregion } |
▶ JSONHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// JSON 헬퍼 /// </summary> public static class JSONHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 직렬화하기 (비동기) - SerializeAsync(item) /// <summary> /// 직렬화하기 (비동기) /// </summary> /// <param name="item">항목</param> /// <returns>JSON 문자열 태스크</returns> public static async Task<string> SerializeAsync(object item) { return await Task.Run<string> ( () => { return JsonConvert.SerializeObject(item); } ); } #endregion #region 역직렬화하기 (비동기) - DeserializeAsync<TItem>(json) /// <summary> /// 역직렬화하기 (비동기) /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="json">JSON 문자열</param> /// <returns>항목 태스크</returns> public static async Task<TItem> DeserializeAsync<TItem>(string json) { return await Task.Run<TItem> ( () => { return JsonConvert.DeserializeObject<TItem>(json); } ); } #endregion } |
▶ NavigationHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 네비게이션 헬퍼 /// </summary> public class NavigationHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 탐색하기 속성 - NavigateToProperty /// <summary> /// 탐색하기 속성 /// </summary> public static readonly DependencyProperty NavigateToProperty = DependencyProperty.RegisterAttached ( "NavigateTo", typeof(string), typeof(NavigationHelper), new PropertyMetadata(null) ); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 탐색하기 값 구하기 - GetNavigateTo(item) /// <summary> /// 탐색하기 값 구하기 /// </summary> /// <param name="item">네비게이션 뷰 항목</param> /// <returns>탐색하기 값</returns> public static string GetNavigateTo(NavigationViewItem item) => (string)item.GetValue(NavigateToProperty); #endregion #region 탐색하기 값 설정하기 - SetNavigateTo(item, value) /// <summary> /// 탐색하기 값 설정하기 /// </summary> /// <param name="item">네비게이션 뷰 항목</param> /// <param name="value">탐색하기 값</param> public static void SetNavigateTo(NavigationViewItem item, string value) => item.SetValue(NavigateToProperty, value); #endregion } |
▶ RuntimeHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
using System.Runtime.InteropServices; using System.Text; namespace TestProject; /// <summary> /// 런타임 헬퍼 /// </summary> public class RuntimeHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Import ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 현재 패키지 전체 명칭 구하기 - GetCurrentPackageFullName(packageFullNameLength, packageFullNameStringBuilder) /// <summary> /// 현재 패키지 전체 명칭 구하기 /// </summary> /// <param name="packageFullNameLength">패키지 전체 명칭 길이</param> /// <param name="packageFullNameStringBuilder">패키지 전체 명칭 문자열 빌더</param> /// <returns>처리 결과</returns> [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullNameStringBuilder); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region MSIX 여부 - IsMSIX /// <summary> /// MSIX 여부 /// </summary> public static bool IsMSIX { get { var length = 0; return GetCurrentPackageFullName(ref length, null) != 15700L; } } #endregion } |
▶ TitleBarHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
using System.Runtime.InteropServices; using Windows.UI; using Windows.UI.ViewManagement; using Microsoft.UI; using Microsoft.UI.Xaml; namespace TestProject; /// <summary> /// 타이틀바 헬퍼 /// </summary> internal class TitleBarHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Import ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 활성 윈도우 구하기 - GetActiveWindow() /// <summary> /// 활성 윈도우 구하기 /// </summary> /// <returns>활성 윈도우 핸들</returns> [DllImport("user32")] private static extern IntPtr GetActiveWindow(); #endregion #region 메시지 보내기 - SendMessage(windowHandle, message, wordParameter, longParameter) /// <summary> /// 메시지 보내기 /// </summary> /// <param name="windowHandle">윈도우 핸들</param> /// <param name="message">메시지</param> /// <param name="wordParameter">WORD 매개 변수</param> /// <param name="longParameter">LONG 매개 변수</param> /// <returns>처리 결과</returns> [DllImport("user32", CharSet = CharSet.Auto)] private static extern IntPtr SendMessage(IntPtr windowHandle, int message, int wordParameter, IntPtr longParameter); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// WAINACTIVE /// </summary> private const int WAINACTIVE = 0x00; /// <summary> /// WAACTIVE /// </summary> private const int WAACTIVE = 0x01; /// <summary> /// WMACTIVATE /// </summary> private const int WMACTIVATE = 0x0006; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 타이틀바 업데이트하기 - UpdateTitleBar(elementTheme) /// <summary> /// 타이틀바 업데이트하기 /// </summary> /// <param name="elementTheme">엘리먼트 테마</param> public static void UpdateTitleBar(ElementTheme elementTheme) { if(App.MainWindow.ExtendsContentIntoTitleBar) { if(elementTheme == ElementTheme.Default) { UISettings uiSettings = new UISettings(); Color backgroundColor = uiSettings.GetColorValue(UIColorType.Background); elementTheme = backgroundColor == Colors.White ? ElementTheme.Light : ElementTheme.Dark; } if (elementTheme == ElementTheme.Default) { elementTheme = Application.Current.RequestedTheme == ApplicationTheme.Light ? ElementTheme.Light : ElementTheme.Dark; } App.MainWindow.AppWindow.TitleBar.ButtonForegroundColor = elementTheme switch { ElementTheme.Dark => Colors.White, ElementTheme.Light => Colors.Black, _ => Colors.Transparent }; App.MainWindow.AppWindow.TitleBar.ButtonHoverForegroundColor = elementTheme switch { ElementTheme.Dark => Colors.White, ElementTheme.Light => Colors.Black, _ => Colors.Transparent }; App.MainWindow.AppWindow.TitleBar.ButtonHoverBackgroundColor = elementTheme switch { ElementTheme.Dark => Color.FromArgb(0x33, 0xff, 0xff, 0xff), ElementTheme.Light => Color.FromArgb(0x33, 0x00, 0x00, 0x00), _ => Colors.Transparent }; App.MainWindow.AppWindow.TitleBar.ButtonPressedBackgroundColor = elementTheme switch { ElementTheme.Dark => Color.FromArgb(0x66, 0xff, 0xff, 0xff), ElementTheme.Light => Color.FromArgb(0x66, 0x00, 0x00, 0x00), _ => Colors.Transparent }; App.MainWindow.AppWindow.TitleBar.BackgroundColor = Colors.Transparent; nint windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow); if(windowHandle == GetActiveWindow()) { SendMessage(windowHandle, WMACTIVATE, WAINACTIVE, IntPtr.Zero); SendMessage(windowHandle, WMACTIVATE, WAACTIVE , IntPtr.Zero); } else { SendMessage(windowHandle, WMACTIVATE, WAACTIVE , IntPtr.Zero); SendMessage(windowHandle, WMACTIVATE, WAINACTIVE, IntPtr.Zero); } } } #endregion #region 캡션 버튼에 시스템 테마 적용하기 - ApplySystemThemeToCaptionButtons() /// <summary> /// 캡션 버튼에 시스템 테마 적용하기 /// </summary> public static void ApplySystemThemeToCaptionButtons() { FrameworkElement frameworkElement = App.AppTitlebar as FrameworkElement; if(frameworkElement != null) { UpdateTitleBar(frameworkElement.ActualTheme); } } #endregion } |
▶ IActivationHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
namespace TestProject; /// <summary> /// 활성화 핸들러 이벤트 /// </summary> public interface IActivationHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 처리 가능 여부 구하기 - CanHandle(argument) /// <summary> /// 처리 가능 여부 구하기 /// </summary> /// <param name="argument">인자</param> /// <returns>처리 가능 여부</returns> bool CanHandle(object argument); #endregion #region 처리하기 (비동기) - HandleAsync(argument) /// <summary> /// 처리하기 (비동기) /// </summary> /// <param name="argument">인자</param> /// <returns>태스크</returns> Task HandleAsync(object argument); #endregion } |
▶ IActivationService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
namespace TestProject; /// <summary> /// 활성화 서비스 인터페이스 /// </summary> public interface IActivationService { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 활성화하기 (비동기) - ActivateAsync(argument) /// <summary> /// 활성화하기 (비동기) /// </summary> /// <param name="argument">인자</param> /// <returns>태스크</returns> Task ActivateAsync(object argument); #endregion } |
▶ IAppNotificationService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
using System.Collections.Specialized; namespace TestProject; /// <summary> /// 앱 알림 서비스 인터페이스 /// </summary> public interface IAppNotificationService { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 초기화하기 - Initialize() /// <summary> /// 초기화하기 /// </summary> void Initialize(); #endregion #region 표시하기 - Show(payload) /// <summary> /// 표시하기 /// </summary> /// <param name="payload">페이로드</param> /// <returns>처리 결과</returns> bool Show(string payload); #endregion #region 인자 파싱하기 - ParseArgument(argument) /// <summary> /// 인자 파싱하기 /// </summary> /// <param name="argument">인자</param> /// <returns>파싱 인자 컬렉션</returns> NameValueCollection ParseArgument(string argument); #endregion #region 등록 취소하기 - Unregister() /// <summary> /// 등록 취소하기 /// </summary> void Unregister(); #endregion } |
▶ IFileService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
namespace TestProject; /// <summary> /// 파일 서비스 인터페이스 /// </summary> public interface IFileService { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 읽기 - Read<TItem>(folderPath, fileName) /// <summary> /// 읽기 /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="folderPath">폴더 경로</param> /// <param name="fileName">파일명</param> /// <returns>항목</returns> TItem Read<TItem>(string folderPath, string fileName); #endregion #region 저장하기 - Save<TItem>(folderPath, fileName, item) /// <summary> /// 저장하기 /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="folderPath">폴더 경로</param> /// <param name="fileName">파일명</param> /// <param name="item">항목</param> void Save<TItem>(string folderPath, string fileName, TItem item); #endregion #region 삭제하기 - Delete(folderPath, fileName) /// <summary> /// 삭제하기 /// </summary> /// <param name="folderPath">폴더 경로</param> /// <param name="fileName">파일명</param> void Delete(string folderPath, string fileName); #endregion } |
▶ ILocalSettingService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
namespace TestProject; /// <summary> /// 지역 설정 서비스 /// </summary> public interface ILocalSettingService { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 설정 읽기 (비동기) - ReadSettingAsync<TValue>(key) /// <summary> /// 설정 읽기 (비동기) /// </summary> /// <typeparam name="TValue">값 타입</typeparam> /// <param name="key">키</param> /// <returns>값 태스크</returns> Task<TValue> ReadSettingAsync<TValue>(string key); #endregion #region 설정 저장하기 (비동기) - SaveSettingAsync<TValue>(key, value) /// <summary> /// 설정 저장하기 (비동기) /// </summary> /// <typeparam name="TValue">값 타입</typeparam> /// <param name="key">키</param> /// <param name="value">값</param> /// <returns>태스크</returns> Task SaveSettingAsync<TValue>(string key, TValue value); #endregion } |
▶ INavigationAware.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
namespace TestProject; /// <summary> /// 네비게이션 인식 인터페이스 /// </summary> public interface INavigationAware { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 탐색 진입시 처리하기 - OnNavigatedTo(parameter) /// <summary> /// 탐색 시작하기 /// </summary> /// <param name="parameter">매개 변수</param> void OnNavigatedTo(object parameter); #endregion #region 탐색 이탈시 처리하기 - OnNavigatedFrom() /// <summary> /// 탐색 이탈시 처리하기 /// </summary> void OnNavigatedFrom(); #endregion } |
▶ INavigationService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; namespace TestProject; /// <summary> /// 네비게이션 서비스 /// </summary> public interface INavigationService { //////////////////////////////////////////////////////////////////////////////////////////////////// Event #region 탐색시 이벤트 - Navigated /// <summary> /// 탐색시 이벤트 /// </summary> event NavigatedEventHandler Navigated; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property #region 뒤로 가기 가능 여부 - CanGoBack /// <summary> /// 뒤로 가기 가능 여부 /// </summary> bool CanGoBack { get; } #endregion #region 프레임 - Frame /// <summary> /// 프레임 /// </summary> Frame Frame { get; set; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 탐색하기 - NavigateTo(pageKey, parameter, clearNavigation) /// <summary> /// 탐색하기 /// </summary> /// <param name="pageKey">페이지 키</param> /// <param name="parameter">매개 변수</param> /// <param name="clearNavigation">탐색 클리어 여부</param> /// <returns>처리 결과</returns> bool NavigateTo(string pageKey, object parameter = null, bool clearNavigation = false); #endregion #region 뒤로 가기 - GoBack() /// <summary> /// 뒤로 가기 /// </summary> /// <returns>처리 결과</returns> bool GoBack(); #endregion } |
▶ INavigationViewService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 네비게이션 뷰 서비스 /// </summary> public interface INavigationViewService { //////////////////////////////////////////////////////////////////////////////////////////////////// Property #region 메뉴 항목 리스트 - MenuItemList /// <summary> /// 메뉴 항목 리스트 /// </summary> IList<object> MenuItemList { get; } #endregion #region 설정 항목 - SettingItem /// <summary> /// 설정 항목 /// </summary> object SettingItem { get; } #endregion #region 초기화하기 - Initialize(navigationView) /// <summary> /// 초기화하기 /// </summary> /// <param name="navigationView">네비게이션 뷰</param> void Initialize(NavigationView navigationView); #endregion #region 이벤트 등록 취소하기 - UnregisterEvents() /// <summary> /// 이벤트 등록 취소하기 /// </summary> void UnregisterEvent(); #endregion #region 선택 항목 구하기 - GetSelectedItem(pageType) /// <summary> /// 선택 항목 구하기 /// </summary> /// <param name="pageType">페이지 타입</param> /// <returns>선택 항목</returns> NavigationViewItem GetSelectedItem(Type pageType); #endregion } |
▶ IPageService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
namespace TestProject; /// <summary> /// 페이지 서비스 /// </summary> public interface IPageService { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 페이지 타입 구하기 - GetPageType(key) /// <summary> /// 페이지 타입 구하기 /// </summary> /// <param name="key">키</param> /// <returns>페이지 타입</returns> Type GetPageType(string key); #endregion } |
▶ IThemeSelectorService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
using Microsoft.UI.Xaml; namespace TestProject; /// <summary> /// 테마 셀렉터 서비스 인터페이스 /// </summary> public interface IThemeSelectorService { //////////////////////////////////////////////////////////////////////////////////////////////////// Property #region 테마 - Theme /// <summary> /// 테마 /// </summary> ElementTheme Theme { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 초기화하기 (비동기) - InitializeAsync() /// <summary> /// 초기화하기 (비동기) /// </summary> /// <returns>태스크</returns> Task InitializeAsync(); #endregion #region 테마 설정하기 (비동기) - SetThemeAsync(theme) /// <summary> /// 테마 설정하기 (비동기) /// </summary> /// <param name="theme">테마</param> /// <returns>태스크</returns> Task SetThemeAsync(ElementTheme theme); #endregion #region 요청 테마 설정하기 - SetRequestedThemeAsync() /// <summary> /// 요청 테마 설정하기 /// </summary> /// <returns>태스크</returns> Task SetRequestedThemeAsync(); #endregion } |
▶ IWebViewService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; namespace TestProject; /// <summary> /// 웹 뷰 서비스 인터페이스 /// </summary> public interface IWebViewService { //////////////////////////////////////////////////////////////////////////////////////////////////// Event #region 탐색 완료시 이벤트 - NavigationCompleted /// <summary> /// 탐색 완료시 이벤트 /// </summary> event EventHandler<CoreWebView2WebErrorStatus> NavigationCompleted; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property #region 소스 - Source /// <summary> /// 소스 /// </summary> Uri Source { get; } #endregion #region 뒤로 가기 가능 여부 - CanGoBack /// <summary> /// 뒤로 가기 가능 여부 /// </summary> bool CanGoBack { get; } #endregion #region 앞으로 가기 가능 여부 - CanGoForward /// <summary> /// 앞으로 가기 가능 여부 /// </summary> bool CanGoForward { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 초기화하기 - Initialize(webView) /// <summary> /// 초기화하기 /// </summary> /// <param name="webView">웹 뷰</param> void Initialize(WebView2 webView); #endregion #region 뒤로 가기 - GoBack() /// <summary> /// 뒤로 가기 /// </summary> void GoBack(); #endregion #region 앞으로 가기 - GoForward() /// <summary> /// 앞으로 가기 /// </summary> void GoForward(); #endregion #region 다시 로드하기 - Reload() /// <summary> /// 다시 로드하기 /// </summary> void Reload(); #endregion #region 이벤트 등록 취소하기 - UnregisterEvent() /// <summary> /// 이벤트 등록 취소하기 /// </summary> void UnregisterEvent(); #endregion } |
▶ LocalSettingOption.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
namespace TestProject; /// <summary> /// 로컬 설정 옵션 /// </summary> public class LocalSettingOption { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 애플리케이션 데이터 폴더 - ApplicationDataFolder /// <summary> /// 애플리케이션 데이터 폴더 /// </summary> public string ApplicationDataFolder { get; set; } #endregion #region 로컬 설정 파일 - LocalSettingFile /// <summary> /// 로컬 설정 파일 /// </summary> public string LocalSettingFile { get; set; } #endregion } |
▶ ActivationService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 활성화 서비스 /// </summary> public class ActivationService : IActivationService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 디폴트 활성화 핸들러 /// </summary> private readonly ActivationHandler<LaunchActivatedEventArgs> defaultActivationHandler; /// <summary> /// 활성화 핸들러 열거 가능형 /// </summary> private readonly IEnumerable<IActivationHandler> activationHandlerEnumerable; /// <summary> /// 테마 선택자 서비스 /// </summary> private readonly IThemeSelectorService themeSelectorService; /// <summary> /// 쉘 엘리먼트 /// </summary> private UIElement shellElement = null; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - ActivationService(defaultActivationHandler, activationHandlerEnumerable, themeSelectorService) /// <summary> /// 생성자 /// </summary> /// <param name="defaultActivationHandler">디폴트 활성화 핸들러</param> /// <param name="activationHandlerEnumerable">활성화 핸들러 열거 가능형</param> /// <param name="themeSelectorService">테마 선택자 서비스</param> public ActivationService(ActivationHandler<LaunchActivatedEventArgs> defaultActivationHandler, IEnumerable<IActivationHandler> activationHandlerEnumerable, IThemeSelectorService themeSelectorService) { this.defaultActivationHandler = defaultActivationHandler; this.activationHandlerEnumerable = activationHandlerEnumerable; this.themeSelectorService = themeSelectorService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 활성화하기 (비동기) - ActivateAsync(argument) /// <summary> /// 활성화하기 (비동기) /// </summary> /// <param name="argument">인자</param> /// <returns>태스크</returns> public async Task ActivateAsync(object argument) { await InitializeAsync(); if(App.MainWindow.Content == null) { this.shellElement = App.GetService<ShellPage>(); App.MainWindow.Content = this.shellElement ?? new Frame(); } await HandleActivationAsync(argument); App.MainWindow.Activate(); await StartupAsync(); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 활성화 처리하기 (비동기) - HandleActivationAsync(argument) /// <summary> /// 활성화 처리하기 (비동기) /// </summary> /// <param name="argument">인자</param> /// <returns>태스크</returns> private async Task HandleActivationAsync(object argument) { IActivationHandler activationHandler = this.activationHandlerEnumerable.FirstOrDefault(handler => handler.CanHandle(argument)); if(activationHandler != null) { await activationHandler.HandleAsync(argument); } if(defaultActivationHandler.CanHandle(argument)) { await defaultActivationHandler.HandleAsync(argument); } } #endregion #region 초기화하기 (비동기) - InitializeAsync() /// <summary> /// 초기화하기 (비동기) /// </summary> /// <returns>태스크</returns> private async Task InitializeAsync() { await this.themeSelectorService.InitializeAsync().ConfigureAwait(false); await Task.CompletedTask; } #endregion #region 시작하기 (비동기) - StartupAsync() /// <summary> /// 시작하기 (비동기) /// </summary> /// <returns>태스크</returns> private async Task StartupAsync() { await this.themeSelectorService.SetRequestedThemeAsync(); await Task.CompletedTask; } #endregion } |
▶ AppNotificationService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
using System.Collections.Specialized; using System.Web; using Microsoft.Windows.AppNotifications; namespace TestProject; /// <summary> /// 앱 알림 서비스 /// </summary> public class AppNotificationService : IAppNotificationService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 네비게이션 서비스 /// </summary> private readonly INavigationService navigationService; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - AppNotificationService(navigationService) /// <summary> /// 생성자 /// </summary> /// <param name="navigationService">네비게이션 서비스</param> public AppNotificationService(INavigationService navigationService) { this.navigationService = navigationService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Destructor #region 소멸자 - ~AppNotificationService() /// <summary> /// 소멸자 /// </summary> ~AppNotificationService() { Unregister(); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 초기화하기 - Initialize() /// <summary> /// 초기화하기 /// </summary> public void Initialize() { AppNotificationManager.Default.NotificationInvoked += AppNotificationManager_NotificationInvoked; AppNotificationManager.Default.Register(); } #endregion #region 표시하기 - Show(payload) /// <summary> /// 표시하기 /// </summary> /// <param name="payload">페이로드</param> /// <returns>표시 결과</returns> public bool Show(string payload) { AppNotification appNotification = new AppNotification(payload); AppNotificationManager.Default.Show(appNotification); return appNotification.Id != 0; } #endregion #region 인자 파싱하기 - ParseArgument(argument) /// <summary> /// 인자 파싱하기 /// </summary> /// <param name="argument">인자</param> /// <returns>파싱 값 컬렉션</returns> public NameValueCollection ParseArgument(string argument) { return HttpUtility.ParseQueryString(argument); } #endregion #region 등록 취소하기 - Unregister() /// <summary> /// 등록 취소하기 /// </summary> public void Unregister() { AppNotificationManager.Default.Unregister(); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 앱 알림 관리자 알림 호출시 처리하기 - AppNotificationManager_NotificationInvoked(sender, e) /// <summary> /// 앱 알림 관리자 알림 호출시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void AppNotificationManager_NotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs e) { if(ParseArgument(e.Argument)["action"] == "Setting") { App.MainWindow.DispatcherQueue.TryEnqueue ( () => { this.navigationService.NavigateTo(typeof(SettingViewModel).FullName!); } ); } App.MainWindow.DispatcherQueue.TryEnqueue ( () => { App.MainWindow.ShowMessageDialogAsync("TODO : Handle notification invocations when your app is already running.", "Notification Invoked"); App.MainWindow.BringToFront(); } ); } #endregion } |
▶ FileService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
using System.Text; using Newtonsoft.Json; namespace TestProject; /// <summary> /// 파일 서비스 /// </summary> public class FileService : IFileService { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 읽기 - Read<TItem>(folderPath, fileName) /// <summary> /// 읽기 /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="folderPath">폴더 경로</param> /// <param name="fileName">파일명</param> /// <returns>항목</returns> public TItem Read<TItem>(string folderPath, string fileName) { string filePath = Path.Combine(folderPath, fileName); if(File.Exists(filePath)) { string json = File.ReadAllText(filePath); return JsonConvert.DeserializeObject<TItem>(json); } return default; } #endregion #region 저장하기 - Save<TItem>(folderPath, fileName, item) /// <summary> /// 저장하기 /// </summary> /// <typeparam name="TItem">항목 타입</typeparam> /// <param name="folderPath">폴더 경로</param> /// <param name="fileName">파일명</param> /// <param name="item">항목</param> public void Save<TItem>(string folderPath, string fileName, TItem item) { if(!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } string json = JsonConvert.SerializeObject(item); string filePath = Path.Combine(folderPath, fileName); File.WriteAllText(filePath, json, Encoding.UTF8); } #endregion #region 삭제하기 - Delete(folderPath, fileName) /// <summary> /// 삭제하기 /// </summary> /// <param name="folderPath">폴더 경로</param> /// <param name="fileName">파일명</param> public void Delete(string folderPath, string fileName) { string filePath = Path.Combine(folderPath, fileName); if(fileName != null && File.Exists(filePath)) { File.Delete(filePath); } } #endregion } |
▶ LocalSettingService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
using Microsoft.Extensions.Options; using Windows.Storage; namespace TestProject; /// <summary> /// 로컬 설정 서비스 /// </summary> public class LocalSettingService : ILocalSettingService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 디폴트 애플리케이션 데이터 폴더 /// </summary> private const string DEFAULT_APPLICATION_DATA_FOLDER = "TestProject/ApplicationData"; /// <summary> /// 디폴트 로컬 설정 파일명 /// </summary> private const string DEFAULT_LOCAL_SETTING_FILENAME = "LocalSettings.json"; /// <summary> /// 파일 서비스 /// </summary> private readonly IFileService fileService; /// <summary> /// 로컬 설정 옵션 /// </summary> private readonly LocalSettingOption localSettingOption; /// <summary> /// 로컬 애플리케이션 데이터 /// </summary> private readonly string localApplicationData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); /// <summary> /// 애플리케이션 데이터 폴더 /// </summary> private readonly string applicationDataFolder; /// <summary> /// 로컬 설정 파일명 /// </summary> private readonly string localSettingFileName; /// <summary> /// 설정 딕셔너리 /// </summary> private IDictionary<string, object> settingDictionary; /// <summary> /// 초기화 여부 /// </summary> private bool isInitialized; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - LocalSettingService(fileService, localSettingOption) /// <summary> /// 생성자 /// </summary> /// <param name="fileService">파일 서비스</param> /// <param name="localSettingOption">로컬 설정 옵션</param> public LocalSettingService(IFileService fileService, IOptions<LocalSettingOption> localSettingOption) { this.fileService = fileService; this.localSettingOption = localSettingOption.Value; this.applicationDataFolder = Path.Combine(this.localApplicationData, this.localSettingOption.ApplicationDataFolder ?? DEFAULT_APPLICATION_DATA_FOLDER); this.localSettingFileName = this.localSettingOption.LocalSettingFile ?? DEFAULT_LOCAL_SETTING_FILENAME; this.settingDictionary = new Dictionary<string, object>(); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 설정 읽기 (비동기) - ReadSettingAsync<TValue>(key) /// <summary> /// 설정 읽기 (비동기) /// </summary> /// <typeparam name="TValue">값 타입</typeparam> /// <param name="key">키</param> /// <returns>항목 태스크</returns> public async Task<TValue> ReadSettingAsync<TValue>(string key) { if(RuntimeHelper.IsMSIX) { if(ApplicationData.Current.LocalSettings.Values.TryGetValue(key, out var instance)) { return await JSONHelper.DeserializeAsync<TValue>((string)instance); } } else { await InitializeAsync(); if(this.settingDictionary != null && this.settingDictionary.TryGetValue(key, out var instance)) { return await JSONHelper.DeserializeAsync<TValue>((string)instance); } } return default; } #endregion #region 설정 저장하기 (비동기) - SaveSettingAsync<TValue>(key, value) /// <summary> /// 설정 저장하기 (비동기) /// </summary> /// <typeparam name="TValue">값 타입</typeparam> /// <param name="key">키</param> /// <param name="value">값</param> /// <returns>태스크</returns> public async Task SaveSettingAsync<TValue>(string key, TValue value) { if(RuntimeHelper.IsMSIX) { ApplicationData.Current.LocalSettings.Values[key] = await JSONHelper.SerializeAsync(value); } else { await InitializeAsync(); this.settingDictionary[key] = await JSONHelper.SerializeAsync(value); await Task.Run(() => this.fileService.Save(this.applicationDataFolder, this.localSettingFileName, this.settingDictionary)); } } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 초기화하기 (비동기) - InitializeAsync() /// <summary> /// 초기화하기 (비동기) /// </summary> /// <returns>태스크</returns> private async Task InitializeAsync() { if(!this.isInitialized) { this.settingDictionary = await Task.Run(() => this.fileService.Read<IDictionary<string, object>> ( this.applicationDataFolder, this.localSettingFileName )) ?? new Dictionary<string, object>(); this.isInitialized = true; } } #endregion } |
▶ NavigationService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
using System.Diagnostics.CodeAnalysis; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; namespace TestProject; /// <summary> /// 네비게이션 서비스 /// </summary> public class NavigationService : INavigationService { //////////////////////////////////////////////////////////////////////////////////////////////////// Event ////////////////////////////////////////////////////////////////////////////////////////// Public #region 탐색시 이벤트 - Navigated /// <summary> /// 탐색시 이벤트 /// </summary> public event NavigatedEventHandler Navigated; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 페이지 서비스 /// </summary> private readonly IPageService pageService; /// <summary> /// 마지막 사용 매개 변수 /// </summary> private object lastParameterUsed; /// <summary> /// 프레임 /// </summary> private Frame frame; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 프레임 - Frame /// <summary> /// 프레임 /// </summary> public Frame Frame { get { if(this.frame == null) { this.frame = App.MainWindow.Content as Frame; RegisterFrameEvent(); } return this.frame; } set { UnregisterFrameEvent(); this.frame = value; RegisterFrameEvent(); } } #endregion #region 뒤로 가기 가능 여부 - CanGoBack /// <summary> /// 뒤로 가기 가능 여부 /// </summary> [MemberNotNullWhen(true, nameof(Frame), nameof(this.frame))] public bool CanGoBack => Frame != null && Frame.CanGoBack; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - NavigationService(pageService) /// <summary> /// 생성자 /// </summary> /// <param name="pageService">페이지 서비스</param> public NavigationService(IPageService pageService) { this.pageService = pageService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 뒤로 가기 - GoBack() /// <summary> /// 뒤로 가기 /// </summary> /// <returns>처리 결과</returns> public bool GoBack() { if(CanGoBack) { object viewModelBeforeNavigation = this.frame.GetPageViewModel(); this.frame.GoBack(); if(viewModelBeforeNavigation is INavigationAware navigationAware) { navigationAware.OnNavigatedFrom(); } return true; } return false; } #endregion #region 탐색하기 - NavigateTo(pageKey, parameter, clearNavigation) /// <summary> /// 탐색하기 /// </summary> /// <param name="pageKey">페이지 키</param> /// <param name="parameter">매개 변수</param> /// <param name="clearNavigation">탐색 지우기 여부</param> /// <returns>처리 결과</returns> public bool NavigateTo(string pageKey, object parameter = null, bool clearNavigation = false) { Type pageType = this.pageService.GetPageType(pageKey); if(this.frame != null && (this.frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(this.lastParameterUsed)))) { this.frame.Tag = clearNavigation; object viewModelBeforeNavigation = this.frame.GetPageViewModel(); bool navigated = this.frame.Navigate(pageType, parameter); if(navigated) { this.lastParameterUsed = parameter; if(viewModelBeforeNavigation is INavigationAware navigationAware) { navigationAware.OnNavigatedFrom(); } } return navigated; } return false; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private //////////////////////////////////////////////////////////////////////////////// Event #region 프레임 탐색시 처리하기 - frame_Navigated(sender, e) /// <summary> /// 프레임 탐색시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void frame_Navigated(object sender, NavigationEventArgs e) { if(sender is Frame frame) { bool clearNavigation = (bool)frame.Tag; if(clearNavigation) { frame.BackStack.Clear(); } if(frame.GetPageViewModel() is INavigationAware navigationAware) { navigationAware.OnNavigatedTo(e.Parameter); } Navigated?.Invoke(sender, e); } } #endregion //////////////////////////////////////////////////////////////////////////////// Function #region 프레임 이벤트 등록하기 - RegisterFrameEvent() /// <summary> /// 프레임 이벤트 등록하기 /// </summary> private void RegisterFrameEvent() { if(this.frame != null) { this.frame.Navigated += frame_Navigated; } } #endregion #region 프레임 이벤트 등록 취소하기 - UnregisterFrameEvent() /// <summary> /// 프레임 이벤트 등록 취소하기 /// </summary> private void UnregisterFrameEvent() { if(this.frame != null) { this.frame.Navigated -= frame_Navigated; } } #endregion } |
▶ NavigationViewService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
using System.Diagnostics.CodeAnalysis; using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 네비게이션 뷰 서비스 /// </summary> public class NavigationViewService : INavigationViewService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 네비게이션 서비스 /// </summary> private readonly INavigationService navigationService; /// <summary> /// 페이지 서비스 /// </summary> private readonly IPageService pageService; /// <summary> /// 네비게이션 뷰 /// </summary> private NavigationView navigationView; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 메뉴 항목 리스트 - MenuItemList /// <summary> /// 메뉴 항목 리스트 /// </summary> public IList<object> MenuItemList => this.navigationView?.MenuItems; #endregion #region 설정 항목 - SettingItem /// <summary> /// 설정 항목 /// </summary> public object SettingItem => this.navigationView?.SettingsItem; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - NavigationViewService(navigationService, pageService) /// <summary> /// 생성자 /// </summary> /// <param name="navigationService">네비게이션 서비스</param> /// <param name="pageService">페이지 서비스</param> public NavigationViewService(INavigationService navigationService, IPageService pageService) { this.navigationService = navigationService; this.pageService = pageService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 초기화하기 - Initialize(navigationView) /// <summary> /// 초기화하기 /// </summary> /// <param name="navigationView">네비게이션 뷰</param> [MemberNotNull(nameof(this.navigationView))] public void Initialize(NavigationView navigationView) { this.navigationView = navigationView; this.navigationView.BackRequested += navigationView_BackRequested; this.navigationView.ItemInvoked += navigationView_ItemInvoked; } #endregion #region 선택 항목 구하기 - GetSelectedItem(pageType) /// <summary> /// 선택 항목 구하기 /// </summary> /// <param name="pageType">페이지 타입</param> /// <returns>네비게이션 뷰 항목</returns> public NavigationViewItem GetSelectedItem(Type pageType) { if(this.navigationView != null) { return GetSelectedItem(this.navigationView.MenuItems, pageType) ?? GetSelectedItem(this.navigationView.FooterMenuItems, pageType); } return null; } #endregion #region 이벤트 등록 취소하기 - UnregisterEvent() /// <summary> /// 이벤트 등록 취소하기 /// </summary> public void UnregisterEvent() { if(this.navigationView != null) { this.navigationView.BackRequested -= navigationView_BackRequested; this.navigationView.ItemInvoked -= navigationView_ItemInvoked; } } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private //////////////////////////////////////////////////////////////////////////////// Event #region 네비게이션 뷰 뒤로 가기 요청시 처리하기 - navigationView_BackRequested(sender, e) /// <summary> /// 네비게이션 뷰 뒤로 가기 요청시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void navigationView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs e) => this.navigationService.GoBack(); #endregion #region 네비게이션 뷰 항목 호출시 처리하기 - navigationView_ItemInvoked(sender, e) /// <summary> /// 네비게이션 뷰 항목 호출시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void navigationView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs e) { if (e.IsSettingsInvoked) { this.navigationService.NavigateTo(typeof(SettingViewModel).FullName!); } else { var selectedItem = e.InvokedItemContainer as NavigationViewItem; if (selectedItem?.GetValue(NavigationHelper.NavigateToProperty) is string pageKey) { this.navigationService.NavigateTo(pageKey); } } } #endregion //////////////////////////////////////////////////////////////////////////////// Function #region 선택 항목 구하기 - GetSelectedItem(menuItemEnumerable, pageType) /// <summary> /// 선택 항목 구하기 /// </summary> /// <param name="menuItemEnumerable">메뉴 항목 열거 가능형</param> /// <param name="pageType">페이지 타입</param> /// <returns>네비게이션 뷰 항목</returns> private NavigationViewItem GetSelectedItem(IEnumerable<object> menuItemEnumerable, Type pageType) { foreach(NavigationViewItem navigationViewItem in menuItemEnumerable.OfType<NavigationViewItem>()) { if(IsMenuItemForPageType(navigationViewItem, pageType)) { return navigationViewItem; } NavigationViewItem selectedNavigationViewItem = GetSelectedItem(navigationViewItem.MenuItems, pageType); if(selectedNavigationViewItem != null) { return selectedNavigationViewItem; } } return null; } #endregion #region 페이지 타입용 메뉴 항목 여부 구하기 - IsMenuItemForPageType(navigationViewItem, sourcePageType) /// <summary> /// 페이지 타입용 메뉴 항목 여부 구하기 /// </summary> /// <param name="navigationViewItem">네비게이션 뷰 항목</param> /// <param name="sourcePageType">소스 페이지 타입</param> /// <returns>페이지 타입용 메뉴 항목 여부</returns> private bool IsMenuItemForPageType(NavigationViewItem navigationViewItem, Type sourcePageType) { if(navigationViewItem.GetValue(NavigationHelper.NavigateToProperty) is string pageKey) { return this.pageService.GetPageType(pageKey) == sourcePageType; } return false; } #endregion } |
▶ PageService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 페이지 서비스 /// </summary> public class PageService : IPageService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 페이지 딕셔너리 /// </summary> private readonly Dictionary<string, Type> pageDictionary = new(); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - PageService() /// <summary> /// 생성자 /// </summary> public PageService() { Configure<MainViewModel , MainPage >(); Configure<TestViewModel , TestPage >(); Configure<WebViewModel , WebPage >(); Configure<SettingViewModel, SettingPage>(); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 페이지 타입 구하기 - GetPageType(key) /// <summary> /// 페이지 타입 구하기 /// </summary> /// <param name="key">키</param> /// <returns>페이지 타입</returns> public Type GetPageType(string key) { Type pageType; lock(this.pageDictionary) { if(!this.pageDictionary.TryGetValue(key, out pageType)) { throw new ArgumentException($"Page not found : {key}. Did you forget to call PageService.Configure?"); } } return pageType; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 구성하기 - Configure<TViewModel, TView>() /// <summary> /// 구성하기 /// </summary> /// <typeparam name="TViewModel">뷰 모델 타입</typeparam> /// <typeparam name="TView">뷰 타입</typeparam> private void Configure<TViewModel, TView>() where TViewModel : ObservableObject where TView : Page { lock(this.pageDictionary) { string key = typeof(TViewModel).FullName!; if(this.pageDictionary.ContainsKey(key)) { throw new ArgumentException($"The key {key} is already configured in PageService"); } Type type = typeof(TView); if(this.pageDictionary.ContainsValue(type)) { throw new ArgumentException($"This type is already configured with key {this.pageDictionary.First(p => p.Value == type).Key}"); } this.pageDictionary.Add(key, type); } } #endregion } |
▶ ThemeSelectorService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
using Microsoft.UI.Xaml; namespace TestProject; /// <summary> /// 테마 선택자 서비스 /// </summary> public class ThemeSelectorService : IThemeSelectorService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 설정 키 /// </summary> private const string SETTING_KEY = "AppBackgroundRequestedTheme"; /// <summary> /// 로컬 설정 서비스 /// </summary> private readonly ILocalSettingService localSettingService; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 테마 - Theme /// <summary> /// 테마 /// </summary> public ElementTheme Theme { get; set; } = ElementTheme.Default; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - ThemeSelectorService(localSettingService) /// <summary> /// 생성자 /// </summary> /// <param name="localSettingService">로컬 설정 서비스</param> public ThemeSelectorService(ILocalSettingService localSettingService) { this.localSettingService = localSettingService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 초기화하기 (비동기) - InitializeAsync() /// <summary> /// 초기화하기 (비동기) /// </summary> /// <returns>태스크</returns> public async Task InitializeAsync() { Theme = await LoadThemeFromSettingAsync(); await Task.CompletedTask; } #endregion #region 테마 설정하기 (비동기) - SetThemeAsync(theme) /// <summary> /// 테마 설정하기 (비동기) /// </summary> /// <param name="theme">테마</param> /// <returns>태스크</returns> public async Task SetThemeAsync(ElementTheme theme) { Theme = theme; await SetRequestedThemeAsync(); await SaveThemeInSettingAsync(Theme); } #endregion #region 요청 테마 설정하기 (비동기) - SetRequestedThemeAsync() /// <summary> /// 요청 테마 설정하기 (비동기) /// </summary> /// <returns>태스크</returns> public async Task SetRequestedThemeAsync() { if(App.MainWindow.Content is FrameworkElement rootElement) { rootElement.RequestedTheme = Theme; TitleBarHelper.UpdateTitleBar(Theme); } await Task.CompletedTask; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 설정에서 테마 로드하기 (비동기) - LoadThemeFromSettingAsync() /// <summary> /// 설정에서 테마 로드하기 (비동기) /// </summary> /// <returns>엘리먼트 테마 태스크</returns> private async Task<ElementTheme> LoadThemeFromSettingAsync() { string themeName = await this.localSettingService.ReadSettingAsync<string>(SETTING_KEY); if(Enum.TryParse(themeName, out ElementTheme cacheTheme)) { return cacheTheme; } return ElementTheme.Default; } #endregion #region 설정에서 테마 저장하기 (비동기) - SaveThemeInSettingAsync(theme) /// <summary> /// 설정에서 테마 저장하기 (비동기) /// </summary> /// <param name="theme">테마</param> /// <returns>태스크</returns> private async Task SaveThemeInSettingAsync(ElementTheme theme) { await this.localSettingService.SaveSettingAsync(SETTING_KEY, theme.ToString()); } #endregion } |
▶ WebViewService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
using System.Diagnostics.CodeAnalysis; using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; namespace TestProject; /// <summary> /// 웹 뷰 서비스 /// </summary> public class WebViewService : IWebViewService { //////////////////////////////////////////////////////////////////////////////////////////////////// Event ////////////////////////////////////////////////////////////////////////////////////////// Public #region 네비게이션 완료시 이벤트 - NavigationCompleted /// <summary> /// 네비게이션 완료시 이벤트 /// </summary> public event EventHandler<CoreWebView2WebErrorStatus> NavigationCompleted; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 웹 뷰 /// </summary> private WebView2 webView; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 소스 - Source /// <summary> /// 소스 /// </summary> public Uri Source => this.webView?.Source; #endregion #region 뒤로 가기 가능 여부 - CanGoBack /// <summary> /// 뒤로 가기 가능 여부 /// </summary> [MemberNotNullWhen(true, nameof(this.webView))] public bool CanGoBack => this.webView != null && this.webView.CanGoBack; #endregion #region 앞으로 가기 가능 여부 - CanGoForward /// <summary> /// 앞으로 가기 가능 여부 /// </summary> [MemberNotNullWhen(true, nameof(this.webView))] public bool CanGoForward => this.webView != null && this.webView.CanGoForward; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - WebViewService() /// <summary> /// 생성자 /// </summary> public WebViewService() { } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 초기화하기 - Initialize(webView) /// <summary> /// 초기화하기 /// </summary> /// <param name="webView">웹 뷰</param> [MemberNotNull(nameof(this.webView))] public void Initialize(WebView2 webView) { this.webView = webView; this.webView.NavigationCompleted += webView_WebViewNavigationCompleted; } #endregion #region 뒤로 가기 - GoBack() /// <summary> /// 뒤로 가기 /// </summary> public void GoBack() => this.webView?.GoBack(); #endregion #region 앞으로 가기 - GoForward() /// <summary> /// 앞으로 가기 /// </summary> public void GoForward() => this.webView?.GoForward(); #endregion #region 다시 로드하기 - Reload() /// <summary> /// 다시 로드하기 /// </summary> public void Reload() => this.webView?.Reload(); #endregion #region 이벤트 등록 취소하기 - UnregisterEvent() /// <summary> /// 이벤트 등록 취소하기 /// </summary> public void UnregisterEvent() { if(this.webView != null) { this.webView.NavigationCompleted -= webView_WebViewNavigationCompleted; } } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 웹 뷰 웹 뷰 네비게이션 완료시 처리하기 - webView_WebViewNavigationCompleted(sender, e) /// <summary> /// 웹 뷰 웹 뷰 네비게이션 완료시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void webView_WebViewNavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs e) => NavigationCompleted?.Invoke(this, e.WebErrorStatus); #endregion } |
▶ FontSizes.xaml
1 2 3 4 5 6 7 8 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <x:Double x:Key="LargeFontSize">24</x:Double> <x:Double x:Key="MediumFontSize">16</x:Double> </ResourceDictionary> |
▶ TextBlock.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="PageTitleStyle" TargetType="TextBlock"> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="TextWrapping" Value="NoWrap" /> <Setter Property="TextTrimming" Value="CharacterEllipsis" /> <Setter Property="FontWeight" Value="SemiLight" /> <Setter Property="FontSize" Value="{StaticResource LargeFontSize}" /> </Style> <Style x:Key="BodyTextStyle" TargetType="TextBlock"> <Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="TextTrimming" Value="CharacterEllipsis" /> <Setter Property="FontWeight" Value="Normal" /> <Setter Property="FontSize" Value="{StaticResource MediumFontSize}" /> </Style> </ResourceDictionary> |
▶ Thickness.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Thickness x:Key="LargeTopMargin">0 36 0 0</Thickness> <Thickness x:Key="LargeTopBottomMargin">0 36 0 36</Thickness> <Thickness x:Key="MediumTopMargin">0 24 0 0</Thickness> <Thickness x:Key="MediumTopBottomMargin">0 24 0 24</Thickness> <Thickness x:Key="MediumLeftRightMargin">24 0 24 0</Thickness> <Thickness x:Key="MediumBottomMargin">0 0 0 24</Thickness> <Thickness x:Key="SmallLeftMargin">12 0 0 0</Thickness> <Thickness x:Key="SmallLeftRightMargin">12 0 12 0</Thickness> <Thickness x:Key="SmallTopMargin">0 12 0 0</Thickness> <Thickness x:Key="SmallRightMargin">0 0 12 0</Thickness> <Thickness x:Key="SmallTopBottomMargin">0 12 0 12</Thickness> <Thickness x:Key="XSmallLeftMargin">8 0 0 0</Thickness> <Thickness x:Key="XSmallTopMargin">0 8 0 0</Thickness> <Thickness x:Key="XSmallLeftTopRightBottomMargin">8 8 8 8</Thickness> <Thickness x:Key="XXSmallTopMargin">0 4 0 0</Thickness> <Thickness x:Key="XXSmallLeftTopRightBottomMargin">4 4 4 4</Thickness> <Thickness x:Key="NavigationViewContentGridBorderThickness">1 1 0 0</Thickness> <CornerRadius x:Key="NavigationViewContentGridCornerRadius">8 0 0 0</CornerRadius> <Thickness x:Key="NavigationViewContentMargin">0 48 0 0</Thickness> <Thickness x:Key="NavigationViewHeaderMargin">56 34 0 0</Thickness> <Thickness x:Key="NavigationViewPageContentMargin">56 24 56 0</Thickness> <Thickness x:Key="MenuBarContentMargin">36 24 36 0</Thickness> <Thickness x:Key="SettingsPageHyperlinkButtonMargin">-12 4 0 0</Thickness> </ResourceDictionary> |
▶ MainViewModel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using CommunityToolkit.Mvvm.ComponentModel; namespace TestProject; /// <summary> /// 메인 뷰 모델 /// </summary> public partial class MainViewModel : ObservableRecipient { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - MainViewModel() /// <summary> /// 생성자 /// </summary> public MainViewModel() { } #endregion } |
▶ SettingViewModel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
using System.Reflection; using System.Windows.Input; using Windows.ApplicationModel; using Microsoft.UI.Xaml; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace TestProject; /// <summary> /// 설정 뷰 모델 /// </summary> public partial class SettingViewModel : ObservableRecipient { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 테마 셀렉터 서비스 /// </summary> private readonly IThemeSelectorService themeSelectorService; /// <summary> /// 엘리먼트 테마 /// </summary> [ObservableProperty] private ElementTheme elementTheme; /// <summary> /// 버전 설명 /// </summary> [ObservableProperty] private string versionDescription; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 스위치 테마 명령 - SwitchThemeCommand /// <summary> /// 스위치 테마 명령 /// </summary> public ICommand SwitchThemeCommand { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - SettingViewModel(themeSelectorService) /// <summary> /// 생성자 /// </summary> /// <param name="themeSelectorService">테마 셀렉터 서비스</param> public SettingViewModel(IThemeSelectorService themeSelectorService) { this.themeSelectorService = themeSelectorService; this.elementTheme = this.themeSelectorService.Theme; this.versionDescription = GetVersionDescription(); SwitchThemeCommand = new RelayCommand<ElementTheme> ( async (parameter) => { if(ElementTheme != parameter) { ElementTheme = parameter; await this.themeSelectorService.SetThemeAsync(parameter); } } ); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 버전 설명 구하기 - GetVersionDescription() /// <summary> /// 버전 설명 구하기 /// </summary> /// <returns>버전 설명</returns> private static string GetVersionDescription() { Version version; if(RuntimeHelper.IsMSIX) { PackageVersion packageVersion = Package.Current.Id.Version; version = new(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision); } else { version = Assembly.GetExecutingAssembly().GetName().Version!; } return $"{"AppDisplayName".GetLocalString()} - {version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; } #endregion } |
▶ ShellViewModel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; namespace TestProject; /// <summary> /// 쉘 뷰 모델 /// </summary> public partial class ShellViewModel : ObservableRecipient { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 뒤로 가기 이용 가능 여부 /// </summary> [ObservableProperty] private bool isBackEnabled; /// <summary> /// 선택 객체 /// </summary> [ObservableProperty] private object selected; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 네비게이션 서비스 - NavigationService /// <summary> /// 네비게이션 서비스 /// </summary> public INavigationService NavigationService { get; } #endregion #region 네비게이션 뷰 서비스 - NavigationViewService /// <summary> /// 네비게이션 뷰 서비스 /// </summary> public INavigationViewService NavigationViewService { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - ShellViewModel(navigationService, navigationViewService) /// <summary> /// 생성자 /// </summary> /// <param name="navigationService">네비게이션 서비스</param> /// <param name="navigationViewService">네비게이션 뷰 서비스</param> public ShellViewModel(INavigationService navigationService, INavigationViewService navigationViewService) { NavigationService = navigationService; NavigationViewService = navigationViewService; NavigationService.Navigated += NavigationService_Navigated; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Private #region 네비게이션 서비스 탐색시 처리하기 - NavigationService_Navigated(sender, e) /// <summary> /// 네비게이션 서비스 탐색시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void NavigationService_Navigated(object sender, NavigationEventArgs e) { IsBackEnabled = NavigationService.CanGoBack; if(e.SourcePageType == typeof(SettingPage)) { Selected = NavigationViewService.SettingItem; return; } NavigationViewItem selectedItem = NavigationViewService.GetSelectedItem(e.SourcePageType); if(selectedItem != null) { Selected = selectedItem; } } #endregion } |
▶ TestViewModel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using CommunityToolkit.Mvvm.ComponentModel; namespace TestProject; /// <summary> /// 테스트 뷰 모델 /// </summary> public partial class TestViewModel : ObservableRecipient { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - TestViewModel() /// <summary> /// 생성자 /// </summary> public TestViewModel() { } #endregion } |
▶ WebViewModel.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
using Windows.System; using Microsoft.Web.WebView2.Core; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace TestProject; /// <summary> /// 웹 뷰 모델 /// </summary> public partial class WebViewModel : ObservableRecipient, INavigationAware { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Prvate #region Field /// <summary> /// 소스 /// </summary> [ObservableProperty] private Uri source = new("https://docs.microsoft.com/windows/apps/"); /// <summary> /// 로딩 여부 /// </summary> [ObservableProperty] private bool isLoading = true; /// <summary> /// 실패 여부 /// </summary> [ObservableProperty] private bool hasFailures; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 웹 뷰 서비스 - WebViewService /// <summary> /// 웹 뷰 서비스 /// </summary> public IWebViewService WebViewService { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - WebViewModel(webViewService) /// <summary> /// 생성자 /// </summary> /// <param name="webViewService">웹 뷰 서비스</param> public WebViewModel(IWebViewService webViewService) { WebViewService = webViewService; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 탐색 진입시 처리하기 - OnNavigatedTo(parameter) /// <summary> /// 탐색 진입시 처리하기 /// </summary> /// <param name="parameter">매개 변수</param> public void OnNavigatedTo(object parameter) { WebViewService.NavigationCompleted += WebViewService_NavigationCompleted; } #endregion #region 탐색 이탈시 처리하기 - OnNavigatedFrom() /// <summary> /// 탐색 이탈시 처리하기 /// </summary> public void OnNavigatedFrom() { WebViewService.UnregisterEvent(); WebViewService.NavigationCompleted -= WebViewService_NavigationCompleted; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private //////////////////////////////////////////////////////////////////////////////// Event #region 웹 뷰 서비스 탐색 완료시 처리하기 - WebViewService_NavigationCompleted(sender, status) /// <summary> /// 웹 뷰 서비스 탐색 완료시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="status">코어 웹 뷰 2 웹 에러 상태</param> private void WebViewService_NavigationCompleted(object sender, CoreWebView2WebErrorStatus status) { IsLoading = false; BrowserBackwardCommand.NotifyCanExecuteChanged(); BrowserForwardCommand.NotifyCanExecuteChanged(); if(status != default) { HasFailures = true; } } #endregion //////////////////////////////////////////////////////////////////////////////// Function #region 브라우저에서 열기 - OpenInBrowser() /// <summary> /// 브라우저에서 열기 /// </summary> /// <returns>태스크</returns> [RelayCommand] private async Task OpenInBrowser() { if(WebViewService.Source != null) { await Launcher.LaunchUriAsync(WebViewService.Source); } } #endregion #region 다시 로드하기 - Reload() /// <summary> /// 다시 로드하기 /// </summary> [RelayCommand] private void Reload() { WebViewService.Reload(); } #endregion #region 브라우저 앞으로 가기 가능 여부 구하기 - BrowserCanGoForward() /// <summary> /// 브라우저 앞으로 가기 가능 여부 구하기 /// </summary> /// <returns>브라우저 앞으로 가기 가능 여부</returns> private bool BrowserCanGoForward() { return WebViewService.CanGoForward; } #endregion #region 브라우저 앞으로 가기 - BrowserForward() /// <summary> /// 브라우저 앞으로 가기 /// </summary> [RelayCommand(CanExecute = nameof(BrowserCanGoForward))] private void BrowserForward() { if (WebViewService.CanGoForward) { WebViewService.GoForward(); } } #endregion #region 브라우저 뒤로 가기 가능 여부 구하기 - BrowserCanGoBackward() /// <summary> /// 브라우저 뒤로 가기 가능 여부 구하기 /// </summary> /// <returns>브라우저 뒤로 가기 가능 여부</returns> private bool BrowserCanGoBackward() { return WebViewService.CanGoBack; } #endregion #region 브라우저 뒤로 가기 - BrowserBackward() /// <summary> /// 브라우저 뒤로 가기 /// </summary> [RelayCommand(CanExecute = nameof(BrowserCanGoBackward))] private void BrowserBackward() { if(WebViewService.CanGoBack) { WebViewService.GoBack(); } } #endregion #region 재시도시 처리하기 - OnRetry() /// <summary> /// 재시도시 처리하기 /// </summary> [RelayCommand] private void OnRetry() { HasFailures = false; IsLoading = true; WebViewService?.Reload(); } #endregion } |
▶ MainPage.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <Page x:Class="TestProject.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" FontFamily="나눔고딕코딩" FontSize="16"> <Border Name="border" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="200" Background="{ThemeResource AccentAAFillColorDefaultBrush}" /> </Page> |
▶ MainPage.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
using Microsoft.UI.Composition; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Hosting; namespace TestProject; /// <summary> /// 메인 페이지 /// </summary> public sealed partial class MainPage : Page { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - MainPage() /// <summary> /// 생성자 /// </summary> public MainPage() { InitializeComponent(); Loaded += Page_Loaded; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Private #region 페이지 로드시 처리하기 - Page_Loaded(sender, e) /// <summary> /// 페이지 로드시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void Page_Loaded(object sender, RoutedEventArgs e) { Visual borderVisual = ElementCompositionPreview.GetElementVisual(this.border); Compositor compositor = borderVisual.Compositor; ScalarKeyFrameAnimation scalarKeyFrameAnimation = compositor.CreateScalarKeyFrameAnimation(); scalarKeyFrameAnimation.InsertKeyFrame(0, 0); scalarKeyFrameAnimation.InsertKeyFrame(1, 1); scalarKeyFrameAnimation.Duration = TimeSpan.FromSeconds(5); borderVisual.StartAnimation("Opacity", scalarKeyFrameAnimation); } #endregion } |
▶ SettingPage.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<Page x:Class="TestProject.SettingPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xaml="using:Microsoft.UI.Xaml" xmlns:local="using:TestProject"> <Page.Resources> <local:EnumerationToBooleanConverter x:Key="EnumerationToBooleanConverterKey" /> </Page.Resources> <Grid> <StackPanel Name="ContentArea"> <TextBlock x:Uid="Settings_Personalization" Style="{ThemeResource SubtitleTextBlockStyle}" /> <StackPanel Margin="{StaticResource SmallTopBottomMargin}"> <TextBlock x:Uid="Settings_Theme" /> <StackPanel Margin="{StaticResource XSmallTopMargin}"> <RadioButton x:Uid="Settings_Theme_Light" GroupName="AppTheme" FontSize="15" IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumerationToBooleanConverterKey}, ConverterParameter=Light, Mode=OneWay}" Command="{x:Bind ViewModel.SwitchThemeCommand}"> <RadioButton.CommandParameter> <xaml:ElementTheme>Light</xaml:ElementTheme> </RadioButton.CommandParameter> </RadioButton> <RadioButton x:Uid="Settings_Theme_Dark" GroupName="AppTheme" FontSize="15" IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumerationToBooleanConverterKey}, ConverterParameter=Dark, Mode=OneWay}" Command="{x:Bind ViewModel.SwitchThemeCommand}"> <RadioButton.CommandParameter> <xaml:ElementTheme>Dark</xaml:ElementTheme> </RadioButton.CommandParameter> </RadioButton> <RadioButton x:Uid="Settings_Theme_Default" GroupName="AppTheme" FontSize="15" IsChecked="{x:Bind ViewModel.ElementTheme, Converter={StaticResource EnumerationToBooleanConverterKey}, ConverterParameter=Default, Mode=OneWay}" Command="{x:Bind ViewModel.SwitchThemeCommand}"> <RadioButton.CommandParameter> <xaml:ElementTheme>Default</xaml:ElementTheme> </RadioButton.CommandParameter> </RadioButton> </StackPanel> </StackPanel> <TextBlock x:Uid="Settings_About" Style="{ThemeResource SubtitleTextBlockStyle}" /> <StackPanel Margin="{StaticResource XSmallTopMargin}"> <TextBlock Style="{ThemeResource BodyTextBlockStyle}" Text="{x:Bind ViewModel.VersionDescription, Mode=OneWay}" /> <TextBlock x:Uid="Settings_AboutDescription" Style="{ThemeResource BodyTextBlockStyle}" Margin="{StaticResource XSmallTopMargin}" /> <HyperlinkButton x:Uid="SettingsPage_PrivacyTermsLink" Margin="{StaticResource SettingsPageHyperlinkButtonMargin}" /> </StackPanel> </StackPanel> </Grid> </Page> |
▶ SettingPage.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 설정 페이지 /// </summary> public sealed partial class SettingPage : Page { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 뷰 모델 - ViewModel /// <summary> /// 뷰 모델 /// </summary> public SettingViewModel ViewModel { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - SettingPage() /// <summary> /// 생성자 /// </summary> public SettingPage() { ViewModel = App.GetService<SettingViewModel>(); InitializeComponent(); } #endregion } |
▶ ShellPage.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
<Page x:Class="TestProject.ShellPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:local="using:TestProject"> <Grid> <Grid Name="appTitleBarGrid" VerticalAlignment="Top" Height="{Binding ElementName=navigationView, Path=CompactPaneLength}" IsHitTestVisible="True"> <Image HorizontalAlignment="Left" Width="16" Height="16" Source="/Assets/WindowIcon.ico" /> <TextBlock Name="appTitleBarTextBlock" Style="{StaticResource CaptionTextBlockStyle}" VerticalAlignment="Center" Margin="28 0 0 0" TextWrapping="NoWrap" /> </Grid> <NavigationView Name="navigationView" ExpandedModeThresholdWidth="1280" Header="{x:Bind ((ContentControl)ViewModel.Selected).Content, Mode=OneWay}" IsBackButtonVisible="Visible" IsSettingsVisible="True" IsBackEnabled="{x:Bind ViewModel.IsBackEnabled, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.Selected, Mode=OneWay}"> <NavigationView.MenuItems> <NavigationViewItem x:Uid="Shell_Main" local:NavigationHelper.NavigateTo="TestProject.MainViewModel"> <NavigationViewItem.Icon> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/> </NavigationViewItem.Icon> </NavigationViewItem> <NavigationViewItem x:Uid="Shell_Test" local:NavigationHelper.NavigateTo="TestProject.TestViewModel"> <NavigationViewItem.Icon> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/> </NavigationViewItem.Icon> </NavigationViewItem> <NavigationViewItem x:Uid="Shell_WebView" local:NavigationHelper.NavigateTo="TestProject.WebViewModel"> <NavigationViewItem.Icon> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph=""/> </NavigationViewItem.Icon> </NavigationViewItem> </NavigationView.MenuItems> <NavigationView.HeaderTemplate> <DataTemplate> <Grid> <TextBlock Style="{ThemeResource TitleTextBlockStyle}" Text="{Binding}" /> </Grid> </DataTemplate> </NavigationView.HeaderTemplate> <mxi:Interaction.Behaviors> <local:NavigationViewHeaderBehavior DefaultHeader="{x:Bind ((ContentControl)ViewModel.Selected).Content, Mode=OneWay}"> <local:NavigationViewHeaderBehavior.DefaultHeaderTemplate> <DataTemplate> <Grid> <TextBlock Style="{ThemeResource TitleTextBlockStyle}" Text="{Binding}" /> </Grid> </DataTemplate> </local:NavigationViewHeaderBehavior.DefaultHeaderTemplate> </local:NavigationViewHeaderBehavior> </mxi:Interaction.Behaviors> <Grid Margin="{StaticResource NavigationViewPageContentMargin}"> <Frame Name="navigationFrame" /> </Grid> </NavigationView> </Grid> </Page> |
▶ ShellPage.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Windows.System; namespace TestProject; /// <summary> /// 쉘 페이지 /// </summary> public sealed partial class ShellPage : Page { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 뷰 모델 - ViewModel /// <summary> /// 뷰 모델 /// </summary> public ShellViewModel ViewModel { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - ShellPage(viewModel) /// <summary> /// 생성자 /// </summary> /// <param name="viewModel">뷰 모델</param> public ShellPage(ShellViewModel viewModel) { ViewModel = viewModel; InitializeComponent(); ViewModel.NavigationService.Frame = this.navigationFrame; ViewModel.NavigationViewService.Initialize(this.navigationView); this.appTitleBarTextBlock.Text = "AppDisplayName".GetLocalString(); App.MainWindow.ExtendsContentIntoTitleBar = true; App.MainWindow.SetTitleBar(this.appTitleBarGrid); App.MainWindow.Activated += MainWindow_Activated; Loaded += Page_Loaded; this.navigationView.DisplayModeChanged += navigationView_DisplayModeChanged; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Private ///////////////////////////////////////////////////////////////////////////////// Event #region 메인 윈도우 활성화시 처리하기 - MainWindow_Activated(sender, e) /// <summary> /// 메인 윈도우 활성화시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void MainWindow_Activated(object sender, WindowActivatedEventArgs e) { App.AppTitlebar = this.appTitleBarTextBlock; } #endregion #region 페이지 로드시 처리하기 - Page_Loaded(sender, e) /// <summary> /// 페이지 로드시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void Page_Loaded(object sender, RoutedEventArgs e) { TitleBarHelper.UpdateTitleBar(RequestedTheme); KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu)); KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.GoBack)); } #endregion #region 네비게이션 뷰 디스플레이 모드 변경시 처리하기 - navigationView_DisplayModeChanged(sender, e) /// <summary> /// 네비게이션 뷰 디스플레이 모드 변경시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void navigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs e) { this.appTitleBarGrid.Margin = new Thickness() { Left = sender.CompactPaneLength * (sender.DisplayMode == NavigationViewDisplayMode.Minimal ? 2 : 1), Top = this.appTitleBarGrid.Margin.Top, Right = this.appTitleBarGrid.Margin.Right, Bottom = this.appTitleBarGrid.Margin.Bottom }; } #endregion #region 키보드 엑셀레이터 호출시 처리하기 - keyboardAccelerator_Invoked(sender, e) /// <summary> /// 키보드 엑셀레이터 호출시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private static void keyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs e) { INavigationService navigationService = App.GetService<INavigationService>(); bool result = navigationService.GoBack(); e.Handled = result; } #endregion ///////////////////////////////////////////////////////////////////////////////// Function #region 키보드 엑셀레이터 만들기 - BuildKeyboardAccelerator(key, modifiers) /// <summary> /// 키보드 엑셀레이터 만들기 /// </summary> /// <param name="key">가상 키</param> /// <param name="modifiers">가상 키 수정자</param> /// <returns>키보드 엑셀레이터</returns> private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null) { KeyboardAccelerator keyboardAccelerator = new KeyboardAccelerator() { Key = key }; if(modifiers.HasValue) { keyboardAccelerator.Modifiers = modifiers.Value; } keyboardAccelerator.Invoked += keyboardAccelerator_Invoked; return keyboardAccelerator; } #endregion } |
▶ TestPage.xaml
1 2 3 4 5 6 7 8 |
<Page x:Class="TestProject.TestPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid Name="rootGrid" /> </Page> |
▶ TestPage.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 테스트 페이지 /// </summary> public sealed partial class TestPage : Page { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 뷰 모델 - ViewModel /// <summary> /// 뷰 모델 /// </summary> public TestViewModel ViewModel { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - TestPage /// <summary> /// 생성자 /// </summary> public TestPage() { ViewModel = App.GetService<TestViewModel>(); InitializeComponent(); } #endregion } |
▶ WebPage.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
<Page x:Class="TestProject.WebPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:TestProject" local:NavigationViewHeaderBehavior.HeaderMode="Never"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <WebView2 Name="webView" Grid.Row="0" Source="{x:Bind ViewModel.Source, Mode=OneWay}" /> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay}"> <ProgressRing IsActive="{x:Bind ViewModel.IsLoading, Mode=OneWay}" /> <TextBlock x:Uid="WebView_Loading" /> </StackPanel> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{x:Bind ViewModel.HasFailures, Mode=OneWay}"> <TextBlock x:Uid="WebView_FailedMessage" HorizontalAlignment="Center" TextWrapping="WrapWholeWords" /> <HyperlinkButton x:Uid="WebView_Reload" HorizontalAlignment="Center" Command="{x:Bind ViewModel.ReloadCommand}" /> </StackPanel> <Grid Grid.Row="1"> <StackPanel Orientation="Horizontal"> <Button x:Uid="BrowserBackButton" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}" Command="{x:Bind ViewModel.BrowserBackwardCommand, Mode=OneWay}"> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" /> </Button> <Button x:Uid="BrowserForwardButton" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}" Command="{x:Bind ViewModel.BrowserForwardCommand, Mode=OneWay}"> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" /> </Button> </StackPanel > <StackPanel HorizontalAlignment="Right" Orientation="Horizontal"> <Button x:Uid="ReloadButton" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}" Command="{x:Bind ViewModel.ReloadCommand}"> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" /> </Button> <Button x:Uid="OpenInBrowserButton" Margin="{StaticResource XSmallLeftTopRightBottomMargin}" Padding="{StaticResource XXSmallLeftTopRightBottomMargin}" Command="{x:Bind ViewModel.OpenInBrowserCommand}"> <FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" /> </Button> </StackPanel> </Grid> </Grid> </Page> |
▶ WebPage.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
using Microsoft.UI.Xaml.Controls; namespace TestProject; /// <summary> /// 웹 페이지 /// </summary> public sealed partial class WebPage : Page { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 뷰 모델 - ViewModel /// <summary> /// 뷰 모델 /// </summary> public WebViewModel ViewModel { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - WebPage() /// <summary> /// 생성자 /// </summary> public WebPage() { ViewModel = App.GetService<WebViewModel>(); InitializeComponent(); ViewModel.WebViewService.Initialize(this.webView); } #endregion } |
▶ MainWindow.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<we:WindowEx x:Class="TestProject.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:muxd="using:Microsoft.UI.Xaml.Media" xmlns:we="using:WinUIEx" PersistenceId="MainWindow" MinWidth="814" MinHeight="607" WindowState="Maximized"> <Window.SystemBackdrop> <muxd:DesktopAcrylicBackdrop /> </Window.SystemBackdrop> </we:WindowEx> |
▶ MainWindow.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
using Windows.UI.ViewManagement; using Microsoft.UI.Dispatching; namespace TestProject; /// <summary> /// 메인 윈도우 /// </summary> public sealed partial class MainWindow : WindowEx { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 디스패처 큐 /// </summary> private DispatcherQueue dispatcherQueue; /// <summary> /// UI 설정 /// </summary> private UISettings uiSettings; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - MainWindow() /// <summary> /// 생성자 /// </summary> public MainWindow() { InitializeComponent(); AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/WindowIcon.ico")); Content = null; Title = "AppDisplayName".GetLocalString(); this.dispatcherQueue = DispatcherQueue.GetForCurrentThread(); this.uiSettings = new UISettings(); uiSettings.ColorValuesChanged += uiSettings_ColorValuesChanged; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Private #region UI 설정 색상 값 변경시 처리하기 - uiSettings_ColorValuesChanged(sender, e) /// <summary> /// UI 설정 색상 값 변경시 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void uiSettings_ColorValuesChanged(UISettings sender, object e) { this.dispatcherQueue.TryEnqueue ( () => { TitleBarHelper.ApplySystemThemeToCaptionButtons(); } ); } #endregion } |
▶ App.xaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<Application x:Class="TestProject.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <ResourceDictionary Source="/Style/FontSizes.xaml" /> <ResourceDictionary Source="/Style/Thickness.xaml" /> <ResourceDictionary Source="/Style/TextBlock.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application> |
▶ App.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.UI.Xaml; namespace TestProject; /// <summary> /// 앱 /// </summary> public partial class App : Application { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 메인 윈도우 - MainWindow /// <summary> /// 메인 윈도우 /// </summary> public static WindowEx MainWindow { get; } = new MainWindow(); #endregion #region 앱 타이틀바 - AppTitlebar /// <summary> /// 앱 타이틀바 /// </summary> public static UIElement AppTitlebar { get; set; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Public #region 호스트 - Host /// <summary> /// 호스트 /// </summary> public IHost Host { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - App() /// <summary> /// 생성자 /// </summary> public App() { InitializeComponent(); Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() .UseContentRoot(AppContext.BaseDirectory) .ConfigureServices ( (context, serviceCollection) => { // 디폴트 활성화 핸들러 serviceCollection.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>(); // 기타 활성화 핸들러 serviceCollection.AddTransient<IActivationHandler, AppNotificationActivationHandler>(); // 서비스 serviceCollection.AddSingleton<IAppNotificationService, AppNotificationService>(); serviceCollection.AddSingleton<ILocalSettingService , LocalSettingService>(); serviceCollection.AddSingleton<IThemeSelectorService , ThemeSelectorService>(); serviceCollection.AddTransient<IWebViewService , WebViewService>(); serviceCollection.AddTransient<INavigationViewService , NavigationViewService>(); serviceCollection.AddSingleton<IActivationService, ActivationService>(); serviceCollection.AddSingleton<IPageService , PageService>(); serviceCollection.AddSingleton<INavigationService, NavigationService>(); // 핵심 서비스 serviceCollection.AddSingleton<IFileService, FileService>(); //뷰/뷰모 모델 serviceCollection.AddTransient<SettingViewModel>(); serviceCollection.AddTransient<SettingPage>(); serviceCollection.AddTransient<WebViewModel>(); serviceCollection.AddTransient<WebPage>(); serviceCollection.AddTransient<TestViewModel>(); serviceCollection.AddTransient<TestPage>(); serviceCollection.AddTransient<MainViewModel>(); serviceCollection.AddTransient<MainPage>(); serviceCollection.AddTransient<ShellPage>(); serviceCollection.AddTransient<ShellViewModel>(); // 구성 serviceCollection.Configure<LocalSettingOption>(context.Configuration.GetSection(nameof(LocalSettingOption))); } ) .Build(); App.GetService<IAppNotificationService>().Initialize(); UnhandledException += App_UnhandledException; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 서비스 구하기 - GetService<TService>() /// <summary> /// 서비스 구하기 /// </summary> /// <typeparam name="TService">서비스 타입</typeparam> /// <returns>서비스</returns> public static TService GetService<TService>() where TService : class { if((App.Current as App).Host.Services.GetService(typeof(TService)) is not TService service) { throw new ArgumentException($"{typeof(TService)} needs to be registered in ConfigureServices within App.xaml.cs."); } return service; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Protected #region 런칭시 처리하기 - OnLaunched(e) /// <summary> /// 런칭시 처리하기 /// </summary> /// <param name="e">이벤트 인자</param> protected async override void OnLaunched(LaunchActivatedEventArgs e) { base.OnLaunched(e); App.GetService<IAppNotificationService>().Show(string.Format("AppNotificationSamplePayload".GetLocalString(), AppContext.BaseDirectory)); await App.GetService<IActivationService>().ActivateAsync(e); } #endregion //////////////////////////////////////////////////////////////////////////////// Private #region 앱 미처리 예외 처리하기 - App_UnhandledException(sender, e) /// <summary> /// 앱 미처리 예외 처리하기 /// </summary> /// <param name="sender">이벤트 발생자</param> /// <param name="e">이벤트 인자</param> private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { // TODO : 적절하게 예외를 기록하고 처리한다. } #endregion } |