Статьи

Портирование достижений Visual Studio для WP на Windows 8 – макет и основные привязки

Одной из замечательных функций, которые в настоящее время выделены для Windows 8 и, в частности, для WinRT, является низкий уровень доступа для существующих разработчиков Windows Phone и Silverlight в целом. Уже был случай, когда существующее приложение Windows Phone было портировано на новую ОС, но я решил на самом деле задокументировать весь процесс портирования в его ядре.

Давайте начнем с самых основ и предположим, что у меня нет никакого кода на моей локальной машине. Я направляюсь на страницу CodePlex и загружаю исходный ZIP. Конечно, вы можете выбрать систему управления версиями и использовать источник таким образом, но я оставлю это на ваше усмотрение. Решение по умолчанию, конечно, открывается в Visual Studio 2010. Содержимое вашего решения должно выглядеть следующим образом:

Теперь давайте работать над реальным новым проектом. Запустите Visual Studio 11 Beta и нажмите « Новый проект» . Выберите Visual C # > Windows Metro Style и, наконец, — Пустое приложение . Дайте уникальное имя проекту. Я лично использовал VSAW8 . Open BlankPage.xaml — это главная рабочая страница, на которой будет отображаться вся необходимая информация о пользователе. Для удобства я переименовал его в MainPage.xaml .

ПРИМЕЧАНИЕ. Не забудьте также переименовать ссылки на классы. Переименование файла в Solution Explorer само по себе не поможет. 

Давайте посмотрим, что мы имеем в MainPage.xaml для проекта Windows Phone.

<phone:PhoneApplicationPage 
    x:Class="VisualStudioAchievements.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:VisualStudioAchievements"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="728"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="False">
    
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <toolkit:PerformanceProgressBar VerticalAlignment="Top" x:Name="perfBar" IsIndeterminate="True" Visibility="{Binding Path=Instance.HidePerfBar,Source={StaticResource LocalBindingPoint},Converter={StaticResource PerfBarConverter}, ConverterParameter=p}"></toolkit:PerformanceProgressBar>

        <Grid Margin="0,20,0,20" Width="440" Background="{StaticResource PhoneAccentBrush}">
            <Image HorizontalAlignment="Left" Source="Images/pagelogo.png"></Image>
        </Grid>
        

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="20,0,20,20">
            <TextBlock Visibility="{Binding Path=Instance.Niners.Count,Source={StaticResource LocalBindingPoint},Converter={StaticResource CountToVisibility}}" TextAlignment="Left" FontFamily="{StaticResource PhoneFontFamilySemiLight}" FontSize="{StaticResource PhoneFontSizeLarge}" TextWrapping="Wrap" Text="add users to track their visual studio achievements." Foreground="Gray"></TextBlock>
            <ListBox Foreground="Black" ItemsSource="{Binding Path=Instance.Niners,Source={StaticResource LocalBindingPoint}}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Width="440" HorizontalAlignment="Center" Margin="0,0,0,20" Tap="StackPanel_Tap" Tag="{Binding Alias}" Height="180">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="80"></RowDefinition>
                                <RowDefinition Height="100"></RowDefinition>
                            </Grid.RowDefinitions>
                            
                            <toolkit:ContextMenuService.ContextMenu>
                                <toolkit:ContextMenu>
                                    <toolkit:MenuItem Tag="{Binding Alias}" Click="mnuPin_Click" Header="pin to start" x:Name="mnuPin"></toolkit:MenuItem>
                                    <toolkit:MenuItem Tag="{Binding Alias}" Click="mnuDelete_Click" Header="delete" x:Name="mnuDelete"></toolkit:MenuItem>
                                </toolkit:ContextMenu>
                            </toolkit:ContextMenuService.ContextMenu>
                            
                            <StackPanel>
                                <TextBlock Foreground="Black" Grid.Row="0" FontFamily="Segoe WP Black" FontSize="25" Text="{Binding Alias,Converter={StaticResource BoldNameConverter}}"></TextBlock>
                                <TextBlock Foreground="Black" Grid.Row="0" Text="{Binding Name}"></TextBlock>
                            </StackPanel>

                            <Grid Grid.Row="1">
                                <Grid.Background>
                                    <ImageBrush ImageSource="Images/niner_back.png"></ImageBrush>
                                </Grid.Background>
                                <StackPanel Orientation="Horizontal">
                                    <Image Source="{Binding Avatar}" Height="78" Width="78" HorizontalAlignment="Left" Margin="10,0,0,0"></Image>
                                    <TextBlock Margin="20,0,0,0" FontSize="40" Text="{Binding Points}" Grid.Column="1" TextAlignment="Right" Foreground="White" VerticalAlignment="Center"></TextBlock>
                                </StackPanel>
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>
 
    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton x:Name="btnAddUser" Click="btnAddUser_Click" IconUri="/Images/appbar.add.png" Text="add"/>
            <shell:ApplicationBarIconButton x:Name="btnCompare" Click="btnCompare_Click"  IconUri="/Images/appbar.arrow.down.up.png" Text="compare"/>
            <shell:ApplicationBar.MenuItems>
                <!--<shell:ApplicationBarMenuItem Text="settings"/>-->
                <shell:ApplicationBarMenuItem x:Name="btnAbout" Click="btnAbout_Click"  Text="about"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>  

Основа здесь, очевидно, является основной сеткой. Я также использую Silverlight Toolkit здесь — чего я должен буду избегать в проекте WinRT, так как он вообще не поддерживает его. Также есть фрагмент, который строит панель приложений. В Windows 8 эта часть самого приложения обрабатывается другим способом, поэтому я просто скопировал и вставил существующий макет сетки в свой проект Metro без дополнительных пользовательских компонентов.

