// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // // App.xaml.h // Declaration of the App class. // using CalculatorApp.ViewModel.Common; using CalculatorApp.ViewModel.Common.Automation; using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Core; using Windows.Foundation; using Windows.Storage; using Windows.UI.Core; using Windows.UI.StartScreen; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace CalculatorApp { namespace ApplicationResourceKeys { static public partial class Globals { public static readonly string AppMinWindowHeight = "AppMinWindowHeight"; public static readonly string AppMinWindowWidth = "AppMinWindowWidth"; } } /// /// Provides application-specific behavior to supplement the default Application class. /// sealed partial class App { /// /// Initializes the singleton application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). /// public App() { InitializeComponent(); m_preLaunched = false; RegisterDependencyProperties(); // TODO: MSFT 14645325: Set this directly from XAML. // Currently this is bugged so the property is only respected from code-behind. HighContrastAdjustment = ApplicationHighContrastAdjustment.None; Suspending += OnSuspending; #if DEBUG DebugSettings.IsBindingTracingEnabled = true; DebugSettings.BindingFailed += (sender, args) => { if (Debugger.IsAttached) { string errorMessage = args.Message; Debugger.Break(); } }; #endif } /// /// Invoked when the application is launched normally by the end user. Other entry points /// will be used when the application is launched to open a specific file, to display /// search results, and so forth. /// /// Details about the launch request and process. protected override void OnLaunched(LaunchActivatedEventArgs args) { if (args.PrelaunchActivated) { // If the app got pre-launch activated, then save that state in a flag m_preLaunched = true; } NavCategory.InitializeCategoryManifest(args.User); OnAppLaunch(args, args.Arguments); } protected override void OnActivated(IActivatedEventArgs args) { if (args.Kind == ActivationKind.Protocol) { // We currently don't pass the uri as an argument, // and handle any protocol launch as a normal app launch. OnAppLaunch(args, null); } } internal void RemoveWindow(WindowFrameService frameService) { // Shell does not allow killing the main window. if (m_mainViewId != frameService.GetViewId()) { _ = HandleViewReleaseAndRemoveWindowFromMap(frameService); } } internal void RemoveSecondaryWindow(WindowFrameService frameService) { // Shell does not allow killing the main window. if (m_mainViewId != frameService.GetViewId()) { RemoveWindowFromMap(frameService.GetViewId()); } } private static Frame CreateFrame() { var frame = new Frame(); frame.FlowDirection = LocalizationService.GetInstance().GetFlowDirection(); return frame; } private static void SetMinWindowSizeAndThemeAndActivate(Frame rootFrame, Size minWindowSize) { // SetPreferredMinSize should always be called before Window.Activate ApplicationView appView = ApplicationView.GetForCurrentView(); appView.SetPreferredMinSize(minWindowSize); // Place the frame in the current Window Window.Current.Content = rootFrame; CalculatorApp.Utils.ThemeHelper.InitializeAppTheme(); Window.Current.Activate(); } private void OnAppLaunch(IActivatedEventArgs args, string argument) { // Uncomment the following lines to display frame-rate and per-frame CPU usage info. //#if _DEBUG // if (IsDebuggerPresent()) // { // DebugSettings->EnableFrameRateCounter = true; // } //#endif args.SplashScreen.Dismissed += DismissedEventHandler; var rootFrame = (Window.Current.Content as Frame); WeakReference weak = new WeakReference(this); float minWindowWidth = (float)((double)Resources[ApplicationResourceKeys.Globals.AppMinWindowWidth]); float minWindowHeight = (float)((double)Resources[ApplicationResourceKeys.Globals.AppMinWindowHeight]); Size minWindowSize = SizeHelper.FromDimensions(minWindowWidth, minWindowHeight); ApplicationView appView = ApplicationView.GetForCurrentView(); ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; // For very first launch, set the size of the calc as size of the default standard mode if (!localSettings.Values.ContainsKey("VeryFirstLaunch")) { localSettings.Values["VeryFirstLaunch"] = false; appView.SetPreferredMinSize(minWindowSize); appView.TryResizeView(minWindowSize); } else { ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.Auto; } // Do not repeat app initialization when the Window already has content, // just ensure that the window is active if (rootFrame == null) { if (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons")) // PC Family { // Disable the system view activation policy during the first launch of the app // only for PC family devices and not for phone family devices try { ApplicationViewSwitcher.DisableSystemViewActivationPolicy(); } catch (Exception) { // Log that DisableSystemViewActionPolicy didn't work } } // Create a Frame to act as the navigation context rootFrame = App.CreateFrame(); // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter if (!rootFrame.Navigate(typeof(MainPage), argument)) { // We couldn't navigate to the main page, kill the app so we have a good // stack to debug throw new SystemException(); } SetMinWindowSizeAndThemeAndActivate(rootFrame, minWindowSize); m_mainViewId = ApplicationView.GetForCurrentView().Id; AddWindowToMap(WindowFrameService.CreateNewWindowFrameService(rootFrame, false, weak)); } else { // For first launch, LaunchStart is logged in constructor, this is for subsequent launches. // !Phone check is required because even in continuum mode user interaction mode is Mouse not Touch if ((UIViewSettings.GetForCurrentView().UserInteractionMode == UserInteractionMode.Mouse) && (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))) { // If the pre-launch hasn't happened then allow for the new window/view creation if (!m_preLaunched) { var newCoreAppView = CoreApplication.CreateNewView(); _ = newCoreAppView.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, async () => { var that = weak.Target as App; if (that != null) { var newRootFrame = App.CreateFrame(); SetMinWindowSizeAndThemeAndActivate(newRootFrame, minWindowSize); if (!newRootFrame.Navigate(typeof(MainPage), argument)) { // We couldn't navigate to the main page, kill the app so we have a good // stack to debug throw new SystemException(); } var frameService = WindowFrameService.CreateNewWindowFrameService(newRootFrame, true, weak); that.AddWindowToMap(frameService); var dispatcher = CoreWindow.GetForCurrentThread().Dispatcher; // CSHARP_MIGRATION_ANNOTATION: // class SafeFrameWindowCreation is being interpreted into a IDisposable class // in order to enhance its RAII capability that was written in C++/CX using (var safeFrameServiceCreation = new SafeFrameWindowCreation(frameService, that)) { int newWindowId = ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()); ActivationViewSwitcher activationViewSwitcher = null; var activateEventArgs = (args as IViewSwitcherProvider); if (activateEventArgs != null) { activationViewSwitcher = activateEventArgs.ViewSwitcher; } if (activationViewSwitcher != null) { _ = activationViewSwitcher.ShowAsStandaloneAsync(newWindowId, ViewSizePreference.Default); safeFrameServiceCreation.SetOperationSuccess(true); } else { var activatedEventArgs = (args as IApplicationViewActivatedEventArgs); if ((activatedEventArgs != null) && (activatedEventArgs.CurrentlyShownApplicationViewId != 0)) { // CSHARP_MIGRATION_ANNOTATION: // here we don't use ContinueWith() to interpret origin code because we would like to // pursue the design of class SafeFrameWindowCreate whichi was using RAII to ensure // some states get handled properly when its instance is being destructed. // // To achieve that, SafeFrameWindowCreate has been reinterpreted using IDisposable // pattern, which forces we use below way to keep async works being controlled within // a same code block. var viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync( frameService.GetViewId(), ViewSizePreference.Default, activatedEventArgs.CurrentlyShownApplicationViewId, ViewSizePreference.Default); // SafeFrameServiceCreation is used to automatically remove the frame // from the list of frames if something goes bad. safeFrameServiceCreation.SetOperationSuccess(viewShown); } } } } }); } else { ActivationViewSwitcher activationViewSwitcher = null; var activateEventArgs = (args as IViewSwitcherProvider); if (activateEventArgs != null) { activationViewSwitcher = activateEventArgs.ViewSwitcher; } if (activationViewSwitcher != null) { _ = activationViewSwitcher.ShowAsStandaloneAsync( ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()), ViewSizePreference.Default); } else { TraceLogger.GetInstance().LogError(ViewMode.None, "App.OnAppLaunch", "Null_ActivationViewSwitcher"); } } // Set the preLaunched flag to false m_preLaunched = false; } else // for touch devices { if (rootFrame.Content == null) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter if (!rootFrame.Navigate(typeof(MainPage), argument)) { // We couldn't navigate to the main page, // kill the app so we have a good stack to debug throw new SystemException(); } } if (ApplicationView.GetForCurrentView().ViewMode != ApplicationViewMode.CompactOverlay) { if (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons")) { // for tablet mode: since system view activation policy is disabled so do ShowAsStandaloneAsync if activationViewSwitcher exists in // activationArgs ActivationViewSwitcher activationViewSwitcher = null; var activateEventArgs = (args as IViewSwitcherProvider); if (activateEventArgs != null) { activationViewSwitcher = activateEventArgs.ViewSwitcher; } if (activationViewSwitcher != null) { var viewId = (args as IApplicationViewActivatedEventArgs).CurrentlyShownApplicationViewId; if (viewId != 0) { _ = activationViewSwitcher.ShowAsStandaloneAsync(viewId); } } } // Ensure the current window is active Window.Current.Activate(); } } } } private void DismissedEventHandler(SplashScreen sender, object e) { _ = SetupJumpList(); } private void RegisterDependencyProperties() { NarratorNotifier.RegisterDependencyProperties(); } private void OnSuspending(object sender, SuspendingEventArgs args) { TraceLogger.GetInstance().LogButtonUsage(); } private sealed class SafeFrameWindowCreation : IDisposable { public SafeFrameWindowCreation(WindowFrameService frameService, App parent) { m_frameService = frameService; m_frameOpenedInWindow = false; m_parent = parent; } public void SetOperationSuccess(bool success) { m_frameOpenedInWindow = success; } public void Dispose() { if (!m_frameOpenedInWindow) { // Close the window as the navigation to the window didn't succeed // and this is not visible to the user. m_parent.RemoveWindowFromMap(m_frameService.GetViewId()); } GC.SuppressFinalize(this); } ~SafeFrameWindowCreation() { Dispose(); } private WindowFrameService m_frameService; private bool m_frameOpenedInWindow; private App m_parent; }; private async Task SetupJumpList() { try { var calculatorOptions = NavCategoryGroup.CreateCalculatorCategory(); var jumpList = await JumpList.LoadCurrentAsync(); jumpList.SystemGroupKind = JumpListSystemGroupKind.None; jumpList.Items.Clear(); foreach (NavCategory option in calculatorOptions.Categories) { if (!option.IsEnabled) { continue; } ViewMode mode = option.Mode; var item = JumpListItem.CreateWithArguments(((int)mode).ToString(), "ms-resource:///Resources/" + NavCategory.GetNameResourceKey(mode)); item.Description = "ms-resource:///Resources/" + NavCategory.GetNameResourceKey(mode); item.Logo = new Uri("ms-appx:///Assets/" + mode.ToString() + ".png"); jumpList.Items.Add(item); } await jumpList.SaveAsync(); } catch { } } private async Task HandleViewReleaseAndRemoveWindowFromMap(WindowFrameService frameService) { WeakReference weak = new WeakReference(this); // Unregister the event handler of the Main Page var frame = (Window.Current.Content as Frame); var mainPage = (frame.Content as MainPage); mainPage.UnregisterEventHandlers(); await frameService.HandleViewRelease(); await Task.Run(() => { var that = weak.Target as App; that.RemoveWindowFromMap(frameService.GetViewId()); }).ConfigureAwait(false /* task_continuation_context::use_arbitrary() */); } private void AddWindowToMap(WindowFrameService frameService) { m_windowsMapLock.EnterWriteLock(); try { m_secondaryWindows[frameService.GetViewId()] = frameService; TraceLogger.GetInstance().UpdateWindowCount(Convert.ToUInt64(m_secondaryWindows.Count)); } finally { m_windowsMapLock.ExitWriteLock(); } } private WindowFrameService GetWindowFromMap(int viewId) { m_windowsMapLock.EnterReadLock(); try { if (m_secondaryWindows.TryGetValue(viewId, out var windowMapEntry)) { return windowMapEntry; } else { return null; } } finally { m_windowsMapLock.ExitReadLock(); } } private void RemoveWindowFromMap(int viewId) { m_windowsMapLock.EnterWriteLock(); try { bool removed = m_secondaryWindows.Remove(viewId); Debug.Assert(removed != false, "Window does not exist in the list"); } finally { m_windowsMapLock.ExitWriteLock(); } } private readonly ReaderWriterLockSlim m_windowsMapLock = new ReaderWriterLockSlim(); private Dictionary m_secondaryWindows = new Dictionary(); private int m_mainViewId; private bool m_preLaunched; } }