在.NET 6中编写自定义控件

目录

介绍

背景

使用代码

第一部分——MyButtonControl

MyButton.cs

MyType.cs

MyTypeConverter.cs

第二部分——MyButton.ClientServerProtocol

AllowNullAttribute.cs

EndpointNames.cs

ViewModelNames.cs

MyButtonViewModelRequest.cs

MyButtonViewModelResponse.cs

MyButtonViewModelEndpoint.cs

第三部分——MyButton.Designer.Server

MyButtonDesigner.cs

MyButtonViewModel.cs

MyButton.ActionList.cs

MyButtonViewModelHandler.cs

MyButtonViewModel.Factory.cs

TypeRoutingProvider.cs

第四部分——MyButton.Designer.Client

MyButtonViewModel.cs

MyButtonEditor.cs

TypeRoutingProvider.cs

第五部分——MyButton.Package


介绍

这是在.NET6Core)中创建自定义按钮的简短演示。将添加一个属性,该属性将打开一个空窗体,并在property字段中写入stringtest

背景

正如Klaus Löffelmann所说,在.NET Core中引入了新的WinForms设计器。我用他的例子写这篇文章,因为我找不到任何其他例子,而且很可能在未来会被改变。这是我的简化示例,主要是从Klaus Löffelmann的例子中复制/粘贴的。

使用代码

此示例是使用Visual Studio 2022制作的,需要四个类库项目和一个Windows控件库:

  1. MyButtonControl——控制实现,如属性、按钮继承
  2. MyButton.ClientServerProtocol——Windows Control Library,客户端和服务器之间的连接,在.NET 4.7和6中
  3. MyButton.Designer.Server——智能标记实现
  4. MyButton.Designer.Client——编辑器的实现,属性的行为,它仍然在.NET 4.7中
  5. MyButton.Package——创建的控件的

安装 NuGet Microsoft.WinForms.Designer.SDK 用于项目MyButton.ClientServerProtocolMyButton.Designer.Server  MyButton.Designer.Client

Install-Package Microsoft.WinForms.Designer.SDK 
       -Version 1.1.0-prerelease-preview3.22076.5 

调试附加到进程 DesignToolsServer.exe。有时,需要清除NuGet缓存,特别是当MyButton.Designer.Client更改时,如果您只是删除文件夹C:\Users\userName\.nuget\packages\mybutton.package,则可以专门为这个项目完成此操作。

若要测试控件,请先在NuGet中添加包源,如此所述。然后,首先从下拉列表中选择包源来安装NuGet

第一部分——MyButtonControl

创建新的.NET 6类库项目。将.csproj更改为如下所示:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>net6.0-windows</TargetFramework>
		<UseWindowsForms>true</UseWindowsForms>
	</PropertyGroup>
</Project>

2、添加三个文件:

  • MyButton.cs
  • MyType.cs
  • MyTypeConverter.cs

MyButton.cs

using System.ComponentModel;
using System.Windows.Forms;

namespace MyButtonControl
{
    [Designer("MyButtonDesigner"),
     ComplexBindingProperties("DataSource")]
    public class MyButton : Button
    {
        public MyType MyProperty { get; set; }
    }
}

MyType.cs

using System.ComponentModel;
using System.Drawing.Design;

namespace MyButtonControl
{
    [TypeConverter(typeof(MyTypeConverter))]
    [Editor("MyButtonEditor", typeof(UITypeEditor))]
    public class MyType
    {
        public string AnotherMyProperty { get; set; }

        public MyType(string value)
        {
            AnotherMyProperty = value;
        }
    }
}

MyTypeConverter.cs

using System;
using System.ComponentModel;
using System.Globalization;

namespace MyButtonControl
{
    internal class MyTypeConverter : TypeConverter
    {
        public override bool CanConvertTo
               (ITypeDescriptorContext context, Type destinationType)
        {
            return true;
        }

        public override bool CanConvertFrom
               (ITypeDescriptorContext context, Type sourceType)
        {
            return true;
        }

        public override object ConvertFrom
               (ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is null)
            {
                return string.Empty;
            }
            return new MyType(value.ToString());
        }

