diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs index 464aa50e148..7b7bb028443 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs @@ -45,11 +45,7 @@ public DataObject() /// public DataObject(object data) { - if (data is DataObject dataObject) - { - _innerData = dataObject._innerData; - } - else if (data is IDataObject iDataObject) + if (data is IDataObject iDataObject) { _innerData = Composition.CreateFromWinFormsDataObject(iDataObject); } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs index dd7d35e65d6..78dae861176 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs @@ -699,4 +699,33 @@ public void Clipboard_SetDataObject_Text() byte[] array = stream.ToArray(); array.Should().BeEquivalentTo("Hello, World!\0"u8.ToArray()); } + + [WinFormsFact] + public void Clipboard_DerivedDataObject_DataPresent() + { + // https://github.com/dotnet/winforms/issues/12789 + SomeDataObject data = new(); + + // This was provided as a workaround for the above and should not break, but should + // also work without it. + data.SetData(SomeDataObject.Format, data); + + Clipboard.SetDataObject(data); + Clipboard.ContainsData(SomeDataObject.Format).Should().BeTrue(); + Clipboard.GetDataObject()!.GetDataPresent(SomeDataObject.Format).Should().BeTrue(); + + data = new(); + Clipboard.SetDataObject(data); + Clipboard.ContainsData(SomeDataObject.Format).Should().BeTrue(); + Clipboard.GetDataObject()!.GetDataPresent(SomeDataObject.Format).Should().BeTrue(); + } + + public class SomeDataObject : DataObject + { + public static string Format => "SomeDataObjectId"; + public override string[] GetFormats() => [Format]; + + public override bool GetDataPresent(string format, bool autoConvert) + => format == Format || base.GetDataPresent(format, autoConvert); + } } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs index 9090c2e9728..baa48e839c5 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs @@ -2657,4 +2657,77 @@ public unsafe void DataObject_ComTypesIDataObject_MockRoundTrip_IsWrapped() IDataObject outData = dropTargetAccessor.CreateDelegate()(inDataPtr); outData.Should().BeSameAs(inData); } + + [Fact] + public void DataObject_CreateFromDataObject_DoesNotUnwrapDataStore() + { + // The inner data should not have it's data store unwrapped. + DataObject dataObject = new(); + DataObject wrapped = new(dataObject); + DataObject.Composition composition = wrapped.TestAccessor().Dynamic._innerData; + IDataObject original = composition.TestAccessor().Dynamic._winFormsDataObject; + original.Should().BeSameAs(dataObject); + } + + [Fact] + public void DataObject_CreateFromDataObject_VirtualsAreCalled() + { + Mock mock = new(MockBehavior.Loose); + DataObject wrapped = new(mock.Object); + + wrapped.GetData("Foo", false); + mock.Verify(o => o.GetData("Foo", false), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.GetData("Foo"); + mock.Verify(o => o.GetData("Foo", true), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.GetData(typeof(string)); + mock.Verify(o => o.GetData("System.String", true), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.GetDataPresent("Foo", false); + mock.Verify(o => o.GetDataPresent("Foo", false), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.GetDataPresent("Foo"); + mock.Verify(o => o.GetDataPresent("Foo", true), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.GetDataPresent(typeof(string)); + mock.Verify(o => o.GetDataPresent("System.String", true), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.GetFormats(false); + mock.Verify(o => o.GetFormats(false), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.GetFormats(); + mock.Verify(o => o.GetFormats(true), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.SetData("Foo", "Bar"); + mock.Verify(o => o.SetData("Foo", "Bar"), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.SetData(typeof(string), "Bar"); + mock.Verify(o => o.SetData(typeof(string), "Bar"), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + + wrapped.SetData("Bar"); + mock.Verify(o => o.SetData("Bar"), Times.Once()); + mock.VerifyNoOtherCalls(); + mock.Reset(); + } }