merge main

This commit is contained in:
vanzue
2025-12-12 16:41:31 +08:00
21 changed files with 673 additions and 43 deletions

View File

@@ -198,20 +198,14 @@ namespace AdvancedPaste.Pages
}
}
private async void ClipboardHistory_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
private void ClipboardHistory_ItemInvoked(ItemsView sender, ItemsViewItemInvokedEventArgs args)
{
if (args.InvokedItem is ClipboardItem item)
if (args.InvokedItem is ClipboardItem item && item.Item is not null)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteClipboardItemClicked());
if (!string.IsNullOrEmpty(item.Content))
{
ClipboardHelper.SetTextContent(item.Content);
}
else if (item.Image is not null)
{
RandomAccessStreamReference image = await item.Item.Content.GetBitmapAsync();
ClipboardHelper.SetImageContent(image);
}
// Use SetHistoryItemAsContent to set the clipboard content without creating a new history entry
Clipboard.SetHistoryItemAsContent(item.Item);
}
}
}

View File

@@ -8,6 +8,7 @@ using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
namespace Microsoft.CmdPal.Core.ViewModels;
@@ -16,6 +17,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
{
public ExtensionObject<ICommandItem> Model => _commandItemModel;
private ExtensionObject<IExtendedAttributesProvider>? ExtendedAttributesProvider { get; set; }
private readonly ExtensionObject<ICommandItem> _commandItemModel = new(null);
private CommandContextItemViewModel? _defaultCommandContextItemViewModel;
@@ -65,6 +68,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
public DataPackageView? DataPackage { get; private set; }
public List<IContextItemViewModel> AllCommands
{
get
@@ -157,6 +162,13 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
// will never be able to load Hotkeys & aliases
UpdateProperty(nameof(IsInitialized));
if (model is IExtendedAttributesProvider extendedAttributesProvider)
{
ExtendedAttributesProvider = new ExtensionObject<IExtendedAttributesProvider>(extendedAttributesProvider);
var properties = extendedAttributesProvider.GetProperties();
UpdateDataPackage(properties);
}
Initialized |= InitializedState.Initialized;
}
@@ -379,6 +391,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
UpdateProperty(nameof(SecondaryCommandName));
UpdateProperty(nameof(HasMoreCommands));
break;
case nameof(DataPackage):
UpdateDataPackage(ExtendedAttributesProvider?.Unsafe?.GetProperties());
break;
}
@@ -431,6 +446,16 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
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()
{
base.UnsafeCleanup();

View File

@@ -5,6 +5,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Storage.Streams;
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
// 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.
if (props?.TryGetValue("FontFamily", out var family) ?? false)
if (props?.TryGetValue(WellKnownExtensionAttributes.FontFamily, out var family) ?? false)
{
FontFamily = family as string;
}

View File

@@ -18,7 +18,7 @@ using WyHash;
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 ProviderSettings _providerSettings;
@@ -232,6 +232,13 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
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}";
}
public IDictionary<string, object?> GetProperties()
{
return new Dictionary<string, object?>
{
[WellKnownExtensionAttributes.DataPackage] = _commandItemViewModel?.DataPackage,
};
}
}

View File

