- Details
- Written by: Stanko Milosev
- Category: Windows Forms
- Hits: 790
Introduction
This is as much as possible short demonstration of creating custom button in .NET6 (core). One property will be added which will open empty form, and write string "test" in the property field.
Background
As Klaus Löffelmann stated, in .NET core new WinForms designer was introduced. This article I wrote using his example, since I could not find any other example, and most probably in the future will be changed. This is my simplified example, mostly copy/pasted from Klaus Löffelmann's example.
Using the code
This example was made using Visual Studio 2022 and there will be 4 class library projects and one Windows Control Library needed:
- MyButtonControl - here will be control implementation like properties, button inheritance
- MyButton.ClientServerProtocol - Windows Control Library, connection between client and server, in both .NET 4.7 and 6.
- MyButton.Designer.Server - here will be smart tag implementation
- MyButton.Designer.Client - here is the implementation of editor, behaviour of the property, and it is still in .NET 4.7
- MyButton.Package - here will be the package of the control created.
Install NuGet package Microsoft.WinForms.Designer.SDK for projects MyButton.ClientServerProtocol, MyButton.Designer.Server and MyButton.Designer.Client:
Install-Package Microsoft.WinForms.Designer.SDK -Version 1.1.0-prerelease-preview3.22076.5
To debug attach to the process DesignToolsServer.exe. Sometimes there is need to clear NuGet cache, specially when there is change in the MyButton.Designer.Client, it can be done specifically for this one project if you just delete the folder C:\Users\userName\.nuget\packages\mybutton.package
To test the control first add package source in NuGet like here explained. Then install NuGet by first choosing package source from drop down list.
First part - MyButtonControl
- Create new .NET 6 class library project. Change .csproj to look like:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-windows</TargetFramework> <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> </Project>
- Add three files:
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;
}
}
}
Second part - MyButton.ClientServerProtocol
- Add new Windows Control Library, delete UserControl1, and change .CSPROJ like:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>net6.0-windows;net472</TargetFrameworks> <UseWindowsForms>true</UseWindowsForms> <LangVersion>9.0</LangVersion> <Nullable>enable</Nullable> </PropertyGroup> </Project>
Save and reload the project in Visual Studio - Install NuGet package Microsoft.WinForms.Designer.SDK:
Install-Package Microsoft.WinForms.Designer.SDK -Version 1.1.0-prerelease-preview3.22076.5
- Add six files:
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);
}
}
Third part - MyButton.Designer.Server
- Create new .NET 6 class library project. Change .csproj to look like:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-windows</TargetFramework> <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> </Project>
- Install NuGet package Microsoft.WinForms.Designer.SDK:
Install-Package Microsoft.WinForms.Designer.SDK -Version 1.1.0-prerelease-preview3.22076.5
- Add six files:
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))
};
}
}
Fourth part - MyButton.Designer.Client
- Create new .NET 6 class library project. Change .csproj to look like:
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <PropertyGroup> <TargetFramework>net472</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <LangVersion>9.0</LangVersion> </PropertyGroup> </Project>
- Install NuGet package Microsoft.WinForms.Designer.SDK:
Install-Package Microsoft.WinForms.Designer.SDK -Version 1.1.0-prerelease-preview3.22076.5
- Add three files:
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)
)
};
}
}
}
Fifth part - MyButton.Package
- Create new .NET 6 class library project, delete Class1.cs. Change .csproj to look like:
<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)"/> </ItemGroup> </Target> </Project>
- Details
- Written by: Stanko Milosev
- Category: Windows Forms
- Hits: 659
First part:
1. Start new class library for .NET Framework (not core)
2. Name it MyControls.
3. Rename Class1 to MyButton.
4. Reference System.Windows.Forms in my case it was like: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Windows.Forms.dll
5. MyButton inherit from Button.
6. Add property like:
public string MyProperty { get; set; }
7. Create new App Windows forms for .NET Framework (not core)
8. Add new tab in toolbox
9. Drag and drop MyControls.dll --- Second part - smart tag: 10. Add attribute [Designer(typeof(MyButtonDesigner))]:
using System.ComponentModel; using System.Windows.Forms; namespace MyControls { [Designer(typeof(MyButtonDesigner))] public class MyButton: Button { public string MyProperty { get; set; } } }11. Add class MyButtonDesigner
12. Add reference to System.Design C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Design.dll in usings add System.Windows.Forms.
13. Add class MyButtonDesigner and inherit from ControlDesigner:
using System.ComponentModel.Design; using System.Windows.Forms.Design; namespace MyControls { public class MyButtonDesigner: ControlDesigner { private DesignerActionListCollection _myButtonActionLists; public override DesignerActionListCollection ActionLists { get { if (_myButtonActionLists is null) { _myButtonActionLists = new DesignerActionListCollection { new MyButtonActionList(Component) }; } return _myButtonActionLists; } } } }14. Add class MyButtonActionList, inherit from DesignerActionList:
using System; using System.ComponentModel; using System.ComponentModel.Design; using System.Reflection; namespace MyControls { public class MyButtonActionList: DesignerActionList { private readonly MyButton _myButton; private readonly DesignerActionUIService _designerActionUiSvc = null; public MyButtonActionList(IComponent component) : base(component) { _myButton = component as MyButton; _designerActionUiSvc = GetService(typeof(DesignerActionUIService)) as DesignerActionUIService; } public string MyProperty { get => _myButton.MyProperty; set { GetPropertyByName("MyProperty").SetValue(_myButton, value); _designerActionUiSvc.Refresh(Component); } } public override DesignerActionItemCollection GetSortedActionItems() { DesignerActionItemCollection items = new DesignerActionItemCollection(); items.Add(new DesignerActionHeaderItem("My Smart tag")); items.Add(new DesignerActionPropertyItem("MyProperty", "MyProperty")); return items; } private PropertyDescriptor GetPropertyByName(string propName) { var prop = TypeDescriptor.GetProperties(_myButton)[propName]; if (null == prop) throw new ArgumentException( "Matching MyProperty property not found!", propName); return prop; } } }Example until now download from here. --- Third part - UIEditor: 15. In MyButton change MyProperty to:
public MyPropertyType MyProperty { get; set; }16. Add new class MyPropertyType, decorate with Editor and TypeConverter:
using System.ComponentModel; using System.Drawing.Design; namespace MyControls { [Editor(typeof(MyButtonEditor), typeof(UITypeEditor))] [TypeConverter(typeof(MyPropertyTypeConverter))] public class MyPropertyType { public string AnotherMyProperty { get; set; } public MyPropertyType(string test) { AnotherMyProperty = test; } } }17. Add one more class MyPropertyTypeConverter:
using System; using System.ComponentModel; using System.Globalization; namespace MyControls { public class MyPropertyTypeConverter : 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 MyPropertyType(value.ToString()); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { return ((MyPropertyType)value)?.AnotherMyProperty; } } }18. Again add class MyButtonEditor:
using System; using System.ComponentModel; using System.Drawing.Design; using System.Windows.Forms; using System.Windows.Forms.Design; namespace MyControls { public class MyButtonEditor: UITypeEditor { public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal; public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) { IWindowsFormsEditorService svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService; Form myForm = new Form(); svc?.ShowDialog(myForm); return new MyPropertyType("test"); } } }Example download from here.
- Details
- Written by: Stanko Milosev
- Category: Windows Forms
- Hits: 1013
DataSet ds = new DataSet(); ds.ReadXml(@"test.xml"); foreach (DataTable dataTable in ds.Tables) { DataGridView dataGridView = new DataGridView(); dataGridView.Dock = DockStyle.Top; dataGridView.DataSource = dataTable; dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells; dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; dataGridView.AllowUserToOrderColumns = true; dataGridView.AllowUserToResizeColumns = true; Controls.Add(dataGridView); Label label = new Label(); label.Text = dataTable.TableName; label.Dock = DockStyle.Top; Controls.Add(label); }Example download from here without XML attached.
- Details
- Written by: Stanko Milosev
- Category: Windows Forms
- Hits: 964
DataSet ds = new DataSet(); ds.ReadXml(@"test.xml"); foreach (DataTable dataTable in ds.Tables) { DataGridView dataGridView = new DataGridView(); dataGridView.Dock = DockStyle.Top; dataGridView.DataSource = dataTable; dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells; dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; dataGridView.AllowUserToOrderColumns = true; dataGridView.AllowUserToResizeColumns = true; Controls.Add(dataGridView); Label label = new Label(); label.Text = dataTable.TableName; label.Dock = DockStyle.Top; Controls.Add(label); }