Migrate ApplicationViewModel to C# (#2353)
Some checks failed
Windows Calculator Continuous Integration Pipeline / Define builds (push) Has been cancelled
Windows Calculator Continuous Integration Pipeline / Build (push) Has been cancelled
Windows Calculator Continuous Integration Pipeline / Run unit tests (push) Has been cancelled
Windows Calculator Continuous Integration Pipeline / Run UI tests (x64) (push) Has been cancelled

* migrate ApplicationViewModel to C#

* fix aot mode

* save ctor
This commit is contained in:
Tian L. 2025-07-09 16:39:03 +08:00 committed by GitHub
commit 5b21e4c58b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 691 additions and 482 deletions

View file

@ -1,293 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#include "ApplicationViewModel.h"
#include "Common/TraceLogger.h"
#include "Common/AppResourceProvider.h"
#include "StandardCalculatorViewModel.h"
#include "DateCalculatorViewModel.h"
#include "DataLoaders/CurrencyHttpClient.h"
#include "DataLoaders/CurrencyDataLoader.h"
#include "DataLoaders/UnitConverterDataLoader.h"
using namespace CalculatorApp;
using namespace CalculatorApp::ViewModel::Common;
using namespace CalculatorApp::ViewModel::DataLoaders;
using namespace CalculatorApp::ViewModel;
using namespace CalculationManager;
using namespace Platform;
using namespace Platform::Collections;
using namespace Windows::System;
using namespace Windows::Storage;
using namespace Utils;
using namespace Windows::Foundation::Collections;
using namespace Windows::Globalization;
using namespace Windows::UI::ViewManagement;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml::Automation;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::Foundation;
using namespace Concurrency;
namespace
{
StringReference CategoriesPropertyName(L"Categories");
StringReference ClearMemoryVisibilityPropertyName(L"ClearMemoryVisibility");
}
ApplicationViewModel::ApplicationViewModel()
: m_CalculatorViewModel(nullptr)
, m_DateCalcViewModel(nullptr)
, m_GraphingCalcViewModel(nullptr)
, m_ConverterViewModel(nullptr)
, m_PreviousMode(ViewMode::None)
, m_mode(ViewMode::None)
, m_categories(nullptr)
{
SetMenuCategories();
}
void ApplicationViewModel::Mode::set(ViewMode value)
{
if (m_mode != value)
{
PreviousMode = m_mode;
m_mode = value;
SetDisplayNormalAlwaysOnTopOption();
OnModeChanged();
RaisePropertyChanged(ModePropertyName);
}
}
void ApplicationViewModel::Categories::set(IObservableVector<NavCategoryGroup ^> ^ value)
{
if (m_categories != value)
{
m_categories = value;
RaisePropertyChanged(CategoriesPropertyName);
}
}
CalculatorApp::ViewModel::Snapshot::ApplicationSnapshot ^ ApplicationViewModel::Snapshot::get()
{
auto snapshot = ref new CalculatorApp::ViewModel::Snapshot::ApplicationSnapshot();
snapshot->Mode = static_cast<int>(Mode);
if (m_CalculatorViewModel != nullptr && m_mode == ViewMode::Standard)
{
snapshot->StandardCalculator = m_CalculatorViewModel->Snapshot;
}
return snapshot;
}
void ApplicationViewModel::Initialize(ViewMode mode)
{
if (!NavCategoryStates::IsValidViewMode(mode) || !NavCategoryStates::IsViewModeEnabled(mode))
{
mode = ViewMode::Standard;
}
try
{
Mode = mode;
}
catch (const std::exception& e)
{
TraceLogger::GetInstance()->LogStandardException(mode, __FUNCTIONW__, e);
if (!TryRecoverFromNavigationModeFailure())
{
// Could not navigate to standard mode either.
// Throw the original exception so we have a good stack to debug.
throw;
}
}
catch (Exception ^ e)
{
TraceLogger::GetInstance()->LogPlatformException(mode, __FUNCTIONW__, e);
if (!TryRecoverFromNavigationModeFailure())
{
// Could not navigate to standard mode either.
// Throw the original exception so we have a good stack to debug.
throw;
}
}
}
void ApplicationViewModel::RestoreFromSnapshot(CalculatorApp::ViewModel::Snapshot::ApplicationSnapshot ^ snapshot)
{
Mode = static_cast<ViewMode>(snapshot->Mode);
if (snapshot->StandardCalculator != nullptr)
{
m_CalculatorViewModel->Snapshot = snapshot->StandardCalculator;
}
}
bool ApplicationViewModel::TryRecoverFromNavigationModeFailure()
{
// Here we are simply trying to recover from being unable to navigate to a mode.
// Try falling back to standard mode and if there are *any* exceptions, we should
// fail because something is seriously wrong.
try
{
Mode = ViewMode::Standard;
return true;
}
catch (...)
{
return false;
}
}
void ApplicationViewModel::OnModeChanged()
{
assert(NavCategoryStates::IsValidViewMode(m_mode));
if (NavCategory::IsCalculatorViewMode(m_mode))
{
if (!m_CalculatorViewModel)
{
m_CalculatorViewModel = ref new StandardCalculatorViewModel();
}
m_CalculatorViewModel->SetCalculatorType(m_mode);
}
else if (NavCategory::IsGraphingCalculatorViewMode(m_mode))
{
if (!m_GraphingCalcViewModel)
{
m_GraphingCalcViewModel = ref new GraphingCalculatorViewModel();
}
}
else if (NavCategory::IsDateCalculatorViewMode(m_mode))
{
if (!m_DateCalcViewModel)
{
m_DateCalcViewModel = ref new DateCalculatorViewModel();
}
}
else if (NavCategory::IsConverterViewMode(m_mode))
{
if (!m_ConverterViewModel)
{
auto dataLoader = std::make_shared<UnitConverterDataLoader>(ref new GeographicRegion());
m_ConverterViewModel =
ref new UnitConverterViewModel(std::make_shared<UnitConversionManager::UnitConverter>(dataLoader, std::make_shared<CurrencyDataLoader>()));
}
m_ConverterViewModel->Mode = m_mode;
}
auto resProvider = AppResourceProvider::GetInstance();
CategoryName = resProvider->GetResourceString(NavCategoryStates::GetNameResourceKey(m_mode));
// Cast mode to an int in order to save it to app data.
// Save the changed mode, so that the new window launches in this mode.
// Don't save until after we have adjusted to the new mode, so we don't save a mode that fails to load.
ApplicationData::Current->LocalSettings->Values->Insert(ModePropertyName, NavCategoryStates::Serialize(m_mode));
// Log ModeChange event when not first launch, log WindowCreated on first launch
if (NavCategoryStates::IsValidViewMode(m_PreviousMode))
{
TraceLogger::GetInstance()->LogModeChange(m_mode);
}
else
{
TraceLogger::GetInstance()->LogWindowCreated(m_mode, ApplicationView::GetApplicationViewIdForWindow(CoreWindow::GetForCurrentThread()));
}
RaisePropertyChanged(ClearMemoryVisibilityPropertyName);
}
void ApplicationViewModel::OnCopyCommand(Object ^ parameter)
{
if (NavCategory::IsConverterViewMode(m_mode))
{
ConverterViewModel->OnCopyCommand(parameter);
}
else if (NavCategory::IsDateCalculatorViewMode(m_mode))
{
DateCalcViewModel->OnCopyCommand(parameter);
}
else if (NavCategory::IsCalculatorViewMode(m_mode))
{
CalculatorViewModel->OnCopyCommand(parameter);
}
}
void ApplicationViewModel::OnPasteCommand(Object ^ parameter)
{
if (NavCategory::IsConverterViewMode(m_mode))
{
ConverterViewModel->OnPasteCommand(parameter);
}
else if (NavCategory::IsCalculatorViewMode(m_mode))
{
CalculatorViewModel->OnPasteCommand(parameter);
}
}
void ApplicationViewModel::SetMenuCategories()
{
// Use the Categories property instead of the backing variable
// because we want to take advantage of binding updates and
// property setter logic.
Categories = NavCategoryStates::CreateMenuOptions();
}
void ApplicationViewModel::ToggleAlwaysOnTop(float width, float height)
{
HandleToggleAlwaysOnTop(width, height);
}
#pragma optimize("", off)
task<void> ApplicationViewModel::HandleToggleAlwaysOnTop(float width, float height)
{
if (ApplicationView::GetForCurrentView()->ViewMode == ApplicationViewMode::CompactOverlay)
{
ApplicationDataContainer ^ localSettings = ApplicationData::Current->LocalSettings;
localSettings->Values->Insert(WidthLocalSettings, width);
localSettings->Values->Insert(HeightLocalSettings, height);
bool success = co_await ApplicationView::GetForCurrentView()->TryEnterViewModeAsync(ApplicationViewMode::Default);
CalculatorViewModel->HistoryVM->AreHistoryShortcutsEnabled = success;
CalculatorViewModel->IsAlwaysOnTop = !success;
IsAlwaysOnTop = !success;
}
else
{
ApplicationDataContainer ^ localSettings = ApplicationData::Current->LocalSettings;
ViewModePreferences ^ compactOptions = ViewModePreferences::CreateDefault(ApplicationViewMode::CompactOverlay);
if (!localSettings->Values->GetView()->HasKey(LaunchedLocalSettings))
{
compactOptions->CustomSize = Size(320, 394);
localSettings->Values->Insert(LaunchedLocalSettings, true);
}
else
{
if (localSettings->Values->GetView()->HasKey(WidthLocalSettings) && localSettings->Values->GetView()->HasKey(HeightLocalSettings))
{
float oldWidth = safe_cast<IPropertyValue ^>(localSettings->Values->GetView()->Lookup(WidthLocalSettings))->GetSingle();
float oldHeight = safe_cast<IPropertyValue ^>(localSettings->Values->GetView()->Lookup(HeightLocalSettings))->GetSingle();
compactOptions->CustomSize = Size(oldWidth, oldHeight);
}
else
{
compactOptions->CustomSize = Size(320, 394);
}
}
bool success = co_await ApplicationView::GetForCurrentView()->TryEnterViewModeAsync(ApplicationViewMode::CompactOverlay, compactOptions);
CalculatorViewModel->HistoryVM->AreHistoryShortcutsEnabled = !success;
CalculatorViewModel->IsAlwaysOnTop = success;
IsAlwaysOnTop = success;
}
SetDisplayNormalAlwaysOnTopOption();
};
#pragma optimize("", on)
void ApplicationViewModel::SetDisplayNormalAlwaysOnTopOption()
{
DisplayNormalAlwaysOnTopOption =
m_mode == ViewMode::Standard && ApplicationView::GetForCurrentView()->IsViewModeSupported(ApplicationViewMode::CompactOverlay) && !IsAlwaysOnTop;
}

