UWP - Perform long view model creation and keep UI responsive
Asked Answered
A

1

1

I've build this example to show my issue. I need to create a hierarchy to be shown in a treeview, treeview to be bound to the view model. In the good old days of VB6 I would have used DoEvents to unlock the UI but here I'm not able to understand how to proceed.

On VS19 create a blank UWP project and call TestLoadSync it then copy paste this in the files:

App.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace TestLoadSync
{
    /// <summary>
    /// Provides application-specific behavior to supplement the default Application class.
    /// </su
    /// mmary>
    sealed partial class App : Application
    {
        public List<PropertyModel> _Properties;
        public List<ImageModel> _ImagesHirerachy;

        /// <summary>
        /// Initializes the singleton application object.  This is the first line of authored code
        /// executed, and as such is the logical equivalent of main() or WinMain().
        /// </summary>
        public App()
        {
            this.InitializeComponent();
            this.Suspending += OnSuspending;
            _Properties = new List<PropertyModel>();
            _ImagesHirerachy = new List<ImageModel>();
        }

        /// <summary>
        /// Invoked when the application is launched normally by the end user.  Other entry points
        /// will be used such as when the application is launched to open a specific file.
        /// </summary>
        /// <param name="e">Details about the launch request and process.</param>
        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                rootFrame.NavigationFailed += OnNavigationFailed;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: Load state from previously suspended application
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            if (e.PrelaunchActivated == false)
            {
                if (rootFrame.Content == null)
                {
                    // When the navigation stack isn't restored navigate to the first page,
                    // configuring the new page by passing required information as a navigation
                    // parameter
                    rootFrame.Navigate(typeof(MainPage), e.Arguments);
                }
                // Ensure the current window is active
                Window.Current.Activate();
            }
        }

        /// <summary>
        /// Invoked when Navigation to a certain page fails
        /// </summary>
        /// <param name="sender">The Frame which failed navigation</param>
        /// <param name="e">Details about the navigation failure</param>
        void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
        }

        /// <summary>
        /// Invoked when application execution is being suspended.  Application state is saved
        /// without knowing whether the application will be terminated or resumed with the contents
        /// of memory still intact.
        /// </summary>
        /// <param name="sender">The source of the suspend request.</param>
        /// <param name="e">Details about the suspend request.</param>
        private void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            //TODO: Save application state and stop any background activity
            deferral.Complete();
        }
    }
}

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace TestLoadSync
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private ObservableCollection<PropertyViewModel> _PropVM;
        public MainPage()
        {
            DataLayer aDAL = new DataLayer();
            _PropVM = new ObservableCollection<PropertyViewModel>();
            this.InitializeComponent();
            ProgB.Maximum = 1;
            aDAL.loadData();
            Debug.WriteLine(((App)App.Current)._Properties.Count());
            Debug.WriteLine(((App)App.Current)._ImagesHirerachy.Count());
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ProgB.Value = 0;
            ProgB.Maximum = ((App)App.Current)._Properties.Count() + 1;
            foreach (PropertyModel aProperty in ((App)App.Current)._Properties)
            {
                ProgB.Value++;
                _PropVM.Add(new PropertyViewModel(aProperty));
            }
            ProgB.Value = ProgB.Maximum;
        }
    }
}

MainPage.xaml

<Page
    x:Class="TestLoadSync.MainPage"
    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:local="using:TestLoadSync"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Grid>
        <Button
            Width="296"
            Height="143"
            Margin="245,214,0,0"
            VerticalAlignment="Top"
            Click="Button_Click"
            Content="Button" />
        <ProgressBar
            x:Name="ProgB"
            Width="296"
            Height="82"
            Margin="245,82,0,0"
            HorizontalAlignment="Left"
            VerticalAlignment="Top" />

    </Grid>
</Page>

