microsoft / winforms-designer-extensibility

MIT License
56 stars 13 forks source link

Custom Type Editor Templates #1

Closed KlausLoeffelmann closed 1 year ago

KlausLoeffelmann commented 1 year ago

Note to reviewers:

==============================================================================

Type Editors with the modern WinForms Designer

The way type editors in the modern WinForms Designer changed considerably. Since the modern WinForms Designer runs out of the Visual Studio process for showing most of the design-time experience, type editors for complex types need to be migrated. This template package for C# and Visual Basic .NET type editors helps with that process.

Building the Solution Template Package

A type editor for the modern out-of-process WinForms Designer is built from several projects. The section Introduction to the Template Solution gives all the necessary background information in detail. What’s important for building the templates: There are 2 Visual Studio Solutions in the repo which represent a working type editor, both in C# and in Visual Basic. This solution is in the path .\winforms-designer-extensibility\Templates\TypeEditor\src\TemplateSolutions*.

image

These solutions provide:

image

The procedure for building the actual templates from the template solutions is as follows:

This copies the relevant project files from the template solution to the templates folder. The batch file then calls dotnet pack to create the solution template package and also installs the package with dotnet new install. You should see the result of that operation:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D:\Git\NetControlDesigners\src\Templates\Templates>dotnet new install .\bin\Debug\Microsoft.WinForms.Designer.TypeEditorTemplate.1.1.0-prerelease-preview3.nupkg
The following template packages will be installed:
   D:\Git\NetControlDesigners\src\Templates\Templates\bin\Debug\Microsoft.WinForms.Designer.TypeEditorTemplate.1.1.0-prerelease-preview3.nupkg

Success: Microsoft.WinForms.Designer.TypeEditorTemplate::1.1.0-prerelease-preview3 installed the following templates:
Template Name          Short Name          Language  Tags
---------------------  ------------------  --------  ------------------------------------------------------------------
WinForms .NET Cust...  WinFormsTypeEditor  [C#]      WinForms/Designer/TypeEditor/ActionList/CodeDomSerializer/Solution
WinForms .NET Cust...  WinFormsTypeEditor  VB        WinForms/Designer/TypeEditor/ActionList/CodeDomSerializer/Solution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Using the Type Editor Template

After building the templates they are ready to use from the CLI as well as from Visual Studio.

Creating a new Type Editor Solution within Visual Studio

image

Creating a new Type Editor Solution from the dotnet CLI

After installing the templates, you are using the type editor solution templates like every other Visual Basic or C# templates from the CLI. Refer to the help option for the exact parameter names. The parameters are the same as in the above additional options description:

Introduction to the Template Solution

Since .NET 3.1 started to support the WinForms Runtime, a new WinForms designer was needed to support .NET applications. This work required a near-complete rearchitecting of the designer, as we responded to the differences between .NET and the .NET Framework based WinForms designer everyone knows and loves.

Until we added support for .NET Core applications there was only a single process, devenv.exe, that both the Visual Studio environment and the application being designed ran within. But .NET Framework and .NET Core can’t both run together within devenv.exe, and as a result we had to take the designer out of process. We call the existing process – the process that Visual Studio runs in – the client process or the Visual Studio process, and the process which shows the actual Form at Design time, the server process or short DesignToolsServer.

Enter the DesignToolsServer

While simple control designer scenarios like type converters, action lists or CodeDom serializers don’t need any substantial rewrites, type editors are a different beast altogether.

To illustrate the additional requirements which arose by introducing different processes, let’s look at a typical type editor for any property of type `Image` like the Button’s BackgroundImage property: While the actual Image that you picked will be rendered on a button in the server process, the dialog you picked the Image with runs in the context of Visual Studio. That in turn means there is communication between the two processes necessary which custom type editors for the modern WinForms Designer need to take care of. In addition, type editor solutions also need to provide a NuGet package whose individual assemblies gets partly loaded into the Visual Studio process and partly into the DesignToolsServer. And to that end, this NuGet needs to have a special structure, which a dedicated Package project takes care of. If you want to learn more about the concept of the modern WinForms Designer: This blog post describes the concept of the different processes in greater detail.

Projects which the templates create

Setting all these things up manually means coordinating a lot of moving parts, and there is a huge potential that things go wrong. The individual projects created by this template help to prevent falling into those traps. The templates create a series of projects and important Solution Folders, depending on your needs for both C# and Visual Basic. Let’s look at the projects which are part of the template solution in detail:

Invoking Type Editors, In Process vs. Out-Of-Process

In the classic framework, invoking of a type editor is a straightforward procedure. Here is what happens, when the user triggers to edit a value of a property by opening a type editor via the Visual Studio’s property browser:

And here now is the all-important difference compared to the out-of-process Designer scenario: When the property browser asks the UITypeEditor to display the visual representation of the value, that type’s value is not available in the context of Visual Studio. The reason: The property browser runs in a process targeting a different .NET version than the process that defines the type. Visual Studio runs, for example, against .NET Framework 4.7.2 while the custom control library you are developing is e. g. targeting .NET 7. There is simply no way that .NET Framework can deal with types defined in or based on types defined .NET 7. So, instead of giving the UITypeEditor the control’s custom/special property’s value directly, it’s handing it via a so-called proxy object.

The concept of proxy objects in the client (Visual Studio) process does require a special infrastructure for handling user inputs in custom type editors. Let’s look at what infrastructure components of the Designer we need to understand, before we talk about the workflow for setting the value in the OOP scenario:

Now, with these important basics in mind, here is the workflow for setting a property value via a type editor in the out-of-process Designer scenario in detail:

    var dialogResult = editorService.ShowDialog(_customTypeEditorDialog);
    if (dialogResult == DialogResult.OK)
    {
        // By now, the UI of the Editor has asked its (client-side) ViewModel
        // to run the code which updates the property value. It passes the data to
        // the server, which in turn updates the server-side ViewModel.
        // When it's time to return the value from the client-side ViewModel back to the
        // Property Browser (which has called the type editor in the first place), the client-side
        // ViewModel accesses its PropertyStore property, which in turn gets the required PropertyStore
        // proxy object directly from the server-side ViewModel.
        value = viewModelClient.PropertyStore;
    }

The PropertyStore property of the ViewModelClient doesn’t have a dedicated backing field to hold the value. Rather, it uses the infrastructure of the proxy to communicate with the server-side view model to get the just created proxy of the server-side view model’s PropertyStore content directly. And the proxy object is what we need here: Again, since the client doesn’t know the type, it can only deal with the proxy objects which point and represent the server types instead.

RussKie commented 1 year ago

Could you please reformat the text and remove the hard wrapping, I'm struggling to read it. It also makes providing suggestions very hard. image

I'd expect your editor to provide soft wrapping (e.g., in Visual Studio Code it's ALT + Z)

RussKie commented 1 year ago

Ditto for the original post - I have to scroll a lot reading 80 chars per line: image

KlausLoeffelmann commented 1 year ago

Could you please reformat the text and remove the hard wrapping, I'm struggling to read it. It also makes providing suggestions very hard.

Hmm. The template guidelines suggest to wrap with ALT-Q, which I did. I never did hard line breaks, that did the authoring template.

merriemcgaw commented 1 year ago

Could you please reformat the text and remove the hard wrapping, I'm struggling to read it. It also makes providing suggestions very hard.

Hmm. The template guidelines suggest to wrap with ALT-Q, which I did. I never did hard line breaks, that did the authoring template.

Could you at least do the Alt+Z for the readme to do the soft line breaks. I have to agree with @RussKie that it's tough to read through it all with the short lines. The templates and code itself are up to you.