├── TabViewTear
├── Assets
│ ├── LargeTile.scale-200.png
│ ├── SmallTile.scale-200.png
│ ├── StoreLogo.scale-100.png
│ ├── StoreLogo.scale-200.png
│ ├── SplashScreen.scale-200.png
│ ├── Square44x44Logo.scale-100.png
│ ├── Square44x44Logo.scale-200.png
│ ├── Wide310x150Logo.scale-200.png
│ ├── Square150x150Logo.scale-200.png
│ ├── Square44x44Logo.targetsize-16.png
│ ├── Square44x44Logo.targetsize-48.png
│ ├── Square44x44Logo.altform-unplated_targetsize-16.png
│ └── Square44x44Logo.altform-unplated_targetsize-48.png
├── .editorconfig
├── Styles
│ ├── _FontSizes.xaml
│ ├── _Colors.xaml
│ ├── Page.xaml
│ ├── _Thickness.xaml
│ └── TextBlock.xaml
├── Helpers
│ ├── ResourceExtensions.cs
│ ├── Singleton.cs
│ ├── Json.cs
│ └── SettingsStorageExtensions.cs
├── App.xaml
├── Services
│ ├── MessageEventArgs.cs
│ ├── ThemeSelectorService.cs
│ ├── NavigationService.cs
│ ├── WindowManagerService.cs
│ ├── ActivationService.cs
│ └── ViewLifetimeControl.cs
├── Activation
│ ├── ActivationHandler.cs
│ └── DefaultLaunchActivationHandler.cs
├── Models
│ └── DataItem.cs
├── Properties
│ ├── AssemblyInfo.cs
│ └── Default.rd.xml
├── App.xaml.cs
├── Package.appxmanifest
├── Strings
│ └── en-us
│ │ └── Resources.resw
├── Views
│ ├── MainPage.xaml
│ └── MainPage.xaml.cs
└── TabViewTear.csproj
├── LICENSE.md
├── TabViewTear.sln
├── .gitattributes
├── .gitignore
└── README.md
/TabViewTear/Assets/LargeTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/LargeTile.scale-200.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/SmallTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/SmallTile.scale-200.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/StoreLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/StoreLogo.scale-200.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Square44x44Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Square44x44Logo.scale-100.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Square44x44Logo.targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Square44x44Logo.targetsize-16.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Square44x44Logo.targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Square44x44Logo.targetsize-48.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Square44x44Logo.altform-unplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
--------------------------------------------------------------------------------
/TabViewTear/Assets/Square44x44Logo.altform-unplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CommunityToolkit/Sample-TabView-TearOff/HEAD/TabViewTear/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
--------------------------------------------------------------------------------
/TabViewTear/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | [*]
5 | end_of_line = crlf
6 |
7 | [*.{cs,xaml}]
8 | indent_style = space
9 | indent_size = 4
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
--------------------------------------------------------------------------------
/TabViewTear/Styles/_FontSizes.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 | 28
6 | 16
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TabViewTear/Styles/_Colors.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/TabViewTear/Styles/Page.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/TabViewTear/Helpers/ResourceExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | using Windows.ApplicationModel.Resources;
5 |
6 | namespace TabViewTear.Helpers
7 | {
8 | internal static class ResourceExtensions
9 | {
10 | private static ResourceLoader _resLoader = new ResourceLoader();
11 |
12 | public static string GetLocalized(this string resourceKey)
13 | {
14 | return _resLoader.GetString(resourceKey);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TabViewTear/Helpers/Singleton.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 |
4 | namespace TabViewTear.Helpers
5 | {
6 | internal static class Singleton
7 | where T : new()
8 | {
9 | private static ConcurrentDictionary _instances = new ConcurrentDictionary();
10 |
11 | public static T Instance
12 | {
13 | get
14 | {
15 | return _instances.GetOrAdd(typeof(T), (t) => new T());
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TabViewTear/Helpers/Json.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace TabViewTear.Helpers
7 | {
8 | public static class Json
9 | {
10 | public static async Task ToObjectAsync(string value)
11 | {
12 | return await Task.Run(() =>
13 | {
14 | return JsonConvert.DeserializeObject(value);
15 | });
16 | }
17 |
18 | public static async Task StringifyAsync(object value)
19 | {
20 | return await Task.Run(() =>
21 | {
22 | return JsonConvert.SerializeObject(value);
23 | });
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/TabViewTear/Styles/_Thickness.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | 0,0,12,7
7 | 0, 20, 0, 48
8 |
9 |
10 | 12,0,12,0
11 | 12,12,12,12
12 |
13 |
14 | 5, 5, 5, 5
15 | 0, 8, 0, 0
16 |
17 |
--------------------------------------------------------------------------------
/TabViewTear/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/TabViewTear/Services/MessageEventArgs.cs:
--------------------------------------------------------------------------------
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 | ///
10 | /// Information about Message sent between Windows.
11 | ///
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 | ///
21 | /// Extra misc data, should be primitive or thread-safe type.
22 | ///
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 | }
34 |
--------------------------------------------------------------------------------
/TabViewTear/Activation/ActivationHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace TabViewTear.Activation
5 | {
6 | // For more information on application activation see https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md
7 | internal abstract class ActivationHandler
8 | {
9 | public abstract bool CanHandle(object args);
10 |
11 | public abstract Task HandleAsync(object args);
12 | }
13 |
14 | internal abstract class ActivationHandler : ActivationHandler
15 | where T : class
16 | {
17 | protected abstract Task HandleInternalAsync(T args);
18 |
19 | public override async Task HandleAsync(object args)
20 | {
21 | await HandleInternalAsync(args as T);
22 | }
23 |
24 | public override bool CanHandle(object args)
25 | {
26 | return args is T && CanHandleInternal(args as T);
27 | }
28 |
29 | protected virtual bool CanHandleInternal(T args)
30 | {
31 | return true;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/TabViewTear/Styles/TextBlock.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
14 |
15 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # TabView Tear-Off Sample
2 |
3 | Copyright (c) .NET Foundation and Contributors
4 |
5 | All rights reserved.
6 |
7 | # MIT License (MIT)
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14 |
--------------------------------------------------------------------------------
/TabViewTear/Models/DataItem.cs:
--------------------------------------------------------------------------------
1 | using Windows.UI.Xaml;
2 |
3 | namespace TabViewTear.Models
4 | {
5 | public class DataItem: DependencyObject
6 | {
7 | public string Title
8 | {
9 | get { return (string)GetValue(TitleProperty); }
10 | set { SetValue(TitleProperty, value); }
11 | }
12 |
13 | // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
14 | public static readonly DependencyProperty TitleProperty =
15 | DependencyProperty.Register("Title", typeof(string), typeof(DataItem), new PropertyMetadata(string.Empty));
16 |
17 | public string Content
18 | {
19 | get { return (string)GetValue(ContentProperty); }
20 | set { SetValue(ContentProperty, value); }
21 | }
22 |
23 | // Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc...
24 | public static readonly DependencyProperty ContentProperty =
25 | DependencyProperty.Register("Content", typeof(string), typeof(DataItem), new PropertyMetadata(string.Empty));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TabViewTear/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 |
6 | // General Information about an assembly is controlled through the following
7 | // set of attributes. Change these attribute values to modify the information
8 | // associated with an assembly.
9 | [assembly: AssemblyTitle("TabViewTear")]
10 | [assembly: AssemblyDescription("")]
11 | [assembly: AssemblyConfiguration("")]
12 | [assembly: AssemblyCompany("")]
13 | [assembly: AssemblyProduct("TabViewTear")]
14 | [assembly: AssemblyCopyright("Copyright © 2018")]
15 | [assembly: AssemblyTrademark("")]
16 | [assembly: AssemblyCulture("")]
17 |
18 | // Version information for an assembly consists of the following four values:
19 | //
20 | // Major Version
21 | // Minor Version
22 | // Build Number
23 | // Revision
24 | //
25 | // You can specify all the values or you can default the Build and Revision Numbers
26 | // by using the '*' as shown below:
27 | // [assembly: AssemblyVersion("1.0.*")]
28 | [assembly: AssemblyVersion("1.0.0.0")]
29 | [assembly: AssemblyFileVersion("1.0.0.0")]
30 | [assembly: ComVisible(false)]
31 |
--------------------------------------------------------------------------------
/TabViewTear/Activation/DefaultLaunchActivationHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | using TabViewTear.Services;
5 |
6 | using Windows.ApplicationModel.Activation;
7 |
8 | namespace TabViewTear.Activation
9 | {
10 | internal class DefaultLaunchActivationHandler : ActivationHandler
11 | {
12 | private readonly Type _navElement;
13 |
14 | public DefaultLaunchActivationHandler(Type navElement)
15 | {
16 | _navElement = navElement;
17 | }
18 |
19 | protected override async Task HandleInternalAsync(LaunchActivatedEventArgs args)
20 | {
21 | // When the navigation stack isn't restored, navigate to the first page and configure
22 | // the new page by passing required information in the navigation parameter
23 | NavigationService.Navigate(_navElement, args.Arguments);
24 |
25 | await Task.CompletedTask;
26 | }
27 |
28 | protected override bool CanHandleInternal(LaunchActivatedEventArgs args)
29 | {
30 | // None of the ActivationHandlers has handled the app activation
31 | return NavigationService.Frame.Content == null;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/TabViewTear/Properties/Default.rd.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/TabViewTear/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using TabViewTear.Services;
4 |
5 | using Windows.ApplicationModel.Activation;
6 | using Windows.UI.Xaml;
7 |
8 | namespace TabViewTear
9 | {
10 | public sealed partial class App : Application
11 | {
12 | private Lazy _activationService;
13 |
14 | private ActivationService ActivationService
15 | {
16 | get { return _activationService.Value; }
17 | }
18 |
19 | public App()
20 | {
21 | InitializeComponent();
22 |
23 | // Deferred execution until used. Check https://msdn.microsoft.com/library/dd642331(v=vs.110).aspx for further info on Lazy class.
24 | _activationService = new Lazy(CreateActivationService);
25 | }
26 |
27 | protected override async void OnLaunched(LaunchActivatedEventArgs args)
28 | {
29 | if (!args.PrelaunchActivated)
30 | {
31 | await ActivationService.ActivateAsync(args);
32 | }
33 | }
34 |
35 | protected override async void OnActivated(IActivatedEventArgs args)
36 | {
37 | await ActivationService.ActivateAsync(args);
38 | }
39 |
40 | private ActivationService CreateActivationService()
41 | {
42 | return new ActivationService(this, typeof(Views.MainPage));
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/TabViewTear/Services/ThemeSelectorService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | using TabViewTear.Helpers;
5 |
6 | using Windows.ApplicationModel.Core;
7 | using Windows.Storage;
8 | using Windows.UI.Core;
9 | using Windows.UI.Xaml;
10 |
11 | namespace TabViewTear.Services
12 | {
13 | public static class ThemeSelectorService
14 | {
15 | private const string SettingsKey = "AppBackgroundRequestedTheme";
16 |
17 | public static ElementTheme Theme { get; set; } = ElementTheme.Default;
18 |
19 | public static async Task InitializeAsync()
20 | {
21 | Theme = await LoadThemeFromSettingsAsync();
22 | }
23 |
24 | public static async Task SetThemeAsync(ElementTheme theme)
25 | {
26 | Theme = theme;
27 |
28 | await SetRequestedThemeAsync();
29 | await SaveThemeInSettingsAsync(Theme);
30 | }
31 |
32 | public static async Task SetRequestedThemeAsync()
33 | {
34 | foreach (var view in CoreApplication.Views)
35 | {
36 | await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
37 | {
38 | if (Window.Current.Content is FrameworkElement frameworkElement)
39 | {
40 | frameworkElement.RequestedTheme = Theme;
41 | }
42 | });
43 | }
44 | }
45 |
46 | private static async Task LoadThemeFromSettingsAsync()
47 | {
48 | ElementTheme cacheTheme = ElementTheme.Default;
49 | string themeName = await ApplicationData.Current.LocalSettings.ReadAsync(SettingsKey);
50 |
51 | if (!string.IsNullOrEmpty(themeName))
52 | {
53 | Enum.TryParse(themeName, out cacheTheme);
54 | }
55 |
56 | return cacheTheme;
57 | }
58 |
59 | private static async Task SaveThemeInSettingsAsync(ElementTheme theme)
60 | {
61 | await ApplicationData.Current.LocalSettings.SaveAsync(SettingsKey, theme.ToString());
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/TabViewTear/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TabViewTear
7 | mhawker
8 | Assets\StoreLogo.png
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/TabViewTear.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28010.2048
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TabViewTear", "TabViewTear\TabViewTear.csproj", "{BAE65131-3FC5-4042-A0F6-958783E3B7CC}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|ARM = Debug|ARM
11 | Debug|x64 = Debug|x64
12 | Debug|x86 = Debug|x86
13 | Release|ARM = Release|ARM
14 | Release|x64 = Release|x64
15 | Release|x86 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|ARM.ActiveCfg = Debug|ARM
19 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|ARM.Build.0 = Debug|ARM
20 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|ARM.Deploy.0 = Debug|ARM
21 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|x64.ActiveCfg = Debug|x64
22 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|x64.Build.0 = Debug|x64
23 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|x64.Deploy.0 = Debug|x64
24 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|x86.ActiveCfg = Debug|x86
25 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|x86.Build.0 = Debug|x86
26 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Debug|x86.Deploy.0 = Debug|x86
27 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|ARM.ActiveCfg = Release|ARM
28 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|ARM.Build.0 = Release|ARM
29 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|ARM.Deploy.0 = Release|ARM
30 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|x64.ActiveCfg = Release|x64
31 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|x64.Build.0 = Release|x64
32 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|x64.Deploy.0 = Release|x64
33 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|x86.ActiveCfg = Release|x86
34 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|x86.Build.0 = Release|x86
35 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}.Release|x86.Deploy.0 = Release|x86
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {1C4966DE-414D-4127-942E-7FCF59DDBDC5}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/TabViewTear/Services/NavigationService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Windows.UI.Xaml;
4 | using Windows.UI.Xaml.Controls;
5 | using Windows.UI.Xaml.Media.Animation;
6 | using Windows.UI.Xaml.Navigation;
7 |
8 | namespace TabViewTear.Services
9 | {
10 | public static class NavigationService
11 | {
12 | public static event NavigatedEventHandler Navigated;
13 |
14 | public static event NavigationFailedEventHandler NavigationFailed;
15 |
16 | private static Frame _frame;
17 | private static object _lastParamUsed;
18 |
19 | public static Frame Frame
20 | {
21 | get
22 | {
23 | if (_frame == null)
24 | {
25 | _frame = Window.Current.Content as Frame;
26 | RegisterFrameEvents();
27 | }
28 |
29 | return _frame;
30 | }
31 |
32 | set
33 | {
34 | UnregisterFrameEvents();
35 | _frame = value;
36 | RegisterFrameEvents();
37 | }
38 | }
39 |
40 | public static bool CanGoBack => Frame.CanGoBack;
41 |
42 | public static bool CanGoForward => Frame.CanGoForward;
43 |
44 | public static bool GoBack()
45 | {
46 | if (CanGoBack)
47 | {
48 | Frame.GoBack();
49 | return true;
50 | }
51 |
52 | return false;
53 | }
54 |
55 | public static void GoForward() => Frame.GoForward();
56 |
57 | public static bool Navigate(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null)
58 | {
59 | // Don't open the same page multiple times
60 | if (Frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(_lastParamUsed)))
61 | {
62 | var navigationResult = Frame.Navigate(pageType, parameter, infoOverride);
63 | if (navigationResult)
64 | {
65 | _lastParamUsed = parameter;
66 | }
67 |
68 | return navigationResult;
69 | }
70 | else
71 | {
72 | return false;
73 | }
74 | }
75 |
76 | public static bool Navigate(object parameter = null, NavigationTransitionInfo infoOverride = null)
77 | where T : Page
78 | => Navigate(typeof(T), parameter, infoOverride);
79 |
80 | private static void RegisterFrameEvents()
81 | {
82 | if (_frame != null)
83 | {
84 | _frame.Navigated += Frame_Navigated;
85 | _frame.NavigationFailed += Frame_NavigationFailed;
86 | }
87 | }
88 |
89 | private static void UnregisterFrameEvents()
90 | {
91 | if (_frame != null)
92 | {
93 | _frame.Navigated -= Frame_Navigated;
94 | _frame.NavigationFailed -= Frame_NavigationFailed;
95 | }
96 | }
97 |
98 | private static void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e) => NavigationFailed?.Invoke(sender, e);
99 |
100 | private static void Frame_Navigated(object sender, NavigationEventArgs e) => Navigated?.Invoke(sender, e);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/TabViewTear/Helpers/SettingsStorageExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 |
5 | using Windows.Storage;
6 | using Windows.Storage.Streams;
7 |
8 | namespace TabViewTear.Helpers
9 | {
10 | // Use these extension methods to store and retrieve local and roaming app data
11 | // More details regarding storing and retrieving app data at https://docs.microsoft.com/windows/uwp/app-settings/store-and-retrieve-app-data
12 | public static class SettingsStorageExtensions
13 | {
14 | private const string FileExtension = ".json";
15 |
16 | public static bool IsRoamingStorageAvailable(this ApplicationData appData)
17 | {
18 | return appData.RoamingStorageQuota == 0;
19 | }
20 |
21 | public static async Task SaveAsync(this StorageFolder folder, string name, T content)
22 | {
23 | var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting);
24 | var fileContent = await Json.StringifyAsync(content);
25 |
26 | await FileIO.WriteTextAsync(file, fileContent);
27 | }
28 |
29 | public static async Task ReadAsync(this StorageFolder folder, string name)
30 | {
31 | if (!File.Exists(Path.Combine(folder.Path, GetFileName(name))))
32 | {
33 | return default(T);
34 | }
35 |
36 | var file = await folder.GetFileAsync($"{name}.json");
37 | var fileContent = await FileIO.ReadTextAsync(file);
38 |
39 | return await Json.ToObjectAsync(fileContent);
40 | }
41 |
42 | public static async Task SaveAsync(this ApplicationDataContainer settings, string key, T value)
43 | {
44 | settings.SaveString(key, await Json.StringifyAsync(value));
45 | }
46 |
47 | public static void SaveString(this ApplicationDataContainer settings, string key, string value)
48 | {
49 | settings.Values[key] = value;
50 | }
51 |
52 | public static async Task ReadAsync(this ApplicationDataContainer settings, string key)
53 | {
54 | object obj = null;
55 |
56 | if (settings.Values.TryGetValue(key, out obj))
57 | {
58 | return await Json.ToObjectAsync((string)obj);
59 | }
60 |
61 | return default(T);
62 | }
63 |
64 | public static async Task SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting)
65 | {
66 | if (content == null)
67 | {
68 | throw new ArgumentNullException(nameof(content));
69 | }
70 |
71 | if (string.IsNullOrEmpty(fileName))
72 | {
73 | throw new ArgumentException("ExceptionSettingsStorageExtensionsFileNameIsNullOrEmpty".GetLocalized(), nameof(fileName));
74 | }
75 |
76 | var storageFile = await folder.CreateFileAsync(fileName, options);
77 | await FileIO.WriteBytesAsync(storageFile, content);
78 | return storageFile;
79 | }
80 |
81 | public static async Task ReadFileAsync(this StorageFolder folder, string fileName)
82 | {
83 | var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
84 |
85 | if ((item != null) && item.IsOfType(StorageItemTypes.File))
86 | {
87 | var storageFile = await folder.GetFileAsync(fileName);
88 | byte[] content = await storageFile.ReadBytesAsync();
89 | return content;
90 | }
91 |
92 | return null;
93 | }
94 |
95 | public static async Task ReadBytesAsync(this StorageFile file)
96 | {
97 | if (file != null)
98 | {
99 | using (IRandomAccessStream stream = await file.OpenReadAsync())
100 | {
101 | using (var reader = new DataReader(stream.GetInputStreamAt(0)))
102 | {
103 | await reader.LoadAsync((uint)stream.Size);
104 | var bytes = new byte[stream.Size];
105 | reader.ReadBytes(bytes);
106 | return bytes;
107 | }
108 | }
109 | }
110 |
111 | return null;
112 | }
113 |
114 | private static string GetFileName(string name)
115 | {
116 | return string.Concat(name, FileExtension);
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/TabViewTear/Services/WindowManagerService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | using Windows.ApplicationModel.Core;
7 | using Windows.UI.Core;
8 | using Windows.UI.ViewManagement;
9 | using Windows.UI.Xaml;
10 | using Windows.UI.Xaml.Controls;
11 |
12 | namespace TabViewTear.Services
13 | {
14 | public delegate void ViewClosedHandler(ViewLifetimeControl viewControl, EventArgs e);
15 |
16 | // For instructions on testing this service see https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/features/multiple-views.md
17 | // More details about showing multiple views at https://docs.microsoft.com/windows/uwp/design/layout/show-multiple-views
18 | public class WindowManagerService
19 | {
20 | private static WindowManagerService _current;
21 |
22 | public static WindowManagerService Current => _current ?? (_current = new WindowManagerService());
23 |
24 | // Contains all the opened secondary views.
25 | public ObservableCollection SecondaryViews { get; } = new ObservableCollection();
26 |
27 | public int MainViewId { get; private set; }
28 |
29 | public CoreDispatcher MainDispatcher { get; private set; }
30 |
31 | public event EventHandler MainWindowMessageReceived;
32 |
33 | public void Initialize()
34 | {
35 | MainViewId = ApplicationView.GetForCurrentView().Id;
36 | MainDispatcher = Window.Current.Dispatcher;
37 | }
38 |
39 | // Displays a view as a standalone
40 | // You can use the resulting ViewLifeTileControl to interact with the new window.
41 | public async Task TryShowAsStandaloneAsync(string windowTitle, Type pageType, string dataContext = null)
42 | {
43 | ViewLifetimeControl viewControl = await CreateViewLifetimeControlAsync(windowTitle, pageType, dataContext);
44 | SecondaryViews.Add(viewControl);
45 | viewControl.StartViewInUse();
46 | var viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewControl.Id, ViewSizePreference.Default, ApplicationView.GetForCurrentView().Id, ViewSizePreference.Default);
47 | viewControl.StopViewInUse();
48 | return viewControl;
49 | }
50 |
51 | // Displays a view in the specified view mode
52 | public async Task TryShowAsViewModeAsync(string windowTitle, Type pageType, ApplicationViewMode viewMode = ApplicationViewMode.Default)
53 | {
54 | ViewLifetimeControl viewControl = await CreateViewLifetimeControlAsync(windowTitle, pageType);
55 | SecondaryViews.Add(viewControl);
56 | viewControl.StartViewInUse();
57 | var viewShown = await ApplicationViewSwitcher.TryShowAsViewModeAsync(viewControl.Id, viewMode);
58 | viewControl.StopViewInUse();
59 | return viewControl;
60 | }
61 |
62 | private async Task CreateViewLifetimeControlAsync(string windowTitle, Type pageType, string dataContext = null)
63 | {
64 | ViewLifetimeControl viewControl = null;
65 |
66 | await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
67 | {
68 | viewControl = ViewLifetimeControl.CreateForCurrentView();
69 | viewControl.Title = windowTitle;
70 | viewControl.Context = dataContext;
71 | viewControl.StartViewInUse();
72 | var frame = new Frame();
73 | frame.RequestedTheme = ThemeSelectorService.Theme;
74 | frame.Navigate(pageType, viewControl);
75 | Window.Current.Content = frame;
76 | Window.Current.Activate();
77 | ApplicationView.GetForCurrentView().Title = viewControl.Title;
78 | });
79 |
80 | return viewControl;
81 | }
82 |
83 | 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 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/TabViewTear/Services/ActivationService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | using TabViewTear.Activation;
7 |
8 | using Windows.ApplicationModel.Activation;
9 | using Windows.System;
10 | using Windows.UI.Core;
11 | using Windows.UI.Xaml;
12 | using Windows.UI.Xaml.Controls;
13 | using Windows.UI.Xaml.Input;
14 | using Windows.UI.Xaml.Navigation;
15 |
16 | namespace TabViewTear.Services
17 | {
18 | // For more information on application activation see https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md
19 | internal class ActivationService
20 | {
21 | private readonly App _app;
22 | private readonly Lazy _shell;
23 | private readonly Type _defaultNavItem;
24 |
25 | public static readonly KeyboardAccelerator AltLeftKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu);
26 |
27 | public static readonly KeyboardAccelerator BackKeyboardAccelerator = BuildKeyboardAccelerator(VirtualKey.GoBack);
28 |
29 | public ActivationService(App app, Type defaultNavItem, Lazy shell = null)
30 | {
31 | _app = app;
32 | _shell = shell;
33 | _defaultNavItem = defaultNavItem;
34 | }
35 |
36 | public async Task ActivateAsync(object activationArgs)
37 | {
38 | if (IsInteractive(activationArgs))
39 | {
40 | // Initialize things like registering background task before the app is loaded
41 | await InitializeAsync();
42 |
43 | // Do not repeat app initialization when the Window already has content,
44 | // just ensure that the window is active
45 | if (Window.Current.Content == null)
46 | {
47 | // Create a Frame to act as the navigation context and navigate to the first page
48 | Window.Current.Content = _shell?.Value ?? new Frame();
49 | NavigationService.NavigationFailed += (sender, e) =>
50 | {
51 | throw e.Exception;
52 | };
53 | NavigationService.Navigated += Frame_Navigated;
54 | if (SystemNavigationManager.GetForCurrentView() != null)
55 | {
56 | SystemNavigationManager.GetForCurrentView().BackRequested += ActivationService_BackRequested;
57 | }
58 | }
59 | }
60 |
61 | var activationHandler = GetActivationHandlers()
62 | .FirstOrDefault(h => h.CanHandle(activationArgs));
63 |
64 | if (activationHandler != null)
65 | {
66 | await activationHandler.HandleAsync(activationArgs);
67 | }
68 |
69 | if (IsInteractive(activationArgs))
70 | {
71 | var defaultHandler = new DefaultLaunchActivationHandler(_defaultNavItem);
72 | if (defaultHandler.CanHandle(activationArgs))
73 | {
74 | await defaultHandler.HandleAsync(activationArgs);
75 | }
76 |
77 | // Ensure the current window is active
78 | Window.Current.Activate();
79 |
80 | // Tasks after activation
81 | await StartupAsync();
82 | }
83 | }
84 |
85 | private async Task InitializeAsync()
86 | {
87 | WindowManagerService.Current.Initialize();
88 | await ThemeSelectorService.InitializeAsync();
89 | }
90 |
91 | private async Task StartupAsync()
92 | {
93 | await ThemeSelectorService.SetRequestedThemeAsync();
94 | }
95 |
96 | private IEnumerable GetActivationHandlers()
97 | {
98 | yield break;
99 | }
100 |
101 | private bool IsInteractive(object args)
102 | {
103 | return args is IActivatedEventArgs;
104 | }
105 |
106 | private void Frame_Navigated(object sender, NavigationEventArgs e)
107 | {
108 | SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = NavigationService.CanGoBack ?
109 | AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
110 | }
111 |
112 | private static KeyboardAccelerator BuildKeyboardAccelerator(VirtualKey key, VirtualKeyModifiers? modifiers = null)
113 | {
114 | var keyboardAccelerator = new KeyboardAccelerator() { Key = key };
115 | if (modifiers.HasValue)
116 | {
117 | keyboardAccelerator.Modifiers = modifiers.Value;
118 | }
119 |
120 | ToolTipService.SetToolTip(keyboardAccelerator, string.Empty);
121 | keyboardAccelerator.Invoked += OnKeyboardAcceleratorInvoked;
122 | return keyboardAccelerator;
123 | }
124 |
125 | private static void OnKeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
126 | {
127 | var result = NavigationService.GoBack();
128 | args.Handled = result;
129 | }
130 |
131 | private void ActivationService_BackRequested(object sender, BackRequestedEventArgs e)
132 | {
133 | var result = NavigationService.GoBack();
134 | e.Handled = result;
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/TabViewTear/Services/ViewLifetimeControl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using TabViewTear.Helpers;
4 |
5 | using Windows.UI.Core;
6 | using Windows.UI.ViewManagement;
7 |
8 | namespace TabViewTear.Services
9 | {
10 | // A custom event that fires whenever the secondary view is ready to be closed. You should
11 | // clean up any state (including deregistering for events) then close the window in this handler
12 | public delegate void ViewReleasedHandler(object sender, EventArgs e);
13 |
14 | // Whenever the main view is about to interact with the secondary view, it should call
15 | // StartViewInUse on this object. When finished interacting, it should call StopViewInUse.
16 | public sealed class ViewLifetimeControl
17 | {
18 | // Window for this particular view. Used to register and unregister for events
19 | private CoreWindow _window;
20 | private int _refCount = 0;
21 | private bool _released = false;
22 |
23 | private event ViewReleasedHandler InternalReleased;
24 |
25 | // Necessary to communicate with the window
26 | public CoreDispatcher Dispatcher { get; private set; }
27 |
28 | // This id is used in all of the ApplicationViewSwitcher and ProjectionManager APIs
29 | public int Id { get; private set; }
30 |
31 | // Initial title for the window
32 | public string Title { get; set; }
33 |
34 | // Optional context to provide from window opener
35 | public string Context { get; set; }
36 |
37 | public event EventHandler MessageReceived;
38 |
39 | public event ViewReleasedHandler Released
40 | {
41 | add
42 | {
43 | bool releasedCopy = false;
44 | lock (this)
45 | {
46 | releasedCopy = _released;
47 | if (!_released)
48 | {
49 | InternalReleased += value;
50 | }
51 | }
52 |
53 | if (releasedCopy)
54 | {
55 | throw new InvalidOperationException("ExceptionViewLifeTimeControlViewDisposal".GetLocalized());
56 | }
57 | }
58 |
59 | remove
60 | {
61 | lock (this)
62 | {
63 | InternalReleased -= value;
64 | }
65 | }
66 | }
67 |
68 | private ViewLifetimeControl(CoreWindow newWindow)
69 | {
70 | Dispatcher = newWindow.Dispatcher;
71 | _window = newWindow;
72 | Id = ApplicationView.GetApplicationViewIdForWindow(_window);
73 | RegisterForEvents();
74 | }
75 |
76 | public static ViewLifetimeControl CreateForCurrentView()
77 | {
78 | return new ViewLifetimeControl(CoreWindow.GetForCurrentThread());
79 | }
80 |
81 | public void SendMessage(string message, int fromid, object data = null)
82 | {
83 | MessageReceived?.Invoke(this, new MessageEventArgs(fromid, Id, message, data));
84 | }
85 |
86 | // Signals that the view is being interacted with by another view,
87 | // so it shouldn't be closed even if it becomes "consolidated"
88 | public int StartViewInUse()
89 | {
90 | bool releasedCopy = false;
91 | int refCountCopy = 0;
92 |
93 | lock (this)
94 | {
95 | releasedCopy = _released;
96 | if (!_released)
97 | {
98 | refCountCopy = ++_refCount;
99 | }
100 | }
101 |
102 | if (releasedCopy)
103 | {
104 | throw new InvalidOperationException("ExceptionViewLifeTimeControlViewDisposal".GetLocalized());
105 | }
106 |
107 | return refCountCopy;
108 | }
109 |
110 | // Should come after any call to StartViewInUse
111 | // Signals that the another view has finished interacting with the view tracked by this object
112 | public int StopViewInUse()
113 | {
114 | int refCountCopy = 0;
115 | bool releasedCopy = false;
116 |
117 | lock (this)
118 | {
119 | releasedCopy = _released;
120 | if (!_released)
121 | {
122 | refCountCopy = --_refCount;
123 | if (refCountCopy == 0)
124 | {
125 | var task = Dispatcher.RunAsync(CoreDispatcherPriority.Low, FinalizeRelease);
126 | }
127 | }
128 | }
129 |
130 | if (releasedCopy)
131 | {
132 | throw new InvalidOperationException("ExceptionViewLifeTimeControlViewDisposal".GetLocalized());
133 | }
134 |
135 | return refCountCopy;
136 | }
137 |
138 | private void RegisterForEvents()
139 | {
140 | ApplicationView.GetForCurrentView().Consolidated += ViewConsolidated;
141 | }
142 |
143 | private void UnregisterForEvents()
144 | {
145 | ApplicationView.GetForCurrentView().Consolidated -= ViewConsolidated;
146 | }
147 |
148 | private void ViewConsolidated(ApplicationView sender, ApplicationViewConsolidatedEventArgs e)
149 | {
150 | StopViewInUse();
151 | }
152 |
153 | private void FinalizeRelease()
154 | {
155 | bool justReleased = false;
156 | lock (this)
157 | {
158 | if (_refCount == 0)
159 | {
160 | justReleased = true;
161 | _released = true;
162 | }
163 | }
164 |
165 | if (justReleased)
166 | {
167 | UnregisterForEvents();
168 | InternalReleased(this, null);
169 | }
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/TabViewTear/Strings/en-us/Resources.resw:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | TabViewTear
122 | Application display name
123 |
124 |
125 | TabViewTear
126 | Application description
127 |
128 |
129 | File name is null or empty. Specify a valid file name
130 | File name is null or empty to save file in settings storage extensions
131 |
132 |
133 | This view is being disposed.
134 | View disposed
135 |
136 |
--------------------------------------------------------------------------------
/TabViewTear/Views/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
102 |
103 | 32
104 | 90
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/TabViewTear/TabViewTear.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | x86
7 | {BAE65131-3FC5-4042-A0F6-958783E3B7CC}
8 | AppContainerExe
9 | Properties
10 | TabViewTear
11 | TabViewTear
12 | en-US
13 | UAP
14 | 10.0.17134.0
15 | 10.0.16299.0
16 | 14
17 | 512
18 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
19 | true
20 | TabViewTear_TemporaryKey.pfx
21 |
22 |
23 | true
24 | bin\x86\Debug\
25 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
26 | NU1603;2008;
27 | full
28 | x86
29 | false
30 | prompt
31 | true
32 |
33 |
34 | bin\x86\Release\
35 | TRACE;NETFX_CORE;WINDOWS_UWP
36 | true
37 | NU1603;2008;
38 | pdbonly
39 | x86
40 | false
41 | prompt
42 | true
43 | true
44 |
45 |
46 | true
47 | bin\ARM\Debug\
48 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
49 | NU1603;2008;
50 | full
51 | ARM
52 | false
53 | prompt
54 | true
55 |
56 |
57 | bin\ARM\Release\
58 | TRACE;NETFX_CORE;WINDOWS_UWP
59 | true
60 | NU1603;2008;
61 | pdbonly
62 | ARM
63 | false
64 | prompt
65 | true
66 | true
67 |
68 |
69 | true
70 | bin\x64\Debug\
71 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP
72 | NU1603;2008;
73 | full
74 | x64
75 | false
76 | prompt
77 | true
78 |
79 |
80 | bin\x64\Release\
81 | TRACE;NETFX_CORE;WINDOWS_UWP
82 | true
83 | NU1603;2008;
84 | pdbonly
85 | x64
86 | false
87 | prompt
88 | true
89 | true
90 |
91 |
92 | PackageReference
93 |
94 |
95 |
96 | 6.1.7
97 |
98 |
99 | 5.0.0
100 |
101 |
102 | 5.0.0
103 |
104 |
105 | 2.0.0
106 |
107 |
108 |
109 | 11.0.2
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | MainPage.xaml
129 |
130 |
131 |
132 |
133 | App.xaml
134 |
135 |
136 |
137 |
138 |
139 | MSBuild:Compile
140 | Designer
141 |
142 |
143 | Designer
144 | MSBuild:Compile
145 |
146 |
147 | Designer
148 | MSBuild:Compile
149 |
150 |
151 | Designer
152 | MSBuild:Compile
153 |
154 |
155 | Designer
156 | MSBuild:Compile
157 |
158 |
159 | MSBuild:Compile
160 | Designer
161 |
162 |
163 |
164 |
165 | Designer
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | MSBuild:Compile
196 | Designer
197 |
198 |
199 |
200 | 14.0
201 |
202 |
203 |
210 |
--------------------------------------------------------------------------------
/TabViewTear/Views/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Toolkit.Uwp.UI.Controls;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Collections.ObjectModel;
5 | using System.ComponentModel;
6 | using System.Linq;
7 | using System.Runtime.CompilerServices;
8 | using TabViewTear.Models;
9 | using TabViewTear.Services;
10 | using Windows.ApplicationModel.Core;
11 | using Windows.ApplicationModel.DataTransfer;
12 | using Windows.UI.Core;
13 | using Windows.UI.ViewManagement;
14 | using Windows.UI.Xaml;
15 | using Windows.UI.Xaml.Controls;
16 | using Windows.UI.Xaml.Input;
17 | using Windows.UI.Xaml.Navigation;
18 |
19 | namespace TabViewTear.Views
20 | {
21 | public sealed partial class MainPage : Page, INotifyPropertyChanged
22 | {
23 | private const string DataIdentifier = "TabData";
24 | private const string DataIndex = "TabIndex";
25 | private const string DataWindow = "TabWindow";
26 | private const string CommandClose = "Close";
27 |
28 | ObservableCollection TabItems = new ObservableCollection();
29 |
30 | public bool IsFullScreen
31 | {
32 | get { return (bool)GetValue(IsFullScreenProperty); }
33 | set { SetValue(IsFullScreenProperty, value); }
34 | }
35 |
36 | // Using a DependencyProperty as the backing store for IsFullScreen. This enables animation, styling, binding, etc...
37 | public static readonly DependencyProperty IsFullScreenProperty =
38 | DependencyProperty.Register(nameof(IsFullScreen), typeof(bool), typeof(MainPage), new PropertyMetadata(false));
39 |
40 | public MainPage()
41 | {
42 | InitializeComponent();
43 |
44 | // Hide default title bar.
45 | // https://docs.microsoft.com/en-us/windows/uwp/design/shell/title-bar
46 | var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
47 | coreTitleBar.ExtendViewIntoTitleBar = true;
48 |
49 | // Register for changes
50 | coreTitleBar.LayoutMetricsChanged += this.CoreTitleBar_LayoutMetricsChanged;
51 | CoreTitleBar_LayoutMetricsChanged(coreTitleBar, null);
52 |
53 | coreTitleBar.IsVisibleChanged += this.CoreTitleBar_IsVisibleChanged;
54 |
55 | // Set XAML element as draggable region.
56 | Window.Current.SetTitleBar(AppTitleBar);
57 |
58 | // Listen for Fullscreen Changes from Shift+Win+Enter or our F11 shortcut
59 | ApplicationView.GetForCurrentView().VisibleBoundsChanged += this.MainPage_VisibleBoundsChanged;
60 | }
61 |
62 | public event PropertyChangedEventHandler PropertyChanged;
63 |
64 | private ViewLifetimeControl _viewLifetimeControl;
65 |
66 | private MessageEventArgs _lastMsg;
67 |
68 | #region Handle Window Lifetime
69 | protected override void OnNavigatedTo(NavigationEventArgs e)
70 | {
71 | base.OnNavigatedTo(e);
72 |
73 | _viewLifetimeControl = e.Parameter as ViewLifetimeControl;
74 | if (_viewLifetimeControl != null)
75 | {
76 | _viewLifetimeControl.StartViewInUse();
77 | // Register for window close
78 | _viewLifetimeControl.Released += OnViewLifetimeControlReleased;
79 | _viewLifetimeControl.MessageReceived += OnViewLifetimeControlMessageReceived;
80 | // Deserialize passed in item to display in this window
81 | TabItems.Add(JsonConvert.DeserializeObject(_viewLifetimeControl.Context.ToString()));
82 | _viewLifetimeControl.Context = null;
83 | _viewLifetimeControl.StopViewInUse();
84 | }
85 | else
86 | {
87 | // Main Window Start
88 | InitializeTestData();
89 |
90 | WindowManagerService.Current.MainWindowMessageReceived += OnViewLifetimeControlMessageReceived;
91 | }
92 | }
93 |
94 | private async void OnViewLifetimeControlReleased(object sender, EventArgs e)
95 | {
96 | _viewLifetimeControl.Released -= OnViewLifetimeControlReleased;
97 | await WindowManagerService.Current.MainDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
98 | {
99 | WindowManagerService.Current.SecondaryViews.Remove(_viewLifetimeControl);
100 | });
101 | }
102 | #endregion
103 |
104 | #region Handle Dragging Tab to Create Window
105 | private async void Items_TabDraggedOutside(object sender, Microsoft.Toolkit.Uwp.UI.Controls.TabDraggedOutsideEventArgs e)
106 | {
107 | if (e.Item is DataItem data && TabItems.Count > 1) // Don't bother creating a new window if we're the last tab, no-op.
108 | {
109 | // Need to serialize item to better provide transfer across window threads.
110 | var lifetimecontrol = await WindowManagerService.Current.TryShowAsStandaloneAsync(data.Title, typeof(MainPage), JsonConvert.SerializeObject(data));
111 |
112 | // Remove Dragged Tab from this window
113 | TabItems.Remove(data);
114 | }
115 | }
116 | #endregion
117 |
118 | #region Handle Tab Change Updating Window Title
119 | private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
120 | {
121 | // Update window title with current item
122 | var first = e.AddedItems.FirstOrDefault();
123 | if (first is DataItem data)
124 | {
125 | ApplicationView.GetForCurrentView().Title = data.Title;
126 | }
127 | }
128 | #endregion
129 |
130 | #region Handle Dragging Tabs between windows
131 | private void Items_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
132 | {
133 | // In Initial Window we need to serialize our tab data.
134 | var item = e.Items.FirstOrDefault();
135 |
136 | if (item is DataItem data)
137 | {
138 | // Add actual data
139 | e.Data.Properties.Add(DataIdentifier, JsonConvert.SerializeObject(data));
140 | // Add our index so we know where to remove from later (if needed)
141 | e.Data.Properties.Add(DataIndex, Items.IndexFromContainer(Items.ContainerFromItem(data)));
142 | // Add Window Id to know if we're transferring to a different window.
143 | e.Data.Properties.Add(DataWindow, ApplicationView.GetForCurrentView().Id);
144 | }
145 | }
146 |
147 | private void Items_DragOver(object sender, DragEventArgs e)
148 | {
149 | // Called before we drop to see if we will accept a drop.
150 |
151 | // Do we have Tab Data?
152 | if (e.DataView.Properties.ContainsKey(DataIdentifier))
153 | {
154 | // Tell OS that we allow moving item.
155 | e.AcceptedOperation = DataPackageOperation.Move;
156 | }
157 | }
158 |
159 | private void Items_Drop(object sender, DragEventArgs e)
160 | {
161 | // Called when we actually get the drop, let's get the data and add our tab.
162 | if (e.DataView.Properties.TryGetValue(DataIdentifier, out object value) && value is string str)
163 | {
164 | var data = JsonConvert.DeserializeObject(str);
165 |
166 | if (data != null)
167 | {
168 | // First we need to get the position in the List to drop to
169 | var listview = sender as TabView;
170 | var index = -1;
171 |
172 | // Determine which items in the list our pointer is inbetween.
173 | for (int i = 0; i < listview.Items.Count; i++)
174 | {
175 | var item = listview.ContainerFromIndex(i) as TabViewItem;
176 |
177 | if (e.GetPosition(item).X - item.ActualWidth < 0)
178 | {
179 | index = i;
180 | break;
181 | }
182 | }
183 |
184 | if (index < 0)
185 | {
186 | // We didn't find a transition point, so we're at the end of the list
187 | TabItems.Add(data);
188 | }
189 | else if (index < listview.Items.Count)
190 | {
191 | // Otherwise, insert at the provided index.
192 | TabItems.Insert(index, data);
193 | }
194 |
195 | Items.SelectedItem = data; // Select new item.
196 |
197 | // Send message to originator to remove the tab.
198 | WindowManagerService.Current.SendMessage((e.DataView.Properties[DataWindow] as int?).Value, CommandClose, e.DataView.Properties[DataIndex]);
199 | }
200 | }
201 | }
202 |
203 | private void OnViewLifetimeControlMessageReceived(object sender, MessageEventArgs e)
204 | {
205 | _lastMsg = e; // Store to complete in DragItemsCompleted.
206 | }
207 |
208 | private async void Items_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
209 | {
210 | // Remove tab from old window after drag completed, if done when message received, item is not 'back' yet from drag processing.
211 | if (args.DropResult == DataPackageOperation.Move && _lastMsg != null)
212 | {
213 | switch (_lastMsg.Message)
214 | {
215 | case CommandClose:
216 | if (_lastMsg.Data is int value)
217 | {
218 | TabItems.RemoveAt(value);
219 |
220 | if (TabItems.Count == 0)
221 | {
222 | // No tabs left on main window, 'switch' to window just created to hide the main view
223 | await ApplicationViewSwitcher.SwitchAsync(_lastMsg.FromId, ApplicationView.GetForCurrentView().Id, ApplicationViewSwitchingOptions.ConsolidateViews);
224 | }
225 | }
226 |
227 | _lastMsg = null;
228 | break;
229 | }
230 | }
231 | }
232 | #endregion
233 |
234 | #region Handle App TitleBar
235 | private void CoreTitleBar_IsVisibleChanged(CoreApplicationViewTitleBar sender, object args)
236 | {
237 | // Adjust our content based on the Titlebar's visibility
238 | // This is used when fullscreen to hide/show the titlebar when the mouse is near the top of the window automatically.
239 | Items.Visibility = sender.IsVisible ? Visibility.Visible : Visibility.Collapsed;
240 | AppTitleBar.Visibility = Items.Visibility;
241 | }
242 |
243 | private void CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args)
244 | {
245 | // Get the size of the caption controls area and back button
246 | // (returned in logical pixels), and move your content around as necessary.
247 | LeftPaddingColumn.Width = new GridLength(sender.SystemOverlayLeftInset);
248 | RightPaddingColumn.Width = new GridLength(sender.SystemOverlayRightInset);
249 |
250 | // Update title bar control size as needed to account for system size changes.
251 | AppTitleBar.Height = sender.Height;
252 | }
253 | #endregion
254 |
255 | #region Handle FullScreen
256 | private void MainPage_VisibleBoundsChanged(ApplicationView sender, object args)
257 | {
258 | // Update Fullscreen from other modes of adjusting view (keyboard shortcuts)
259 | IsFullScreen = ApplicationView.GetForCurrentView().IsFullScreenMode;
260 | }
261 |
262 | private void AppFullScreenShortcut(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
263 | {
264 | // Toggle FullScreen from F11 Keyboard Shortcut
265 | if (!IsFullScreen)
266 | {
267 | IsFullScreen = ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
268 | }
269 | else
270 | {
271 | ApplicationView.GetForCurrentView().ExitFullScreenMode();
272 | IsFullScreen = false;
273 | }
274 | }
275 |
276 | private void Button_FullScreen_Click(object sender, RoutedEventArgs e)
277 | {
278 | // Redirect to our shortcut key.
279 | AppFullScreenShortcut(null, null);
280 | }
281 | #endregion
282 |
283 | private void Set(ref T storage, T value, [CallerMemberName]string propertyName = null)
284 | {
285 | if (Equals(storage, value))
286 | {
287 | return;
288 | }
289 |
290 | storage = value;
291 | OnPropertyChanged(propertyName);
292 | }
293 |
294 | private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
295 |
296 | private void InitializeTestData()
297 | {
298 | TabItems.Add(new DataItem()
299 | {
300 | Title = "Item 1",
301 | Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a consectetur arcu, eu imperdiet nisl. Nunc id interdum odio. Aliquam non vulputate sem. Proin lacinia, lacus vitae finibus malesuada, leo libero interdum nisl, et dictum justo tortor semper tortor. Phasellus suscipit malesuada ultrices. Cras sodales vel lectus quis mattis. Sed consequat mollis ultrices. Nam eleifend purus sit amet massa mattis facilisis. Donec fringilla convallis nibh eget venenatis. Morbi ac venenatis ex. Integer ultrices velit eget dictum ultrices. Nunc aliquet lectus vitae feugiat varius. Nulla erat nisi, scelerisque ut sollicitudin id, vestibulum at mi. Donec neque velit, ornare consectetur aliquet id, egestas nec sapien. Nulla nec magna sed nunc varius bibendum."
302 | });
303 | TabItems.Add(new DataItem()
304 | {
305 | Title = "Item 2",
306 | Content = "Aliquam fringilla euismod neque sit amet porta. Aliquam et ligula in neque ullamcorper interdum sit amet et magna. Quisque maximus accumsan lorem at rhoncus. Pellentesque mattis, eros non accumsan auctor, libero turpis sodales urna, id porta mi dolor at elit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec lacinia leo arcu, vitae malesuada sapien consequat eget. Pellentesque vestibulum interdum convallis. Mauris nulla elit, tempus sit amet enim finibus, suscipit tempor ante. Nullam pulvinar libero sed tincidunt sagittis. Suspendisse potenti. Nulla porta lacinia lacus vel bibendum. Sed sagittis dignissim leo, ac gravida sem mattis pellentesque."
307 | });
308 | TabItems.Add(new DataItem()
309 | {
310 | Title = "Item 3",
311 | Content = "Donec tellus nisl, volutpat vel urna eu, vestibulum sollicitudin sapien. Aliquam libero ex, egestas ut dapibus ullamcorper, mattis non nisl. Pellentesque quis hendrerit nibh. In lobortis placerat interdum. Aliquam et eleifend velit. Nunc ipsum orci, auctor eget eros non, euismod accumsan quam. Nam sit amet convallis est. Integer eget mauris pharetra, fringilla elit a, eleifend felis. Nullam vel ex posuere, blandit tellus nec, lobortis mauris. Nulla rhoncus nisi vel leo condimentum, non cursus lacus tempus."
312 | });
313 | TabItems.Add(new DataItem()
314 | {
315 | Title = "Item 4",
316 | Content = "Nullam sollicitudin magna dui, imperdiet vulputate arcu pharetra eu. Vivamus lobortis lectus ut diam pretium, ut fermentum est malesuada. Sed eget pretium nisi. Cras eget vestibulum purus. Vivamus tincidunt luctus maximus. Cras erat enim, molestie sit amet tortor sit amet, porttitor tincidunt neque. Nam malesuada odio justo, sed sagittis tellus mollis in. Proin congue enim quis libero faucibus, eu condimentum dolor convallis. Mauris blandit ipsum sit amet maximus convallis. Integer porta dolor id purus hendrerit, a semper mi blandit. In malesuada lacus a tellus interdum, vel consequat turpis molestie. Curabitur eget venenatis massa."
317 | });
318 | TabItems.Add(new DataItem()
319 | {
320 | Title = "Item 5",
321 | Content = "Etiam egestas, tellus ut molestie cursus, odio eros accumsan nulla, ut tempor libero nisi a ante. Sed posuere, velit id dictum lobortis, magna lorem dapibus urna, vitae mattis tellus libero et ligula. Praesent vel orci vehicula, accumsan ipsum ac, venenatis erat. Vestibulum consequat nulla eget arcu accumsan, tempus condimentum nulla euismod. Cras mattis tellus tortor, vitae vulputate lectus vulputate ac. Nunc nisl est, porttitor vitae diam a, pulvinar faucibus augue. Morbi vitae bibendum sem, non porta dolor. Cras turpis sem, rhoncus eget ultrices a, pretium venenatis libero. Fusce convallis eu sapien eu imperdiet. Nullam pulvinar ante a lobortis commodo. Aenean at est vel est faucibus efficitur in eget turpis. In efficitur bibendum dolor vitae dapibus. Mauris dapibus risus sit amet lectus ornare, et eleifend urna pretium. Integer non semper nibh, sit amet bibendum nulla. Nulla facilisi."
322 | });
323 | TabItems.Add(new DataItem()
324 | {
325 | Title = "Item 6",
326 | Content = "Integer in pulvinar justo, non venenatis leo. Nam quis pulvinar libero, id laoreet elit. Nunc vehicula vitae lectus et venenatis. Etiam et porta dui. Nulla rutrum lacinia dolor. Nullam convallis libero eget nisi tristique, quis convallis enim finibus. Suspendisse consectetur lorem eleifend sem venenatis ultrices. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc ligula urna, aliquam vitae est a, dictum gravida nulla. Sed eu vestibulum nisl. Phasellus rhoncus volutpat mauris, vitae semper quam molestie et. Fusce mattis turpis a congue maximus. Suspendisse justo dui, varius non metus vel, euismod pretium velit."
327 | });
328 | }
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | topic: sample
3 | languages:
4 | - csharp
5 | products:
6 | - windows
7 | - windows-uwp
8 | ---
9 |
10 | TabView Tear-Off Sample
11 | =======================
12 |
13 | This sample demonstrates how to use the [Windows Community Toolkit](https://github.com/windows-toolkit/WindowsCommunityToolkit/)'s [TabView](https://docs.microsoft.com/en-us/windows/communitytoolkit/controls/tabview
14 | ) control in combination with [Windows Template Studio](https://github.com/microsoft/windowsTemplateStudio)'s [Multiple Views](https://github.com/Microsoft/WindowsTemplateStudio/blob/dev/docs/features/multiple-views.md) support to show how to emulate Microsoft Edge's tear-off tab windowing in your UWP app. It also demonstrates how to place Tabs in the Title Bar of the Application and properly handle Full Screen support.
15 |
16 | Requirements
17 | ------------
18 | Requires VS 2017 and Windows 10 version 16299 or above.
19 |
20 | Dependencies
21 | ------------
22 | - [Windows Community Toolkit 5.0](https://github.com/windows-toolkit/WindowsCommunityToolkit/)
23 | - [Json.NET](https://www.newtonsoft.com/json)
24 |
25 | Considerations
26 | --------------
27 | 1. Each Window runs its own Thread, this has implications on data transfer, Window messaging, and UI Page/Control construction.
28 |
29 | 2. When constructing a new Window, it needs it's own UI shell to be reconstructed.
30 |
31 | 3. This samples assumes the implementor will be using a collection of custom data items bound to the TabView.
32 |
33 | 4. This sample assumes all Windows are managed by the same process and shares the same implementation for each Window.
34 |
35 | Known Issues
36 | ------------
37 | 1. Dragging a tab to another monitor/position doesn't open the window on the other monitor/position.
38 |
39 | This is a platform limitation for two reasons, A) we can't determine which monitor the user has dropped the item on, and B) we can't request the window to be opened at a specific location.
40 |
41 | This also causes an issue if the origin window is FullScreen as the new window is created on the same monitor and takes focus away from the original window, making it look like FullScreen mode has been exited. Clicking on the Taskbar Icon for the app restores the origin window to FullScreen as expected.
42 |
43 | 2. The right-most tab will disappear when dragging a tab to another window.
44 |
45 | This is a known bug which needs to be resolved in the TabView control, see [Issue #2670](https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/2670).
46 |
47 | 3. Dragging into a FullScreen Window is not supported.
48 |
49 | Currently, the hidden TitleBar of a FullScreen app doesn't appear when performing any drag operation. We could mitigate this in the future by detecting the drag entering our window and toggling the visibility of our TabView (ignoring the visibility of the TitleBar from the system in this scenario).
50 |
51 | About the Sample
52 | ----------------
53 |
54 | For many years, browsers have allowed users to drag tabs out of their windows to move tabs between monitors. They also let users drag tabs between windows. This scenario is alluring for other document based apps as well.
55 |
56 | This sample demonstrates the main building blocks needed to provide this experience with the new TabView control. There are a few main technical pieces we need to make this scenario work harmoniously:
57 |
58 | 1. Detect dragging a Tab out of the window.
59 | 2. Create a secondary window to display content.
60 | 3. Transfer our tab data to the other window.
61 | 4. Move a tab between two existing windows.
62 | 5. Close a window if the last tab is moved.
63 |
64 | In addition we can provide added app real-estate by properly handling the following:
65 | 1. Placing tabs in the TitleBar.
66 | 2. Handling Full Screen Mode.
67 |
68 | The rest of this article will share how this sample addresses each of these challenges.
69 |
70 | ## Detecting Tab Drag
71 |
72 | Fortunately, this is an easy one as the TabView control [provides a `TabDraggedOutside` event](https://docs.microsoft.com/en-us/windows/communitytoolkit/controls/tabview#events). We can listen to this event to know when the user has requested a tab to leave its window.
73 |
74 | The TabView does this by looking for a drag which had no operation accepted. This means another window or application didn't accept the drag as a valid operation and in our case is an excellent indicator that the user dragged the tab outside the window and wants to 'tear' it off.
75 |
76 | ## Create a Secondary Window
77 |
78 | Once the user has dragged a tab outside of the window, we need to create a Secondary window in order to display the tab. We also need to remove this tab from our original window.
79 |
80 | Fortunately, the Windows Template Studio provides a feature template ("[Multiple views](https://github.com/Microsoft/WindowsTemplateStudio/blob/dev/docs/features/multiple-views.md
81 | )") for setting up and controlling the life-cycle of Secondary Windows.
82 |
83 | In our case, we needed to provide some context for the new window to create itself (the tab's data), so I added a `Context` property to the `ViewLifetimeControl` and modified the `TryShowAsStandaloneAsync` method on `WindowManagerService` to accept this context and add it to the construction of the ViewLifetimeControl.
84 |
85 | This allows us in the `OnNavigatedTo` event of our page to grab this context out of the `Parameter` argument when a secondary window is created.
86 |
87 | Now we can simply create a new window using our same page type and pass it our data (more on this in the next section):
88 |
89 | ```csharp
90 | private async void Items_TabDraggedOutside(object sender, Microsoft.Toolkit.Uwp.UI.Controls.TabDraggedOutsideEventArgs e)
91 | {
92 | if (e.Item is DataItem data && TabItems.Count > 1) // Don't bother creating a new window if we're the last tab, no-op.
93 | {
94 | // Need to serialize item to better provide transfer across window threads.
95 | var lifetimecontrol = await WindowManagerService.Current.TryShowAsStandaloneAsync(data.Title, typeof(MainPage), JsonConvert.SerializeObject(data));
96 |
97 | // Remove Dragged Tab from this window
98 | TabItems.Remove(data);
99 | }
100 | }
101 | ```
102 |
103 | ## Transferring Tab Data
104 |
105 | One thing we have to be conscious of when creating a new window is the new window will run on its new thread. While we can technically pass a reference across to the new window to our tab data, this will cause complications from it being originally created on a different thread when we try and access it again.
106 |
107 | To circumvent these issues we use JSON serialization to create a thread neutral package (as a string) to pass data between our windows. This is pretty painless with the help of the [Json.NET](https://www.newtonsoft.com/json) library.
108 |
109 | The sample uses TabView with an `ItemsSource` bound to an `ObservableCollection` of `DataItem` objects. `DataItem` is a custom class we've used to represent our tab data. In this example it simply has `Title` and `Content` properties used to represent the tab header and what is displayed within the tab. However, it could have additional properties. The important thing is that our data is easily serializable into JSON.
110 |
111 | Then we can easily convert between our object and JSON with the following code:
112 |
113 | ```csharp
114 | // Convert an object to a string
115 | var data = new DataItem() { Title = "Test Tab", Content = "Our content." };
116 | var str = JsonConvert.SerializeObject(data);
117 |
118 | // Convert it back
119 | var datanew = JsonConvert.DeserializeObject(str);
120 | ```
121 |
122 | However, you could use any other serialization technique here, if desired. JSON is nice as it's still human readable if you need to diagnose any odd problems or also use for saving or interoperate between implementations in other languages or platforms.
123 |
124 | ## Moving Tabs between Windows
125 |
126 | Surprisingly, this is the most difficult task. There are a number of challenges here in this space:
127 |
128 | 1. How to enable drag and drop.
129 | 2. Storing information about the tab.
130 | 3. Creating a new tab when its dropped.
131 | 4. Ensuring the tab is dropped where the user wanted it.
132 | 5. Closing the tab in the originating window.
133 |
134 | ### Drag and Drop
135 |
136 | To enable the Drag and Drop scenario for our TabView we need to enable the following properties and events in XAML:
137 |
138 | ```xml
139 |
148 | ```
149 |
150 | The first three will let us drag items out of the TabView and let the TabView accept drops. The other events are all the ones we need to register to perform different steps of our drag operation and occur in the following order:
151 |
152 | The `DragItemsStarting` event is where we will save the data and information about the tab needed to move it to a new window.
153 |
154 | The `DragOver` event is needed so the target TabView can accept the drag operation.
155 |
156 | The `Drop` event is used by the target TabView to receive the tab data and construct a new tab in its own window. This is also where we can figure out where the user was trying to drop the tab to put it in the right spot in the target TabView.
157 |
158 | Finally, the `DragItemsCompleted` event is called in our originating window. This is where we do our final clean-up and remove the original tab that was now dragged to the Secondary window.
159 |
160 | ### Tab Info
161 |
162 | Fortunately, we can save our tab just like we did in our other case and add it to our drag properties in our `DragItemsStarting` event:
163 |
164 | ```csharp
165 | private void Items_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
166 | {
167 | // In Initial Window we need to serialize our tab data.
168 | var item = e.Items.FirstOrDefault();
169 |
170 | if (item is DataItem data)
171 | {
172 | // Add actual data
173 | e.Data.Properties.Add(DataIdentifier, JsonConvert.SerializeObject(data));
174 | // Add our index so we know where to remove from later (if needed)
175 | e.Data.Properties.Add(DataIndex, Items.IndexFromContainer(Items.ContainerFromItem(data)));
176 | // Add Window Id to know if we're transferring to a different window.
177 | e.Data.Properties.Add(DataWindow, ApplicationView.GetForCurrentView().Id);
178 | }
179 | }
180 | ```
181 |
182 | The first argument to each of these `Add` calls is just a string which we've made a constant at the top of the class for convenience and consistency.
183 |
184 | We also add information about where the tab was located originally and in which window this tab is from. This will be all the information we need in order to accomplish our task.
185 |
186 | We use properties here rather than storing text as we don't want other applications allowing the tab to be dragged with some text as a target. This should increase the likelihood that an application won't accept the drag so that we know we want to create a new window.
187 |
188 | ### Creating a new Tab on Drop
189 |
190 | In order to accept a drop, we need to first indicate that we want to accept an incoming drop. We do this in the `DragOver` event by accepting the operation:
191 |
192 | ```csharp
193 | private void Items_DragOver(object sender, DragEventArgs e)
194 | {
195 | // Do we have Tab Data?
196 | if (e.DataView.Properties.ContainsKey(DataIdentifier))
197 | {
198 | // Tell OS that we allow moving item.
199 | e.AcceptedOperation = DataPackageOperation.Move;
200 | }
201 | }
202 | ```
203 |
204 | We simply check if the thing looks like a tab and then if so, say that we'll accept it.
205 |
206 | This is a requirement for us to get our `Drop` event next.
207 |
208 | The bulk of our `Drop` event in the sample deals with the next section of how we place the tab where the user indicated. The main part we use to get our tab data is at the top:
209 |
210 | ```csharp
211 | if (e.DataView.Properties.TryGetValue(DataIdentifier, out object value) && value is string str)
212 | {
213 | var data = JsonConvert.DeserializeObject(str);
214 | ```
215 |
216 | Then, we'll then insert the tab data into our `TabItems` collection (more in the next section on that).
217 |
218 | And finally, we'll select the tab that was dropped, see below. However, if we just stopped there the original tab would remain in the first window, so we also need to send a message back to remove it (more on this two sections down).
219 |
220 | ```csharp
221 | Items.SelectedItem = data; // Select new item.
222 |
223 | // Send message to originator to remove the tab.
224 | WindowManagerService.Current.SendMessage((e.DataView.Properties[DataWindow] as int?).Value, CommandClose, e.DataView.Properties[DataIndex]);
225 | ```
226 |
227 | ### Tab Placement during drag
228 |
229 | Most Drag and Drop examples to another list simply just add the item dropped to the end of the collection. This is in contrast to how drag and drop works within a single list. And the operating system by default shows the nice separation animation to indicate to the user where the item will be dropped in both cases. We'd like our tabs to respect this request by the user.
230 |
231 | To do so, we need to determine where the drop location is in relation to our `TabViewItem` headers. First we get our `TabView` as the sender of the `Drop` event and create a tracker for which index we should drop our tab into our collection:
232 |
233 | ```csharp
234 | // First we need to get the position in the List to drop to
235 | var listview = sender as TabView;
236 | var index = -1;
237 | ```
238 |
239 | Next we loop through each of our `TabViewItem` objects and check their position in relation to our drop point:
240 |
241 | ```csharp
242 | // Determine which items in the list our pointer is inbetween.
243 | for (int i = 0; i < listview.Items.Count; i++)
244 | {
245 | var item = listview.ContainerFromIndex(i) as TabViewItem;
246 |
247 | if (e.GetPosition(item).X - item.ActualWidth < 0)
248 | {
249 | index = i;
250 | break;
251 | }
252 | }
253 | ```
254 |
255 | Since our tabs our horizontal, we use the `X` value, but this same method works for vertical lists as well (swapping it out to `Y` and `ActualHeight`).
256 |
257 | We are getting the relative position of the item in relation to the drop point, so we subtract the size of the item to understand where the cursor is in relation to the bounding box. We know that when these values transition to a negative value we're in the vicinity of the mouse cursor's actual location and should use that index to insert our new tab.
258 |
259 | If we go through all our tabs and still have positive values, it means our cursor is at the end of the list. This finally allows us to simply do a check to determine where we need to insert our new tab into our collection:
260 |
261 | ```csharp
262 | if (index < 0)
263 | {
264 | // We didn't find a transition point, so we're at the end of the list
265 | TabItems.Add(data);
266 | }
267 | else if (index < listview.Items.Count)
268 | {
269 | // Otherwise, insert at the provided index.
270 | TabItems.Insert(index, data);
271 | }
272 | ```
273 |
274 | ### Closing the original Tab
275 |
276 | The last piece of our big puzzle with transferring a tab between windows is removing the tab from the originating window. This isn't a simple task because when the `DragItemsCompleted` event fires we have no information to help us distinguish between a drag within the window and a drag to another window. Both signatures and parameters values in both cases are the same.
277 |
278 | Therefore, we need our receiving window to send a message to tell us to remove our tab. You may have noticed we used a `SendMessage` command on the `WindowManagerService`. This is something that we had to add for this scenario.
279 |
280 | We first created a new `MessageEventArgs` class which could contain information about a message sent between windows. This contained properties such as `FromId` and `ToId` for storing Window identifiers and `Message` and `Data` to aid in routing and information storage.
281 |
282 | **It's important to note**, just like our tab dragging and windowing scenarios, sending messages between windows has the same inherent threading issues. So, we need to be careful about the type of data we send.
283 |
284 | With this structure in place, I added both a `SendMessage` method to the `WindowManagerService` and `ViewLifetimeControl` as well as a `MessageReceived` and `MainMessageReceived` event. The Main Window of the app is a special case, so it needed its own event that the first window could subscribe to, as seen in our `OnNavigatedTo` event where we detect this condition, subscribe to the event, and initialize our starting tabs.
285 |
286 | We now had the infrastructure to send and receive a message between our windows, recalling from before:
287 |
288 | ```csharp
289 | // Send message to originator to remove the tab.
290 | WindowManagerService.Current.SendMessage((e.DataView.Properties[DataWindow] as int?).Value, CommandClose, e.DataView.Properties[DataIndex]);
291 |
292 | // Registered in OnNavigatedTo:
293 | _viewLifetimeControl.MessageReceived += OnViewLifetimeControlMessageReceived;
294 | // Or for Main Window:
295 | WindowManagerService.Current.MainWindowMessageReceived += OnViewLifetimeControlMessageReceived;
296 | ```
297 |
298 | However, when we receive this message in our drag and drop phase, it's not the right time to act on closing the tab as it has temporarily been removed from the collection already by the drag operation. Therefore we simply store it in a private variable:
299 |
300 | ```csharp
301 | private void OnViewLifetimeControlMessageReceived(object sender, MessageEventArgs e)
302 | {
303 | _lastMsg = e; // Store to complete in DragItemsCompleted.
304 | }
305 | ```
306 |
307 | Then we can act on it in the case where we detected a move and received a message that it was to another window in our `DragItemsCompleted` event:
308 |
309 | ```csharp
310 | private async void Items_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
311 | {
312 | // Remove tab from old window after drag completed, if done when message received, item is not 'back' yet from drag processing.
313 | if (args.DropResult == DataPackageOperation.Move && _lastMsg != null)
314 | {
315 | switch (_lastMsg.Message)
316 | {
317 | case CommandClose:
318 | if (_lastMsg.Data is int value)
319 | {
320 | TabItems.RemoveAt(value);
321 |
322 | if (TabItems.Count == 0)
323 | {
324 | // To cover in the next section, as we want to close the window here.
325 | }
326 | }
327 |
328 | _lastMsg = null;
329 | break;
330 | }
331 | }
332 | }
333 | ```
334 |
335 | Above, we look at the message we received and if its a 'Close' command (our only one right now) then we remove the tab at the specified index (which we had set originally back in our `DragItemsStarting` event and passed forward).
336 |
337 | Now, we have a functioning drag of a tab across to our other window!
338 |
339 | ## 'Closing' windows no longer needed
340 |
341 | What happens when we drag the last tab out of our window? We normally would expect in this pattern to close the window.
342 |
343 | However, UWP doesn't provide a straight-forward way to tell a window to close, especially our Main Window.
344 |
345 | We can use the following trick though to Consolidate our view to another using the `ApplicationViewSwitcher.SwitchAsync` method. This will let us specify that we want to really be showing a different view instead of our current one. And if that view is already open, then it should just clean-up our old one...
346 |
347 | ```csharp
348 | // No tabs left on main window, 'switch' to window just created to hide the main view
349 | await ApplicationViewSwitcher.SwitchAsync(_lastMsg.FromId, ApplicationView.GetForCurrentView().Id, ApplicationViewSwitchingOptions.ConsolidateViews);
350 | ```
351 |
352 | With this simple call, we've now cleaned up our empty view and finished our example.
353 |
354 | ## (optional) Tabs in TitleBars
355 |
356 | Many apps with tabs prefer to make use of the TitleBar space of the app to display tabs, like Microsoft Edge.
357 |
358 | We can enable this with a few lines of code and some XAML. First we need to hook into the *CoreApplication's TitleBar* and register some events to detect changes to the `ApplicationView`:
359 |
360 | ```csharp
361 | // https://docs.microsoft.com/en-us/windows/uwp/design/shell/title-bar
362 | var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
363 | coreTitleBar.ExtendViewIntoTitleBar = true;
364 |
365 | // Register for changes
366 | coreTitleBar.LayoutMetricsChanged += this.CoreTitleBar_LayoutMetricsChanged;
367 | CoreTitleBar_LayoutMetricsChanged(coreTitleBar, null);
368 |
369 | coreTitleBar.IsVisibleChanged += this.CoreTitleBar_IsVisibleChanged;
370 |
371 | // Set XAML element as draggable region.
372 | Window.Current.SetTitleBar(AppTitleBar);
373 |
374 | ```
375 |
376 | The above code will put the `AppTitleBar` element from our XAML into the TitleBar and have it handle input for the window. It's important that we follow the metrics provided so that the user can still interact and move the window around in places without it entirely being content.
377 |
378 | We can add a placeholder for this Title Bar area in our XAML:
379 |
380 | ```xml
381 |
385 |
386 |
387 |
388 |
389 |
390 |
391 | ```
392 |
393 | It's hard to put the TabView directly in this specially designated area though, as input behaves differently for the TitleBar in order to allow the user to move the Window based on certain conditions.
394 |
395 | Therefore, we're going to use this placeholder to get all the proper metrics and host the TabView in our main app still. This will give us the flexibility we need to still interact with the TabView as we intend as well as the OS properly detecting the areas we want the user to be able to move the app with.
396 |
397 | We also need to shrink the TabView down a bit to fit in the TitleBar, so we add a Resource to it for that:
398 |
399 | ```xml
400 |
401 | 32
402 | ```
403 |
404 | We'll then setup the Grid for our app with 2 Rows and 3 Columns:
405 |
406 | ```xml
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 | ```
418 |
419 | The rows represent the area for the TitleBar with the Tabs and our Content.
420 |
421 | The columns represent the padding reserved by the system (for Window control buttons, etc...) and our actual content. We bind our app's columns to the ones we're using with our placeholder in the `AppTitleBar`.
422 |
423 | We then place our TabView in Row 0, Column 1 and our Content in Row 1 with a ColumnSpan of 3.
424 |
425 | The *order* of how we place these things in our XAML is **vitally important** as well. We need to layer things correctly in order to have everything display and be interacted with properly:
426 |
427 | ```xml
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 | ```
438 |
439 | This will enable the TabView to be displayed over the content when in Full Screen mode (see next section) as well as allowing the TitleBar to properly let the user move the Window in areas where the TabView is Transparent.
440 |
441 | Finally, we need to adjust our TabView to not show it's content by default as we'll use a `ContentPresenter` to display the content outside the TabView. This lets us better handle Full Screen mode next as otherwise the content would disappear with our TitleBar.
442 |
443 | We remove the content from the TabView by setting up a blank `DataTemplate`:
444 |
445 | ```xml
446 |
447 |
448 |
449 | ```
450 |
451 | Then we create `ContentPresenter` at the top of our Grid to host the `SelectedItem`'s content:
452 |
453 | ```xml
454 |
455 | ```
456 |
457 | We can use a casting in our `x:Bind` expression in order to get our model type's `Content` property. We can even add a `ContentTemplate` to better display our text:
458 |
459 | ```xml
460 |
461 |
462 |
463 |
464 |
465 | ```
466 |
467 | ## Handling Full Screen
468 |
469 | Did you know that users have a built-in way to make a UWP app Full Screen **Shift+Win+Enter**? We'll add an additional **F11** shortcut as well as a button to help make it clear this is a feature.
470 |
471 | First, we'll add a `IsFullScreen` property to our window to know what mode we're in. Then we'll add some code to detect when it happens automatically and update our value:
472 |
473 | ```csharp
474 | // Listen for Fullscreen Changes from Shift+Win+Enter or our F11 shortcut
475 | ApplicationView.GetForCurrentView().VisibleBoundsChanged += this.MainPage_VisibleBoundsChanged;
476 | ```
477 |
478 | Then we can use this property to manage the rest of our logic with the VisualStateManager.
479 |
480 | When the app is Full Screen, we want to hide the Full Screen button (the maximize button of the app becomes exit full screen) as well as move our TabView out of the way from being seen and make our content the main focus. We use the VisualStateManager to accomplish these changes to our Grid arrangement:
481 |
482 | ```xml
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 | ```
507 |
508 | We change the background color of our TitleBar area so that when it overlays the content it's not Transparent. It needs to be Transparent when the window is outside of Full Screen mode in order to enable the user to interact with it to move the window around.
509 |
510 | The key piece of code driving our VSM is the StateTrigger:
511 |
512 | ```xml
513 |
514 |
515 |
516 | ```
517 |
518 | This lets us automatically drive the correct storyboard based on our `IsFullScreen` property we setup before.
519 |
520 | Finally, we add an extra shortcut key to the accelerators of our app in XAML:
521 |
522 | ```xml
523 |
524 |
525 |
529 |
530 | ```
531 |
532 | In 1803, they added a property to hide tooltips, this is important as otherwise hovering in locations on the app sometimes can show an 'F11' tooltip where we don't want one.
533 |
534 | And then hook up our buttons and listeners:
535 |
536 | ```csharp
537 | private void MainPage_VisibleBoundsChanged(ApplicationView sender, object args)
538 | {
539 | // Update Fullscreen from other modes of adjusting view (keyboard shortcuts)
540 | IsFullScreen = ApplicationView.GetForCurrentView().IsFullScreenMode;
541 | }
542 |
543 | private void AppFullScreenShortcut(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
544 | {
545 | // Toggle FullScreen from F11 Keyboard Shortcut
546 | if (!IsFullScreen)
547 | {
548 | IsFullScreen = ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
549 | }
550 | else
551 | {
552 | ApplicationView.GetForCurrentView().ExitFullScreenMode();
553 | IsFullScreen = false;
554 | }
555 | }
556 |
557 | private void Button_FullScreen_Click(object sender, RoutedEventArgs e)
558 | {
559 | // Redirect to our shortcut key.
560 | AppFullScreenShortcut(null, null);
561 | }
562 | ```
563 |
--------------------------------------------------------------------------------