mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
CmdPal: Add drag & drop support (#44165)
## Summary of the Pull Request This PR adds basic drag-and-drop support for items in list and grid views. It introduces two new properties on `ListItem`, backed by `IExtendedAttributesProvider`: `DataPackage` and `DataPackageView`. These properties are mutually exclusive. `DataPackage` serves as a convenience property allowing the item to retain the underlying object without risk of losing it. Across the extension boundary, only the immutable `DataPackageView` snapshot is transferred. When `DataPackage` is set, `DataPackageView` is derived from it. This PR includes initial concrete drag-and-drop implementations for: - File Indexer - Clipboard History **Todo / Missing pieces** - [x] Extend `DataPackage` support to top-level command items, enabling scenarios such as index fallback ~ - [x] Provide automatic drag-and-drop for unconfigured list items (e.g., copying title and subtitle as text) - [x] Keep CmdPal open - [ ] ~Clipboard commands (since we have the DataPackage...)~ - [ ] ~Improve logging~ ## Pictures? Moving ones! https://github.com/user-attachments/assets/13eb9a71-e760-43ea-8c2d-cd41cf377905 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #38289 <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
This commit is contained in:
@@ -8,6 +8,7 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
|
|||||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
{
|
{
|
||||||
public ExtensionObject<ICommandItem> Model => _commandItemModel;
|
public ExtensionObject<ICommandItem> Model => _commandItemModel;
|
||||||
|
|
||||||
|
private ExtensionObject<IExtendedAttributesProvider>? ExtendedAttributesProvider { get; set; }
|
||||||
|
|
||||||
private readonly ExtensionObject<ICommandItem> _commandItemModel = new(null);
|
private readonly ExtensionObject<ICommandItem> _commandItemModel = new(null);
|
||||||
private CommandContextItemViewModel? _defaultCommandContextItemViewModel;
|
private CommandContextItemViewModel? _defaultCommandContextItemViewModel;
|
||||||
|
|
||||||
@@ -65,6 +68,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
|
|
||||||
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
||||||
|
|
||||||
|
public DataPackageView? DataPackage { get; private set; }
|
||||||
|
|
||||||
public List<IContextItemViewModel> AllCommands
|
public List<IContextItemViewModel> AllCommands
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -157,6 +162,13 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
// will never be able to load Hotkeys & aliases
|
// will never be able to load Hotkeys & aliases
|
||||||
UpdateProperty(nameof(IsInitialized));
|
UpdateProperty(nameof(IsInitialized));
|
||||||
|
|
||||||
|
if (model is IExtendedAttributesProvider extendedAttributesProvider)
|
||||||
|
{
|
||||||
|
ExtendedAttributesProvider = new ExtensionObject<IExtendedAttributesProvider>(extendedAttributesProvider);
|
||||||
|
var properties = extendedAttributesProvider.GetProperties();
|
||||||
|
UpdateDataPackage(properties);
|
||||||
|
}
|
||||||
|
|
||||||
Initialized |= InitializedState.Initialized;
|
Initialized |= InitializedState.Initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,6 +391,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
UpdateProperty(nameof(SecondaryCommandName));
|
UpdateProperty(nameof(SecondaryCommandName));
|
||||||
UpdateProperty(nameof(HasMoreCommands));
|
UpdateProperty(nameof(HasMoreCommands));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case nameof(DataPackage):
|
||||||
|
UpdateDataPackage(ExtendedAttributesProvider?.Unsafe?.GetProperties());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,6 +446,16 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
UpdateProperty(nameof(Icon));
|
UpdateProperty(nameof(Icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateDataPackage(IDictionary<string, object?>? properties)
|
||||||
|
{
|
||||||
|
DataPackage =
|
||||||
|
properties?.TryGetValue(WellKnownExtensionAttributes.DataPackage, out var dataPackageView) == true &&
|
||||||
|
dataPackageView is DataPackageView view
|
||||||
|
? view
|
||||||
|
: null;
|
||||||
|
UpdateProperty(nameof(DataPackage));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UnsafeCleanup()
|
protected override void UnsafeCleanup()
|
||||||
{
|
{
|
||||||
base.UnsafeCleanup();
|
base.UnsafeCleanup();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Storage.Streams;
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||||
@@ -57,7 +58,7 @@ public partial class IconDataViewModel : ObservableObject, IIconData
|
|||||||
// because each call to GetProperties() is a cross process hop, and if you
|
// because each call to GetProperties() is a cross process hop, and if you
|
||||||
// marshal-by-value the property set, then you don't want to throw it away and
|
// marshal-by-value the property set, then you don't want to throw it away and
|
||||||
// re-marshal it for every property. MAKE SURE YOU CACHE IT.
|
// re-marshal it for every property. MAKE SURE YOU CACHE IT.
|
||||||
if (props?.TryGetValue("FontFamily", out var family) ?? false)
|
if (props?.TryGetValue(WellKnownExtensionAttributes.FontFamily, out var family) ?? false)
|
||||||
{
|
{
|
||||||
FontFamily = family as string;
|
FontFamily = family as string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ using WyHash;
|
|||||||
|
|
||||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||||
|
|
||||||
public sealed partial class TopLevelViewModel : ObservableObject, IListItem
|
public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IExtendedAttributesProvider
|
||||||
{
|
{
|
||||||
private readonly SettingsModel _settings;
|
private readonly SettingsModel _settings;
|
||||||
private readonly ProviderSettings _providerSettings;
|
private readonly ProviderSettings _providerSettings;
|
||||||
@@ -232,6 +232,13 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
|
|||||||
{
|
{
|
||||||
UpdateInitialIcon();
|
UpdateInitialIcon();
|
||||||
}
|
}
|
||||||
|
else if (e.PropertyName == nameof(CommandItem.DataPackage))
|
||||||
|
{
|
||||||
|
DoOnUiThread(() =>
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(CommandItem.DataPackage));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,4 +401,12 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
|
|||||||
{
|
{
|
||||||
return $"{nameof(TopLevelViewModel)}: {Id} ({Title}) - display: {DisplayTitle} - fallback: {IsFallback} - enabled: {IsEnabled}";
|
return $"{nameof(TopLevelViewModel)}: {Id} ({Title}) - display: {DisplayTitle} - fallback: {IsFallback} - enabled: {IsEnabled}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IDictionary<string, object?> GetProperties()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
[WellKnownExtensionAttributes.DataPackage] = _commandItemViewModel?.DataPackage,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,9 +439,12 @@
|
|||||||
<ListView
|
<ListView
|
||||||
x:Name="ItemsList"
|
x:Name="ItemsList"
|
||||||
Padding="0,2,0,0"
|
Padding="0,2,0,0"
|
||||||
|
CanDragItems="True"
|
||||||
ContextCanceled="Items_OnContextCanceled"
|
ContextCanceled="Items_OnContextCanceled"
|
||||||
ContextRequested="Items_OnContextRequested"
|
ContextRequested="Items_OnContextRequested"
|
||||||
DoubleTapped="Items_DoubleTapped"
|
DoubleTapped="Items_DoubleTapped"
|
||||||
|
DragItemsCompleted="Items_DragItemsCompleted"
|
||||||
|
DragItemsStarting="Items_DragItemsStarting"
|
||||||
IsDoubleTapEnabled="True"
|
IsDoubleTapEnabled="True"
|
||||||
IsItemClickEnabled="True"
|
IsItemClickEnabled="True"
|
||||||
ItemClick="Items_ItemClick"
|
ItemClick="Items_ItemClick"
|
||||||
@@ -458,9 +461,12 @@
|
|||||||
<GridView
|
<GridView
|
||||||
x:Name="ItemsGrid"
|
x:Name="ItemsGrid"
|
||||||
Padding="16,0"
|
Padding="16,0"
|
||||||
|
CanDragItems="True"
|
||||||
ContextCanceled="Items_OnContextCanceled"
|
ContextCanceled="Items_OnContextCanceled"
|
||||||
ContextRequested="Items_OnContextRequested"
|
ContextRequested="Items_OnContextRequested"
|
||||||
DoubleTapped="Items_DoubleTapped"
|
DoubleTapped="Items_DoubleTapped"
|
||||||
|
DragItemsCompleted="Items_DragItemsCompleted"
|
||||||
|
DragItemsStarting="Items_DragItemsStarting"
|
||||||
IsDoubleTapEnabled="True"
|
IsDoubleTapEnabled="True"
|
||||||
IsItemClickEnabled="True"
|
IsItemClickEnabled="True"
|
||||||
ItemClick="Items_ItemClick"
|
ItemClick="Items_ItemClick"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using Microsoft.UI.Xaml.Controls.Primitives;
|
|||||||
using Microsoft.UI.Xaml.Input;
|
using Microsoft.UI.Xaml.Input;
|
||||||
using Microsoft.UI.Xaml.Media;
|
using Microsoft.UI.Xaml.Media;
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
using Windows.System;
|
using Windows.System;
|
||||||
|
|
||||||
@@ -891,6 +892,89 @@ public sealed partial class ListPage : Page,
|
|||||||
ItemView.SelectedIndex = newIndex;
|
ItemView.SelectedIndex = newIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Items_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (e.Items.FirstOrDefault() is not ListItemViewModel item || item.DataPackage is null)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy properties
|
||||||
|
foreach (var (key, value) in item.DataPackage.Properties)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
e.Data.Properties[key] = value;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// noop - skip any properties that fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup e.Data formats as deferred renderers to read from the item's DataPackage
|
||||||
|
foreach (var format in item.DataPackage.AvailableFormats)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
e.Data.SetDataProvider(format, request => DelayRenderer(request, item, format));
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// noop - skip any formats that fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new DragStartedMessage());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new DragCompletedMessage());
|
||||||
|
Logger.LogError("Failed to start dragging an item", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DelayRenderer(DataProviderRequest request, ListItemViewModel item, string format)
|
||||||
|
{
|
||||||
|
var deferral = request.GetDeferral();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
item.DataPackage?.GetDataAsync(format)
|
||||||
|
.AsTask()
|
||||||
|
.ContinueWith(dataTask =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (dataTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
request.SetData(dataTask.Result);
|
||||||
|
}
|
||||||
|
else if (dataTask.IsFaulted && dataTask.Exception is not null)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Failed to get data for format '{format}' during drag-and-drop", dataTask.Exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
deferral.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"Failed to set data for format '{format}' during drag-and-drop", ex);
|
||||||
|
deferral.Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Items_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new DragCompletedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Code stealed from <see cref="Controls.ContextMenu.NavigateDown"/>
|
/// Code stealed from <see cref="Controls.ContextMenu.NavigateDown"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
IRecipient<ShowWindowMessage>,
|
IRecipient<ShowWindowMessage>,
|
||||||
IRecipient<HideWindowMessage>,
|
IRecipient<HideWindowMessage>,
|
||||||
IRecipient<QuitMessage>,
|
IRecipient<QuitMessage>,
|
||||||
|
IRecipient<DragStartedMessage>,
|
||||||
|
IRecipient<DragCompletedMessage>,
|
||||||
IDisposable
|
IDisposable
|
||||||
{
|
{
|
||||||
private const int DefaultWidth = 800;
|
private const int DefaultWidth = 800;
|
||||||
@@ -79,6 +81,8 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
|
|
||||||
private WindowPosition _currentWindowPosition = new();
|
private WindowPosition _currentWindowPosition = new();
|
||||||
|
|
||||||
|
private bool _preventHideWhenDeactivated;
|
||||||
|
|
||||||
private MainWindowViewModel ViewModel { get; }
|
private MainWindowViewModel ViewModel { get; }
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
@@ -119,6 +123,8 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
||||||
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
|
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
|
||||||
|
WeakReferenceMessenger.Default.Register<DragStartedMessage>(this);
|
||||||
|
WeakReferenceMessenger.Default.Register<DragCompletedMessage>(this);
|
||||||
|
|
||||||
// Hide our titlebar.
|
// Hide our titlebar.
|
||||||
// We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed
|
// We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed
|
||||||
@@ -751,6 +757,12 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We're doing something that requires us to lose focus, but we don't want to hide the window
|
||||||
|
if (_preventHideWhenDeactivated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// This will DWM cloak our window:
|
// This will DWM cloak our window:
|
||||||
HideWindow();
|
HideWindow();
|
||||||
|
|
||||||
@@ -1027,4 +1039,44 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
_windowThemeSynchronizer.Dispose();
|
_windowThemeSynchronizer.Dispose();
|
||||||
DisposeAcrylic();
|
DisposeAcrylic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Receive(DragStartedMessage message)
|
||||||
|
{
|
||||||
|
_preventHideWhenDeactivated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DragCompletedMessage message)
|
||||||
|
{
|
||||||
|
_preventHideWhenDeactivated = false;
|
||||||
|
Task.Delay(200).ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
DispatcherQueue.TryEnqueue(StealForeground);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void StealForeground()
|
||||||
|
{
|
||||||
|
var foregroundWindow = PInvoke.GetForegroundWindow();
|
||||||
|
if (foregroundWindow == _hwnd)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is bad, evil, and I'll have to forgo today's dinner dessert to punish myself
|
||||||
|
// for writing this. But there's no way to make this work without it.
|
||||||
|
// If the window is not reactivated, the UX breaks down: a deactivated window has to
|
||||||
|
// be activated and then deactivated again to hide.
|
||||||
|
var currentThreadId = PInvoke.GetCurrentThreadId();
|
||||||
|
var foregroundThreadId = PInvoke.GetWindowThreadProcessId(foregroundWindow, null);
|
||||||
|
if (foregroundThreadId != currentThreadId)
|
||||||
|
{
|
||||||
|
PInvoke.AttachThreadInput(currentThreadId, foregroundThreadId, true);
|
||||||
|
PInvoke.SetForegroundWindow(_hwnd);
|
||||||
|
PInvoke.AttachThreadInput(currentThreadId, foregroundThreadId, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PInvoke.SetForegroundWindow(_hwnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.Messages;
|
||||||
|
|
||||||
|
public record DragCompletedMessage;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.Messages;
|
||||||
|
|
||||||
|
public record DragStartedMessage;
|
||||||
@@ -63,4 +63,7 @@ CreateWindowEx
|
|||||||
WNDCLASSEXW
|
WNDCLASSEXW
|
||||||
RegisterClassEx
|
RegisterClassEx
|
||||||
GetStockObject
|
GetStockObject
|
||||||
GetModuleHandle
|
GetModuleHandle
|
||||||
|
|
||||||
|
GetWindowThreadProcessId
|
||||||
|
AttachThreadInput
|
||||||
@@ -12,6 +12,8 @@ using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
|||||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers;
|
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
|
using WinRT;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
||||||
|
|
||||||
@@ -62,6 +64,8 @@ internal sealed partial class ClipboardListItem : ListItem
|
|||||||
RequestedShortcut = KeyChords.DeleteEntry,
|
RequestedShortcut = KeyChords.DeleteEntry,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DataPackageView = _item.Item.Content;
|
||||||
|
|
||||||
if (item.IsImage)
|
if (item.IsImage)
|
||||||
{
|
{
|
||||||
Title = "Image";
|
Title = "Image";
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.CmdPal.Core.Common.Commands;
|
using Microsoft.CmdPal.Core.Common.Commands;
|
||||||
|
using Microsoft.CmdPal.Ext.Indexer.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Pages;
|
using Microsoft.CmdPal.Ext.Indexer.Pages;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Foundation.Metadata;
|
using Windows.Foundation.Metadata;
|
||||||
|
using FileAttributes = System.IO.FileAttributes;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Indexer.Data;
|
namespace Microsoft.CmdPal.Ext.Indexer.Data;
|
||||||
|
|
||||||
@@ -36,6 +39,8 @@ internal sealed partial class IndexerListItem : ListItem
|
|||||||
Title = indexerItem.FileName;
|
Title = indexerItem.FileName;
|
||||||
Subtitle = indexerItem.FullPath;
|
Subtitle = indexerItem.FullPath;
|
||||||
|
|
||||||
|
DataPackage = DataPackageHelper.CreateDataPackageForPath(this, FilePath);
|
||||||
|
|
||||||
var commands = FileCommands(indexerItem.FullPath, browseByDefault);
|
var commands = FileCommands(indexerItem.FullPath, browseByDefault);
|
||||||
if (commands.Any())
|
if (commands.Any())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||||
|
using Microsoft.CmdPal.Ext.Indexer.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Storage.Streams;
|
using Windows.Storage.Streams;
|
||||||
@@ -42,6 +43,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
|||||||
Subtitle = string.Empty;
|
Subtitle = string.Empty;
|
||||||
Icon = null;
|
Icon = null;
|
||||||
MoreCommands = null;
|
MoreCommands = null;
|
||||||
|
DataPackage = null;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -53,6 +55,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
|||||||
Subtitle = string.Empty;
|
Subtitle = string.Empty;
|
||||||
Icon = null;
|
Icon = null;
|
||||||
MoreCommands = null;
|
MoreCommands = null;
|
||||||
|
DataPackage = null;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
|||||||
Subtitle = item.FileName;
|
Subtitle = item.FileName;
|
||||||
Title = item.FullPath;
|
Title = item.FullPath;
|
||||||
Icon = listItemForUs.Icon;
|
Icon = listItemForUs.Icon;
|
||||||
|
DataPackage = DataPackageHelper.CreateDataPackageForPath(listItemForUs, item.FullPath);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -92,13 +96,15 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
|||||||
_searchEngine.Query(query, _queryCookie);
|
_searchEngine.Query(query, _queryCookie);
|
||||||
var results = _searchEngine.FetchItems(0, 20, _queryCookie, out var _);
|
var results = _searchEngine.FetchItems(0, 20, _queryCookie, out var _);
|
||||||
|
|
||||||
if (results.Count == 0 || ((results[0] as IndexerListItem) is null))
|
if (results.Count == 0 || (results[0] is not IndexerListItem indexerListItem))
|
||||||
{
|
{
|
||||||
// Exit 2: We searched for the file, and found nothing. Oh well.
|
// Exit 2: We searched for the file, and found nothing. Oh well.
|
||||||
// Hide ourselves.
|
// Hide ourselves.
|
||||||
Title = string.Empty;
|
Title = string.Empty;
|
||||||
Subtitle = string.Empty;
|
Subtitle = string.Empty;
|
||||||
Command = new NoOpCommand();
|
Command = new NoOpCommand();
|
||||||
|
MoreCommands = null;
|
||||||
|
DataPackage = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,11 +112,12 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
|||||||
{
|
{
|
||||||
// Exit 3: We searched for the file, and found exactly one thing. Awesome!
|
// Exit 3: We searched for the file, and found exactly one thing. Awesome!
|
||||||
// Return it.
|
// Return it.
|
||||||
Title = results[0].Title;
|
Title = indexerListItem.Title;
|
||||||
Subtitle = results[0].Subtitle;
|
Subtitle = indexerListItem.Subtitle;
|
||||||
Icon = results[0].Icon;
|
Icon = indexerListItem.Icon;
|
||||||
Command = results[0].Command;
|
Command = indexerListItem.Command;
|
||||||
MoreCommands = results[0].MoreCommands;
|
MoreCommands = indexerListItem.MoreCommands;
|
||||||
|
DataPackage = DataPackageHelper.CreateDataPackageForPath(indexerListItem, indexerListItem.FilePath);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,6 +128,8 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
|||||||
Title = string.Format(CultureInfo.CurrentCulture, fallbackItemSearchPageTitleCompositeFormat, query);
|
Title = string.Format(CultureInfo.CurrentCulture, fallbackItemSearchPageTitleCompositeFormat, query);
|
||||||
Icon = Icons.FileExplorerIcon;
|
Icon = Icons.FileExplorerIcon;
|
||||||
Command = indexerPage;
|
Command = indexerPage;
|
||||||
|
MoreCommands = null;
|
||||||
|
DataPackage = null;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,6 +140,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
|
|||||||
Icon = null;
|
Icon = null;
|
||||||
Command = new NoOpCommand();
|
Command = new NoOpCommand();
|
||||||
MoreCommands = null;
|
MoreCommands = null;
|
||||||
|
DataPackage = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
|
using Windows.Storage;
|
||||||
|
using File = System.IO.File;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Indexer.Helpers;
|
||||||
|
|
||||||
|
internal static class DataPackageHelper
|
||||||
|
{
|
||||||
|
public static DataPackage CreateDataPackageForPath(ICommandItem listItem, string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataPackage = new DataPackage();
|
||||||
|
dataPackage.SetText(path);
|
||||||
|
_ = dataPackage.TrySetStorageItemsAsync(path);
|
||||||
|
dataPackage.Properties.Title = listItem.Title;
|
||||||
|
dataPackage.Properties.Description = listItem.Subtitle;
|
||||||
|
dataPackage.RequestedOperation = DataPackageOperation.Copy;
|
||||||
|
return dataPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> TrySetStorageItemsAsync(this DataPackage dataPackage, string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
var file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||||
|
dataPackage.SetStorageItems([file]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Directory.Exists(filePath))
|
||||||
|
{
|
||||||
|
var folder = await StorageFolder.GetFolderFromPathAsync(filePath);
|
||||||
|
dataPackage.SetStorageItems([folder]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing there
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// Access denied – skip or report, but don't crash
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.CmdPal.Core.Common.Commands;
|
using Microsoft.CmdPal.Core.Common.Commands;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||||
|
using Microsoft.CmdPal.Ext.Indexer.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
@@ -28,6 +29,9 @@ internal sealed partial class ExploreListItem : ListItem
|
|||||||
|
|
||||||
Title = indexerItem.FileName;
|
Title = indexerItem.FileName;
|
||||||
Subtitle = indexerItem.FullPath;
|
Subtitle = indexerItem.FullPath;
|
||||||
|
|
||||||
|
DataPackage = DataPackageHelper.CreateDataPackageForPath(this, FilePath);
|
||||||
|
|
||||||
List<CommandContextItem> context = [];
|
List<CommandContextItem> context = [];
|
||||||
if (indexerItem.IsDirectory())
|
if (indexerItem.IsDirectory())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,254 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
|
namespace SamplePagesExtension;
|
||||||
|
|
||||||
|
internal sealed partial class SampleDataTransferPage : ListPage
|
||||||
|
{
|
||||||
|
private readonly IListItem[] _items;
|
||||||
|
|
||||||
|
public SampleDataTransferPage()
|
||||||
|
{
|
||||||
|
var dataPackageWithText = CreateDataPackageWithText();
|
||||||
|
var dataPackageWithDelayedText = CreateDataPackageWithDelayedText();
|
||||||
|
var dataPackageWithImage = CreateDataPackageWithImage();
|
||||||
|
|
||||||
|
_items =
|
||||||
|
[
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Draggable item with a plain text",
|
||||||
|
Subtitle = "A sample page demonstrating how to drag and drop data",
|
||||||
|
DataPackage = dataPackageWithText,
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Draggable item with a lazily rendered plain text",
|
||||||
|
Subtitle = "A sample page demonstrating how to drag and drop data with delayed rendering",
|
||||||
|
DataPackage = dataPackageWithDelayedText,
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Draggable item with an image",
|
||||||
|
Subtitle = "This item has an image - package contains both file and a bitmap",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||||
|
DataPackage = dataPackageWithImage,
|
||||||
|
},
|
||||||
|
new ListItem(new SampleDataTransferOnGridPage())
|
||||||
|
{
|
||||||
|
Title = "Drag & drop grid",
|
||||||
|
Subtitle = "A sample page demonstrating a grid list of items",
|
||||||
|
Icon = new IconInfo("\uF0E2"),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataPackage CreateDataPackageWithText()
|
||||||
|
{
|
||||||
|
var dataPackageWithText = new DataPackage
|
||||||
|
{
|
||||||
|
Properties =
|
||||||
|
{
|
||||||
|
Title = "Item with data package with text",
|
||||||
|
Description = "This item has associated text with it",
|
||||||
|
},
|
||||||
|
RequestedOperation = DataPackageOperation.Copy,
|
||||||
|
};
|
||||||
|
dataPackageWithText.SetText("Text data in the Data Package");
|
||||||
|
return dataPackageWithText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataPackage CreateDataPackageWithDelayedText()
|
||||||
|
{
|
||||||
|
var dataPackageWithDelayedText = new DataPackage
|
||||||
|
{
|
||||||
|
Properties =
|
||||||
|
{
|
||||||
|
Title = "Item with delayed render data in the data package",
|
||||||
|
Description = "This items has an item associated with it that is evaluated when requested for the first time",
|
||||||
|
},
|
||||||
|
RequestedOperation = DataPackageOperation.Copy,
|
||||||
|
};
|
||||||
|
dataPackageWithDelayedText.SetDataProvider(StandardDataFormats.Text, request =>
|
||||||
|
{
|
||||||
|
var d = request.GetDeferral();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
request.SetData(DateTime.Now.ToString("G", CultureInfo.CurrentCulture));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
d.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dataPackageWithDelayedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataPackage CreateDataPackageWithImage()
|
||||||
|
{
|
||||||
|
var dataPackageWithImage = new DataPackage
|
||||||
|
{
|
||||||
|
Properties =
|
||||||
|
{
|
||||||
|
Title = "Item with delayed render image in the data package",
|
||||||
|
Description = "This items has an image associated with it that is evaluated when requested for the first time",
|
||||||
|
},
|
||||||
|
RequestedOperation = DataPackageOperation.Copy,
|
||||||
|
};
|
||||||
|
dataPackageWithImage.SetDataProvider(StandardDataFormats.Bitmap, async void (request) =>
|
||||||
|
{
|
||||||
|
var deferral = request.GetDeferral();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Images/Swirls.png"));
|
||||||
|
var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
|
||||||
|
var streamRef = RandomAccessStreamReference.CreateFromStream(stream);
|
||||||
|
request.SetData(streamRef);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
deferral.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dataPackageWithImage.SetDataProvider(StandardDataFormats.StorageItems, async void (request) =>
|
||||||
|
{
|
||||||
|
var deferral = request.GetDeferral();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Images/Swirls.png"));
|
||||||
|
var items = new[] { file };
|
||||||
|
request.SetData(items);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
deferral.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dataPackageWithImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IListItem[] GetItems() => _items;
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Samples")]
|
||||||
|
internal sealed partial class SampleDataTransferOnGridPage : ListPage
|
||||||
|
{
|
||||||
|
public SampleDataTransferOnGridPage()
|
||||||
|
{
|
||||||
|
GridProperties = new GalleryGridLayout
|
||||||
|
{
|
||||||
|
ShowTitle = true,
|
||||||
|
ShowSubtitle = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IListItem[] GetItems()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Red Rectangle",
|
||||||
|
Subtitle = "Drag me",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
|
||||||
|
DataPackage = CreateDataPackageForImage("Assets/Images/RedRectangle.png"),
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Swirls",
|
||||||
|
Subtitle = "Drop me",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||||
|
DataPackage = CreateDataPackageForImage("Assets/Images/Swirls.png"),
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Windows Digital",
|
||||||
|
Subtitle = "Drag me",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||||
|
DataPackage = CreateDataPackageForImage("Assets/Images/Win-Digital.png"),
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Red Rectangle",
|
||||||
|
Subtitle = "Drop me",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
|
||||||
|
DataPackage = CreateDataPackageForImage("Assets/Images/RedRectangle.png"),
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Space",
|
||||||
|
Subtitle = "Drag me",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/Space.png"),
|
||||||
|
DataPackage = CreateDataPackageForImage("Assets/Images/Space.png"),
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Swirls",
|
||||||
|
Subtitle = "Drop me",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||||
|
DataPackage = CreateDataPackageForImage("Assets/Images/Swirls.png"),
|
||||||
|
},
|
||||||
|
new ListItem(new NoOpCommand())
|
||||||
|
{
|
||||||
|
Title = "Windows Digital",
|
||||||
|
Subtitle = "Drag me",
|
||||||
|
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||||
|
DataPackage = CreateDataPackageForImage("Assets/Images/Win-Digital.png"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataPackage CreateDataPackageForImage(string relativePath)
|
||||||
|
{
|
||||||
|
var dataPackageWithImage = new DataPackage
|
||||||
|
{
|
||||||
|
Properties =
|
||||||
|
{
|
||||||
|
Title = "Image",
|
||||||
|
Description = "This item has an image associated with it.",
|
||||||
|
},
|
||||||
|
RequestedOperation = DataPackageOperation.Copy,
|
||||||
|
};
|
||||||
|
|
||||||
|
var imageUri = new Uri($"ms-appx:///{relativePath}");
|
||||||
|
|
||||||
|
dataPackageWithImage.SetDataProvider(StandardDataFormats.Bitmap, async (request) =>
|
||||||
|
{
|
||||||
|
var deferral = request.GetDeferral();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(imageUri);
|
||||||
|
var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
|
||||||
|
var streamRef = RandomAccessStreamReference.CreateFromStream(stream);
|
||||||
|
request.SetData(streamRef);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
deferral.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dataPackageWithImage.SetDataProvider(StandardDataFormats.StorageItems, async (request) =>
|
||||||
|
{
|
||||||
|
var deferral = request.GetDeferral();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(imageUri);
|
||||||
|
var items = new[] { file };
|
||||||
|
request.SetData(items);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
deferral.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dataPackageWithImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,6 +106,13 @@ public partial class SamplesListPage : ListPage
|
|||||||
Subtitle = "A demo of the settings helpers",
|
Subtitle = "A demo of the settings helpers",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Data package samples
|
||||||
|
new ListItem(new SampleDataTransferPage())
|
||||||
|
{
|
||||||
|
Title = "Clipboard and Drag-and-Drop Demo",
|
||||||
|
Subtitle = "Demonstrates clipboard integration and drag-and-drop functionality",
|
||||||
|
},
|
||||||
|
|
||||||
// Evil edge cases
|
// Evil edge cases
|
||||||
// Anything weird that might break the palette - put that in here.
|
// Anything weird that might break the palette - put that in here.
|
||||||
new ListItem(new EvilSamplesPage())
|
new ListItem(new EvilSamplesPage())
|
||||||
|
|||||||
@@ -2,14 +2,23 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Windows.ApplicationModel.DataTransfer;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
using WinRT;
|
||||||
|
|
||||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
public partial class CommandItem : BaseObservable, ICommandItem
|
public partial class CommandItem : BaseObservable, ICommandItem, IExtendedAttributesProvider
|
||||||
{
|
{
|
||||||
|
private readonly PropertySet _extendedAttributes = new();
|
||||||
|
|
||||||
private ICommand? _command;
|
private ICommand? _command;
|
||||||
private WeakEventListener<CommandItem, object, IPropChangedEventArgs>? _commandListener;
|
private WeakEventListener<CommandItem, object, IPropChangedEventArgs>? _commandListener;
|
||||||
private string _title = string.Empty;
|
private string _title = string.Empty;
|
||||||
|
|
||||||
|
private DataPackage? _dataPackage;
|
||||||
|
private DataPackageView? _dataPackageView;
|
||||||
|
|
||||||
public virtual IIconInfo? Icon
|
public virtual IIconInfo? Icon
|
||||||
{
|
{
|
||||||
get => field;
|
get => field;
|
||||||
@@ -91,6 +100,32 @@ public partial class CommandItem : BaseObservable, ICommandItem
|
|||||||
|
|
||||||
= [];
|
= [];
|
||||||
|
|
||||||
|
public DataPackage? DataPackage
|
||||||
|
{
|
||||||
|
get => _dataPackage;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_dataPackage = value;
|
||||||
|
_dataPackageView = null;
|
||||||
|
_extendedAttributes[WellKnownExtensionAttributes.DataPackage] = value?.AsAgile().Get()?.GetView()!;
|
||||||
|
OnPropertyChanged(nameof(DataPackage));
|
||||||
|
OnPropertyChanged(nameof(DataPackageView));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataPackageView? DataPackageView
|
||||||
|
{
|
||||||
|
get => _dataPackageView;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_dataPackage = null;
|
||||||
|
_dataPackageView = value;
|
||||||
|
_extendedAttributes[WellKnownExtensionAttributes.DataPackage] = value?.AsAgile().Get()!;
|
||||||
|
OnPropertyChanged(nameof(DataPackage));
|
||||||
|
OnPropertyChanged(nameof(DataPackageView));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public CommandItem()
|
public CommandItem()
|
||||||
: this(new NoOpCommand())
|
: this(new NoOpCommand())
|
||||||
{
|
{
|
||||||
@@ -132,4 +167,9 @@ public partial class CommandItem : BaseObservable, ICommandItem
|
|||||||
Title = title;
|
Title = title;
|
||||||
Subtitle = subtitle;
|
Subtitle = subtitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IDictionary<string, object> GetProperties()
|
||||||
|
{
|
||||||
|
return _extendedAttributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,6 @@ public partial class FontIconData : IconData, IExtendedAttributesProvider
|
|||||||
|
|
||||||
public IDictionary<string, object>? GetProperties() => new ValueSet()
|
public IDictionary<string, object>? GetProperties() => new ValueSet()
|
||||||
{
|
{
|
||||||
{ "FontFamily", FontFamily },
|
{ WellKnownExtensionAttributes.FontFamily, FontFamily },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
|
public static class WellKnownExtensionAttributes
|
||||||
|
{
|
||||||
|
public const string DataPackage = "Microsoft.CommandPalette.DataPackage";
|
||||||
|
|
||||||
|
public const string FontFamily = "FontFamily";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user