Статьи

Портирование достижений Visual Studio для WP на Windows 8 — незначительные проблемы и сбор данных

Это следующий пост из серии, в котором описывается, как перенести приложение Windows Phone на Windows 8. Вчера я показал, как легко можно перемещать базовую компоновку и основные привязки . Сегодня я исправляю некоторые незначительные проблемы, а также показываю, как получить основные пользовательские данные.

Прежде всего, вы, вероятно, заметили, что, хотя мои элементы управления изображениями привязаны к источнику Uri из модели Niner, изображения не отображаются. Поэтому я не получаю изображения профиля для каждого отслеживаемого пользователя через мое приложение. Я не уверен, является ли это ошибкой или нет, но, очевидно, прямое связывание через XAML не слишком хорошо работает для изображений. Поэтому мне пришлось подумать об обходном пути. Вместо привязки URL к свойству Source я привязываю его к тегу. Затем я использую обработчик событий Loaded, который фактически загружает изображение. XAML будет выглядеть так:

<Image Tag="{Binding Avatar}" Loaded="NinerImageLoaded" Height="78" Width="78" HorizontalAlignment="Left" Margin="10,0,0,0"></Image>

Помните, что свойство Avatar находится в модели Niner, которая представляет пользователя Channel9. Теперь код для обработчика событий, о котором я упоминал выше, выглядит следующим образом:

private void NinerImageLoaded(object sender, RoutedEventArgs e)
{
    Image entity = (Image)sender;
    BitmapImage image = new BitmapImage(new Uri(entity.Tag.ToString(), UriKind.Absolute));

    entity.Source = image;
}

Это было легко, и теперь мы получаем образец изображения для каждой записи пользователя (на основе кода, который я показал в предыдущей статье):

Давайте теперь получим реальные данные, так как обработка выборочных единиц не совсем цель этого приложения. В приложении VSA для Windows Phone у меня был класс NinerReader, который обрабатывал как данные JSON, так и HTML. Это было немного сложно, но все же сделало свою работу довольно хорошо. Вы можете ознакомиться с ним, посмотрев на исходный код CodePlex .

Прежде всего, мне нужно изменить пакет кода манипуляции JSON. Посмотрите на этот фрагмент:

JsonValue obj = JsonObject.Parse(JSONContent);
JsonValue value = obj["Achievements"];

foreach (JsonValue aValue in value)
{
    Achievement achievement = new Achievement();
    achievement.Category = aValue["Category"];

This is an example of perfectly-working code inside a Windows Phone project. However, Visual Studio 11 gives me this in a Metro application:

Something is obviously wrong, and I need to find out what exactly.

NOTE: In a Windows 8 Metro application, you will no longer reference System.Json. Use Windows.Data.Json instead.

JsonObject.Parse(string) no longer returns a JsonValue, but rather a JsonObject. When I am reading an object subitem via obj[itemID], I get an IJsonValue instead of JsonValue.I must admit — JSON parsing got much better in Metro applications, and it makes it much easier to get the necessary data. With a little bit of refactoring, I changed my snippet to this:

JsonObject obj = JsonObject.Parse(JSONContent);
IJsonValue value = obj["Achievements"];

foreach (IJsonValue aValue in value.GetArray())
{
    Achievement achievement = new Achievement();
    achievement.Category = aValue.GetObject().GetNamedString("Category");

    try
    {
        DateTime dateEarned = new DateTime();
        DateTime.TryParse(aValue.GetObject().GetNamedString("DateEarned"), out dateEarned);
        achievement.DateEarned = dateEarned;

        achievement.Description = aValue.GetObject().GetNamedString("Description");
        achievement.FriendlyName = aValue.GetObject().GetNamedString("FriendlyName");
        achievement.Icon = new Uri(aValue.GetObject().GetNamedString("Icon"));
        achievement.IconSmall = new Uri(aValue.GetObject().GetNamedString("IconSmall"));
        achievement.Name = aValue.GetObject().GetNamedString("Name");
        string data = aValue.GetObject().GetNamedString("Points");
        achievement.Points = Convert.ToInt32(data);

        CurrentNiner.Achievements.Add(achievement);

        CurrentNiner.Points += achievement.Points;
    }
    catch
    {
        // Do something else here. Eventually.
        Debug.WriteLine("Achievement not earned");
    }
}

GetNamedString is a lifesaver here — finally a decent method instead of direct array referencing. It works in a similar way, so generally it should not cause major problems with legacy code.

Also, when invoking the action that should be triggered on completion, I was getting this:

This cross-thread problem can be easily mitigated with the help of CoreDispatcher, that is integrated by me in the App class — it can be instantiated when the first page is loaded. The idea itself was provided by Rob Caplan here.

If you prefer to handle the asynchronicity yourself, the worker thread can invoke back to the UI thread by calling InvokeAsync on the Xaml Window’s Dispatcher property (which returns the App’s Windows.UI.Core.CoreDispatcher) . 

this.Dispatcher.InvokeAsync(CoreDispatcherPriority.Normal, (s,invokeEventArgs) => { /* UI thread stuff */}, this, null);

The data acquisition layer is complete at this step. You can test it by using this snippet somewhere in the application:

NinerReader reader = new NinerReader();
reader.GetNiner("dennisdel", true, niner =>
{
    BindingPoint.Instance.Niners.Add(niner);
});

Your result should resemble this:

Also, you will most likely see some exceptions being logged in the Output dialog — that is normal, since those represent cases where I am trying to get the date when I earned an achievement that I haven’t actually earned yet. You can safely ignore those for now.

Conclusion

I showed you that even though some Windows Phone code can be quite ramified and complex, there are only minor adjustments necessary to make it work with WinRT. The biggest aspect that you have to pay attention to is multi-threading, that is handled in a way that might be new for some WinPhone developers.