question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[API Proposal]: Introduce Commands and DataContext to WinForms to adapt modern Binding scenarios

See original GitHub issue

Rationale:

We want our customers to modernize their existing WinForms Apps and allow them to adapt new Azure-based technologies. It is important for our customers, that the business logic of their WinForms LOB apps to this end becomes more flexible and can be rearchitected away from the WinForms-typical code-behind and towards UI Controller or ViewModels, which can be easy reused and unit tested. This is also an incentive to pivot away from the 15 years old Typed DataSets in WinForms, which have very limited support in .NET WinForms Apps over .NET Framework, since the necessary Data Source Provider Service is not available for .NET Apps altogether. Feedback for suggesting this as our new Binding strategy was highly engaged, very positive, and shows that we’re on the right way with that.

WinForms has a pretty powerful binding engine, but to achieve those things, a few essential features are missing:

  • ToolStripItems (the items that make pull-down menus, status bar labels and other status bar controls) are components rather than controls, and components deriving from ToolStripItem are currently not bindable.
  • Buttons and ToolstripItems don’t provide properties to store or bind to Commands based on System.Windows.Input.ICommand.
  • There is no central hooking point on a Form or a UserControl to distribute a data source for data binding purposes from the parent down to every child it’s hosting, like the DataContext property does that in WPF (or BindingContext in Maui Apps).

Having these features in WinForms, enables a series of important scenarios:

  • Using ViewModels/Controllers in WinForms: Customer’s LOB logic should be leverage to other UI technologies (Maui, UWP, WPF) and Azure Services, which is not possible or at least only quite difficult to achieve with the typical existing WinForms code-behind methodology.

MauiWinForms

  • Easier adaptation of modern ORMs: Since for the .NET Designer, the Data Source Provider Service is not available, with this new feature, it becomes easier to adapt and migrate away from ancient WinForms data layer architectures: While typed DataSet and DataTable were clearly designed with the WinForms code-behind model and direct binding to UI Elements of WinForms in mind, with the new data binding features present, modernized WinForms Apps could more easily consume typical ViewModels and Models as their Controller and Data Layer abstractions, and, what’s important: Models in this scenarios being POCO classes which are generated and maintained by Entity Framework or Entity Framework Core.

  • Making it possible to unit test WinForms Apps business logic: Especially with the introduction of commands and command parameters, it would be way easier to use ViewModels/UI-Controller in binding scenarios for WinForms, that can be reused in Maui scenarios. While WinForms will not be able to achieve the same flexibility as compared to XAML based data binding, binding commands is a considerable step in that direction, Customers have repeatedly asked for that, and making ToolStripItems bindable at the same time, would be equally helpful in easier adapting ViewModels/UI Controller Models.

  • It’s almost impossible to unit test Code-behind apps, which mixes business logic and UI-Control code. But that’s how WinForms Apps are typically “architected”. The new API makes it way simple, to separated the Business Logic from the actual Code-Behind code, so it can be properly unit and integration tested.

Github Repo for Sample-App

https://github.com/KlausLoeffelmann/WinFormsCommandBinding

API-Changes, Overview:

Note: For .NET 7, we want to put this feature out in Preview, attributing respective new classes and properties with the RequiresPreviewFeature attribute, collect feedback and then enable it for .NET 8.

