[API Proposal]: Allow `Control.Invoke` on `Binding`
- Dominant language
- C#
- Stars
- 4.9k
- Forks
- 1.1k
- Avg merge
- 1d 13m
- Merged PRs (30d)
- 85
Description
### Background and motivation
`Binding`s allow for Form `Control`s and models to be updated when their values are changed by the system in any way.
This would allow the form to be reactive, with processes running in the background performing all the necessary actions and having them reflected by the bindings automatically.
This already works with MAUI, specially if you use the `CommunityToolkit.Mvvm` and its generators.
However, while Winforms support bindings as well, it does not support asynchronous updates, because doing so would raise a `InvalidOperationException`.
This happens because the `Binding` class updates the value of the property directly, without checking if the control is in the UI Thread or not.
I then propose a new constructor to the `Binding` class in which you can set a flag to prefer using `Control.Invoke` when updating the bindings. `Control.Invoke` forces the `Action` to run at the UI Thread, solving the problem.
I have already [opened an issue](https://github.com/dotnet/winforms/issues/8532) about [this as well as a PR](https://github.com/dotnet/winforms/pull/8547).
### API Proposal
```csharp
namespace System.Windows.Forms;
public class Binding
{
public Binding(string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString, IFormatProvider formatInfo, bool invokeControl);
}
```
```csharp
namespace System.Windows.Forms;
public class ControlBindingsCollection : BindingsCollection
{
public Binding Add(string propertyName, object dataSource, string dataMember, bool formattingEnabled, DataSourceUpdateMode updateMode, object nullValue, string formatString, IFormatProvider formatInfo, bool invokeControl);
}
```
### API Usage
```csharp
// Create a new form and a text box to bind values to
Form form = new Form();
TextBox textBox = new TextBox();
textBox.Parent = form;
// Create a binding to handle the invoke method
Binding binding = new Binding("Text", mainObject, "Text", false, 0, null, string.Empty, null, true);
textBox.DataBindings.Add(binding);
form.Show();
// Perform the binding update in a separate thread to escape the UI Thread on purpose
var thread = new Thread(() =>
{
textBox.Text = "Updated test text";
Assert.Equal("Updated test text", textBox.Text);
Assert.Equal("Updated test text", mainObject.Text);
});
thread.Start();
```
### Alternative Designs
I have added a new constructor to the `System.Windows.Forms.Binding` class and a new public method to `System.Windows.Forms.ControlBindingsCollection` without changing previous APIs.
### Risks
In my limited study of the APIs, I didn't see any breaking risk associated with this proposal.
There may be performance repercussions in the `Control.Invoke` mechanic on heavy load but I was unable to test it.
[The PR I mentioned](https://github.com/dotnet/winforms/pull/8547) has some tests which could be useful in analyzing those scenarios.
Contributor guide
Assessment
This issue has not been assessed yet.