WPF ProgressBar not updating its progress status - wpf

I am using a wpf UserControl to replace text in files in selected drawing files of AutoCAD. The wpf control is to display a status (ProgressBar) indicating the number of files processed at any given time. So I put up the following code, but the ProgressBar simply does not show any progress. Here is the part of relevant code.
XAML:
<ProgressBar HorizontalAlignment="Stretch" Name="pgrSearch" Minimum="0" Maximum="{Binding Path=ProgressBarMaximum}"
Value="{Binding Path=ProgressBarCurrent}" Height="20" Margin="10" />
CodeBehind:
public partial class ReplaceUserControl : UserControl, INotifyPropertyChanged {
public ReplaceUserControl() {
InitializeComponent();
this.DataContext = this;
}
....
private int _progressBarMaximum;
public int ProgressBarMaximum {
get { return _progressBarMaximum; }
set { _progressBarMaximum = value; RaisePropertyChanged("ProgressBarMaximum"); }
}
private int _progressBarCurrent;
private int ProgressBarCurrent {
get { return _progressBarCurrent; }
set { _progressBarCurrent = value; RaisePropertyChanged("ProgressBarCurrent"); }
}
private void ReplaceTextInFiles() { //Called from Button_Click Handler
....
ProgressBarMaximum = filesList.Count - 1;
SearchReplaceWorker replaceWorker = new SearchReplaceWorker(); //The Work Horse
replaceWorker.FileProcessed += new FileProcessedEventHandler(worker_FileProcessed); //Raised by Work Horse when each file is processed
BackgroundWorker workerThread = new BackgroundWorker(); //The Background Worker Thread
workerThread.DoWork += (o, e) => {
replaceWorker.ReplaceTextInFiles(SearchText, ReplaceText, filesList, ReportFolderPath, MatchCase, MatchSubstring);
};
workerThread.RunWorkerAsync(); //Start the Background Thread Async
}
void worker_FileProcessed(object sender, EventArgs e) {
ProgressBarCurrent = ProgressBarCurrent + 1; //Update the ProgressBar status
}
Why doesn't the ProgressBar update itself when the ProgressBarCurrent is incremented as indicated above in code.
Edit:
In Order to process the ProgressBar update code on UI thread, I changed my code to use BackgroundWorker.ReportProgress() as given under.
CodeBehind for UserControl:
private void ReplaceTextInFiles() { //Called from Button_Click()
if (!Directory.Exists(SearchFolderPath)) {
MessageBox.Show("Invalid Directory Selected for Search");
return;
}
if (!Directory.Exists(ReportFolderPath)) {
MessageBox.Show("Invalid Directory Selected for Report File");
return;
}
List<string> filesList = null;
try {
if (LookInSubFolders) {
filesList = Directory.GetFiles(#SearchFolderPath, "*.dwg", SearchOption.AllDirectories).ToList();
}
else {
filesList = Directory.GetFiles(#SearchFolderPath, "*.dwg", SearchOption.TopDirectoryOnly).ToList();
}
}
catch (Exception ex) {
MessageBox.Show("Error Occurred getting the files list. Contact Admin");
}
pgrSearch.Visibility = Visibility.Visible;
ProgressBarMaximum = filesList.Count - 1;
SearchReplaceWorker replaceWorker = new SearchReplaceWorker();
BackgroundWorker workerThread = new BackgroundWorker();
workerThread.WorkerReportsProgress = true;
workerThread.ProgressChanged += (o, e) => { //This event handler gets called correctly.
ProgressBarCurrent++;
};
workerThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(workerThread_RunWorkerCompleted);
workerThread.DoWork += (o, e) => {
replaceWorker.ReplaceTextInFiles(workerThread, SearchText, ReplaceText, filesList, ReportFolderPath, MatchCase, MatchSubstring);
};
workerThread.RunWorkerAsync();
}
The BackgroundWorker:
public void ReplaceTextInFiles(BackgroundWorker workerThread, string searchText, string replaceText, List<string> filesList, string reportPath,
bool MatchCase, bool MatchSubstring) {
...
workerThread.ReportProgress(50);
}
Still the ProgressBar doesn't update itself.

I created a test project with your initial code. After a while I found that you have declared the ProgressBarCurrent property as private. After changing to public it worked for me. So it doesn't seem necessary to update the property on the UI thread. It looks like a Dispatcher.Invoke call is made internally when reading back the updated property value.

Related

How to pass object to process output event

I have an ObservableCollection<Conversion> Queue, bound to ListBox control with ItemTemplate containing a TextBlock and a Button. When the button is clicked, a Win32 process starts. This process has an ErrorDataReceived event handler method which reads the process output and is supposed to update the PercentComplete property of the Conversion object in the collection. PercentComplete is bound to TextBlock's Text property.
How do I update PercentComplete from Win32 process event? I was hoping to pass the Conversion object to the ErrorDataReceived event handler, but the DataReceivedEventArgs only has a single Data property of type string.
Here is the code:
XAML:
<ListBox ItemsSource="{Binding Queue}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding PercentComplete}" />
<Button Command="convertor:Commands.RunConversion">Run</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code-behind:
private ObservableCollection<Conversion> _queue;
public ObservableCollection<Conversion> Queue
{
get { return _queue; }
set
{
_queue = value;
RaisePropertyChange("Queue");
}
}
private Conversion _selectedItem;
public Conversion SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChange("SelectedItem");
}
}
private void RunConversion_Executed(object sender, ExecutedRoutedEventArgs e)
{
...
using (var ffmpeg = new Process())
{
...
ffmpeg.EnableRaisingEvents = true;
ffmpeg.ErrorDataReceived += FfmpegProcess_ErrorDataReceived;
// I realize it is weird I am working with ErrorDataReceived instead
// of OutputDataReceived event, but that's how ffmpeg.exe rolls.
ffmpeg.Start();
ffmpeg.BeginErrorReadLine();
}
}
private void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
var processOutput = e.Data;
var percentComplete = ParsePercentComplete(processOutput);
//TODO Pass percentComplete to Conversion.PercentComplete!?
}
Class:
public class Conversion : INotifyPropertyChanged
{
private double _percentComplete;
public double PercentComplete
{
get { return _percentComplete; }
set
{
_percentComplete = value;
RaisePropertyChange("PercentComplete");
}
}
public void RaisePropertyChange(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Ok, I solved it. The key to the solution was the process.Id which provides the reference to the process specific to the ObservableCollection item.
Specifically, I expanded the Conversion with Process Process property to store the information of that particular process, and then I can find the item in the collection and update its properties from process output in process' event handler.
Here is the updated code:
Code-behind:
private ObservableCollection<Conversion> _queue;
public ObservableCollection<Conversion> Queue
{
get { return _queue; }
set
{
_queue = value;
RaisePropertyChange("Queue");
}
}
private Conversion _selectedItem;
public Conversion SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChange("SelectedItem");
}
}
private void RunConversion_Executed(object sender, ExecutedRoutedEventArgs e)
{
...
var ffmpeg = new Process();
ffmpeg.EnableRaisingEvents = true;
ffmpeg.ErrorDataReceived += FfmpegProcess_ErrorDataReceived;
ffmpeg.Start();
conversion.Process = ffmpeg; // This is new
ffmpeg.BeginErrorReadLine();
}
private void FfmpegProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
var processOutput = e.Data;
var percentComplete = ParsePercentComplete(processOutput);
var processId = (sender as Process).Id; // These three lines are new
var conversion = Queue.Where(c => c.Process.Id == processId).FirstOrDefault();
conversion.PercentComplete = percentComplete; // WTF!!!!
}
Class
public class Conversion : INotifyPropertyChanged
{
private double _percentComplete;
public double PercentComplete
{
get { return _percentComplete; }
set
{
_percentComplete = value;
RaisePropertyChange("PercentComplete");
}
}
// New property
private Process _process;
public Process Process
{
get { return _process; }
set
{
_process= value;
RaisePropertyChange("Process");
}
}
public void RaisePropertyChange(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}

WPF C# Cefsharp Ver. 71, each load of user control creates new CefSharp.BrowserSubprocess.exe

Please check the code snippets below, this get loaded everytime i navigate to my view(user control) and it creates new CefSharp.BrowserProcess.exe on each load and renders last visited URL.
Problem with is is that it does not maintain the session storage of the site (URL) And load is incorrect with data is lost.
viewModel (main) code:
private void OnLoad()
{
IsBusy = true;
try
{
if (string.IsNullOrEmpty(TieAddress))
{
TieAddress = _serviceJournalsBaseSettings.GetTieUrl();
}
var cookieManager = Cef.GetGlobalCookieManager();
Cookie cookie = new Cookie
{
Name = BaseSettings.GetTieCookieName(),
Value = BaseSettings.GetTieCookie()
};
cookieManager.SetCookie(BaseSettings.GetTieCookieUrl(), cookie);
}
catch (Exception ex)
{
ShowErrorNotification(ex.Message);
}
finally
{
IsBusy = false;
}
}
View (User control) Code:
<wpf:ChromiumWebBrowser
Grid.Row="1" Grid.ColumnSpan="2"
x:Name="BrowserTieView"
Address="{Binding TieAddress, Mode=TwoWay}"
Title="Browser Tie View"
AllowDrop="True"/>
View.Xaml.cs
public partial class ServiceJournalsView : UserControl
{
public ServiceJournalsView()
{
InitializeComponent();
BrowserTieView.DownloadHandler = new DownloadHandler();
BrowserTieView.BrowserSettings = new BrowserSettings()
{
ApplicationCache = CefState.Enabled,
FileAccessFromFileUrls = CefState.Enabled,
Javascript = CefState.Enabled,
LocalStorage = CefState.Enabled,
WebSecurity = CefState.Disabled,
JavascriptCloseWindows = CefState.Enabled,
JavascriptDomPaste = CefState.Enabled,
};
BrowserTieView.LoadError += (sender, args) =>
{
// Don't display an error for downloaded files.
if (args.ErrorCode == CefErrorCode.Aborted)
{
return;
}
// Display a load error message.
var errorBody = string.Format(
"<html><body bgcolor=\"white\"><h2>Failed to load URL {0} with error {1} ({2}).</h2></body></html>",
args.FailedUrl, args.ErrorText, args.ErrorCode);
args.Frame.LoadHtml(errorBody, base64Encode: true);
};
Unloaded += async delegate (object sender, RoutedEventArgs args)
{
BrowserTieView.WebBrowser.Dispose();
BrowserTieView.Dispose();
await Task.Delay(10);
};
}
public ServiceJournalsViewModel VMServiceJournalsViewModel
{
get => (ServiceJournalsViewModel) DataContext;
set { DataContext = value; }
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
BrowserTieView.RegisterJsObject("serviceJournalsJsModel", VMServiceJournalsViewModel.ServiceJournalsJsModel);
}
catch (Exception ex)
{
}
}
}
As per discussion in the comments of the question posted( and as per #amaitland) Multiple instances of Cefsharp.BrowserSubprocess.exe is perfectly normal.

Updating UI from a background thread which is called in a loop in main UI when the thread finishes

I have a WinForms application that is calling a business class method that performs some heavy duty action taking about 5 seconds for each call. The main form calls this method in a loop. This loop can run from 10 times to maybe up to 10 thousand times.
The WinForms application sends a parameter to the business class and has an area to display the time taken for each method call and what the value returned by the method. How do I inform my main window and update a text area in the main winform with what the method has returned for each call?
Currently the data comes all at once after all the threads have finished. Is there a way to update the UI for all the iterations of the loop once the each call is done? I don't mind if it is done sequentially also.
The FORM
HeavyDutyClass hd;
public Form1()
{
InitializeComponent();
hd = new HeavyDutyClass();
}
//BUTTON CLICK
private void Start_Click(object sender, EventArgs e)
{
int filecount = 5000; //BAD - opening 5000 threads! Any other approach?
hd.FileProcessed += new EventHandler(hd_FileProcessed);
var threads = new Thread[filecount];
for (int i = 0; i < filecount; i++)
{
threads[i] = new Thread(() => { hd.LongRunningMethod(); });
threads[i].Start();
}
}
//BUSINESS CLASS EVENT THAT FIRES WHEN BUSINESS METHOD COMPELTES
void hd_FileProcessed(object sender, EventArgs e)
{
if (dgv.InvokeRequired)
{
dgv.Invoke((MethodInvoker)delegate { UpdateGrid(); });
}
}
private void UpdateGrid()
{
dgv.Rows.Add(1);
int i = dgv.Rows.Count;
dgv.Rows [ i-1].Selected = true;
dgv.FirstDisplayedScrollingRowIndex = i - 1;
}
The business HeavyDuty class
public event EventHandler FileProcessed;
public HeavyDutyClass()
{
}
protected virtual void OnMyEvent(EventArgs e)
{
if (FileProcessed != null)
{
FileProcessed(this, e);
}
}
public bool LongRunningMethod()
{
for (double i = 0; i < 199990000; i++)
{
//time consuming loop
}
OnMyEvent(EventArgs.Empty);
return true;
}
Add a Winforms Project, Drop a Label Control on the Form , Copy-Paste this code and Hit F5
[EDIT]: Updated with the business class comment from the user
NB: My form class is named Form3. You may have to change your Program.cs or vice-versa.
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
}
With this you can achieve Cancel functionality also very easily.
Observe that during initialisation, I set the WorkerSupportsCancellation = true & then I check for _worker.CancellationPending in the DoWork. So, if you want to cancel the process by a Cancel Button click, then you will write this code in the button handler- _worker.CancelAsync();