To provide a more ViewModel/View Controller compatible way for a WinForms LOB architecture, I propose the following API-Changes:

  • Introduce the abstract classes BindableComponent, CommandComponent and CommandControl which describes the necessary methods and properties for extending a WinForms Component or a WinForms Control with the command logic. Since a Component is not bindable by default, we introduce a new abstract class which is bringing all the necessary infrastructure to make a Component bindable. CommandComponent (literally) on top of that introduces a Command property, allows to executes an Action assigned to Command, when the context allows for the Command to be executed. The action assigned to Command is being passed the parameter which is stored in the Component’s or Control’s new property CommandParameter. This is the same way, commands work in WPF, UWP, Xamarin and Maui.

  • Inherit ButtonBase from CommandControl instead of Control, so it will get all the necessary infrastructure for binding to commands, and for executing commands - if the context allows - OnClick.

  • Inherit ToolStripItem for CommandComponent, to make a) all ToolStripItem derived components bindable, and b) introduce a bindable Command property on all ToolStripItem derived components which can be clicked. Note: IBindableComponent is not a new API. It was especially developed to provide Data Binding for components (and it’s of course implemented in Control to make it bindable). Implementing this Interface makes the new APIs BindingContext and OnBindingContextChanged necessary. The functionality of BindingContext is handed over to the existing static method BindingContext.UpdateBindings, which was especially designed to handle bindable Components. The idea is to seperate BindableComponent and CommandComponent, so other components can derive from BindableComponent to provide principle binding functionality, without necessarily to provide command binding.

  • Introduce a property DataContext on Control as an ambient property, and - to meet the WinForms DataBinding conventions - implement a virtual protected method OnDataContextChanged and the DataContextChanged event along with it. This way a child (User)Control can marshal a new/changing data source to whatever BindingSourceComponent needs to be assigned to.

FAQ

A few of most frequently asked questions up to now:

Why do we need On[Propertyname]Changed methods and [Propertyname]Changed Events for each property

That’s the convention in WinForms to support data binding back to the source: A property is bindable in the direction to the data source only, if the property can raise a respective xxxChanged event. A further convention in WinForms is that for each event there is a Onxxx method, that can be overwritten in a derived class, which raises the event, since events cannot be raised directly in a derived class.

What’s the reason for the naming

Naming pf new properties is based on precedence in existing UI stacks (Command, CommandParameter, DataContext in WPF). Naming of additional new events and methods is based on either existing interfaces or naming conventions in WinForms.

Why are we only introducing Commands on ButtonBase and ToolStripItem

Because only those make sense in the context of executing commands, and that’s the way is done also in WPF. We have been discussing LinkLabel, but came to the conclusion that it would probably too special a use case. Since we are providing an easy way to extend controls with commands by letting exsting Controls or Commands inherit from CommandComponent or CommandControl, users can easily implement additional scenarios in controls how they see fit. The same is true for control vendors.

Do we need to extend the Designer?

No. As can be seen in the screenshots here, the Designer can deal with every of these changes out of the box. There is no necessity for additional work in the WinForms Designer.

If we have a typical Controller or ViewModel based on a MVVM-Framework like the MVVM Toolkit (or just implementing INotifyPropertyChanged directly like in the following sample), then the Designer can already work with that (see following screenshot):

using System;
using System.ComponentModel;

namespace WinFormsCommandBinding.Models.Controller
{
    public class SimpleEditorController : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        private string? _textDocument;
        
        // Simple class which implements the details of ICommand.
        private RelayCommand? _newCommand;

        public SimpleEditorController()
        {
            _newCommand = new(ExecuteRelayCommand, CanExecuteRelayCommand);
        }

        private void ExecuteRelayCommand(object? commandParameter)
            => TextDocument = String.Empty;

        private bool CanExecuteRelayCommand(object? commandParameter)
            => TextDocument != String.Empty;

        public RelayCommand? NewCommand
        {
            get => _newCommand;

            set
            {
                if (!Equals(_newCommand, value))
                {
                    _newCommand = value;
                    PropertyChanged?.Invoke(this, new(nameof(NewCommand)));
                }
            }
        }

        public string? TextDocument
        {
            get => _textDocument;

            set
            {
                var wasEmpty = String.IsNullOrEmpty(_textDocument);

                if (!Equals(_textDocument, value))
                {
                    _textDocument = value;
                    PropertyChanged?.Invoke(this, new(nameof(TextDocument)));

                    if (wasEmpty ^ string.IsNullOrEmpty(_textDocument))
                    {
                        // Execution-Context changed because the TextDocument was empty
                        // and now it's not anymore or viceversa.
                        NewCommand?.RaiseCanExecuteChanged();
                    }
                }
            }
        }
    }
}

image

List of new APIs

Introducing the new abstract component BindableComponent

