calculator/src/Calculator/App.xaml.cpp
Daniel Belcher f210290ddc - Avoid referencing project headers from precompiled headers.
Before this change, the pchs for CalcViewModel and Calculator project referenced project headers.  If those project headers (or any of their dependencies) were to change, then the pch would be recompiled, slowing local build times.

  By removing references to project headers, the pch will be compiled once and is resilient to changes in the project.  Now that project headers are explicit about their dependencies, when there is a change to a project header only the translation units referencing the modified header will need to be rebuilt.

- Manually tested by ensuring Calculator project builds locally.

@Microsoft/calculator-team
2019-02-14 18:20:25 -08:00

474 lines
18 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// App.xaml.cpp
// Implementation of the App class.
//
#include "pch.h"
#include "App.xaml.h"
#include "CalcViewModel/Common/TraceLogger.h"
#include "CalcViewModel/Common/Automation/NarratorNotifier.h"
#include "CalcViewModel/Common/AppResourceProvider.h"
#include "CalcViewModel/Common/LocalizationSettings.h"
#include "CalcViewModel/ViewState.h"
#include "Views/MainPage.xaml.h"
using namespace CalculatorApp;
using namespace CalculatorApp::Common;
using namespace CalculatorApp::Common::Automation;
using namespace Concurrency;
using namespace Microsoft::WRL;
using namespace Platform;
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Resources;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage;
using namespace Windows::System;
using namespace Windows::UI::Core;
using namespace Windows::UI::Popups;
using namespace Windows::UI::StartScreen;
using namespace Windows::UI::ViewManagement;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Media::Animation;
using namespace Windows::UI::Xaml::Navigation;
using namespace Windows::ApplicationModel::Activation;
namespace CalculatorApp
{
namespace ApplicationResourceKeys
{
StringReference AppMinWindowHeight(L"AppMinWindowHeight");
StringReference AppMinWindowWidth(L"AppMinWindowWidth");
}
}
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
App::App()
{
TraceLogger::GetInstance().LogAppLaunchStart();
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.
this->HighContrastAdjustment = ApplicationHighContrastAdjustment::None;
#if _DEBUG
this->DebugSettings->IsBindingTracingEnabled = true;
this->DebugSettings->BindingFailed += ref new BindingFailedEventHandler([](_In_ Object^ /*sender*/, _In_ BindingFailedEventArgs^ e)
{
if (IsDebuggerPresent())
{
::Platform::String^ errorMessage = e->Message;
__debugbreak();
}
});
#endif
}
bool App::m_isAnimationEnabled = true;
/// <summary>
/// Return True if animation is enabled by user setting.
/// </summary>
bool App::IsAnimationEnabled()
{
return App::m_isAnimationEnabled;
}
/// <summary>
/// Return the current application view state. The value
/// will match one of the constants in the ViewState namespace.
/// </summary>
String^ App::GetAppViewState()
{
String^ newViewState;
CoreWindow^ window = CoreWindow::GetForCurrentThread();
if ((window->Bounds.Width >= 560) && (window->Bounds.Height >= 356))
{
newViewState = ViewState::DockedView;
}
else
{
newViewState = ViewState::Snap;
}
return newViewState;
}
void App::AddWindowToMap(_In_ WindowFrameService^ frameService)
{
reader_writer_lock::scoped_lock lock(m_windowsMapLock);
m_secondaryWindows[frameService->GetViewId()] = frameService;
TraceLogger::GetInstance().UpdateWindowCount(m_secondaryWindows.size());
}
WindowFrameService^ App::GetWindowFromMap(int viewId)
{
reader_writer_lock::scoped_lock_read lock(m_windowsMapLock);
auto windowMapEntry = m_secondaryWindows.find(viewId);
if (windowMapEntry != m_secondaryWindows.end())
{
return windowMapEntry->second;
}
return nullptr;
}
void App::RemoveWindowFromMap(int viewId)
{
reader_writer_lock::scoped_lock lock(m_windowsMapLock);
auto iter = m_secondaryWindows.find(viewId);
assert(iter != m_secondaryWindows.end() && "Window does not exist in the list");
m_secondaryWindows.erase(viewId);
}
void App::RemoveWindow(_In_ WindowFrameService^ frameService)
{
// Shell does not allow killing the main window.
if (m_mainViewId != frameService->GetViewId())
{
HandleViewReleaseAndRemoveWindowFromMap(frameService);
}
}
task<void> App::HandleViewReleaseAndRemoveWindowFromMap(_In_ WindowFrameService^ frameService)
{
WeakReference weak(this);
// Unregister the event handler of the Main Page
auto frame = safe_cast<Frame^>(Window::Current->Content);
auto mainPage = safe_cast<MainPage^>(frame->Content);
mainPage->UnregisterEventHandlers();
return frameService->HandleViewRelease()
.then([weak, frameService]()
{
auto that = weak.Resolve<App>();
that->RemoveWindowFromMap(frameService->GetViewId());
}, task_continuation_context::use_arbitrary());
}
task<void> App::SetupJumpList()
{
try
{
auto calculatorOptions = NavCategoryGroup::CreateCalculatorCategory();
auto jumpList = co_await JumpList::LoadCurrentAsync();
jumpList->SystemGroupKind = JumpListSystemGroupKind::None;
jumpList->Items->Clear();
for (NavCategory^ option : calculatorOptions->Categories)
{
ViewMode mode = option->Mode;
auto item = JumpListItem::CreateWithArguments(((int)mode).ToString(), L"ms-resource:///Resources/"+ NavCategory::GetNameResourceKey(mode));
item->Description = L"ms-resource:///Resources/" + NavCategory::GetNameResourceKey(mode);
item->GroupName = L"ms-resource:///Resources/" + NavCategoryGroup::GetHeaderResourceKey(calculatorOptions->GroupType);
item->Logo = ref new Uri("ms-appx:///Assets/" + mode.ToString() + ".png");
jumpList->Items->Append(item);
}
co_await jumpList->SaveAsync();
}
catch(...) {}
}
void App::RemoveSecondaryWindow(_In_ WindowFrameService^ frameService)
{
// Shell does not allow killing the main window.
if (m_mainViewId != frameService->GetViewId())
{
RemoveWindowFromMap(frameService->GetViewId());
}
}
Frame^ App::CreateFrame()
{
auto frame = ref new Frame();
frame->FlowDirection = LocalizationService::GetInstance()->GetFlowDirection();
return frame;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
void App::OnLaunched(LaunchActivatedEventArgs^ args)
{
TraceLogger::GetInstance().LogWindowLaunched();
if (args->PrelaunchActivated)
{
// If the app got pre-launch activated, then save that state in a flag
m_preLaunched = true;
TraceLogger::GetInstance().LogAppPrelaunchedBySystem();
}
OnAppLaunch(args, args->Arguments);
}
void App::OnAppLaunch(IActivatedEventArgs^ args, String^ argument)
{
auto previousExecutionState = args->PreviousExecutionState;
TraceLogger::GetInstance().LogOnAppLaunch(previousExecutionState.ToString()->Data());
#if _DEBUG
if (IsDebuggerPresent())
{
DebugSettings->EnableFrameRateCounter = true;
}
#endif
auto userSettings = ref new Windows::UI::ViewManagement::UISettings();
m_isAnimationEnabled = userSettings->AnimationsEnabled;
args->SplashScreen->Dismissed += ref new TypedEventHandler<SplashScreen^, Object^>(this, &App::DismissedEventHandler);
auto rootFrame = dynamic_cast<Frame^>(Window::Current->Content);
WeakReference weak(this);
float minWindowWidth = static_cast<float>(static_cast<double>(this->Resources->Lookup(ApplicationResourceKeys::AppMinWindowWidth)));
float minWindowHeight = static_cast<float>(static_cast<double>(this->Resources->Lookup(ApplicationResourceKeys::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->HasKey(L"VeryFirstLaunch"))
{
localSettings->Values->Insert(ref new String(L"VeryFirstLaunch"), false);
appView->SetPreferredMinSize(minWindowSize);
appView->TryResizeView(minWindowSize);
}
else
{
appView->PreferredLaunchWindowingMode = ApplicationViewWindowingMode::Auto;
}
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == nullptr)
{
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^ e)
{
// 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(MainPage::typeid, argument))
{
// We couldn't navigate to the main page, kill the app so we have a good
// stack to debug
throw std::bad_exception();
}
SetMinWindowSizeAndActivate(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.
TraceLogger::GetInstance().LogAppLaunchStart();
// !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)
{
auto newCoreAppView = CoreApplication::CreateNewView();
newCoreAppView->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([args, argument, minWindowSize, weak]()
{
TraceLogger::GetInstance().LogNewWindowCreationBegin(ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread()));
auto that = weak.Resolve<App>();
if (that != nullptr)
{
auto rootFrame = App::CreateFrame();
SetMinWindowSizeAndActivate(rootFrame, minWindowSize);
if (!rootFrame->Navigate(MainPage::typeid, argument))
{
// We couldn't navigate to the main page, kill the app so we have a good
// stack to debug
throw std::bad_exception();
}
auto frameService = WindowFrameService::CreateNewWindowFrameService(rootFrame, true, weak);
that->AddWindowToMap(frameService);
auto dispatcher = CoreWindow::GetForCurrentThread()->Dispatcher;
auto safeFrameServiceCreation = std::make_shared<SafeFrameWindowCreation>(frameService, that);
int newWindowId = ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread());
ActivationViewSwitcher^ activationViewSwitcher;
auto activateEventArgs = dynamic_cast<IViewSwitcherProvider^>(args);
if (activateEventArgs != nullptr)
{
activationViewSwitcher = activateEventArgs->ViewSwitcher;
}
if (activationViewSwitcher != nullptr)
{
activationViewSwitcher->ShowAsStandaloneAsync(newWindowId, ViewSizePreference::Default);
safeFrameServiceCreation->SetOperationSuccess(true);
}
else
{
auto activatedEventArgs = dynamic_cast<IApplicationViewActivatedEventArgs^>(args);
if ((activatedEventArgs != nullptr) && (activatedEventArgs->CurrentlyShownApplicationViewId != 0))
{
create_task(ApplicationViewSwitcher::TryShowAsStandaloneAsync(
frameService->GetViewId(),
ViewSizePreference::Default,
activatedEventArgs->CurrentlyShownApplicationViewId,
ViewSizePreference::Default
))
.then([safeFrameServiceCreation](bool viewShown)
{
// SafeFrameServiceCreation is used to automatically remove the frame
// from the list of frames if something goes bad.
safeFrameServiceCreation->SetOperationSuccess(viewShown);
}, task_continuation_context::use_current());
}
}
}
TraceLogger::GetInstance().LogNewWindowCreationEnd(ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread()));
}));
}
else
{
TraceLogger::GetInstance().LogNewWindowCreationBegin(ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread()));
ActivationViewSwitcher^ activationViewSwitcher;
auto activateEventArgs = dynamic_cast<IViewSwitcherProvider^>(args);
if (activateEventArgs != nullptr)
{
activationViewSwitcher = activateEventArgs->ViewSwitcher;
}
if (activationViewSwitcher != nullptr)
{
activationViewSwitcher->ShowAsStandaloneAsync(ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread()), ViewSizePreference::Default);
TraceLogger::GetInstance().LogNewWindowCreationEnd(ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread()));
TraceLogger::GetInstance().LogPrelaunchedAppActivatedByUser();
}
else
{
TraceLogger::GetInstance().LogError(L"Null_ActivationViewSwitcher");
}
}
// Set the preLaunched flag to false
m_preLaunched = false;
}
else // for touch devices
{
if (rootFrame->Content == nullptr)
{
// 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(MainPage::typeid, argument))
{
// We couldn't navigate to the main page,
// kill the app so we have a good stack to debug
throw std::bad_exception();
}
}
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;
auto activateEventArgs = dynamic_cast<IViewSwitcherProvider^>(args);
if (activateEventArgs != nullptr)
{
activationViewSwitcher = activateEventArgs->ViewSwitcher;
}
if (activationViewSwitcher != nullptr)
{
auto viewId = safe_cast<IApplicationViewActivatedEventArgs^>(args)->CurrentlyShownApplicationViewId;
if (viewId != 0)
{
activationViewSwitcher->ShowAsStandaloneAsync(viewId);
}
}
}
// Ensure the current window is active
Window::Current->Activate();
}
}
}
void App::SetMinWindowSizeAndActivate(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;
Window::Current->Activate();
}
void App::RegisterDependencyProperties()
{
NarratorNotifier::RegisterDependencyProperties();
}
void App::OnActivated(IActivatedEventArgs^ args)
{
if (args->Kind == ActivationKind::Protocol)
{
TraceLogger::GetInstance().LogWindowActivated();
// We currently don't pass the uri as an argument,
// and handle any protocol launch as a normal app launch.
OnAppLaunch(args, nullptr);
}
}
void App::DismissedEventHandler(SplashScreen^ sender, Object^ e)
{
SetupJumpList();
}
float App::GetAppWindowHeight()
{
CoreWindow^ window = CoreWindow::GetForCurrentThread();
return window->Bounds.Height;
}