/Disclaimer, this is my first time working with WPF and with multi-threading, so bear with me if I am making some big mistakes/
So I have an application with a tabcontrol. In one of the Tabs I intend to load in a visio file via the usual windows form host + Visio viewer activeX control etc... And it works perfectly. The only issue is that when I load in the document the UI freezes for 20 seconds (as I am loading in rather huge files). As I read this is because my application is running on a simple thread. So I was trying to implement a background worker to keep the UI reactive while the background thread is running.
When I instantiate my UserControl then I add to the Tab (Initialpath is the filepath of the visio file):
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync(initialpath);
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
DiagramView UCworker;
UCworker = new DiagramView((string)e.Argument);
e.Result = UCworker;
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UC = (DiagramView)e.Result;
this.Host.Child = UC;
}
And When it creates the new DiagramView:
public DiagramView(string path)
{
InitializeComponent();
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.Load(path);
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
}
And for this line:
this.viewer = new AxVisioViewer.AxViewer();
I get: : 'ActiveX control 'f8cf7a98-2c45-4c8d-9151-2d716989ddab' cannot be instantiated because the current thread is not in a single-threaded apartment.'
I read that the backgroundworker is not capable to modify the UI elements (correct me if I am wrong)
I also saw this thread: Single-threaded apartment - cannot instantiate ActiveX control
But I am not sure how to implement it (this STA apartment state business) and when I tried, the visio viewer simple crashed when trying to open the document.
I need some guideline how to approach this issue, cause my goal would be to having a loading page to display with an animation until the Document is finished loading/rendering so I can display it.
Thank you in advance for the answers.
UPDATE: I also tried the following approach:
public partial class TabDiagramView : UserControl, INotifyPropertyChanged
{
public delegate void DisplayVisio(DiagramView view);
public DisplayVisio DelegateM;
DiagramView UC;
public TabDiagramView()
{
InitializeComponent();
DelegateM = new DisplayVisio(DisplayV);
Thread t = new Thread(RT);
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
}
#region Thread
private void RT()
{
DiagramView UCworker;
UCworker = new DiagramView(initialpath);
Dispatcher.Invoke(DelegateM, UCworker);
}
private void DisplayV (DiagramView DiagV)
{
UC = DiagV;
this.Host.Child = DiagV;
}
But in this case I get the following message on UC and the this.Host.chilld=DiagV when I am in the DisplayV method: System.InvalidOperationException: 'Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.'
It's simply impossible to load a UI control from another thread. However you could solve the freezing problem if the library had provided a thread-safe approach (e.g. AxViewer.LoadAsync) so the only way to make viewer.Load bearable for the user is to delay viewer.Load operation until the window is loaded:
string _path;
public DiagramView(string path)
{
InitializeComponent();
_path = path;
this.Resize += new EventHandler(this.UpdateSize);
this.viewer = new AxVisioViewer.AxViewer();
this.Controls.Add(this.viewer);
this.viewer.CreateControl();
this.viewer.HighQualityRender = false;
this.viewer.BackColor = Color.White;
this.viewer.PageTabsVisible = true;
this.viewer.ContextMenuEnabled = false;
this.viewer.PropertyDialogEnabled = false;
this.viewer.ToolbarVisible = true;
this.viewer.OnSelectionChanged += Viewer_OnSelectionChanged;
this.Loaded += Viewer_Loaded;
}
private void Viewer_Loaded(object sender, RoutedEventArgs e)
{
viewer.Load(_path);
}
And more importantly, delete the background worker code. It doesn't do any good here.
Related
I have a C# Windows Form Application that has a menu called Start Download Files and thi menu have then two instantiated user controls(submenu, tab menus).
For each tab (user control) i have a download button and a timer that runs it every 5 minutes.
I am using a private background worker which is created every time the control loads and runs a method to start download the files. That gives me a lot of troubles which i still can't find a solution for because:
- when i enable the timer for both controls they start the download multiple times for each and i get into concurrency accessing the files or
- cross thread exceptions
Does someone experienced something similar and maybe can give me a hint?
My code looks like this:
public partial class ucGeneralInfo : UserControl
{
private BackgroundWorker backgroundWorker;
private void TimerDownloadFrequency_Tick(object sender, ElapsedEventArgs e)
{
if (backgroundWorker.IsBusy != true)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.RunWorkerAsync(Branch);
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Download_Process((string) e.Argument);
}
private void Download_Process(string Branch)
{
// copying files
// processing files
}
}
}
For concurrent access, use lock.
lock(fileLock)
{
wc.DownloadFileAsync (/*...*/);
}
For Cross-thread exception use Invoke .
Invoke(new MethodInvoker(delegate
{
timer.Enable = true;
}));
I have two files. One which contains a variable of type DataProgressBar and calls the startAsyncWorker() method before loading its own components. The other is the DataProgressBar displayed below. When I run my program and call startAsyncWorker() in the other files the behavior
EXPECTED: small window with a progressbar is displayed, loading from 0 to 100 as the work in WorkerDatabaseInsertion is performed. Then when finished my first class file which contains the DataProgressBar, will move on to its next instruction.
EXPERIENCED: small window with a progressbar is displayed, no change is made to the UI, thread seems to freeze as no output or evidence of processing is shown. I close the window, the calling file resumes.
public partial class DataProgressBar : Window
{
private BackgroundWorker bgw;
private String _path;
private LFPReader _lfp;
private Access db;
public DataProgressBar(String p, LFPReader reader, Access database)
{
InitializeComponent();
/* Set private class level variables */
_path = p;
_lfp = reader;
db = database;
db.open();
/* Set up worker and progressbar */
bgw = new BackgroundWorker();
SetUpWorker(bgw);
progressbar.Maximum = 100;
progressbar.Minimum = 0;
progressbar.Value = 0;
}
public void startAsyncWorker()
{
if(bgw.IsBusy != true)
{
bgw.RunWorkerAsync();
}
}
/// <summary>
/// This methods exists for completeness, but we will
/// probably not need to directly cancel the worker from here.
/// --Kurtpr
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void cancelAsyncWorker(object sender, EventArgs e)
{
if (bgw.WorkerSupportsCancellation == true)
{
bgw.CancelAsync();
}
}
private void SetUpWorker(BackgroundWorker worker)
{
worker.WorkerReportsProgress = true; // we need this in order to update the UI
worker.WorkerSupportsCancellation = true; // Not sure why, but we may need this to cancel
worker.DoWork += new DoWorkEventHandler(WorkerDatabaseInsertion);
worker.ProgressChanged += new ProgressChangedEventHandler(WorkerProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerBurnNotice);
}
private void WorkerDatabaseInsertion(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker;
ImageInfo ImageData = new ImageDB.ImageInfo();
double size = _lfp.GetImageData().ToArray().Length;
int index = 0;
//_path is setup in loadAsLFP() before this call.
foreach (var image in _lfp.GetImageData())
{
index++;
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
image.FullPath = Path.Combine(_path, image.FullPath);
ImageData.Add(new ImageDB(image));
db.insertImage(new ImageDB(image));
worker.ReportProgress((int)(index/size));
}
}
private void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine("Hello");
progressbar.Value = e.ProgressPercentage;
}
private void WorkerBurnNotice(object sender, RunWorkerCompletedEventArgs e)
{
db.close();
}
}
I suspect I am wrong about a fundamental aspect of BackgroundWorker. Where is my logical fault here?
EDIT Here is the code that calls and creates the DataProgressBar object.
private void createDb(string filePath, LFPReader lfp)
{
//Set up LFP related objects.
ImageData = new ImageDB.ImageInfo();
//create database file.
filePath = filePath.Replace(".lfp", ".mdb").Replace(".LFP", ".mdb");
_lfpName = filePath; // update to use the database file
Access db = new Access(filePath.Replace(".lfp", ".mdb"));
db.createDatabase();
//used for calculating progress of creating this new database.
var progressbar = new DataProgressBar(_path, lfp, db);
progressbar.ShowDialog();
progressbar.startAsyncWorker();
CurImage = ImageData.First().FullPath;
//Make sure that data context is set for these images.
DataContext = ImageData;
}
I think your progress calculation logic is faulty here.
worker.ReportProgress((int)(index/size));
This line will always report progress as 0. So, your progress bar will be always stuck at 0 position.
Instead, use following to report progress in percentage.
worker.ReportProgress((int)(index*100/size));
Update:
The code you have shared seems to be correct.
I think the problem lies in the way you have implemented progress bar.
I suppose you are calling startAsyncWorker method from main thread as follows;
var dpb = new DataProgressBar(p, reader, database);
dpb.startAsyncWorker();
After above two lines are called, your main thread should be free.
That means, following code will cause your progressbar to freeze for 50 seconds because, even if your DoWork is running perfectly, UI will not be updated since main thread is not free.
var dpb = new DataProgressBar(p, reader, database);
dpb.startAsyncWorker();
Thread.Sleep(50000); //Main thread is busy for 50 seconds
Update 2:
The real problem lies in following lines;
var progressbar = new DataProgressBar(_path, lfp, db);
progressbar.ShowDialog();
progressbar.startAsyncWorker();
Actually ShowDialog() method shows DataProgressBar as Modal dialog. That means, the control will not go to next line unless you close that dialog.
Your problem should be solved using following code;
var progressbar = new DataProgressBar(_path, lfp, db);
progressbar.startAsyncWorker();
progressbar.ShowDialog();
It will first start your background worker and then DataProgressBar dialog will be shown.
I've got a Loading page that starts up (typically I have my home page load it on startup but I've swapped it recently). I have an excel file with tons of data in it. I have a class for dumping that data. I also have a class for each of the lists I want to create. The loading page should be updating it's percentage as it goes.
I was able to get it to work in the example below.
public partial class Window1 : Window
{
public MainController main = new MainController();
public ExcelImporter tempExcel = new ExcelImporter();
List<Character.MainRace> listRaces = new List<Character.MainRace>();
List<string> tempString = new List<string>();
public Window1()
{
InitializeComponent();
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBarTest.Value = e.ProgressPercentage;
textBlockPercent.Text = e.ProgressPercentage.ToString() + "%";
textBlockTest.Text = (string)e.UserState;
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
tempExcel.Create();
tempExcel.SetSheet("Races");
var worker = sender as BackgroundWorker;
worker.ReportProgress(0, String.Format("Process Iteration 1."));
List<string> myRaceName = tempExcel.GetRangeValue("A2", "A46");
List<string> myRaceShortDescription = tempExcel.GetRangeValue("N2", "N46");
for (int raceSelection = 0; raceSelection < myRaceName.Count - 1; raceSelection++)
{
Character.MainRace tempRace = new Character.MainRace();
tempRace.Race = myRaceName[raceSelection];
double percentage = ((double)raceSelection / (double)myRaceName.Count) * 100.0;
Thread.Sleep(10);
worker.ReportProgress((int)percentage, String.Format("Processing " + myRaceName[raceSelection]));
tempRace.ShortDescription = myRaceShortDescription[raceSelection];
listRaces.Add(tempRace);
}
worker.ReportProgress(100, "Done Processing");
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("All Done!");
progressBarTest.Value = 0;
textBlockTest.Text = "";
}
}
There's really two places where I need the UI to update. Each time I'm loading a new set of data for each loader like Human, elf, etc for race and fighter, paladin, thief, etc for class. As well as when I move on to the next set of data like Classes Race, Feats, etc. (though to keep this a bit shorter I only included showing just 1 for race.
I'm pretty new to coding in general especially wpf. I've been going through bits a pieces of tutorials. I've tried using INotifyproperty,background worker, dispatcher, binding and not binding the content. I'm just not getting it. Everything works separately but not together. From what I understand, it's because the UI thread which is the main thread getting held up because of my long process.
As Jeroen mentioned, we need an example. You're trying to update a WPF UI based on the contents of [cringe] hard-coded Excel ranges. I'm guessing the workbook import part works, but the UI is freezing? or not updating?
First, try using a DataGrid to display your data on the UI. Bind its ItemsSource to a List (or ObservableCollection) of a class that holds all your pertinent data nicely. The DataGrid should do the rest as the AutoGenerateColumns is true by default.
Then, for further help, you'll have to attempt something and provide a reproducible example of what's failing. Showing the implementation of your BackgroundWorker would provide some clues on holding the main UI thread.
I have been trying to figure out a problem with a background load I do on startup. The application runs totally fine but when its closed, it hangs forever. I assumed this was a threading issue. I have narrowed it down to the following code. I have been googling around but not come across anything which fits the problem I am having, can anybody elaborate on the thread safety here?
I assumed that considering the loading screen is closed when the worker is completed ( m_LoaderWindow.Close(); ) that it wouldn't be problematic.
This code doesn't work
m_LoaderWindow = new LoadingWindow();
m_BackgroundWorker = new BackgroundWorker();
OnProgressDelegate = m_BackgroundWorker.ReportProgress;
m_BackgroundWorker.WorkerReportsProgress = true;
m_BackgroundWorker.ProgressChanged += (object sender, ProgressChangedEventArgs arg) =>
{
LoaderWindow.Context.Progress = arg.ProgressPercentage;
};
m_BackgroundWorker.DoWork += MBackgroundWorkerOnDoWork;
m_BackgroundWorker.RunWorkerCompleted += MBackgroundWorkerOnRunWorkerCompleted;
m_BackgroundWorker.RunWorkerAsync();
m_LoaderWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
m_LoaderWindow.Owner = Application.Current.MainWindow;
m_LoaderWindow.ShowDialog();
This code works (but obviously no loading screen)
m_BackgroundWorker = new BackgroundWorker();
OnProgressDelegate = m_BackgroundWorker.ReportProgress;
m_BackgroundWorker.WorkerReportsProgress = true;
m_BackgroundWorker.ProgressChanged += (object sender, ProgressChangedEventArgs arg) =>
{
LoaderWindow.Context.Progress = arg.ProgressPercentage;
};
m_BackgroundWorker.DoWork += MBackgroundWorkerOnDoWork;
m_BackgroundWorker.RunWorkerCompleted += MBackgroundWorkerOnRunWorkerCompleted;
m_BackgroundWorker.RunWorkerAsync();
Here is the worker completed code
private void MBackgroundWorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
m_LoaderWindow.Close();
}));
}
Ok it wasn't related to anything with the threading. In my ViewModel I was doing this:
public LoadingWindow m_LoaderWindow = new LoadingWindow();
And then I was allocating it AGAIN in the main thread.
Although I can't explain why this causes it to hang on exit?
c# .net Winforms,IDE: VS 2010
I having two windows from F1, F2.
F1 is the caller, and F2 is the form which I want to load in thread because it is having lots of rich controls on it.
I am able to load the f2 in child thread but it just get visible and goes, because its on child thread.(See Case 1 Code)
Case 1 Code
private void button1_Click(object sender, EventArgs e)
{
StartProgress();
Thread th = new Thread(new ThreadStart(LoadForm));
th.Start();
}
private void StartProgress()
{
progressBar1.Maximum = 100;
progressBar1.Step = 1;
for (int i = 0; i <= 100; i++)
{
label1.Text = i + "%";
progressBar1.PerformStep();
Thread.Sleep(10);
label1.Refresh();
}
}
private void LoadForm()
{
Form2 f2 = new Form2();
f2.Show();
}
}
Then I did reverse that is I loaded progress bar in child thread and loaded f2 on main thread.(See Case 2 Code)
//Code Case2:
case 2 when progress bar is on child thread.
private void button1_Click(object sender, EventArgs e)
{
LoadForm();
Thread th = new Thread(new ThreadStart(StartProgress));
th.Start();
LoadForm();
}
private void StartProgress()
{
progressBar1.Maximum = 100;
progressBar1.Step = 1;
for (int i = 0; i <= 100; i++)
{
label1.Text = i + "%";
progressBar1.PerformStep();
Thread.Sleep(10);
label1.Refresh();
}
}
private void LoadForm()
{
Form2 f2 = new Form2();
f2.Show();
}
but case 2 having 2 problems.
Problem 1: It loads the f2 as usual with flicking.
Problem 2: Cross Thread Opration progressbar1
//Pls suggest how to load the f2 in background and show it after the progress bar is loaded.
If you need to load a secondary window in the background, a better approach would be to load the data in the background of your current UI and then pass that information to the new Window.
public void btnNewWindow_Click(object sender, EventArgs args)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += WorkerOnDoWork;
worker.ProgressChanged += WorkerOnProgressChanged;
worker.RunWorkerCompleted += WorkerOnRunWorkerCompleted;
worker.RunWorkerAsync();
}
private List<string> _data = new List<string>();
private void WorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
//Work has finished. Launch new UI from here.
Form2 f2 = new Form2(_data);
f2.Show();
}
private ProgressBar progressBar1;
void WorkerOnProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Log process here
}
private void WorkerOnDoWork(object sender, DoWorkEventArgs e)
{
//Perform work you need to load the data.
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(50);
_data.Add("Test" + i);
}
}
I put this together real quick in WPF but you should be able to make this work with the same principles. I will reiterate that you shouldn't use a second UI thread. Load the data up front and then pass along to the window when ready and available.
If all you want to do is avoid the flicker, create F2 as hidden, and only show it once its initialization is complete.
It's been a while since I did any Windows Forms programming, but I guess you can show the form in your _Load event handler.
You shouldn't have more than one User Interface (UI) thread. The complexity to keep the data between your interface will be quite difficult and often make the application Not Thread Safe. Which will cause error after error for you.
The primary goal should keep the User Interface (UI) thread responsive. This way as the user interacts with your application the interface is responsive as the application performs computational task.
Here is a great article on keeping your UI thread responsive here.
You may want to note what Microsoft says here:
Note: Not all changes to the UI are necessarily done on the UI thread.
There's a separate render thread that can apply UI changes that won't
affect how input is handled or the basic layout. For example many
animations and transitions that inform users that a UI action has
taken place can actually run on this render thread. But if it's your
code that is changing UI elements somewhere in your pages, it's best
to assume that your code could have the potential to block the UI
thread, unless you're familiar with those APIs or subsystems and know
for certain they don't affect the UI thread.
Update:
I didn't realize you were using Windows Forms, the article listed above is for XAML. You can use this article to help build a responsive UI with computational task here for Windows Forms. (Link is for WPF, you'll need to port for Windows Forms).