Skip to content
This repository was archived by the owner on Nov 29, 2022. It is now read-only.

Commit 7e4a2db

Browse files
Add Dragging Tabs Between Windows
1 parent 7fe8c31 commit 7e4a2db

File tree

6 files changed

+181
-1
lines changed

6 files changed

+181
-1
lines changed
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace TabViewTear.Services
8+
{
9+
/// <summary>
10+
/// Information about Message sent between Windows.
11+
/// </summary>
12+
public class MessageEventArgs: EventArgs
13+
{
14+
public int FromId { get; private set; }
15+
16+
public int ToId { get; private set; }
17+
18+
public string Message { get; private set; }
19+
20+
/// <summary>
21+
/// Extra misc data, should be primitive or thread-safe type.
22+
/// </summary>
23+
public object Data { get; private set; }
24+
25+
public MessageEventArgs(int from, int to, string message, object data = null)
26+
{
27+
FromId = from;
28+
ToId = to;
29+
Message = message;
30+
Data = data;
31+
}
32+
}
33+
}

TabViewTear/Services/ViewLifetimeControl.cs

+7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public sealed class ViewLifetimeControl
3434
// Optional context to provide from window opener
3535
public string Context { get; set; }
3636

37+
public event EventHandler<MessageEventArgs> MessageReceived;
38+
3739
public event ViewReleasedHandler Released
3840
{
3941
add
@@ -76,6 +78,11 @@ public static ViewLifetimeControl CreateForCurrentView()
7678
return new ViewLifetimeControl(CoreWindow.GetForCurrentThread());
7779
}
7880

81+
public void SendMessage(string message, int fromid, object data = null)
82+
{
83+
MessageReceived?.Invoke(this, new MessageEventArgs(fromid, Id, message, data));
84+
}
85+
7986
// Signals that the view is being interacted with by another view,
8087
// so it shouldn't be closed even if it becomes "consolidated"
8188
public int StartViewInUse()

TabViewTear/Services/WindowManagerService.cs

+18
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class WindowManagerService
2828

2929
public CoreDispatcher MainDispatcher { get; private set; }
3030

31+
public event EventHandler<MessageEventArgs> MainWindowMessageReceived;
32+
3133
public void Initialize()
3234
{
3335
MainViewId = ApplicationView.GetForCurrentView().Id;
@@ -79,5 +81,21 @@ await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority
7981
}
8082

8183
public bool IsWindowOpen(string windowTitle) => SecondaryViews.Any(v => v.Title == windowTitle);
84+
85+
public ViewLifetimeControl GetWindowById(int id) => SecondaryViews.FirstOrDefault(v => v.Id == id);
86+
87+
public void SendMessage(int toid, string message, object data = null)
88+
{
89+
if (toid == MainViewId)
90+
{
91+
// Special case for main window
92+
MainWindowMessageReceived?.Invoke(this, new MessageEventArgs(ApplicationView.GetForCurrentView().Id, toid, message, data));
93+
}
94+
else
95+
{
96+
// Any secondary window
97+
GetWindowById(toid)?.SendMessage(message, ApplicationView.GetForCurrentView().Id, data);
98+
}
99+
}
82100
}
83101
}

TabViewTear/TabViewTear.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
<Compile Include="Services\DragAndDrop\DropConfiguration.cs" />
123123
<Compile Include="Services\DragAndDrop\ListViewDropConfiguration.cs" />
124124
<Compile Include="Services\DragAndDrop\VisualDropConfiguration.cs" />
125+
<Compile Include="Services\MessageEventArgs.cs" />
125126
<Compile Include="Services\NavigationService.cs" />
126127
<Compile Include="Services\ThemeSelectorService.cs" />
127128
<Compile Include="Services\ViewLifetimeControl.cs" />

TabViewTear/Views/MainPage.xaml

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
mc:Ignorable="d">
1010

