Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom control's properties can't be used by xaml outside the control's own assembly (F#) #17064

Open
marklam opened this issue Sep 19, 2024 · 13 comments
Labels
by-design The behavior reported in the issue is actually correct.

Comments

@marklam
Copy link

marklam commented Sep 19, 2024

Describe the bug

Avalonia bindings expect the properties of a control to be defined as static fields

e.g. C#

    public static readonly StyledProperty<string> MessageProperty = AvaloniaProperty.Register<UserControl1, string>("Message");

Which in the IL is

 .field public static initonly class [Avalonia.Base]Avalonia.StyledProperty`1<string> MessageProperty

Static fields in F# are given internal/assembly access

e.g. F#

    static let MessageProperty = AvaloniaProperty.Register<UserControl1, string>("Message")

Which in the IL is

 .field assembly static class [Avalonia.Base]Avalonia.StyledProperty`1<string> MessageProperty

If the controls project (.fsproj) has the property <ProduceReferenceAssembly>true</ProduceReferenceAssembly> then the view will be built against the controls' reference assembly, which will have had the property definition fields removed (because they were marked as internal).

Controls ProduceReferenceAssembly View AvaloniaUseCompiledBindingsByDefault Failure
false false Runtime System.FieldAccessException
false true Runtime System.FieldAccessException
true false Compiler Avalonia error AVLN3000: Unable to find suitable setter or adder for property Message of type FSharpControl:FSharpControl.UserControl1 for argument Avalonia.Markup:Avalonia.Data.Binding
true true Compiler Avalonia error AVLN3000: Unable to find suitable setter or adder for property Message of type FSharpControl:FSharpControl.UserControl1 for argument Avalonia.Markup:Avalonia.Data.Binding

To Reproduce

https://github.com/marklam/AvaloniaBindToFSharpControl contains a C# view app with equivalent F# and C# control libraries, each containing a UserControl1 with a Message Avalonia property.

The F# project also has a UserControl2 which defines a public static property-getter for the StyledProperty, which cannot be bound because it's defined as a property not a field.

Expected behavior

Since the compiled bindings will fail at runtime, the compiler should probably produce an error for an internal field being bound from a different assembly.

But most of all, it would be fantastic if the bindings could be made to a StyledProperty defined in a static property-getter, instead of only those defined in a static field, since F# can declare those easily. (See UserControl2.fs in the example repo.)

Avalonia version

11.1.3

OS

Windows

Additional context

No response

@marklam marklam added the bug label Sep 19, 2024
@maxkatz6 maxkatz6 added the by-design The behavior reported in the issue is actually correct. label Jan 30, 2025
@maxkatz6
Copy link
Member

Avalonia bindings require property definition to be accessible, which is why they are usually public.
While one might argue that setter/getter are public, Avalonia doesn't usually use them, and instead calls control.SetValue(Definition, value, priority) and control.GetValue(Definition), where priority is a necessary argument.

@catamounttechnology
Copy link

Are there workarounds? I had decided to use Avalonia instead of WPF especially for its F# support, but it doesn't seem to be well tested for F#.

@catamounttechnology catamounttechnology marked this as a duplicate of #18086 Jan 30, 2025
@maxkatz6
Copy link
Member

maxkatz6 commented Jan 30, 2025

It is tested. But F# doesn't work well with conventional MVVM approaches, like bindings.
Instead, I would recommend using F# specific libraries built on top of Avalonia, which utilize language capabilities:
https://github.com/fsprojects/Avalonia.FuncUI
https://github.com/fabulous-dev/Fabulous.Avalonia

Keeping traditional XAML and MVVM would still likely require you to write some C#.

@catamounttechnology
Copy link

catamounttechnology commented Jan 30, 2025

I think these packages do not leverage the visual designer in Visual studio. So no they are not a solution for me. Any idea on whether and when this issue will be fixed?

@maxkatz6
Copy link
Member

@catamounttechnology this issue is fundamental and can't be "fixed".

Language limitation can be lifted when request is implemented:
fsharp/fslang-suggestions#746

@maxkatz6
Copy link
Member

Or, probably a decent workaround, you can enable internal visible to from the project with control to the project with XAML. This should work with any .NET language.

@catamounttechnology
Copy link

wouldn't be easier for Avalonia to accept static public properties instead of only static public fields for AvaloniaProperties?

@marklam
Copy link
Author

marklam commented Jan 31, 2025

I think these packages do not leverage the visual designer in Visual studio. So no they are not a solution for me. Any idea on whether and when this issue will be fixed?

This approach can allow you to use the visual designer: https://github.com/JordanMarr/ReactiveElmish.Avalonia

I use an interface for a ViewModel, with implementations for a DesignViewModel and the real app's ViewModel. You can use the ReactiveElmish to expose model changes as bindable viewmodel properties.

Then for making changes to the program model, you can implement commands to send update messages, and invoke the commands from events using https://github.com/wieslawsoltes/Avalonia.Xaml.Behaviors

@timunie timunie removed the bug label Jan 31, 2025
@catamounttechnology
Copy link

catamounttechnology commented Jan 31, 2025

@marklam This is great work and I will try it. However I don't see (yet) how it can help in overcoming this issue. If someone wants to create a package containing custom controls with custom DependencyProperty (or StyledProperty as Avalonia calls them), that someone cannot use F# and must fallback to C#

@marklam
Copy link
Author

marklam commented Jan 31, 2025

I did two things, for anything new I created controls with ViewModels and did evertything via the ViewModel.

For the pre-existing controls that I had moved outside the Views assembly, I made a hacky script to change the visibility of the fields using Mono.Cecil. Although since there's only one view assembly in my case, the InternalsVisibleTo approach mentioned above by @maxkatz6 would probably have been a lot nicer.

@catamounttechnology
Copy link

Is that "hacky script" shareable?

@marklam
Copy link
Author

marklam commented Jan 31, 2025

@catamounttechnology
Copy link

catamounttechnology commented Feb 1, 2025

@marklam I have used the script and it seems to work

Without script (ILSpy)
internal static StyledProperty PippoProperty;

With script
public static readonly StyledProperty PippoProperty;

However in the main project I still Get the same binding error
MainView.axaml(21,62,21,62): Avalonia error AVLN3000: Unable to find suitable setter or adder for property Pippo of type QuanTive.Controls.Avalonia:QuanTive.Controls.Avalonia.Chart for argument Avalonia.Markup:Avalonia.Data.Binding,

type Chart() as this = 
    inherit Core.Chart()
    static let PippoProperty = AvaloniaProperty.Register<Chart, int>("Pippo", 0)

    do this.InitializeComponent()

    member private this.InitializeComponent() = 
        AvaloniaXamlLoader.Load(this)

    member __.Pippo
        with get() = this.GetValue(PippoProperty)
        and set(value) = this.SetValue(PippoProperty, value) |> ignore

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
by-design The behavior reported in the issue is actually correct.
Projects
None yet
Development

No branches or pull requests

4 participants