        public override object ConvertTo(ITypeDescriptorContext context, 
                        CultureInfo culture, object value, Type destinationType)
        {
            return ((MyType)value)?.AnotherMyProperty;
        }
    }
}

第二部分——MyButton.ClientServerProtocol

1、添加新的Windows控件库,删除UserControl1和更改.CSPROJ 如下:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFrameworks>net6.0-windows;net472</TargetFrameworks>
		<UseWindowsForms>true</UseWindowsForms>
		<LangVersion>9.0</LangVersion>
		<Nullable>enable</Nullable>
	</PropertyGroup>
</Project>

Visual Studio中保存并重新加载项目。

2、安装NuGet包 Microsoft.WinForms.Designer.SDK

Install-Package Microsoft.WinForms.Designer.SDK 
       -Version 1.1.0-prerelease-preview3.22076.5

3、添加六个文件:

  • AllowNullAttribute.cs
  • EndpointNames.cs
  • ViewModelNames.cs
  • MyButtonViewModelRequest.cs
  • MyBuutonViewModelResopnse.cs
  • MyButtonViewModelEndpoint.cs

AllowNullAttribute.cs

#if NETFRAMEWORK
namespace System.Diagnostics.CodeAnalysis
{
    [System.AttributeUsage(System.AttributeTargets.Field | 
     System.AttributeTargets.Parameter | 
     System.AttributeTargets.Property, Inherited = false)]
    public class AllowNullAttribute : Attribute
    { }
}
#endif 

EndpointNames.cs

namespace MyButton.ClientServerProtocol
{
    public static class EndpointNames
    {
        public const string MyButtonViewModel = nameof(MyButtonViewModel);
    }
}

ViewModelNames.cs

namespace MyButton.ClientServerProtocol
{
    public static class ViewModelNames
    {
        public const string MyButtonViewModel = nameof(MyButtonViewModel);
    }
}

MyButtonViewModelRequest.cs

using Microsoft.DotNet.DesignTools.Protocol.DataPipe;
using Microsoft.DotNet.DesignTools.Protocol;
using Microsoft.DotNet.DesignTools.Protocol.Endpoints;
using System;

namespace MyButton.ClientServerProtocol
{
    public class MyButtonViewModelRequest : Request
    {
        public SessionId SessionId { get; private set; }
        public object? MyPropertyEditorProxy { get; private set; }

        public MyButtonViewModelRequest() { }

        public MyButtonViewModelRequest(SessionId sessionId, object? myProxy)
        {
            SessionId = sessionId.IsNull ? 
            throw new ArgumentNullException(nameof(sessionId)) : sessionId;
            MyPropertyEditorProxy = myProxy;
        }

        public MyButtonViewModelRequest(IDataPipeReader reader) : base(reader) { }

        protected override void ReadProperties(IDataPipeReader reader)
        {
            SessionId = reader.ReadSessionId(nameof(SessionId));
            MyPropertyEditorProxy = reader.ReadObject(nameof(MyPropertyEditorProxy));
        }

        protected override void WriteProperties(IDataPipeWriter writer)
        {
            writer.Write(nameof(SessionId), SessionId);
            writer.WriteObject(nameof(MyPropertyEditorProxy), MyPropertyEditorProxy);
        }
    }
}

MyButtonViewModelResponse.cs

using Microsoft.DotNet.DesignTools.Protocol.DataPipe;
using Microsoft.DotNet.DesignTools.Protocol.Endpoints;
using System;
using System.Diagnostics.CodeAnalysis;

namespace MyButton.ClientServerProtocol
{
    public class MyButtonViewModelResponse : Response
    {
        [AllowNull]
        public object ViewModel { get; private set; }

        [AllowNull]
        public object MyProperty { get; private set; }

        public MyButtonViewModelResponse() { }

        public MyButtonViewModelResponse(object viewModel, object myProperty)
        {
            ViewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
            MyProperty = myProperty;
        }

        public MyButtonViewModelResponse(object viewModel)
        {
            ViewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
        }