View file

@ -1,122 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "Snapshots.h"
#include "StandardCalculatorViewModel.h"
#include "DateCalculatorViewModel.h"
#include "GraphingCalculator/GraphingCalculatorViewModel.h"
#include "UnitConverterViewModel.h"
namespace CalculatorApp
{
namespace ViewModel
{
[Windows::UI::Xaml::Data::Bindable] public ref class ApplicationViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
{
public:
ApplicationViewModel();
void Initialize(CalculatorApp::ViewModel::Common::ViewMode mode); // Use for first init, use deserialize for rehydration
void RestoreFromSnapshot(CalculatorApp::ViewModel::Snapshot::ApplicationSnapshot^ snapshot);
OBSERVABLE_OBJECT();
OBSERVABLE_PROPERTY_RW(StandardCalculatorViewModel ^, CalculatorViewModel);
OBSERVABLE_PROPERTY_RW(DateCalculatorViewModel ^, DateCalcViewModel);
OBSERVABLE_PROPERTY_RW(GraphingCalculatorViewModel ^, GraphingCalcViewModel);
OBSERVABLE_PROPERTY_RW(UnitConverterViewModel ^, ConverterViewModel);
OBSERVABLE_PROPERTY_RW(CalculatorApp::ViewModel::Common::ViewMode, PreviousMode);
OBSERVABLE_PROPERTY_R(bool, IsAlwaysOnTop);
OBSERVABLE_NAMED_PROPERTY_RW(Platform::String ^, CategoryName);
// Indicates whether calculator is currently in standard mode _and_ supports CompactOverlay _and_ is not in Always-on-Top mode
OBSERVABLE_PROPERTY_R(bool, DisplayNormalAlwaysOnTopOption);
COMMAND_FOR_METHOD(CopyCommand, ApplicationViewModel::OnCopyCommand);
COMMAND_FOR_METHOD(PasteCommand, ApplicationViewModel::OnPasteCommand);
property CalculatorApp::ViewModel::Common::ViewMode Mode
{
CalculatorApp::ViewModel::Common::ViewMode get()
{
return m_mode;
}
void set(CalculatorApp::ViewModel::Common::ViewMode value);
}
static property Platform::String^ ModePropertyName
{
Platform::String^ get()
{
return Platform::StringReference(L"Mode");
}
}
property Windows::Foundation::Collections::IObservableVector<CalculatorApp::ViewModel::Common::NavCategoryGroup^>^ Categories
{
Windows::Foundation::Collections::IObservableVector<CalculatorApp::ViewModel::Common::NavCategoryGroup^>^ get()
{
return m_categories;
}
void set(Windows::Foundation::Collections::IObservableVector<CalculatorApp::ViewModel::Common::NavCategoryGroup^>^ value);
}
property Windows::UI::Xaml::Visibility ClearMemoryVisibility
{
Windows::UI::Xaml::Visibility get()
{
return CalculatorApp::ViewModel::Common::NavCategory::IsCalculatorViewMode(Mode) ? Windows::UI::Xaml::Visibility::Visible
: Windows::UI::Xaml::Visibility::Collapsed;
}
}
property CalculatorApp::ViewModel::Snapshot::ApplicationSnapshot ^ Snapshot
{
CalculatorApp::ViewModel::Snapshot::ApplicationSnapshot ^ get();
}
static property Platform::String ^ LaunchedLocalSettings
{
Platform::String ^ get()
{
return Platform::StringReference(L"calculatorAlwaysOnTopLaunched");
}
}
static property Platform::String ^ WidthLocalSettings
{
Platform::String ^ get()
{
return Platform::StringReference(L"calculatorAlwaysOnTopLastWidth");
}
}
static property Platform::String ^ HeightLocalSettings
{
Platform::String ^ get()
{
return Platform::StringReference(L"calculatorAlwaysOnTopLastHeight");
}
}
void ToggleAlwaysOnTop(float width, float height);
private:
bool TryRecoverFromNavigationModeFailure();
void OnModeChanged();
void OnCopyCommand(Platform::Object ^ parameter);
void OnPasteCommand(Platform::Object ^ parameter);
void SetMenuCategories();
CalculatorApp::ViewModel::Common::ViewMode m_mode;
Windows::Foundation::Collections::IObservableVector<CalculatorApp::ViewModel::Common::NavCategoryGroup ^> ^ m_categories;
Concurrency::task<void> HandleToggleAlwaysOnTop(float width, float height);
void SetDisplayNormalAlwaysOnTopOption();
};
}
}