Show Splash Screen & Progress Bar with Percentage using MVVM & WPF

I need to show Splash Screen with Image & progress bar.
In my application start up i have the code as below to show the main window.
SplashScreenWindowViewModel vm = new SplashScreenWindowViewModel();
AutoResetEvent ev = new AutoResetEvent(false);
Thread uiThread = new Thread(() =>
{
vm.Dispatcher = Dispatcher.CurrentDispatcher;
ev.Set();
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate()
{
SplashScreenWindow splashScreenWindow = new SplashScreenWindow();
splashScreenWindow = new SplashScreenWindow();
splashScreenWindow.Show();
splashScreenWindow.DataContext = vm;
vm.InstigateWorkCommand.Execute(null);
});
Dispatcher.Run();
});
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.IsBackground = true;
uiThread.Start();
ev.WaitOne();
In my main viewmodel i have code as below
class MainviewModel : viewmodelbase
{
rivate string _message;
private object content;
private readonly BackgroundWorker worker;
private readonly ICommand instigateWorkCommand;
public SplashScreenWindowViewModel()
{
this.instigateWorkCommand = new
RelayCommand(() => this.worker.RunWorkerAsync(), () => !this.worker.IsBusy);
this.worker = new BackgroundWorker { WorkerReportsProgress = true };
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
_message = "0 % completed";
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private double _currentProgress;
public double CurrentProgress
{
get { return this._currentProgress; }
set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
RaisePropertyChanged("CurrentProgress");
}
}
}
private int _progressMax;
public int ProgressMax
{
get { return this._progressMax; }
set
{
if(this._progressMax != value)
{
this._progressMax = value;
RaisePropertyChanged("ProgressMax");
}
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// calling my long running operation
DAL.dotimeconsumingcode();
worker.ReportProgress((int)e.argument);
}
public string Message
{
get
{
return _message;
}
set
{
if (Message == value) return;
_message = value;
RaisePropertyChanged("Message");
}
}
public object Content
{
get
{
return content;
}
set
{
if (Content == value) return;
content = value;
RaisePropertyChanged("Content");
}
}
public Dispatcher Dispatcher
{
get;
set;
}
}
MY UI has one user control with progress bar and one splash main window.
when my long running operation is completed , my Main window(main application) is opened.
//User Control
<ProgressBar Height="27" Value="{Binding CurrentProgress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Margin="53,162,57,0" Name="progressBar" Grid.Row="1"
Maximum="{Binding Path=ProgressMax, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ProgressVisibility}" />
//SplashWindow
<localView:usercontrol/>
My Problem is
ProgressChangedevent is not firing and % completion is not showing up in the text block either. Please help
You have not registered a complete handler and you are not calling progress properly.
This sample from MSDN covers it all.
BackGroundWorker