        public MyButtonViewModelResponse(IDataPipeReader reader) : base(reader) { }

        protected override void ReadProperties(IDataPipeReader reader)
        {
            ViewModel = reader.ReadObject(nameof(ViewModel));
        }

        protected override void WriteProperties(IDataPipeWriter writer)
        {
            writer.WriteObject(nameof(ViewModel), ViewModel);
            writer.WriteObject(nameof(MyProperty), MyProperty);
        }
    }
}

MyButtonViewModelEndpoint.cs

using System.Composition;
using Microsoft.DotNet.DesignTools.Protocol.DataPipe;
using Microsoft.DotNet.DesignTools.Protocol.Endpoints;

namespace MyButton.ClientServerProtocol
{
    [Shared]
    [ExportEndpoint]
    public class MyButtonViewModelEndpoint : 
           Endpoint<MyButtonViewModelRequest, MyButtonViewModelResponse>
    {
        public override string Name => EndpointNames.MyButtonViewModel;

        protected override MyButtonViewModelRequest 
                           CreateRequest(IDataPipeReader reader)
            => new(reader);

        protected override MyButtonViewModelResponse 
                           CreateResponse(IDataPipeReader reader)
            => new(reader);
    }
}

第三部分——MyButton.Designer.Server

1、创建新的.NET 6类库项目。将.csproj更改为如下所示:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>net6.0-windows</TargetFramework>
		<UseWindowsForms>true</UseWindowsForms>
	</PropertyGroup>
</Project>

2、安装NuGet包 Microsoft.WinForms.Designer.SDK

Install-Package Microsoft.WinForms.Designer.SDK 
       -Version 1.1.0-prerelease-preview3.22076.5

3、添加六个文件:

  • MyButtonDesigner.cs
  • MyButtonViewModel.cs
  • MyButton.ActionList.cs
  • MyButtonViewModelHandler.cs
  • MyButtonViewModel.Factory.cs
  • TypeRoutingProvider.cs

MyButtonDesigner.cs

using Microsoft.DotNet.DesignTools.Designers;
using Microsoft.DotNet.DesignTools.Designers.Actions;

namespace MyButton.Designer.Server
{
    internal partial class MyButtonDesigner : ControlDesigner
    {
        public override DesignerActionListCollection ActionLists
            => new()
            {
                new ActionList(this)
            };
    }
}

MyButtonViewModel.cs

using Microsoft.DotNet.DesignTools.ViewModels;
using System;
using System.Diagnostics.CodeAnalysis;
using MyButton.ClientServerProtocol;
using MyButtonControl;

namespace MyButton.Designer.Server
{
    internal partial class MyButtonViewModel : ViewModel
    {
        public MyButtonViewModel(IServiceProvider provider) : base(provider)
        {
        }

        public MyButtonViewModelResponse Initialize(object myProperty)
        {
            MyProperty = new MyType(myProperty.ToString());
            return new MyButtonViewModelResponse(this, MyProperty);
        }

        [AllowNull]
        public MyType MyProperty { get; set; }
    }
}

MyButton.ActionList.cs

using Microsoft.DotNet.DesignTools.Designers.Actions;
using System.ComponentModel;
using MyButtonControl;

namespace MyButton.Designer.Server
{
    internal partial class MyButtonDesigner
    {
        private class ActionList : DesignerActionList
        {
            private const string Behavior = nameof(Behavior);
            private const string Data = nameof(Data);

            public ActionList(MyButtonDesigner designer) : base(designer.Component)
            {
            }

            public MyType MyProperty
            {
                get => ((MyButtonControl.MyButton)Component!).MyProperty;

                set =>
                    TypeDescriptor.GetProperties(Component!)[nameof(MyProperty)]!
                        .SetValue(Component, value);
            }

            public override DesignerActionItemCollection GetSortedActionItems()
            {
                DesignerActionItemCollection actionItems = new()
                {
                    new DesignerActionHeaderItem(Behavior),
                    new DesignerActionHeaderItem(Data),
                    new DesignerActionPropertyItem(
                        nameof(MyProperty),
                        "Empty form",
                        Behavior,
                        "Display empty form.")
                };

                return actionItems;
            }
        }
    }
}

