Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF - Updating Label Content During Processing

Well I've tried several methods of getting this to work, background worker, Dispatcher.Invoke, threading within the called class and nothing seems, to work. The best solution so far is an Extension method which calls the invoke of the control. Also I've tried avoid passing the data for the label through my event classes and simply invoking within my processing code, however this made no difference.

In regards to the background component, I kept getting exceptions saying the background worker was busy, so I instantiated the class several times, however the label only visibly changed once the entire operation had been complete.

I've removed my previous code, here's everything that is relevant, as it seems the issue is difficult to resolve.

Method Being Called

 private void TestUris()
        {
            string text = new TextRange(rtxturis.Document.ContentStart, rtxturis.Document.ContentEnd).Text;
            string[] lines = Regex.Split(text.Remove(text.Length - 2), "\r\n");

            foreach (string uri in lines)
            {
                SafeUpdateStatusText(uri);
                bool result;
                string modUri;

                if (!uri.Contains("http://"))
                {
                    modUri = uri;
                    result = StoreData.LinkUriExists(new Uri("http://" + modUri));
                }
                else
                {

                    modUri = uri.Substring(7);
                    result = StoreData.LinkUriExists(new Uri(uri));
                }

                if (!result)
                {
                    Yahoo yahoo = new Yahoo();
                    yahoo.Status.Sending += (StatusChange);
                    uint yahooResult = 0;

                    yahooResult = yahoo.ReturnLinkCount(modUri);

                    if (yahooResult > 1000 )
                    { results.Add(new ScrapeDetails(Guid.NewGuid(), modUri, 1000, "Will be processed", true)); }
                    else
                    { results.Add(new ScrapeDetails(Guid.NewGuid(), modUri, (int)yahooResult, "Insufficient backlinks", false)); }

                }
                else
                {
                    results.Add(new ScrapeDetails(Guid.NewGuid(), modUri, 0, "Previously been processed", false));
                }
            }


            foreach (var record in results)
            {
                dgvresults.Items.Add(record);

            }

            EnableStartButton();

        }

Yahoo Class

public class Yahoo
    {        

        /// <summary>
        /// Returns the amount of links each Uri has.
        /// </summary>
        public uint ReturnLinkCount(string uri)
        {
            string html;
            Status.Update(uri, false); //this is where the status is called
            try
            {

                html = client.DownloadString(string.Format("http://siteexplorer.search.yahoo.com/search?p=http%3A%2F%2F{0}&fr=sfp&bwm=i", uri));

            }
            catch (WebException ex)
            {
               ProcessError(ex.ToString());
               return 0;
            }

           return (LinkNumber(html));

        }

Status Classes

public class StatusEventArgs : EventArgs
    {
        private string _message;
        private bool _isidle;

        public StatusEventArgs(string message, bool isidle)
        {
            this._message = message;
            this._isidle = isidle;
        }

        public bool IsIdle
        {
            get { return _isidle; }
        }

        public string Message
        {
            get { return _message; }
        }
    }

   public class Status
    {
        public Status()
        {
        }

        // Declaring an event, with a custom event arguments class
        public event EventHandler<StatusEventArgs> Sending;

        // Some method to fire the event.
        public void Update(string message, bool isIdle)
        {
            StatusEventArgs msg = new StatusEventArgs(message, isIdle);
            OnUpdate(msg);
        }

        // The method that invokes the event.
        protected virtual void OnUpdate(StatusEventArgs e)
        {
            EventHandler<StatusEventArgs> handler = Sending;

            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

Method That Changes the labels Content

 private  void StatusChange(object sender, StatusEventArgs e)
        {

            if(!e.IsIdle)
            {
                lblstatus.Content = e.Message;
                lblstatus.Foreground = StatusColors.Green;
                lblstatus.Refresh();
            }
            else
            {
                lblstatus.Content = e.Message;
                lblstatus.Foreground = StatusColors.Grey;
                lblstatus.Refresh();
            }

        }

The Refresh static method called:

 public static class ExtensionMethods
    {
        private static Action EmptyDelegate = delegate() { };

        public static void Refresh(this UIElement uiElement)
        {
            uiElement.Dispatcher.Invoke(DispatcherPriority.Render   , EmptyDelegate);
        }

Another EDIT: Staring at my code for a bit longer, I've realised, that the foreach loop will execute really quickly, the operation which takes the time, is

yahooResult = yahoo.ReturnLinkCount(modUri); 

Therefore I've declared the status class (which handles the event and invokes the label etc) and subscibed to it. I've gotten better results, although it still feels random, sometimes I see a couple of label updates, and sometimes one even though the exact same URI's are passed, so weird.

like image 436
Ash Avatar asked Aug 02 '10 14:08

Ash


4 Answers

I hope there is sth. helpful...

 private void button1_Click(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem(o =>
    {
        int result = 0;
        for (int i = 0; i < 9999999; i++)
        {
            result++;
            Dispatcher.BeginInvoke(new Action(() =>
            {
                this.label1.Content = result;
            }));
            Thread.Sleep(1);
        }
    });
}
like image 100
fangZhiyang Avatar answered Nov 12 '22 17:11

fangZhiyang


SOLVED IT YES WOOHOOOOOOOO 3 days of testing, testing, testing.

I decided to start a new project just with the extension method above and a simply for loop to test UI update functionality. I started testing different DispatchPrioraties (tested them all).

Weirdly, I found the highest priorities were the worse, for example using Send didn't update the label at all, Render updated it twice on average. This was the weird behavior I was experiencing as I tried different priorities. I discovered Background:

The enumeration value is 4. Operations are processed after all other non-idle operations are completed.

Now this sounded exactly what I didn't want, as obviously the label should update during processing, hence why I never tried it. I'm guessing that once one of my method has been completed, before the next it called, the UI is updated. I'm find of guessing, but it 100% consistently updates correctly on two separate operations.

Thanks all.

like image 22
Ash Avatar answered Nov 12 '22 18:11

Ash


Well this is going to sound stupid but you could just reference the forms namespace, and then you can do this

     using System.Windows.Forms;

     mylabel = "Start";
     Application.doEvents();

     myLabel = "update"
     Application.doEvents();

now the problem using this would be you are using wpf but you can still reference forms and use this namespace. The other issue is what ever is in the que would execute directly to the ui. However this is the most simplistic way of doing label updates i could think of.

like image 4
user1778625 Avatar answered Nov 12 '22 17:11

user1778625


would it be easier/better to add the status info as a property on this object, and have it just fire property change notifications?

that way the label text (or whatever) could be bound to the property instead of having the async work try to update a label?

or add a method like this to update status if you have to update it?

    void SafeUpdateStatusText(string text)
    {
        // update status text on event thread if necessary
        Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate
        {
            lblstatus.Content = text;
        }, null);
    }

otherwise, i don't think we have enough details to help yet....

like image 1
John Gardner Avatar answered Nov 12 '22 19:11

John Gardner