Alternative WPF language JAML = XAML - XML + JSON
The New Year was approaching in the yard, and the idea that XAML could be better did not come out of my head. And in order to be better, he needs to stop being. So the idea was born to write an alternative to the nightmarish and terrible XAML: without
Features:
Sounds great? And it looks like this:
When / if I finish the language, it will look something like this:
First, a little about sad things: this is not a library that you can put in a project so that it immediately becomes 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 will not open, that working code can be written in a sane time.
It took a little more than a week to develop (yes, yes, this is how I spend the New Year holidays). Yesterday I wrote documentation on a github, today I am writing this article. Therefore, the following is not and cannot be attached to the library: support for types other than those built into the framework, sane and perceived error messages by people, carelessness, even in simple cases, unit tests.
Now our pragmatic half of the readers resolutely closed the tab in the browser. With idealists and dreamers - we continue.
For those who want to get dirty little hands and do not disdain documentation in English, I ask you to visit the github . There in ReadMe and Wiki all the necessary information: the installation process, syntax and more. Here is the same, but more briefly.
Compared to XML, JSON is rather limited: its "objects" have neither "type" nor "content". Therefore, the type (that in XML after <) is defined by the monetary property "$", and the interiors (that in XML between
In addition to the monetary property, you can put 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):
As already mentioned, a monetary property can be omitted if the type is "obvious" (that is, the property in which we put the object indicates that it can be put in it: T for IEnumerable <T>, ControlTemplate for ControlTemplate).
And now the complete list.
You can dilute the expressions with spaces to your taste:
Get to the point. Here is an example in which the binding value is
Separating an expression in C # from 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 specify the end of the expression explicitly with
If the expression is too complicated, then you can either write a static method in code-behind, or move the logic to another class in general - to taste.
Triggers are written to the on object. The name of the property is binding. The value is the inside of the trigger (including with the set object). You can write expression bindings as described above. A trigger is considered triggered if it takes a Boolean value of True. For example:
Well, a property for appetizer
Thanks to the authors of Json.NET, T4MultiFile, as well as to the Indians whose code I picked from the guts of System.Xaml to parse "markup extensions."
Perhaps the biggest inconvenience with your language is that there is no support for all sorts of Resharper in principle. XAML refactoring with a resolver sucks and smacks, but at least there is one. Plus, six years after the appearance of WPF, the resharper did give birth to at least some work with DataContext and so on. If six years went into the official XAML from all sides, then about some kind of left-wing language - you can’t even dream of it. And on the side through the plugin, probably, nothing can be attached. 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 like the idea?
<Setter.Value>
, without {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 all this junk, from the writing of which there are urges to gently stroke the computer with a stool and recall the distant Hindu relatives of WPF developers.Features:
- Warm JSON tube-like syntax without quotes instead of diabolical XML angle brackets.
- Short and sane syntax for markup extensions: kilometer once-written-then-read-scary-bindings
{Binding Path=Name, RelativeSource={RelativeSource AncestorType={x:Type Button}}, Converter={StaticResource Converter}}
turn into almost assignments{= ~Button.Name, Converter={@Converter} }.
- Kosher expressions in C # to replace the non-kosher converters:
{= ${=Property1} == ${=Property2} || ${=Property3} == null }
. - Death of the “elemental” record of properties -
<Setter.Value>
go into oblivion. - A furious repetition of repetitions is deprecated: if you can only put it somewhere
ColumnDefinition
, do not repeat it ten times. - Setters and triggers cease to be multi-letter serialized crutches: setters look like property assignments, triggers look like conditions.
- Death to the duplication of ten "clr-namespace" with the names of the cathedrals and other evil spirits.
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 the 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 you can put in a project so that it immediately becomes 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 will not open, that working code can be written in a sane time.
It took a little more than a week to develop (yes, yes, this is how I spend the New Year holidays). Yesterday I wrote documentation on a github, today I am writing this article. Therefore, the following is not and cannot be attached to the library: support for types other than those built into the framework, sane and perceived error messages by people, carelessness, even in simple cases, unit tests.
Now our pragmatic half of the readers resolutely closed the tab in the browser. With idealists and dreamers - we continue.
For those who want to get dirty little hands and do not disdain documentation in English, I ask you to visit the github . There in ReadMe and Wiki all the necessary information: the installation process, syntax and more. Here is the same, but more briefly.
About oddities
You may have noticed that the file begins with "_ =". This dummy assignment convinces Visual Studio that the file is the most JavaScript, and not some kind of JSON unknown to nature (unfortunately, even in 2013, the JSON editor does not exist in nature, such things). We are already losing the code completion (IntelliSense), and so, digging into the settings ( Tools> Options> Text Editor> File Extension ), we can get highlighting and checking the brackets at least at the Script Editor level .Compared to XML, JSON is rather limited: its "objects" have neither "type" nor "content". Therefore, the type (that in XML after <) is defined by the monetary property "$", and the interiors (that in XML between
и
) - property "_". This is a temporary inconvenience, I am thinking about changing the language. Therefore, XAML<ButtonVisibility="Visible"><Button.ToolTip><TextBlockText="Tool tip text"/></Button.ToolTip><TextBlockText="Button text"/></Button>
takes on the look of JAML {
$: 'Button', Visibility: 'Visible',
Tooltip: { $: 'TextBlock', Text: "Tool tip text" },
_: { $: 'TextBlock', Text: "Button text" }
}
In addition to the monetary property, you can put 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, a monetary property can be omitted if the type is "obvious" (that is, the property in which we put the object indicates that it can be put in it: T for IEnumerable <T>, ControlTemplate for ControlTemplate).
About markup extension
JAML contains a huge number of syntax shortcuts of the built-in “markup extensions”, but at the root of all shortcuts there is a very small (and, I hope, easy to remember) number of ideas:Abbreviation | Value |
---|---|
= | Compute value dynamically (for the most part, binders) |
@ | Get value by key (for the most part resources) |
~ | Get Type |
ref | Get item by name |
Abbreviation | In a single call, the T4 turns into an inexhaustible ... |
---|---|
{@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) |
{= ref.controlName.PropertyPath }
(but do not overdo it: after all, there is a mixture of elephant-like regulars with crutches IndexOf inside, seasoned with System.Xaml gutted guts).About expressions and converters
Now in bindings you can write expressions in C #. The sub-bindings of the created multi-binding (or the binding, if there is only one sub-binding) are enclosed in brackets${...}
. Behind the scenes, expressions turn into classes that implement interfaces IValueConverter
and IMultiValueConverter
. Do not forget that objects come to converters, that is, usually you can’t do without type conversion (automatic determination of expression types is in the plans). On the contrary, no checks DependencyProperty.UnsetValue
are needed - the check is added by itself. Get to the point. Here is an example in which the binding value is
IsMouseOver == IsMouseDirectlyOver
:{= (bool)${=this.IsMouseOver} == (bool)${=this.IsMouseDirectlyOver} }
This converts to the following XAML: <MultiBindingConverter="{x:Static my:MainWin._jaml_MainWindowConverter1}"><BindingPath="IsMouseOver"RelativeSource="{RelativeSource Mode=Self}" /><BindingPath="IsMouseDirectlyOver"RelativeSource="{RelativeSource Mode=Self}" /></MultiBinding>
Separating an expression in C # from 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 specify 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 (like string.Format("{{{0}", value)
- I don’t know why, but you never know) - here you can use the escape sequence. If the expression is too complicated, then you can either write a static method in code-behind, or move the logic to another class in general - to taste.
About setters in styles and triggers
They are recorded as ordinary properties of an object in the "set" property.{
$: 'Style MyButtonStyle',
set: {
Width: 16, Height: 16,
Background: 'Red'
}
}
If you need to specify Setter.TargetName
, then 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, you can write dollars instead of dots: ref$controlName$PropertyPath
(a dollar in JavaScript and JSON is considered a full-fledged character, in JAML it is replaced by a dot). The same method works with attached properties. Triggers are written to the on object. The name of the property is binding. The value is the inside of the trigger (including with the set object). You can write expression bindings 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, a property for appetizer
Grid$
- it takes from one to four integers and turns them into Grid.Row, Grid.Column, Grid.RowSpan, Grid.ColumnSpan, respectively. If Grid$='2 3'
you write , you get Grid.Row="2" Grid.Column="3"
.References
- Jaml project on GitHub , ReadMe attached.
- Jaml Wiki on GitHub , instructions for use and examples attached.
Thanks to the authors of Json.NET, T4MultiFile, as well as to the Indians whose code I picked from the guts of System.Xaml to parse "markup extensions."
What's next?
Now it is not clear what to do with this experiment, because there are much more urgent matters than changing the world (I want to eat something). Most likely, after a while I will return to the project and bring it to a state where I can use it in my real project. In the meantime, I'm interested in the opinion of others: is it necessary? will anyone use? need to be rewritten from scratch, or can you bring to mind what is available?Perhaps the biggest inconvenience with your language is that there is no support for all sorts of Resharper in principle. XAML refactoring with a resolver sucks and smacks, but at least there is one. Plus, six years after the appearance of WPF, the resharper did give birth to at least some work with DataContext and so on. If six years went into the official XAML from all sides, then about some kind of left-wing language - you can’t even dream of it. And on the side through the plugin, probably, nothing can be attached. 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 like the idea?
Only registered users can participate in the survey. Please come in.