Note: Even though on first glance this might sense to put in the System.Component namespace and in the runtime, this is for the WinForms binding infrastructure only: IBindableComponent is in System.Windows.Forms and so is BindingContext. Providing the infrastructure for Updating/Managing binding for Components has been implemented in WinForms all along, see here: BindingContext.cs (dot.net)

namespace System.Windows.Forms
{
    public abstract class BindableComponent : Component, IBindableComponent
    {
        public event EventHandler? BindingContextChanged;
        public ControlBindingsCollection? DataBindings { get; }
        public BindingContext? BindingContext { get; set; }
        protected virtual void OnBindingContextChanged(EventArgs e) { }
    }
}

Introducing the new abstract component CommandComponent

namespace System.Windows.Forms

    internal abstract class CommandComponent : BindableComponent
    {
        public event EventHandler? CommandCanExecuteChanged;
        public event EventHandler? CommandChanged;
        public event EventHandler? CommandParameterChanged;

        public abstract bool Enabled { get; set; }
        public System.Windows.Input.ICommand? Command { get; set; }
        public object? CommandParameter { get; set; }
        protected virtual void OnCommandChanged(EventArgs e) { }
        protected virtual void OnCommandCanExecuteChanged(EventArgs e) { }
        protected virtual void OnCommandParameterChanged(EventArgs e) { }
        protected virtual void OnRequestCommandExecute(EventArgs e) { }
    }
}

Introducing the new abstract control CommandControl

namespace System.Windows.Forms
{
    internal abstract class CommandControl : Control
    {
        public event EventHandler? CommandCanExecuteChanged;
        public event EventHandler? CommandChanged;
        public event EventHandler? CommandParameterChanged;

        public System.Windows.Input.ICommand? Command { get; set; }
        public object? CommandParameter { get; set; }
        protected virtual void OnCommandChanged(EventArgs e) { }
        protected virtual void OnCommandCanExecuteChanged(EventArgs e) { }
        protected virtual void OnCommandParameterChanged(EventArgs e) { }
        protected virtual void OnRequestCommandExecute(EventArgs e) { }
    }
}

Inheriting ButtonBase from CommandControl

namespace System.Windows.Forms
{
    public abstract partial class ButtonBase
-        : Control
+        : CommandControl
    {
          .
          .
          .
    }
}

Inheriting ToolStripItem from CommandComponent

namespace System.Windows.Forms
{
    public abstract partial class ToolStripItem 
-                            : Component,
+                            : CommandComponent,
                              IDropTarget,
                              ISupportOleDropSource,
                              IArrangedElement,
                              IKeyboardToolTip
    {
    .
    .
    .
    }
}

Introducing DataContext on the existing class class Control

We need an extension point for assigning the data sources not only to a Form or a UserControl, but at the same time make that data source available to each of the child (User)Controls which this root control is hosting. To this end, we introduce the DataContext Property of type object on Control, and we implement it as an ambient property. This way, it doesn’t use any additional memory resources, as long as it is not used. When it is assigned to a Form, its OnDataContextChanged for the Form (and for each respective child control) is called. OnDataContextChanged then raises the DataContextChanged event. The root control or/and the children can handle that event to decide, what to do with the data source: Usually, the data source then gets assigned to the BindingSource component(s) the Control uses, which then initiates the actual data binding.

namespace System.Windows.Forms
{
    public partial class Control :
        Component,
        Ole32.IOleControl,
        Ole32.IOleObject,
        Ole32.IOleInPlaceObject,
        Ole32.IOleInPlaceActiveObject,
        Ole32.IOleWindow,
        Ole32.IViewObject,
        Ole32.IViewObject2,
        Ole32.IPersist,
        Ole32.IPersistStreamInit,
        Oleaut32.IPersistPropertyBag,
        Ole32.IPersistStorage,
        Ole32.IQuickActivate,
        ISupportOleDropSource,
        IDropTarget,
        ISynchronizeInvoke,
        IWin32Window,
        IArrangedElement,
        IBindableComponent,
        IKeyboardToolTip,
        IHandle
    {
        public event EventHandler DataContextChanged;

        public virtual Object? DataContext { get; set; }

        protected virtual void OnDataContextChanged(EventArgs e) { }
        protected virtual void OnParentDataContextChanged(EventArgs e) { }

    }
}

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:11
  • Comments:18 (16 by maintainers)

