0

I am developing a windows 8 store app, using C#/xaml, with the MVVM pattern. I want to refresh a page automatically every 30 seconds, after a search I came up with this, but how do I implement it into a MVVM page?

Edit: As of Charleh's answer I came up with this:

    var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
            var period = TimeSpan.FromSeconds(30);

            var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
             {

                await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                 {
                     RefreshOrdersList();
                 });
             }, period);

But the VS compiler marks an 'Error' under the dispatcher.RunAsync() function: "The 'await' operator can only be used within an async lambda expression". When I remove the 'await' keyword the application runs with a warning saying "because this call is not awaited, execution of the current method continues before the call is completed, Consider apply the 'await' operator to the result of the call".

RefreshOrdersList function -gets all orders and details from WCF service:

     private async void RefreshOrdersList()
    {

        var orders = await proxy.GetAllOrdersAsync();

        IList<OrderCart> orderModel = new List<OrderCart>();
        foreach (var order in orders)
        {
            OrderCart oc = new OrderCart { Id = order.Id, FullPrice = Convert.ToDouble(order.Total), TableId = order.TableId, IsDone = false, OrderTime = (DateTime)order.StartOrderTime };
            order.OrderDetails = await proxy.GetOrderDetailsByOrderIdAsync(order.Id);
            oc.OrderCartItems = new ObservableCollection<OrderCartItem>();
            foreach (var orderDetail in order.OrderDetails)
            {
                var course = await proxy.GetCourseByIdAsync(orderDetail.CourseId);
                OrderCartItem oci = new OrderCartItem { Quantity = orderDetail.Amount, Course = new Course(course) };
                oc.OrderCartItems.Add(oci);
            }
            orderModel.Add(oc);
        }

        var sortOrder = orderModel.OrderByDescending(x => x.Id);

        Items = new ObservableCollection<OrderCartViewModel>(sortOrder.
                                                                    Select(o => new OrderCartViewModel(o))
                                                                    .ToList());

Items property:

       public ObservableCollection<OrderCartViewModel> Items
    {
        get { return _items; }

        private set { SetProperty(ref _items, value); }
    }

Anyone??

Community
  • 1
  • 1
WIN8
  • 15
  • 6
  • What exactly is your problem? What did you try? – nvoigt Jan 05 '14 at 12:21
  • I tried to implement the timer in the view-model but does not recognize the Dispatcher.RunAsync() function? I know it is a problem' but how to solve it, Anyone?? – WIN8 Jan 05 '14 at 12:25
  • Try to use DispatcherTimer http://stackoverflow.com/questions/9078704/how-do-we-set-timers-in-metro-app – Hung Le Jan 14 '14 at 22:38

2 Answers2

1

The dispatcher is found on UI types (controls etc) and it's job is to sync to the UI thread so you will only find this in your views.

Is there any reason the whole view needs to reload?

If you are using the MVVM pattern, you can just do your work asyncronously in the viewmodel, and then update your properties as per usual.

e.g.

class SomeViewModel
{
    IEnumerable<Results> Results { get; set; } // Obviously this would actually need to raise PropertyChanged from INotifyPropertyChanged in order to refresh any bindings

    public SomeViewModel()
    {
        var period = TimeSpan.FromMinutes(1);

        var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
        {
             // do your query/work here
             DoSomeWorkAsync();
        }, 
        period);
    }

    void DoSomeWorkAsync()
    {
        // Get the data
        someService.GetResults((result) => { Results = result.Data; });
    }
}

The binding system will take care of the UI update. I assume you just want to go off to the datasource (a web service maybe?) and get new results every 30 seconds, do you need more than just a data/binding update?

Disclaimer: Didn't test this, and you'd also need to be aware of exceptions thrown within the thread pool work item and handle appropriately

Edit: In response to your question about the binding system

The WPF/RT/SL binding system looks for data sources that implement certain interfaces - one of these is INotifyPropertyChanged. As long as the datasource you are binding to implements this (and in this case your datasource is the viewmodel), and you raise a PropertyChanged event for the property when it changes, the binding system will know to refresh

I have a code snippet in Visual Studio (well actually Resharper as it works a bit better) that writes the property for me - I think there is one included in VS but it should look something like this:

