[Spec] C# Markup for MVU and MVVM #119
Replies: 29 comments 20 replies
-
Yes I would want this very much. The current C# markup seems too verbose. |
Beta Was this translation helpful? Give feedback.
-
Hi @VincentH-Net, interesting idea but if I understood correctly it may be an issue with different overloads, no?
.Color ("#ABCDEF") => If someone creates a different overload for color, which also needs a string as input, how would it be? Will the person have to create a different Color class name? Nowadays it is unlimited as the person can do "new Color(#ABCDEF")" or may create a new one like "new ColorBasedOnTheDay("01/01/2001")"
.TextLeft () => How are the guys going to check if the same property is being repeated? They will have to check in this case if TextRight and TextMiddle already exist in the syntax.... I imagine that today they only have to check if TextAlignment is being called twice. Also, when you press the dot, you will have 2000 options in your IntelliSense, no?
One issue I see here is that every Windows Forms developer who don't want to learn XAML will use the tool into existing XAML developed applications to convert what they don't understand. Sorry if I misunderstood the suggestions. |
Beta Was this translation helpful? Give feedback.
-
Hi @jcbeppler, thanks for chiming in. No worries, none of the things you mention are an issue. I'll explain inline below.
Replicating ctor overloads as helper overloads - in this example
We won't. We could, but it is not needed. Even if a dev would overlook that
No; you will have about the same number of extension method intellisense options as for existing properties - and only where you import the C# Markup namespace. You can even select to only show the extension methods. And intellicode shows the most likely first.
Understanding is not the issue; if you know one, you understand the other since it is the same object model, just with different syntax. The tool is just there to automate the mindless syntax conversion for you. So typical use would be when a dev wants to reuse (a variation of) an existing XAML snippet (either from the same app or from the internet) in a C# Markup page.
NP; I'm happy to clarify where I can |
Beta Was this translation helpful? Give feedback.
-
Hi @VincentH-Net , thanks for the clarification. That is definitely a change of paradigm. I just think it appears a little bit strange the comparison in the code you created (on the right) because it is basically the same as the original (from the left). In your code you just grouped many things on the same line which if you do the same on the left it will have the same size. Of course you removed the "new" keyword which is basic from C#, not that it couldn't be done but it will look strange if in some places you need to use it and in others not. Also for me (maybe because I am long time user) it looks strange that I will have something called VStack and HStack considering that both are StackPanel just with different orientation. I didn't like so much the changes but I will think more about the subject. 😄 |
Beta Was this translation helpful? Give feedback.
-
I played around a little with constructor wrapper generators, but I've always been a little unhappy with the look of mixing object initializers, static constructor wrappers, and fluent extension methods. With some of my hand written static constructor wrappers, I ended up adding optional function arguments for each of the properties of the control, so that I could set them in a single function call expression rather than use the fluent API: // CSharpForMarkup
Content = new Label("Text") { }
.Margin(5)
.FillHorizontal()
// Static ctor wrapper with only ctor arguments
Content = Label("Text")
.Margin(5)
.FillHorizontal()
// Combined static wrapper
Content = Label(
text: "Text",
margin: Uniform(5),
horizontal: Fill
); The drawback is that it's slightly more verbose than the static ctor wrapper with fluent extensions, and not extensible without additional fluent extensions. But it seems more readable to me maybe? In an ideal world, I would love to combine two language changes: removing the // Extension properties, elide `new` keyword
Content = Label {
Margin = Uniform(5),
HorizontalOptions = FillAndExpand, // static using directive, or maybe new target type enum language feature
ThirdPartyProperty = someValue, // an extension property defined by a third party
}; Obviously these small examples don't really demonstrate the readability issue as much as I'd like but if you've had experience with these layouts hopefully you recognize the issue. Otherwise I can try to come up with a more involved example. |
Beta Was this translation helpful? Give feedback.
-
// Combined static wrapper
Content = Label(
text: "Text",
margin: Uniform(5),
horizontal: Fill
); That's how Fabulous currently works, fabulous-dev/Fabulous#738 discusses how to improve. |
Beta Was this translation helpful? Give feedback.
-
It would also be nice if we could use object initializer syntax with function return values somehow, though that would encourage mutability: Content = MyStyledLabel("Text") then { Margin = Uniform(5) }; With an immutable UI tree, we'd just use the new Content = MyStyledLabel("Text") with { Margin = Uniform(5) }; Here the new |
Beta Was this translation helpful? Give feedback.
-
@JeroMiya both the combined static wrapper and the object initializer syntax have the limitation that they cannot use the view or the property that you are setting to scope the names of assignable property values. E.g. both Label ("Text", margin: Uniform(5), horizontal: Fill); // Combined static wrapper
Label ("Text") then { Margin = Uniform(5), Horizontal: Fill}; // Object initializer syntax So each possible value that you can potentially assign to any property in the entire UI framework needs to be available in source file (page) context. Including all values of all enums and all static values (e.g. all You could to this with multiple
The power of fluent is in the Label ("Text")
.TextLeft () // Fluent helper scopes to only Label type and joins property name and enum value
.Color () .Black () // Fluent subchain scopes .Color() to views that have color, and .Black () etc names to only after Color()
Label ("Text",
textAlignment: TextAlignment.Left, // Forced to use scope name repetition to avoid name collision
color: Color.Black) // Forced to use scope name repetition to avoid name collision
Label ("Text") then {
TextAlignment = TextAlignment.Left, // Forced to use scope name repetition to avoid name collision
Color = Color.Black } // Forced to use scope name repetition to avoid name collision So the fluent approach scales better to the full framework, if you want to use concise names and don't want to repeat yourself. |
Beta Was this translation helpful? Give feedback.
-
@JeroMiya wrote:
I had the same feeling; the best scaling approach I could think of to improve that (without changing C#) was:
This is the approach I described in this spec, e.g.: Label ("Text")
.TextLeft ()
.Color () .Black () |
Beta Was this translation helpful? Give feedback.
-
I don't know if changing C# can be in scope for this spec, because we need to release this within a year. Extension properties, optional new in initializers and implicit parameter value type will solve many of the issues that we solve with fluent and static ctor wrapper in this spec. Perhaps best to collect / discuss thoughts in this C# language proposal examples for UI markup gist and then create a proper language proposal from that? One thing though. After we have solved & released with the fluent API, C# changes will mainly help to make UI frameworks lighter, but it will have less additional benefit for the devs who write the markup. So maybe it will not be worth it unless C# is changed to support markup within 6 months... |
Beta Was this translation helpful? Give feedback.
-
@Happypig375 wrote
Thanks for that link to the Fabulous discussion. I picked up some insights there that resonate for C# Markup: The fluent DSL approach has several advantages.
Comparing several markup DSLs, SwiftUI comes out on top: HStack(alignment = .leading) {
Label("Hello world")
.font (.title)
.color (.grey)
Image("path/to/image")
}
This spec already comes close to SwiftUI. The above swift would become this C#: HStack (
Label ("Hello world")
.Font () .Title ()
.Color () .Grey (),
Image ("path/to/image")
) .Alignment () .Leading () To get even closer we could adopt:
|
Beta Was this translation helpful? Give feedback.
-
@VincentH-Net Yep, understood. I think your proposal works in terms of consistency and readability, though I think it might benefit the spec if at least some of the more common properties are added as additional optional arguments to the static ctor wrappers, similar to Fabulous. I like that style because it encourages writing code in an immutable way (even if the types themselves are not actually immutable). I've written a static ctor wrapper generator before, but it was quite limited - particularly with respect to generic types and arguments. And it didn't include additional properties like Fabulous. I'd be interested to see if anyone else has made more progress than I on a more complete solution. Also, just to clarify on enums - I was assuming some kind of target type inference would be involved to allow unambiguous shortening of the enum values to just the enum value name without the type name repetition, in situations where the enum target type is known (i.e. function arguments or property initializers). That would eliminate the ambiguity storm caused when you try to use too many |
Beta Was this translation helpful? Give feedback.
-
@JeroMiya wrote:
Yes; that is what I propose - based on things that work well for devs in SwiftUI (see above):
One thing to keep in mind is that this proposal wants to align MVVM C# Markup and MVU (Comet) markup. Comet already has optional parameters for views. We would need to have the same set of parameters for MVU and MVVM. Both static ctor wrappers and property helpers should be generated from the underlying properties, but with intelligent direction to the code gen so we can:
Yes, I thought along those lines too; it would require a C# change, hence my comment on that above |
Beta Was this translation helpful? Give feedback.
-
I updated the CodeGen section in the spec to include factory methods (aka "static ctor wrappers" @JeroMiya) with optional parameters |
Beta Was this translation helpful? Give feedback.
-
I really like the recommendation below. Not sure if you are recommending this as an optional approach or as a replacement. I would say go with replacement. I don't think it would be a good experience where sometimes a StackLayout is used and other times HStack is used. The framework needs to provide consistency. At least for MVU - its a fresh start so break ties with past controls now.
|
Beta Was this translation helpful? Give feedback.
-
I updated the discussion item with improved Bind() helpers for MVVM. This eliminates the view type name when binding to a non-default property: Label () .Bind (Label.TextColorProperty, nameof(Option.TextColor)) => Label () .TextColor() .Bind (nameof(Option.TextColor)) |
Beta Was this translation helpful? Give feedback.
-
I updated the discussion item with an additional tool description: Tool: Syntax Colorizer for C# MarkupBecause of the fluent API for views, properties and property values everything is a function. This reduces readability - the editor shows all in a single color: Label () .TextColor() .Blue() Here is a comparison between standard C# colorizing and XAML-like colorizing (using the default XAML font colors in VS 2019 Windows dark theme): We could color views, properties and values the same way that XAML does (some finetuning needed). The colorizer should be driven by codegen on the framework build. The codegen could travel the UI object model, guided by in/exclusion filters (in C#). Since the codegen knows if something is a view / property / value it could include attributes with the generated fluent function. That would also allow manually coded fluent functions to be decorated manually with the same attributes. I would recommend to also make a distinction between container / layout views versus atomic views (to support autoformat). Then create a syntax colorizer addin that mimics the XAML color scheme based on the attributes. The attributes should also indicate which views / properties / values it exposes, so the codegen would not generate helpers for e.g. a manually implemented method that sets 2 properties; the attributes manually added on that helper would suppress automatic generation of 2 property helpers and possibly also of any value helpers for those 2 properties. Also, the colorizer can diminish visibility of noise elements like |
Beta Was this translation helpful? Give feedback.
-
@VincentH-Net Hi, as I previously mentioned, wouldn't it be better if you change your example screenshot with a real 1-to-1 example? As you didn't do, I took the freedom of doing it and including my comments. And weeks after thinking about it, yeah, HStack and VStack continue to be a really bad idea, imagine that now I cannot create a logic for the StackPanel to be Horizontal or Vertical depending on the size of the Screen. I will need to have an IF statement and repeat the whole code. The way it is today I just need to change the Orientation property to Horizontal or Vertical and that's it. Also, as I mentioned before, without TextAlignment in the example you gave, your InteliSense will have 4 more options instead of having 1 (TextLeft, TextRight, TextTop, TextBottom). Today there's only one TextAlignment property in the InteliSense which leads to an Enum with a defined number of 4 options, which then, for e.g. can be used as a number casting (not that it will be used, but it's good to have this flexibility). |
Beta Was this translation helpful? Give feedback.
-
Are VStack and HStack a XAML breaking change? |
Beta Was this translation helpful? Give feedback.
-
Some thoughts:
VStack() {
Image(item.image)
Text(item.description)
} Overall I'm genuinely excited about the proposals in this thread—lots of great changes that move towards a more declarative, expressive future that makes a lot of sense in my opinion. As long as MAUI isn't too encumbered with backwards compatibility issues I think it stands a real chance to rival SwiftUI and even go beyond with the power of cross-platform compatibility. |
Beta Was this translation helpful? Give feedback.
-
SwiftUI is multi apple platform not Cross platform.
If it was believe me I would have considered it
But since it’s not Xamarin.Forms is still the best cross platform framework
to build apps
On Mon, 31 Aug 2020 at 19:48, Mark Fayngersh ***@***.***> wrote:
To be fair, Switch + SwiftUI is also cross platform in that a single
codebase should be able to render on a phone, tablet, watch, desktop, and
TV. I'd argue that MAUI and SwiftUI have similar design concerns, MAUI just
dealing with more platforms than SwiftUI.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#119 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACOGERRVVI5P5UZTJ2BZANTSDPO6FANCNFSM4O477OUQ>
.
--
Thanks,
Matthew Joughin
Founder / CTO
+27 81 529 7169
http://www.adaptableapps.net
|
Beta Was this translation helpful? Give feedback.
-
Wouldn't
be more performant(as far as the underlining implementation is concerned) and readable than
|
Beta Was this translation helpful? Give feedback.
-
Wouldn't
be more performant(as far as the underlining implementation is concerned) and readable than
|
Beta Was this translation helpful? Give feedback.
-
Wouldn't
be more performant(as far as the underlining implementation is concerned) and readable than
And probably more easier to debug since one can set a breakpoint on the delegate. |
Beta Was this translation helpful? Give feedback.
-
Wouldn't
be more performant(as far as the underlining implementation is concerned) and readable than
And probably more easier to debug since one can set a breakpoint on the delegate. |
Beta Was this translation helpful? Give feedback.
-
This C# Markup Spec has been implemented in the The MVVM Community Toolkit is an open-source library in the .NET Foundation created and maintained by Microsoft Engineering in conjunction with community contributions.
|
Beta Was this translation helpful? Give feedback.
-
C# Markup Gen 2 has been implemented, with even more improvements than proposed here. It leverages the power of C# 10, and borrows from Flutter as well: The first prerelease, targeting WinUI 3 and Uno Platform, is available on GitHub at C# Markup 2 and on NuGet CSharpMarkup.WinUI At the moment I am close to releasing C# Markup 2 for WPF (something old, something new...), proving the premise that C# Markup 2 can be applied to the main .NET UI frameworks. A C# Markup 2 implementation for .NET MAUI is certainly possible and something I expect to add; I have held off because MAUI is not done yet, but if there is enough interest I could move that forward. Please let me know with likes / replies on this comment. Thanks! |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Try Comet - C# MVU UI |
Beta Was this translation helpful? Give feedback.
-
C# Markup for MVU and MVVM
Let Maui developers switch and mix MVVM and MVU patterns with minimal markup changes, combining - and improving on - the best of Comet markup and Forms C# Markup.
Example
This is the C# Markup for the Maui MVU example that @davidortinau demonstrated at Build 2020, in versions for MVU and MVVM, both in current markup and in the proposed improved markup:
The obvious remaining difference is in bindings, which makes sense because that is what MVVM / MVU is about.
Complete source with markup helpers and logic: MVU, MVVM.
Updated MVVM Bind() Helpers 20200814
In this blog post I prototyped improved
Bind()
helpers, using fluent subchains:This allows to write:
Instead of
Source of the improved helpers: here
Common Markup Improvements
These changes improve markup readability and conciseness for the current markup in both MVU and MVVM:
Eliminate
new
new VStack { new Label (), new Button () }
=>
VStack ( Label (), Button () )
Eliminate type name repetition
.Color (new Color("#ABCDEF"))
=>
.Color ("#ABCDEF")
.TextAlignment (TextAlignment.Left)
=>
.TextLeft ()
.Color (Color.White)
=>
.Color (White)
.Color().White ()
.Text().Left ()
Format markup
.
and(
- similar to the formatting of property names and values in XAMLMVU Markup Improvements
These changes improve markup readability and conciseness for current C# Markup for MVU, and align the markup to C# Markup for MVVM:
Shorten overly verbose property names
.TextAlignment (TextAlignment.Left)
.LineBreakMode (LineBreakMode.NoWrap)
=>
.TextLeft ()
.LinesNoWrap ()
Single helper for properties that are often set in combination
.Color (Color.White) .Background ("#7258F6")
=>
.Color (White, "#7258F6")
Move UI logic to
.logic.cs
partial class fileLiveStreamSample3Format.logic.cs
:MVVM Markup Improvements
These changes improve markup readability and conciseness for current C# Markup for MVVM, and align the markup to Comet-style MVU markup:
Eliminate property name for assigning (not binding) to the default assignment properties of a view
new Label { Text = "Hi" }
=>
Label ("Hi")
Eliminate view type name when binding to non-default property
Label () .Bind (Label.TextColorProperty, nameof(vm.TextColor))
=>
Label () .TextColor() .Bind (nameof(vm.TextColor))
Eliminate
Children =
andContent =
=>
new StackLayout { Orientation = StackOrientation.Vertical, Children = { ... } }
=>
VStack (...)
API
The above example demonstrates different types of markup improvements and alignments, and how they could be implemented. The full API results from applying this for all views and properties. This spec is a work in progress, intended to grow into the full API.
Codegen
As a consequence of eliminating the
new
keyword, a fluent helper is needed for each property of each view. The original implementation of C# Markup avoided this, mainly to prevent that PR to become too big (impact and size) to merge.As I explained in the original C# Markup PR (see the header CodeGen API would have little value):
MAUI may well include fluent helpers for all views and properties (like Comet); if not, it will be simple to generate them for C# Markup.
Most kinds of helpers demonstrated above could be generated (factories, ctor unwrappers, enum and static values).
Both factory methods and property helpers should be generated from the underlying properties, but with intelligent direction to the code gen so we can:
.Color () .Black ()
) or joining a property name with a value name (.TextLeft ()
) for a property typeFuture
The improvements in this spec are designed to mimic some of these language improvements. This avenue will also be pursued, but this is more long term.
Tooling
These tools would further improve the developer experience of C# Markup:
Syntax Colorizer for C# Markup
Because of the fluent API for views, properties and property values everything in the picture above is a function. This reduces readability. We need to color views, properties and values the same way that XAML does. That colorizer should also be driven by codegen on the framework build. The codegen could travel the UI object model, guided by in/exclusion filters (in C#). Since the codegen knows if something is a view / property / value it could include attributes with the generated fluent function. That would also allow manually coded fluent functions to be decorated manually with the same attributes. I would recommend to also make a distinction between container / layout views versus atomic views. Then create a syntax colorizer addin that mimics the XAML color scheme. The attributes should also indicate which views / properties / values it exposes, so the codegen would not generate helpers for e.g. a manually implemented method that sets 2 properties; the attributes manually added on that helper would suppress automatic generation of 2 property helpers and possibly also of any value helpers for those 2 properties.
Also, the colorizer can diminish visibility of noise elements like
nameof()
( )
and,
- so basically while C# has not been made more markup friendy, just hide the noise. (partly transparent, neutral color). Finally it would be nice if( )
became collapsible for the child list in layouts, with vertical connecting lines in the editor like for{ }
. Then the wait for C# 10+ is more bearableAutoformat C# Markup
Whitespace, group & sort helpers into lines based on category, extract UI logic to partial class. Note that formatting conventions for markup differ from those for logic. This is due to the inherent deeply nested, high cohesive nature of declaratice markup (as opposed to procedural logic). The implementation could use the same attributes that the syntax colorizer uses to identofy functions as a container, view, property name or property value
Convert XAML to C# Markup
So the many existing examples in the internet can be used directly by devs that choose to use C# Markup.
Backward Compatibility
The C# Markup API only adds to underlying API's; it does not introduce any breaking changes to existing code.
There may be some breaking changes in the C# Markup helpers for Xamarin Forms, but since that feature is currently experimental this is acceptable. Best would be to only move C# Markup in Forms out of Experimental after this spec has been completed and a backport into Forms has been done, to make markup C# compatible when migrating from Forms to MAUI.
All platforms are supported. The API and it's implementation are platform agnostic.
Difficulty : [medium]
Examples of all types of helpers are already implemented. The main effort would be implementing the codegen with overrides and direction, implementing the tools, and adding full unit tests.
Beta Was this translation helpful? Give feedback.
All reactions