#c# #windows-phone-7 #asynchronous #httpwebrequest
#c# #windows-phone-7 #асинхронный #httpwebrequest
Вопрос:
Я работаю над приложением для Windows Phone 7, которое позволяет пользователю просматривать статистику каждого из своих «сайтов» в их Chargify.com учетная запись.
Я просматривал обучающее видео с множественным обзором, которое помогло мне пройти большую часть пути, однако мои данные поступают из сложного источника, и у них был жестко запрограммированный список.
Итак, вот настройка:
Модель:
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;
}
}
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");
}
}
}
Представление:
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;
}
}
Проблема в том, что когда я вызываю это.DataContext — член _viewModel еще не имеет своих данных. Итак, view привязывает данные, но значение пустое.
Есть предложения? Все работает нормально, за исключением того, что представление не заполняет привязанные элементы управления к данным..
— Kori
Комментарии:
1. Как выглядит ваше представление, в котором вы выполняете привязку? Просто скопируйте один из текстовых блоков / etc. Я предполагаю, что вы привязываете onetime и вам нужно использовать режим привязки oneway.
2. Это выглядит следующим образом: <Высота текстового блока = «30» Горизонтальное выравнивание =»Левое» поле = «351,23,0,0» Имя = «sellerNameTbl» Текст =»{Путь привязки=SiteStats.seller_name}» Вертикальное выравнивание =»Top» />
Ответ №1:
_viewModel
должно быть ObservableCollection<>
для статистики, и вы привязываете свой пользовательский интерфейс к этой коллекции. Всякий раз, когда элементы добавляются в коллекцию или удаляются из нее, пользовательский интерфейс обновляется автоматически (поскольку он отправляет OnPropertyChanged
событие)
Комментарии:
1. Это только половина решения. Коллекция также должна быть потокобезопасной или заполняться из потока пользовательского интерфейса. И его SiteStats — это не коллекция, а отдельный класс с одним строковым свойством.
2. Есть и другие свойства, но в целом это довольно простой объект, который я получаю обратно — я просто не хотел вводить все это. / ленивый
Ответ №2:
Ваша проблема не в WPF, а в GetSiteStatistics. Поскольку вы получаете результат асинхронно, ваш метод почти всегда возвращает null, если только случайно BeginGetResponse не выполняется до возврата метода GetSiteStatistics. Это привело бы к сбою в любом приложении.
Вы могли бы заставить GetSiteStatistics всегда создавать и возвращать объект и заполнять его только в BeginGetResponse . Но тогда вы должны убедиться, что все это потокобезопасно.
Ответ №3:
Хорошо, я думаю, с этим разобрался..
Это было там все время, я просто пропустил это.
1) вы получаете данные асинхронно. «Запрос.Запуск ответа (результат => …. );» произойдет когда-нибудь в будущем, но вы возвращаете свой результат до того, как это произойдет.. код продолжает перемещаться, он не ждет вашего результата. Вот что вы хотите сделать:
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
}
}
Соответствующий код ViewModel будет выглядеть примерно так:
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);
});
}
Вот изменения, которые вам нужно внести в класс Application (я не уверен, насколько это потокобезопасно, и я бы действительно рекомендовал вам использовать что-то вроде DispatcherHelper от MVVMLight.
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;
}
}