View file

@ -314,7 +314,6 @@
<ResourceCompile Include="CalcViewModel.rc" PreprocessorDefinitions="%(PreprocessorDefinitions);APP_VERSION_MAJOR=$(Version.Split(`.`)[0]);APP_VERSION_MINOR=$(Version.Split(`.`)[1]);APP_VERSION_BUILD=$(Version.Split(`.`)[2]);APP_VERSION_REVISION=$(Version.Split(`.`)[3])" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ApplicationViewModel.h" />
<ClInclude Include="Common\AppResourceProvider.h" />
<ClInclude Include="Common\Automation\NarratorAnnouncement.h" />
<ClInclude Include="Common\Automation\NarratorNotifier.h" />
@ -359,7 +358,6 @@
<ClInclude Include="UnitConverterViewModel.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ApplicationViewModel.cpp" />
<ClCompile Include="Common\AppResourceProvider.cpp" />
<ClCompile Include="Common\Automation\NarratorAnnouncement.cpp" />
<ClCompile Include="Common\Automation\NarratorNotifier.cpp" />

View file

@ -82,7 +82,6 @@
<ClCompile Include="GraphingCalculator\GraphingSettingsViewModel.cpp">
<Filter>GraphingCalculator</Filter>
</ClCompile>
<ClCompile Include="ApplicationViewModel.cpp" />
<ClCompile Include="DateCalculatorViewModel.cpp" />
<ClCompile Include="HistoryItemViewModel.cpp" />
<ClCompile Include="HistoryViewModel.cpp" />
@ -189,7 +188,6 @@
<ClInclude Include="GraphingCalculator\VariableViewModel.h">
<Filter>GraphingCalculator</Filter>
</ClInclude>
<ClInclude Include="ApplicationViewModel.h" />
<ClInclude Include="DateCalculatorViewModel.h" />
<ClInclude Include="GraphingCalculatorEnums.h" />
<ClInclude Include="HistoryItemViewModel.h" />

View file

