This commit is contained in:
dan_clark@outlook.com
2022-03-23 15:44:32 +00:00
commit 2ac0e3ed26
129 changed files with 4197 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
+363
View File
@@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
+1
View File
@@ -0,0 +1 @@
TheXamlGuy.TaskbarGroup
@@ -0,0 +1,15 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class DataTemplateBuilder : IDataTemplateBuilder
{
private readonly Dictionary<Type, Type> items = new();
public IDataTemplateCollection DataTemplates => new DataTemplateCollection(items);
public IDataTemplateBuilder Map<TViewModel, TView>()
{
items.Add(typeof(TViewModel), typeof(TView));
return this;
}
}
}
@@ -0,0 +1,12 @@
using System.Collections.ObjectModel;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class DataTemplateCollection : ReadOnlyDictionary<Type, Type>, IDataTemplateCollection
{
public DataTemplateCollection(IDictionary<Type, Type> dictionary) : base(dictionary)
{
}
}
}
+78
View File
@@ -0,0 +1,78 @@
using System.Reactive.Linq;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Collections;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Disposer : IDisposer
{
private readonly ConditionalWeakTable<object, CompositeDisposable> subjects = new();
public void Add(object subject, params object[] objects)
{
var disposables = subjects.GetOrCreateValue(subject);
foreach (var disposable in objects.OfType<IDisposable>())
{
disposables.Add(disposable);
}
foreach (var notDisposable in objects.Where(x => x is not IDisposable))
{
disposables.Add(Disposable.Create(() => MakeNotDisposable(notDisposable)));
}
}
public void Dispose(object subject)
{
if (subjects.TryGetValue(subject, out CompositeDisposable disposables))
{
disposables.Dispose();
}
}
public void Remove(object subject, IDisposable disposer)
{
var disposables = subjects.GetOrCreateValue(subject);
if (disposer != null)
{
disposables.Remove(disposer);
}
}
public TDisposable Replace<TDisposable>(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable
{
var disposables = subjects.GetOrCreateValue(subject);
if (disposer is not null)
{
disposables.Remove(disposer);
}
disposables.Add(replacement);
return replacement;
}
private void MakeNotDisposable(object target)
{
if (target is IEnumerable enumerableTarget)
{
foreach (var item in enumerableTarget)
{
MakeNotDisposable(item);
}
}
if (target is IDisposable disposableTarget)
{
disposableTarget.Dispose();
}
if (target is not IDisposable)
{
Dispose(target);
}
}
}
}
@@ -0,0 +1,14 @@
using System.Reflection;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class EventAggregatorInvoker : IEventAggregatorInvoker
{
public void Invoke<TMessage>(object target, TMessage message, MethodInfo methodInfo)
{
if (message is null) throw new ArgumentNullException(nameof(message));
methodInfo.Invoke(target, new object[] { message });
}
}
}
@@ -0,0 +1,12 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IAsyncMessageHandler<TMessage>
{
Task Handle(TMessage message, CancellationToken canellationToken = default);
}
public interface IAsyncMessageHandler<TReturn, TMessage>
{
Task<TReturn> Handle(TMessage message, CancellationToken cancellationToken = default);
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IBindViewModel<TViewModel> where TViewModel : class
{
public TViewModel ViewModel { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDataTemplateBuilder
{
IDataTemplateCollection DataTemplates { get; }
IDataTemplateBuilder Map<TViewModel, TView>();
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDataTemplateCollection : IReadOnlyDictionary<Type, Type>
{
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDispatcherTimer
{
void Start();
void Stop();
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDispatcherTimerFactory
{
IDispatcherTimer Create(Action actionDelegate, TimeSpan interval);
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDisposer
{
void Add(object subject, params object[] objects);
void Dispose(object subject);
void Remove(object subject, IDisposable disposer);
TDisposable Replace<TDisposable>(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable;
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDropTarget<TTarget>
{
void Register(TTarget target);
}
}
@@ -0,0 +1,9 @@
using System.Reflection;
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IEventAggregatorInvoker
{
void Invoke<TMessage>(object target, TMessage message, MethodInfo methodInfo);
}
}
@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace TheXamlGuy.TaskbarGroup.Core
{
public static class IHostingExtensions
{
public static IHostBuilder ConfigureDataTemplates(this IHostBuilder hostBuilder, Action<IDataTemplateBuilder> builderDelegate)
{
hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
{
var builder = new DataTemplateBuilder();
builderDelegate?.Invoke(builder);
serviceCollection.AddSingleton(builder.DataTemplates);
});
return hostBuilder;
}
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IInitializable
{
void Initialize();
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IMediator
{
void Handle(object request, params object[] parameters);
void Handle<TEvent>() where TEvent : new();
TResponse Handle<TResponse, TRequest>(params object[] parameters) where TRequest : new();
TResponse Handle<TResponse>(object request, params object[] parameters);
Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters);
Task HandleAsync(object request, params object[] parameters);
Task HandleAsync<TEvent>() where TEvent : new();
Task<TResponse> HandleAsync<TResponse, TRequest>(params object[] parameters) where TRequest : new();
Task<TResponse> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters);
Task<TResponse> HandleAsync<TResponse>(object request, params object[] parameters);
}
}
@@ -0,0 +1,12 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IMessageHandler<TMessage>
{
void Handle(TMessage message);
}
public interface IMessageHandler<TReturn, TMessage>
{
TReturn Handle(TMessage message);
}
}
@@ -0,0 +1,14 @@
using System.Reactive.Concurrency;
using System.Reactive.Subjects;
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IMessenger
{
void Send<TMessage>() where TMessage : new();
void Send<TMessage>(TMessage message);
IDisposable Subscribe<TMessage>(Action<TMessage> actionDelegate, IScheduler? scheduler = null, Func<TMessage, bool>? where = null);
}
}
@@ -0,0 +1,31 @@
using System.Reactive.Linq;
namespace TheXamlGuy.TaskbarGroup.Core
{
public record FileDropped();
public static class IObservableExtensions
{
public static IDisposable WeakSubscribe<TMessage>(this IObservable<TMessage> observable, IEventAggregatorInvoker invoker, Action<TMessage> actionDelegate)
{
var methodInfo = actionDelegate.Method;
var weakReference = new WeakReference(actionDelegate.Target);
IDisposable? subscription = null;
subscription = observable.Subscribe(item =>
{
if (weakReference.Target is object target)
{
invoker.Invoke(target, item, methodInfo);
}
else
{
subscription?.Dispose();
}
});
return subscription;
}
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IPointerMonitor : IInitializable, IDisposable
{
}
}
@@ -0,0 +1,22 @@
using Microsoft.Extensions.DependencyInjection;
namespace TheXamlGuy.TaskbarGroup.Core
{
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddRequiredCore(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<IDisposer, Disposer>()
.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (type, parameter) => ActivatorUtilities.CreateInstance(provider, type, parameter)))
.AddSingleton<IEventAggregatorInvoker, EventAggregatorInvoker>()
.AddSingleton<IMessenger, Messenger>()
.AddSingleton<IMediator, Mediator>()
.AddSingleton<IInitializable, WndProcMonitor>()
.AddSingleton<ITaskbar, Taskbar>()
.AddSingleton<IInitializable, TaskbarMonitor>()
.AddSingleton<IInitializable, PointerMonitor>()
.AddSingleton<IInitializable, TaskbarButtonMonitor>();
}
}
}
@@ -0,0 +1,10 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IServiceFactory
{
T Create<T>(params object[] parameters);
T Create<T>(Type type);
T Create<T>();
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbar
{
TaskbarState GetCurrentState();
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbarButton : IDisposable
{
TaskbarButtonBounds Bounds { get; }
string Name { get; }
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbarButtonMonitor : IInitializable
{
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbarMonitor : IInitializable
{
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITemplateSelector
{
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IWndProcMonitor : IInitializable, IDisposable
{
}
}
@@ -0,0 +1,13 @@
namespace System.Runtime.CompilerServices
{
using System.ComponentModel;
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IsExternalInit
{
}
}
@@ -0,0 +1,24 @@
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
public sealed class NotNullAttribute : Attribute { }
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
public sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
// NOTE: you can find the full list of attributes in this gist:
// https://gist.github.com/Sergio0694/eb988b243dd4a720a66fe369b63e5b08.
// Keeping this one shorter so that the Medium embed doesn't take up too much space.
}
+70
View File
@@ -0,0 +1,70 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Mediator : IMediator
{
private readonly IServiceFactory serviceFactory;
public Mediator(IServiceFactory serviceFactory)
{
this.serviceFactory = serviceFactory;
}
public void Handle<TEvent>() where TEvent : new()
{
Handle(new TEvent());
}
public TResponse Handle<TResponse, TRequest>(params object[] parameters) where TRequest : new()
{
return Handle<TResponse>(new TRequest(), parameters);
}
public void Handle(object request, params object[] parameters)
{
GetHandler(typeof(IMessageHandler<>).MakeGenericType(request.GetType()), parameters)
.Handle((dynamic)request);
}
public TResponse Handle<TResponse>(object request, params object[] parameters)
{
return GetHandler(typeof(IMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters)
.Handle((dynamic)request);
}
public Task HandleAsync<TEvent>() where TEvent : new()
{
return HandleAsync(new TEvent());
}
public Task<TResponse> HandleAsync<TResponse, TRequest>(params object[] parameters) where TRequest : new()
{
return HandleAsync<TResponse>(new TRequest(), parameters);
}
public Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters)
{
return GetHandler(typeof(IAsyncMessageHandler<>).MakeGenericType(request.GetType()), parameters)
.Handle((dynamic)request, cancellationToken);
}
public Task HandleAsync(object request, params object[] parameters)
{
return HandleAsync(request, CancellationToken.None, parameters);
}
public Task<TResponse> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters)
{
return GetHandler(typeof(IAsyncMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters)
.Handle((dynamic)request, cancellationToken);
}
public Task<TResponse> HandleAsync<TResponse>(object request, params object[] parameters)
{
return HandleAsync<TResponse>(request, CancellationToken.None, parameters);
}
private dynamic GetHandler(Type type, params object[] parameters)
{
return parameters.Length == 0 ? serviceFactory.Create<object>(type) : serviceFactory.Create<object>(type, parameters);
}
}
}
+54
View File
@@ -0,0 +1,54 @@
using System.Reactive.Concurrency;
using System.Collections.Concurrent;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Messenger : IMessenger
{
public IEventAggregatorInvoker invoker;
private readonly ConcurrentDictionary<Type, object> subjects = new();
private IScheduler dispatcher;
public Messenger(IEventAggregatorInvoker invoker)
{
var synchronizationContext = SynchronizationContext.Current;
if (synchronizationContext is null) throw new NullReferenceException(nameof(synchronizationContext));
this.invoker = invoker;
dispatcher = new SynchronizationContextScheduler(synchronizationContext);
}
public ISubject<TMessage> GetSubject<TMessage>()
{
return (ISubject<TMessage>)subjects.GetOrAdd(typeof(TMessage), type => new BehaviorSubject<TMessage>(default));
}
public void Send<TMessage>() where TMessage : new()
{
Send(new TMessage());
}
public void Send<TMessage>(TMessage message)
{
GetSubject<TMessage>().OnNext(message);
}
public IDisposable Subscribe<TMessage>(Action<TMessage> actionDelegate, IScheduler? scheduler = null, Func<TMessage, bool>? where = null)
{
if (scheduler is null)
{
scheduler = Scheduler.Default;
}
if (where == null)
{
where = x => true;
}
return GetSubject<TMessage>().AsObservable().Skip(1).Where(where).ObserveOn(scheduler).WeakSubscribe(invoker, actionDelegate);
}
}
}
@@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": true
}
@@ -0,0 +1,17 @@
SetWindowsHookEx
GetModuleHandle
CallNextHookEx
GetPhysicalCursorPos
FindWindowEx
FindWindow
GetWindowRect
DestroyWindow
DefWindowProcW
CreateWindowExW
RegisterClassW
GetSystemMetrics
MonitorFromWindow
RegisterWindowMessage
GetDpiForWindow
SetWindowPos
SHCreateShellItemArrayFromDataObject
@@ -0,0 +1,132 @@
using System.Collections.ObjectModel;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class ObservableCollectionViewModel<TItemViewModel> :
ObservableCollection<TItemViewModel>, IDisposable
where TItemViewModel : class
{
private readonly IDisposer disposer;
private readonly IMessenger messenger;
private readonly IServiceFactory serviceFactory;
public ObservableCollectionViewModel(IMessenger messenger,
IServiceFactory serviceFactory,
IDisposer disposer)
{
this.messenger = messenger;
this.serviceFactory = serviceFactory;
this.disposer = disposer;
}
public bool IsInitialized { get; protected set; }
public void Add()
{
var item = serviceFactory.Create<TItemViewModel>();
disposer.Add(this, item);
base.Add(item);
}
public void Add(object parameter, params object[] parameters)
{
var item = serviceFactory.Create<TItemViewModel>(new[] { parameter }.Concat(parameters));
disposer.Add(this, item);
base.Add(item);
}
public new void Clear()
{
foreach (var item in this)
{
disposer.Dispose(item);
}
base.Clear();
}
public void Dispose()
{
OnDisposing();
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public void Initialize()
{
if (IsInitialized)
{
return;
}
IsInitialized = true;
OnInitialize();
}
public void Insert<TItem>(params object[] parameters) where TItem : TItemViewModel
{
var item = serviceFactory.Create<TItem>(parameters);
disposer.Add(this, item);
base.Add(item);
}
public new void Insert(int index, TItemViewModel item)
{
disposer.Add(this, item);
base.Insert(index, item);
}
public void Insert<TItem>(int index, params object[] parameters) where TItem : TItemViewModel
{
var item = serviceFactory.Create<TItem>(parameters);
disposer.Add(this, item);
base.Insert(index, item);
}
public void Insert(TItemViewModel item)
{
base.Insert(0, item);
disposer.Add(item);
}
public new void Remove(TItemViewModel item)
{
disposer.Dispose(item);
base.Remove(item);
}
protected virtual void OnDisposing()
{
}
protected virtual void OnInitialize()
{
}
protected void Publish<TEvent>(TEvent @event)
{
messenger.Send(@event);
}
protected void Publish<TEvent>() where TEvent : new()
{
messenger.Send<TEvent>();
}
protected void Register<TEvent>(Action<TEvent> action)
{
disposer.Add(this, messenger.Subscribe(action));
}
protected void Register<TEvent>(Action<TEvent> action, Func<TEvent, bool> condition)
{
disposer.Add(this, messenger.Subscribe(action, null, condition));
}
}
}
@@ -0,0 +1,68 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace TheXamlGuy.TaskbarGroup.Core
{
[INotifyPropertyChanged]
public partial class ObservableViewModel : IDisposable
{
private readonly IDisposer disposer;
private readonly IMessenger messenger;
public ObservableViewModel(IMessenger messenger,
IServiceFactory serviceFactory,
IDisposer disposer)
{
this.messenger = messenger;
this.disposer = disposer;
}
public bool IsInitialized { get; protected set; }
public void Dispose()
{
OnDisposing();
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public void Initialize()
{
if (IsInitialized)
{
return;
}
IsInitialized = true;
OnInitialize();
}
protected virtual void OnDisposing()
{
}
protected virtual void OnInitialize()
{
}
protected void Publish<TEvent>(TEvent @event)
{
messenger.Send(@event);
}
protected void Publish<TEvent>() where TEvent : new()
{
messenger.Send<TEvent>();
}
protected void Register<TEvent>(Action<TEvent> action)
{
disposer.Add(this, messenger.Subscribe(action));
}
protected void Register<TEvent>(Action<TEvent> action, Func<TEvent, bool> condition)
{
disposer.Add(this, messenger.Subscribe(action, null, condition));
}
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public enum PointerButton
{
Left,
Middle,
Right
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerDrag(PointerLocation Location);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerLocation(int X, int Y);
}
@@ -0,0 +1,155 @@
using Windows.Win32;
using Windows.Win32.UI.WindowsAndMessaging;
using Windows.Win32.Foundation;
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class PointerMonitor : IPointerMonitor
{
private readonly IMessenger messenger;
private bool isDisposed;
private bool isPointerPressed;
private HOOKPROC? mouseEventDelegate;
private UnhookWindowsHookExSafeHandle? mouseHandle;
private bool isPointerDrag;
public PointerMonitor(IMessenger messenger)
{
this.messenger = messenger;
}
~PointerMonitor()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public unsafe void Initialize()
{
InitializeHook();
}
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
RemoveHook();
isDisposed = true;
}
}
private unsafe void InitializeHook()
{
mouseEventDelegate = new HOOKPROC(MouseProc);
mouseHandle = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_MOUSE_LL, mouseEventDelegate, PInvoke.GetModuleHandle("user32.dll"), 0);
}
private unsafe bool TryGetPointer(out POINT point)
{
fixed (POINT* lpPointLocal = &point)
{
return PInvoke.GetPhysicalCursorPos(lpPointLocal);
}
}
private bool TryGetPointerLocation([MaybeNullWhen(false)]out PointerLocation location)
{
if (TryGetPointer(out POINT point))
{
location = new PointerLocation(point.x, point.y);
return true;
}
location = null;
return false;
}
private LRESULT MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
if (TryGetPointerLocation(out var location))
{
switch ((uint)wParam.Value)
{
case (uint)WndProcMessages.WM_MOUSEMOVE:
SendPointerMoved(location);
break;
case (uint)WndProcMessages.WM_LBUTTONUP:
SendPointerReleased(location, PointerButton.Left);
break;
case (uint)WndProcMessages.WM_MBUTTONUP:
SendPointerReleased(location, PointerButton.Middle);
break;
case (uint)WndProcMessages.WM_RBUTTONUP:
SendPointerReleased(location, PointerButton.Right);
break;
case (uint)WndProcMessages.WM_LBUTTONDOWN:
SendPointerPressed(location, PointerButton.Left);
break;
case (uint)WndProcMessages.WM_MBUTTONDOWN:
SendPointerPressed(location, PointerButton.Middle);
break;
case (uint)WndProcMessages.WM_RBUTTONDOWN:
SendPointerPressed(location, PointerButton.Right);
break;
}
}
}
return PInvoke.CallNextHookEx(mouseHandle, nCode, wParam, lParam);
}
private unsafe void RemoveHook()
{
if (mouseHandle is not null && mouseHandle.DangerousGetHandle() != IntPtr.Zero)
{
PInvoke.UnhookWindowsHookEx((HHOOK)mouseHandle.DangerousGetHandle());
}
}
private void SendPointerMoved(PointerLocation location)
{
if (isPointerPressed)
{
if (!isPointerDrag)
{
isPointerDrag = true;
}
messenger.Send(new PointerDrag(location));
}
messenger.Send(new PointerMoved(location));
}
private void SendPointerPressed(PointerLocation location, PointerButton button)
{
isPointerPressed = true;
messenger.Send(new PointerPressed(location, button));
}
private void SendPointerReleased(PointerLocation location, PointerButton button)
{
if (isPointerPressed)
{
if (isPointerDrag)
{
isPointerDrag = false;
messenger.Send(new PointerDragReleased(location, button));
}
isPointerPressed = false;
messenger.Send(new PointerReleased(location, button));
}
}
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerMoved(PointerLocation Location);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerPressed(PointerLocation Location, PointerButton Button = PointerButton.Left);
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerDragReleased(PointerLocation Location, PointerButton Button = PointerButton.Left);
public record PointerReleased(PointerLocation Location, PointerButton Button = PointerButton.Left);
}
@@ -0,0 +1,14 @@
using Windows.Foundation;
using Windows.Win32.Foundation;
namespace TheXamlGuy.TaskbarGroup.Core
{
internal static class RECTExtensions
{
internal static Rect ToRect(this RECT rect)
{
if (rect.right - rect.left < 0 || rect.bottom - rect.top < 0) return new Rect(rect.left, rect.top, 0, 0);
return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
}
}
}
+109
View File
@@ -0,0 +1,109 @@
using System.Runtime.InteropServices;
using Windows.Foundation;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using Windows.Win32.Graphics.Gdi;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Screen
{
private const int CCHDEVICENAME = 32;
private const int PRIMARY_MONITOR = unchecked((int)0xBAADF00D);
private static readonly bool _multiMonitorSupport;
private readonly IntPtr _monitorHandle;
static Screen()
{
_multiMonitorSupport = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS) != 0;
}
internal Screen(IntPtr monitorHandle)
{
if (!_multiMonitorSupport || monitorHandle == (IntPtr)PRIMARY_MONITOR)
{
Bounds = SystemInformationHelper.VirtualScreen;
Primary = true;
DeviceName = "DISPLAY";
}
else
{
var monitorData = GetMonitorData(monitorHandle);
Bounds = new Rect(monitorData.MonitorRect.left, monitorData.MonitorRect.top, monitorData.MonitorRect.right - monitorData.MonitorRect.left, monitorData.MonitorRect.bottom - monitorData.MonitorRect.top);
Primary = (monitorData.Flags & (int)MonitorFlag.MONITOR_DEFAULTTOPRIMARY) != 0;
DeviceName = monitorData.DeviceName;
}
_monitorHandle = monitorHandle;
}
private enum MonitorFlag : uint
{
MONITOR_DEFAULTTONULL = 0,
MONITOR_DEFAULTTOPRIMARY = 1,
MONITOR_DEFAULTTONEAREST = 2
}
public Rect Bounds { get; }
public string DeviceName { get; }
public bool Primary { get; }
public Rect WorkingArea => GetWorkingArea();
public static Screen FromHandle(IntPtr handle)
{
return _multiMonitorSupport ? new Screen(PInvoke.MonitorFromWindow((HWND)handle, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST)) : new Screen((IntPtr)PRIMARY_MONITOR);
}
public override bool Equals(object? obj)
{
if (obj is not Screen monitor) return false;
return _monitorHandle == monitor._monitorHandle;
}
public override int GetHashCode()
{
return (int)_monitorHandle;
}
[DllImport("user32.dll", EntryPoint = "GetMonitorInfo", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool GetMonitorInfoEx(IntPtr hMonitor, ref MonitorData lpmi);
private MonitorData GetMonitorData(IntPtr monitorHandle)
{
var monitorData = new MonitorData();
monitorData.Size = Marshal.SizeOf(monitorData);
GetMonitorInfoEx(monitorHandle, ref monitorData);
return monitorData;
}
private Rect GetWorkingArea()
{
if (!_multiMonitorSupport || _monitorHandle == (IntPtr)PRIMARY_MONITOR)
{
return SystemInformationHelper.WorkingArea;
}
var monitorData = GetMonitorData(_monitorHandle);
return new Rect(monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.top, monitorData.WorkAreaRect.right - monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.bottom - monitorData.WorkAreaRect.top);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct MonitorData
{
public int Size;
public RECT MonitorRect;
public RECT WorkAreaRect;
public uint Flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string DeviceName;
}
}
}
@@ -0,0 +1,30 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class ServiceFactory : IServiceFactory
{
private readonly Func<Type, object> factory;
private readonly Func<Type, object[], object> factoryWithParameters;
public ServiceFactory(Func<Type, object> factory, Func<Type, object[], object> factoryWithParameters)
{
this.factory = factory;
this.factoryWithParameters = factoryWithParameters;
}
public T Create<T>(params object[] parameters)
{
return (T)factoryWithParameters(typeof(T), parameters);
}
public T Create<T>(Type type)
{
return (T)factory(type);
}
public T Create<T>()
{
return (T)factory(typeof(T));
}
}
}
@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using Windows.Foundation;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace TheXamlGuy.TaskbarGroup.Core
{
internal static class SystemInformationHelper
{
private const int SPI_GETWORKAREA = 48;
public static Rect VirtualScreen => GetVirtualScreen();
public static Rect WorkingArea => GetWorkingArea();
private static Rect GetVirtualScreen()
{
var size = new Size(PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN), PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN));
return new Rect(0, 0, size.Width, size.Height);
}
private static Rect GetWorkingArea()
{
var rect = new RECT();
SystemParametersInfo(SPI_GETWORKAREA, 0, ref rect, 0);
return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);
}
}
+80
View File
@@ -0,0 +1,80 @@
using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Taskbar : ITaskbar
{
private const string ShellTrayHandleName = "Shell_TrayWnd";
private enum AppBarEdge : uint
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3
}
private enum AppBarMessage : uint
{
New = 0x00000000,
Remove = 0x00000001,
QueryPos = 0x00000002,
SetPos = 0x00000003,
GetState = 0x00000004,
GetTaskbarPos = 0x00000005,
Activate = 0x00000006,
GetAutoHideBar = 0x00000007,
SetAutoHideBar = 0x00000008,
WindowPosChanged = 0x00000009,
SetState = 0x0000000A,
}
public TaskbarState GetCurrentState()
{
var handle = GetSystemTrayHandle();
var state = new TaskbarState
{
Screen = Screen.FromHandle(handle)
};
var appBarData = GetAppBarData(handle);
GetAppBarPosition(ref appBarData);
state.Rect = appBarData.rect.ToRect();
state.Placement = (TaskbarPlacement)appBarData.uEdge;
return state;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr DefWindowProcW(IntPtr handle, uint msg, IntPtr wParam, IntPtr lParam);
private static IntPtr GetSystemTrayHandle() => WindowHelper.Find(ShellTrayHandleName);
[DllImport("shell32.dll", SetLastError = true)]
private static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, ref AppBarData pData);
private AppBarData GetAppBarData(IntPtr handle)
{
return new AppBarData
{
cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)),
hWnd = handle
};
}
private void GetAppBarPosition(ref AppBarData appBarData) => SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref appBarData);
[StructLayout(LayoutKind.Sequential)]
private struct AppBarData
{
public uint cbSize;
public IntPtr hWnd;
public uint uCallbackMessage;
public AppBarEdge uEdge;
public RECT rect;
public int lParam;
}
}
}
@@ -0,0 +1,103 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class TaskbarButton : ITaskbarButton
{
private readonly IMessenger messenger;
private readonly IDisposer disposer;
private bool isWithinBounds;
private bool isDrag;
public TaskbarButton(IMessenger messenger,
IDisposer disposer,
string name,
TaskbarButtonBounds bounds)
{
this.messenger = messenger;
this.disposer = disposer;
Name = name;
Bounds = bounds;
disposer.Add(this, messenger.Subscribe<PointerReleased>(OnPointerReleased));
disposer.Add(this, messenger.Subscribe<PointerMoved>(OnPointerMoved));
disposer.Add(this, messenger.Subscribe<PointerDrag>(OnPointerDrag));
}
public TaskbarButtonBounds Bounds { get; internal set; }
public string Name { get; internal set; }
public void Dispose()
{
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
private bool IsWithinBounds(PointerLocation args)
{
if (args.X >= Bounds.X
&& args.X <= Bounds.X + Bounds.Width
&& args.Y >= Bounds.Y
&& args.Y <= Bounds.Y + Bounds.Height)
{
return true;
}
else
{
return false;
}
}
private void OnPointerDrag(PointerDrag args)
{
if (isWithinBounds)
{
if (isDrag)
{
messenger.Send(new TaskbarButtonDragOver(this));
}
else
{
messenger.Send(new TaskbarButtonDragEnter(this));
}
isDrag = true;
}
else
{
isDrag = false;
}
}
private void OnPointerMoved(PointerMoved args)
{
if (IsWithinBounds(args.Location))
{
if (isWithinBounds)
{
return;
}
isWithinBounds = true;
messenger.Send(new TaskbarButtonEntered(this));
}
else
{
isDrag = false;
isWithinBounds = false;
}
}
private void OnPointerReleased(PointerReleased args)
{
if (!isDrag && isWithinBounds)
{
messenger.Send(new TaskbarButtonInvoked(this));
}
if (isDrag)
{
isDrag = false;
}
}
}
}
@@ -0,0 +1,23 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonBounds
{
public TaskbarButtonBounds()
{
}
public TaskbarButtonBounds(int x, int y, int width, int height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
public int X { get; }
public int Y { get; }
public int Width { get; }
public int Height { get; }
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonCreated(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonDragEnter(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonDragOver(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonEntered(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonInvoked(TaskbarButton Button);
}
@@ -0,0 +1,153 @@
using Windows.Win32.Foundation;
using UIAutomationClient;
using System.Diagnostics;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class TaskbarButtonMonitor : ITaskbarButtonMonitor
{
private readonly IDispatcherTimer dispatcherTimer;
private readonly IDispatcherTimerFactory dispatcherTimerFactory;
private readonly IServiceFactory serviceFactory;
private readonly IMessenger messenger;
private readonly Dictionary<string, TaskbarButton> taskbarButtons = new();
private RECT taskbarBoundsCache;
private IUIAutomationCondition? taskListCondition;
private IUIAutomationElement? taskListElement;
private HWND taskListHandle;
public TaskbarButtonMonitor(IMessenger messenger,
IDispatcherTimerFactory dispatcherTimerFactory,
IServiceFactory serviceFactory)
{
this.messenger = messenger;
this.dispatcherTimerFactory = dispatcherTimerFactory;
this.serviceFactory = serviceFactory;
dispatcherTimer = dispatcherTimerFactory.Create(OnDispatcher, TimeSpan.FromMilliseconds(500));
}
public void Initialize()
{
var clientUIAutomation = new CUIAutomation();
taskListCondition = clientUIAutomation.CreateTrueCondition();
var trayHandle = WindowHelper.Find("Shell_TrayWnd");
var rebarHandle = WindowHelper.Find("ReBarWindow32", trayHandle);
var taskHandle = WindowHelper.Find("MSTaskSwWClass", rebarHandle);
taskListHandle = WindowHelper.Find("MSTaskListWClass", taskHandle);
taskListElement = clientUIAutomation.ElementFromHandle(taskListHandle);
if (WindowHelper.TryGetBounds(taskListHandle, out var bounds))
{
taskbarBoundsCache = bounds;
}
dispatcherTimer.Start();
UpdateTaskbarButtons();
}
private bool CheckDirtyTaskbarRegion()
{
if (WindowHelper.TryGetBounds(taskListHandle, out var bounds))
{
var width = taskbarBoundsCache.right - taskbarBoundsCache.left;
var height = taskbarBoundsCache.bottom - taskbarBoundsCache.top;
var deltaWidth = bounds.right - bounds.left;
var deltaHeight = bounds.bottom - bounds.top;
if (width != deltaWidth || height != deltaHeight)
{
taskbarBoundsCache = bounds;
return true;
}
}
return false;
}
private Dictionary<string, tagRECT> FindTaskbarButtons()
{
var taskElements = taskListElement?.FindAll(TreeScope.TreeScope_Descendants | TreeScope.TreeScope_Children, taskListCondition);
var buttons = new Dictionary<string, tagRECT>();
if (taskElements is not null)
{
for (int index = 0; index <= taskElements.Length - 1; index++)
{
var taskUIElement = taskElements.GetElement(index);
var name = taskUIElement.CurrentName;
var rect = taskUIElement.CurrentBoundingRectangle;
buttons.Add(name, rect);
}
}
return buttons;
}
private void OnDispatcher()
{
dispatcherTimer.Stop();
if (CheckDirtyTaskbarRegion())
{
UpdateTaskbarButtons();
}
dispatcherTimer.Start();
}
private void UpdateTaskbarButtons()
{
if (taskListElement is null)
{
return;
}
var buttons = FindTaskbarButtons();
foreach (var buttonToRemove in taskbarButtons.Where(taskbarButton => !buttons.ContainsKey(taskbarButton.Key)))
{
var key = buttonToRemove.Key;
var button = buttonToRemove.Value;
Debug.WriteLine($"{key} button removed");
taskbarButtons.Remove(key);
messenger.Send(new TaskbarButtonRemoved(button));
button.Dispose();
}
foreach (var button in buttons)
{
var name = button.Key;
var bounds = button.Value;
var buttonBounds = new TaskbarButtonBounds(bounds.left,
bounds.top,
bounds.right - bounds.left,
bounds.bottom - bounds.top);
if (taskbarButtons.TryGetValue(name, out var taskbarButton))
{
Debug.WriteLine($"{name} button updated");
taskbarButtons[name].Bounds = buttonBounds;
messenger.Send(new TaskbarButtonUpdated(taskbarButtons[name]));
}
else
{
Debug.WriteLine($"{name} button added");
taskbarButtons.Add(name, serviceFactory.Create<TaskbarButton>(name, buttonBounds));
messenger.Send(new TaskbarButtonCreated(taskbarButtons[name]));
}
}
}
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonRemoved(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonUpdated(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarChanged;
}
@@ -0,0 +1,30 @@
using Windows.Win32;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class TaskbarMonitor : ITaskbarMonitor
{
private const int SPI_SETWORKAREA = 0x002F;
private readonly uint WM_TASKBARCREATED = PInvoke.RegisterWindowMessage("TaskbarCreated");
private readonly IMessenger messenger;
public TaskbarMonitor(IMessenger messenger)
{
this.messenger = messenger;
}
public void Initialize()
{
messenger.Subscribe<WndProc>(OnWndProc);
}
private void OnWndProc(WndProc args)
{
if (args.Message == WM_TASKBARCREATED || args.Message == (int)WndProcMessages.WM_SETTINGCHANGE && (int)args.WParam == SPI_SETWORKAREA)
{
messenger.Send<TaskbarChanged>();
}
}
}
}
@@ -0,0 +1,10 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public enum TaskbarPlacement
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3
}
}
@@ -0,0 +1,11 @@
using Windows.Foundation;
namespace TheXamlGuy.TaskbarGroup.Core
{
public struct TaskbarState
{
public TaskbarPlacement Placement;
public Rect Rect;
public Screen Screen;
}
}
@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
<LangVersion>10.0</LangVersion>
<Platforms>x64;x86</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0-preview2" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.635-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Runtime.WindowsRuntime" Version="5.0.0-preview.5.20278.1" />
</ItemGroup>
<ItemGroup>
<COMReference Include="UIA">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>930299ce-9965-4dec-b0f4-a54848d4b667</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
<COMReference Include="UIAutomationClient">
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>944de083-8fb8-45cf-bcb7-c477acb2f897</Guid>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
</Project>
@@ -0,0 +1,37 @@
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class WindowHelper
{
public static void MoveAndResize(HWND handle, int x, int y, int width, int height)
{
PInvoke.SetWindowPos(handle, new HWND(), x, y, width, height, 0);
}
public static void BringToForeground(HWND handle)
{
if (TryGetBounds(handle, out var bounds))
{
PInvoke.SetWindowPos(handle, new HWND(), bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top, SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);
}
}
public static HWND Find(string windowName) => PInvoke.FindWindow(windowName, null);
public static HWND Find(string windowName, HWND parentHandle)
{
return PInvoke.FindWindowEx(parentHandle, new HWND(), windowName, null);
}
public static unsafe bool TryGetBounds(IntPtr handle, out RECT rect)
{
fixed (RECT* lpRectLocal = &rect)
{
return PInvoke.GetWindowRect(new HWND(handle), lpRectLocal);
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record WndProc(uint Message, uint WParam, uint LParam);
}
@@ -0,0 +1,14 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
internal enum WndProcMessages
{
WM_LBUTTONUP = 0x0202,
WM_MBUTTONUP = 0x0208,
WM_RBUTTONUP = 0x0205,
WM_MOUSEMOVE = 0x0200,
WM_SETTINGCHANGE = 0x001A,
WM_MBUTTONDOWN = 0x0207,
WM_LBUTTONDOWN = 0x0201,
WM_RBUTTONDOWN = 0x0204
}
}
@@ -0,0 +1,68 @@
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class WndProcMonitor : IWndProcMonitor
{
private WNDPROC? handler;
private readonly IMessenger messenger;
public WndProcMonitor(IMessenger messenger)
{
this.messenger = messenger;
}
public IntPtr Handle { get; private set; }
public void Dispose()
{
PInvoke.DestroyWindow((HWND)Handle);
}
private unsafe void InitializeWndProc()
{
var windowName = Guid.NewGuid().ToString();
handler = Wndproc;
WNDCLASSW wndProcWindow;
wndProcWindow.style = 0;
wndProcWindow.lpfnWndProc = handler;
wndProcWindow.cbClsExtra = 0;
wndProcWindow.cbWndExtra = 0;
wndProcWindow.hInstance = new HINSTANCE();
wndProcWindow.hIcon = new HICON();
wndProcWindow.hCursor = new HCURSOR();
wndProcWindow.hbrBackground = new HBRUSH();
fixed (char* menuName = "")
{
wndProcWindow.lpszMenuName = new PCWSTR(menuName);
}
fixed (char* className = windowName)
{
wndProcWindow.lpszClassName = new PCWSTR(className);
}
PInvoke.RegisterClass(wndProcWindow);
Handle = PInvoke.CreateWindowEx(0, wndProcWindow.lpszClassName, new PCWSTR(), 0, 0, 0, 0, 0, new HWND(),
new HMENU(),
new HINSTANCE());
}
private LRESULT Wndproc(HWND param0, uint param1, WPARAM param2, LPARAM param3)
{
messenger.Send(new WndProc(param1, (uint)param2.Value, (uint)param3.Value));
return PInvoke.DefWindowProc(param0, param1, param2, param3);
}
public void Initialize()
{
InitializeWndProc();
}
}
}
@@ -0,0 +1,44 @@
using System;
using TheXamlGuy.TaskbarGroup.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;
using System.Reflection;
namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation
{
public class DataTemplateFactory : IDataTemplateFactory
{
private readonly IDataTemplateCollection datatemplateCollection;
public DataTemplateFactory(IDataTemplateCollection datatemplateCollection)
{
this.datatemplateCollection = datatemplateCollection;
}
public virtual DataTemplate Create(Type dataType)
{
if (dataType is null) throw new ArgumentNullException(nameof(dataType));
if (!datatemplateCollection.TryGetValue(dataType, out Type viewType))
{
var assembly = dataType.GetTypeInfo().Assembly;
viewType = Type.GetType($"{dataType.FullName?.Replace("ViewModel", "View")}, {assembly.FullName}");
}
if (viewType is not null)
{
var xaml = $"<DataTemplate " +
"xmlns:foundation=\"using:TheXamlGuy.TaskbarGroup.Flyout.Foundation\" " +
"xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" " +
"xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" " +
$"xmlns:local=\"using:{viewType.Namespace}\">" +
$"<local:{viewType.Name} />" +
"</DataTemplate>";
return (DataTemplate)XamlReader.Load(xaml);
}
return new DataTemplate();
}
}
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.UI.Xaml;
namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation
{
public class DropTarget
{
public void Initialize(UIElement target)
{
target.DragOver += OnDragOver;
target.Drop += OnDrop;
}
private async void OnDrop(object sender, DragEventArgs args)
{
if (args.DataView.Contains(StandardDataFormats.StorageItems))
{
var items = await args.DataView.GetStorageItemsAsync();
if (items.Count > 0)
{
foreach (var storageItem in items)
{
if (storageItem is StorageFile storageFile)
{
if (storageFile.Path is { Length: > 0 })
{
}
else
{
var properties = await storageFile.Properties.RetrievePropertiesAsync(new List<string>
{
"System.AppUserModel.ID"
});
var appUserModelId = properties["System.AppUserModel.ID"];
if (appUserModelId is not null)
{
}
}
}
if (storageItem is StorageFolder storageFolder)
{
}
}
}
}
}
private void OnDragOver(object sender, DragEventArgs args)
{
args.AcceptedOperation = DataPackageOperation.Link;
}
}
}
@@ -0,0 +1,37 @@
using TheXamlGuy.TaskbarGroup.Core;
using Windows.UI.Xaml;
namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation
{
public static class IBindViewModelExtensions
{
public static void Bind<TViewModel>(this IBindViewModel<TViewModel> view) where TViewModel : class
{
if (view is FrameworkElement frameworkElement)
{
void DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
var viewModelProperty = view.GetType().GetProperty("ViewModel");
if (viewModelProperty is not null)
{
viewModelProperty.SetMethod.Invoke(sender, new[] { sender.DataContext });
}
}
frameworkElement.DataContextChanged += DataContextChanged;
}
}
public static void Bind<TViewModel>(this IBindViewModel<TViewModel> view, object viewModel) where TViewModel : class
{
if (view is FrameworkElement frameworkElement)
{
var viewModelProperty = view.GetType().GetProperty("ViewModel");
if (viewModelProperty is not null)
{
viewModelProperty.SetValue(frameworkElement, viewModel);
}
}
}
}
}
@@ -0,0 +1,10 @@
using System;
using Windows.UI.Xaml;
namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation
{
public interface IDataTemplateFactory
{
DataTemplate Create(Type type);
}
}
@@ -0,0 +1,18 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using TheXamlGuy.TaskbarGroup.Core;
namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation
{
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddRequiredFlyoutFoundation(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<TemplateSelector>()
.AddSingleton<IDataTemplateCollection>(new DataTemplateCollection(new Dictionary<Type, Type>()))
.AddSingleton<DataTemplateFactory>();
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Runtime.InteropServices;
using Windows.Foundation;
using Windows.UI.Xaml;
//PLACEHOLDER - Replace with the *correct* GUID. I haven't found any hint for this, yet.
[ComImport, Guid("15645012-8F3F-5090-B584-DF078FCC509A"), InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface IAtlasRequestCallback
{
bool AtlasRequest(uint width, uint height, Windows.Graphics.DirectX.DirectXPixelFormat pixelFormat);
}
[ComImport, Guid("06636C29-5A17-458D-8EA2-2422D997A922"), InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface IWindowPrivate
{
bool TransparentBackground { get; set; }
void Show();
void Hide();
void MoveWindow(int x, int y, int width, int height);
void SetAtlasSizeHint(uint width, uint height);
void ReleaseGraphicsDeviceOnSuspend(bool enable);
void SetAtlasRequestCallback(IAtlasRequestCallback callback);
Rect GetWindowContentBoundsForElement(DependencyObject element);
}
@@ -0,0 +1,29 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TheXamlGuy.TaskbarGroup.Flyout.Foundation")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TheXamlGuy.TaskbarGroup.Flyout.Foundation")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains Runtime Directives, specifications about types your application accesses
through reflection and other dynamic code patterns. Runtime Directives are used to control the
.NET Native optimizer and ensure that it does not remove code accessed by your library. If your
library does not do any reflection, then you generally do not need to edit this file. However,
if your library reflects over types, especially types passed to it or derived from its types,
then you should write Runtime Directives.
The most common use of reflection in libraries is to discover information about types passed
to the library. Runtime Directives have three ways to express requirements on types passed to
your library.
1. Parameter, GenericParameter, TypeParameter, TypeEnumerableParameter
Use these directives to reflect over types passed as a parameter.
2. SubTypes
Use a SubTypes directive to reflect over types derived from another type.
3. AttributeImplies
Use an AttributeImplies directive to indicate that your library needs to reflect over
types or methods decorated with an attribute.
For more information on writing Runtime Directives for libraries, please visit
https://go.microsoft.com/fwlink/?LinkID=391919
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library Name="TheXamlGuy.TaskbarGroup.Flyout.Foundation">
<!-- add directives for your library here -->
</Library>
</Directives>
@@ -0,0 +1,31 @@
using TheXamlGuy.TaskbarGroup.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace TheXamlGuy.TaskbarGroup.Flyout.Foundation
{
public class TemplateSelector : DataTemplateSelector, ITemplateSelector
{
private readonly DataTemplateFactory dataTemplateFactory;
public TemplateSelector(DataTemplateFactory dataTemplateFactory)
{
this.dataTemplateFactory = dataTemplateFactory;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is not null)
{
var dataType = item.GetType();
var dataTemplate = dataTemplateFactory.Create(dataType);
if (dataTemplate is not null)
{
return dataTemplate;
}
}
return base.SelectTemplateCore(item, container);
}
}
}
@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{35B035A4-E21B-4379-936B-6DEDA31AF860}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TheXamlGuy.TaskbarGroup.Flyout.Foundation</RootNamespace>
<AssemblyName>TheXamlGuy.TaskbarGroup.Flyout.Foundation</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<PlatformTarget>x86</PlatformTarget>
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<PlatformTarget>ARM</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<PlatformTarget>ARM</PlatformTarget>
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
<PlatformTarget>ARM64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
<PlatformTarget>ARM64</PlatformTarget>
<OutputPath>bin\ARM64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<PlatformTarget>x64</PlatformTarget>
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<Compile Include="DataTemplateFactory.cs" />
<Compile Include="DropTarget.cs" />
<Compile Include="IDataTemplateFactory.cs" />
<Compile Include="IServiceCollectionExtensions.cs" />
<Compile Include="IWindowPrivate.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TemplateSelector.cs" />
<Compile Include="IBindViewModelExtensions.cs" />
<EmbeddedResource Include="Properties\TheXamlGuy.TaskbarGroup.Flyout.Foundation.rd.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
<Version>6.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
</PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.8.0-prerelease.220118001</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TheXamlGuy.TaskbarGroup.Core\TheXamlGuy.TaskbarGroup.Core.csproj">
<Project>{40d170f4-f8c1-4fae-8a22-a22bf096bbef}</Project>
<Name>TheXamlGuy.TaskbarGroup.Core</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"></Target><Target Name="AfterBuild"></Target>
-->
</Project>
+10
View File
@@ -0,0 +1,10 @@
<xamlhost:XamlApplication
x:Class="TheXamlGuy.TaskbarGroup.Flyout.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
xmlns:xamlhost="using:Microsoft.Toolkit.Win32.UI.XamlHost">
<Application.Resources>
<controls:XamlControlsResources />
</Application.Resources>
</xamlhost:XamlApplication>
@@ -0,0 +1,20 @@
using Microsoft.Toolkit.Win32.UI.XamlHost;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
namespace TheXamlGuy.TaskbarGroup.Flyout
{
public sealed partial class App : XamlApplication
{
public App()
{
Initialize();
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
(Window.Current as object as IWindowPrivate).TransparentBackground = true;
base.OnLaunched(args);
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,77 @@
using System;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media.Animation;
namespace TheXamlGuy.TaskbarGroup.Flyout.Controls
{
public class TaskbarButtonFlyout : ContentControl
{
public static readonly DependencyProperty TemplateSettingsProperty =
DependencyProperty.Register(nameof(TemplateSettings),
typeof(TaskbarButtonFlyoutTemplateSettings), typeof(TaskbarButtonFlyout),
new PropertyMetadata(null));
private UIElement child;
private Border container;
public TaskbarButtonFlyout()
{
DefaultStyleKey = typeof(TaskbarButtonFlyout);
TemplateSettings = new TaskbarButtonFlyoutTemplateSettings();
}
public event EventHandler<object> Closed;
public event EventHandler<object> Opened;
public TaskbarButtonFlyoutTemplateSettings TemplateSettings
{
get => (TaskbarButtonFlyoutTemplateSettings)GetValue(TemplateSettingsProperty);
set => SetValue(TemplateSettingsProperty, value);
}
public bool IsOpen { get; private set; }
public void Close()
{
if(container is not null)
{
container.Child = null;
}
}
protected override void OnApplyTemplate()
{
container = GetTemplateChild("Container") as Border;
if (container != null)
{
child = container.Child;
container.Child = null;
}
}
public void ShowAt(TaskbarButtonFlyoutPlacement taskbarPlacement)
{
VisualStateManager.GoToState(this, "DefaultPlacement", true);
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var width = child.DesiredSize.Width - 1;
var height = child.DesiredSize.Height - 1;
TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.HeightProperty, height);
TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.WidthProperty, width);
TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.NegativeHeightProperty, -height);
TemplateSettings.SetValue(TaskbarButtonFlyoutTemplateSettings.NegativeWidthProperty, -width);
VisualStateManager.GoToState(this, $"{taskbarPlacement}Placement", true);
container.Child = child;
}
}
}
@@ -0,0 +1,115 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:TheXamlGuy.TaskbarGroup.Flyout.Controls">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<AcrylicBrush
x:Key="AcrylicBackgroundFillColorBrush"
BackgroundSource="HostBackdrop"
FallbackColor="#2C2C2C"
TintColor="#2C2C2C"
TintOpacity="0.8" />
<StaticResource x:Key="TaskbarButtonFlyoutBorderBrush" ResourceKey="SurfaceStrokeColorDefaultBrush" />
<StaticResource x:Key="TaskbarButtonFlyoutBackgroundBrush" ResourceKey="AcrylicBackgroundFillColorBrush" />
<Thickness x:Key="TaskbarButtonFlyoutBorderWidth">1</Thickness>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<AcrylicBrush
x:Key="AcrylicBackgroundFillColorBrush"
BackgroundSource="HostBackdrop"
FallbackColor="#F9F9F9"
TintColor="#FCFCFC"
TintOpacity="0.8" />
<StaticResource x:Key="TaskbarButtonFlyoutBorderBrush" ResourceKey="SurfaceStrokeColorDefaultBrush" />
<StaticResource x:Key="TaskbarButtonFlyoutBackgroundBrush" ResourceKey="AcrylicBackgroundFillColorBrush" />
<Thickness x:Key="TaskbarButtonFlyoutBorderWidth">1</Thickness>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<AcrylicBrush
x:Key="NotificationFlyoutPresenterBackgroundAccentBrush"
BackgroundSource="HostBackdrop"
FallbackColor="{ThemeResource SystemAccentColorDark1}"
TintColor="{ThemeResource SystemAccentColorDark1}"
TintOpacity="0.8" />
<Style TargetType="controls:TaskbarButtonFlyout">
<Setter Property="BorderThickness" Value="{ThemeResource TaskbarButtonFlyoutBorderWidth}" />
<Setter Property="BorderBrush" Value="{ThemeResource TaskbarButtonFlyoutBorderBrush}" />
<Setter Property="Background" Value="{ThemeResource TaskbarButtonFlyoutBackgroundBrush}" />
<Setter Property="CornerRadius" Value="{ThemeResource OverlayCornerRadius}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:TaskbarButtonFlyout">
<Border x:Name="Container" Margin="{TemplateBinding Margin}">
<Grid x:Name="LayoutRoot">
<Border
x:Name="BackgroundElement"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
BackgroundSizing="OuterBorderEdge"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Border.Transitions>
<TransitionCollection>
<EntranceThemeTransition x:Name="EntranceThemeTransition" />
</TransitionCollection>
</Border.Transitions>
<Grid>
<ContentControl
x:Name="ContentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}" />
</Grid>
</Border>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PlacementStates">
<VisualState x:Name="DefaultPlacement" />
<VisualState x:Name="BottomPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Height}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="TopPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeHeight}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="LeftPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeWidth}" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="RightPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Width}" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ThemeStates">
<VisualState x:Name="DefaultTheme" />
<VisualState x:Name="ColorPrevalenceTheme">
<VisualState.Setters>
<Setter Target="BackgroundElement.Background" Value="{ThemeResource TaskbarButtonFlyoutPresenterBackgroundBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -0,0 +1,10 @@
namespace TheXamlGuy.TaskbarGroup.Flyout.Controls
{
public enum TaskbarButtonFlyoutPlacement
{
Left,
Top,
Right,
Bottom,
}
}
@@ -0,0 +1,50 @@
using Windows.UI.Xaml;
namespace TheXamlGuy.TaskbarGroup.Flyout.Controls
{
public class TaskbarButtonFlyoutTemplateSettings : DependencyObject
{
public static readonly DependencyProperty HeightProperty =
DependencyProperty.Register(nameof(Height),
typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings),
new PropertyMetadata(0d));
public static readonly DependencyProperty NegativeHeightProperty =
DependencyProperty.Register(nameof(NegativeHeight),
typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings),
new PropertyMetadata(0d));
public static readonly DependencyProperty NegativeWidthProperty =
DependencyProperty.Register(nameof(NegativeWidth),
typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings),
new PropertyMetadata(0d));
public static readonly DependencyProperty WidthProperty =
DependencyProperty.Register(nameof(Width),
typeof(double), typeof(TaskbarButtonFlyoutTemplateSettings),
new PropertyMetadata(0d));
public double Height
{
get => (double)GetValue(HeightProperty);
set => SetValue(HeightProperty, value);
}
public double NegativeHeight
{
get => (double)GetValue(NegativeHeightProperty);
set => SetValue(NegativeHeightProperty, value);
}
public double NegativeWidth
{
get => (double)GetValue(NegativeWidthProperty);
set => SetValue(NegativeWidthProperty, value);
}
public double Width
{
get => (double)GetValue(WidthProperty);
set => SetValue(WidthProperty, value);
}
}
}
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
IgnorableNamespaces="uap mp">
<Identity
Name="0abc410a-f3f8-42ba-8eba-1fc1f13f5548"
Publisher="CN=Daniel Clark"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="0abc410a-f3f8-42ba-8eba-1fc1f13f5548" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>TheXamlGuy.TaskbarGroup.Flyout</DisplayName>
<PublisherDisplayName>Daniel Clark</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="TheXamlGuy.TaskbarGroup.Flyout.App">
<uap:VisualElements
DisplayName="TheXamlGuy.TaskbarGroup.Flyout"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="TheXamlGuy.TaskbarGroup.Flyout"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>
@@ -0,0 +1,29 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TheXamlGuy.TaskbarGroup.Flyout")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TheXamlGuy.TaskbarGroup.Flyout")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]
@@ -0,0 +1,31 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at https://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Serialize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>
@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{4B56828E-30D0-4EAF-89BC-480480989E6C}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TheXamlGuy.TaskbarGroup.Flyout</RootNamespace>
<AssemblyName>TheXamlGuy.TaskbarGroup.Flyout</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.19041.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\TaskbarButtonFlyoutPlacement.cs" />
<Compile Include="Controls\TaskbarButtonFlyoutTemplateSettings.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Controls\TaskbarButtonFlyout.cs" />
<Compile Include="Views\TaskbarButtonGroupItemViewModel.cs" />
<Compile Include="Views\TaskbarButtonGroupView.xaml.cs">
<DependentUpon>TaskbarButtonGroupView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TaskbarButtonGroupViewModel.cs" />
<Compile Include="Views\TaskbarButtonView.xaml.cs">
<DependentUpon>TaskbarButtonView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TaskbarButtonViewModel.cs" />
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.12</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Win32.UI.XamlApplication">
<Version>6.1.3</Version>
</PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.8.0-prerelease.220118001</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Page Include="Controls\TaskbarButtonFlyout.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Themes\Generic.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TaskbarButtonGroupView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TaskbarButtonView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TheXamlGuy.TaskbarGroup.Core\TheXamlGuy.TaskbarGroup.Core.csproj">
<Project>{40D170F4-F8C1-4FAE-8A22-A22BF096BBEF}</Project>
<Name>TheXamlGuy.TaskbarGroup.Core</Name>
</ProjectReference>
<ProjectReference Include="..\TheXamlGuy.TaskbarGroup.Flyout.Foundation\TheXamlGuy.TaskbarGroup.Flyout.Foundation.csproj">
<Project>{35b035a4-e21b-4379-936b-6deda31af860}</Project>
<Name>TheXamlGuy.TaskbarGroup.Flyout.Foundation</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"></Target><Target Name="AfterBuild"></Target>
-->
</Project>
@@ -0,0 +1,5 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Controls/TaskbarButtonFlyout.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
@@ -0,0 +1,15 @@
using TheXamlGuy.TaskbarGroup.Core;
namespace TheXamlGuy.TaskbarGroup.Flyout
{
public class TaskbarButtonGroupItemViewModel : ObservableViewModel
{
public TaskbarButtonGroupItemViewModel(IMessenger messenger,
IServiceFactory serviceFactory,
IDisposer disposer) : base(messenger, serviceFactory, disposer)
{
}
public string Name { get; set; }
}
}
@@ -0,0 +1,34 @@
<UserControl
x:Class="TheXamlGuy.TaskbarGroup.Flyout.TaskbarButtonGroupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
AllowDrop="True">
<StackPanel>
<TextBox HorizontalAlignment="Stretch" Text="{x:Bind ViewModel.Name, Mode=TwoWay}" />
<GridView
x:Name="GridView"
IsItemClickEnabled="True"
ItemsSource="{x:Bind ViewModel}"
SelectionMode="None">
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="Margin" Value="0" />
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemTemplate>
<DataTemplate>
<Border
Width="94"
Height="84"
Background="Transparent" />
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid MaximumRowsOrColumns="2" Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</StackPanel>
</UserControl>

Some files were not shown because too many files have changed in this diff Show More