1111
<controls:TabView
12+
x:Name="Items"
1213
TabWidthBehavior="Equal"
1314
SelectedTabWidth="200"
14-
x:Name="Items"
1515
CanDragItems="True"
1616
CanReorderItems="True"
1717
AllowDrop="True"
1818
TabDraggedOutside="Items_TabDraggedOutside"
1919
SelectionChanged="Items_SelectionChanged"
20+
DragItemsStarting="Items_DragItemsStarting"
21+
DragItemsCompleted="Items_DragItemsCompleted"
22+
DragOver="Items_DragOver"
23+
Drop="Items_Drop"
2024
ItemsSource="{x:Bind TabItems}">
2125
<controls:TabView.Resources>
2226
<x:Double x:Key="TabViewItemHeaderMinWidth">90</x:Double>

TabViewTear/Views/MainPage.xaml.cs

+117
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.CompilerServices;
77
using TabViewTear.Models;
88
using TabViewTear.Services;
9+
using Windows.ApplicationModel.DataTransfer;
910
using Windows.UI.Core;
1011
using Windows.UI.ViewManagement;
1112
using Windows.UI.Xaml;
@@ -16,6 +17,11 @@ namespace TabViewTear.Views
1617
{
1718
public sealed partial class MainPage : Page, INotifyPropertyChanged
1819
{
20+
private const string DataIdentifier = "TabData";
21+
private const string DataIndex = "TabIndex";
22+
private const string DataWindow = "TabWindow";
23+
private const string CommandClose = "Close";
24+
1925
ObservableCollection<DataItem> TabItems = new ObservableCollection<DataItem>();
2026

2127
public MainPage()
@@ -26,6 +32,8 @@ public MainPage()
2632
public event PropertyChangedEventHandler PropertyChanged;
2733

2834
private ViewLifetimeControl _viewLifetimeControl;
35+
36+
#region Handle Window Lifetime
2937
protected override void OnNavigatedTo(NavigationEventArgs e)
3038
{
3139
base.OnNavigatedTo(e);
@@ -36,6 +44,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
3644
_viewLifetimeControl.StartViewInUse();
3745
// Register for window close
3846
_viewLifetimeControl.Released += OnViewLifetimeControlReleased;
47+
_viewLifetimeControl.MessageReceived += OnViewLifetimeControlMessageReceived;
3948
// Deserialize passed in item to display in this window
4049
TabItems.Add(JsonConvert.DeserializeObject<DataItem>(_viewLifetimeControl.Context.ToString()));
4150
_viewLifetimeControl.Context = null;
@@ -45,9 +54,18 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
4554
{
4655
// Main Window Start
4756
InitializeTestData();
57+
58+
WindowManagerService.Current.MainWindowMessageReceived += OnViewLifetimeControlMessageReceived;
4859
}
4960
}
5061

62+
private MessageEventArgs _lastMsg;
63+
64+
private async void OnViewLifetimeControlMessageReceived(object sender, MessageEventArgs e)
65+
{
66+
_lastMsg = e; // Store to complete in DragItemsCompleted.
67+
}
68+
5169
private async void OnViewLifetimeControlReleased(object sender, EventArgs e)
5270
{
5371
_viewLifetimeControl.Released -= OnViewLifetimeControlReleased;
@@ -56,7 +74,9 @@ await WindowManagerService.Current.MainDispatcher.RunAsync(CoreDispatcherPriorit
5674
WindowManagerService.Current.SecondaryViews.Remove(_viewLifetimeControl);
5775
});
5876
}
77+
#endregion
5978

79+
#region Handle Dragging Tab to Create Window
6080
private async void Items_TabDraggedOutside(object sender, Microsoft.Toolkit.Uwp.UI.Controls.TabDraggedOutsideEventArgs e)
6181
{
6282
if (e.Item is DataItem data)
@@ -69,12 +89,15 @@ private async void Items_TabDraggedOutside(object sender, Microsoft.Toolkit.Uwp.
6989

7090
if (TabItems.Count == 0)
7191
{
92+
// TODO: If drag wasn't received by another window and last tab, ignore?
7293
// No tabs left on main window, 'switch' to window just created to hide the main view
7394
await ApplicationViewSwitcher.SwitchAsync(lifetimecontrol.Id, ApplicationView.GetForCurrentView().Id, ApplicationViewSwitchingOptions.ConsolidateViews);
7495
}
7596
}
7697
}
98+
#endregion
7799

100+
#region Handle Tab Change Updating Window Title
78101
private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
79102
{
80103
// Update window title with current item
@@ -84,6 +107,100 @@ private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
84107
ApplicationView.GetForCurrentView().Title = data.Title;
85108
}
86109
}
110+
#endregion
111+
112+
#region Handle Dragging Tabs between windows
113+
private void Items_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
114+
{
115+
// In Initial Window we need to serialize our tab data.
116+
var item = e.Items.FirstOrDefault();
117+
118+
if (item is DataItem data)
119+
{
120+
// Add actual data
121+
e.Data.Properties.Add(DataIdentifier, JsonConvert.SerializeObject(data));
122+
// Add our index so we know where to remove from later (if needed)
123+
e.Data.Properties.Add(DataIndex, Items.IndexFromContainer(Items.ContainerFromItem(data)));
124+
// Add Window Id to know if we're transferring to a different window.
125+
e.Data.Properties.Add(DataWindow, ApplicationView.GetForCurrentView().Id);
126+
}
127+
}
128+
129+
private void Items_DragOver(object sender, DragEventArgs e)
130+
{
131+
// Called before we drop to see if we will accept a drop.
132+
133+
// Do we have Tab Data?
134+
if (e.DataView.Properties.ContainsKey(DataIdentifier))
135+
{
136+
// Tell OS that we allow moving item.
137+
e.AcceptedOperation = DataPackageOperation.Move;
138+
}
139+
}
140+
141+
private void Items_Drop(object sender, DragEventArgs e)
142+
{
143+
// Called when we actually get the drop, let's get the data and add our tab.
144+
var pos = e.GetPosition(this);
145+
146+
if (e.DataView.Properties.TryGetValue(DataIdentifier, out object value) && value is string str)
147+
{
148+
var data = JsonConvert.DeserializeObject<DataItem>(str);
149+
150+
if (data != null)
151+
{
152+
//var minpos = pos;
153+
//var mindist = 1000.0;
154+
//var minindex = -1;
155+
//foreach (var item in Items.Items)
156+
//{
157+
// var tab = Items.ContainerFromItem(item);
158+
// var p = e.GetPosition(tab as UIElement);
159+
// var amt = Math.Abs(p.X - minpos.X) + Math.Abs(p.Y - minpos.Y);
160+
// if (amt < mindist)
161+
// {
162+
// minindex = Items.IndexFromContainer(tab);
163+
// mindist = amt;
164+
// minpos = p;
165+
// }
166+
//}
167+
168+
169+
// TODO: Figure out how to insert this in the right place.
170+
TabItems.Add(data);
171+
172+
Items.SelectedItem = data; // Select new item.
173+
174+
// Send message to origintator to remove the tab.
175+
WindowManagerService.Current.SendMessage((e.DataView.Properties[DataWindow] as int?).Value, CommandClose, e.DataView.Properties[DataIndex]);
176+
}
177+
}
178+
}
179+
180+
private async void Items_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
181+
{
182+
if (args.DropResult == DataPackageOperation.Move && _lastMsg != null)
183+
{
184+
switch (_lastMsg.Message)
185+
{
186+
case CommandClose:
187+
if (_lastMsg.Data is int value)
188+
{
189+
TabItems.RemoveAt(value);
190+
191+
if (TabItems.Count == 0)
192+
{
193+
// No tabs left on main window, 'switch' to window just created to hide the main view
194+
await ApplicationViewSwitcher.SwitchAsync(_lastMsg.FromId, ApplicationView.GetForCurrentView().Id, ApplicationViewSwitchingOptions.ConsolidateViews);
195+
}
196+
}
197+
198+
_lastMsg = null;
199+
break;
200+
}
201+
}
202+
}
203+
#endregion
87204

88205
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
89206
{

0 commit comments

Comments
 (0)