2
votes

Use case
I'm developing a small application in C# that is called by another application to retrieve data from the Internet. It runs as a process on its own, but almost all of the interaction with it, is managed by the calling application. Therefor it does not have a GUI. However I'd like to add a progress bar using WPF that is shown during certain data retrievals that could take up to a minute. It's fairly easy to make an estimate of how much work is done and how much is left and therefor I find a progress bar suitable.

Research done
I have a fair understanding of threading after reading large parts of Albahari's pdf on threading (http://www.albahari.info/threading/threading.pdf). I have also read through a lot of posts on SO and MSDN in this matter. Most posts suggest the use of a background worker for the time consuming data retrieval while keeping the GUI in the main thread and therefor suggest solutions using a background worker. That feels awkward in this scenario though, where the main task is data retrieval and not GUI interaction.

I've spend a bunch of hours trying to make sense of different tutorials and forum posts while trying to conform them to my problem, but I have not succeeded and now I'm pretty much back to square one. Basically I'd like to end up with the following two classes outlined below:

ProgressBarWindow

public partial class ProgressBarWindow : Window
{
    public ProgressBarWindow()
    {
        InitializeComponent();
    }

    public void setValue(int value) 
    {
        // This function should be available from the main thread
    }
}

Querier

Public class Querier
{



    public List<Item> getItems()
    {
        // call ProgressBarWindow.setValue(0);
        ...
        // call ProgressBarWindow.setValue(100);
        // call ProgressBarWindow.Close();
    }
}

It's my understanding that UI must run under single threads and therefor my ProgressBarWindow object could not be instantiated in a new thread while at the same time be available to the main thread (kind of).

Dispatcher.BeginInvoke appears to be my savior here but so far I haven't been able to figure out what should go into the Querier class and what to go in the ProgressBarWindow class. How can I make the two threads interact with the same instance of ProgressBarWindow?

Please ask if you need more details and I will try to clarify.

3
Please avoid the setXXX() and getXXX() syntax. it looks so much java-like that I'm about to throw up. Instead of that, use C#-like code and create proper Properties.Federico Berasategui
Point taken! I usually try.Anders

3 Answers

1
votes

You can use the Progress class to update the UI with the current progress of a long running operation.

First create an instance of Progress in your UI:

Progress<int> progress = new Progress<int>(currentProgress =>
{
    progressBar.Value = currentProgress;
    //todo do other stuff
});

Then pass it to the long running process:

public List<Item> getItems(IProgress<int> progress)
{
    progress.Report(0);
    //todo do something
    progress.Report(100);
}
0
votes

Here is a generic function which i generally use:

    public static void Invoke(this UIElement element,Action action)
    {
        element.Dispatcher.Invoke(action, null);
    }

And to use it, simply call:

this.Invoke(() => ProgressBarWindow.SetValue(0));

So, in the getItems() function, you would have something along the lines of:

public List<Item> getItems()
{
    ProgressBarWindow wnd;
    MainWindow.Invoke(() => wnd = new ProgressBarWindow())
    MainWindow.Invoke(() => wnd.SetValue(0))
    ...
    MainWindow.Invoke(() => wnd.SetValue(100))
    MainWindow.Invoke(() => wnd.Close())
}

Make sure you always have a way to get to the main window is anything (the one running from either App.xml, or App.Run(...). You can then issue any GUI actions through it (even if you have to create a new Loader window for example, as long as it's done within the main thread)

0
votes

App.xaml

public partial class App : Application
{
    private void Application_Startup_1(object sender, StartupEventArgs e)
    {
        Task.Factory.StartNew<List<int>>(() => Querier.GetItems());
    }
}

ProgressBarWindow.xaml.cs

public partial class ProgressWindow : Window
{
    public ProgressWindow()
    {
        InitializeComponent();
        Querier.Start +=()=> Visibility = Visibility.Visible;
        Querier.Stop += () => Visibility = Visibility.Collapsed;
        Querier.ReportProgress +=OnReportProgress;
    }

    public void OnReportProgress(int value)
    {
        txtBox.Text = value.ToString();
    }
}

ProgressBarWindow.xaml

<Grid>
    <TextBox x:Name="txtBox"></TextBox>
</Grid>

Querier

public class Querier
{
    public static event Action Start;

    public static event Action Stop;

    public static event Action<int> ReportProgress;

    public static List<int> GetItems()
    {
        if (Start != null)
            App.Current.Dispatcher.BeginInvoke(Start,null);

        for (int index = 0; index <= 10; index++)
        {
            Thread.Sleep(200);
            if (ReportProgress != null)
                App.Current.Dispatcher.BeginInvoke(ReportProgress, index*10);
        }


        if (Stop != null)
            App.Current.Dispatcher.BeginInvoke(Stop, null);

        return Enumerable.Range(1, 100).ToList();

    }

}

I am just trying to give an idea hope this will help.