I was working with the WPF project, converted to UWP in this article, in order to check usage of the new MVVM toolkit, explained in my last article and use the bindings with x:Bind, that are not supported in WPF (in fact, they are, but not natively. You can add the CompiledBindings.WPF package to your WPF project to have them).
I converted it, changed the Views to use the x:Bind bindings and ended up with this project.
There are some notes, here with the conversion:
- This is an UWP project and, by default, uses C# 7. I wanted to use C# 9 t get the source generator goodies in the MVVM Toolkit. So I upgraded the language version using the
LangVersion
tag in the project. To do this, open the csproj file (you must unload the project and edit the file) and add thisPropertyGroup
:
<PropertyGroup>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
- I removed the details view, by adding a ContentControl with a template with the Customer details:
<ContentControl Grid.Row="2" Content="{x:Bind master.SelectedItem, Mode=OneWay}"
ContentTemplate="{StaticResource DetailTemplate}" Margin="5" />
<Page.Resources>
<c:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<DataTemplate x:Key="DetailTemplate" x:DataType="customerlib:Customer">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="600" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Customer Id:" Grid.Column="0" Grid.Row="0" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Name="customerIdTextBox"
Text="{x:Bind CustomerId, Mode=TwoWay}" VerticalAlignment="Center" />
<FontIcon Grid.Column="2" FontFamily="Segoe MDL2 Assets" Glyph=""
Margin="20,0" Visibility="{x:Bind IsVip, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBlock Text="Company Name:" Grid.Column="0" Grid.Row="1" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Name="companyNameTextBox"
Text="{x:Bind CompanyName, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Contact Name:" Grid.Column="0" Grid.Row="2" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="2" Margin="3" Name="contactNameTextBox"
Text="{x:Bind ContactName, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Contact Title:" Grid.Column="0" Grid.Row="3" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="3" Margin="3" Name="contactTitleTextBox"
Text="{x:Bind ContactTitle, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Address:" Grid.Column="0" Grid.Row="4" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="4" Margin="3" Name="addressTextBox"
Text="{x:Bind Address, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="City:" Grid.Column="0" Grid.Row="5" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="5" Margin="3" Name="cityTextBox"
Text="{x:Bind City, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Postal Code:" Grid.Column="0" Grid.Row="6" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="6" Margin="3" Name="postalCodeTextBox"
Text="{x:Bind PostalCode, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Region:" Grid.Column="0" Grid.Row="7" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="7" Margin="3" Name="regionTextBox"
Text="{x:Bind Region, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Country:" Grid.Column="0" Grid.Row="8" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="8" Margin="3" Name="countryTextBox"
Text="{x:Bind Country, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Phone:" Grid.Column="0" Grid.Row="9" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="9" Margin="3" Name="phoneTextBox"
Text="{x:Bind Phone, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Fax:" Grid.Column="0" Grid.Row="10" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="10" Margin="3" Name="faxTextBox"
Text="{x:Bind Fax, Mode=TwoWay}" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</Page.Resources>
- The bindings for the DataGrid columns couldn’t be replaced by x:Bind. I could do it by replacing the DataGridTextColumn with DataGridTemplateColumn like in:
<controls:DataGridTemplateColumn Header="Customer ID">
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="customerlib:Customer">
<TextBlock Text="{x:Bind CustomerId, Mode=TwoWay}" />
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
But I thought it didn’t worth the extra code and I left it with the bindings. If you find some way to replace the Bindings in the DataGridTextColumn for x:Bind, please leave it in the comments.
You would ask, why change the Binding for x:Bind. There are some good reasons for that:
- x:Bind is resolved at compile time, while Binding is resolved at runtime, so it should make your code run faster
- By being resolved at compile time, you know in advance if some binding works or not, while with Binding, you will have something that doesn’t work and you don’t know why
- You can bind to functions in your ViewModel, thus removing the need of some converters (but not all of them)
On the other side, you have to determine explicitely the data you’re binding with x:Bind:
- The data templates must have a DataType
- You cannot use the DataContext property of the View, as it’s an Object and, as so, doesn’t have the properties to bind
- You must bind to properties/fields in the View. That’s why I created this code in Mainpage.xaml.cs:
private MainViewModel _mainVm;
public MainPage()
{
this.InitializeComponent();
_mainVm = App.Current.MainVM;
}
- You must declare explicitely which property/field you are binding, like in
ItemsSource="{x:Bind _mainVm.Customers}"
With that, we can run the project and this is what you get:
It’s an empty window, the customer data is not there. I tried to figure out what was happening and I set a breakpoint in CustomerRepository.GetCustomersAsync:
Everything is good, there are 91 customers, but none of them is shown in the Datagrid. Why is that? After checking and rechecking the data and the bindings, I remembered one difference between Binding and x:Bind. While Binding uses the OneWay most of the time, x:Bind uses OneTime.
It seems to be a small difference, but it bytes you when you have to deliver some code 😦. OneWay is a binding from the source (the class) to the destination (the view), but every time the class changes its value and raises the PropertyChanged event, the view reflects the change. OneTime is there for performance reasons: it’s set and forget. When the view is first rendered, it will check the binding, set the value and forget. The changes on the class won’t affect the view.
As we are setting the customer’s list in an asynchronous way, when the view is rendered, there is no data available, so it doesn’t show anything. When the data is set, all changes aren’t propagated to the view and nothing is shown. Fortunately, this is a simple change:
<controls:DataGrid AutoGenerateColumns="False" x:Name="master" Grid.Row="1"
ItemsSource="{x:Bind _mainVm.Customers, Mode=OneWay}" SelectedItem="{x:Bind _mainVm.SelectedCustomer, Mode=TwoWay}">
Now, our data shows fine, but when we select a customer, we get this:
Wait a minute, where are our bindings? We just got the first one and nothing else? What is happening here? Is x:Bind broken? Let’s see. I started to change my x:Bind to Binding and see the effect:
<TextBlock Text="Company Name:" Grid.Column="0" Grid.Row="1" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Name="companyNameTextBox"
Text="{Binding CompanyName, Mode=TwoWay}" VerticalAlignment="Center" />
<TextBlock Text="Contact Name:" Grid.Column="0" Grid.Row="2" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="2" Margin="3" Name="contactNameTextBox"
Text="{Binding ContactName, Mode=TwoWay}" VerticalAlignment="Center" />
I got these bindings working, so x:Bind must be broken, I thought. So, my idea of using x:Bind was broken and I started to change the x:Bind for Binding. That worked fine until I changed this one:
<FontIcon Grid.Column="2" FontFamily="Segoe MDL2 Assets" Glyph=""
Margin="20,0" Visibility="{Binding IsVip, Converter={StaticResource BooleanToVisibilityConverter}}" />
When I changed it, I got this runtime error
Wait a minute – Cannot find a Resource with the Name/Key BooleanToVisibilityConverter ? I’m sure I’ve declared it in the Resources section:
<c:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
Oops – my bad! It’s BoolToVisibilityConverter, not BooleanToVisibilityConverter. I corrected the error and all the bindings started to work with x:Bind:
So, in some way, x:Bind has a bug, but not in the way I expected: instead of pointing the faulty converter with red squiggles, it will crash the bindings that come after it and won’t work anymore. That’s why, it was showing just the customer Id, the star for the IsVip property and nothing else. And it was showing the error at runtime, but I didn’t see it. In the Output window, in the middle of many other messages, a two line message indicated the error:
I think that, being checked as compile time, the converters should also be checked at compile time and not throw an exception in the output window at runtime and blow the rest of the bindings. But that’s just my point of view. Now, everything works and I will make sure that my converters are well defined 😃.
The source code for this project is at https://github.com/bsonnino/ConvertersInUWP