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

有帮助吗?

解决方案

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

其他提示

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);
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top