目录
第二部分——MyButton.ClientServerProtocol
第三部分——MyButton.Designer.Server
第四部分——MyButton.Designer.Client
介绍
这是在.NET6(Core)中创建自定义按钮的简短演示。将添加一个属性,该属性将打开一个空窗体,并在property字段中写入string “test”。
背景
正如Klaus Löffelmann所说,在.NET Core中引入了新的WinForms设计器。我用他的例子写这篇文章,因为我找不到任何其他例子,而且很可能在未来会被改变。这是我的简化示例,主要是从Klaus Löffelmann的例子中复制/粘贴的。
使用代码
此示例是使用Visual Studio 2022制作的,需要四个类库项目和一个Windows控件库:
- MyButtonControl——控制实现,如属性、按钮继承
- MyButton.ClientServerProtocol——Windows Control Library,客户端和服务器之间的连接,在.NET 4.7和6中
- MyButton.Designer.Server——智能标记实现
- MyButton.Designer.Client——编辑器的实现,属性的行为,它仍然在.NET 4.7中
- MyButton.Package——创建的控件的
安装 NuGet包 Microsoft.WinForms.Designer.SDK 用于项目MyButton.ClientServerProtocol、MyButton.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
- 创建新的.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