On the project add new... - Class - call it: DataLayer.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestLoadSync
{
    public class PropertyModel
    {
        public int id;
        public PropertyModel(int _id)
        {
            id = _id;
        }
    }
    public class ImageModel
    {
        public int id;
        public int property;
        public int parent;
        public string desc;
        public ImageModel(int _id, int _property, int _parent, string _desc)
        {
            id = _id;
            property = _property;
            parent = _parent;
            desc = _desc;
        }
    }

    class PropertyViewModel
    {
        private PropertyModel _Property;
        List<ImageViewModel> _Images;
        public PropertyViewModel()
        {
        }
        public PropertyViewModel(PropertyModel aProperty)
        {
            List<ImageModel> _SubImages;
            _Property = aProperty;

            Debug.WriteLine("Property: " + aProperty.id);


            _Images = new List<ImageViewModel>();

            _SubImages = ((App)App.Current)._ImagesHirerachy
            .Where(x => x.property == aProperty.id && x.parent == 0)
            .ToList();

            foreach (ImageModel aImage in _SubImages)
            {
                _Images.Add(new ImageViewModel(aImage, 1));
            }
        }
    }

    class ImageViewModel
    {
        ImageModel _Image;
        List<ImageViewModel> _Images;

        public ImageViewModel()
        {
        }
        public ImageViewModel(ImageModel aImage, int level)
        {
            List<ImageModel> _SubImages;
            _Image = aImage;
            string aS = new string('-', level);

            Debug.WriteLine("          " + aS + aImage.id);

            _Images = new List<ImageViewModel>();

            _SubImages = ((App)App.Current)._ImagesHirerachy
            .Where(x => x.parent == aImage.id && x.property == aImage.property)
            .ToList();

            foreach (ImageModel aSImage in _SubImages)
            {
                _Images.Add(new ImageViewModel(aSImage, ++level));
            }
        }
    }

    class DataLayer
    {
        private int maxProperties = 1000;
        private int maxSubItems = 100;
        public void loadData()
        {
            for (int i = 0; i < maxProperties; i++)
            {
                ((App)App.Current)._Properties.Add(new PropertyModel(i));
            }
            for (int i = 0; i < maxSubItems; i++)
            {
                for (int j = 0; j < (i > maxSubItems / 2 ? maxSubItems / 2 : i); j++)
                {
                    ((App)App.Current)._ImagesHirerachy.Add(new ImageModel(maxProperties + i * (maxSubItems / 2) + j, i, 0, "-" + (((App)App.Current)._ImagesHirerachy.Count() + 1).ToString()));
                }
            }
            for (int i = 0; i < maxSubItems; i++)
            {
                for (int j = 0; j < (i > maxSubItems / 4 ? maxSubItems / 4 : i); j++)
                {
                    ((App)App.Current)._ImagesHirerachy.Add(new ImageModel(maxProperties*2+ i * (maxSubItems/2) + j, i, maxProperties + i * (maxSubItems / 2) + j, "--" + (((App)App.Current)._ImagesHirerachy.Count() + 1).ToString()));
                    if (i == j)
                    {
                        ((App)App.Current)._ImagesHirerachy.Add(new ImageModel(maxProperties * 4 + i * (maxSubItems / 2) + j, i, maxProperties*2 + i * (maxSubItems / 2) + j,  "---" + (((App)App.Current)._ImagesHirerachy.Count() + 1).ToString()));
                    }

                }
            }
        }
    }
}

If you run it the ProgressBar will not fill in smoothly :( but it does not.

I've used simple numbers (1000/100) in the procedure to create the test structure but in my real case are much higher.

In the final app I'll use the MVVM light model and, obviously, the data will be read and saved from DB/File.

Note that the Models are both flat. The hierarchy is given thanks to the "parent" field in the image class that if is <>0 refers to the parent image. If is =0 then the image has to be attached to the property.

What I'm focusing here is how to create the ViewModel structures in the correct hierarchy so that I can bind the Page to the

_PropVM

and have the whole tree being built.

Adherence answered 10/11, 2019 at 21:32 Comment(0)
Z
1

The ProgressBar can't response because your loop and the UI are executing on the same thread. While the loop is busy, it blocks the thread and can't update the UI. So you can calls the Task.Run( ) method to create a task and put time-consuming operations inside, then use await to perform asynchronous operations, like below:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    ProgB.Value = 0;
    ProgB.Maximum = ((App)App.Current)._Properties.Count() + 1;
    foreach (PropertyModel aProperty in ((App)App.Current)._Properties)
    {
        await Task.Run(() => _PropVM.Add(new PropertyViewModel(aProperty)));
        ProgB.Value++;
    }
    ProgB.Value = ProgB.Maximum;
}
Zadoc answered 11/11, 2019 at 4:10 Comment(2)
Perfect. Thank you.Adherence
Is there a more efficient way of doing it? because to create the 1000 items it takes really a lotAdherence

© 2022 - 2024 — McMap. All rights reserved.