File: DesignBehaviorsTests.cs
Web Access
Project: src\src\System.Windows.Forms\tests\IntegrationTests\UIIntegrationTests\System.Windows.Forms.UI.IntegrationTests.csproj (System.Windows.Forms.UI.IntegrationTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms.Design;
using Xunit.Abstractions;
 
namespace System.Windows.Forms.UITests;
 
public class DesignBehaviorsTests : ControlTestBase
{
    public DesignBehaviorsTests(ITestOutputHelper testOutputHelper)
        : base(testOutputHelper)
    {
    }
 
    [WinFormsFact]
    public async Task DesignBehaviorsTests_can_DragDrop_ToolboxItem()
    {
        // Regression test for https://github.com/dotnet/winforms/issues/6919, and it verifies that we can successfully
        // drag and drop a toolbox item from a toolbox on to a designer surface.
 
        Application.ThreadException += (s, e) =>
        {
            // This will preserve the full stack, which otherwise gets replaced
            throw new InvalidOperationException(e.Exception.Message, e.Exception);
        };
 
        await RunSingleControlTestAsync<TreeView>(async (form, treeView) =>
        {
            // This will indicate when the designer surface has loaded
            SemaphoreSlim loadSignal = new(0);
            // This will indicate when the drag and drop operation has completed
            SemaphoreSlim dndSignal = new(0);
 
            form.Size = new(600, 550);
            treeView.Dock = DockStyle.Left;
            treeView.Width = 100;
 
            // Create a toolbox item and assign it to the node
            // When we drag the node to the design surface, this will go through the toolbox service API
            ToolboxItem toolboxItem = new()
            {
                TypeName = typeof(SampleControl).FullName,
                DisplayName = nameof(SampleControl),
                AssemblyName = typeof(SampleControl).Assembly.GetName()
            };
 
            TreeNode? node = treeView.Nodes.Add("root");
            node.Tag = toolboxItem;
 
            // Create the designer surface and all required plumbing
            SampleDesignerLoader designerLoader = new();
            ServiceContainer serviceContainer = new();
            DesignSurface designSurface = new(serviceContainer);
 
            var designerHost = (IDesignerHost)designSurface.GetService(typeof(IDesignerHost))!;
            serviceContainer.RemoveService(typeof(IToolboxService), false);
            serviceContainer.AddService(typeof(IToolboxService), new SampleToolboxService(designerHost));
 
            if (designerHost.GetService(typeof(IComponentChangeService)) is IComponentChangeService componentChangeService)
            {
                componentChangeService.ComponentAdded += (s, e) =>
                {
                    if (e.Component is Form form)
                    {
                        form.Text = @"RootForm";
                        form.Size = new Size(400, 400);
                    }
                    else
                    {
                        ((Control)e.Component!).Size = new Size(50, 50);
                    }
                };
            }
 
            designSurface.Loaded += (s, e) =>
            {
                Control rootView = (Control)designSurface.View;
                rootView.Dock = DockStyle.Fill;
                form.Controls.Add(rootView);
                rootView.BringToFront();
 
                // Indicate that we have finished loading the designer surface
                loadSignal.Release();
            };
 
            treeView.ItemDrag += treeView_ItemDrag;
 
            designSurface.BeginLoad(designerLoader);
 
            // Wait for the designer surface to load
            await loadSignal.WaitAsync(millisecondsTimeout: 3_000);
 
            // Initiate the drag and drop and wait for the signal that it finished
            var dndStartCoordinates = treeView.PointToScreen(node.Bounds.Location);
            await InitiateDrangDropAsync(form, dndStartCoordinates, (Control)designSurface.View);
            await dndSignal.WaitAsync(millisecondsTimeout: 3_000);
 
            return;
 
            void treeView_ItemDrag(object? sender, ItemDragEventArgs e)
            {
                if (designerHost?.GetService(typeof(IToolboxService)) is not IToolboxService toolboxService ||
                    e.Item is not TreeNode node ||
                    node.Tag is not ToolboxItem toolboxItem)
                {
                    return;
                }
 
                var dataObject = toolboxService.SerializeToolboxItem(toolboxItem) as DataObject;
 
                var effects = node.TreeView!.DoDragDrop(dataObject!, DragDropEffects.Copy);
            }
 
            async Task InitiateDrangDropAsync(Form form, Point startCoordinates, Control rootView)
            {
                var virtualPointStart = ToVirtualPoint(startCoordinates);
                startCoordinates.Offset(110, 50);
                var virtualPointEnd = ToVirtualPoint(startCoordinates);
 
                await InputSimulator.SendAsync(
                    form,
                    inputSimulator => inputSimulator.Mouse.MoveMouseTo(virtualPointStart.X + 6, virtualPointStart.Y + 6)
                                                          .LeftButtonDown()
                                                          .MoveMouseTo(virtualPointEnd.X, virtualPointEnd.Y)
                                                          .LeftButtonUp());
 
                dndSignal.Release();
            }
        });
    }
 
#pragma warning disable CS8603 // Possible null reference return.
    internal class SampleToolboxService : IToolboxService
    {
        private readonly IDesignerHost _designerHost;
 
        public SampleToolboxService(IDesignerHost designerHost)
        {
            _designerHost = designerHost;
        }
 
        public void AddCreator(ToolboxItemCreatorCallback creator, string format)
        {
            // this method intentionally keep empty
        }
 
        public void AddCreator(ToolboxItemCreatorCallback creator, string format, IDesignerHost host)
        {
            // this method intentionally keep empty
        }
 
        public void AddLinkedToolboxItem(ToolboxItem toolboxItem, IDesignerHost host)
        {
            // this method intentionally keep empty
        }
 
        public void AddLinkedToolboxItem(ToolboxItem toolboxItem, string category, IDesignerHost host)
        {
            // this method intentionally keep empty
        }
 
        public void AddToolboxItem(ToolboxItem toolboxItem)
        {
            // this method intentionally keep empty
        }
 
        public void AddToolboxItem(ToolboxItem toolboxItem, string category)
        {
            // this method intentionally keep empty
        }
 
        public ToolboxItem DeserializeToolboxItem(object serializedObject)
        {
            return DeserializeToolboxItem(serializedObject, _designerHost);
        }
 
        public ToolboxItem DeserializeToolboxItem(object serializedObject, IDesignerHost? host)
        {
            ToolboxItem? item = ((DataObject)serializedObject)?.GetData(typeof(ToolboxItem)) as ToolboxItem;
            return item!;
        }
 
        public ToolboxItem GetSelectedToolboxItem()
        {
            return GetSelectedToolboxItem(null!);
        }
 
        public ToolboxItem GetSelectedToolboxItem(IDesignerHost host)
        {
            return null;
        }
 
        public ToolboxItemCollection GetToolboxItems()
        {
            return null;
        }
 
        public ToolboxItemCollection GetToolboxItems(IDesignerHost host)
        {
            return null;
        }
 
        public ToolboxItemCollection GetToolboxItems(string category)
        {
            return null;
        }
 
        public ToolboxItemCollection GetToolboxItems(string category, IDesignerHost host)
        {
            return null;
        }
 
        public bool IsSupported(object serializedObject, IDesignerHost host)
        {
            return true;
        }
 
        public bool IsSupported(object serializedObject, ICollection filterAttributes)
        {
            return true;
        }
 
        public bool IsToolboxItem(object serializedObject)
        {
            return true;
        }
 
        public bool IsToolboxItem(object serializedObject, IDesignerHost host)
        {
            return true;
        }
 
        public void Refresh()
        {
            throw new NotImplementedException();
        }
 
        public void RemoveCreator(string format)
        {
            // this method intentionally keep empty
        }
 
        public void RemoveCreator(string format, IDesignerHost host)
        {
            // this method intentionally keep empty
        }
 
        public void RemoveToolboxItem(ToolboxItem toolboxItem)
        {
            // this method intentionally keep empty
        }
 
        public void RemoveToolboxItem(ToolboxItem toolboxItem, string category)
        {
            // this method intentionally keep empty
        }
 
        public void SelectedToolboxItemUsed()
        {
            // this method intentionally keep empty
        }
 
        public object SerializeToolboxItem(ToolboxItem toolboxItem)
        {
            return new DataObject(toolboxItem);
        }
 
        public bool SetCursor()
        {
            return false;
        }
 
        public void SetSelectedToolboxItem(ToolboxItem toolboxItem)
        {
            // this method intentionally keep empty
        }
 
        public CategoryNameCollection CategoryNames => null;
 
        public string SelectedCategory
        {
            get => null;
            set { }
        }
    }
 
    internal class SampleDesignerLoader : BasicDesignerLoader
    {
        protected override void PerformFlush(IDesignerSerializationManager serializationManager)
        {
            throw new NotImplementedException();
        }
 
        protected override void PerformLoad(IDesignerSerializationManager serializationManager)
        {
            if (LoaderHost is null)
                return;
 
            ArrayList errors = [];
 
            LoaderHost.CreateComponent(typeof(Form));
 
            LoaderHost.EndLoad(nameof(Form), true, errors);
        }
    }
 
    [Designer(typeof(SampleControlDesigner))]
    public class SampleControl : Control
    {
    }
 
    internal class SampleControlDesigner : ControlDesigner
    {
        protected override void OnPaintAdornments(PaintEventArgs pe)
        {
            base.OnPaintAdornments(pe);
 
            pe.Graphics.DrawString($"Design time \n{Control.Site?.Name} !", Control.Font, SystemBrushes.WindowText, new PointF(12, 12));
        }
    }
}
#pragma warning restore CS8603