github_iconTop GitHub Comments

4reactions
KlausLoeffelmanncommented, Mar 14, 2022

I know folks are waiting for more demos and concepts how to implement this in real life demos. I will start working on this implementation shortly, and we’ll aim to enable this or at least parts of this in the .NET 7 timeframe.

To be as transparent as possible: Our SDK, improving the usage of the SDK, and - of course - its documentation has however higher priority, so we need to complete a few work items there, first.

To keep this discussion alive and give folks a clearer idea how this functionality can be used (and also to address the more and more incoming requests for a model-binding demo from the Databinding post, and also as an answer to @bairog, here is a link to my private repo, in which I was collecting ideas around this. It’s pretty much what the Databinding posts also reflects.

Also please, keep in mind, the primary idea of the Databinding post was to introduce the new Databinding functionality in the designer. We should lead the discussion about WinForms UI Controllers/ViewModel ideas here.

https://github.com/KlausLoeffelmann/WinFormsCommandBinding

2reactions
terrajobstcommented, Jul 21, 2022

Video

  • We’d prefer not having the classes CommandComponent and CommandControl
    • Instead just follow the WinForms model of having a naming convention
  • We still need BindableComponent because Component is lower than WinForms (where IBindableComponent lives).
  • We’d like the designer to mark the generated InitializeComponent with [RequiresPreviewFeatures(Url = "<placeholder>")] when these are used
namespace System.Windows.Forms;

[RequiresPreviewFeatures(Url = "<placeholder>")]
public abstract class BindableComponent : Component, IBindableComponent
{
    public event EventHandler? BindingContextChanged;
    public ControlBindingsCollection? DataBindings { get; }
    public BindingContext? BindingContext { get; set; }
    protected virtual void OnBindingContextChanged(EventArgs e);
}

// Suppress warning
public abstract class Control : BindableComponent // Used to be Component
{
}

public partial class ButtonBase
{
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public event EventHandler? CommandCanExecuteChanged;
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public event EventHandler? CommandChanged;
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public event EventHandler? CommandParameterChanged;

    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public ICommand? Command { get; set; }
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public object? CommandParameter { get; set; }

    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnCommandChanged(EventArgs e);
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnCommandCanExecuteChanged(EventArgs e);
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnCommandParameterChanged(EventArgs e);
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnRequestCommandExecute(EventArgs e);
}

public abstract partial class ToolStripItem : BindableComponent // Used to be Component
{
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public event EventHandler? CommandCanExecuteChanged;
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public event EventHandler? CommandChanged;
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public event EventHandler? CommandParameterChanged;

    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public ICommand? Command { get; set; }
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    public object? CommandParameter { get; set; }

    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnCommandChanged(EventArgs e);
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnCommandCanExecuteChanged(EventArgs e);
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnCommandParameterChanged(EventArgs e);
    [RequiresPreviewFeatures(Url = "<placeholder>")]
    protected virtual void OnRequestCommandExecute(EventArgs e);
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Actions · dotnet/winforms
[API Proposal]: Introduce Commands and DataContext to WinForms to adapt modern Binding scenarios Backport PR to branch #1368: Issue comment #4895 (comment) ...
Read more >
Using Command Binding in Windows Forms apps to go ...
The simplified scenarios used in this blog post to explain the UI-Controller/MVVM approach in WinForms with the new WinForms Command Binding ...
Read more >
Windows Forms Binding Improvements in .NET 7 for MVVM ...
NET 7 framework includes command binding preview features that aim to modernise Windows Forms applications. These features introduce new ...
Read more >
WinForms Todo List using MVVM and .NET 7 - Dalibor Stys
Let's call them MainForm and MainFormViewModel. We'll create a new instance of MainFormViewModel in code behind of MainForm and assign it to new ......
Read more >
winforms data binding
NET 1.1 simple data binding mechanism: an object property is bound to a control property, ... [API Proposal]: Introduce Commands and DataContext to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found