@ -306,7 +306,7 @@ void NavCategoryStates::SetCurrentUser(Platform::String^ userId)
CurrentUserId = userId;
}
IObservableVector<NavCategoryGroup ^> ^ NavCategoryStates::CreateMenuOptions()
IVector<NavCategoryGroup ^> ^ NavCategoryStates::CreateMenuOptions()
{
auto menuOptions = ref new Vector<NavCategoryGroup ^>();
menuOptions->Append(CreateCalculatorCategoryGroup());

View file

@ -146,14 +146,14 @@ namespace CalculatorApp::ViewModel
OBSERVABLE_PROPERTY_R(Platform::String ^, Name);
OBSERVABLE_PROPERTY_R(Platform::String ^, AutomationName);
OBSERVABLE_PROPERTY_R(CategoryGroupType, GroupType);
OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector<NavCategory ^> ^, Categories);
OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IVector<NavCategory ^> ^, Categories);
};
public ref class NavCategoryStates sealed
{
public:
static void SetCurrentUser(Platform::String^ user);
static Windows::Foundation::Collections::IObservableVector<NavCategoryGroup ^> ^ CreateMenuOptions();
static Windows::Foundation::Collections::IVector<NavCategoryGroup ^> ^ CreateMenuOptions();
static NavCategoryGroup ^ CreateCalculatorCategoryGroup();
static NavCategoryGroup ^ CreateConverterCategoryGroup();

View file

@ -40,6 +40,7 @@ namespace CalculatorApp
[Windows::UI::Xaml::Data::Bindable] public ref class StandardCalculatorViewModel sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
{
public:
StandardCalculatorViewModel();
void UpdateOperand(int pos, Platform::String ^ text);
OBSERVABLE_OBJECT_CALLBACK(OnPropertyChanged);
@ -267,9 +268,11 @@ namespace CalculatorApp
void SwitchAngleType(CalculatorApp::ViewModel::Common::NumbersAndOperatorsEnum num);
void FtoEButtonToggled();
internal : void OnPaste(Platform::String ^ pastedString);
void OnCopyCommand(Platform::Object ^ parameter);
void OnPasteCommand(Platform::Object ^ parameter);
void SetCalculatorType(CalculatorApp::ViewModel::Common::ViewMode targetState);
internal : void OnPaste(Platform::String ^ pastedString);
ButtonInfo MapCharacterToButtonId(char16 ch);
@ -284,7 +287,6 @@ namespace CalculatorApp
Platform::String ^ GetLocalizedStringFormat(Platform::String ^ format, Platform::String ^ displayValue);
void OnPropertyChanged(Platform::String ^ propertyname);
void SetCalculatorType(CalculatorApp::ViewModel::Common::ViewMode targetState);
Platform::String ^ GetRawDisplayValue();
void Recalculate(bool fromHistory = false);
@ -302,9 +304,6 @@ namespace CalculatorApp
return m_CurrentAngleType;
}
internal :;
explicit StandardCalculatorViewModel();
private:
void SetMemorizedNumbers(const std::vector<std::wstring>& memorizedNumbers);
void UpdateProgrammerPanelDisplay();

View file

@ -152,6 +152,13 @@ UnitConverterViewModel::UnitConverterViewModel(const shared_ptr<UCM::IUnitConver
PopulateData();
}
UnitConverterViewModel::UnitConverterViewModel()
: UnitConverterViewModel(std::make_shared<UCM::UnitConverter>( //
std::make_shared<UnitConverterDataLoader>(ref new Windows::Globalization::GeographicRegion()), //
std::make_shared<CurrencyDataLoader>()))
{
}
void UnitConverterViewModel::ResetView()
{
m_model->SendCommand(UCM::Command::Reset);

View file

@ -158,6 +158,11 @@ namespace CalculatorApp
internal : UnitConverterViewModel(const std::shared_ptr<UnitConversionManager::IUnitConverter>& model);
public:
UnitConverterViewModel();
virtual ~UnitConverterViewModel()
{
}
OBSERVABLE_OBJECT_CALLBACK(OnPropertyChanged);
OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector<Category ^> ^, Categories);
@ -238,15 +243,14 @@ namespace CalculatorApp
void OnPaste(Platform::String ^ stringToPaste);
void RefreshCurrencyRatios();
void OnValueActivated(IActivatable ^ control);
void OnCopyCommand(Platform::Object ^ parameter);
void OnPasteCommand(Platform::Object ^ parameter);
internal : void ResetView();
void PopulateData();
CalculatorApp::ViewModel::Common::NumbersAndOperatorsEnum MapCharacterToButtonId(const wchar_t ch, bool& canSendNegate);
void DisplayPasteError();
void OnCopyCommand(Platform::Object ^ parameter);
void OnPasteCommand(Platform::Object ^ parameter);
enum class CurrencyFormatterParameter
{
Default,

View file

@ -300,7 +300,6 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\CalcViewModel\ApplicationViewModel.h" />
<ClInclude Include="..\CalcViewModel\Common\AlwaysSelectedCollectionView.h" />
<ClInclude Include="..\CalcViewModel\Common\AppResourceProvider.h" />
<ClInclude Include="..\CalcViewModel\Common\Automation\NarratorAnnouncement.h" />
@ -345,7 +344,6 @@
<ClInclude Include="..\CalcViewModel\UnitConverterViewModel.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\CalcViewModel\ApplicationViewModel.cpp" />
<ClCompile Include="..\CalcViewModel\Common\AppResourceProvider.cpp" />
<ClCompile Include="..\CalcViewModel\Common\Automation\NarratorAnnouncement.cpp" />
<ClCompile Include="..\CalcViewModel\Common\Automation\NarratorNotifier.cpp" />

View file

@ -24,7 +24,6 @@
<ClCompile Include="..\CalcViewModel\Common\Automation\NarratorNotifier.cpp">
<Filter>Common\Automation</Filter>
</ClCompile>
<ClCompile Include="..\CalcViewModel\ApplicationViewModel.cpp" />
<ClCompile Include="..\CalcViewModel\DateCalculatorViewModel.cpp" />
<ClCompile Include="..\CalcViewModel\HistoryItemViewModel.cpp" />
<ClCompile Include="..\CalcViewModel\HistoryViewModel.cpp" />
@ -71,9 +70,6 @@
<ClCompile Include="..\CalcViewModel\DataLoaders\CurrencyDataLoader.cpp">
<Filter>DataLoaders</Filter>
</ClCompile>
<ClCompile Include="..\CalcViewModel\DataLoaders\CurrencyHttpClient.cpp">
<Filter>DataLoaders</Filter>
</ClCompile>
<ClCompile Include="..\CalcViewModel\DataLoaders\UnitConverterDataLoader.cpp">
<Filter>DataLoaders</Filter>
</ClCompile>
@ -88,6 +84,7 @@
</ClCompile>
<ClCompile Include="..\CalcViewModel\Common\RadixType.cpp" />
<ClCompile Include="..\CalcViewModel\Snapshots.cpp" />
<ClCompile Include="DataLoaders\CurrencyHttpClient.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\CalcViewModel\Common\AppResourceProvider.h">
@ -99,7 +96,6 @@
<ClInclude Include="..\CalcViewModel\Common\Automation\NarratorNotifier.h">
<Filter>Common\Automation</Filter>
</ClInclude>
<ClInclude Include="..\CalcViewModel\ApplicationViewModel.h" />
<ClInclude Include="..\CalcViewModel\DateCalculatorViewModel.h" />
<ClInclude Include="..\CalcViewModel\HistoryItemViewModel.h" />
<ClInclude Include="..\CalcViewModel\HistoryViewModel.h" />
@ -174,9 +170,6 @@
<ClInclude Include="..\CalcViewModel\DataLoaders\CurrencyDataLoader.h">
<Filter>DataLoaders</Filter>
</ClInclude>
<ClInclude Include="..\CalcViewModel\DataLoaders\CurrencyHttpClient.h">
<Filter>DataLoaders</Filter>
</ClInclude>
<ClInclude Include="..\CalcViewModel\DataLoaders\UnitConverterDataConstants.h">
<Filter>DataLoaders</Filter>
</ClInclude>

View file

@ -0,0 +1,384 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.Foundation;
using Windows.Storage;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using CalculatorApp.ViewModel;
using CalculatorApp.ViewModel.Common;
using CalculatorApp.ViewModel.Snapshot;
namespace CalculatorApp.ManagedViewModels
{
public class ApplicationViewModel : INotifyPropertyChanged
{
private StandardCalculatorViewModel _calcVm;
private DateCalculatorViewModel _dateVm;
private GraphingCalculatorViewModel _graphVm;
private UnitConverterViewModel _unitVm;
private ViewMode _mode = ViewMode.None;
private ViewMode _previousMode = ViewMode.None;
private bool _isAlwaysOnTop = false;
private bool _displayNormalAlwaysOnTopOption;
private string _categoryName;
private IList<NavCategoryGroup> _categories = NavCategoryStates.CreateMenuOptions();
public const string WidthLocalSettingsKey = "calculatorAlwaysOnTopLastWidth";
public const string HeightLocalSettingsKey = "calculatorAlwaysOnTopLastHeight";
public event PropertyChangedEventHandler PropertyChanged;
public ViewMode Mode
{
get => _mode;
set
{
if (_mode != value)
{
PreviousMode = _mode;
_mode = value;
SetDisplayNormalAlwaysOnTopOption();
OnModeChanged();
RaisePropertyChanged();
}
}
}
public ICommand CopyCommand => new RelayCommand(OnCopyCommand);
public ICommand PasteCommand => new RelayCommand(OnPasteCommand);
public Visibility ClearMemoryVisibility
{
get => NavCategory.IsCalculatorViewMode(_mode) ?
Visibility.Visible : Visibility.Collapsed;
}
public ApplicationSnapshot Snapshot
{
get
{
var snapshot = new ApplicationSnapshot();
snapshot.Mode = (int)_mode;
if (_calcVm != null)
{
snapshot.StandardCalculator = _calcVm.Snapshot;
}
return snapshot;
}
}
public StandardCalculatorViewModel CalculatorViewModel
{
get => _calcVm;
set
{
if (_calcVm != value)
{
_calcVm = value;
RaisePropertyChanged();
}
}
}
public DateCalculatorViewModel DateCalcViewModel
{
get => _dateVm;
set
{
if (_dateVm != value)
{
_dateVm = value;
RaisePropertyChanged();
}
}
}
public GraphingCalculatorViewModel GraphingCalcViewModel
{
get => _graphVm;
set
{
if (_graphVm != value)
{
_graphVm = value;
RaisePropertyChanged();
}
}
}
public UnitConverterViewModel ConverterViewModel
{
get => _unitVm;
set
{
if (_unitVm != value)
{
_unitVm = value;
RaisePropertyChanged();
}
}
}
public ViewMode PreviousMode
{
get => _previousMode;
set
{
if (_previousMode != value)
{
_previousMode = value;
RaisePropertyChanged();
}
}
}
public bool IsAlwaysOnTop
{
get => _isAlwaysOnTop;
private set
{
if (_isAlwaysOnTop != value)
{
_isAlwaysOnTop = value;
RaisePropertyChanged();
}
}
}
// Indicates whether calculator is currently in standard mode _and_ supports CompactOverlay _and_ is not in Always-on-Top mode
public bool DisplayNormalAlwaysOnTopOption
{
get => _displayNormalAlwaysOnTopOption;
private set
{
if (_displayNormalAlwaysOnTopOption != value)
{
_displayNormalAlwaysOnTopOption = value;
RaisePropertyChanged();
}
}
}
public string CategoryName
{
get => _categoryName;
set
{
if (_categoryName != value)
{
_categoryName = value;
RaisePropertyChanged();
}
}
}
public IList<NavCategoryGroup> Categories
{
get => _categories;
set
{
if (_categories != value)
{
_categories = value;
RaisePropertyChanged();
}
}
}
public async Task ToggleAlwaysOnTop(float width, float height)
{
var DefaultSize = new Size(320, 394);
const string LaunchedSettingsKey = "calculatorAlwaysOnTopLaunched";
var settings = ApplicationData.Current.LocalSettings;
if (ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.CompactOverlay)
{
settings.Values[WidthLocalSettingsKey] = width;
settings.Values[HeightLocalSettingsKey] = height;
bool success = await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.Default);
_calcVm.HistoryVM.AreHistoryShortcutsEnabled = success;
_calcVm.IsAlwaysOnTop = !success;
IsAlwaysOnTop = !success;
}
else
{
var compactOptions = ViewModePreferences.CreateDefault(ApplicationViewMode.CompactOverlay);
if (!settings.Values.ContainsKey(LaunchedSettingsKey))
{
compactOptions.CustomSize = DefaultSize;
settings.Values[LaunchedSettingsKey] = true;
}
else
{
if (settings.Values.TryGetValue(WidthLocalSettingsKey, out var oldWidth) &&
settings.Values.TryGetValue(HeightLocalSettingsKey, out var oldHeight))
{
compactOptions.CustomSize = new Size((float)oldWidth, (float)oldHeight);
}
else
{
compactOptions.CustomSize = DefaultSize;
}
}
bool success = await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay, compactOptions);
_calcVm.HistoryVM.AreHistoryShortcutsEnabled = !success;
_calcVm.IsAlwaysOnTop = success;
IsAlwaysOnTop = success;
}
SetDisplayNormalAlwaysOnTopOption();
}
public void Initialize(ViewMode mode)
{
if (!NavCategoryStates.IsValidViewMode(mode) || !NavCategoryStates.IsViewModeEnabled(mode))
{
mode = ViewMode.Standard;
}
try
{
Mode = mode;
}
catch (Exception e)
{
TraceLogger.GetInstance().LogError(mode, nameof(Initialize), e.Message);
if (!TryRecoverFromNavigationModeFailure())
{
// Could not navigate to standard mode either.
// Throw the original exception so we have a good stack to debug.
throw;
}
}
}
public void RestoreFromSnapshot(ApplicationSnapshot snapshot)
{
Mode = (ViewMode)snapshot.Mode;
if (snapshot.StandardCalculator != null)
{
_calcVm.Snapshot = snapshot.StandardCalculator;
}
}
private void OnModeChanged()
{
Debug.Assert(NavCategoryStates.IsValidViewMode(_mode));
if (NavCategory.IsCalculatorViewMode(_mode))
{
if (_calcVm == null)
{
_calcVm = new StandardCalculatorViewModel();
}
_calcVm.SetCalculatorType(_mode);
}
else if (NavCategory.IsGraphingCalculatorViewMode(_mode))
{
if (_graphVm == null)
{
_graphVm = new GraphingCalculatorViewModel();
}
}
else if (NavCategory.IsDateCalculatorViewMode(_mode))
{
if (_dateVm == null)
{
_dateVm = new DateCalculatorViewModel();
}
}
else if (NavCategory.IsConverterViewMode(_mode))
{
if (_unitVm == null)
{
_unitVm = new UnitConverterViewModel();
}
_unitVm.Mode = _mode;
}
var resProvider = AppResourceProvider.GetInstance();
CategoryName = resProvider.GetResourceString(NavCategoryStates.GetNameResourceKey(_mode));
// Cast mode to an int in order to save it to app data.
// Save the changed mode, so that the new window launches in this mode.
// Don't save until after we have adjusted to the new mode, so we don't save a mode that fails to load.
ApplicationData.Current.LocalSettings.Values[nameof(Mode)] = NavCategoryStates.Serialize(_mode);
// Log ModeChange event when not first launch, log WindowCreated on first launch
if (NavCategoryStates.IsValidViewMode(_previousMode))
{
TraceLogger.GetInstance().LogModeChange(_mode);
}
else
{
TraceLogger.GetInstance().LogWindowCreated(
_mode,
ApplicationView.GetApplicationViewIdForWindow(CoreWindow.GetForCurrentThread()));
}
RaisePropertyChanged(nameof(ClearMemoryVisibility));
}
private void OnCopyCommand(object param)
{
if (NavCategory.IsConverterViewMode(_mode))
{
_unitVm.OnCopyCommand(param);
}
else if (NavCategory.IsDateCalculatorViewMode(_mode))
{
_dateVm.OnCopyCommand(param);
}
else if (NavCategory.IsCalculatorViewMode(_mode))
{
_calcVm.OnCopyCommand(param);
}
}
private void OnPasteCommand(object param)
{
if (NavCategory.IsConverterViewMode(_mode))
{
_unitVm.OnPasteCommand(param);
}
else if (NavCategory.IsCalculatorViewMode(_mode))
{
_calcVm.OnPasteCommand(param);
}
}
private void SetDisplayNormalAlwaysOnTopOption()
{
DisplayNormalAlwaysOnTopOption = _mode == ViewMode.Standard &&
ApplicationView.GetForCurrentView().IsViewModeSupported(ApplicationViewMode.CompactOverlay) &&
!_isAlwaysOnTop;
}
private bool TryRecoverFromNavigationModeFailure()
{
// Here we are simply trying to recover from being unable to navigate to a mode.
// Try falling back to standard mode and if there are *any* exceptions, we should
// fail because something is seriously wrong.
try
{
Mode = ViewMode.Standard;
return true;
}
catch
{
return false;
}
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View file

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Calculator.ManagedViewModels</RootNamespace>
<AssemblyName>Calculator.ManagedViewModels</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.22000.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<PlatformTarget>x86</PlatformTarget>
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<PlatformTarget>ARM</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<PlatformTarget>ARM</PlatformTarget>
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
<PlatformTarget>ARM64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
<PlatformTarget>ARM64</PlatformTarget>
<OutputPath>bin\ARM64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<Compile Include="ApplicationViewModel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RelayCommand.cs" />
<EmbeddedResource Include="Properties\Calculator.ManagedViewModels.rd.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.14</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CalcViewModel\CalcViewModel.vcxproj">
<Project>{812d1a7b-b8ac-49e4-8e6d-af5d59500d56}</Project>
<Name>CalcViewModel</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,8 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Calculator.ManagedViewModels")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("Microsoft Windows Calculator")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: ComVisible(false)]

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains Runtime Directives, specifications about types your application accesses
through reflection and other dynamic code patterns. Runtime Directives are used to control the
.NET Native optimizer and ensure that it does not remove code accessed by your library. If your
library does not do any reflection, then you generally do not need to edit this file. However,
if your library reflects over types, especially types passed to it or derived from its types,
then you should write Runtime Directives.
The most common use of reflection in libraries is to discover information about types passed
to the library. Runtime Directives have three ways to express requirements on types passed to
your library.
1. Parameter, GenericParameter, TypeParameter, TypeEnumerableParameter
Use these directives to reflect over types passed as a parameter.
2. SubTypes
Use a SubTypes directive to reflect over types derived from another type.
3. AttributeImplies
Use an AttributeImplies directive to indicate that your library needs to reflect over
types or methods decorated with an attribute.
For more information on writing Runtime Directives for libraries, please visit
https://go.microsoft.com/fwlink/?LinkID=391919
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library Name="Calculator.ManagedViewModels">
<!-- add directives for your library here -->
</Library>
</Directives>

View file

@ -0,0 +1,36 @@
// Licensed under the MIT License.
using System;
using System.Windows.Input;
namespace CalculatorApp.ManagedViewModels
{
public class RelayCommand: ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View file

@ -29,6 +29,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TraceLogging", "TraceLoggin
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalcViewModelCopyForUT", "CalcViewModelCopyForUT\CalcViewModelCopyForUT.vcxproj", "{CC9B4FA7-D746-4F52-9401-0AD1B4D6B16D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calculator.ManagedViewModels", "Calculator.ManagedViewModels\Calculator.ManagedViewModels.csproj", "{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
@ -41,6 +43,30 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM.ActiveCfg = Debug|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM.Build.0 = Debug|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM.Deploy.0 = Debug|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM64.ActiveCfg = Debug|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM64.Build.0 = Debug|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM64.Deploy.0 = Debug|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x64.ActiveCfg = Debug|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x64.Build.0 = Debug|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x64.Deploy.0 = Debug|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x86.ActiveCfg = Debug|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x86.Build.0 = Debug|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x86.Deploy.0 = Debug|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM.ActiveCfg = Release|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM.Build.0 = Release|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM.Deploy.0 = Release|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM64.ActiveCfg = Release|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM64.Build.0 = Release|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM64.Deploy.0 = Release|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x64.ActiveCfg = Release|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x64.Build.0 = Release|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x64.Deploy.0 = Release|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x86.ActiveCfg = Release|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x86.Build.0 = Release|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x86.Deploy.0 = Release|x86
{311E866D-8B93-4609-A691-265941FEE101}.Debug|ARM.ActiveCfg = Debug|ARM
{311E866D-8B93-4609-A691-265941FEE101}.Debug|ARM.Build.0 = Debug|ARM
{311E866D-8B93-4609-A691-265941FEE101}.Debug|ARM64.ActiveCfg = Debug|ARM64
@ -169,30 +195,6 @@ Global
{FC81FF41-02CD-4CD9-9BC5-45A1E39AC6ED}.Release|x64.Build.0 = Release|x64
{FC81FF41-02CD-4CD9-9BC5-45A1E39AC6ED}.Release|x86.ActiveCfg = Release|Win32
{FC81FF41-02CD-4CD9-9BC5-45A1E39AC6ED}.Release|x86.Build.0 = Release|Win32
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM.ActiveCfg = Debug|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM.Build.0 = Debug|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM.Deploy.0 = Debug|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM64.ActiveCfg = Debug|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM64.Build.0 = Debug|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|ARM64.Deploy.0 = Debug|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x64.ActiveCfg = Debug|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x64.Build.0 = Debug|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x64.Deploy.0 = Debug|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x86.ActiveCfg = Debug|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x86.Build.0 = Debug|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Debug|x86.Deploy.0 = Debug|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM.ActiveCfg = Release|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM.Build.0 = Release|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM.Deploy.0 = Release|ARM
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM64.ActiveCfg = Release|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM64.Build.0 = Release|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|ARM64.Deploy.0 = Release|ARM64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x64.ActiveCfg = Release|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x64.Build.0 = Release|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x64.Deploy.0 = Release|x64
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x86.ActiveCfg = Release|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x86.Build.0 = Release|x86
{3B773403-B0D6-4F9A-948E-512A7A5FB315}.Release|x86.Deploy.0 = Release|x86
{CC9B4FA7-D746-4F52-9401-0AD1B4D6B16D}.Debug|ARM.ActiveCfg = Debug|ARM
{CC9B4FA7-D746-4F52-9401-0AD1B4D6B16D}.Debug|ARM.Build.0 = Debug|ARM
{CC9B4FA7-D746-4F52-9401-0AD1B4D6B16D}.Debug|ARM64.ActiveCfg = Debug|ARM64
@ -207,6 +209,22 @@ Global
{CC9B4FA7-D746-4F52-9401-0AD1B4D6B16D}.Release|x64.Build.0 = Release|x64
{CC9B4FA7-D746-4F52-9401-0AD1B4D6B16D}.Release|x86.ActiveCfg = Release|Win32
{CC9B4FA7-D746-4F52-9401-0AD1B4D6B16D}.Release|x86.Build.0 = Release|Win32
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|ARM.ActiveCfg = Debug|ARM
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|ARM.Build.0 = Debug|ARM
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|ARM64.Build.0 = Debug|ARM64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|x64.ActiveCfg = Debug|x64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|x64.Build.0 = Debug|x64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|x86.ActiveCfg = Debug|x86
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Debug|x86.Build.0 = Debug|x86
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|ARM.ActiveCfg = Release|ARM
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|ARM.Build.0 = Release|ARM
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|ARM64.ActiveCfg = Release|ARM64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|ARM64.Build.0 = Release|ARM64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|x64.ActiveCfg = Release|x64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|x64.Build.0 = Release|x64
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|x86.ActiveCfg = Release|x86
{2179CFDE-CDED-4DF0-8C24-A0EF6B425771}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -806,6 +806,10 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Calculator.ManagedViewModels\Calculator.ManagedViewModels.csproj">
<Project>{2179cfde-cded-4df0-8c24-a0ef6b425771}</Project>
<Name>Calculator.ManagedViewModels</Name>
</ProjectReference>
<ProjectReference Include="..\CalcViewModel\CalcViewModel.vcxproj">
<Project>{812d1a7b-b8ac-49e4-8e6d-af5d59500d56}</Project>
<Name>CalcViewModel</Name>

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using CalculatorApp.ViewModel;
using CalculatorApp.ManagedViewModels;
using CalculatorApp.ViewModel.Common;
using System;

View file

@ -19,7 +19,7 @@ using Microsoft.UI.Xaml.Controls;
using CalculatorApp.Common;
using CalculatorApp.Converters;
using CalculatorApp.JsonUtils;
using CalculatorApp.ViewModel;
using CalculatorApp.ManagedViewModels;
using CalculatorApp.ViewModel.Common;
using CalculatorApp.ViewModel.Common.Automation;
@ -159,9 +159,9 @@ namespace CalculatorApp
{
var initialMode = ViewMode.Standard;
var localSettings = ApplicationData.Current.LocalSettings;
if (localSettings.Values.ContainsKey(ApplicationViewModel.ModePropertyName))
if (localSettings.Values.ContainsKey(nameof(ApplicationViewModel.Mode)))
{
initialMode = NavCategoryStates.Deserialize(localSettings.Values[ApplicationViewModel.ModePropertyName]);
initialMode = NavCategoryStates.Deserialize(localSettings.Values[nameof(ApplicationViewModel.Mode)]);
}
if (e.Parameter == null)
@ -202,12 +202,6 @@ namespace CalculatorApp
private void InitializeNavViewCategoriesSource()
{
NavViewCategoriesSource = ExpandNavViewCategoryGroups(Model.Categories);
Model.Categories.VectorChanged += (sender, args) =>
{
NavViewCategoriesSource.Clear();
NavViewCategoriesSource = ExpandNavViewCategoryGroups(Model.Categories);
};
_ = Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Low, () =>
{
var graphCategory = (NavCategory)NavViewCategoriesSource.Find(x =>
@ -258,7 +252,7 @@ namespace CalculatorApp
private void OnAppPropertyChanged(object sender, PropertyChangedEventArgs e)
{
string propertyName = e.PropertyName;
if (propertyName == ApplicationViewModel.ModePropertyName)
if (propertyName == nameof(ApplicationViewModel.Mode))
{
ViewMode newValue = Model.Mode;
ViewMode previousMode = Model.PreviousMode;
@ -324,7 +318,7 @@ namespace CalculatorApp
UpdateViewState();
SetDefaultFocus();
}
else if (propertyName == ApplicationViewModel.CategoryNamePropertyName)
else if (propertyName == nameof(ApplicationViewModel.CategoryName))
{
SetHeaderAutomationName();
AnnounceCategoryName();
@ -541,8 +535,8 @@ namespace CalculatorApp
if (Model.IsAlwaysOnTop)
{
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
localSettings.Values[ApplicationViewModel.WidthLocalSettings] = ActualWidth;
localSettings.Values[ApplicationViewModel.HeightLocalSettings] = ActualHeight;
localSettings.Values[ApplicationViewModel.WidthLocalSettingsKey] = ActualWidth;
localSettings.Values[ApplicationViewModel.HeightLocalSettingsKey] = ActualHeight;
}
}

View file

@ -98,14 +98,14 @@ namespace CalculatorUnitTests
public:
TEST_METHOD(CreateNavCategoryGroup)
{
IObservableVector<NavCategoryGroup ^> ^ menuOptions = NavCategoryStates::CreateMenuOptions();
IVector<NavCategoryGroup ^> ^ menuOptions = NavCategoryStates::CreateMenuOptions();
VERIFY_ARE_EQUAL(2, menuOptions->Size);
NavCategoryGroup ^ calculatorGroup = menuOptions->GetAt(0);
VERIFY_ARE_EQUAL(CategoryGroupType::Calculator, calculatorGroup->GroupType);
IObservableVector<NavCategory ^> ^ calculatorCategories = calculatorGroup->Categories;
IVector<NavCategory ^> ^ calculatorCategories = calculatorGroup->Categories;
ValidateNavCategory(calculatorCategories, 0u, ViewMode::Standard);
ValidateNavCategory(calculatorCategories, 1u, ViewMode::Scientific);
ValidateNavCategory(calculatorCategories, 2u, ViewMode::Graphing);
@ -116,7 +116,7 @@ namespace CalculatorUnitTests
NavCategoryGroup ^ converterGroup = menuOptions->GetAt(1);
VERIFY_ARE_EQUAL(CategoryGroupType::Converter, converterGroup->GroupType);
IObservableVector<NavCategory ^> ^ converterCategories = converterGroup->Categories;
IVector<NavCategory ^> ^ converterCategories = converterGroup->Categories;
VERIFY_ARE_EQUAL(13, converterCategories->Size);
ValidateNavCategory(converterCategories, 0u, ViewMode::Currency);
ValidateNavCategory(converterCategories, 1u, ViewMode::Volume);
@ -134,7 +134,7 @@ namespace CalculatorUnitTests
}
private:
void ValidateNavCategory(IObservableVector<NavCategory ^> ^ categories, unsigned int index, ViewMode expectedMode)
void ValidateNavCategory(IVector<NavCategory ^> ^ categories, unsigned int index, ViewMode expectedMode)
{
VERIFY_IS_LESS_THAN(0u, categories->Size);
VERIFY_IS_GREATER_THAN(categories->Size, index);