MyButtonViewModelHandler.cs

using Microsoft.DotNet.DesignTools.Protocol.Endpoints;
using MyButton.ClientServerProtocol;

namespace MyButton.Designer.Server
{
    [ExportRequestHandler(EndpointNames.MyButtonViewModel)]
    public class MyButtonViewModelHandler : 
           RequestHandler<MyButtonViewModelRequest, MyButtonViewModelResponse>
    {
        public override MyButtonViewModelResponse HandleRequest
                        (MyButtonViewModelRequest request)
        {
            var designerHost = GetDesignerHost(request.SessionId);

            var viewModel = CreateViewModel<MyButtonViewModel>(designerHost);

            return viewModel.Initialize(request.MyPropertyEditorProxy!);
        }
    }
}

MyButtonViewModel.Factory.cs

using Microsoft.DotNet.DesignTools.ViewModels;
using System;
using MyButton.ClientServerProtocol;

namespace MyButton.Designer.Server
{
    internal partial class MyButtonViewModel
    {
        [ExportViewModelFactory(ViewModelNames.MyButtonViewModel)]
        private class Factory : ViewModelFactory<MyButtonViewModel>
        {
            protected override MyButtonViewModel CreateViewModel
                                       (IServiceProvider provider)
                => new(provider);
        }
    }
}

TypeRoutingProvider.cs

using Microsoft.DotNet.DesignTools.TypeRouting;
using System.Collections.Generic;

namespace MyButton.Designer.Server
{
    [ExportTypeRoutingDefinitionProvider]
    internal class TypeRoutingProvider : TypeRoutingDefinitionProvider
    {
        public override IEnumerable<TypeRoutingDefinition> GetDefinitions()
            => new[]
            {
                new TypeRoutingDefinition(
                    TypeRoutingKinds.Designer,
                    nameof(MyButtonDesigner),
                    typeof(MyButtonDesigner))
            };
    }
}

第四部分——MyButton.Designer.Client

1、创建新的.NET 6类库项目。将.csproj更改为如下所示:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
	<PropertyGroup>
		<TargetFramework>net472</TargetFramework>
		<UseWindowsForms>true</UseWindowsForms>
		<LangVersion>9.0</LangVersion>
	</PropertyGroup>
</Project>

2、安装NuGet包 Microsoft.WinForms.Designer.SDK

Install-Package Microsoft.WinForms.Designer.SDK 
       -Version 1.1.0-prerelease-preview3.22076.5 

3、添加三个文件:

  • MyButtonViewModel.cs
  • MyButtonEditor.cs
  • TypeRoutingProvider.cs

MyButtonViewModel.cs

using System;
using Microsoft.DotNet.DesignTools.Client.Proxies;
using Microsoft.DotNet.DesignTools.Client;
using Microsoft.DotNet.DesignTools.Client.Views;
using MyButton.ClientServerProtocol;

namespace MyButton.Designer.Client
{
    internal partial class MyButtonViewModel : ViewModelClient
    {
        [ExportViewModelClientFactory(ViewModelNames.MyButtonViewModel)]
        private class Factory : ViewModelClientFactory<MyButtonViewModel>
        {
            protected override MyButtonViewModel CreateViewModelClient
                               (ObjectProxy? viewModel)
                => new(viewModel);
        }

        private MyButtonViewModel(ObjectProxy? viewModel)
            : base(viewModel)
        {
            if (viewModel is null)
            {
                throw new NullReferenceException(nameof(viewModel));
            }
        }

        public static MyButtonViewModel Create(
            IServiceProvider provider,
            object? templateAssignmentProxy)
        {
            var session = provider.GetRequiredService<DesignerSession>();
            var client = provider.GetRequiredService<IDesignToolsClient>();

            var createViewModelEndpointSender =
                client.Protocol.GetEndpoint
                       <MyButtonViewModelEndpoint>().GetSender(client);

