⬆️ ⬇️

Alternative WPF language JAML = XAML - XML ​​+ JSON

The New Year came on the court, and the thought never came out that XAML could be better. And to be better, he needs to stop being. Thus, the idea was born to write an alternative to a horrible and horrible XAML: without <Setter.Value> , without {Binding Path=Name, RelativeSource={RelativeSource AncestorType={x:Type Button}}, Converter={StaticResource Converter}} <Setter.Value> {Binding Path=Name, RelativeSource={RelativeSource AncestorType={x:Type Button}}, Converter={StaticResource Converter}} , without FirstValueEqualsToSecondValueOrThirdValueEqualsNullConverter , without <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> , without <MultiDataTrigger> <MultiDataTrigger.Triggers> <DataTrigger> <DataTrigger.Binding> <MultiDataBinding>... without xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" , without everything of this junk, from the writing of which for the tenth time there is the desire to gently stroke the computer with a stool and the distant memories are remembered related relatives of WPF developers.







Features:



Sounds great? And it looks like this:

')

 _={ $: 'Window root', Resources: [{ $: 'Style MyButtonStyle Button', set: { Background: 'Red', Foreground: 'Green' }, on: { '{=this.IsMouseOver}': {set: { Background: 'Yellow', Foreground: 'Blue' }} } }], _: [{ $: 'Grid', RowDefinitions: [ { Height: '*' } ], ColumnDefinitions: [ { Width: '*' } ], _: [{ $: 'Button btnPressMe', Content: 'Press me!', Style: '{@MyButtonStyle}' }] }] } 


When / if I finish a language, it will look something like this:



 Window root { Resources: [ Style MyButtonStyle Button { set: { Background: Red, Foreground: Green }, on: { {=this.IsMouseOver}: {set: { Background: Yellow, Foreground: Blue }} } } ], Grid { RowDefinitions: [ { Height: * } ], ColumnDefinitions: [ { Width: * } ], Button btnPressMe { Content: 'Press me!', Style: {@MyButtonStyle} } } } 




First, a little about sad things: this is not a library that can be put into a project so that it becomes immediately good. This is a proof-of-concept . The meaning of writing a library is to prove that it can be written, that the gates of Hell do not open, that working code can be written in a reasonable time.



It took a little more than a week to develop (yes, I spend the New Year holidays like this). I wrote the githaba documentation yesterday, today I am writing this article. Therefore, the following is not attached to the library and cannot be attached: support for types other than built-in frameworks, imputed and perceived human error messages, futilitylessness, at least in simple cases, unit tests.



Now our pragmatic half of readers has resolutely closed the tab in the browser. With idealists and dreamers - we continue.



For those who want to mess up little hands and do not hesitate to read documentation in curved English, I ask you to visit the githab . There in ReadMe and Wiki all the necessary information: the installation process, syntax, and more. Here is the same, but more briefly.



About weirdness

You may have noticed that the file starts with "_ =". This bogus assignment convinces Visual Studio that the file is JavaScript itself and not some kind of JSON unknown to nature (unfortunately, even in 2013 the JSON editor does not exist in nature, such are the cases). We are losing the addition of the code (IntelliSense), and so, having rummaged through the settings ( Tools> Options> Text Editor> File Extension ), we can get backlighting and checking brackets at least at the Script Editor level.



JSON is rather limited compared to XML: its “objects” have neither “type” nor “content”. Therefore, the type (that in XML after <) is specified by the money property "$", and the inside (that in XML between ) is specified by the property "_". This is a temporary inconvenience, I am thinking about changing the language. Therefore xaml
 <Button Visibility="Visible"> <Button.ToolTip> <TextBlock Text="Tool tip text"/> </Button.ToolTip> <TextBlock Text="Button text"/> </Button> 
becomes JAML
 { $: 'Button', Visibility: 'Visible', Tooltip: { $: 'TextBlock', Text: "Tool tip text" }, _: { $: 'TextBlock', Text: "Button text" } } 


Even in the money property, you can lay information about the identifier (x: Name / x: Key), visibility (x: ClassModifier / x: FieldModifier), and in the case of styles and triggers also "implicit" keys (TargetType / DataType). Full syntax (square brackets - optional):
 [visibility] typeName [identifier [implicit identifier]] 
Examples:
 Button Button btnCancel private Button btnCancel DataTemplate {~Button} DataTemplate MyButtonTemplate {~Button} 


As already mentioned, the money property can be omitted if the type is “obvious” (that is, the property in which we put the object, reports that you can put in it: T for IEnumerable <T>, ControlTemplate for ControlTemplate).



About markup extensions

JAML contains a huge number of syntax abbreviations for embedded “markup extensions”, but at the root of all abbreviations there is a very small (and, I hope, easily remembered) number of ideas:

ReductionValue
=

Calculate value dynamically (for the most part, banding)
@

Get value by key (mostly resources)
~

Get type
ref

Get item by name
And now - the full list.

ReductionOne call T4 turns into inelegant ...
{@Key}

{StaticResource Key}

{@=Key}

{DynamicResource Key}

{~TypeName}

{x:Type TypeName}

{static.TypeName.Property}

{x:Static TypeName.Property}

null

{x:Null} (native JSON type)

