开发者

How can I bind my WP7 interface to data I'm retrieving asynchronously?

I'm working on a Windows Phone 7 app which allows a user to view the statistics of each of their "sites" under their Chargify.com account.

I've been following a Plural-sight training video which got me most of the way there, however my data is coming from a complex source and they had hard-coded a list.

So, here's the setup:

The model:

 public SiteStats
 {
     public string seller_name { get; set;}
     public static GetSiteStatistics(string subdomain, string apiKey)
     {
        SiteStats retVal = null;
        HttpWebRequest request = WebRequest.Create(string.Format("https://{0}.chargify.com/stats.json", subdomain)) as HttpWebRequest;
        NetworkCredential credentials = new NetworkCredential(apiKey, "X");
        request.Credentials = credentials;
        request.Method = "GET";
        request.Accept = "application/json";
        request.BeginGetResponse(result =>
        {
            using (var response = request.EndGetResponse(result))
            {
                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    string stats = reader.ReadToEnd();
                    retVal = Json.Deserialize<SiteStats>(stats);
                }
            }
        }, request);
  开发者_运维技巧      return retVal;
     }
 }

The ViewModel:

 public class SiteDetailViewModel : ViewModelBase
 {
    private SiteStats _siteStats;
    public SiteDetailViewModel(string subdomain) : this()
    {
       this._siteStats = SiteStats.GetSiteStatistics(subdomain, "apiKeyHere");
    }
    public SiteDetailViewModel : base() { ViewName = "site details"; }
    public SiteStats SiteStats
    {
      get { return _siteStats; }
      set {
        if (_siteStats != value) {
          _siteStats = value;
          OnPropertyChanged("SiteStats");
        }
     }
 }

The View:

 public partial class SiteDetailView : PhoneApplicationPage
 {
   private SiteDetailViewModel _viewModel;

   public SiteDetailView()
   {
      InitializeComponent();
      Loaded += new RoutedEventHandler(SiteDetailView_Loaded);
   }
   void SiteDetailView_Loaded(object sender, RoutedEventArgs e)
   {
      string subdomain = NavigationContext.QueryString["subdomain"];
      _viewModel = new SiteDetailViewModel(subdomain);
      this.DataContext = _viewModel;
   }
 }

The problem is, that when I call this.DataContext - the _viewModel member doesn't have it's data yet. So, the view databinds - but the value is empty.

Any suggestions? Everything works fine except that the View isn't populating the bound controls to the data ..

-- Kori


_viewModel should have an ObservableCollection<> for the stats and you bind your UI to that collection. Whenever items are added to or removed from the collection, the UI is updated automatically (since it sends out the OnPropertyChanged event)


Your problem is not WPF, but GetSiteStatistics. Since you're getting the result async your method almost always returns null, unless by chance the BeginGetResponse is executed before the GetSiteStatistics method returns. It would fail in any application.

You could have GetSiteStatistics always create and return an object and only fill it in BeginGetResponse. But then you should make sure the whole thing is thread safe.


Alright I got this one figured out I think..

It was there all along I just missed it.

1) you are asynchronously getting data. The "request.BeginGetResponse(result=> .... );" happens sometime in the future, but you return your result before that happens.. the code keeps moving it doesn't wait for your result. Here's what you want to do:

public class SiteStats
{
  public string seller_name { get; set;}
  public static void GetSiteStatistics(string subdomain, string apiKey, Action<SiteStats> callback)
  {
    SiteStats retVal = null;
    HttpWebRequest request = WebRequest.Create(string.Format("https://{0}.chargify.com/stats.json", subdomain)) as HttpWebRequest;
    NetworkCredential credentials = new NetworkCredential(apiKey, "X");
    request.Credentials = credentials;
    request.Method = "GET";
    request.Accept = "application/json";
    request.BeginGetResponse(result =>
    {
        using (var response = request.EndGetResponse(result))
        {
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                string stats = reader.ReadToEnd();
                retVal = Json.Deserialize<SiteStats>(stats);
                callback(retVal);
            }
        }
    }, request);
    //return retVal; // you can't return here
  }
}

The pertinent ViewModel Code will look something like this:

public SiteDetailViewModel(string subdomain) : this()
{
   SiteStats.GetSiteStatistics(subdomain, "apiKeyHere", (result)=> {
     // Note you may need to wrap this in a Dispatcher call 
     // as you may be on the wrong thread to update the UI 
     // if that happens you'll get a cross thread access 
     // you will have to expose the dispatcher through some 
     // other mechanism. One way to do that would be a static
     // on your application class which we'll emulate and 
     // I'll give you the code in a sec
     myRootNamespace.App.Dispatcher.BeginInvoke(()=>this._siteStats = results);
   });
}

Here are the changes you need to make to the Application class (I'm not sure how threadsafe this is and I would really recommend that you use something like MVVMLight's DispatcherHelper.

public partial class App : Application
{

    public static Dispatcher Dispatcher { get; private set; } // Add this line!!

    // More code follows we're skipping it

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        this.RootVisual = new MainPage(); 
        Dispatcher = this.RootVisual.Dispatcher; // only add this line!!
    }

    private void Application_Exit(object sender, EventArgs e)
    {
        // Do this to clean up
        Dispatcher = null;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