private int _someInt;
public int SomeInt
{
    get { return _someInt; }
    set 
    {
        if(_someInt != value)
        {
            _someInt = value;
            // Helper function in the class which checks to see if propertychanged has any subscribers
            OnPropertyChanged("_someInt"); 
        }
    }
}

Note: There are better implementations of this using Expressions instead of 'magic strings' or using new language features. If you are using the latest c# version the addition of CallerMemberName attribute means you don't need to specify the property name

Edit 2: Ok a complete example may look like this

public class SomeViewModel : INotifyPropertyChanged
{
    #region Properties (that support INotifyPropertyChanged)

    private IEnumerable<Result> _results;
    public IEnumerable<Result> Results
    {
        get
        {
            return _results;
        }
        set
        {
            if (value != _results)
            {
                _results = value;
                OnPropertyChanged("Results");
            }
        }
    }

    #endregion

    // A reference to the service that will get some data
    private IDataService _dataService;

    public SomeViewModel(IDataService dataService)
    {
        _dataService = dataService;

        var period = TimeSpan.FromMinutes(1);

        var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
        {
            // do your query/work here
            GetData();
        },
        period);
    }

    #region Data fetch method

    /// <summary>
    /// Method to get some data - waits on a service to return some results
    /// </summary>
    void GetData()
    {
        // Get the data
        _dataService.GetResults((result) => 
        { 
            // Try this instead
            Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.Invoke(() => {
            Results = result.Data; }
        });
    }

    #endregion

    #region INotifyPropertyChanged

    // Note: usually most MVVM frameworks have a base class which implements the INPC interface for you (such as PropertyChangedBase in Caliburn Micro)
    // so it might be worth fishing around in your framework for it if you are using one.. If you aren't using a framework then you are a braver man than me
    // start using one now!

    /// <summary>
    /// The property changed event
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// The helper function to raise the event
    /// </summary>
    /// <param name="propertyName">Name of the property that changed (to tell the binding system which control to update on screen based on bindings)</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

Edit: update code above. Let me know if you can't get hold of the dispatcher still

Charleh
  • 13,749
  • 3
  • 37
  • 57
  • No, I have no reason to reload the whole view. But how do I "awake" the data binding system every 30 sec. from my view-model? – WIN8 Jan 05 '14 at 12:36
  • Updated, let me know if you need any more information, or even a complete example – Charleh Jan 05 '14 at 12:44
  • Thanx for the response, but I am still having trouble, Can you provide a complete example? – WIN8 Jan 05 '14 at 13:07
  • Updated again - let me know if you have any more questions, and if you aren't using an MVVM framework then it's advisable to do so as you will spend a lot of time writing plumbing code and doing stuff that other people have already figured out – Charleh Jan 05 '14 at 13:20
  • I followed your example,Thanx again!, page reloads fine at first, but after the time period goes by this exception comes up at the observable collection "The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))", I looked it up and I found [this](http://stackoverflow.com/questions/14805543/windows-8-app-the-application-called-an-interface-that-was-marshalled-for-a-diff), How to solve this?, (note: I am using the "prism" functionality) – WIN8 Jan 05 '14 at 13:32
  • Edit question-- please advice! – WIN8 Jan 05 '14 at 15:05
  • Looks like you still need to access the dispatcher to marshall the update to the UI thread (I've been using Silverlight a lot and SL doesn't do sync service calls). I'll update – Charleh Jan 05 '14 at 15:12
  • No, It didn't work!, the view-model does not recognize the dispatcher. – WIN8 Jan 05 '14 at 15:30
  • Try `Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher` instead – Charleh Jan 05 '14 at 17:09
  • Did you use Invoke or an Async method? If you use the original code you will need to await the async method. You should probably read up on async/await keywords in C# – Charleh Jan 05 '14 at 17:45
  • I have found the problem!, var timer = ThreadPoolTimer.CreatePeriodicTimer(async(source), Tanx for your time Charleh! – WIN8 Jan 05 '14 at 19:11
1

Just need to add the 'async' keyword var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source)=>, and no errors or warning were detected:

   var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
        var period = TimeSpan.FromSeconds(30);

        var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source) =>
         {

            await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
             {
                 RefreshOrdersList();
             });
         }, period);
WIN8
  • 15
  • 6