Первое, что вы можете заметить, это то, что я использовал обработчик событий Tap, чтобы отслеживать, когда пользователь нажимает на запись пользователя. В приложениях Metro нет события Tap.

Как только эта часть будет удалена, пришло время исправить привязку. Для VSA WP у меня был класс с именем BindingPoint.cs . Я скопировал его в проект Metro как есть — все, что мне нужно было исправить, это добавить ссылки на System.ComponentModel , чтобы получить доступ к INotifyPropertyChanged . Кроме того, мне нужны были правильные ссылки на S ystem.Collections.ObjectModel (для ObservableCollection <T> ) и мою собственную модель Найнера .

ПРИМЕЧАНИЕ. Как правило, не должно возникать проблем с прямым импортом классов, представляющих ваши собственные модели данных. до тех пор, пока вы правильно ссылаетесь на любые перекрестные классы. Например, если вы используете пользовательскую модель, которая ссылается на другую модель, не забудьте добавить обе в проект.

В частности, мне особенно интересен тот факт, что я больше не использую класс Dispatcher для выполнения многопоточных вызовов. Вместо этого внутри BindingPoint для метода NotifyPropertyChanged я использую Task.Run, который вызывается с помощью await .

async private void NotifyPropertyChanged(String info)
{
    if (PropertyChanged != null)
    {
         await Task.Run(() => { PropertyChanged(this, new PropertyChangedEventArgs(info)); });
    }
}

Мне также нужно добавить несколько конвертеров, которые связаны внутри MainPage.xaml . Первый — CountToVisibilityConverter — класс, который помогает мне решить, будет ли отображаться описательный TextBlock или нет, в зависимости от того, есть ли пользователи в наборе отслеживаемых членов Channel9. Класс реализует IValueConverter, и вам нужно знать об одном изменении — изменилась подпись для метода Convert. Вот что у вас было в проекте Windows Phone:

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 

А вот что у вас есть в проекте Metro:

public object Convert(object value, Type targetType, object parameter, string language)

Обратите внимание на тонкое изменение  параметра CultureInfo в строку . Кроме этого, идея преобразования стоимости остается прежней. Кроме того, не забывайте, что ссылки XAML на классы связывания и преобразования требуют предварительного объявления. Я выбрал их внутри App.xaml:

<Application
    x:Class="VSAW8.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:VSAW8">

    <Application.Resources>      
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Common/StandardStyles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <local:BindingPoint x:Key="LocalBindingPoint"></local:BindingPoint>
            <local:CountToVisibilityConverter x:Key="CountToVisibility"></local:CountToVisibilityConverter>
        </ResourceDictionary>
    </Application.Resources>
</Application>

После того, как все настройки были сделаны, я получил сетку, в которой отсутствуют все ссылки на телефоны:

    <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Grid Margin="0,20,0,20" Width="440">
            <Image HorizontalAlignment="Left" Source="Images/pagelogo.png"></Image>
        </Grid>


        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="20,0,20,20">
            <TextBlock Visibility="{Binding Path=Instance.Niners.Count,Source={StaticResource LocalBindingPoint},Converter={StaticResource CountToVisibility}}" TextAlignment="Left" TextWrapping="Wrap" Text="add users to track their visual studio achievements." Foreground="Gray"></TextBlock>
            <ListBox Foreground="Black" ItemsSource="{Binding Path=Instance.Niners,Source={StaticResource LocalBindingPoint}}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Width="440" HorizontalAlignment="Center" Margin="0,0,0,20" Tag="{Binding Alias}" Height="180">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="80"></RowDefinition>
                                <RowDefinition Height="100"></RowDefinition>
                            </Grid.RowDefinitions>

                            <StackPanel>
                                <TextBlock Foreground="Black" Grid.Row="0" FontFamily="Segoe WP Black" FontSize="25" Text="{Binding Alias}"></TextBlock>
                                <TextBlock Foreground="Black" Grid.Row="0" Text="{Binding Name}"></TextBlock>
                            </StackPanel>

                            <Grid Grid.Row="1">
                                <Grid.Background>
                                    <ImageBrush ImageSource="Images/niner_back.png"></ImageBrush>
                                </Grid.Background>
                                <StackPanel Orientation="Horizontal">
                                    <Image Source="{Binding Avatar}" Height="78" Width="78" HorizontalAlignment="Left" Margin="10,0,0,0"></Image>
                                    <TextBlock Margin="20,0,0,0" FontSize="40" Text="{Binding Points}" Grid.Column="1" TextAlignment="Right" Foreground="White" VerticalAlignment="Center"></TextBlock>
                                </StackPanel>
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>

Чтобы проверить, насколько хорошо это работает, я создал простой цикл, который добавляет группу пользователей в коллекцию Niners внутри BindingPoint .

for (int i = 100; i < 120; i++)
{
    Niner niner = new Niner();
    niner.Alias = "Den";
    niner.Avatar = new Uri("http://www.blogymate.com/Exclusive/E1711201121246.jpg");
    niner.Caption = "TEST";
    niner.Name = "Den Delimarsky";
    niner.Points = i;

    BindingPoint.Instance.Niners.Add(niner);
}

Макет, с которым я закончил, таков:

Очевидно, что основные части все еще отсутствуют, и я буду работать над общими корректировками, но мне удалось продемонстрировать, как базовые структуры XAML и связывания остались практически неизменными, а усилия по переносу этой части проекта минимальны.