diff --git a/WinUIGallery/ControlPages/TabViewPage.xaml b/WinUIGallery/ControlPages/TabViewPage.xaml
index dbd282d22..cd039998d 100644
--- a/WinUIGallery/ControlPages/TabViewPage.xaml
+++ b/WinUIGallery/ControlPages/TabViewPage.xaml
@@ -1,4 +1,4 @@
-
+
+
+
+
@@ -25,19 +29,19 @@
-
+
-
+
-
+
@@ -79,7 +83,7 @@
-
+
@@ -172,19 +176,19 @@
-
+
-
+
-
+
@@ -214,19 +218,19 @@
-
+
-
+
-
+
@@ -259,17 +263,17 @@
-
+
-
+
-
+
diff --git a/WinUIGallery/ControlPages/TabViewPage.xaml.cs b/WinUIGallery/ControlPages/TabViewPage.xaml.cs
index 3d95c3074..07da43467 100644
--- a/WinUIGallery/ControlPages/TabViewPage.xaml.cs
+++ b/WinUIGallery/ControlPages/TabViewPage.xaml.cs
@@ -1,260 +1,268 @@
-using System;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Input;
-using WinUIGallery.SamplePages;
-using WinUIGallery.Helper;
-using Windows.ApplicationModel.Core;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Windowing;
-using Microsoft.UI.Dispatching;
-using WinUIGallery.TabViewPages;
-using System.Collections.ObjectModel;
-
-namespace WinUIGallery.ControlPages
-{
- public class MyData
- {
- public string DataHeader { get; set; }
- public Microsoft.UI.Xaml.Controls.IconSource DataIconSource { get; set; }
- public object DataContent { get; set; }
- }
-
- public sealed partial class TabViewPage : Page
- {
- ObservableCollection myDatas;
-
- public TabViewPage()
- {
- this.InitializeComponent();
-
- // Launching isn't supported yet on Desktop
- // Blocked on Task 27517663: DCPP Preview 2 Bug: Dragging in TabView windowing sample causes app to crash
- //this.LaunchExample.Visibility = Visibility.Collapsed;
-
- InitializeDataBindingSampleData();
- }
-
-#region SharedTabViewLogic
- private void TabView_Loaded(object sender, RoutedEventArgs e)
- {
- for (int i = 0; i < 3; i++)
- {
- (sender as TabView).TabItems.Add(CreateNewTab(i));
- }
- }
-
- private void TabView_AddButtonClick(TabView sender, object args)
- {
- sender.TabItems.Add(CreateNewTab(sender.TabItems.Count));
- }
-
- private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
- {
- sender.TabItems.Remove(args.Tab);
- }
-
- private TabViewItem CreateNewTab(int index)
- {
- TabViewItem newItem = new TabViewItem
- {
- Header = $"Document {index}",
- IconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = Symbol.Document }
- };
-
- // The content of the tab is often a frame that contains a page, though it could be any UIElement.
- Frame frame = new Frame();
-
- switch (index % 3)
- {
- case 0:
- frame.Navigate(typeof(SamplePage1));
- break;
- case 1:
- frame.Navigate(typeof(SamplePage2));
- break;
- case 2:
- frame.Navigate(typeof(SamplePage3));
- break;
- }
-
- newItem.Content = frame;
-
- return newItem;
- }
-#endregion
-
-#region ItemsSourceSample
- private void InitializeDataBindingSampleData()
- {
- myDatas = new ObservableCollection();
-
- for (int index = 0; index < 3; index++)
- {
- myDatas.Add(CreateNewMyData(index));
- }
- }
-
- private MyData CreateNewMyData(int index)
- {
- var newData = new MyData
- {
- DataHeader = $"MyData Doc {index}",
- DataIconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = Symbol.Placeholder }
- };
-
- Frame frame = new Frame();
-
- switch (index % 3)
- {
- case 0:
- frame.Navigate(typeof(SamplePage1));
- break;
- case 1:
- frame.Navigate(typeof(SamplePage2));
- break;
- case 2:
- frame.Navigate(typeof(SamplePage3));
- break;
- }
-
- newData.DataContent = frame;
-
- return newData;
- }
-
- private void TabViewItemsSourceSample_AddTabButtonClick(TabView sender, object args)
- {
- // Add a new MyData item to the collection. TabView automatically generates a TabViewItem.
- myDatas.Add(CreateNewMyData(myDatas.Count));
- }
-
- private void TabViewItemsSourceSample_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
- {
- // Remove the requested MyData object from the collection.
- myDatas.Remove(args.Item as MyData);
- }
-#endregion
-
-#region KeyboardAcceleratorSample
- private void NewTabKeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
- {
- var senderTabView = args.Element as TabView;
- senderTabView.TabItems.Add(CreateNewTab(senderTabView.TabItems.Count));
-
- args.Handled = true;
- }
-
- private void CloseSelectedTabKeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
- {
- var InvokedTabView = (args.Element as TabView);
-
- // Only close the selected tab if it is closeable
- if (((TabViewItem)InvokedTabView.SelectedItem).IsClosable)
- {
- InvokedTabView.TabItems.Remove(InvokedTabView.SelectedItem);
- }
-
- args.Handled = true;
- }
-
- private void NavigateToNumberedTabKeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
- {
- var InvokedTabView = (args.Element as TabView);
-
- int tabToSelect = 0;
-
- switch (sender.Key)
- {
- case Windows.System.VirtualKey.Number1:
- tabToSelect = 0;
- break;
- case Windows.System.VirtualKey.Number2:
- tabToSelect = 1;
- break;
- case Windows.System.VirtualKey.Number3:
- tabToSelect = 2;
- break;
- case Windows.System.VirtualKey.Number4:
- tabToSelect = 3;
- break;
- case Windows.System.VirtualKey.Number5:
- tabToSelect = 4;
- break;
- case Windows.System.VirtualKey.Number6:
- tabToSelect = 5;
- break;
- case Windows.System.VirtualKey.Number7:
- tabToSelect = 6;
- break;
- case Windows.System.VirtualKey.Number8:
- tabToSelect = 7;
- break;
- case Windows.System.VirtualKey.Number9:
- // Select the last tab
- tabToSelect = InvokedTabView.TabItems.Count - 1;
- break;
- }
-
- // Only select the tab if it is in the list
- if (tabToSelect < InvokedTabView.TabItems.Count)
- {
- InvokedTabView.SelectedIndex = tabToSelect;
- }
-
- args.Handled = true;
- }
-#endregion
-
- private void TabWidthBehaviorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- string widthModeString = (e.AddedItems[0] as ComboBoxItem).Content.ToString();
- TabViewWidthMode widthMode = TabViewWidthMode.Equal;
- switch (widthModeString)
- {
- case "Equal":
- widthMode = TabViewWidthMode.Equal;
- break;
- case "SizeToContent":
- widthMode = TabViewWidthMode.SizeToContent;
- break;
- case "Compact":
- widthMode = TabViewWidthMode.Compact;
- break;
- }
- TabView3.TabWidthMode = widthMode;
- }
-
- private void TabCloseButtonOverlayModeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- string overlayModeString = (e.AddedItems[0] as ComboBoxItem).Content.ToString();
- TabViewCloseButtonOverlayMode overlayMode = TabViewCloseButtonOverlayMode.Auto;
- switch (overlayModeString)
- {
- case "Auto":
- overlayMode = TabViewCloseButtonOverlayMode.Auto;
- break;
- case "OnHover":
- overlayMode = TabViewCloseButtonOverlayMode.OnPointerOver;
- break;
- case "Always":
- overlayMode = TabViewCloseButtonOverlayMode.Always;
- break;
- }
- TabView4.CloseButtonOverlayMode = overlayMode;
- }
-
- private void TabViewWindowingButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
- {
- var tabViewSample = new TabViewWindowingSamplePage();
-
- var newWindow = WindowHelper.CreateWindow();
- newWindow.ExtendsContentIntoTitleBar = true;
- newWindow.Content = tabViewSample;
- newWindow.AppWindow.SetIcon("Assets/Tiles/GalleryIcon.ico");
- tabViewSample.LoadDemoData();
- tabViewSample.SetupWindowMinSize(newWindow);
-
- newWindow.Activate();
- }
- }
-}
+using System;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Input;
+using WinUIGallery.SamplePages;
+using WinUIGallery.Helper;
+using Windows.ApplicationModel.Core;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Windowing;
+using Microsoft.UI.Dispatching;
+using WinUIGallery.TabViewPages;
+using System.Collections.ObjectModel;
+using Microsoft.UI.Xaml.Media;
+using System.Collections;
+
+namespace WinUIGallery.ControlPages
+{
+ public class MyData
+ {
+ public string DataHeader { get; set; }
+ public Microsoft.UI.Xaml.Controls.IconSource DataIconSource { get; set; }
+ public object DataContent { get; set; }
+ }
+
+ public sealed partial class TabViewPage : Page
+ {
+ ObservableCollection myDatas;
+
+ public TabViewPage()
+ {
+ this.InitializeComponent();
+
+ // Launching isn't supported yet on Desktop
+ // Blocked on Task 27517663: DCPP Preview 2 Bug: Dragging in TabView windowing sample causes app to crash
+ //this.LaunchExample.Visibility = Visibility.Collapsed;
+
+ InitializeDataBindingSampleData();
+ }
+
+#region SharedTabViewLogic
+ private void TabView_Loaded(object sender, RoutedEventArgs e)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ (sender as TabView).TabItems.Add(CreateNewTab(i));
+ }
+ }
+
+ private void TabView_AddButtonClick(TabView sender, object args)
+ {
+ sender.TabItems.Add(CreateNewTab(sender.TabItems.Count));
+ }
+
+ private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
+ {
+ sender.TabItems.Remove(args.Tab);
+ }
+
+ private TabViewItem CreateNewTab(int index)
+ {
+ TabViewItem newItem = new TabViewItem
+ {
+ Header = $"Document {index}",
+ IconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = Symbol.Document },
+ ContextFlyout = TabViewContextMenu
+ };
+
+ // The content of the tab is often a frame that contains a page, though it could be any UIElement.
+ Frame frame = new Frame();
+
+ switch (index % 3)
+ {
+ case 0:
+ frame.Navigate(typeof(SamplePage1));
+ break;
+ case 1:
+ frame.Navigate(typeof(SamplePage2));
+ break;
+ case 2:
+ frame.Navigate(typeof(SamplePage3));
+ break;
+ }
+
+ newItem.Content = frame;
+
+ return newItem;
+ }
+#endregion
+
+#region ItemsSourceSample
+ private void InitializeDataBindingSampleData()
+ {
+ myDatas = new ObservableCollection();
+
+ for (int index = 0; index < 3; index++)
+ {
+ myDatas.Add(CreateNewMyData(index));
+ }
+ }
+
+ private MyData CreateNewMyData(int index)
+ {
+ var newData = new MyData
+ {
+ DataHeader = $"MyData Doc {index}",
+ DataIconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = Symbol.Placeholder }
+ };
+
+ Frame frame = new Frame();
+
+ switch (index % 3)
+ {
+ case 0:
+ frame.Navigate(typeof(SamplePage1));
+ break;
+ case 1:
+ frame.Navigate(typeof(SamplePage2));
+ break;
+ case 2:
+ frame.Navigate(typeof(SamplePage3));
+ break;
+ }
+
+ newData.DataContent = frame;
+
+ return newData;
+ }
+
+ private void TabViewItemsSourceSample_AddTabButtonClick(TabView sender, object args)
+ {
+ // Add a new MyData item to the collection. TabView automatically generates a TabViewItem.
+ myDatas.Add(CreateNewMyData(myDatas.Count));
+ }
+
+ private void TabViewItemsSourceSample_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
+ {
+ // Remove the requested MyData object from the collection.
+ myDatas.Remove(args.Item as MyData);
+ }
+#endregion
+
+#region KeyboardAcceleratorSample
+ private void NewTabKeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
+ {
+ var senderTabView = args.Element as TabView;
+ senderTabView.TabItems.Add(CreateNewTab(senderTabView.TabItems.Count));
+
+ args.Handled = true;
+ }
+
+ private void CloseSelectedTabKeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
+ {
+ var InvokedTabView = (args.Element as TabView);
+
+ // Only close the selected tab if it is closeable
+ if (((TabViewItem)InvokedTabView.SelectedItem).IsClosable)
+ {
+ InvokedTabView.TabItems.Remove(InvokedTabView.SelectedItem);
+ }
+
+ args.Handled = true;
+ }
+
+ private void NavigateToNumberedTabKeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
+ {
+ var InvokedTabView = (args.Element as TabView);
+
+ int tabToSelect = 0;
+
+ switch (sender.Key)
+ {
+ case Windows.System.VirtualKey.Number1:
+ tabToSelect = 0;
+ break;
+ case Windows.System.VirtualKey.Number2:
+ tabToSelect = 1;
+ break;
+ case Windows.System.VirtualKey.Number3:
+ tabToSelect = 2;
+ break;
+ case Windows.System.VirtualKey.Number4:
+ tabToSelect = 3;
+ break;
+ case Windows.System.VirtualKey.Number5:
+ tabToSelect = 4;
+ break;
+ case Windows.System.VirtualKey.Number6:
+ tabToSelect = 5;
+ break;
+ case Windows.System.VirtualKey.Number7:
+ tabToSelect = 6;
+ break;
+ case Windows.System.VirtualKey.Number8:
+ tabToSelect = 7;
+ break;
+ case Windows.System.VirtualKey.Number9:
+ // Select the last tab
+ tabToSelect = InvokedTabView.TabItems.Count - 1;
+ break;
+ }
+
+ // Only select the tab if it is in the list
+ if (tabToSelect < InvokedTabView.TabItems.Count)
+ {
+ InvokedTabView.SelectedIndex = tabToSelect;
+ }
+
+ args.Handled = true;
+ }
+#endregion
+
+ private void TabWidthBehaviorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ string widthModeString = (e.AddedItems[0] as ComboBoxItem).Content.ToString();
+ TabViewWidthMode widthMode = TabViewWidthMode.Equal;
+ switch (widthModeString)
+ {
+ case "Equal":
+ widthMode = TabViewWidthMode.Equal;
+ break;
+ case "SizeToContent":
+ widthMode = TabViewWidthMode.SizeToContent;
+ break;
+ case "Compact":
+ widthMode = TabViewWidthMode.Compact;
+ break;
+ }
+ TabView3.TabWidthMode = widthMode;
+ }
+
+ private void TabCloseButtonOverlayModeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ string overlayModeString = (e.AddedItems[0] as ComboBoxItem).Content.ToString();
+ TabViewCloseButtonOverlayMode overlayMode = TabViewCloseButtonOverlayMode.Auto;
+ switch (overlayModeString)
+ {
+ case "Auto":
+ overlayMode = TabViewCloseButtonOverlayMode.Auto;
+ break;
+ case "OnHover":
+ overlayMode = TabViewCloseButtonOverlayMode.OnPointerOver;
+ break;
+ case "Always":
+ overlayMode = TabViewCloseButtonOverlayMode.Always;
+ break;
+ }
+ TabView4.CloseButtonOverlayMode = overlayMode;
+ }
+
+ private void TabViewWindowingButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ var tabViewSample = new TabViewWindowingSamplePage();
+
+ var newWindow = WindowHelper.CreateWindow();
+ newWindow.ExtendsContentIntoTitleBar = true;
+ newWindow.Content = tabViewSample;
+ newWindow.AppWindow.SetIcon("Assets/Tiles/GalleryIcon.ico");
+ tabViewSample.LoadDemoData();
+ tabViewSample.SetupWindowMinSize(newWindow);
+
+ newWindow.Activate();
+ }
+
+ private void TabViewContextMenu_Opening(object sender, object e)
+ {
+ TabViewHelper.PopulateTabViewContextMenu((MenuFlyout)sender);
+ }
+ }
+}
diff --git a/WinUIGallery/Helper/TabViewHelper.cs b/WinUIGallery/Helper/TabViewHelper.cs
new file mode 100644
index 000000000..504b3295b
--- /dev/null
+++ b/WinUIGallery/Helper/TabViewHelper.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace WinUIGallery.Helper
+{
+ public static class TabViewHelper
+ {
+ public static void PopulateTabViewContextMenu(MenuFlyout contextMenu)
+ {
+ contextMenu.Items.Clear();
+
+ var item = (TabViewItem)contextMenu.Target;
+ ListView tabViewListView = null;
+ TabView tabView = null;
+
+ DependencyObject current = item;
+
+ while (current != null)
+ {
+ DependencyObject parent = VisualTreeHelper.GetParent(current);
+
+ if (parent is ListView parentTabViewListView)
+ {
+ tabViewListView = parentTabViewListView;
+ }
+ else if (parent is TabView parentTabView)
+ {
+ tabView = parentTabView;
+ }
+
+ if (tabViewListView != null && tabView != null)
+ {
+ break;
+ }
+
+ current = parent;
+ }
+
+ if (tabViewListView == null || tabView == null)
+ {
+ return;
+ }
+
+ // First, if there are tabs to the left or to the right of the tab on which this context menu is opening,
+ // then we'll include menu items to move this tab to the left or to the right.
+ //
+ // There are two possible cases for tab views: either they have explicitly set tab items, or they have a data item source set.
+ // To move a tab left or right with explicitly set tab items, we'll remove and replace the tab item itself.
+ // To move a tab left or right with a data item source set, we'll instead remove and replace the data item in the source list.
+ int index = tabViewListView.IndexFromContainer(item);
+
+ if (index > 0)
+ {
+ MenuFlyoutItem moveLeftItem = new() { Text = "Move tab left" };
+ moveLeftItem.Click += (s, args) =>
+ {
+ if (tabView.TabItemsSource is IList itemsSourceList)
+ {
+ var item = itemsSourceList[index];
+ itemsSourceList.RemoveAt(index);
+ itemsSourceList.Insert(index - 1, item);
+ }
+ else
+ {
+ var item = tabView.TabItems[index];
+ tabView.TabItems.RemoveAt(index);
+ tabView.TabItems.Insert(index - 1, item);
+ }
+ };
+ contextMenu.Items.Add(moveLeftItem);
+ }
+
+ if (index < tabViewListView.Items.Count - 1)
+ {
+ MenuFlyoutItem moveRightItem = new() { Text = "Move tab right" };
+ moveRightItem.Click += (s, args) =>
+ {
+ if (tabView.TabItemsSource is IList itemsSourceList)
+ {
+ var item = itemsSourceList[index];
+ itemsSourceList.RemoveAt(index);
+ itemsSourceList.Insert(index + 1, item);
+ }
+ else
+ {
+ var item = tabView.TabItems[index];
+ tabView.TabItems.RemoveAt(index);
+ tabView.TabItems.Insert(index + 1, item);
+ }
+ };
+ contextMenu.Items.Add(moveRightItem);
+ }
+ }
+ }
+}
diff --git a/WinUIGallery/Helper/UIHelper.cs b/WinUIGallery/Helper/UIHelper.cs
index 2d17a985a..49c0b327a 100644
--- a/WinUIGallery/Helper/UIHelper.cs
+++ b/WinUIGallery/Helper/UIHelper.cs
@@ -63,5 +63,22 @@ static public void AnnounceActionForAccessibility(UIElement ue, string annouceme
peer.RaiseNotificationEvent(AutomationNotificationKind.ActionCompleted,
AutomationNotificationProcessing.ImportantMostRecent, annoucement, activityID);
}
+
+ public static T GetParent(DependencyObject child) where T : DependencyObject
+ {
+ DependencyObject current = child;
+
+ while (current != null)
+ {
+ if (current is T parent)
+ {
+ return parent;
+ }
+
+ current = VisualTreeHelper.GetParent(current);
+ }
+
+ return null;
+ }
}
}
diff --git a/WinUIGallery/Helper/Win32WindowHelper.cs b/WinUIGallery/Helper/Win32WindowHelper.cs
index 79e5d2778..ca10f919a 100644
--- a/WinUIGallery/Helper/Win32WindowHelper.cs
+++ b/WinUIGallery/Helper/Win32WindowHelper.cs
@@ -7,8 +7,8 @@ namespace WinUIGallery.Helper
{
internal class Win32WindowHelper
{
- private static WinProc newWndProc = null;
- private static nint oldWndProc = nint.Zero;
+ private WinProc newWndProc = null;
+ private nint oldWndProc = nint.Zero;
private POINT? minWindowSize = null;
private POINT? maxWindowSize = null;
diff --git a/WinUIGallery/TabViewPages/MyTabContentControl.xaml b/WinUIGallery/TabViewPages/MyTabContentControl.xaml
index 70ec6f135..cb9ab5afa 100644
--- a/WinUIGallery/TabViewPages/MyTabContentControl.xaml
+++ b/WinUIGallery/TabViewPages/MyTabContentControl.xaml
@@ -13,7 +13,7 @@
-
-
+
+
diff --git a/WinUIGallery/TabViewPages/MyTabContentControl.xaml.cs b/WinUIGallery/TabViewPages/MyTabContentControl.xaml.cs
index 4d3072913..21ce40ce0 100644
--- a/WinUIGallery/TabViewPages/MyTabContentControl.xaml.cs
+++ b/WinUIGallery/TabViewPages/MyTabContentControl.xaml.cs
@@ -7,6 +7,14 @@ namespace WinUIGallery.TabViewPages
{
public sealed partial class MyTabContentControl : UserControl
{
+ public bool IsInProgress
+ {
+ get { return (bool)GetValue(IsInProgressProperty); }
+ set { SetValue(IsInProgressProperty, value); }
+ }
+
+ public static readonly DependencyProperty IsInProgressProperty = DependencyProperty.Register("IsInProgress", typeof(bool), typeof(MyTabContentControl), new PropertyMetadata(false));
+
public MyTabContentControl()
{
this.InitializeComponent();
diff --git a/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml b/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml
index 38c0658da..55baecb70 100644
--- a/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml
+++ b/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml
@@ -17,13 +17,27 @@
TabTearOutWindowRequested="Tabs_TabTearOutWindowRequested"
TabTearOutRequested="Tabs_TabTearOutRequested"
ExternalTornOutTabsDropping="Tabs_ExternalTornOutTabsDropping"
- ExternalTornOutTabsDropped="Tabs_ExternalTornOutTabsDropped">
+ ExternalTornOutTabsDropped="Tabs_ExternalTornOutTabsDropped"
+ TabItemsSource="{x:Bind TabItemDataList}">
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml.cs b/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml.cs
index 00d6e878c..15e86ec0f 100644
--- a/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml.cs
+++ b/WinUIGallery/TabViewPages/TabViewWindowingSamplePage.xaml.cs
@@ -1,30 +1,49 @@
-using System;
-using Windows.ApplicationModel.Core;
-using Windows.ApplicationModel.DataTransfer;
-using Windows.Foundation.Metadata;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Microsoft.UI.Dispatching;
+using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Hosting;
-using Microsoft.UI.Xaml.Navigation;
-using Microsoft.UI.Windowing;
-using WinUIGallery.Helper;
-using System.Threading;
-using Microsoft.UI.Dispatching;
-using System.Threading.Tasks;
-using Windows.System;
-using DispatcherQueueHandler = Microsoft.UI.Dispatching.DispatcherQueueHandler;
-using System.Linq;
using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Input;
+using Windows.System;
+using WinUIGallery.Helper;
namespace WinUIGallery.TabViewPages
{
+ public class TabItemData : DependencyObject
+ {
+ public string Header
+ {
+ get { return (string)GetValue(HeaderProperty); }
+ set { SetValue(HeaderProperty, value); }
+ }
+
+ public string Content
+ {
+ get { return (string)GetValue(ContentProperty); }
+ set { SetValue(ContentProperty, value); }
+ }
+
+ public bool IsInProgress
+ {
+ get { return (bool)GetValue(IsInProgressProperty); }
+ set { SetValue(IsInProgressProperty, value); }
+ }
+
+ public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(TabItemData), new PropertyMetadata(""));
+ public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(string), typeof(TabItemData), new PropertyMetadata(""));
+ public static readonly DependencyProperty IsInProgressProperty = DependencyProperty.Register("Content", typeof(bool), typeof(TabItemData), new PropertyMetadata(false));
+ }
+
public sealed partial class TabViewWindowingSamplePage : Page
{
- private const string DataIdentifier = "MyTabItem";
+ private static readonly List windowList = [];
+ private static Window tabTearOutWindow = null;
+
private Win32WindowHelper win32WindowHelper;
- private Window tabTearOutWindow = null;
+
+ private readonly ObservableCollection tabItemDataList = [];
+ public ObservableCollection TabItemDataList => tabItemDataList;
public TabViewWindowingSamplePage()
{
@@ -45,6 +64,35 @@ private void TabViewWindowingSamplePage_Loaded(object sender, RoutedEventArgs e)
currentWindow.ExtendsContentIntoTitleBar = true;
currentWindow.SetTitleBar(CustomDragRegion);
CustomDragRegion.MinWidth = 188;
+
+ if (!windowList.Contains(currentWindow))
+ {
+ windowList.Add(currentWindow);
+
+ // We can have a window we're dragging in two different ways: either we created a new window
+ // for tearing out purposes, or we're dragging an existing window.
+ // If we created a new window, tabTearOutWindow will be set to that window.
+ // Otherwise, it won't be set to anything, so we should set it to the window we're currently dragging.
+ var inputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(currentWindow.AppWindow.Id);
+
+ inputNonClientPointerSource.EnteredMoveSize += (s, args) =>
+ {
+ if (tabTearOutWindow == null)
+ {
+ tabTearOutWindow = currentWindow;
+ }
+ };
+
+ inputNonClientPointerSource.ExitedMoveSize += (s, args) =>
+ {
+ tabTearOutWindow = null;
+ };
+
+ currentWindow.Closed += (s, args) =>
+ {
+ windowList.Remove(currentWindow);
+ };
+ }
}
public void LoadDemoData()
@@ -52,27 +100,15 @@ public void LoadDemoData()
// Main Window -- add some default items
for (int i = 0; i < 3; i++)
{
- Tabs.TabItems.Add(new TabViewItem() { IconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource() { Symbol = Symbol.Placeholder }, Header = $"Item {i}", Content = new MyTabContentControl() { DataContext = $"Page {i}" } });
+ TabItemDataList.Add(new TabItemData() { Header = $"Item {i}", Content = $"Page {i}" });
}
Tabs.SelectedIndex = 0;
}
- public void AddTabToTabs(TabViewItem tab)
- {
- Tabs.TabItems.Add(tab);
- }
-
private void Tabs_TabTearOutWindowRequested(TabView sender, TabViewTabTearOutWindowRequestedEventArgs args)
{
- var newPage = new TabViewWindowingSamplePage();
-
- tabTearOutWindow = WindowHelper.CreateWindow();
- tabTearOutWindow.ExtendsContentIntoTitleBar = true;
- tabTearOutWindow.Content = newPage;
- tabTearOutWindow.AppWindow.SetIcon("Assets/Tiles/GalleryIcon.ico");
- newPage.SetupWindowMinSize(tabTearOutWindow);
-
+ tabTearOutWindow = CreateNewWindow();
args.NewWindowId = tabTearOutWindow.AppWindow.Id;
}
@@ -83,13 +119,7 @@ private void Tabs_TabTearOutRequested(TabView sender, TabViewTabTearOutRequested
return;
}
- var newPage = (TabViewWindowingSamplePage)tabTearOutWindow.Content;
-
- foreach (TabViewItem tab in args.Tabs.Cast())
- {
- GetParentTabView(tab)?.TabItems.Remove(tab);
- newPage.AddTabToTabs(tab);
- }
+ MoveDataItems(TabItemDataList, GetTabItemDataList(tabTearOutWindow), args.Items, 0);
}
private void Tabs_ExternalTornOutTabsDropping(TabView sender, TabViewExternalTornOutTabsDroppingEventArgs args)
@@ -99,65 +129,175 @@ private void Tabs_ExternalTornOutTabsDropping(TabView sender, TabViewExternalTor
private void Tabs_ExternalTornOutTabsDropped(TabView sender, TabViewExternalTornOutTabsDroppedEventArgs args)
{
- int position = 0;
-
- foreach (TabViewItem tab in args.Tabs.Cast())
- {
- GetParentTabView(tab)?.TabItems.Remove(tab);
- sender.TabItems.Insert(args.DropIndex + position, tab);
- position++;
- }
+ MoveDataItems(GetTabItemDataList(tabTearOutWindow), TabItemDataList, args.Items, args.DropIndex);
}
- private TabView GetParentTabView(TabViewItem tab)
+ private static Window CreateNewWindow()
{
- DependencyObject current = tab;
+ var newPage = new TabViewWindowingSamplePage();
+
+ var window = WindowHelper.CreateWindow();
+ window.ExtendsContentIntoTitleBar = true;
+ window.Content = newPage;
+ window.AppWindow.SetIcon("Assets/Tiles/GalleryIcon.ico");
+ newPage.SetupWindowMinSize(window);
- while (current != null)
+ return window;
+ }
+
+ private static void MoveDataItems(ObservableCollection source, ObservableCollection destination, object[] dataItems, int index)
+ {
+ foreach (object tabItemData in dataItems)
{
- if (current is TabView tabView)
- {
- return tabView;
- }
+ source.Remove((TabItemData)tabItemData);
+ destination.Insert(index, (TabItemData)tabItemData);
- current = VisualTreeHelper.GetParent(current);
+ index++;
}
-
- return null;
}
- private TabViewItem CreateNewTVI(string header, string dataContext)
+ private static TabView GetTabView(Window window)
{
- var newTab = new TabViewItem()
- {
- IconSource = new Microsoft.UI.Xaml.Controls.SymbolIconSource()
- {
- Symbol = Symbol.Placeholder
- },
- Header = header,
- Content = new MyTabContentControl()
- {
- DataContext = dataContext
- }
- };
+ var tabViewPage = (TabViewWindowingSamplePage)window.Content;
+ return tabViewPage.Tabs;
+ }
- return newTab;
+ private static ObservableCollection GetTabItemDataList(Window window)
+ {
+ var tabViewPage = (TabViewWindowingSamplePage)window.Content;
+ return tabViewPage.TabItemDataList;
}
private void Tabs_AddTabButtonClick(TabView sender, object args)
{
- var tab = CreateNewTVI("New Item", "New Item");
- sender.TabItems.Add(tab);
+ TabItemDataList.Add(new TabItemData() { Header = "New Item", Content = "New Item" });
}
private void Tabs_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
{
- sender.TabItems.Remove(args.Tab);
+ TabItemDataList.Remove((TabItemData)args.Item);
- if (sender.TabItems.Count == 0)
+ if (TabItemDataList.Count == 0)
{
WindowHelper.GetWindowForElement(this).Close();
}
}
+
+ private void TabViewContextMenu_Opening(object sender, object e)
+ {
+ // The contents of the context menu depends on the state of the application, so we'll build it dynamically.
+ MenuFlyout contextMenu = (MenuFlyout)sender;
+
+ // We'll first put the generic tab view context menu items in place.
+ TabViewHelper.PopulateTabViewContextMenu(contextMenu);
+
+ var tabViewItem = (TabViewItem)contextMenu.Target;
+ ListView tabViewListView = UIHelper.GetParent(tabViewItem);
+ var window = WindowHelper.GetWindowForElement(tabViewItem);
+
+ if (tabViewListView == null)
+ {
+ return;
+ }
+
+ var tabItemDataList = GetTabItemDataList(window);
+ var tabDataItem = tabViewListView.ItemFromContainer(tabViewItem);
+
+ // Second, we'll include menu items to move this tab to those windows.
+ MenuFlyoutSubItem moveSubItem = new() { Text = "Move tab to" };
+
+ // If there are at least two tabs in this window, we'll include the option to move the tab to a new window.
+ // This option doesn't make sense if there is only one tab, because in that case the source window would have no tabs left,
+ // and we would effectively be just moving the tab from one window with only one tab to another window with only one tab,
+ // leaving us in the same state as we started in.
+ if (tabItemDataList.Count > 1)
+ {
+ MenuFlyoutItem newWindowItem = new() { Text = "New window", Icon = new SymbolIcon(Symbol.NewWindow) };
+
+ newWindowItem.Click += (s, args) =>
+ {
+ var newWindow = CreateNewWindow();
+ MoveDataItems(tabItemDataList, GetTabItemDataList(newWindow), [tabDataItem], 0);
+
+ // Activating the window and setting its selected item hit a failed assert if the content hasn't been loaded yet,
+ // so we'll defer these for a tick to allow that to happen first.
+ DispatcherQueue.TryEnqueue(() =>
+ {
+ newWindow.Activate();
+ GetTabView(newWindow).SelectedItem = tabDataItem;
+ });
+ };
+
+ moveSubItem.Items.Add(newWindowItem);
+ }
+
+ // If there are other windows that exist, we'll include the option to move the tab to those windows.
+ List moveToWindowItems = [];
+
+ foreach (Window otherWindow in windowList)
+ {
+ if (window == otherWindow)
+ {
+ continue;
+ }
+
+ var windowTabItemDataList = GetTabItemDataList(otherWindow);
+
+ if (windowTabItemDataList.Count > 0)
+ {
+ string moveToWindowItemText = $"Window with \"{windowTabItemDataList[0].Header}\"";
+
+ if (windowTabItemDataList.Count > 1)
+ {
+ int remainingTabCount = windowTabItemDataList.Count - 1;
+ moveToWindowItemText += $" and {remainingTabCount} other tab{(remainingTabCount == 1 ? "" : "s")}";
+ }
+
+ MenuFlyoutItem moveToWindowItem = new() { Text = moveToWindowItemText, Icon = new SymbolIcon(Symbol.BackToWindow) };
+ moveToWindowItem.Click += (s, args) =>
+ {
+ MoveDataItems(tabItemDataList, windowTabItemDataList, [tabDataItem], windowTabItemDataList.Count);
+
+ // If removing the tab from its current tab view will leave no tabs remaining, then we'll close the tab view's window.
+ if (tabItemDataList.Count == 0)
+ {
+ window.Close();
+ }
+
+ // Activating the window and setting its selected item hit a failed assert if the content hasn't been loaded yet,
+ // so we'll defer these for a tick to allow that to happen first.
+ DispatcherQueue.TryEnqueue(() =>
+ {
+ otherWindow.Activate();
+ GetTabView(otherWindow).SelectedItem = tabDataItem;
+ });
+ };
+ moveToWindowItems.Add(moveToWindowItem);
+ }
+ }
+
+ // Only include a separator if we're going to be including at least one move-to-window item.
+ if (moveToWindowItems.Count > 0)
+ {
+ contextMenu.Items.Add(new MenuFlyoutSeparator());
+ }
+
+ foreach (MenuFlyoutItem moveToWindowItem in moveToWindowItems)
+ {
+ moveSubItem.Items.Add(moveToWindowItem);
+ }
+
+ // Only include the move-to sub-item if it has any items.
+ if (moveSubItem.Items.Count > 0)
+ {
+ contextMenu.Items.Add(moveSubItem);
+ }
+
+ // If the context menu ended up with no items at all, then we'll prevent it from being shown.
+ if (contextMenu.Items.Count == 0)
+ {
+ contextMenu.Hide();
+ }
+ }
}
}
diff --git a/WinUIGallery/WinUIGallery.csproj b/WinUIGallery/WinUIGallery.csproj
index b4defb30b..6f22bb2eb 100644
--- a/WinUIGallery/WinUIGallery.csproj
+++ b/WinUIGallery/WinUIGallery.csproj
@@ -313,4 +313,8 @@
+
+
+
+
\ No newline at end of file