Clipboard rework (#19347)
* New clipboard with IDataTransfer * Move legacy format handling to DataObject wrappers * macOS clipboard rework * Browser clipboard rework * Android clipboard rework * iOS new clipboard * X11 clipboard rework * Simplify IDataTransfer API * Simplify IClipboardImpl API * Add DataFormat documentation * Make DataFormat.SystemName platform specific * Fix clipboard/DnD samples * Fix native clipboard UTI conversion * Adjust IDataTransfer namespaces * Add Obsolete attributes to IDataObject related methods * Better API for DataTransferItem * Tizen clipboard rework * Add missing clipboard extension methods * Split IDataTransferItem into IAsyncDataTransferItem and ISyncDataTransferItem * Rename back ISyncDataTransfer to IDataTransfer * Added IClipboard API suppressions * Make IPlatformDragSource NotClientImplementable * Rename DataFormatKinds * Added DataTransferItem.CreateText/File * Implemented typed DataFormat<T> * Fix X11 text/uri-list encoding * Add API suppressions * Adjust ClipboardUriListHelper stream ownership * Fix legacy clipboard BinaryFormatter deserialization * Fix macOS build
This commit is contained in:
committed by
GitHub
parent
d060f96d52
commit
1f404646fa
@@ -6,12 +6,10 @@
|
||||
|
||||
<Button Click="CopyText" Content="Copy text to clipboard" />
|
||||
<Button Click="PasteText" Content="Paste text from clipboard" />
|
||||
<Button Click="CopyTextDataObject" Content="Copy text to clipboard (data object)" />
|
||||
<Button Click="PasteTextDataObject" Content="Paste text from clipboard (data object)" />
|
||||
|
||||
<Button Click="CopyFilesDataObject" Content="Copy files to clipboard (data object)" />
|
||||
<Button Click="PasteFilesDataObject" Content="Paste files from clipboard (data object)" />
|
||||
|
||||
<Button Click="CopyFiles" Content="Copy files to clipboard" />
|
||||
<Button Click="PasteFiles" Content="Paste files from clipboard" />
|
||||
<Button Click="CopyBinaryData" Content="Copy bytes to clipboard" />
|
||||
<Button Click="PasteBinaryData" Content="Paste bytes from clipboard" />
|
||||
<Button Click="GetFormats" Content="Get clipboard formats" />
|
||||
<Button Click="Clear" Content="Clear clipboard" />
|
||||
|
||||
|
||||
@@ -10,21 +10,22 @@ using Avalonia.Input.Platform;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Platform.Storage.FileIO;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace ControlCatalog.Pages
|
||||
{
|
||||
public partial class ClipboardPage : UserControl
|
||||
{
|
||||
private readonly DataFormat<byte[]> _customBinaryDataFormat =
|
||||
DataFormat.CreateBytesApplicationFormat("controlcatalog-binary-data");
|
||||
|
||||
private INotificationManager? _notificationManager;
|
||||
private INotificationManager NotificationManager => _notificationManager
|
||||
??= new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
|
||||
|
||||
private readonly DispatcherTimer _clipboardLastDataObjectChecker;
|
||||
private DataObject? _storedDataObject;
|
||||
private DataTransfer? _storedDataTransfer;
|
||||
public ClipboardPage()
|
||||
{
|
||||
_clipboardLastDataObjectChecker =
|
||||
@@ -41,37 +42,19 @@ namespace ControlCatalog.Pages
|
||||
|
||||
private async void CopyText(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent)
|
||||
await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty);
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
await clipboard.SetTextAsync(ClipboardContent.Text ?? string.Empty);
|
||||
}
|
||||
|
||||
private async void PasteText(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
ClipboardContent.Text = await clipboard.GetTextAsync();
|
||||
ClipboardContent.Text = await clipboard.TryGetTextAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void CopyTextDataObject(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
var dataObject = _storedDataObject = new DataObject();
|
||||
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
|
||||
await clipboard.SetDataObjectAsync(dataObject);
|
||||
}
|
||||
}
|
||||
|
||||
private async void PasteTextDataObject(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private async void CopyFilesDataObject(object? sender, RoutedEventArgs args)
|
||||
private async void CopyFiles(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
@@ -105,10 +88,11 @@ namespace ControlCatalog.Pages
|
||||
|
||||
if (files.Count > 0)
|
||||
{
|
||||
var dataObject = _storedDataObject = new DataObject();
|
||||
dataObject.Set(DataFormats.Files, files);
|
||||
await clipboard.SetDataObjectAsync(dataObject);
|
||||
NotificationManager.Show(new Notification("Success", "Copy completated.", NotificationType.Success));
|
||||
var dataTransfer = _storedDataTransfer = new DataTransfer();
|
||||
foreach (var file in files)
|
||||
dataTransfer.Add(DataTransferItem.Create(DataFormat.File, file));
|
||||
await clipboard.SetDataAsync(dataTransfer);
|
||||
NotificationManager.Show(new Notification("Success", "Copy completed.", NotificationType.Success));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -117,11 +101,11 @@ namespace ControlCatalog.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async void PasteFilesDataObject(object? sender, RoutedEventArgs args)
|
||||
private async void PasteFiles(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
var files = await clipboard.GetDataAsync(DataFormats.Files) as IEnumerable<Avalonia.Platform.Storage.IStorageItem>;
|
||||
var files = await clipboard.TryGetFilesAsync();
|
||||
|
||||
ClipboardContent.Text = files != null ? string.Join(Environment.NewLine, files.Select(f => f.TryGetLocalPath() ?? f.Name)) : string.Empty;
|
||||
}
|
||||
@@ -131,11 +115,32 @@ namespace ControlCatalog.Pages
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
var formats = await clipboard.GetFormatsAsync();
|
||||
var formats = await clipboard.GetDataFormatsAsync();
|
||||
ClipboardContent.Text = string.Join(Environment.NewLine, formats);
|
||||
}
|
||||
}
|
||||
|
||||
private async void CopyBinaryData(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
var dataTransfer = _storedDataTransfer = new DataTransfer();
|
||||
var bytes = new byte[10 * 1024 * 1024];
|
||||
new Random().NextBytes(bytes);
|
||||
dataTransfer.Add(DataTransferItem.Create(_customBinaryDataFormat, bytes));
|
||||
await clipboard.SetDataAsync(dataTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
private async void PasteBinaryData(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
var bytes = await clipboard.TryGetValueAsync(_customBinaryDataFormat);
|
||||
ClipboardContent.Text = bytes is null ? "<null>" : $"{bytes.Length} bytes";
|
||||
}
|
||||
}
|
||||
|
||||
private async void Clear(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
@@ -159,22 +164,28 @@ namespace ControlCatalog.Pages
|
||||
}
|
||||
|
||||
private Run OwnsClipboardDataObject => this.Get<Run>("OwnsClipboardDataObject");
|
||||
private bool _checkingClipboardDataObject;
|
||||
private bool _checkingClipboardDataTransfer;
|
||||
private async void CheckLastDataObject(object? sender, EventArgs e)
|
||||
{
|
||||
if(_checkingClipboardDataObject)
|
||||
if(_checkingClipboardDataTransfer)
|
||||
return;
|
||||
try
|
||||
{
|
||||
_checkingClipboardDataObject = true;
|
||||
var task = TopLevel.GetTopLevel(this)?.Clipboard?.TryGetInProcessDataObjectAsync();
|
||||
var owns = task != null && (await task) == _storedDataObject && _storedDataObject != null;
|
||||
_checkingClipboardDataTransfer = true;
|
||||
|
||||
var owns = false;
|
||||
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
|
||||
{
|
||||
var dataTransfer = await clipboard.TryGetInProcessDataAsync();
|
||||
owns = dataTransfer == _storedDataTransfer && dataTransfer is not null;
|
||||
}
|
||||
|
||||
OwnsClipboardDataObject.Text = owns ? "Yes" : "No";
|
||||
OwnsClipboardDataObject.Foreground = owns ? Brushes.Green : Brushes.Red;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_checkingClipboardDataObject = false;
|
||||
_checkingClipboardDataTransfer = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform.Storage;
|
||||
|
||||
@@ -12,7 +14,10 @@ namespace ControlCatalog.Pages
|
||||
public class DragAndDropPage : UserControl
|
||||
{
|
||||
private readonly TextBlock _dropState;
|
||||
private const string CustomFormat = "application/xxx-avalonia-controlcatalog-custom";
|
||||
|
||||
private readonly DataFormat<string> _customFormat =
|
||||
DataFormat.CreateStringApplicationFormat("xxx-avalonia-controlcatalog-custom");
|
||||
|
||||
public DragAndDropPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -22,13 +27,13 @@ namespace ControlCatalog.Pages
|
||||
|
||||
SetupDnd(
|
||||
"Text",
|
||||
d => d.Set(DataFormats.Text, $"Text was dragged {++textCount} times"),
|
||||
d => d.Add(DataTransferItem.Create(DataFormat.Text, $"Text was dragged {++textCount} times")),
|
||||
DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
|
||||
|
||||
SetupDnd(
|
||||
"Custom",
|
||||
d => d.Set(CustomFormat, "Test123"),
|
||||
DragDropEffects.Move);
|
||||
d => d.Add(DataTransferItem.Create(_customFormat, "Test123")),
|
||||
DragDropEffects.Copy | DragDropEffects.Move);
|
||||
|
||||
SetupDnd(
|
||||
"Files",
|
||||
@@ -38,13 +43,13 @@ namespace ControlCatalog.Pages
|
||||
TopLevel.GetTopLevel(this) is { } topLevel &&
|
||||
await topLevel.StorageProvider.TryGetFileFromPathAsync(name) is { } storageFile)
|
||||
{
|
||||
d.Set(DataFormats.Files, new[] { storageFile });
|
||||
d.Add(DataTransferItem.Create(DataFormat.File, storageFile));
|
||||
}
|
||||
},
|
||||
DragDropEffects.Copy);
|
||||
}
|
||||
|
||||
private void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects) =>
|
||||
private void SetupDnd(string suffix, Action<DataTransfer> factory, DragDropEffects effects) =>
|
||||
SetupDnd(
|
||||
suffix,
|
||||
o =>
|
||||
@@ -54,17 +59,17 @@ namespace ControlCatalog.Pages
|
||||
},
|
||||
effects);
|
||||
|
||||
private void SetupDnd(string suffix, Func<DataObject, Task> factory, DragDropEffects effects)
|
||||
private void SetupDnd(string suffix, Func<DataTransfer, Task> factory, DragDropEffects effects)
|
||||
{
|
||||
var dragMe = this.Get<Border>("DragMe" + suffix);
|
||||
var dragState = this.Get<TextBlock>("DragState" + suffix);
|
||||
|
||||
async void DoDrag(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
var dragData = new DataObject();
|
||||
var dragData = new DataTransfer();
|
||||
await factory(dragData);
|
||||
|
||||
var result = await DragDrop.DoDragDrop(e, dragData, effects);
|
||||
var result = await DragDrop.DoDragDropAsync(e, dragData, effects);
|
||||
switch (result)
|
||||
{
|
||||
case DragDropEffects.Move:
|
||||
@@ -97,9 +102,9 @@ namespace ControlCatalog.Pages
|
||||
}
|
||||
|
||||
// Only allow if the dragged data contains text or filenames.
|
||||
if (!e.Data.Contains(DataFormats.Text)
|
||||
&& !e.Data.Contains(DataFormats.Files)
|
||||
&& !e.Data.Contains(CustomFormat))
|
||||
if (!e.DataTransfer.Contains(DataFormat.Text)
|
||||
&& !e.DataTransfer.Contains(DataFormat.File)
|
||||
&& !e.DataTransfer.Contains(_customFormat))
|
||||
e.DragEffects = DragDropEffects.None;
|
||||
}
|
||||
|
||||
@@ -114,13 +119,13 @@ namespace ControlCatalog.Pages
|
||||
e.DragEffects = e.DragEffects & (DragDropEffects.Copy);
|
||||
}
|
||||
|
||||
if (e.Data.Contains(DataFormats.Text))
|
||||
if (e.DataTransfer.Contains(DataFormat.Text))
|
||||
{
|
||||
_dropState.Text = e.Data.GetText();
|
||||
_dropState.Text = e.DataTransfer.TryGetText();
|
||||
}
|
||||
else if (e.Data.Contains(DataFormats.Files))
|
||||
else if (e.DataTransfer.Contains(DataFormat.File))
|
||||
{
|
||||
var files = e.Data.GetFiles() ?? Array.Empty<IStorageItem>();
|
||||
var files = e.DataTransfer.TryGetFiles() ?? [];
|
||||
var contentStr = "";
|
||||
|
||||
foreach (var item in files)
|
||||
@@ -143,16 +148,9 @@ namespace ControlCatalog.Pages
|
||||
|
||||
_dropState.Text = contentStr;
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
else if (e.Data.Contains(DataFormats.FileNames))
|
||||
else if (e.DataTransfer.Contains(_customFormat))
|
||||
{
|
||||
var files = e.Data.GetFileNames();
|
||||
_dropState.Text = string.Join(Environment.NewLine, files ?? Array.Empty<string>());
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
else if (e.Data.Contains(CustomFormat))
|
||||
{
|
||||
_dropState.Text = "Custom: " + e.Data.Get(CustomFormat);
|
||||
_dropState.Text = "Custom: " + e.DataTransfer.TryGetValue(_customFormat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user