From c0b1b7a6849732762c9a933a664167ac3a65f478 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Fri, 27 Mar 2026 15:24:19 +0800 Subject: [PATCH 1/7] Fix Clipboard.GetDataObject().GetImage regression for bitmap images --- .../System/Windows/Forms/OLE/DataObject.cs | 10 +++++++++- .../System/Windows/Forms/ClipboardTests.cs | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs b/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs index b9205b0ef05..7f9a829bc5a 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs @@ -221,7 +221,15 @@ public virtual StringCollection GetFileDropList() return dropList; } - public virtual Image? GetImage() => GetData(DataFormats.Bitmap, autoConvert: true) as Image; + public virtual Image? GetImage() + { + if (TryGetData(DataFormats.Bitmap, autoConvert: true, out Image? image)) + { + return image; + } + + return GetData(DataFormats.Bitmap, autoConvert: true) as Image; + } public virtual string GetText(TextDataFormat format) { diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs index 3a4b7f42fbf..f4cf6309008 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs @@ -356,6 +356,21 @@ public void SetImage_InvokeBitmap_GetReturnsExpected() Clipboard.ContainsImage().Should().BeTrue(); } + [WinFormsFact] + public void GetDataObject_GetImage_RoundTripsBitmap() + { + using Bitmap bitmap = new(10, 10); + bitmap.SetPixel(1, 2, Color.FromArgb(0x01, 0x02, 0x03, 0x04)); + Clipboard.SetImage(bitmap); + + DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType().Subject; + + var result = dataObject.GetImage().Should().BeOfType().Subject; + result.Size.Should().Be(bitmap.Size); + result.GetPixel(1, 2).Should().Be(Color.FromArgb(0xFF, 0xD2, 0xD2, 0xD2)); + Clipboard.ContainsImage().Should().BeTrue(); + } + [WinFormsFact] public void SetImage_InvokeMetafile_GetReturnsExpected() { From 049369cb5fbbb7d5fd7a5338ab420612a2dd7caa Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Fri, 27 Mar 2026 16:59:07 +0800 Subject: [PATCH 2/7] Use Loose so that protected virtual TryGetDataCore can be invoked --- .../System/Windows/Forms/DataObjectTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs index 3321c4bbf50..0dccb79d798 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs @@ -911,7 +911,10 @@ public void GetImage_InvokeWithData_ReturnsExpected(object result, Image expecte [MemberData(nameof(GetImage_TheoryData))] public void GetImage_InvokeMocked_ReturnsExpected(object result, Image expected) { - Mock mockDataObject = new(MockBehavior.Strict); + // Use Loose so that protected virtual TryGetDataCore can be invoked + // without an explicit setup and simply return default (false), + // forcing GetImage to fall back to GetData. + Mock mockDataObject = new(MockBehavior.Loose); mockDataObject .Setup(o => o.GetImage()) .CallBase(); From c943d782536476a95d1bdb480545488be47d3bec Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Tue, 7 Apr 2026 11:30:21 +0800 Subject: [PATCH 3/7] Remove GetData fallback from DataObject.GetImage and remove the mocked test that depended on the old GetData fallback. --- .../System/Windows/Forms/OLE/DataObject.cs | 11 ++--------- .../System/Windows/Forms/DataObjectTests.cs | 19 ------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs b/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs index 7f9a829bc5a..483d939dfd9 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/OLE/DataObject.cs @@ -221,15 +221,8 @@ public virtual StringCollection GetFileDropList() return dropList; } - public virtual Image? GetImage() - { - if (TryGetData(DataFormats.Bitmap, autoConvert: true, out Image? image)) - { - return image; - } - - return GetData(DataFormats.Bitmap, autoConvert: true) as Image; - } + public virtual Image? GetImage() => + TryGetData(DataFormats.Bitmap, autoConvert: true, out Image? image) ? image : null; public virtual string GetText(TextDataFormat format) { diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs index 0dccb79d798..b6e47a6f857 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs @@ -907,25 +907,6 @@ public void GetImage_InvokeWithData_ReturnsExpected(object result, Image expecte dataObject.GetImage().Should().Be(expected); } - [Theory] - [MemberData(nameof(GetImage_TheoryData))] - public void GetImage_InvokeMocked_ReturnsExpected(object result, Image expected) - { - // Use Loose so that protected virtual TryGetDataCore can be invoked - // without an explicit setup and simply return default (false), - // forcing GetImage to fall back to GetData. - Mock mockDataObject = new(MockBehavior.Loose); - mockDataObject - .Setup(o => o.GetImage()) - .CallBase(); - mockDataObject - .Setup(o => o.GetData(DataFormats.Bitmap, true)) - .Returns(result) - .Verifiable(); - mockDataObject.Object.GetImage().Should().BeSameAs(expected); - mockDataObject.Verify(o => o.GetData(DataFormats.Bitmap, true), Times.Once()); - } - [Fact] public void GetText_InvokeDefault_ReturnsEmpty() { From 4eed3c3ec2246ec6610f5b54743c1bae34acc210 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Wed, 8 Apr 2026 15:44:38 +0800 Subject: [PATCH 4/7] Moving case "GetDataObject_GetImage_RoundTripsBitmap" to DataObjectObjectTests.cs and adding a "delegation relationship" test for the `GetImage` method. --- .../System/Windows/Forms/ClipboardTests.cs | 15 ----- .../System/Windows/Forms/DataObjectTests.cs | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs index f4cf6309008..3a4b7f42fbf 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ClipboardTests.cs @@ -356,21 +356,6 @@ public void SetImage_InvokeBitmap_GetReturnsExpected() Clipboard.ContainsImage().Should().BeTrue(); } - [WinFormsFact] - public void GetDataObject_GetImage_RoundTripsBitmap() - { - using Bitmap bitmap = new(10, 10); - bitmap.SetPixel(1, 2, Color.FromArgb(0x01, 0x02, 0x03, 0x04)); - Clipboard.SetImage(bitmap); - - DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType().Subject; - - var result = dataObject.GetImage().Should().BeOfType().Subject; - result.Size.Should().Be(bitmap.Size); - result.GetPixel(1, 2).Should().Be(Color.FromArgb(0xFF, 0xD2, 0xD2, 0xD2)); - Clipboard.ContainsImage().Should().BeTrue(); - } - [WinFormsFact] public void SetImage_InvokeMetafile_GetReturnsExpected() { diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs index b6e47a6f857..604ae3ed7d9 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs @@ -907,6 +907,68 @@ public void GetImage_InvokeWithData_ReturnsExpected(object result, Image expecte dataObject.GetImage().Should().Be(expected); } + [Theory] + [MemberData(nameof(GetImage_TheoryData))] + public void GetImage_Invoke_CallsTryGetData(object result, Image expected) + { + var dataObject = new DataObjectOverridesTryGetDataForImage + { + ImageToReturn = expected, + ResultToReturn = expected is not null, + }; + + var image = dataObject.GetImage(); + + image.Should().BeSameAs(expected); + dataObject.CallCount.Should().Be(1); + dataObject.LastFormat.Should().Be(DataFormats.Bitmap); + dataObject.LastAutoConvert.Should().BeTrue(); + } + + private sealed class DataObjectOverridesTryGetDataForImage : DataObject + { + public int CallCount { get; private set; } + public string? LastFormat { get; private set; } + public bool LastAutoConvert { get; private set; } + public Image? ImageToReturn { get; set; } + public bool ResultToReturn { get; set; } + + protected override bool TryGetDataCore( + string format, + Func? resolver, + bool autoConvert, + out T data) + { + CallCount++; + LastFormat = format; + LastAutoConvert = autoConvert; + + if (typeof(T) == typeof(Image)) + { + data = (T)(object?)ImageToReturn!; + return ResultToReturn; + } + + data = default!; + return false; + } + } + + [WinFormsFact] + public void GetDataObject_GetImage_RoundTripsBitmap() + { + using Bitmap bitmap = new(10, 10); + bitmap.SetPixel(1, 2, Color.FromArgb(0x01, 0x02, 0x03, 0x04)); + Clipboard.SetImage(bitmap); + + DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType().Subject; + + var result = dataObject.GetImage().Should().BeOfType().Subject; + result.Size.Should().Be(bitmap.Size); + result.GetPixel(1, 2).Should().Be(Color.FromArgb(0xFF, 0xD2, 0xD2, 0xD2)); + Clipboard.ContainsImage().Should().BeTrue(); + } + [Fact] public void GetText_InvokeDefault_ReturnsEmpty() { From d4e858f4bbeeea4d3fc9ccd40977c8c40ee4df50 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Thu, 9 Apr 2026 10:17:55 +0800 Subject: [PATCH 5/7] Update test case --- .../System/Windows/Forms/DataObjectTests.cs | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs index 604ae3ed7d9..09e648b596b 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs @@ -378,11 +378,29 @@ internal class DataObjectOverridesTryGetDataCore : DataObject // true is the default value for general purpose APIs (GetData). private readonly bool _autoConvert; - public DataObjectOverridesTryGetDataCore(string format, Func? resolver, bool autoConvert) : base() + private readonly Type _expectedType; + private readonly bool _resultToReturn; + private readonly object? _dataToReturn; + + public DataObjectOverridesTryGetDataCore(string format, Func? resolver, bool autoConvert) + : this(format, resolver, autoConvert, typeof(string), resultToReturn: false, dataToReturn: null) + { + } + + public DataObjectOverridesTryGetDataCore( + string format, + Func? resolver, + bool autoConvert, + Type expectedType, + bool resultToReturn, + object? dataToReturn) : base() { _format = format; _resolver = resolver; _autoConvert = autoConvert; + _expectedType = expectedType; + _resultToReturn = resultToReturn; + _dataToReturn = dataToReturn; } public int Count { get; private set; } @@ -397,9 +415,16 @@ protected override bool TryGetDataCore( format.Should().Be(_format); resolver.Should().BeEquivalentTo(_resolver); autoConvert.Should().Be(_autoConvert); - typeof(T).Should().Be(); + typeof(T).Should().Be(_expectedType); Count++; + + if (typeof(T) == _expectedType) + { + data = (T)_dataToReturn!; + return _resultToReturn; + } + // This is a mock implementation that never returns anything. data = default; return false; @@ -911,47 +936,22 @@ public void GetImage_InvokeWithData_ReturnsExpected(object result, Image expecte [MemberData(nameof(GetImage_TheoryData))] public void GetImage_Invoke_CallsTryGetData(object result, Image expected) { - var dataObject = new DataObjectOverridesTryGetDataForImage - { - ImageToReturn = expected, - ResultToReturn = expected is not null, - }; + bool autoConvert = true; + bool resultToReturn = result is Image; + Image dataToReturn = result as Image; + DataObjectOverridesTryGetDataCore dataObject = new( + DataFormats.Bitmap, + resolver: null, + autoConvert, + expectedType: typeof(Image), + resultToReturn, + dataToReturn); + dataObject.Count.Should().Be(0); - var image = dataObject.GetImage(); + Image image = dataObject.GetImage(); image.Should().BeSameAs(expected); - dataObject.CallCount.Should().Be(1); - dataObject.LastFormat.Should().Be(DataFormats.Bitmap); - dataObject.LastAutoConvert.Should().BeTrue(); - } - - private sealed class DataObjectOverridesTryGetDataForImage : DataObject - { - public int CallCount { get; private set; } - public string? LastFormat { get; private set; } - public bool LastAutoConvert { get; private set; } - public Image? ImageToReturn { get; set; } - public bool ResultToReturn { get; set; } - - protected override bool TryGetDataCore( - string format, - Func? resolver, - bool autoConvert, - out T data) - { - CallCount++; - LastFormat = format; - LastAutoConvert = autoConvert; - - if (typeof(T) == typeof(Image)) - { - data = (T)(object?)ImageToReturn!; - return ResultToReturn; - } - - data = default!; - return false; - } + dataObject.Count.Should().Be(1); } [WinFormsFact] From 789ec449ec84f20e3d6658b44c9a22ac92137a9e Mon Sep 17 00:00:00 2001 From: Leaf Shi <132890443+LeafShi1@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:30:41 +0800 Subject: [PATCH 6/7] Update src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../System/Windows/Forms/DataObjectTests.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs index 09e648b596b..bcd25c30b13 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs @@ -957,16 +957,23 @@ public void GetImage_Invoke_CallsTryGetData(object result, Image expected) [WinFormsFact] public void GetDataObject_GetImage_RoundTripsBitmap() { - using Bitmap bitmap = new(10, 10); - bitmap.SetPixel(1, 2, Color.FromArgb(0x01, 0x02, 0x03, 0x04)); - Clipboard.SetImage(bitmap); + try + { + using Bitmap bitmap = new(10, 10); + bitmap.SetPixel(1, 2, Color.FromArgb(0x01, 0x02, 0x03, 0x04)); + Clipboard.SetImage(bitmap); - DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType().Subject; + DataObject dataObject = Clipboard.GetDataObject().Should().BeOfType().Subject; - var result = dataObject.GetImage().Should().BeOfType().Subject; - result.Size.Should().Be(bitmap.Size); - result.GetPixel(1, 2).Should().Be(Color.FromArgb(0xFF, 0xD2, 0xD2, 0xD2)); - Clipboard.ContainsImage().Should().BeTrue(); + var result = dataObject.GetImage().Should().BeOfType().Subject; + result.Size.Should().Be(bitmap.Size); + result.GetPixel(1, 2).Should().Be(Color.FromArgb(0xFF, 0xD2, 0xD2, 0xD2)); + Clipboard.ContainsImage().Should().BeTrue(); + } + finally + { + Clipboard.Clear(); + } } [Fact] From 630bb4c718ba5c98d571d9d910fd31909e980676 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Fri, 10 Apr 2026 09:20:47 +0800 Subject: [PATCH 7/7] Update test case --- .../System/Windows/Forms/DataObjectTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs index bcd25c30b13..1af799f5561 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataObjectTests.cs @@ -419,9 +419,9 @@ protected override bool TryGetDataCore( Count++; - if (typeof(T) == _expectedType) + if (typeof(T) == _expectedType && _dataToReturn is not null) { - data = (T)_dataToReturn!; + data = (T)_dataToReturn; return _resultToReturn; }