Skip to content

Commit 9670230

Browse files
zadjii-msftDHowett
authored andcommitted
Rebuild the profile nav via MenuItemsSource; mitigate a crash (#14630)
Directly manipulating the `NavigationView::MenuItems` vector is bad. If you do that, you're gonna get crashes, in WinUI code for `Measure`. However, a WinUI PR (below) gave me an idea: Changing `NavigationView::MenuItemsSource` will wholly invalidate the entirety of the nav view items, and that will avoid the crash. This code does that. It's a wee bit janky, but it works. Closes #13673 _might_ affect #12333, need to try and repro. See also: * #9273 * #10390 * microsoft/microsoft-ui-xaml#6302 * microsoft/microsoft-ui-xaml#3138, which was the fix for microsoft/microsoft-ui-xaml#2818 (cherry picked from commit 8c17475) Service-Card-Id: 88428128 Service-Version: 1.17
1 parent c9a4ab7 commit 9670230

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

src/cascadia/TerminalSettingsEditor/MainPage.cpp

+52-3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
117117
lastBreadcrumb = _breadcrumbs.GetAt(size - 1);
118118
}
119119

120+
// Collect all the values out of the old nav view item source
120121
auto menuItems{ SettingsNav().MenuItems() };
121122

122123
// We'll remove a bunch of items and iterate over it twice.
@@ -156,7 +157,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
156157
}),
157158
menuItemsSTL.end());
158159

159-
menuItems.ReplaceAll(menuItemsSTL);
160+
// Now, we've got a list of just the static entries again. Lets take
161+
// those and stick them back into a new winrt vector, and set that as
162+
// the source again.
163+
//
164+
// By setting MenuItemsSource in its entirety, rather than manipulating
165+
// MenuItems, we avoid a crash in WinUI.
166+
auto newSource = winrt::single_threaded_vector<IInspectable>(std::move(menuItemsSTL));
167+
SettingsNav().MenuItemsSource(newSource);
160168

161169
// Repopulate profile-related menu items
162170
_InitializeProfilesList();
@@ -209,7 +217,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
209217
// Couldn't find the selected item, fallback to first menu item
210218
// This happens when the selected item was a profile which doesn't exist in the new configuration
211219
// We can use menuItemsSTL here because the only things they miss are profile entries.
212-
const auto& firstItem{ menuItemsSTL.at(0).as<MUX::Controls::NavigationViewItem>() };
220+
const auto& firstItem{ SettingsNav().MenuItems().GetAt(0).as<MUX::Controls::NavigationViewItem>() };
213221
SettingsNav().SelectedItem(firstItem);
214222
_Navigate(unbox_value<hstring>(firstItem.Tag()), BreadcrumbSubPage::None);
215223
}
@@ -527,7 +535,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
527535

528536
void MainPage::_InitializeProfilesList()
529537
{
530-
const auto menuItems = SettingsNav().MenuItems();
538+
const auto& itemSource{ SettingsNav().MenuItemsSource() };
539+
if (!itemSource)
540+
{
541+
// There wasn't a MenuItemsSource set yet? The only way that's
542+
// possible is if we haven't used
543+
// _MoveXamlParsedNavItemsIntoItemSource to move the hardcoded menu
544+
// entries from XAML into our runtime menu item source. Do that now.
545+
546+
_MoveXamlParsedNavItemsIntoItemSource();
547+
}
548+
const auto menuItems = SettingsNav().MenuItemsSource().try_as<IVector<IInspectable>>();
531549

532550
// Manually create a NavigationViewItem for each profile
533551
// and keep a reference to them in a map so that we
@@ -557,6 +575,37 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
557575
menuItems.Append(addProfileItem);
558576
}
559577

578+
// BODGY
579+
// Does the very wacky business of moving all our MenuItems that we
580+
// hardcoded in XAML into a runtime MenuItemsSource. We'll then use _that_
581+
// MenuItemsSource as the source for our nav view entries instead. This
582+
// lets us hardcode the initial entries in precompiled XAML, but then adjust
583+
// the items at runtime. Without using a MenuItemsSource, the NavView just
584+
// crashes when items are removed (see GH#13673)
585+
void MainPage::_MoveXamlParsedNavItemsIntoItemSource()
586+
{
587+
if (SettingsNav().MenuItemsSource())
588+
{
589+
// We've already copied over the original items to a source. We can
590+
// just skip this now.
591+
return;
592+
}
593+
594+
auto menuItems{ SettingsNav().MenuItems() };
595+
// Remove all the existing items, and move them to a separate vector
596+
// that we'll use as a MenuItemsSource. By doing this, we avoid a WinUI
597+
// bug (MUX#6302) where modifying the NavView.Items() directly causes a
598+
// crash. By leaving these static entries in XAML, we maintain the
599+
// benefit of instantiating them from the XBF, rather than at runtime.
600+
//
601+
// --> Copy it into an STL vector to simplify our code and reduce COM overhead.
602+
std::vector<IInspectable> menuItemsSTL(menuItems.Size(), nullptr);
603+
menuItems.GetMany(0, menuItemsSTL);
604+
605+
auto newSource = winrt::single_threaded_vector<IInspectable>(std::move(menuItemsSTL));
606+
SettingsNav().MenuItemsSource(newSource);
607+
}
608+
560609
void MainPage::_CreateAndNavigateToNewProfile(const uint32_t index, const Model::Profile& profile)
561610
{
562611
const auto newProfile{ profile ? profile : _settingsClone.CreateNewProfile() };

src/cascadia/TerminalSettingsEditor/MainPage.h

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
6868
void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage);
6969

7070
void _UpdateBackgroundForMica();
71+
void _MoveXamlParsedNavItemsIntoItemSource();
7172

7273
winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr };
7374

0 commit comments

Comments
 (0)