{tpl.Property}

{TemplateBinding Property}

{=PropertyPath}

{Binding PropertyPath}

{=}

{Binding}

{=ref.controlName.PropertyPath}

{Binding PropertyPath, ElementName=controlName}

{=this.PropertyPath}

{Binding PropertyPath, RelativeSource={RelativeSource Self}}

{=tpl.PropertyPath}

{Binding PropertyPath, RelativeSource={RelativeSource TemplatedParent}}

{=~TypeName.PropertyPath}

{Binding PropertyPath, RelativeSource={RelativeSource AncestorType=TypeName}}

{=@{...}.PropertyPath}

{Binding PropertyPath, Source={...}}

{= ${=Prop1} + 42 + ${=Prop2} }

<Binding> or <MultiBinding> (see below)

You can taste expressions with spaces: {= ref.controlName.PropertyPath } (but do not overdo it: after all, there is a mixture of elephant-like regulars with IndexOf crutches flavored with System.Xaml pickled inside).



About expressions and converters

Now you can write C # expressions in buyindings. The sub-purchases of the multi-baing being created (or the one if the sub-baiting is one) are enclosed in the brackets ${...} . Behind the scenes, expressions become classes that implement the IValueConverter and IMultiValueConverter . We must not forget that objects come to converters, that is, usually you cannot do without type conversion (automatic definition of expression types is in the plans). On the contrary, checks for DependencyProperty.UnsetValue not needed - the check is added by itself.



Get to the point. Here is an example in which the value of the binding is IsMouseOver == IsMouseDirectlyOver :
 {= (bool)${=this.IsMouseOver} == (bool)${=this.IsMouseDirectlyOver} } 
This is converted to the following XAML:
 <MultiBinding Converter="{x:Static my:MainWin._jaml_MainWindowConverter1}"> <Binding Path="IsMouseOver" RelativeSource="{RelativeSource Mode=Self}" /> <Binding Path="IsMouseDirectlyOver" RelativeSource="{RelativeSource Mode=Self}" /> </MultiBinding> 


Separating a C # expression from the markup extension arguments is a daunting task for regular expressions, and the only thing they can do is to check the pair of curly braces. Actually, the expression ends on the first comma after the last sub-binding. If this behavior does not suit you, then you can set the end of the expression explicitly with ${} . For example:
 {= string.Format("Visibility = {0}, Name = {1}", ${=this.Visibility}, param) ${}, ConverterParameter={@Name} } 
You are also unlucky if the curly braces are unpaired (such as string.Format("{{{0}", value) - I don’t know why, but you never know) - here you can use escaping sequences.



If the expression is too complicated, then you can either write a static method in the code-behind, or put the logic in another class altogether - to your taste.



About setters in styles and triggers

These are recorded as normal properties of an object in the "set" property.
 { $: 'Style MyButtonStyle', set: { Width: 16, Height: 16, Background: 'Red' } } 
If you need to set Setter.TargetName , use the syntax ref.controlName.PropertyPath .
 set: { 'ref.btnCancel.Width': 16, 'ref.btnCancel.Height': 16, 'ref.btnCancel.Background': 'Red' } 
If you hate brackets around property names, then you can write dollars instead of dots: ref$controlName$PropertyPath (dollar in JavaScript and JSON is considered a full-fledged symbol, in JAML it is replaced with a dot). The same method works with attached properties.



Triggers are written to the “on” object. Property name is binding. The value is the inside of the trigger (including the “set” object). You can write binding expressions as described above. A trigger is considered triggered if it takes a boolean value of True. For example:
 { $: 'Style CheckBox', on: { '{=this.IsChecked}': { set: { Background: 'Red', Foreground: 'Green' } }, '{= (bool)${=this.IsMouseOver} && (bool)${=this.IsChecked} }': { set: { Background: 'Yellow', Foreground: 'Blue' } } } } 
The syntax ref.controlName.PropertyPath in template triggers also works.



Well, for a snack, the Grid$ property takes one to four integers and turns them into Grid.Row, Grid.Column, Grid.RowSpan, Grid.ColumnSpan, respectively. You write Grid$='2 3' - you get Grid.Row="2" Grid.Column="3" .



Links



Thanks to the authors Json.NET, T4MultiFile, as well as to the Hindus, whose code I picked out from the System.Xaml giblets, to parse the "markup extensions."



What's next?

Now it is not clear what to do with this experiment, because there are much more urgent things than changing the world (you feel like eating). Most likely, after a while I will return to the project and bring it to the state where I can use it in my real project. In the meantime, I am interested in the opinion of others: is it necessary? will anyone use? need to rewrite from scratch, or you can bring to mind the existing?



Perhaps the biggest inconvenience with their language is that there is no support at all for the Resharpers. XAML refactoring sucks and smacks of resharper, but he does have some. Plus, six years after the appearance of WPF in Resharper, they still gave birth to at least some work with DataContext and other things. If it took six years for the official XAML to be on all sides, then there’s no reason to think about some kind of left tongue. And you probably don’t attach anything to the side through the plugin. In general, you have to write JAML, but constantly look into XAML. However, as for me, it's still better.



In general, what thoughts? How do you think?

Source: https://habr.com/ru/post/165943/



All Articles