@@ -368,32 +368,69 @@ internal sealed partial class BlurImageControl : Control
{
try
{
if (imageSource is Microsoft.UI.Xaml.Media.Imaging.BitmapImage bitmapImage)
if (imageSource is not Microsoft.UI.Xaml.Media.Imaging.BitmapImage bitmapImage)
{
_imageBrush ??= _compositor?.CreateSurfaceBrush();
if (_imageBrush is null)
{
return;
}
var loadedSurface = LoadedImageSurface.StartLoadFromUri(bitmapImage.UriSource);
loadedSurface.LoadCompleted += (_, _) =>
{
if (_imageBrush is not null)
{
_imageBrush.Surface = loadedSurface;
_imageBrush.Stretch = ConvertStretch(ImageStretch);
_imageBrush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.Linear;
}
};
_effectBrush?.SetSourceParameter(ImageSourceParameterName, _imageBrush);
return;
}
_imageBrush ??= _compositor?.CreateSurfaceBrush();
if (_imageBrush is null)
{
return;
}
Logger.LogDebug($"Starting load of BlurImageControl from '{bitmapImage.UriSource}'");
var loadedSurface = LoadedImageSurface.StartLoadFromUri(bitmapImage.UriSource);
loadedSurface.LoadCompleted += OnLoadedSurfaceOnLoadCompleted;
SetLoadedSurfaceToBrush(loadedSurface);
_effectBrush?.SetSourceParameter(ImageSourceParameterName, _imageBrush);
}
catch (Exception ex)
{
Logger.LogError("Failed to load image for BlurImageControl: {0}", ex);
}
return;
void OnLoadedSurfaceOnLoadCompleted(LoadedImageSurface loadedSurface, LoadedImageSourceLoadCompletedEventArgs e)
{
switch (e.Status)
{
case LoadedImageSourceLoadStatus.Success:
Logger.LogDebug($"BlurImageControl loaded successfully: has _imageBrush? {_imageBrush != null}");
try
{
SetLoadedSurfaceToBrush(loadedSurface);
}
catch (Exception ex)
{
Logger.LogError("Failed to set surface in BlurImageControl", ex);
throw;
}
break;
case LoadedImageSourceLoadStatus.NetworkError:
case LoadedImageSourceLoadStatus.InvalidFormat:
case LoadedImageSourceLoadStatus.Other:
default:
Logger.LogError($"Failed to load image for BlurImageControl: Load status {e.Status}");
break;
}
}
}
private void SetLoadedSurfaceToBrush(LoadedImageSurface loadedSurface)
{
var surfaceBrush = _imageBrush;
if (surfaceBrush is null)
{
return;
}
surfaceBrush.Surface = loadedSurface;
surfaceBrush.Stretch = ConvertStretch(ImageStretch);
surfaceBrush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.Linear;
}
private static CompositionStretch ConvertStretch(Stretch stretch)

View File

@@ -439,9 +439,12 @@
<ListView
x:Name="ItemsList"
Padding="0,2,0,0"
CanDragItems="True"
ContextCanceled="Items_OnContextCanceled"
ContextRequested="Items_OnContextRequested"
DoubleTapped="Items_DoubleTapped"
DragItemsCompleted="Items_DragItemsCompleted"
DragItemsStarting="Items_DragItemsStarting"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="Items_ItemClick"
@@ -458,9 +461,12 @@
<GridView
x:Name="ItemsGrid"
Padding="16,0"
CanDragItems="True"
ContextCanceled="Items_OnContextCanceled"
ContextRequested="Items_OnContextRequested"
DoubleTapped="Items_DoubleTapped"
DragItemsCompleted="Items_DragItemsCompleted"
DragItemsStarting="Items_DragItemsStarting"
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="Items_ItemClick"

View File

@@ -18,6 +18,7 @@ using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.System;
@@ -891,6 +892,89 @@ public sealed partial class ListPage : Page,
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>
/// Code stealed from <see cref="Controls.ContextMenu.NavigateDown"/>
/// </summary>

View File

