简体   繁体   中英

C# WPF Cancel ProgressBar from a different Thread

I have a UserControl with a big table that is displaying values using a lot of converters. I am trying to display a ProgressBar in a new Window with Indeterminate State that is closing automatically when the UserControl Loaded event is fired.

This is the Thread creation in the backcode of my UserControl :

Thread progressBarThread = new Thread(new ThreadStart(delegate { 
    ProgressBarWindow progressBarWindow = new ProgressBarWindow();
    progressBarWindow.IsIndeterminate = true;
    progressBarWindow.LaunchProgressBarInBackground();
    progressBarWindow.ShowDialog();
}));

progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();

this.Loaded += (sender, e) => { Dispatcher.FromThread(progressBarThread).InvokeShutdown(); };

This code is "working", it is opening progressBarWindow but when I shutdown the thread using InvokeShutdown (the ugliest way to do it, i agree). The problem is that the DoWork from my backgroundWorker.

Here is the DoWork function :

private void BackgroundWorker_WaitUntilShouldStop(object sender, DoWorkEventArgs e)
{
    // Do not access the form's BackgroundWorker reference directly.
    // Instead, use the reference provided by the sender parameter.
    BackgroundWorker bw = sender as BackgroundWorker;

    // Start the time-consuming operation.
    while (!bw.CancellationPending)
    {
        Thread.Sleep(500);
    }
}

I would like to call my function contained in ProgressBarWindow to stop the DoWork from runnning and close the ProgressBarWindow normaly using :

progressBar.StopProgressBarInBackground();

This method is calling backgroundWorker.CancelAsync();

This will result in backgroundWorker terminating and progressBarWindow closing automatically.

But I don't have access to progressBar that is inside the progressBarThread. I tried to pass my UserControl using :

progressBarThread.Start(this);

this being the main window.

When trying to pass a variable from the main thread, this error is thrown :

An exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll but was not handled in user code  Additional information: The calling thread cannot access this object because a different thread owns it.

Does someone have a nice and correct way to do it without using myThread.InvokeShutdown() ?

Edit 1 :

I found a solution to my problem using a volatile variable :

volatile bool _isLoaded;
void CreateAndStopProgressBarWhenIsLoaded()
{
    Thread progressBarThread= new Thread(new ThreadStart(    
    {
        Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
        progressBar.IsIndeterminate = true;
        progressBar.LaunchProgressBarInBackground();

        DispatcherTimer dispatcherTimer = new DispatcherTimer();
        dispatcherTimer = new DispatcherTimer();
        dispatcherTimer.Tick += (sender, e) => {
            if (_isLoaded)
                progressBar.StopProgressBarInBackground();
        };
        // Try to stop `progressBar` every 500 ms
        dispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
        dispatcherTimer.Start();

        progressBar.ShowDialog();

        // Will only be reached once progressBar.ShowDialog(); returns
        dispatcherTimer.Stop();
    }));
    progressBarThread.SetApartmentState(ApartmentState.STA);
    progressBarThread.Start();

    this.Loaded += (sender, e) => {
        _isLoaded = this.IsLoaded;
        progressBarThread.Join(); // Wait for progressBarThread to end
    };
}

Now the question is do you have a better solution ?

Edit 2 :

Here is my final solution thanks to @AlexSeleznyov :

    void CreateAndStopProgressBarWhenIsLoaded()
    {
        Controls.ProgressBar.ProgressBar pb = null;
        ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        Thread progressBarThread = new Thread(new ThreadStart(delegate
        {
            Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
            pb = progressBar;
            manualResetEvent.Set();
            progressBar.IsIndeterminate = true;
            progressBar.LaunchProgressBarInBackground();

            progressBar.ShowDialog();
        }));
        progressBarThread.SetApartmentState(ApartmentState.STA);
        progressBarThread.Start();

        this.Loaded += (sender, e) => {
            pb.Dispatcher.Invoke(delegate {
                manualResetEvent.WaitOne();
                pb.StopProgressBarInBackground();
            });
            progressBarThread.Join();
        };
    }

I think that you can use BackgroundWorker.RunWorkerCompleted in ProgressBarWindow - it will be invoked when you cancel a backgroundWorker.

private void backgroundWorker_RunWorkerCompleted(object sender, 
                                                 RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        //close the window
    }
}

You might try this approach, to cache ProgressBar instance and then use it from another thread. Dispatcher.Invoke eradicates need for CheckAccess I've mentioned in comments.

void CreateAndStopProgressBarWhenIsLoaded()
{
    var pb = new Controls.ProgressBar.ProgressBar[1];

    Thread progressBarThread= new Thread(new ThreadStart(    
    {
            Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
            pb[0] = progressBar;
            progressBar.IsIndeterminate = true;
            progressBar.LaunchProgressBarInBackground();

            progressBar.ShowDialog();
    }));
    progressBarThread.SetApartmentState(ApartmentState.STA);
    progressBarThread.Start();

    this.Loaded += (sender, e) => {
         pb[0].Dispatcher.Invoke(()=>pb[0].Close());
    };

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM