I am having issues updating a separately opened window's progress bar from a background worker inside another class.
The program execution goes like this:
MainWindow loads
Click button to do some work and open a popup
progress bar (newly opened window)
Background worker does work
and reports progress to popup progress bar
Popup progress bar
hopefully updates.
The progress bar Value is bound to a property, which in the step-through debugger, looks to be getting updated okay by the background worker. These changes just are not reflected on the popup progress bar view. However, the binding is not broken because if I manually try and set the property value for the progress bar it works fine.
Furthermore, when I put the progress bar inside the initially started MainWindow view it updates fine. Any suggestions??
Here is the some code:
MainWindowViewModel
public class MainWindowViewModel: BaseViewModel
{
private void PerformSomeAction()
{
var popUpProgressBar = new PopUpProgressBarViewModel();
popUpProgressBar.Show(popUpProgressBar);
var worker = new BackgroundWorker { WorkerReportsProgress = true };
worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
if (args.ProgressPercentage != popUpProgressBar.Progresser)
{
Progresser = args.ProgressPercentage;
popUpProgressBar.Progresser = args.ProgressPercentage;
}
};
worker.DoWork += delegate
{
for (int i = 0; i < 101; i++)
{
worker.ReportProgress(i);
System.Threading.Thread.Sleep(10);
}
MessageBox.Show("Done");
};
worker.RunWorkerAsync();
}
private int _progresser;
public int Progresser
{
get { return _progresser; }
set
{
if (_progresser == value) return;
_progresser = value;
OnPropertyChanged("Progresser");
}
}
private RelayCommand _startProcessing; //set private member
public ICommand StartProcessing //public field used by xaml binding
{
get
{
return _startProcessing = MakeCommandSafely(_startProcessing, () => PerformSomeAction());
}
}
}
PopUpProgressBarViewModel
public class PopUpProgressBarViewModel : BaseViewModel
{
private PopUpProgressBar _popUpProgressBar;
public void Show(PopUpProgressBarViewModel context)
{
_popUpProgressBar = new PopUpProgressBar {DataContext = context};
_popUpProgressBar.Show();
}
private int _progresser;
public int Progresser
{
get { return _progresser; }
set
{
if (_progresser == value) return;
_progresser = value;
OnPropertyChanged("Progresser");
}
}
}
For full solution file (so you can see whats happening), see here
As #Doug said, since you are already setting the DataContext:
_popUpProgressBar = new PopUpProgressBar {DataContext = context};
You can change the PopUpProgressBar to
<Window x:Class="OpeningWindow_With_ProgressBar.View.PopUpProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:OpeningWindow_With_ProgressBar.ViewModel" Title="PopUpProgressBar" Height="150" Width="300">
<Grid>
<StackPanel>
<Label FontWeight="Bold">Loading Something</Label>
<ProgressBar Minimum="0" Maximum="100" Margin="0,10,0,0" Height="25px" Width="250px" Value="{Binding Path=Progresser, Mode=OneWay}"></ProgressBar>
<TextBlock Margin="10,10,0,0" Text="Details of loading..."></TextBlock>
</StackPanel>
</Grid>
You are creating two PopUpProgressBarViewModels. You've got one that's being created as a resource inside PopUpProgressBar.xaml, and the other one is being created in MainWindowViewModel (line 18).
Your XAML is bound to the one created inside PopUpProgressBar.xaml, while the one that you're updating is the one created in MainWindowViewModel.
If you can pare it down so only one is created, that should solve your problem.
Related
My goal is to update the progress bar while another set of script (calculations) is running.
I have followed the sample files from here and tried to bind it to my MVVM script but the progress bar would not update.
Here is the Progressbar script
In the script below, I have included progressBarCounter and noOfDataas a value in another script that is calculated in a method.
Proof that data is updated
public partial class ProgressBarTaskOnWorkerThread : Window
{
public ProgressBarTaskOnWorkerThread()
{
InitializeComponent();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
EtabsDataFormatting.ViewModel.SpliceViewModel data = new EtabsDataFormatting.ViewModel.SpliceViewModel();
for (int i = data.progressBarCounter; i < data.noOfData;)
{
(sender as BackgroundWorker).ReportProgress(i);
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pbStatus.Value = e.ProgressPercentage;
int perc = Convert.ToInt32(pbStatus.Value);
UpdateProgress(perc);
}
public void UpdateProgress(int percentage)
{
pbStatus.Value = percentage;
if (percentage == 100)
{
Close();
}
}
}
Here is part of my XAML code for the button to start calculations and run the progressbar
The command Binding = RunCalcBtn is bound to the calculation scripts, therefore, I have created a click to run the progress bar instead.
<Button x:Name = "ApplyButton" Margin="0 1 0 1" Content ="Start Calculation" Command="{Binding RunCalcBtn, Mode=TwoWay}" Click ="PrgBar_Click"/>
Progressbar XAML.cs button click
This part displays the progress bar, but it does not update.
private void PrgBar_Click(object sender, RoutedEventArgs e)
{
ProgressBar.ProgressBarTaskOnWorkerThread progressWindow = new ProgressBar.ProgressBarTaskOnWorkerThread();
progressWindow.Show();
}
Thank you so much for helping me in advance!
As Flithor has said, the best way to achieve this is with Progress<T>.
I give a short illustration of how to use this.
Firstly you need to create a Property in your View Model so that you can bind the ProgressBar's Value to something. Your ViewModel will need to implement INotifyPropertyChanged so that the Property set can invoke RaisePropertyChangedEvent.
Next create a Progress inside the method called by the Button click and pass it to your worker method. Use an ICommand for this, so that it can be bound to your Button (you don't need the Click event). Something like this:
var progress = new Progress<int>(percent =>
{
ProgressProperty = percent;
});
await Task.Run(() => myWorker(progress));
Finally within your worker method you periodically update the value like this:
private void myWorker(IProgress<int> progress)
{
progress.Report(1);
// ...
progress.Report(100);
}
By way of explanation: I used an integer, but you can also use a double if you want really fine calculations! The constructor of the Progress object takes the ProgressProperty (the name I gave to the property that gets bound to the ProgressBar) as a parameter. This means that when the worker calls Report(), the ProgressProperty is automatically updated with the new value, and hence can be reflected in the UI. Finally your worker method is invoked with await so that the UI is able to update on every incremented value.
For a very full explanation on Progress, see Stephen Cleary's blog
In MVVM WPF, you should do this to take full advantage of it:
View:
<Grid>
<ProgressBar Name="myProgressBar"
Minimum="0"
Value="{Binding ProgressBarValue,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Maximum="100"
Foreground="{Binding ColorState,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Background="#424242"
BorderBrush="Transparent"
BorderThickness="0"/>
<TextBlock Text="{Binding ElementName=myProgressBar, Path=Value,Mode=OneWay,UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:0}%}"
FontWeight="DemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
ViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Threading;
namespace YourNameSpace.Models
{
public class Device : INotifyPropertyChanged
{
public Device()
{
this.ProgressBarValue = 50; // Your ProgressBar Foreground will be "GREEN" automatically
// This is the
}
private double progressBarValue;
public double ProgressBarValue
{
get { return progressBarValue; }
set
{
progressBarValue = value;
if(progressBarValue < 50)
this.ColorState = "Red";
else if (progressBarValue >= 50)
this.ColorState = "Green";
NotifyPropertyChanged("ProgressBarValue");
}
}
private string colorState = "Transparent";
public string ColorState
{
get { return colorState; }
set { colorState = value; NotifyPropertyChanged("ColorState"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string Obj)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(Obj));
}
}
}
}
You can REMOVE this from your code:
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pbStatus.Value = e.ProgressPercentage;
int perc = Convert.ToInt32(pbStatus.Value);
UpdateProgress(perc);
}
public void UpdateProgress(int percentage)
{
pbStatus.Value = percentage;
if (percentage == 100)
{
Close();
}
}
And ONLY use this:
for (int i = data.progressBarCounter; i < 100; i++)
{
ProgressBarValue = i;
}
Your
ProgressBar Value
Progress Foreground Color
will be updated automatically.
I have a WPF application which is built on the MVVM design pattern.
I wish to implement a progress bar in the app, that follows the MVVM pattern.
Does any one have any suggestions on how to implement this?
Thanks in advance
Typically your UI would simply bind to properties in your VM:
<ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}"
Visibility="{Binding ProgressVisibility}"/>
Your VM would use a BackgroundWorker to do the work on a background thread, and to periodically update the CurrentProgress value. Something like this:
public class MyViewModel : ViewModel
{
private readonly BackgroundWorker worker;
private readonly ICommand instigateWorkCommand;
private int currentProgress;
public MyViewModel()
{
this.instigateWorkCommand =
new DelegateCommand(o => this.worker.RunWorkerAsync(),
o => !this.worker.IsBusy);
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
}
// your UI binds to this command in order to kick off the work
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
public int CurrentProgress
{
get { return this.currentProgress; }
private set
{
if (this.currentProgress != value)
{
this.currentProgress = value;
this.OnPropertyChanged(() => this.CurrentProgress);
}
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// do time-consuming work here, calling ReportProgress as and when you can
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
}
Use a ProgressBar control and bind its Value property to a property of the ViewModel:
View
<ProgressBar Minimum="0" Maximum="0" Value="{Binding CurrentProgress}" />
ViewModel
private double _currentProgress;
public double CurrentProgress
{
get { return _currentProgress; }
private set
{
_currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
Add two properties to your VM:
bool IsProgressBarVisible
double ProgressValue
If you start a long time operation in your VM, set the IsProgressBarVisible-property to true and set the ProgressValue periodical to the current progress value. Try to calculate a value between 0 and 100. This has the advantage that you don't have to provide a minimum and maximum value.
After the asynchronous operation has completed, set the IsProgressBarVisible to false.
In XAML, bind to these two properties. Use a value converter to convert the boolean visibility to a Visibility.
<ProgressBar Value="{Binding ProgressValue}" Visibility="{Binding IsProgressBarVisible,Converter={StaticResource BooleanToVisibility_ValueConverter}}"/>
I have test app which adding items to ListBox in loop. Now, ListBox updates, when all items added to ObservableCollection. I need to update ListBox when every item added. Please, help russian guy =)
Here is my code:
MainWindow.xaml
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="432,288.04,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ListBox x:Name="urlsListBox" ItemsSource="{Binding Urls}" HorizontalAlignment="Left" Height="300" Margin="10,10,0,0" VerticalAlignment="Top" Width="417"/>
</Grid>
MainWindow.xaml.cs
/// <summary>
/// Логика взаимодействия для MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ViewModel model = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.GetUrls();
}
}
ViewModel.cs
class ViewModel
{
public ObservableCollection<Url> Urls { get; set; }
public ViewModel()
{
Urls = new ObservableCollection<Url>();
}
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
//Thread.Sleep(300);
}
}
}
public class Url
{
public string link { get; set; }
}
I think you need to notify the UI about the change happening in the URL observable collection each time it adds a new item.
If you have implemented INotifyPropertyChanged in your view model then register the observable collection with any change happening in it.
private ObservableCollection<Url> _urls;
public ObservableCollection<Url> Urls
{ get
{
return _urls;
}
set
{
_urls = value;
OnPropertyModified("Urls");
}
}
You want UI to update on every item addition in collection i guess.
Looking at Thread.Sleep(300), i am assuming you want to wait for 300 milliseconds before adding another item to collection.
DispatcherTimer is what you are looking for then. This sample will work:
public void GetUrls()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 300);
int i = 0;
timer.Tick += (s, e) =>
{
Urls.Add(new Url { link = i.ToString() });
++i;
if (i == 5)
timer.Stop();
};
timer.Start();
}
This will add item in collection after every 300 ms and will stop timer once item count reaches to 5.
Like i mentioned in comment
Dispatcher associated with UI thread can process one operation at a
time. When it's adding item to collection, it can't process Rendering
operation at same time. Hence GUI not getting refreshed. Usage of DispatcherTimer gives some time to rendering operations to get processed.
Another approach without DispatcherTimer would be to enqueue empty delegate on UI dispatcher with Render priority so that all operations with priority higher than Render (including Render) gets processed before moving to addition of another item in collection.
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
App.Current.Dispatcher.Invoke((Action)(() => { }),
DispatcherPriority.Render);
}
}
I am working on a support tool that displays multiple TabItems in a TabControl. Each TabItem represents an employee, and within each of these employee Tabs there is another TabControl which contains additional TabItems. These TabItems represent Outlook folders for that employee (like "Working", "Completed", etc). Each of these folder TabItems contains a ListBox that is bound to an ObservableCollection of MailItems that pertain to that Outlook folder. These aren't huge collections - only a dozen or so items per ListBox. Although, in total, across all TabItems there could conceivably be 100 items or so.
The way I have currently built the application is that the app fires up and populates the screen with the appropriate employee tabs and sub-tabs. This process is fairly quick and I'm happy. I have created a Static Global.System.Timer that all folder TabItem's code-behind is synchronized with. So every 5 minutes the application will clear all ObserverableCollections and re-scan the Outlook folders.
The problem is that the scan process brings the application to a halt. I have tried using a BackgroundWorker to gather mail from Outlook as a background process, then pass a List<MailItem> object to a RunWorkerCompleted method that then runs a this.Dispatcher.BeginInvoke process that clears the respective ObservableCollection then adds the items from the List<MailItem> back to the ObservableCollection. I've even set this Dispatcher to the lower priority.
Despite this, the application is very clunky feeling during the scan/populate ListBox process. I am unclear on how to better design this and I admit I am somewhat new to this. I realize that clearing out each of the ObservableCollections is inefficient, but Outlook folder change events aren't particularly reliable, so I need to do a brute force re-scan every once in a while to insure all MailItems are represented.
Below is my code for the WPF control that contains the ListBox. Keep in mind that there are about 10 of these ListBox controls active at once.
// This entire UserControl is essentially a ListBox control
public partial class TicketListView : UserControl
{
private TicketList _ticketList; //this is the ObservableCollection object
private Folder _folder; // Outlook Folder
public TicketListView(Folder folder)
{
InitializeComponent();
_ticketList = this.FindResource("TicketList") as TicketList;
_folder = folder;
GlobalStatics.Timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Refresh();
}
private void Refresh()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
List<MailItem> tickets = new List<MailItem>();
string filter = TicketMonitorStatics.TicketFilter(14);
Items items = _folder.Items.Restrict(filter);
try
{
foreach (MailItem mi in items.OfType<MailItem>())
{
tickets.Add(mi);
}
}
catch (System.Exception) { }
e.Result = tickets;
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
List<MailItem> tickets = e.Result as List<MailItem>;
this.Dispatcher.BeginInvoke(new System.Action(delegate
{
_ticketList.Clear();
PopulateList(tickets);
}));
BackgroundWorker worker = sender as BackgroundWorker;
worker.Dispose();
}
private void PopulateList(List<MailItem> ticketList)
{
foreach (MailItem mi in ticketList)
{
this.Dispatcher.BeginInvoke(new System.Action(delegate
{
_ticketList.Add(mi);
}), System.Windows.Threading.DispatcherPriority.SystemIdle);
}
}
}
for your requirement, specially in WPF, you shouldn't be using timer or background worker to keep the view responsive. Instead you should be designing your app with MVVM pattern. MVVM is Model, View and View Model, where if there is a change in the model, the model updates the View Model, and the view model updates the view. This is done by just inheriting a "INotifyPropertyChanged" Interface.
Here is a simple example
Xaml part:
<Window x:Class="SimpleMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="259" Width="445">
<Grid Margin="0,0,2,-3">
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="10,33,0,0" VerticalAlignment="Top" Width="75"/>
<Label x:Name="label" Content="{Binding Name}" HorizontalAlignment="Left" Margin="103,23,0,0" VerticalAlignment="Top" Width="220" BorderBrush="Black" BorderThickness="1" Height="32" Padding="0"/>
</Grid>
</Window>
And The .cs Part
using System.ComponentModel;
using System.Windows;
namespace SimpleMVVM
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private AnimalViewModel _animal= new AnimalViewModel ();
public MainWindow()
{
InitializeComponent();
DataContext = _animal;
button.Click += (sender, e) => _animal.Name = "Taylor" ;
}
}
public class AnimalViewModel : AnimalModel
{
public AnimalViewModel ()
{
}
}
public class AnimalModel : INotifyPropertyChanged
{
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, args);
}
}
}
}
No imagine that the button click is the update triggered by scheduler, you model get updated first that trigger a property changed event to update the view.
Using this pattern your code will be much reliable.
I hope this helps.
Regards
Jegan
I want to bind the value property of a WPF ProgressBar to a dependency property that is updated during a long running process. If the long running process is called from the main thread then this blocks the UI (and hence the ProgressBar) from updating until the process completes - preventing the desired progress through the process being shown. The long running process also cannot be run by spinning a seperate thread because it is not possible to update a dependency property from a different thread to its owner (i.e. the thread on which it was created).
In the code below, when the button is clicked, the long running process runs and the progress bar jumps from 0% to 100% when it completes. Instead, I want to be able to click the button and have the progress bar show the progress through the long running progress (i.e. not just updating from 0% to 100% when the process finishes but showing a smooth progression).
MainWindow.xaml
<Window x:Class="ProgressBarTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<Button Width="200" Height="50" x:Name="btnRun" Click="btnRun_Click">Run Process</Button>
<ProgressBar Width="200" Height="20" x:Name="pbProgress" Minimum="0" Maximum="100" Value="{Binding Path=MyFoo.ProgressValue}"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Threading;
namespace ProgressBarTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public Foo MyFoo { get; set; }
public MainWindow()
{
MyFoo = new Foo();
InitializeComponent();
}
private void btnRun_Click(object sender, RoutedEventArgs e)
{
btnRun.IsEnabled = false;
MyFoo.LongRunningProcess(); // Since this runs on same thread as UI, progress bar does not update until the long running process completes.
btnRun.IsEnabled = true;
}
}
public class Foo : DependencyObject
{
public static readonly DependencyProperty ProgressValueProperty = DependencyProperty.Register("ProgressValue", typeof(double), typeof(Foo));
public double ProgressValue
{
get { return (double)GetValue(ProgressValueProperty); }
set
{
SetValue(ProgressValueProperty, value);
}
}
public Foo()
{
ProgressValue = 0;
}
public void LongRunningProcess()
{
do
{
ProgressValue += 1;
Thread.Sleep(30);
}
while (ProgressValue < 100);
}
}
}
P.S. I know there is a way I can do this by passing the ProgressBar instance as an argument to the long running process so that it can update it directly through Dispatcher.Invoke, but that is not what I want. I want to have the progress bar update through binding to a dependency property.
Thanks
Sean
I'll try to be nicer than Euphoric.
You need to run your LongRunningProcess using a BackgroundWorker that runs the process on a different thread and then update the property using the ProgressChanged event
// This event handler updates the progress bar.
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
MyFoo.ProgressValue = e.ProgressPercentage;
}
The UI needs to be updated from the main thread. Hence you need to use the Dispather to update the property.
Use this from within the LongRunningProcess() to put the call on the Dispatcher queue:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action<double>(UpdateData), value);
and the implement the updating method like so:
private void UpdateData(double value){
MyFoo.ProgressValue = value;
}
Hope this helps.
I Dunno if you found a solution to your issue, but this is how i have solved a simular problem in the past. This enables you to bind to your DP and just update the normal property
public static readonly DependencyProperty progressProperty = DependencyProperty.Register("progress", typeof(int), typeof(this));
public int progress
{
get
{
return (int)this.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
(DispatcherOperationCallback)delegate { return GetValue(progressProperty ); },
progressProperty );
}
protected set
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { SetValue(progressProperty , value); }, value);
}
}