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.