2

I am writing a home WPF app that is obtaining a file from a server at a configured interval.

It's a basic window, with a couple of labels. I have the following

  • Start Time (reflects DateTime the "Start" event was hit
  • Duration (reflects the time the app has been running)
  • Speed (the download speed of the file)

I want to update the Duration on the Main window each second so I have the following code to do this (in a seperate class "RunDownloader.cs").

    private void StartTickTimer()
    {
        const double interval = 1000;

        if (_tickTimer == null)
        {
            _tickTimer = new Timer
            {
                Interval = interval
            };
            _tickTimer.Elapsed += _ticktimer_Elapsed;
        }

        _tickTimer.Start();
    }

On _ticktimer_Elapsed I call a method in the main window _mainWindow.UpdateTicker();

This does the following.

    public void UpdateTicker()
    {
        var timeStarted = lblTimeStarted.Content.ToString();
        DateTime startTime = DateTime.Parse(timeStarted);
        TimeSpan span = DateTime.Now.Subtract(startTime);

        //ToDo: Output time taken here!
        //lblTimeElapsed.Content =
    }

I have two issues.

  1. I have the following exception when calling lblTimeStarted.Content.ToString(); in UpdateTicker()

        "The calling thread cannot access this object because a different thread owns it."
    
  2. I dont quite know, how to show the duration correctly for lblTimeElapsed.Content from TimeSpan

Thanks in advance for any answers. :D

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Joey Bob
  • 312
  • 8
  • 21

3 Answers3

6

In WPF you cannot update UI objects (that get created on the UI thread) from threads other than the UI thread.
In order to update UI controls from some other thread (eg a timer thread) you need to use the Dispatcher to run your update code on the UI thread.
This Question/answer may help you or you will find plenty of info by googling "WPF Dispatcher".
An example dispatcher call - the lamda code will get posted off to run on the UI thread:

Dispatcher.BeginInvoke(new Action(() =>
{
    text_box.AppendText(formated_msg);
    text_box.ScrollToEnd();
}));

Alternatively you could replace your existing timer with a DispatchTimer - unlike the timer you are using it ensures that the timer callback is on the UI thread:

Reasons for using a DispatcherTimer opposed to a System.Timers.Timer are that the DispatcherTimer runs on the same thread as the Dispatcher and a DispatcherPriority can be set on the DispatcherTimer.

Community
  • 1
  • 1
Ricibob
  • 7,505
  • 5
  • 46
  • 65
1
  1. Your timer is running on its own thread and invoking the UpdateTicker() method from there. However, most UI frameworks, including WPF, prohibit accessing UI controls from threads other than the one which the respective control was created on (the latter is usually denoted as the "UI thread"). You have two main options here:

    • Use a DispatcherTimer. This will run on your UI thread and avoids any threading issues, but then, since your UpdateTicker() code also runs on this thread, your UI will be unresponsive while you are doing processing. This may or may not be an issue; if all you do is a couple of field/property changes, this is fine.
    • In your timer callback, use this.Dispatcher.Invoke() or this.Dispatcher.BeginInvoke() to call your UI update method once other processing is completed (example: this.Dispatcher.Invoke((Action) UpdateTicker)). This will "bump" the call to the proper thread for the UI update, while maintaining the asynchrony for data processing. In other words, this is a more effective approach.
  2. The TimeSpan struct has a ToString() method that accepts formatting; or, if this is inconvenient, it has several helper properties (Days, Hours, Minutes, TotalDays, TotalHours, TotalMinutes etc.) that you can use for display purposes.

Alan
  • 6,501
  • 1
  • 28
  • 24
0

You can do it like: In main windows:

public void ChangeTime(string time)
        {
            lblsb.Content = time;
        }

And in RunDownloader:

class RunDownloader
    {
        Timer _tickTimer;
        MainWindow window;

        public RunDownloader(MainWindow window)
        {
            this.window = window;
        }

        private delegate void MyDel(string str);

        public void StartTickTimer()
        {
            const double interval = 1000;

            if (_tickTimer == null)
            {
                _tickTimer = new Timer
                {
                    Interval = interval
                };
                _tickTimer.Elapsed += (object sender, ElapsedEventArgs e) =>
                    {
                        window.Dispatcher.BeginInvoke(new MyDel(window.ChangeTime), DateTime.Now.ToLongDateString());
                    };
            }

            _tickTimer.Start();
        }

    }
ninja
  • 106
  • 4