2025-11-28 00:35:46 +09:00

328 lines
14 KiB
C++

// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License.
#include "pch.h"
#include "MainWindow.xaml.h"
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif
#include "TabViewModel.h"
namespace winrt
{
using namespace winrt::AppTabsIntegration;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::Foundation::Metadata;
using namespace winrt::Windows::Graphics::Imaging;
using namespace winrt::Windows::Storage::Streams;
using namespace winrt::Windows::UI::Shell;
using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Microsoft::UI::Xaml::Controls;
using namespace winrt::Microsoft::UI::Xaml::Media::Imaging;
}
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace
{
// Helper for making Windows.UI.WindowId objects from HWNDs.
winrt::Windows::UI::WindowId WindowsWindowIdFromHwnd(HWND hwnd)
{
static_assert(sizeof(winrt::Windows::UI::WindowId) == sizeof(winrt::Microsoft::UI::WindowId));
auto id = winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd);
return reinterpret_cast<winrt::Windows::UI::WindowId&>(id);
}
}
namespace winrt::AppTabsIntegration::implementation
{
MainWindow::MainWindow(winrt::TabViewModel const& initialTab)
{
InitializeComponent();
m_dispatcherQueue = this->DispatcherQueue();
MainWindowTabView().TabItemsSource(m_tabs);
// Retrieve the window handle (HWND) of this WinUI 3 window.
HWND hwnd;
winrt::check_hresult(this->try_as<::IWindowNative>()->get_WindowHandle(&hwnd));
// Apply right-to-left flow direction if this window has right-to-left layout.
if (GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
{
this->Content().as<winrt::FrameworkElement>().FlowDirection(FlowDirection::RightToLeft);
}
// Add WindowTabManager support if it is available.
if (ApiInformation::IsApiContractPresent(winrt::name_of<WindowTabManagerContract>(), 1) && WindowTabManager::IsSupported())
{
// Get the WindowTabManager for this window.
m_tabManager = WindowTabManager::GetForWindow(WindowsWindowIdFromHwnd(hwnd));
// Register WindowTabManager events so we can respond to requests from the system.
m_tabSwitchRevoker = m_tabManager.TabSwitchRequested(winrt::auto_revoke, { get_weak(), &MainWindow::TabSwitchRequested});
m_tabCloseRevoker = m_tabManager.TabCloseRequested(winrt::auto_revoke, { get_weak(), &MainWindow::TabCloseRequested });
m_tabThumbnailRequestedRevoker = m_tabManager.TabThumbnailRequested(winrt::auto_revoke, { get_weak(), &MainWindow::TabThumbnailRequested });
// You can use WindowTabManager::IsTabTearOutSupported() to detect whether the system supports tab tear-out.
// If the feature is not supported, then the TabTearOutRequested event is never raised, but it's still okay
// to register a handler for it.
m_tabTearOutRequestedRevoker = m_tabManager.TabTearOutRequested(winrt::auto_revoke, { get_weak(), &MainWindow::TabTearOutRequested });
}
// Subscribe to vector changes so we can keep the WindowTabManager in sync.
// This is also how we learn about XAML tab rearrangement.
m_tabs.VectorChanged({ get_weak(), &MainWindow::TabCollection_VectorChanged });
// Insert the initial app tab.
// The VectorChanged event handler will update the WindowTabManager.
if (initialTab)
{
// Caller provided an initial tab.
m_tabs.Append(initialTab);
}
else
{
// Create a default initial tab.
m_tabs.Append(CreateTabViewModel());
}
}
winrt::TabViewModel MainWindow::CreateTabViewModel()
{
static int s_counter = 0;
winrt::hstring tabName = L"Tab #" + winrt::to_hstring(++s_counter);
WindowTab windowTab{ nullptr };
if (m_tabManager)
{
// Make a WindowTab that is the counterpart to our app tab.
windowTab = WindowTab();
// The Icon of the WindowTab should match the icon of the tab in the app (if any).
RandomAccessStreamReference icoStream = RandomAccessStreamReference::CreateFromUri(Uri(L"ms-appx:///Assets/icon.ico"));
windowTab.Icon(WindowTabIcon::CreateFromImage(icoStream));
}
// Create a tab to add to our TabView.
auto const tabViewModel = winrt::make<TabViewModel>(tabName, windowTab);
return tabViewModel;
}
// Handle the XAML event that tells us that the user wants to close a tab.
// This event is raised on the UI thread.
void MainWindow::TabView_CloseRequested(TabView const&, TabViewTabCloseRequestedEventArgs const& args)
{
auto const tabViewModel = args.Item().as<winrt::TabViewModel>();
RemoveTabByItem(tabViewModel);
}
// Handle the XAML event that tells us that the user wants to create a new tab.
// This event is raised on the UI thread.
void MainWindow::TabView_AddTabClicked(TabView const&, IInspectable const&)
{
// Add a new TabViewModel to our app's TabView.
winrt::TabViewModel tabViewModel = CreateTabViewModel();
// The VectorChanged event handler will update the WindowTabManager.
m_tabs.Append(tabViewModel);
// Auto-select the newly-added tab.
MainWindowTabView().SelectedItem(tabViewModel);
}
// Handle the XAML event that tells us that the user switched tabs.
// This event is raised on the UI thread.
void MainWindow::TabView_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const&)
{
auto const tabModel = MainWindowTabView().SelectedItem().as<winrt::TabViewModel>();
if (m_tabManager && tabModel)
{
// Tell the WindowTabManager that we have changed active tabs.
m_tabManager.SetActiveTab(TabViewModel::GetWindowTab(tabModel));
}
}
// Handle the XAML event that tells us that the user tore out a tab.
// This event is raised on the UI thread.
void MainWindow::TabView_TabDroppedOutside(TabView const&, TabViewTabDroppedOutsideEventArgs const& args)
{
// Don't let the user tear out the last tab.
if (m_tabs.Size() < 2)
{
return;
}
// Remove the tab from this window
winrt::TabViewModel tabViewModel = args.Item().as<winrt::TabViewModel>();
RemoveTabByItem(tabViewModel);
// Create another MainWindow with that tab as its only tab.
auto tornOut = winrt::make<MainWindow>(tabViewModel);
// Position the window under the cursor.
POINT pos;
if (GetCursorPos(&pos))
{
HWND hwnd;
winrt::check_hresult(tornOut.try_as<::IWindowNative>()->get_WindowHandle(&hwnd));
RECT rect;
GetWindowRect(hwnd, &rect);
MapWindowPoints(HWND_DESKTOP, hwnd, reinterpret_cast<LPPOINT>(&rect), 2);
if (this->Content().as<winrt::FrameworkElement>().FlowDirection() == FlowDirection::RightToLeft)
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
pos.x -= clientRect.right;
}
SetWindowPos(hwnd, nullptr, pos.x + rect.left, pos.y + rect.top,
0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}
// Show the new window.
tornOut.Activate();
}
// Handle the IObservableVector event that tells us that the list of TabDataModel objects changed.
// This event is raised on the UI thread.
void MainWindow::TabCollection_VectorChanged(IObservableVector<winrt::TabViewModel> const&, IVectorChangedEventArgs const& args)
{
// Keep the WindowTabManager's tab list in sync with our app's TabView tab list.
if (m_tabManager)
{
auto index = args.Index();
switch (args.CollectionChange())
{
case CollectionChange::ItemInserted:
m_tabManager.Tabs().InsertAt(index, TabViewModel::GetWindowTab(m_tabs.GetAt(index)));
break;
case CollectionChange::ItemRemoved:
m_tabManager.Tabs().RemoveAt(index);
break;
}
}
// Update the window title to reflect the number of tabs.
auto const tabCount = m_tabs.Size();
if (tabCount == 1)
{
this->Title(L"AppTabsIntegration Sample (1 tab)");
}
else
{
this->Title(L"AppTabsIntegration Sample (" + winrt::to_hstring(tabCount) + L" tabs)");
}
}
// Handle the system event that tells us that the user used the system to select a tab in our app.
// This event can be raised on any thread, so we explicitly switch to the UI thread before doing work.
fire_and_forget MainWindow::TabSwitchRequested(WindowTabManager const&, WindowTabSwitchRequestedEventArgs args)
{
// Take a strong reference to the MainWindow so it doesn't get destructed while we co_await.
auto strongThis = get_strong();
co_await wil::resume_foreground(m_dispatcherQueue);
// Switch to that tab in our UI. When we get the TabView.SelectionChanged event,
// we will update the WindowTabManager to let it know that the switch has occurred.
winrt::TabViewModel tabViewModel = TabViewModel::GetFromWindowTab(args.Tab());
MainWindowTabView().SelectedItem(tabViewModel);
}
// Handle the system event that tells us that the user used the system to close a tab in our app.
// This event can be raised on any thread, so we explicitly switch to the UI thread before doing work.
fire_and_forget MainWindow::TabCloseRequested(WindowTabManager const&, WindowTabCloseRequestedEventArgs args)
{
// Take a strong reference to the MainWindow so it doesn't get destructed while we co_await.
auto strongThis = get_strong();
co_await wil::resume_foreground(m_dispatcherQueue);
winrt::TabViewModel tabViewModel = TabViewModel::GetFromWindowTab(args.Tab());
RemoveTabByItem(tabViewModel);
}
// Handle the system event that tells us that the system wants us to produce a thumbnail of a tab.
// This event can be raised on any thread, so we explicitly switch to the UI thread before doing work.
fire_and_forget MainWindow::TabThumbnailRequested(WindowTabManager const&, WindowTabThumbnailRequestedEventArgs args)
{
// Take a strong reference to the MainWindow so it doesn't get destructed while we co_await.
auto strongThis = get_strong();
// Take a deferral before we co_await so the system knows that we are still working on the thumbnail.
auto deferral = args.GetDeferral();
co_await wil::resume_foreground(m_dispatcherQueue);
// For demonstration purposes, the thumbnail for a tab is the tab's solid color.
// A real program would probably generate a thumbnail image of the tab contents.
winrt::TabViewModel tabViewModel = TabViewModel::GetFromWindowTab(args.Tab());
auto color = tabViewModel.Color();
uint8_t pixel[4] = { color.B, color.G, color.R, 0xFF };
auto inMemoryStream = InMemoryRandomAccessStream();
auto encoder = co_await BitmapEncoder::CreateAsync(BitmapEncoder::PngEncoderId(), inMemoryStream);
encoder.SetPixelData(
BitmapPixelFormat::Bgra8, BitmapAlphaMode::Ignore,
1, 1, 96, 96, pixel);
auto size = args.RequestedSize();
encoder.BitmapTransform().ScaledWidth(size.Width);
encoder.BitmapTransform().ScaledHeight(size.Height);
co_await encoder.FlushAsync();
inMemoryStream.Seek(0);
auto streamRef = RandomAccessStreamReference::CreateFromStream(inMemoryStream);
args.Image(streamRef);
// Complete the deferral to tell the system that the Image is ready.
deferral.Complete();
}
// Handle the system event that tells us that the user has asked for a tab to be torn out.
// This event can be raised on any thread, so we explicitly switch to the UI thread before doing work.
winrt::fire_and_forget MainWindow::TabTearOutRequested(WindowTabManager const&, const WindowTabTearOutRequestedEventArgs args)
{
// Take a strong reference to the MainWindow so it doesn't get destructed while we co_await.
auto strongThis = get_strong();
// Take a deferral before we co_await so the system knows that we are still working on the tear-out.
auto deferral = args.GetDeferral();
co_await wil::resume_foreground(m_dispatcherQueue);
// Remove the tab from this window
winrt::TabViewModel tabViewModel = TabViewModel::GetFromWindowTab(args.Tab());
RemoveTabByItem(tabViewModel);
// Create another MainWindow with that tab as its only tab.
auto tornOut = winrt::make<MainWindow>(tabViewModel);
HWND hwnd;
winrt::check_hresult(tornOut.try_as<::IWindowNative>()->get_WindowHandle(&hwnd));
args.WindowId(reinterpret_cast<uint64_t>(hwnd));
// Complete the deferral to tell the system the WindowId is ready.
deferral.Complete();
// Show the window after completing the deferral.
tornOut.Activate();
}
void MainWindow::RemoveTabByItem(winrt::TabViewModel const& model)
{
uint32_t index;
if (m_tabs.IndexOf(model, index))
{
// Note that this RemoveAt may trigger a SelectionChanged if the current selection is being removed.
// Our VectorChanged event handler will update the WindowTabManager.
m_tabs.RemoveAt(index);
}
// If that was the last tab, then exit this window.
if (m_tabs.Size() == 0)
{
this->Close();
}
}
}