I’m a latecomer to Microsoft’s user interface technologies. I never used Windows Framework (WPF) on top of .net and I never used Silverlight on the web. The last year was my first taste of these tools through the XAML framework that is part of the “Universal Windows Platform” (UWP) – that is, the Windows 10 user interface layer (and Win8).
XAML has steadily evolved since the WPF days, and it took a little while to really understand the different major eras of the technology, especially since the UWP flavour of XAML strips out some of the older syntaxes in the name of efficiency on mobile platforms, better error checking at compile-time and code readability and ease-of-use. The technology’s old enough that much of the Google search hits and StackOverflow results are not applicable on the modern UWP platform.
My Tips
So what were a few of my first lessons when using XAML on UWP?
- Prefer x:Bind to Binding
- Responsive Design
- Expressions in x:Bind calls
- Prefer XAML or ViewModels over code-behind
- Use XAML Designer
- Use Commands, get disabling for free
- More to read
Prefer x:Bind to Binding
The main reason: x:Bind gives you compile-time checks and better performance. Be aware of the small differences:
- OneTime binding by default instead of OneWay binding
- Binding path is the framework element (control) not the DataContext (view model)
Responsive Design
Combining VisualState and AdaptiveTriggers is quite a powerful way to hide/show, relocate
or resize different parts of a user interface. The only pain point with this is that you can’t alter a style in response to a window size change (or other trigger), only modify the properties of each individual control explicitly. I tried one workaround: making a dummy ViewModel DependencyProperty that gets updated by the trigger, and then binding the styles to that property. This works, but can’t be previewed in XAML Designer, which I found useful. Instead, I created a dummy invisible control, set the triggers to modify that control,
and then bound the style to the invisible control’s properties. This works both “live” and in XAML Designer, even updating live as the XAML Designer target device resolution is changed.
I found this reference useful on this subject.
Expressions in x:Bind calls
In the Windows Anniversary Edition (Fall 2016), Microsoft added some great new
capabilities that allow some really nice, clean syntax in XAML, without the use of converters. You can now do the following, provided that your ViewModel class implements the xAnd and xMult functions:
<Button Visibility="{x:Bind Vm.xAnd(Vm.IsVisible, Vm.IsStateOn)}" Width={x:Bind Vm.xMult(Vm.Width, 0.5)}">
Notice a few things here:
- despite having only boolean ViewModel properties, we can do a boolean operation on them and auto-convert the boolean result to a Visibility type, without any converters. Always prefer x:Bind expressions to convertors.
- you can write single or multi-operand functions. (In Visual Studio 2015, XAML Designer didn’t support multi-operand, but Visual Studio 2017 added full support.)
- you can embed constants and work with integer or double types
- you cannot nest functions or overload function names
This opens up a huge number of ways of writing cleaner and more readable XAML code.
Prefer XAML or ViewModels over code-behind
Code-behind (the C++ or C# source for the XAML View) should be a last resort. It’s already challenging enough to keep track of a XAML file and a ViewModel; burying logic in the View divides the reader’s attention even further.
There are situations where something is part of the “View” rather than the ViewModel: for example, a property that has nothing to do with the Model and only exists to enable the XAML implementation – such as a control spacing property or some such. This is perhaps the one case where View code-behind might make sense. But even there – if you can do it in XAML, that’s usually better.
Use XAML Designer
XAML Designer – the Visual Studio built-in visual editor for XAML – hasn’t had much love in a long time. Many things are broken, and it can’t handle some useful things. And if you don’t put effort into setting up the XAML correctly, a page/control can be totally illegible.
Why prefer XAML Designer?
- Compile times for XAML – especially include C++ code generations – are brutal. You can iterate much faster if you avoid the compile.
- XAML Designer can do a lot of responsive design without even doing a build
- It’s a much quicker way to learn XAML, and even explore events/properties that you weren’t aware of.
How to make XAML Designer more useable:
- Ensure your UserControl has d:DesignWidth and d:DesignHeight settings
- Ensure ViewModel has a default zero-parameter constructor
- Bind the ViewModel to the DataContext in XAML, not in code-behind. e.g.,
<Page.DataContext><local:MyViewModel/></Page.DataContext>
- Use AdaptiveTriggers for responsive design, rather than ViewModel or View logic
- Add FallbackValues for x:Binds that XAML Designer can’t understand. They’ll only be used at design-time.
- Don’t bother trying to get “real” design-time data working via d:DesignInstance; it doesn’t work with a 64-bit application or with x:Bind.
(In theory, you’re supposed to be able to writed:DataContext="{d:DesignInstance local:MyViewModel, IsDesignTimeCreatable=True}"
and get XAML Designer to actually construct an instance of your real ViewModel, and query its values to get the “right” default values when you’re designing. But… it clearly hasn’t been maintained in a long time, it only works in 32-bit and with Binding.)
Use Commands, get disabling for free
Why use Commands as the mechanism for the XAML/View to modify the view model’s state?
For one, it’s often less code: XAML can directly bind to the viewmodel’s command property, instead of XAML having a click event, and the View’s click event codebehind then modifies the viewmodel.
But also: you can set it up so that when the command “cannot execute”, then the user interface will automatically grey out and prevent clicking. The details:
- the command should be a ViewModel property, it should implement a CanExecute method, and that CanExecute method should issue a PropertyChanged notification
- the XAML control should be bound to the command
- when the command cannot be executed, the XAML control will then enter the “Disabled” visual state.
More to Read
- Best Practices for Developing UWP Applications: a bit C#-centric, but some good tips
- Refer to the Right documentation: surprisingly hard. Google tends to bring up .NET docs rather than UWP, and Visual Studio 2013/15 over the latest 2017 docs.
- Use the Live Visual Tree and Live Property Explorer
- Use templated controls over user controls
- If you have troubles with Visual Studio stability (or speed), try disabling XAML Designer