DataBinding Image.Source to a PhotoChooserTask result not working

I have the following class:
public class Sticky : INotifyPropertyChanged {
// ... some members
private BitmapImage _frontPic;
[DataMember]
public BitmapImage FrontPic {
get {
return _frontPic;
}
set {
_frontPic = value;
Changed("FrontPic");
Changed("FrontBrush");
}
}
}
I'm trying to databind it to this XAML:
<Image Width="173" Height="173" Source="{Binding FrontPic}" />
after launching a PhotoChooserTask with this code in my PhoneApplicationPage:
public Sticky Sticky { get; set; }
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) {
Sticky = new Sticky();
DataContext = Sticky;
}
private void ShowFrontPicPicker(object sender, RoutedEventArgs e) {
var t = new PhotoChooserTask();
t.PixelHeight = 173;
t.PixelWidth = 173;
t.ShowCamera = true;
t.Completed += (s, ev) => {
if (ev.TaskResult == TaskResult.OK) {
var img = new BitmapImage();
img.SetSource(ev.ChosenPhoto);
Sticky.FrontPic = img;
}
};
t.Show();
}
However, my image remains blank. If I assign the Image.Source property directly to the Image without databinding, everything works. Databinding other properties works, it's just the image that seems to be the problem. How can I make the DataBinding on the image work?
Found the problem! The completed callback for PhotoChooserTask does not excecute in the UI thread, so a call to Dispatcher.BeginInvoke must be added:
t.Completed += (s, ev) => Dispatcher.BeginInvoke(() => {
// do stuff...
});

Resources