Closed YegorStepanov closed 2 years ago
This is because ContentDialog.Content
takes an object
; the repro code passes in the markup object, which is displayed as it's type name:
You can use .UI
to pass in the UI object contained in the markup object:
ContentDialog(
StackPanel(TextBox(Text: "TextBox")).UI
)
and then the ContentDialog displays as intended:
You only have to explicitly pass in .UI
where an object
is expected; all markup objects have an implicit operator to automatically return .UI
where a UIElement
(or derived class) is expected.
Is it possible to set object content through method call?
No, that would not be safe because markup objects are performance optimized to have no more than one instance per type.
This means that there is no GC pressure from markup object instances - the only object instances created are the real UI objects. The single markup object instance is what is used to form the fluent API chain.
However that requires that child controls are not created in the fluent API chain that sets properties on the parent control. In the case that the child control is of the same type as the parent control, that would break the chain.
This is why the C# Markup API is shaped so that child controls are passed in as parameters to the start of a parent control chain. That ensures that the child control chains are fully evaluated before the parent control chain starts.
Also see this explanation in the source
What's about performance? I should add to Treeview 10K elements sometimes. Is the performance impact near zero (in comparison with XAML)? What performance scenarios do we need to be careful with?
I have not measured this yet, but C# Markup should indeed be as fast or faster than XAML.
C# Markup creates no objects in addition to the UI object itself and every property is a single assignment without the need for a lookup or parse.
On Windows, the XAML engine is written in C++ which is faster than C#, but it has to do a lot more work to parse XAML and create a UI object and set all it's properties.
In Uno Platform, the XAML is converted to C# at build time via code generation; this generated code is not smaller than the C# Markup code. In the case of the webassembly target of Uno Platform, C# Markup could be slower than the C# code that Uno Platform generates from XAML - I recall that Uno Platform optimized that code to avoid C# generics, which at some point in time were slower in webassembly. However that may have changed - C# webassembly is constantly being optimized. A webassembly performance test is on my wish list.
Thank you for freeing us from XAML❤️
You are welcome! I love this type of feedback - spread the word!
@VincentH-Net You have done a wonderful job instead of MS!
Content
parameter?ContentDialog(Title: "Title",
Content:
StackPanel().UI
It's possible to get .UI
on your side? This clutters up the code a bit.
It turns out I can never use the property's ctor with children, right? i.e. the constructors for collections
StackPanel(Padding: Thickness(0))
What about adding optional Content parameter?
One of the nice things about C# Markup when compared to XAML is how easy it is to add your own convenience API's.
What you describe above you can add as a 2-liner to your own extension methods class, e.g.:
MarkupExtensions.cs
:
public static ContentDialog ContentDialog(string Title, DependencyObject Content)
=> Helpers.ContentDialog(content: Content?.UI).Title(Title);
I expect to add helpers like this for common cases where .UI is needed. If you encounter more cases, best would be to create an issue
It turns out I can never use the property's ctor with children, right? i.e. the constructors for collections
The generated factory method for views that can have children only takes one params
collection, but nothing prevents you from adding overloads with more parameters before that e.g.:
public static StackPanel StackPanel(UI.Thickness Padding = default, params UI.UIElement[] Children)
=> Helpers.StackPanel(Children).Padding(Padding);
...
StackPanel(Padding: new UI.Thickness(), TextBox(Text: "TextBox"), Button("Click Me"))
However, you have to be careful not to introduce ambiguities and parameters only work well for properties that take primitive types. The extension method way to set properties allows to add nice convenience overloads, e.g.:
StackPanel ()
.Padding (0)
.Padding (2, 4)
.Paddings (top: 6)
.Padding().Bind (...)
After some experimentation, I found that having the children of a layout first and the properties at the end nicely moves the less important details out of the way. In my experience it makes the whole markup more quickly scannable
@VincentH-Net
Adding ContentDialog(DependencyObject Content)
hides the default Helpers.ContentDialog()
, so I need to create HelpersOfHelpers :)
class and call it without using static
.
I don't know how to name the issue, so I ask it here.
If the default controls generate by SourceGenerators, I suggest adding [Markup(typeof(MyControl))]
that generates Markup's methods automatically. It seems to be possible to implement it.
There is a C#-only part of WinUI3 - WindowsCommunityToolkit
, that as I understand, not supporting.
VisualStateManager
?Invoke
for setting simple properties like ScrollViewer.HorizontalScrollMode
?🎄🎄🎄
@YegorStepanov wrote:
Adding
ContentDialog(DependencyObject Content)
hides the defaultHelpers.ContentDialog()
, so I need to createHelpersOfHelpers :)
class and call it withoutusing static
.
Defining your own additional helpers in the app works well, also with using static, as long as you take care with the overload method signatures. It helps to not give parameters default values but instead define separate overloads for different parameter combinations. This avoids ambiguities / hiding.
In your case, when you add these overloads in MarkupExtensions.cs
in the example solution:
public static ContentDialog ContentDialog(string Title, DependencyObject Content)
=> Helpers.ContentDialog(content: Content?.UI).Title(Title);
public static ContentDialog ContentDialog(DependencyObject Content)
=> Helpers.ContentDialog(content: Content?.UI);
And you add global using static WinUICsMarkupExamples.MarkupExtensions;
to GlobalUsings.cs
(or a regular using static
to the page),
All of these variations work - nothing is hidden:
ContentDialog(
"Title 1",
TextBlock("This is content 1")
) .PrimaryButtonText("PrimaryText")
ContentDialog(
TextBlock("This is content 2")
) .Title ("Title 2")
.PrimaryButtonText ("PrimaryText")
ContentDialog("This is content 3")
.Title("Title 3")
.PrimaryButtonText("PrimaryText")
ContentDialog()
.Title("Title 4")
.PrimaryButtonText("PrimaryText")
Let me know if this works for you. NJoy!
@VincentH-Net You are right, it's working.
I haven't created a new class and simply added these methods to UI-class.
screenshot
![image](https://user-images.githubusercontent.com/19392641/146653393-fbccf459-d288-4e72-8514-1871032d993f.png)Steps to reproduce:
Add to example
FlutterPage.cs:
FlutterPage.logic.cs:
App.xaml.cs:
Another questions:
Is it possible to set
object content
through method call?Something like:
instead of
What's about performance?
I should add to Treeview 10K elements sometimes. Is the performance impact near zero (in comparison with XAML)? What performance scenarios do we need to be careful with?
Thank you for freeing us from XAML❤️