I want to load a text file in WPF-RichTextbox using BackgroundWorker
Here is my code :
private delegate void update();
private void reader()
{
StreamReader str = new StreamReader("C:\\test.txt");
while (!str.EndOfStream)
{
richTextBox1.AppendText(str.ReadToEnd());
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
progress wnd = new progress();
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += delegate(object s, DoWorkEventArgs args)
{
update up = new update(reader);
richTextBox1.Dispatcher.Invoke(up);
};
bg.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
wnd.Close();
};
wnd.Show();
bg.RunWorkerAsync();
}
and for progress.xaml :
<Window x:Class="WpfApplication3.progress"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="progress" Height="192" Width="452" ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True" WindowStartupLocation="CenterScreen" WindowStyle="None">
<Grid>
<Label Content="loading ...." Height="28" HorizontalAlignment="Left" Margin="46,32,0,0" Name="label1" VerticalAlignment="Top" />
<ProgressBar Height="29" HorizontalAlignment="Left" Margin="46,78,0,0" Name="progressBar1" VerticalAlignment="Top" Width="325" IsIndeterminate="True" />
</Grid>
When I set wnd.showdialog() , it shows the indeterminate status of progress bar but the application does not load any text .
When I set wnd.show() , it loads the text but when loading , the progress bar freezes and
does not show indeterminate status.
Thanks for any help.
EDIT :
due to Servey and SLaks answers , i have updated the code but it has the same problem (it loads the text but when loading , the progress bar freezes and does not show indeterminate status.)
what is the problem? here is my updated code :
StringBuilder sb = new StringBuilder();
private void reader()
{
StreamReader str = new StreamReader("C:\\test.txt");
while (!str.EndOfStream)
{
sb.Append(str.ReadToEnd());
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
progress wnd = new progress();
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += delegate(object s, DoWorkEventArgs args)
{
reader();
};
bg.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
richTextBox1.AppendText(sb.ToString());
wnd.Close();
};
wnd.Show();
bg.RunWorkerAsync();
}
Ok I've had a look at your uploaded code and your problem is most probably with how you're doing the loads. As far as your loading goes, the following code does the job as you want it to:
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
Loaded -= OnLoaded;
var wnd = new ProgressWindow();
var dispatcher = wnd.Dispatcher;
Task.Factory.StartNew(() =>
{
using (var stream = new StreamReader("C:\\test.txt"))
return stream.ReadToEnd();
}).ContinueWith(x =>
{
//Main window dispatcher
Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => richTextBox1.AppendText(x.Result)));
//progress window dispatcher
dispatcher.Invoke(DispatcherPriority.DataBind, new Action(wnd.Close));
});
wnd.ShowDialog();
}
While this works as intended, the freeze will happen regardless (i had a freeze of several seconds loading an 800kb text file). The problem is your use of RichTextBox. Judging by your code, you have no idea how wfp works and you're trying to work in a typical winform style.
RichTextBox in Wpf is very different from winforms RichTextBox. Similarly, a TextBlock is very different from a typical Label. If all you want is color and other formatting options, a TextBlock is very capable of doing that on its own and is far faster than building RichTextBox the way you're building.
If you must build RichTextBox from a text file, you need to learn about FlowDocument and how to create one from a text file yourself in order to avoid the freeze. The alternate to this solution will be running two UI threads (one for each window) and you're very likely to find that a lot more hassle than its worth.
Before attempting this sort of thing, you should sort your WPF basics out and learn the typical MVVM pattern before taking a dive into threaded WPF world. And again, avoid RTB in WPF if you can. The base controls in WPF are far more powerful than their winforms counterparts if you know how to use them.
You're not actually doing any work in the background. You're starting a background worker and the the only thing that the worker is doing in the background is scheduling an operation to run on the UI thread, and that operation is where you do all of your non-UI work. Since you're doing non-UI work on the UI thread, no other graphical operations can be performed until it finishes.
You'll want to read from the file in the BGW's do work event, store it in memory, and then in the Completed event, that runs in the UI thread, you can then place that data into the UI.
Related
Long story short, due to shifting business requirements, I need to be able to show the end user the progress of a file archival process controlled by a C# console application. The console app essentially gets a list of local files from a db and then copies them to an archive location. This was originally supposed to be a background process triggered by a scheduled task. Now, however, users can launch it manually with various arguments from the command line, so I was recently tasked with letting the user know the status of the archive process.
I thought I would just use a WPF ProgressBar control for this, but now I'm going in circles trying to sort out the best way to do this. I've been working with the answer from #JamesWilkins here: WPF window from a Console project?
I've added the ProgressBar window to the console application, and added the following to the Main method in the console(super simplified for clarity):
[STAThread]
static void Main(string[] args)
{
// EXISTING CONSOLE LOGIC
ParseCommandLineArgs();
Configure();
// ADDED
InitializeWindows(); // opens the WPF window and waits here
// EXISTING CONSOLE LOGIC
BeginArchival();
}
static void InitializeWindows()
{
WinApp = new Application();
WinApp.Run(ProgressBar = new ProgressBar()); // blocking call
}
Then in the ProgressBar.xaml code behind:
public partial class ProgressBar : Window
{
public ProgressBar()
{
InitializeComponent();
}
private void ProgressBar_OnContentRendered(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
// foreach file that is archived, report progress to the
// ProgressBar.
for (int i = 0; i < 100; i++)
{
(sender as BackgroundWorker).ReportProgress(i);
}
}
private void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
PbArchiveStatus.Value = e.ProgressPercentage;
}
}
The process waits at the InitializeWindows() method until the progress bar is closed, so it doesn't hit any of the archive logic that I need the progress bar to show progress for. It seems that I essentially need to put all of the existing console logic inside the ProgressBar.worker_DoWork() method, but at this point my brain is starting to hurt so I thought I'd reach out.
Am I on the right track, or is there a better way to add a GUI-based progress bar to a console utility? Let me know if I can clarify anything at all.
I created a BackgroundWorker (mainBw) in my UI (WPF) thread. It has an infinite loop where it sleeps for 1.5 sec and calls a function via Application.Current.Dispatcher.Invoke which just outputs text from the "global" text variable to a TextBox.
Also before the loop it created another (child) BackgroundWorker which ReportsProgress, in ProgressChanged event handler it modifies the text variable.
I thought that it will not work because there is no anything like WinForms Application.DoEvents() in the mainBw loop so it can't process the event handler. But it works. Why?
Here is the code:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WpfApplication6
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private BackgroundWorker mainBw = new BackgroundWorker();
private void Button_Click(object sender, RoutedEventArgs e)
{
mainBw.DoWork += MainBwOnDoWork;
mainBw.RunWorkerAsync();
btn.IsEnabled = false;
}
private string text = "abc";
private void MainBwOnDoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += BwOnDoWork;
bw.ProgressChanged += BwOnProgressChanged;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
while (true)
{
Thread.Sleep(1500);
text += " main ";
Application.Current.Dispatcher.Invoke(new Action(() => { WriteToUIThread(); }));
}
}
private void WriteToUIThread()
{
tbox.Text = DateTime.Now + " " + text + Environment.NewLine + tbox.Text;
}
private void BwOnProgressChanged(object sender, ProgressChangedEventArgs e)
{
text += e.UserState.ToString();
}
private void BwOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
while (true)
{
Thread.Sleep(3000);
(sender as BackgroundWorker).ReportProgress(0, "child");
}
}
}
}
// XAML
<Window x:Class="WpfApplication6.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">
<Grid>
<Button Name="btn" Content="Button" HorizontalAlignment="Left" Height="105" Margin="43,47,0,0" VerticalAlignment="Top" Width="165" Click="Button_Click"/>
<TextBox Name="tbox" HorizontalAlignment="Left" Height="114" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="456" Margin="27,182,0,0"/>
</Grid>
</Window>
BackgroundWorker uses a universal way to get code to run on the UI thread, it uses the static SynchronizationContext.Current property to find a synchronization provider. ReportProgress() uses its Post() method to marshal the call.
If you run a Winforms app then the Current property will reference an instance of WindowsFormsSynchronizationContext class. Automatically installed when you create a Form or call Application.Run(). It uses Control.Begin/Invoke() to implement the Post and Send methods.
And if you run a WPF app then the Current property will reference an instance of DispatcherSynchronizationContext, it uses Dispatcher.Begin/Invoke().
So this just works automagically.
It works because the BackgroundWorker does work in a background thread (hence the name). Since it's not running in the UI thread, it's not blocking the UI thread, it is just sending short methods to be run in the UI thread every once in a while.
That said, it's still not a particularly well designed approach to solving the problem. If you want to run some code every 3 seconds just use a Timer instead. If you use the timer in the Forms namespace it will fire it's event in the UI thread on your behalf.
It works, because the BackgroundWorker runs - as its name says - in a background thread independently fromo the main UI thread.
The ReportProgress-event gets marshalled to the UI thread so that this event can easily be handled, without calling Invoke-methods on the controls involved.
Contraray, the Application.DoEvents()-method in WinForms allowed the process to handle other messages for perfoming long going operations in main thread, without using a background thread.
I have a code that fetches tweets from a specific Twitter account using Tweetsharp library, creates instance of a custom UserControl and post tweet text to that UserControl then add it to a StackPanel.
However, I have to get a lot of tweets and it seems that the application would freeze while adding user controls to the StackPanel. I tried using BackgroundWorker, but I wasn't lucky until now.
My code :
private readonly BackgroundWorker worker = new BackgroundWorker();
// This ( UserControl ) is used in the MainWindow.xaml
private void UserControl_Loaded_1(object sender, RoutedEventArgs e)
{
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
int usrID;
var service = new TwitterService(ConsumerKey, ConsumerSecret);
service.AuthenticateWith(AccessToken, AccessTokenSecret);
ListTweetsOnUserTimelineOptions options = new ListTweetsOnUserTimelineOptions();
options.UserId = usrID;
options.IncludeRts = true;
options.Count = 10;
twitterStatuses = service.ListTweetsOnUserTimeline(options);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
foreach (var item in twitterStatuses)
{
TweetViewer tweetViewer = new TweetViewer(); // A UserControl within another UserControl
tweetViewer.Tweet = item.Text;
tweetViewer.Username = "#stackoverflow";
tweetViewer.RealName = "Stack Overflow"
tweetViewer.Avatar = ImageSourcer(item.Author.ProfileImageUrl);
stackPanel.Children.Add(tweetViewer);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
What I want to do now is to solve the problem of not being able to perform the code contained in worker_RunWorkerCompleted within a BackgroundWorker but every time I try to perform it using a BackgroundWorker it fails & gives me errors like :
The calling thread must be STA, because many UI components require this.
I tried also using a STA System.Threading.Thread instead of the BackgroundWorker but without luck!
What am I missing ? I'm really new to WPF and I may be ignoring something important.
You get this exception because your background worker uses a new thread, and this thread is different than the main UI thread.
To simplify the error message says that you cannot change your UI element from another thread, they are independant.
This answer will solve your problem.
I also found this answer from #Marc Gravell
///...blah blah updating files
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
///...blah blah more updating files
In my WPF application i have to show a progressbar progress with in a timer tick event, which i am writing as below,
System.Windows.Forms.Timer timer;
public MainWindow()
{
timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
this.timer.Tick += new System.EventHandler(this.timer_Tick);
}
load event as below
private void Window_Loaded(object sender, RoutedEventArgs e)
{
progressBar1.Minimum = 0;
progressBar1.Value = DateTime.Now.Second;
progressBar1.Maximum = 700;
timer.Start();
}
And at last in tick event,
private void timer_Tick(object sender, EventArgs e)
{
Duration duration = new Duration(TimeSpan.FromSeconds(20));
//progress bar animation
System.Windows.Media.Animation.DoubleAnimation doubleanimation = new System.Windows.Media.Animation.DoubleAnimation(200.0, duration);
progressBar1.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
When the program's progressbar shows the progress for two-three bars and then it stops increment. Later there is no effect in the progress at all.
Why?
Since your ProgressBar doesn't relate to any particular behavior, it looks like a job for an indeterminate bar.
This other SO question provides some insight about it. In short, it's a XAML one-liner:
<!-- MinVal, MaxVal, Height needed for this to work -->
<ProgressBar x:Name="progressBar1" Margin="5" IsIndeterminate="True"
MinimumValue="0" MaximumValue="700" value="0" Height="20"/>
Then in code, you go like this:
progressBar1.IsIndeterminate = true; // start animation
progressBar1.IsIndeterminate = false; // stop animation
In my WPF application I have ... System.Windows.Forms.Timer timer;
That is the wrong type of timer. Use a DispatcherTimer instead.
When i execute my program progressbar shows the progress for two-three bars and then it stops
This surprises me, I wouldn't have expected it to work at all. This means you may have other problems too, like blocking the main (dispatcher) thread.
You are only setting the Value once, in the Loaded event:
progressBar1.Value = DateTime.Now.Second;
There is no change to progressBar1.Value in the Tick event. So it figures that it stops moving.
Use DispatcherTimer instead of Timer (Forms object), and use Value property of ProgressBar.
Try this:
MainWindows.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="55" Width="261">
<Grid>
<ProgressBar Name="pb" Maximum="60" />
</Grid>
</Window>
MainWindows.xaml.cs:
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private DispatcherTimer timer;
public MainWindow()
{
InitializeComponent();
this.timer = new DispatcherTimer();
this.timer.Tick += timer_Tick;
this.timer.Interval = new System.TimeSpan(0, 0, 1);
this.timer.Start();
}
private void timer_Tick(object sender, System.EventArgs e)
{
this.pb.Value = System.DateTime.Now.Second % 100;
}
}
}
You can change the behaviour of the progress bar by changing the Value property (don't forget defining the Maximum property in the xaml).
I found this (WPF Multithreading: Using the BackgroundWorker and Reporting the Progress to the UI. link) to contain a great solution for my needs, albeit with a dialog box.
The one thing I found very useful was that the worker thread couldn't access the MainWindow's controls (in its own method). However, when using a delegate inside the main windows event handler, it was possible.
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
pd.Close();
// Get a result from the asynchronous worker
T t = (t)args.Result
this.ExampleControl.Text = t.BlaBla;
};
I have a problem in my WPF app. I have a custom CircularProgressBar. When I retrieve data from database it takes a few seconds.
I would like to show the CircularProgressBar in my app while the data is retrieved.
This code runs the CircularProgressBar :
CircularProgressBar cb = new CircularProgressBar();
stk.Children.Add(cb);
ThreadStart thStart = delegate()
{
ThreadStart inv = delegate()
{
stk.Children.Remove(cb);
};
this.Dispatcher.Invoke(inv, null);
};
Thread myThread = new Thread(thStart);
myThread.Start();
in my custom class (Printer).
And where I call this window:
Printer p = new Printer();
p.Show();
//Code For retrieve Data from DataBase
p.close();
So this happens : CircularProgressBar shows for a few seconds and it not running. Where is my bug?
You can simply use background worker:
private BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
//load data from database
System.Threading.Thread.Sleep(1);
worker.ReportProgress(progressbar_value);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress.value= progressbar_value;
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//progress completed
}
This is not how you do stuff in wpf -
use a model to populate the data from the db and than bind CircularProgressBar visibility to
the state you're in (hide it when you completed the task).
all this boilerplate code should be in xaml.
If I were you, I would simplify life by using databinding with dependencyproperties.
What are the steps to follow.
1) Create a dependency property called IsBusyProperty of type bool in your custom progressbar.
2) Register a delegate to its value change event (this is done when you create the dependency property).
3) You can now bind this IsBusyProperty to a status in your code that says hey I am busy.
4) When the value is set to true you get your progressbar to start its magic.
5) When it is set to false you stop the magic.
It is far simpler to create a control with a storyboard that rotates, so long as your ui is not locked it will rotate then simply kill it afterward.
Try this