简体   繁体   中英

Data from ViewModel is not binding to XAML from PageLoad Event

I am new to MVVM architecture. I am building an UWP app. I basically have a View(XAML), code behind (Xaml.cs), ViewModel and Data Services.

My View/XAML looks like this:

<Page
    x:Class="SnapBilling.SyncModule.SyncView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" 
    mc:Ignorable="d" Loaded="Page_Loaded"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    
    
    <Grid>
        <Grid.Resources>
            <!--DataTemplate for Published Date column defined in Grid.Resources.  PublishDate is a property on the ItemsSource of type DateTime -->
            <DataTemplate x:Key="ProgressTemplate" >
                <ProgressBar x:Name="progressBar1" Value="{Binding Value.ProgressPercentage}" Maximum="100" Margin="20"/>
            </DataTemplate>
            
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="4*" />
            <RowDefinition Height="2*" />
        </Grid.RowDefinitions>
        <controls:DataGrid x:Name="BackupSummaryDataGrid" 
                            Grid.Row="0"
                            Margin="40"
                            ItemsSource="{x:Bind DataContext.UploadProgressInfoDict}"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            AlternatingRowBackground="Transparent"
                            AreRowDetailsFrozen="False"
                            AreRowGroupHeadersFrozen="True"
                            AutoGenerateColumns="False"
                            CanUserReorderColumns="True"
                            CanUserResizeColumns="True"
                            ColumnHeaderHeight="32"
                            FrozenColumnCount="0"
                            GridLinesVisibility="None"
                            HeadersVisibility="Column"
                            HorizontalScrollBarVisibility="Visible"
                            IsReadOnly="False"
                            MaxColumnWidth="400"
                            RowDetailsVisibilityMode="Collapsed"
                            RowGroupHeaderPropertyNameAlternative="Range"
                            SelectionMode="Extended"
                            VerticalScrollBarVisibility="Visible"
                            >

            <controls:DataGrid.Columns>
                <controls:DataGridTextColumn Tag="SyncType" Header="Sync Type" Binding="{Binding Key}" IsReadOnly="True"  />
                <controls:DataGridTextColumn Tag="remaining" Header="Pending Items" Binding="{Binding Value.remainingNow}" IsReadOnly="True"  />
                <controls:DataGridTemplateColumn Header="% remaining" CellTemplate="{StaticResource ProgressTemplate}" />
                <controls:DataGridTextColumn Header="Status" Binding="{Binding Value.ProgressMessage}" IsReadOnly="True"/>

            </controls:DataGrid.Columns>
            
        </controls:DataGrid>
    </Grid>
</Page>

Now when the page/view xaml loads up, it calls the constructor of the xaml class where we initialize the component and set the data context with the view model object. This is the code behind class,

public sealed partial class SyncView : Page, IView
{
    public SyncView()
    {
        this.InitializeComponent();
        DataContext = new SyncViewModel(ServiceLocator.Current.GetService<ICommonServices>());
    }
    
    private void Page_Loaded(object sender, RoutedEventArgs e)
    {
    
    }
}

Now here DataContext = new SyncViewModel(ServiceLocator.Current.GetService<ICommonServices>()); gets a ViewModel object created and binds to the data context properly.

Problem

When I set the data context from the Page_Loaded event instead of the constructor like below the viewmodel object is not binding to the datacontext of the page.

private void Page_Loaded(object sender, RoutedEventArgs e)
        {
        DataContext = new SyncViewModel(ServiceLocator.Current.GetService<ICommonServices>());

        }

How to solve this?

So the order the code will execute is

  1. Constructor call
  2. View elements creation
  3. Binding
  4. Page loaded event (only occurs when all children have been added)

What is happening there is that when you set your DataContext in the constructor when the binding comes in the DataContext is not null however when you set DataContext in the Page_Loaded the binding will occur with DataContext being null hence you see no data in you view.

Now if you can I'll advised you to initialize the View-model in the constructor prior to binding. however if for some reason you need it to bind in the Page_Loaded event you need a way to tell you view to update the value when it changes.

Luckily the framework already has a solution for that, you could use the INotifyPropertyChanged interface on you ViewModel so it can notify the view about a property value change, in you case the Value.ProgressPercentage I guess.

But you will have to code an event handler that will update you view accordingly
something like this

 private void OnViewModelPropertyChange(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case nameof(ViewModel.prop1):
                    // do something
                    break;
                case nameof(ViewModel.prop2):
                    // do something else
                    break;
            }
        }

And that's why I recommend you use data binding instead, not that is super complicated but it will save you a few lines of extra coding.

Now in the code behind:

public SyncView()
{
 this.Loaded += SyncView_Loaded;  
}
private void SyncView_Loaded(object sender, RoutedEventArgs e)
{
 this.InitializeComponent();
 DataContext = new SyncViewModel(ServiceLocator.Current.GetService<ICommonServices>());
}

And in the XAML:

ItemsSource="{Binding UploadProgressInfoDict}"

Reason: Earlier I was creating a view model object (eg, VM) and saving it in a property of codebehind class, then i was setting the datacontext with that property. later on i was binding the itemssource with that object's UploadProgressInfoDict like

ItemsSource="{Binding VM.UploadProgressInfoDict}"

Removed this piece and it works fine!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM