简体   繁体   中英

INotifyPropertyChanged/Binding?

I am trying to figure out how to update my main UI (for example a textblock) from another thread. The only way I have been able to do it so far is using the Progress object. I have a situation where I cannot use the Progress object and I was recently pointed to using the MVVM / binding method. I have watch a few videos and done some examples but I can't seem to get it to work.

<TextBlock Name="txtblock1" Text="{Binding count}"></TextBlock>

Here are my errors

Exception thrown: 'System.Runtime.InteropServices.COMException' in System.Runtime.WindowsRuntime.dll Exception thrown: 'System.Runtime.InteropServices.COMException' in mscorlib.ni.dll Exception thrown: 'System.Runtime.InteropServices.COMException' in mscorlib.ni.dll

View (Code behind)

public sealed partial class MainPage : Page 
{
    public MainPage()
    {
        this.InitializeComponent();
        ViewModelexample obj = new ViewModelexample();
        txtblock1.DataContext = obj;
        obj.Methodasync();
    }
}

ViewModel

public class ViewModelexample : INotifyPropertyChanged
{
    public string count { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void onPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler !=null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }


    public async void Methodasync()
    {
        await Method();         
    }

    public Task Method()
    {
        return Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                Task.Delay(1000).Wait();
                Debug.WriteLine(i.ToString());
                count = i.ToString();
                onPropertyChanged(i.ToString());
            }
        });
    }
}

Any ideas on what I might be doing wrong?

Thank you

Your count Property needs to be able to notify that it has changed

public class ViewModelexample : INotifyPropertyChanged
{    
    private string _count;
    public string count { 
        get { return _count; } 
        set {
            if(value != _count) {
                _count = value;
                OnPropertyChanged(nameof(count));
            }
        } 
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        // the new Null-conditional Operators are thread-safe:
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private int _testCount = 0;
    public void Method() {
        testCount++;
        Debug.WriteLine(testCount.ToString());
        count = testCount.ToString();
    }

}

The above works because the new Null-conditional Operators are thread-safe :

Another use for the null-condition member access is invoking delegates in a thread-safe way with much less code. ... The new way is thread-safe because the compiler generates code to evaluate PropertyChanged one time only, keeping the result in a temporary variable.

To Test it you can edit your ViewModel method and have the view call it on an event like on page loaded or a button click.

public sealed partial class MainPage : Page 
{
    ViewModelexample obj = null;
    public MainPage()
    {
        this.InitializeComponent();
        obj = new ViewModelexample();
        this.DataContext = obj;        
    }

    public void OnSomeEventHandler() {
        obj.Method();
    }
}

I was under the assumption that binding would take care of the cross threading call.

No, this is only true for some MVVM frameworks (such as WPF). For this reason, I prefer to treat all my ViewModels as belonging to the UI.

With your code sample, you should be able to use Progress<T> :

public async void Methodasync()
{
  var progress = new Progress<int>(value =>
  {
    count = value;
    onPropertyChanged("count");
  });
  await Method(progress);
}

public Task Method(IProgress<int> progress)
{
  return Task.Run(() =>
  {
    for (int i = 0; i < 100; i++)
    {
      Task.Delay(1000).Wait();
      Debug.WriteLine(i.ToString());
      progress.Report(i);
    }
  });
}

Also note that I changed from StartNew to Run . As a general rule, don't use StartNew for reasons I describe on my blog .

If you "really for serious" cannot use Progress<T> for some odd reason, then you can use Reactive Extensions or SynchronizationContext directly.

Example using SynchronizationContext :

public Task Method()
{
  var context = SynchronizationContext.Current;
  return Task.Run(() =>
  {
    for (int i = 0; i < 100; i++)
    {
      Task.Delay(1000).Wait();
      Debug.WriteLine(i.ToString());
      var localI = i;
      context.Post(() =>
      {
        count = localI;
        onPropertyChanged("count");
      });
    }
  });
}

The localI thing is to avoid closing over the loop variable .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM