views:

134

answers:

2

Hey all

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}&amp;fr=sfp&amp;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); enter code here

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.

A: 

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....

John Gardner
Thanks for the method and advice John. I've tested that method five times with different Dispatch priories and the label doesn't change until the method I posted above is complete, it then displays the last uri processed.
Ash
Feels so random, sometimes it shows one update, sometimes more, so weird considering I'm passing the same data and the conditions are also identical.
Ash
are you doing it in another thread? you could put breaks in and make sure your UI is getting updated properly.If you are not doign it in another thread, you might never be giving the UI time to change?
John Gardner
Yeh the UI is being given time, I've just tried my update with an operation which takes around 12 seconds, I've only ever seen 1 label update throughout this period.I'm sure I never had this problem with winforms.
Ash
A: 

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.

Ash