,
i want to know how can i implement data virtualzation in mvvm pattern ?
i have searched but i didnt find data virtualzation using mvvm pattern.
this link descripes data virtualzation very good but not in mvvm.
http://www.codeproject.com/Articles/34405/WPF-Data-Virtualization
so please provide a link or example..
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Threading;
namespace DataVirtualization
{
/// <summary>
/// Interaction logic for DemoWindow.xaml
/// </summary>
public partial class DemoWindow
{
/// <summary>
/// Initializes a new instance of the <see cref="DemoWindow"/> class.
/// </summary>
public DemoWindow()
{
InitializeComponent();
// use a timer to periodically update the memory usage
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 1);
timer.Tick += timer_Tick;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
tbMemory.Text = string.Format("{0:0.00} MB", GC.GetTotalMemory(true)/1024.0/1024.0);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// create the demo items provider according to specified parameters
int numItems = int.Parse(tbNumItems.Text);
int fetchDelay = int.Parse(tbFetchDelay.Text);
DemoCustomerProvider customerProvider = new DemoCustomerProvider(numItems, fetchDelay);
// create the collection according to specified parameters
int pageSize = int.Parse(tbPageSize.Text);
int pageTimeout = int.Parse(tbPageTimeout.Text);
if ( rbNormal.IsChecked.Value )
{
DataContext = new List<Customer>(customerProvider.FetchRange(0, customerProvider.FetchCount()));
}
else if ( rbVirtualizing.IsChecked.Value )
{
DataContext = new VirtualizingCollection<Customer>(customerProvider, pageSize);
}
else if ( rbAsync.IsChecked.Value )
{
DataContext = new AsyncVirtualizingCollection<Customer>(customerProvider, pageSize, pageTimeout*1000);
}
}
}
}
Thanks
Related
I have read through a few threads here, but have not come to any conclusion.
An asynchronous method in another class is to be executed by button click.
In this method the current state should be added again and again in a text field of the form:
public interface MainForm {
string LogText { get; set; }
}
public partial class KatalogForm : Form, MainForm {
public string LogText {
get { return rtbxLog.Text; }
set { rtbxLog.Text += value; }
}
private void btnCreateCatalogues_Click(object sender, EventArgs e) {
Task.Run(() => catalogues.Create());
}
}
Excerpt from the second class:
private static MainForm mainForm;
public async void Create() {
//Stuff
//Update TextBox
}
Since the method is asynchronous, I can't access the textbox directly here.
I know that I have to work with Invoke here, but I can't implement this properly.
What is the best way to solve this?
Usually, when I have to asynchronously update a TextBox with data from external sources (ex: an operations log) I use a queue and a timer (Windows.Forms).
The method called by external objects adds the new data.
The timer clears pending data at a reasonable rate and updates the text box.
The advantages are:
Fast requests: The update method invoked by external sources only add items to the queue.
Thread safe: The text box is only updated within its own UI thread, so no cross-thread errors can occur.
Less allocations: No Task or BeginInvoke calls are required, avoiding allocation of temporary objects.
Few UI updates: The items are applied to the text box as batches (multiple items at the same time) based on the timer frequency.
First-in/First-out: No risk of overlap of items due to asynchronous operations scheduled and executed in the wrong order.
See the sample class (LogBox user control) below, split into LogBox.cs and LogBox.Designer.cs:
Other objects would call Log method.
The default rate is 100ms; that is, 10 times a second (for a human reader should be enough).
Sample code (LogBox.cs):
using System;
using System.Collections.Concurrent;
using System.Windows.Forms;
namespace SAMPLE
{
[Docking(DockingBehavior.Ask)]
public partial class LogBox : UserControl
{
private readonly ConcurrentQueue<string> PendingLog = new ConcurrentQueue<string>();
public LogBox()
{
InitializeComponent();
}
private void tmrLog_Tick(object sender, EventArgs e) { this.ProcessPendingLog(); }
private void ProcessPendingLog()
{
if (!this.Disposing && !this.IsDisposed && this.IsHandleCreated)
try
{
if (!this.PendingLog.IsEmpty)
{
string item;
while (this.PendingLog.TryDequeue(out item))
{
txtLog.AppendText(item);
}
}
}
catch (Exception ex) { /* ... */ }
}
public void Log(string text) { this.PendingLog.Enqueue(text); }
}
}
Sample code (LogBox.Designer.cs):
namespace SAMPLE
{
partial class LogBox
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.tmrLog = new System.Windows.Forms.Timer(this.components);
this.txtLog = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// tmrLog
//
this.tmrLog.Enabled = true;
this.tmrLog.Tick += new System.EventHandler(this.tmrLog_Tick);
//
// txtLog
//
this.txtLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtLog.Location = new System.Drawing.Point(0, 0);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ReadOnly = true;
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.txtLog.Size = new System.Drawing.Size(200, 200);
this.txtLog.TabIndex = 0;
//
// LogBox
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.txtLog);
this.Name = "LogBox";
this.Size = new System.Drawing.Size(200, 200);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Timer tmrLog;
private System.Windows.Forms.TextBox txtLog;
}
}
I would recommend use IProgress<string> for catalogues.Create()
Task.Run(async () =>
{
var createProgress = new Progress<string>(ReportProgress);
await catalogues.Create(createProgress);
});
void ReportProgress(string reportMessage)
{
//update log here
}
usage inside Create
async Task Create(IProgress<string> progress)
{
foreach (var category in categories)
{
// some staff
progress.Report($"{category} - completed");
}
}
IProgress example
I have timer which is calling list of stored actions. I want those actions to be called asynchronously. So I wrapped my CPU bound operation into task, then made async/await in action. However, it's not updating combo box. Clearly the context is not switching back to UI, but I don't understand why and what I should do to fix it.
Code in main form:
public FormMain()
{
InitializeComponent();
pt = new PeriodicTask(() => Execute());
pt.Start();
actions = new List<ActionWrapper>();
actions.Add(new ActionWrapper() { Periodic = false, MyAction = async () => {
bool b = await NetworkOperation();
comboBoxPairs.DataSource = pairs; // this doesn't update combo box
comboBoxPairs.DisplayMember = "Name";
//comboBoxPairs.Refresh(); // this is even crashing app
}});
}
private Task<bool> NetworkOperation()
{
return Task.Run(() => {
// CPU bound activity goes here
return true;
});
}
private void Execute()
{
Parallel.ForEach(actions,
new ParallelOptions { MaxDegreeOfParallelism = 10 },
x => {
x.MyAction();
if (!x.Periodic)
actions.Remove(x);
});
}
Timer class:
public class PeriodicTask
{
private System.Threading.Timer timer;
private int dueTime;
private int periodTime;
private Action callBack;
public PeriodicTask(Action cb)
{
callBack = cb;
timer = new System.Threading.Timer(Task, null, Timeout.Infinite, Timeout.Infinite);
dueTime = 100;
periodTime = 5000;
}
public void Start()
{
timer.Change(dueTime, periodTime);
}
public void Stop()
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
}
private void Task(object parameter)
{
callBack();
}
}
This is the wrapper class I use to hold action:
public class ActionWrapper
{
public bool Periodic { get; set; }
public Func<Task> MyAction { get; set; }
}
It's not that it does not switch back, it does not start on a UI thread in the first place because you are using System.Threading.Timer, which processes the ticks on thread pool threads, out of WPF context.
You can replace it with DispatcherTimer.
If a System.Timers.Timer is used in a WPF application, it is worth noting that the System.Timers.Timer runs on a different thread then the user interface (UI) thread. In order to access objects on the user interface (UI) thread, it is necessary to post the operation onto the Dispatcher of the user interface (UI) thread using Invoke or BeginInvoke. Reasons for using a DispatcherTimer opposed to a System.Timers.Timer are that the DispatcherTimer runs on the same thread as the Dispatcher and a DispatcherPriority can be set on the DispatcherTimer.
Also, you need to deal with re-entrance, same as for System.Threading.Timer, because a Cpu bound operation could still be processing the previous tick.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
DispatcherTimer timer = new DispatcherTimer();
long currentlyRunningTasksCount;
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += async (s, e) =>
{
// Prevent re-entrance.
// Skip the current tick if a previous one is already in processing.
if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0)
{
return;
}
try
{
await ProcessTasks();
}
finally
{
Interlocked.Decrement(ref currentlyRunningTasksCount);
}
};
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// This one would crash, ItemsSource requires to be invoked from the UI thread.
// ThreadPool.QueueUserWorkItem(o => { listView.Items.Add("started"); });
listView.Items.Add("started");
timer.Start();
}
async Task ProcessTasks()
{
var computed = await Task.Run(() => CpuBoundComputation());
listView.Items.Add(string.Format("tick processed on {0} threads", computed.ToString()));
}
/// <summary>
/// Computes Cpu-bound tasks. From here and downstream, don't try to interact with the UI.
/// </summary>
/// <returns>Returns the degree of parallelism achieved.</returns>
int CpuBoundComputation()
{
long concurrentWorkers = 0;
return
Enumerable.Range(0, 1000)
.AsParallel()
.WithDegreeOfParallelism(Math.Max(1, Environment.ProcessorCount - 1))
.Select(i =>
{
var cur = Interlocked.Increment(ref concurrentWorkers);
SimulateExpensiveOne();
Interlocked.Decrement(ref concurrentWorkers);
return (int)cur;
})
.Max();
}
/// <summary>
/// Simulate expensive computation.
/// </summary>
void SimulateExpensiveOne()
{
// Prevent from optimizing out the unneeded result with GC.KeepAlive().
GC.KeepAlive(Enumerable.Range(0, 1000000).Select(i => (long)i).Sum());
}
}
}
If you need a precise control on what's happening, you are better off with queueing events and displaying them independently of processing:
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
DispatcherTimer fastTimer = new DispatcherTimer();
BackgroundProcessing processing = new BackgroundProcessing();
public MainWindow()
{
InitializeComponent();
processing.Start();
fastTimer.Interval = TimeSpan.FromMilliseconds(10);
fastTimer.Tick += Timer_Tick;
fastTimer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
Notification notification;
while ((notification = processing.TryDequeue()) != null)
{
listView.Items.Add(new { notification.What, notification.HappenedAt, notification.AttributedToATickOf });
}
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
processing.Stop();
}
}
public class Notification
{
public string What { get; private set; }
public DateTime AttributedToATickOf { get; private set; }
public DateTime HappenedAt { get; private set; }
public Notification(string what, DateTime happenedAt, DateTime attributedToATickOf)
{
What = what;
HappenedAt = happenedAt;
AttributedToATickOf = attributedToATickOf;
}
}
public class BackgroundProcessing
{
/// <summary>
/// Different kind of timer, <see cref="System.Threading.Timer"/>
/// </summary>
Timer preciseTimer;
ConcurrentQueue<Notification> notifications = new ConcurrentQueue<Notification>();
public Notification TryDequeue()
{
Notification token;
notifications.TryDequeue(out token);
return token;
}
public void Start()
{
preciseTimer = new Timer(o =>
{
var attributedToATickOf = DateTime.Now;
var r = new Random();
Parallel.ForEach(Enumerable.Range(0, 2), i => {
Thread.Sleep(r.Next(10, 5000));
var happenedAt = DateTime.Now;
notifications.Enqueue(
new Notification("Successfully loaded cpu 100%", happenedAt, attributedToATickOf));
});
}, null, 0, 1000);
}
public void Stop()
{
preciseTimer.Change(0, 0);
}
}
}
UPDATE:
For Windows Forms you could replace DispatcherTimer with the System.Windows.Forms.Timer in the second code sample.
I am wondering about timers in WPF.
what i basically know is how to make a simple count down timer (label) count down like this code:
private void buttonStartOne_Click(object sender, RoutedEventArgs e)
{
counterOne = new DispatcherTimer();
counterOne.Tick += new EventHandler(counterOne_Tick);
counterOne.Interval = new TimeSpan(0, 0, 1);
counterOneTime = 10;
counterOne.Start();
}
private void counterOne_Tick(object sender, EventArgs e)
{
// code goes here
if (counterOneTime > 0)
{
counterOneTime--;
labelCounterOne.Content = counterOneTime + "s";
}
else
counterOne.Stop();
}
In this example code above, the countdown is just 10 seconds.
What i want, and dont know is how i should make it as: HH:mm:ss and than make it count down.
Would you do that with 3 separate counters and labels (one for each time unit)?
Or what should be a better way to tackle this one?
You could convert your Timespan to a string with the ToString(string format) method. Set the resulting string on your label
public class TimeController
{
private static readonly TimeSpan TimeSpan = new TimeSpan(0, 0, 1);
private static int _time;
protected static readonly DispatcherTimer Timer = new DispatcherTimer();
protected static readonly DispatcherTimer BeeperTimer = new DispatcherTimer();
protected static readonly Stopwatch StopWatch = new Stopwatch();
protected static Label TimerLabel;
protected static Button StartButton;
internal static int Time { get { return _time; } set { _time = value; ExtractAndUpdate(); } }
internal static bool Countdown { get; set; }
/// <summary>
/// Static constructor
/// </summary>
static TimeController()
{
BeeperTimer.Interval = TimeSpan;
BeeperTimer.Tick += BeeperTick;
Timer.Interval = TimeSpan;
Timer.Tick += TimerTick;
}
/// <summary>
/// Timer tick event method
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void TimerTick(object sender, EventArgs e)
{
if (Countdown)
if (Time > 0)
{
ExtractAndUpdate();
Time -= 1;
}
else
{
StopRunning();
BeeperTimer.Start();
}
else
ExtractAndUpdate();
}
/// <summary>
/// Start timer and stopwatch
/// </summary>
protected static void StartRunning()
{
Timer.Start();
StopWatch.Start();
StartButton.Content = Labels.Pause;
}
/// <summary>
/// Stop timer and stopwatch
/// </summary>
protected static void StopRunning()
{
Timer.Stop();
StopWatch.Stop();
StartButton.Content = Labels.Start;
}
/// <summary>
/// Beeper event method and label blinking
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void BeeperTick(object sender, EventArgs e)
{
TimerLabel.Visibility = TimerLabel.Visibility.Equals(Visibility.Hidden) ? Visibility.Visible : Visibility.Hidden;
Console.Beep();
}
/// <summary>
/// Extract time and update label
/// </summary>
private static void ExtractAndUpdate()
{
var elapsed = Countdown ? ConvertToTimeSpan() : StopWatch.Elapsed;
UpdateTimeLabel(elapsed);
}
/// <summary>
/// Convert int to TimeSpan
/// </summary>
/// <returns></returns>
internal static TimeSpan ConvertToTimeSpan()
{
var hours = Time / 3600;
var minutes = (Time % 3600) / 60;
var seconds = Time % 60;
return new TimeSpan(hours, minutes, seconds);
}
/// <summary>
/// Update label with data and change color
/// </summary>
/// <param name="elapsed"></param>
protected static void UpdateTimeLabel(TimeSpan elapsed)
{
TimerLabel.Foreground = Brushes.Black;
var time = String.Format(CultureInfo.CurrentCulture, "{0:00h} {1:00m} {2:00s}", elapsed.Hours, elapsed.Minutes, elapsed.Seconds);
if (Countdown && elapsed.TotalMinutes < 1)
TimerLabel.Foreground = Brushes.Red;
TimerLabel.Content = time;
}
}
I have a number of classes that do stuff, typically step through a recordset and call a webservice or two for each record.
At the moment this all runs in the GUI thread and hangs painting. First thought was to use a BackgroundWorker and implement a nice progress bar, handle errors, completion etc. All the nice things a Background worker enables.
As soon as the code hit the screen it started to smell. I was writing a lot of the background worker into each class, repeating most of the ProcessRows method in a bw_DoWork method and thinking there should be a better way, and it's probably already been done.
Before I go ahead and reinvent the wheel is there a pattern or implementation for a class that seperates out the background worker? It would take classes that implement an interface such as ibackgroundable, but the classes could still be run standalone, and would require minimal change to implement the interface.
Edit: A simplified example requested by #Henk:
I have:
private void buttonUnlockCalls_Click(object sender, EventArgs e)
{
UnlockCalls unlockCalls = new UnlockCalls();
unlockCalls.MaxRowsToProcess = 1000;
int processedRows = unlockCalls.ProcessRows();
this.textProcessedRows.text = processedRows.ToString();
}
I think I want:
private void buttonUnlockCalls_Click(object sender, EventArgs e)
{
UnlockCalls unlockCalls = new UnlockCalls();
unlockCalls.MaxRowsToProcess = 1000;
PushToBackground pushToBackground = new PushToBackground(unlockCalls)
pushToBackground.GetReturnValue = pushToBackground_GetReturnValue;
pushToBackground.DoWork();
}
private void pushToBackground_GetReturnValue(object sender, EventArgs e)
{
int processedRows = e.processedRows;
this.textProcessedRows.text = processedRows.ToString();
}
I could go ahead and do this, but don't want to reinvent.
The answer I'm looking for would along the lines of "Yes, Joe did a good implementation of that (here)" or "That's a Proxy Widget pattern, go read about it (here)"
Each operation needs to implement the following interface:
/// <summary>
/// Allows progress to be monitored on a multi step operation
/// </summary>
interface ISteppedOperation
{
/// <summary>
/// Move to the next item to be processed.
/// </summary>
/// <returns>False if no more items</returns>
bool MoveNext();
/// <summary>
/// Processes the current item
/// </summary>
void ProcessCurrent();
int StepCount { get; }
int CurrentStep { get; }
}
This seperates the enumeration of the steps from the processing.
Here is a sample operation:
class SampleOperation : ISteppedOperation
{
private int maxSteps = 100;
//// The basic way of doing work that I want to monitor
//public void DoSteppedWork()
//{
// for (int currentStep = 0; currentStep < maxSteps; currentStep++)
// {
// System.Threading.Thread.Sleep(100);
// }
//}
// The same thing broken down to implement ISteppedOperation
private int currentStep = 0; // before the first step
public bool MoveNext()
{
if (currentStep == maxSteps)
return false;
else
{
currentStep++;
return true;
}
}
public void ProcessCurrent()
{
System.Threading.Thread.Sleep(100);
}
public int StepCount
{
get { return maxSteps; }
}
public int CurrentStep
{
get { return currentStep; }
}
// Re-implement the original method so it can still be run synchronously
public void DoSteppedWork()
{
while (MoveNext())
ProcessCurrent();
}
}
This can be called from the form like this:
private void BackgroundWorkerButton_Click(object sender, EventArgs eventArgs)
{
var operation = new SampleOperation();
BackgroundWorkerButton.Enabled = false;
BackgroundOperation(operation, (s, e) =>
{
BackgroundWorkerButton.Enabled = true;
});
}
private void BackgroundOperation(ISteppedOperation operation, RunWorkerCompletedEventHandler runWorkerCompleted)
{
var backgroundWorker = new BackgroundWorker();
backgroundWorker.RunWorkerCompleted += runWorkerCompleted;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += new DoWorkEventHandler((s, e) =>
{
while (operation.MoveNext())
{
operation.ProcessCurrent();
int percentProgress = (100 * operation.CurrentStep) / operation.StepCount;
backgroundWorker.ReportProgress(percentProgress);
if (backgroundWorker.CancellationPending) break;
}
});
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler((s, e) =>
{
var progressChangedEventArgs = e as ProgressChangedEventArgs;
this.progressBar1.Value = progressChangedEventArgs.ProgressPercentage;
});
backgroundWorker.RunWorkerAsync();
}
I haven't done it yet but I'll be moving BackgroundOperation() into a class of its own and implementing the method to cancel the operation.
I would put my non-UI code into a new class and use a Thread (not background worker). To show progress, have the new class fire events back to the UI and use Dispatcher.Invoke to update the UI.
There is a bit of coding in this, but it is cleaner and works. And is more maintainable than using background worker (which is only really intended for small tasks).
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.
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;
}