Search code examples
c#mvvmwindows-phone-8.1compass

Compass in Windows Phone 8.1 COMException OnPropertyChanged MVVM


I got a problem with my MVVM pattern.

class MagnetometrViewModel : INotifyPropertyChanged
{
    private Compass compass;
    double temp;
    public MagnetometrViewModel()
    {
        compass = Compass.GetDefault();
        CompassControl_Loaded();
    }

    private double heading;
    public double Heading
    {
        get
        {
            return heading;
        }
        set
        {
            heading = value;
            OnPropertyChanged("Heading");
        }
    }

    private void CompassControl_Loaded()
    {
        compass = Windows.Devices.Sensors.Compass.GetDefault();
        if (compass != null)
        {
            compass.ReadingChanged += CompassReadingChanged;
        }
    }

    private void CompassReadingChanged(Windows.Devices.Sensors.Compass sender, Windows.Devices.Sensors.CompassReadingChangedEventArgs args)
    {

         temp = Convert.ToDouble(args.Reading.HeadingMagneticNorth);
         Heading = temp;
         //Heading = Convert.ToDouble(args.Reading.HeadingMagneticNorth);
    }

    #region PropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion
}

When I debuging on that line temp = Convert.ToDouble(args.Reading.HeadingMagneticNorth); it get the result, but my other method OnPropertyChanged throws exception: System.Runtime.InteropServices.COMException


Solution

  • You need to fire the PropertyChanged events on the UI thread, if you're doing a Silverlight Windows Phone app, then you can do something a little like:

        protected delegate void OnUIThreadDelegate();
        /// <summary>
        /// Allows the specified delegate to be performed on the UI thread.
        /// </summary>
        /// <param name="onUIThreadDelegate">The delegate to be executed on the UI thread.</param>
        protected static void OnUIThread(OnUIThreadDelegate onUIThreadDelegate)
        {
            if (onUIThreadDelegate != null)
            {
                if (Deployment.Current.Dispatcher.CheckAccess())
                {
                    onUIThreadDelegate();
                }
                else
                {
                    Deployment.Current.Dispatcher.BeginInvoke(onUIThreadDelegate);
                }
            }
        }
    
        protected virtual void OnPropertyChanged(string propertyName)
        {
            OnUIThread(() =>
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            });
        }
    

    If you're using the more modern Universal style apps, then simply change the OnUIThread implementation to be something more like:

        protected async void OnUIThread(DispatchedHandler onUIThreadDelegate)
        {
            if (onUIThreadDelegate != null)
            {
                var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
                if (dispatcher.HasThreadAccess)
                {
                    onUIThreadDelegate();
                }
                else
                {
                    await dispatcher.RunAsync(CoreDispatcherPriority.Normal, onUIThreadDelegate);
                }
            }
        }