Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding new strongly typed methods to Clipboard, DataObject and IDataObject. #11545

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Winforms.sln
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "documentation", "documentat
docs\developer-guide.md = docs\developer-guide.md
docs\getting-started.md = docs\getting-started.md
docs\issue-guide.md = docs\issue-guide.md
docs\list-of-diagnostics.md = docs\list-of-diagnostics.md
docs\porting-guidelines.md = docs\porting-guidelines.md
README.md = README.md
docs\roadmap.md = docs\roadmap.md
Expand Down
4 changes: 4 additions & 0 deletions docs/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The acceptance criteria for adding an obsoletion includes:
* Add new constants to `src\Common\src\Obsoletions.cs`, following the existing conventions
* A `...Message` const using the same description added to the table below
* A `...DiagnosticId` const for the `WFDEV###` id
* If adding <Obsolete> attribute to Microsoft.VisualBasic.Forms assembly, edit src\Microsoft.VisualBasic.Forms\src\Obsoletions.vb file
* Annotate `src` files by referring to the constants defined from `Obsoletions.cs`
* Specify the `UrlFormat = Obsoletions.SharedUrlFormat`
* Example: `[Obsolete(Obsoletions.DomainUpDownAccessibleObjectMessage, DiagnosticId = Obsoletions.DomainUpDownAccessibleObjectDiagnosticId, UrlFormat = Obsoletions.SharedUrlFormat)]`
Expand All @@ -39,6 +40,9 @@ The acceptance criteria for adding an obsoletion includes:
| __`WFDEV002`__ | `DomainUpDown.DomainUpDownAccessibleObject` is no longer used to provide accessible support for `DomainUpDown` controls. Use `ControlAccessibleObject` instead. |
| __`WFDEV003`__ | `DomainUpDown.DomainItemAccessibleObject` is no longer used to provide accessible support for `DomainUpDown` items. |
| __`WFDEV004`__ | `Form.OnClosing`, `Form.OnClosed` and the corresponding events are obsolete. Use `Form.OnFormClosing`, `Form.OnFormClosed`, `Form.FormClosing` and `Form.FormClosed` instead. |
| __`WFDEV005`__ | `Clipboard.GetData(string)` method is obsolete. Use `Clipboard.TryGetData<T>` instead. |
| __`WFDEV005`__ | `DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead. |
| __`WFDEV005`__ | `ClipboardProxy.GetData(As String)` method is obsolete. Use `ClipboardProxy.TryGetData(Of T)(As String, As T)` instead. |


## Analyzer Warnings
Expand Down
9 changes: 8 additions & 1 deletion src/Common/src/Obsoletions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal static class Obsoletions
{
internal const string SharedUrlFormat = "https://aka.ms/winforms-warnings/{0}";

// Please see docs\project\list-of-diagnostics.md for instructions on the steps required
// Please see docs\list-of-diagnostics.md for instructions on the steps required
// to introduce a new obsoletion, apply it to downlevel builds, claim a diagnostic id,
// and ensure the "aka.ms/dotnet-warnings/{0}" URL points to documentation for the obsoletion
// The diagnostic ids reserved for obsoletions are WFDEV### (WFDEV001 - WFDEV999).
Expand All @@ -24,4 +24,11 @@ internal static class Obsoletions

internal const string FormOnClosingClosedMessage = "Form.OnClosing, Form.OnClosed and the corresponding events are obsolete. Use Form.OnFormClosing, Form.OnFormClosed, Form.FormClosing and Form.FormClosed instead.";
internal const string FormOnClosingClosedDiagnosticId = "WFDEV004";

internal const string ClipboardGetDataMessage = "`Clipboard.GetData(string)` method is obsolete. Use `Clipboard.TryGetData<T>` instead.";
internal const string ClipboardGetDataDiagnosticId = "WFDEV005";

internal const string DataObjectGetDataMessage = "`DataObject.GetData` methods are obsolete. Use the corresponding `DataObject.TryGetData<T>` instead.";

internal const string ClipboardProxyGetDataMessage = "`ClipboardProxy.GetData(As String)` method is obsolete. Use `ClipboardProxy.TryGetData(Of T)(As String, As T)` instead.";
}
3 changes: 2 additions & 1 deletion src/Common/tests/TestUtilities/AppContextSwitchNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public const string EnableUnsafeBinaryFormatterSerialization
= "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization";

/// <summary>
/// Switch that controls <see cref="AppContext"/> switch caching.
/// Switch that controls <see cref="AppContext"/> switch caching. This switch is set to
/// <see langword="true" /> in our test assemblies.
/// </summary>
public const string LocalAppContext_DisableCaching
= "TestSwitch.LocalAppContext.DisableCaching";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Imports System.ComponentModel
Imports System.Drawing
Imports System.IO
Imports System.Windows.Forms
Imports System.Reflection.Metadata
Imports System.Runtime.InteropServices

Namespace Microsoft.VisualBasic.MyServices

Expand Down Expand Up @@ -93,8 +95,15 @@ Namespace Microsoft.VisualBasic.MyServices
''' </summary>
''' <param name="format">The type of data being sought.</param>
''' <returns>The data.</returns>
<Obsolete(
Obsoletions.ClipboardProxyGetDataMessage,
False,
DiagnosticId:=Obsoletions.ClipboardGetDataDiagnosticId,
UrlFormat:=Obsoletions.SharedUrlFormat)>
Public Function GetData(format As String) As Object
#Disable Warning WFDEV005 ' Type or member is obsolete
Return Clipboard.GetData(format)
#Enable Warning WFDEV005
End Function

''' <summary>
Expand Down Expand Up @@ -183,6 +192,16 @@ Namespace Microsoft.VisualBasic.MyServices
Clipboard.SetFileDropList(filePaths)
End Sub

''' <inheritdoc cref="Clipboard.TryGetData(Of T)(String, Func(Of TypeName, Type), ByRef T)" />
Public Function TryGetData(Of T)(format As String, resolver As Func(Of TypeName, Type), <Out> ByRef data As T) As Boolean
Return Clipboard.TryGetData(format, resolver, data)
End Function

''' <inheritdoc cref="Clipboard.TryGetData(Of T)(String, ByRef T)" />
Public Function TryGetData(Of T)(format As String, <Out> ByRef data As T) As Boolean
Return Clipboard.TryGetData(format, data)
End Function

''' <summary>
''' Saves the passed in <see cref="Image"/> to the clipboard.
''' </summary>
Expand All @@ -192,7 +211,7 @@ Namespace Microsoft.VisualBasic.MyServices
End Sub

''' <summary>
''' Saves the passed in String to the clipboard.
''' Saves the passed in <see cref="String" /> to the clipboard.
''' </summary>
''' <param name="text">The <see cref="String"/> to save.</param>
Public Sub SetText(text As String)
Expand Down
13 changes: 13 additions & 0 deletions src/Microsoft.VisualBasic.Forms/src/Obsoletions.vb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.

Friend NotInheritable Class Obsoletions

Friend Const SharedUrlFormat As String = "https://aka.ms/winforms-warnings/{0}"

' Please see docs\list-Of-diagnostics.md for how to claim a diagnostic id.
' The diagnostic ids reserved for obsoletions are WFDEV### (WFDEV001 - WFDEV999).

Friend Const ClipboardProxyGetDataMessage As String = "`ClipboardProxy.GetData(As String)` method is obsolete. Use `ClipboardProxy.TryGetData(Of T)(As String, As T)` instead."
Friend Const ClipboardGetDataDiagnosticId As String = "WFDEV005"
End Class
2 changes: 2 additions & 0 deletions src/Microsoft.VisualBasic.Forms/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Microsoft.VisualBasic.MyServices.ClipboardProxy.TryGetData(Of T)(format As String, ByRef data As T) -> Boolean
Microsoft.VisualBasic.MyServices.ClipboardProxy.TryGetData(Of T)(format As String, resolver As System.Func(Of System.Reflection.Metadata.TypeName, System.Type), ByRef data As T) -> Boolean
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
namespace Microsoft.VisualBasic.Devices.Tests;

[Collection("Sequential")]
[CollectionDefinition("Sequential", DisableParallelization = true)]
[UISettings(MaxAttempts = 3)] // Try up to 3 times before failing.
public class ComputerTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#nullable enable

using System.Drawing;
using System.Reflection.Metadata;
using Microsoft.VisualBasic.Devices;
using DataFormats = System.Windows.Forms.DataFormats;
using TextDataFormat = System.Windows.Forms.TextDataFormat;
Expand All @@ -16,6 +17,7 @@ namespace Microsoft.VisualBasic.MyServices.Tests;
[UISettings(MaxAttempts = 3)] // Try up to 3 times before failing.
public class ClipboardProxyTests
{
#pragma warning disable WFDEV005 // Type or member is obsolete
private static string GetUniqueText() => Guid.NewGuid().ToString("D");

[WinFormsFact]
Expand Down Expand Up @@ -83,4 +85,42 @@ public void Text()
System.Windows.Forms.Clipboard.GetText(TextDataFormat.UnicodeText).Should().Be(clipboard.GetText(TextDataFormat.UnicodeText));
clipboard.GetText(TextDataFormat.UnicodeText).Should().Be(text);
}

[WinFormsFact]
public void DataOfT_CustomType_BinaryFormatterRequired()
{
var clipboard = new Computer().Clipboard;
DataWithObjectField data = new("thing1", "thing2");
using BinaryFormatterScope scope = new(enable: true);
clipboard.SetData(typeof(DataWithObjectField).FullName!, data);
clipboard.TryGetData(typeof(DataWithObjectField).FullName!, DataResolver, out DataWithObjectField? actual).Should()
.Be(System.Windows.Forms.Clipboard.TryGetData(typeof(DataWithObjectField).FullName!, DataResolver, out DataWithObjectField? expected));
actual.Should().BeEquivalentTo(expected);
}

[Serializable]
private class DataWithObjectField
{
public DataWithObjectField(string text1, object object2)
{
_text1 = text1;
_object2 = object2;
}

public string _text1;
public object _object2;
}

private static Type DataResolver(TypeName typeName)
{
Type type = typeof(DataWithObjectField);

// Namespace-qualified type name.
if (type.FullName == typeName.FullName)
{
return type;
}

throw new NotSupportedException($"Unexpected type {typeName.AssemblyQualifiedName}.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.ComponentModel;
using System.Drawing;
using System.Formats.Nrbf;
using System.Private.Windows.Core.BinaryFormat;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.Serialization;
Expand Down Expand Up @@ -41,6 +42,50 @@ internal static SerializationRecord Decode(this Stream stream)
}
}

internal static SerializationRecord Decode(this Stream stream, out IReadOnlyDictionary<SerializationRecordId, SerializationRecord> recordMap)
{
try
{
return NrbfDecoder.Decode(stream, out recordMap, leaveOpen: true);
}
catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException)
{
// Make the exception easier to catch, but retain the original stack trace.
// TODO(TanyaSo) is this really converted to NotSupported up the stack??
throw ex.ConvertToSerializationException();
}
catch (TargetInvocationException ex)
{
throw ExceptionDispatchInfo.Capture(ex.InnerException!).SourceException.ConvertToSerializationException();
}
}

/// <summary>
/// Deserializes the <see cref="SerializationRecord"/> to an object.
/// </summary>
[RequiresUnreferencedCode("Ultimately calls resolver for type names in the data.")]
public static object? Deserialize(
this SerializationRecord rootRecord,
IReadOnlyDictionary<SerializationRecordId, SerializationRecord> recordMap,
ITypeResolver typeResolver)
{
DeserializationOptions options = new()
{
TypeResolver = typeResolver
};

try
{
return Deserializer.Deserialize(rootRecord.Id, recordMap, options);
}
catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException or TargetInvocationException or SerializationException)
{
Debug.WriteLine(ex.ToString());
}

return null;
}

internal delegate bool TryGetDelegate(SerializationRecord record, [NotNullWhen(true)] out object? value);

internal static bool TryGet(TryGetDelegate get, SerializationRecord record, [NotNullWhen(true)] out object? value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ public void BinaryFormatWriter_TryWriteDrawingPrimitivesObject_UnsupportedObject
stream.Position.Should().Be(0);
}

public static IEnumerable<object[]?> TryWriteFrameworkObject_SupportedObjects_TestData =>
((IEnumerable<object[]?>)HashtableTests.Hashtables_TestData).Concat(
public static IEnumerable<object[]> TryWriteFrameworkObject_SupportedObjects_TestData =>
((IEnumerable<object[]>)HashtableTests.Hashtables_TestData).Concat(
ListTests.PrimitiveLists_TestData).Concat(
ListTests.ArrayLists_TestData).Concat(
PrimitiveTypeTests.Primitive_Data).Concat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace System.Windows.Forms.Nrbf.Tests;

public class SerializationRecordExtensionsTests
{
public static IEnumerable<object[]?> TryGetFrameworkObject_SupportedObjects_TestData =>
public static IEnumerable<object[]> TryGetFrameworkObject_SupportedObjects_TestData =>
BinaryFormatWriterTests.TryWriteFrameworkObject_SupportedObjects_TestData;

[Theory]
Expand Down
Loading
Loading