levitali / CompiledBindings

MIT License
265 stars 14 forks source link

This project should be merged into the official WPF repo! #1

Open netcorefan1 opened 2 years ago

netcorefan1 commented 2 years ago

I want to congratulate with the author for being the only one to take this initiative for a feature that should be part of WPF sdk. It works great, but over the time I found a severe limitation which makes the implementation pretty much unusable: unlike UWP versiom, the markup extension only accepts one parameter when binding to a function. With mostly of the framework ones we're kicked off since only a very small amount of functions takes one parameter. With custom functions is pretty much the same as long as we need to pass more than one parameter and there is no hack that works (tuples, class instance with its properties as parameters etc). Is this something difficult to implement?

I also found big problems with async functions. The markup extension does not accept a return type of Task and I've not been able to find a way to run any async code. Regular Bindings deals with this through IsAsync property. Something like this would be good or, even better, the ability to accept "await" keyword.

Other improvements:

Thanks

levitali commented 2 years ago

Thank you for your feedback!

Like in UWP, it is possible to bind to a function which takes many parameters. For example:

<TextBlock Text="{x:Bind system:String.Format('{0}  {1}', Quantity, Unit}"/>

The static function String.Format takes in the example three parameters: format string, as well as to values.

Maybe you misunderstood in my description binding to a function "as target", which is completely unavailable in UWP and WPF. Here is a bit modified above example:

<TextBlock m:SetText="{x:Bind system:String.Format('{0}  {1}', Quantity, Unit}"/>

In this example two functions are used. The first one on right side can take unlimited number of parameters. Instead of setting the right side expression to a property, like in “classic” WPF or UWP bindings, a function is called, which excepts the evaluated value of the expression.

As for Tasks and IsAsync. Actually, the Binding.IsAsync has nothing to do with Tasks. The property tells only that getting of the property should not block the UI thread. It should be only used if getting a property synchronously can take long time.

Binding to a property or a function, which returns Task<T>, with await, maybe an interesting feature and I will consider implementing it. The problem that I see here, is what to do with possible exceptions. Should they just be ignored?

The “new” keyword is already supported. I didn't describe it earlier, but now added it to the description. You can create objects like this:

<TextBlock Text="{x:Bind SomeFunction(Property1, new local:Class1(Property2)}"/>

I’ve considered implementing Bindings.Update method for explicitly applying bindings. In comparison to UWP, the problem here is, that there can be many Bindings classes. As I wrote in my description, you can change the DataType anywhere in XAML. For each “DataType” a correspoing Bindings class is generated.

I will consinder to implement this feature for bindings without explicit DataType.

netcorefan1 commented 2 years ago

Many thanks for your prompt response. Multiple parameters works. I must have messed up something with the m: Namespace. In addition to that, R# finds lot of errors and false positives ones can only be detected on compilation (I should disable inspection, but if I do I will also lose intellisense). However, when the m namespace is omitted still happens what I have reported. To reproduce:

// MainWindow.xaml.cs
public string InstanceFunction(int val1) => $"InstanceFunction({val1})";
public string InstanceFunction(int val1, int val2) => $"InstanceFunction({val1}, {val2})";

// MainWindow.xaml
 // works (how is this possible without m:namespace?):
<TextBlock Text="{x:Bind InstanceFunction(1) }" /> 
 // raise Error XLS0506 No constructor for type 'BindExtension' has 2 parameters:
<TextBlock Text="{x:Bind InstanceFunction(1, 2) }" />

I've been surprised to see that the new keyword works too! This is amazing! Should also work object initializer? I tried, but I get a generic Syntax error '{' (may be we need to escape braces?):

<TextBlock m:Text="{x:Bind SomeFunction(Property1, new local:Class1 { Val1 = 888 }) }" />

It would be great if this could work because it will save us from writing initialization code in code behind (and when the object comes third party code it could become even harder).

Regarding await, you could take a look at MVVM toolkit to see how they implemented their AsyncRelayCommand\<T>. I can't find any info on how they handle exceptions. I think that ignoring is the best solution if the alternative is the inability to run any async code. The benefits we get from just adding a single "await" keyword are invaluable. I would just add an xml comment to warn users that their async code must handle any possible exception and return the expected data. What could be done is implementing the same CancellationToken, CanBeCanceled, IsCancellationRequested, Cancel method and ExecutionTask property to monitor the pending operation. It will make dealing with problems easier, but the user still need to make sure to handle exceptions (like should do in any part of a program). As of now the only way to run async functions is by calling them from within the previously mentioned async command provided from the toolkit (I'm not even sure if relies on reflection under the hood), but it seems to be that this just adds unneeded complexity when we could just call the same functions directly from xaml.

I'm not sure on how Bindings.Update() works. I always use the MVVM pattern and while playing with x:bind everything worked as expected in data binding (on both ways). If this is only useful for non MVVM scenarios, then I would leave its implementation as the last step if really makes sense (because sincerely, even in most simple projects, even the more primitive model view should be used by default since it still provides great benefits). Bindings without explicit DataType would certainly be a great thing, but if this is something that requires too much efforts as you say, than I am not sure at what point is worth. I'm at the beginning of a project where I'm basically switching to x:bind for the first time and until now, I don't see any problem to declare data type each time, but may be it's too soon to speak and I should wait until the binding model becomes more complex.

What I think should have higher priority is designer support. As of now, to preview data binding in the designer I'm forced to add an additional line of code for each call to x:bind:

<TextBlock Text="{x:Bind Name, Mode=TwoWay}" d:Text="{Binding Name, Mode=TwoWay}" />

Apart than making the code more difficult to read, I also expect this hack to work only for very basic binding since x:bind and Binding work differently. Do you think would be complicated to implement designer support?

I'm surprised that WPF guys have not yet asked you to merge your project in their repo! Unlike WinForms, wpf is more resources intensive and compiled bindings should be supported out of the box. I hope that this lack is not guided from the intention to convince people to switch to UWP because no one that know what wpf is and can do will do the switch unless they're forced to do through the lack of the new features.

levitali commented 2 years ago

I released a new Version 1.0.5. Now it is possible to bind to asynchronous properties and functions. Please see Readme for more details,

netcorefan1 commented 2 years ago

I've been away for more than a week and now I see so many surprises in the ReadMe! I didn't had chances to play deeper with the new async feature, but at first look it is working really well. Thanks! You're amazing!

levitali commented 2 years ago

Yes, the designer support would be great. As well as Hot Reload. Because we at our firm mostly program Xamarin Forms (there is no Designer), Hot Reload feature is more requested.