            var response =
                createViewModelEndpointSender.SendRequest
                          (new MyButtonViewModelRequest(session.Id,
                    templateAssignmentProxy));
            var viewModel = (ObjectProxy)response.ViewModel!;

            var clientViewModel = provider.CreateViewModelClient<MyButtonViewModel>
                                                                       (viewModel);

            return clientViewModel;
        }

        public object? MyProperty
        {
            get => ViewModelProxy?.GetPropertyValue(nameof(MyProperty));
            set => ViewModelProxy?.SetPropertyValue(nameof(MyProperty), value);
        }
    }
}

MyButtonEditor.cs

using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MyButton.Designer.Client
{
    public class MyButtonEditor : UITypeEditor
    {

        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
            => UITypeEditorEditStyle.Modal;

        public override object? EditValue(
            ITypeDescriptorContext context,
            IServiceProvider provider,
            object? value)
        {
            if (provider is null)
            {
                return value;
            }


            Form myTestForm;
            myTestForm = new Form();
            var editorService = 
                provider.GetRequiredService<IWindowsFormsEditorService>();
            editorService.ShowDialog(myTestForm);

            MyButtonViewModel viewModelClient = 
                              MyButtonViewModel.Create(provider, "test");
            return viewModelClient.MyProperty;
        }
    }
}

TypeRoutingProvider.cs

using Microsoft.DotNet.DesignTools.Client.TypeRouting;
using System.Collections.Generic;

namespace MyButton.Designer.Client
{
    [ExportTypeRoutingDefinitionProvider]
    internal class TypeRoutingProvider : TypeRoutingDefinitionProvider
    {
        public override IEnumerable<TypeRoutingDefinition> GetDefinitions()
        {
            return new[]
            {
                new TypeRoutingDefinition(
                    TypeRoutingKinds.Editor,
                    nameof(MyButtonEditor),
                    typeof(MyButtonEditor)
                )
            };
        }
    }
}

第五部分——MyButton.Package

  1. 创建新的.NET 6类库项目,删除 Class1.cs。将.csproj更改为如下所示:

<project sdk="Microsoft.NET.Sdk">
	<propertygroup>
		<targetframework>net6.0</targetframework>
		<includebuildoutput>false</includebuildoutput>
		<producereferenceassembly>false</producereferenceassembly>
		<generatepackageonbuild>true</generatepackageonbuild>
		<targetsfortfmspecificcontentinpackage>$
        (TargetsForTfmSpecificContentInPackage);_GetFilesToPackage
        </targetsfortfmspecificcontentinpackage>
		<runpostbuildevent>Always</runpostbuildevent>
	</propertygroup>
	<target name="_GetFilesToPackage">
		<itemgroup>
			<_File Include="$(SolutionDir)\MyButtonControl\bin\
                   $(Configuration)\net6.0-windows\MyButtonControl.dll"/>
			<_File Include="$(SolutionDir)\MyButton.Designer.Client\bin\
                   $(Configuration)\net472\MyButton.Designer.Client.dll"
			       TargetDir="Design/WinForms"/>
			<_File Include="$(SolutionDir)\MyButton.Designer.Server\bin\
                   $(Configuration)\net6.0-windows\MyButton.Designer.Server.dll"
			       TargetDir="Design/WinForms/Server"/>

			<_File Include="$(SolutionDir)\MyButton.ClientServerProtocol\bin\
             $(Configuration)\net472\MyButton.ClientServerProtocol.dll" 
             TargetDir="Design/WinForms" />
			<_File Include="$(SolutionDir)\MyButton.ClientServerProtocol\bin\
             $(Configuration)\net6.0-windows\MyButton.ClientServerProtocol.dll" 
             TargetDir="Design/WinForms/Server" />
		</itemgroup>
		<itemgroup>
			<tfmspecificpackagefile include="@(_File)" 
             packagepath="$(BuildOutputTargetFolder)/$(TargetFramework)/
             %(_File.TargetDir)">
		</tfmspecificpackagefile></itemgroup>
	</target>
</project>

https://www.codeproject.com/Articles/5341387/Writing-Custom-Control-in-NET-6

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值