Search code examples
mvvmuwpuwp-xamlxbind

UWP - How to specify the order of updates when use x:Bind?


I'm developing a UWP app and I'm facing a problem. The app uses the MVVM pattern with Template10. I have created a similar solution that recreates the problem that I'm facing. In that solution, a list of orders are displayed, the user chooses an order and then click the "Edit" button. Then a second page is displayed and pre-loaded with the previous selected order, in this second page the user can edit the order. The problem is in the second page, the data bound to comboboxes doesn't show. Maybe the problem is related to this question. In my case, the SelectedValue is set before the ItemsSource. After debugging, I have reached these lines of code in OrderEditionPage.g.cs:

private void Update_ViewModel(global::ComboApp.ViewModels.OrderEditionPageViewModel obj, int phase)
{
    this.bindingsTracking.UpdateChildListeners_ViewModel(obj);
    if (obj != null)
    {
        if ((phase & (NOT_PHASED | DATA_CHANGED | (1 << 0))) != 0)
        {
            this.Update_ViewModel_SelectedOrder(obj.SelectedOrder, phase);
        }
        if ((phase & (NOT_PHASED | (1 << 0))) != 0)
        {
            this.Update_ViewModel_BusinessAssociates(obj.BusinessAssociates, phase);
            this.Update_ViewModel_TransactionTypes(obj.TransactionTypes, phase);
            this.Update_ViewModel_OrderTypes(obj.OrderTypes, phase);
            this.Update_ViewModel_ShowSelectedOrder(obj.ShowSelectedOrder, phase);
        }
    }
}

If I could achieve this line of code be executed at last, my problem would be solved: this.Update_ViewModel_SelectedOrder(obj.SelectedOrder, phase);

How could I achieve this? How does Visual Studio determine the order of this lines?

OrderEditionPage.xaml

<Page
    x:Class="ComboApp.Views.OrderEditionPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:myconverters="using:ComboApp.Converters"
    xmlns:t10converters="using:Template10.Converters"
    mc:Ignorable="d">

    <Page.Resources>
        <t10converters:ChangeTypeConverter x:Key="TypeConverter" />
        <myconverters:DateTimeConverter x:Key="DateTimeConverter" />
    </Page.Resources>

    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <StackPanel
            Padding="15, 5"
            Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <TextBox
                Header="Order #"
                Margin="5"
                Width="150"
                HorizontalAlignment="Left"
                Text="{x:Bind ViewModel.SelectedOrder.ExternalId, Mode=TwoWay}" />
            <ComboBox
                Header="Business Associate"
                Margin="5"
                MinWidth="300"
                SelectedValuePath="BusinessAssociateId"
                DisplayMemberPath="Name1"
                ItemsSource="{x:Bind ViewModel.BusinessAssociates}"
                SelectedValue="{x:Bind ViewModel.SelectedOrder.BusinessAssociateId, Mode=TwoWay, Converter={StaticResource TypeConverter}}" />
            <DatePicker
                Header="Delivery Date"
                Margin="5"
                MinWidth="0"
                Width="200"
                Date="{x:Bind ViewModel.SelectedOrder.DeliveryDate, Mode=TwoWay, Converter={StaticResource DateTimeConverter}}" />
            <ComboBox
                Header="Transaction"
                MinWidth="200"
                Margin="5"
                SelectedValuePath="Value"
                DisplayMemberPath="Display"
                ItemsSource="{x:Bind ViewModel.TransactionTypes}"
                SelectedValue="{x:Bind ViewModel.SelectedOrder.TransactionType, Mode=TwoWay}" />
            <TextBox
                Header="Priority"
                Margin="5"
                MaxWidth="150"
                HorizontalAlignment="Left"
                Text="{x:Bind ViewModel.SelectedOrder.Priority}" />
            <ComboBox
                Header="Type"
                Margin="5"
                MinWidth="200"
                SelectedValuePath="Value"
                DisplayMemberPath="Display"
                ItemsSource="{x:Bind ViewModel.OrderTypes}"
                SelectedValue="{x:Bind ViewModel.SelectedOrder.OrderType, Mode=TwoWay}" />
            <TextBox
                Header="Information"
                Margin="5"
                Height="100"
                AcceptsReturn="True"
                TextWrapping="Wrap"
                ScrollViewer.VerticalScrollBarVisibility="Auto"
                Text="{x:Bind ViewModel.SelectedOrder.Information, Mode=TwoWay}" />
            <Button
                Margin="5"
                Content="Show"
                Width="100"
                HorizontalAlignment="Right"
                Command="{x:Bind ViewModel.ShowSelectedOrder}" />
        </StackPanel>
    </ScrollViewer>
</Page>

OrderEditionPage.xaml.cs

using ComboApp.ViewModels;
using Windows.UI.Xaml.Controls;

namespace ComboApp.Views
{
    public sealed partial class OrderEditionPage : Page
    {
        public OrderEditionPageViewModel ViewModel => DataContext as OrderEditionPageViewModel;

        public OrderEditionPage()
        {
            this.InitializeComponent();
        }
    }
}

OrderEditionPageViewModel.cs

using ComboApp.Models;
using ComboApp.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Template10.Mvvm;
using Template10.Utils;
using Windows.UI.Xaml.Navigation;

namespace ComboApp.ViewModels
{
    public class OrderEditionPageViewModel
        : ViewModelBase
    {
        private IBusinessAssociateService businessAssociateService;

        private Order selectedOrder;
        public Order SelectedOrder
        {
            get { return selectedOrder; }
            set { Set(ref selectedOrder, value); }
        }

        public ObservableCollection<object> TransactionTypes { get; set; } = new ObservableCollection<object>();
        public ObservableCollection<object> OrderTypes { get; set; } = new ObservableCollection<object>();
        public ObservableCollection<BusinessAssociate> BusinessAssociates { get; set; } = new ObservableCollection<BusinessAssociate>();

        public OrderEditionPageViewModel(IBusinessAssociateService businessAssociateService)
        {
            this.businessAssociateService = businessAssociateService;

            TransactionTypes.Add(new { Value = "I", Display = "Incoming" });
            TransactionTypes.Add(new { Value = "O", Display = "Outgoing" });
            TransactionTypes.Add(new { Value = "T", Display = "Transfer" });

            OrderTypes.Add(new { Value = "M", Display = "Manual" });
            OrderTypes.Add(new { Value = "A", Display = "Automatic" });
            OrderTypes.Add(new { Value = "S", Display = "Semi-automatic" });
        }

        public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
        {
            // Loading buiness associates
            var response = await businessAssociateService.GetNextPageAsync();
            if (response.IsSuccessful)
            {
                BusinessAssociates.AddRange(response.Result.Items);
            }

            SelectedOrder = (Order)parameter;
            await base.OnNavigatedToAsync(parameter, mode, state);
        }

        private DelegateCommand showSelectedOrder;
        public DelegateCommand ShowSelectedOrder => showSelectedOrder ?? (showSelectedOrder = new DelegateCommand(async () =>
        {
            await Views.MessageBox.ShowAsync(JsonConvert.SerializeObject(SelectedOrder, Formatting.Indented));
        }));

    }
}

Solution

  • It is a known issue of x:Bind when the SelectedValue of a ComboBox is sometimes set before its ItemsSource, you can read more about it here.

    As a workaround you can use Bindings instead of x:Bind, but make sure that ItemsSource binding is placed before SelectedValue binding in XAML.

    Alternatively you can try calling Bindings.Update() in the Page_Loaded event of your second page.