@@ -52,6 +52,8 @@ public sealed partial class MainWindow : WindowEx,
IRecipient<ShowWindowMessage>,
IRecipient<HideWindowMessage>,
IRecipient<QuitMessage>,
IRecipient<DragStartedMessage>,
IRecipient<DragCompletedMessage>,
IDisposable
{
private const int DefaultWidth = 800;
@@ -79,6 +81,8 @@ public sealed partial class MainWindow : WindowEx,
private WindowPosition _currentWindowPosition = new();
private bool _preventHideWhenDeactivated;
private MainWindowViewModel ViewModel { get; }
public MainWindow()
@@ -119,6 +123,8 @@ public sealed partial class MainWindow : WindowEx,
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
WeakReferenceMessenger.Default.Register<DragStartedMessage>(this);
WeakReferenceMessenger.Default.Register<DragCompletedMessage>(this);
// Hide our titlebar.
// We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed
@@ -751,6 +757,12 @@ public sealed partial class MainWindow : WindowEx,
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:
HideWindow();
@@ -1027,4 +1039,44 @@ public sealed partial class MainWindow : WindowEx,
_windowThemeSynchronizer.Dispose();
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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -63,4 +63,7 @@ CreateWindowEx
WNDCLASSEXW
RegisterClassEx
GetStockObject
GetModuleHandle
GetModuleHandle
GetWindowThreadProcessId
AttachThreadInput

View File

@@ -12,6 +12,8 @@ using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
using Microsoft.CmdPal.Ext.ClipboardHistory.Helpers.Analyzers;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
using WinRT;
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Models;
@@ -62,6 +64,8 @@ internal sealed partial class ClipboardListItem : ListItem
RequestedShortcut = KeyChords.DeleteEntry,
};
DataPackageView = _item.Item.Content;
if (item.IsImage)
{
Title = "Image";

View File

@@ -2,15 +2,17 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CmdPal.Common.Commands;
using Microsoft.CmdPal.Ext.Indexer.Commands;
using Microsoft.CmdPal.Core.Common.Commands;
using Microsoft.CmdPal.Ext.Indexer.Helpers;
using Microsoft.CmdPal.Ext.Indexer.Pages;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation.Metadata;
using FileAttributes = System.IO.FileAttributes;
namespace Microsoft.CmdPal.Ext.Indexer.Data;
@@ -37,6 +39,8 @@ internal sealed partial class IndexerListItem : ListItem
Title = indexerItem.FileName;
Subtitle = indexerItem.FullPath;
DataPackage = DataPackageHelper.CreateDataPackageForPath(this, FilePath);
var commands = FileCommands(indexerItem.FullPath, browseByDefault);
if (commands.Any())
{

View File

@@ -7,6 +7,7 @@ using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Helpers;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Storage.Streams;
@@ -42,6 +43,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
Subtitle = string.Empty;
Icon = null;
MoreCommands = null;
DataPackage = null;
return;
}
@@ -53,6 +55,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
Subtitle = string.Empty;
Icon = null;
MoreCommands = null;
DataPackage = null;
return;
}
@@ -67,6 +70,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
Subtitle = item.FileName;
Title = item.FullPath;
Icon = listItemForUs.Icon;
DataPackage = DataPackageHelper.CreateDataPackageForPath(listItemForUs, item.FullPath);
try
{
@@ -92,13 +96,15 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
_searchEngine.Query(query, _queryCookie);
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.
// Hide ourselves.
Title = string.Empty;
Subtitle = string.Empty;
Command = new NoOpCommand();
MoreCommands = null;
DataPackage = null;
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!
// Return it.
Title = results[0].Title;
Subtitle = results[0].Subtitle;
Icon = results[0].Icon;
Command = results[0].Command;
MoreCommands = results[0].MoreCommands;
Title = indexerListItem.Title;
Subtitle = indexerListItem.Subtitle;
Icon = indexerListItem.Icon;
Command = indexerListItem.Command;
MoreCommands = indexerListItem.MoreCommands;
DataPackage = DataPackageHelper.CreateDataPackageForPath(indexerListItem, indexerListItem.FilePath);
return;
}
@@ -121,6 +128,8 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
Title = string.Format(CultureInfo.CurrentCulture, fallbackItemSearchPageTitleCompositeFormat, query);
Icon = Icons.FileExplorerIcon;
Command = indexerPage;
MoreCommands = null;
DataPackage = null;
return;
}
@@ -131,6 +140,7 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System
Icon = null;
Command = new NoOpCommand();
MoreCommands = null;
DataPackage = null;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -5,6 +5,7 @@
using System.Collections.Generic;
using Microsoft.CmdPal.Core.Common.Commands;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Helpers;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
@@ -28,6 +29,9 @@ internal sealed partial class ExploreListItem : ListItem
Title = indexerItem.FileName;
Subtitle = indexerItem.FullPath;
DataPackage = DataPackageHelper.CreateDataPackageForPath(this, FilePath);
List<CommandContextItem> context = [];
if (indexerItem.IsDirectory())
{

View File

@@ -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;
}
}

View File

@@ -106,6 +106,13 @@ public partial class SamplesListPage : ListPage
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
// Anything weird that might break the palette - put that in here.
new ListItem(new EvilSamplesPage())

View File

@@ -2,14 +2,23 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// 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;
public partial class CommandItem : BaseObservable, ICommandItem
public partial class CommandItem : BaseObservable, ICommandItem, IExtendedAttributesProvider
{
private readonly PropertySet _extendedAttributes = new();
private ICommand? _command;
private WeakEventListener<CommandItem, object, IPropChangedEventArgs>? _commandListener;
private string _title = string.Empty;
private DataPackage? _dataPackage;
private DataPackageView? _dataPackageView;
public virtual IIconInfo? Icon
{
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()
: this(new NoOpCommand())
{
@@ -132,4 +167,9 @@ public partial class CommandItem : BaseObservable, ICommandItem
Title = title;
Subtitle = subtitle;
}
public IDictionary<string, object> GetProperties()
{
return _extendedAttributes;
}
}

View File

@@ -27,6 +27,6 @@ public partial class FontIconData : IconData, IExtendedAttributesProvider
public IDictionary<string, object>? GetProperties() => new ValueSet()
{
{ "FontFamily", FontFamily },
{ WellKnownExtensionAttributes.FontFamily, FontFamily },
};
}

View File

@@ -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";
}