1

I am facing an issue while binding value from c# code to WPF UI. I have gone through the basic of threading and come to know that I have to use the Dispatcher to bind the ui-thread from my custom background thread.

I have a requirement of like, I want to update my WPF UI continuously by hitting the nse-stockmarket api every second and the do some logic accordingly so that I can show weather share price is increasing or decreasing.

Below is the code how I am trying to achieve this...

Note: I am not getting any kind of exception not even "CROSS-Thread"

 //globally declared var stockName = "";
 //wpf button click 
 private void Button_Click(object sender, RoutedEventArgs e)
  {

      stockName = "LUPIN";
      new Thread(() =>
          {
            RunStockParallel(share.Key);
            Action action = new Action(SetTextBoxValues);

          }).Start();

   }    



public void RunStockParallel(string stockName){
  var count = 0 ;
           do
            {
                HttpWebRequest stocks = null;
                try
                {
                    //your logic will be here.. 
                }
                catch (Exception e)
                {
                    //throw e;
                }


      //It will call the delegate method so that UI can update. 
                Action action = new Action(SetTextBoxValues);

                stockName = count++;
            } while (true);
}




 private void SetTextBoxValues()
        {
            this.Dispatcher.Invoke(() =>
            {

                this.text1.Text = stockName;

            });

        }

As I am using do-while loop, it will keep looping until I terminate the application. In this do-while loop I am continuously trying to update the WPF ui by update the Text1 textbox with this "counter++;".

But its not working as expected. Need suggestion or solution. :)

vijay sahu
  • 765
  • 1
  • 7
  • 29
  • Possible duplicate of [Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on](https://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the) –  Apr 23 '18 at 04:21
  • 2
    This is a very old fasioned and resourceful way to run concurrent tasks, consider using tasks, with async and await and possibly WaitAll – TheGeneral Apr 23 '18 at 04:22
  • In addition to @TheGeneral 's comment, you might want to take a look into `DispatcherTimer`. – eitamal Apr 23 '18 at 04:27
  • You’re creating an Action and not doing anything with it. Why not just call the method? But others have already mentioned the better ways of doing things with async/await etc. – Sami Kuhmonen Apr 23 '18 at 04:45
  • @MickyD I have resolved this cross-thread error already.. even below code is not throwing any exception its keep running without any exception. The only problem is my ui is not getting update.. – vijay sahu Apr 23 '18 at 04:52

5 Answers5

2

You are not invoking the delegate that you are creating. Also, the variable that you are incrementing is not the variable that you are using to update the UI. You are only upgrading the local variable of the method RunStockParallel().

Below is a working version. Hopefully it helps.

PS: I would suggest not to use the below piece of code in production. When you close your application, SetTextBoxValues() will throw a TaskCanceledException which is not at all ideal. As someone has already mentioned, this is probably a very old fashioned way to perform concurrent tasks. You might want to switch to using a Task-based or async/await approach, where you can avoid such exceptions very effectively by using CancellationToken.

private void Button_Click(object sender, RoutedEventArgs e)
  {

     stockName = "LUPIN";
     new Thread(() =>
        {
           RunStockParallel(stockName);
           Action action = new Action(SetTextBoxValues); // Maybe this is not required? But this was present in your original code, so I left it as is.
        }).Start();
  }

  public void RunStockParallel(string stockName)
  {
     var count = 0;
     do
     {
        HttpWebRequest stocks = null;
        try
        {
           //your logic will be here.. 
        }
        catch (Exception e)
        {
           //throw e;
        }

        //It will call the delegate method so that UI can update. 
        Action action = new Action(SetTextBoxValues);
        //Invoke the delegate
        action();
        //Increment the globally declared var stockname
        this.stockName = count++.ToString();

     } while (true);
  }

  private void SetTextBoxValues()
  {
     this.Dispatcher.Invoke(() =>
           {
              this.text1.Text = stockName;
           });
  }
0

@tushardevsharma

I got an answer for this.. I have added just below peace of code in the RunStockParallel Method , inside the try catch block.. my main logic part with this

HttpWebRequest stocks = null;
try
{
   //your logic will be here.. 

       Dispatcher.BeginInvoke(new Action(() =>
       {
           txtName.Text = stockName;


       }), DispatcherPriority.Background);

}
catch (Exception e)
{
   //throw e;
}
vijay sahu
  • 765
  • 1
  • 7
  • 29
0

I would do it the WPF way, so you don't need to care about using Dispatcher...

XAML Code:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignInstance local:ViewModel}">
    <StackPanel>
        <Button Command="{Binding StartPollingCommand}" Content="Start Polling" />
        <TextBlock Text="{Binding StockValue}" />
    </StackPanel>
</Window>

Your C# Code:

public partial class MainWindow {
    public MainWindow() {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}

public class ViewModel : INotifyPropertyChanged {
    private string _stockValue;
    public event PropertyChangedEventHandler PropertyChanged;

    public ICommand StartPollingCommand {
        get { return new RelayCommand(param => DoExecuteStartPollingCommand()); }
    }

    private void DoExecuteStartPollingCommand() {
        try {
            Task.Run(() => RunStockParallel("StockName"));
        } catch (Exception ex) {
            //TODO
        }
    }

    private void RunStockParallel(string stockName) {
        var count = 0;
        do {

            try {
                // Do Something to get your Data
                //HttpWebRequest stocks = null;
                var stockresults = DateTime.Now;
                StockValue = stockresults.ToString();
            } catch (Exception e) {
                //throw e;
            }
            //Wait some time before getting the next stockresults
            Thread.Sleep(1000);
        } while (true);
    }

    public string StockValue {
        get => _stockValue;
        set {
            _stockValue = value;
            OnPropertyChanged();
        }
    }

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class RelayCommand : ICommand {
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors

    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<object> execute)
        : this(execute, null) { }

    /// <summary>
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<object> execute, Predicate<object> canExecute) {
        if (execute == null)
            throw new ArgumentNullException("execute"); //NOTTOTRANS

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter) {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public void Execute(object parameter) {
        _execute(parameter);
    }

    #endregion // ICommand Members
}

You should end the running Task smoothly before closeing the application...

Markus
  • 2,184
  • 2
  • 22
  • 32
-1

I don't know what error you have encountered but assuming you had a Cross Thread Operation Not Valid Error. It is probably due to this line

this.text1.Text = stockName;

You are accessing the text1 directly and thus will show you an error about Cros Threading. A safe way was to invoke the .Text method using a delegate function

this.text1.Invoke(new Action(() => this.text1.Text = stockName));

I have not tested it but you had the idea. If you want to have a cross-threading safe call. You may refer to this

Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on

keysl
  • 2,127
  • 1
  • 12
  • 16
  • This above code works in windows form application but in WPF you have to use Dispatcher.. which I have already used... – vijay sahu Apr 23 '18 at 04:54
-1

Your problem in the code is that there is no sleep. The thread is too busy. You need to either add a Thread.Sleep(100) in the while loop or use a semaphore.

Joe Sonderegger
  • 784
  • 5
  • 15