Traga seu App Windows Store para o Windows 10

Dando continuidade à série de posts sobre como trazer o seu aplicativo para o Windows 10 (Universal Windows), hoje iremos falar dos aplicativos Windows 8.1 e Windows Phone 8.1 (referência: Move from Windows Runtime 8.x to UWP). Esses aplicativos podem ser tanto Windows como Windows Phone e Universais.

Os aplicativos universais são criados usando o template Windows 8 Universal (que agora é chamado de “Universal Windows 8.1 ” no Visual Studio 2015). Neles temos três projetos:

  • Shared: Código compartilhado entre projetos.
  • WindowsPhone: Projeto específico para Windows Phone.
  • Windows: Projeto específico para Windows desktop/tablet.
Se eu não migrar?
Não se preocupe, os aplicativos Windows 8.1, Windows Phone 8.1 e Universais já funcionam no Windows 10, sendo o Windows 8.1 para desktop e Windows Phone 8.1 para mobile.
Por que migrar?
São várias as razões. Iremos enumerar algumas delas aqui:

  • Poder utilizar novos recursos do Windows 10 como Cortana, Drag/Drop para dentro do aplicativo, Adaptive Tiles, etc.
  • Incluir novas formas de lançar Background Tasks.
  • Publicar para diferentes famílias de dispositivos Windows 10, alcançando novos usuários.
  • Ter um ganho de desempenho com a compilação para a nova plataforma.

Este post possui os seguintes tópicos:

Para exemplificar, iremos usar o projeto template Universal “HubApp”, do Windows 8, criado a partir do Visual Studio 2015.

E aí? Vamos fazer a atualização? Assim como eu, você pode ser positivamente surpreendido ao ver que, sem muito esforço, seu código já estará executando em um projeto Universal Windows.

Importante
Antes de iniciar o processo de atualização, tenha certeza que o seu código está sob controle de versão ou que você tenha backup do código.

Como migrar?

Boa parte do seu código poderá ser aproveitado no novo projeto Universal Windows. O grau de aproveitamento do código dependerá de como está a sua implementação atualmente. Mas de certo sabemos que a parte do layout específico Windows e Windows Phone deverão ser reconsiderados levando-se em conta o design responsivo e adaptável para os diferentes tamanhos de tela dos dispositivos Windows 10.

Se você tem uma solução somente para Windows 8.1 ou Windows Phone 8.1, você pode atualizá-lo diretamente ou ainda criar um novo projeto “Universal Windows”. Neste último caso você trará gradativamente seu código atual para o novo projeto e ainda poderá dar suporte para a versão 8.1.

Agora, se você tem a sua solução com os projetos Windows 8 Universal, você pode escolher entre:

  • Manter os projetos Windows e WindowsPhone e criar um novo projeto “Universal Windows”. Nesse caso você poderá utilizar o mesmo projeto “Shared” e ainda manter suporte para seu aplicativo Windows 8.1/Windows Phone 8.1 para eventuais correções de bugs para usuários que ainda não migraram para o Windows 10.
  • Escolher o projeto Windows 8.1 ou Windows Phone 8.1 e atualizá-lo para Universal Windows. A escolha do projeto a ser atualizado dependerá do layout que você considerar mais adaptável para diferentes tamanhos de tela. E neste caso você estará optando pela ruptura não dando mais suporte para a versão 8.1.

Se os aplicativos para Windows 8.1 e Windows Phone 8.1 forem muito diferentes quanto as funcionalidades e recursos, pode-se cogitar ter dois projetos Windows 10, sendo um para Mobile e outra para Desktops e Tablets. Nesse caso você irá migrar os dois.

Atualizando o Projeto

Para atualizar o projeto o Visual Studio ainda não possui uma ferramenta. Mas foi disponibilizado um script Powershell para ajudar a fazer a migração. Aqui iremos fazer o processo sobre o projeto Windows do nosso HubApp:

  1. Baixar o ZIP do repositório GIT que contém os scripts para atualização.
  2. Descompactar o arquivo e abrir o prompt de comando (Cmd).
  3. Executar o comando Run_Upgrade_to_uwp.bat no diretório do seu projeto Windows ou Windows Phone.

    C:\Users\joao.cunha\Documents\Visual Studio 2015\Projects\HubApp1\HubApp1\HubApp1.WindowsPhone>C:\Users\joao.cunha\Downloads\ProjectUpgradeUtility-master\ProjectUpgradeUtility-master\Run_Upgrade_to_uwp.bat

    Optamos aqui por fazer a migração a partir do projeto WindowsPhone pois é mais fácil partir de um layout mobile para desktop do que o contrário.

Pronto, o projeto já é Universal Windows. Mas ainda faltam algumas coisas. Para o projeto Windows, por exemplo, teríamos que fazer algumas alterações no arquivo de manifesto. O elemento Application deve ser definido da seguinte forma:

    <Application Id="App"
        Executable="$targetnametoken$.exe"
        EntryPoint="HubApp1.Windows.App">
        <uap:VisualElements
            DisplayName="HubApp1.Windows"
            Square150x150Logo="Assets\Logo.png"
            Square44x44Logo="Assets\SmallLogo.png"
            Description="HubApp1.Windows"            
            BackgroundColor="#464646">
            <uap:DefaultTile Wide310x150Logo="Assets\WideLogo.png"/>
            <uap:SplashScreen Image="Assets\SplashScreen.png" />
        </uap:VisualElements>
    </Application>
  • Nota 1:Alguns manifestos podem ainda definir o tamanho mínimo da tela. Se for o seu caso, remova pois isso deverá ser feito posteriormente em código.
  • Nota 2:Ainda poderá ser necessário adicionar ou alterar imagens do projeto. Para verificar, feche o editor de código do manifesto e abra o arquivo novamente pelo editor visual. Depois disso, acesse a aba “Visual Assets” e defina as imagens que estão faltando.

Feito isso, vamos editar o arquivo “.csproj” do novo projeto Universal Windows:

  1. Fechar o projeto. Para tanto, selecione o projeto e, no menu “Project”, e então a opção “Unload Project”.
  2. Editar o arquivo .csproj através de editor de arquivos.
  3. Para o primeiro Property Group, caso o conteúdo do elemento seja AnyCPU, altere para x86.
  4. Remova depois quaisquer elementos PropertyGroup em que o conteúdo do elemento seja AnyCPU.
  5. Clicar no projeto e, através do menu Project, selecione a opção “Reload Project”.
  6. Tente recompilar o projeto. Neste momento o projeto deve compilar sem erros.

Lembre-se que no caso dos projetos Windows 8.1 e Windows Phone 8.1 Universal, os arquivos App.xaml.cs e App.xaml estão no projeto Shared. Para nosso exemplo aqui, vamos mantê-lo lá.

Revendo código específico Windows ou Windows Phone

Agora está na hora de revermos o código. Provavelmente há muito código específico Windows ou Windows Phone que poderá falhar pois muitos desses códigos foram migrados para uma API comum. Aliás, as diretivas de compilação WINDOWS_PHONE_APP e WINDOWS_APP não são mais definidas por default. No lugar temos agora o WINDOWS_UWP.

Sugiro procurarmos por cada ocorrência dessas diretivas no nosso código. Para começar, o arquivo App.xaml.cs tem muitas dessas diretivas. Para o nosso exemplo, vamos simplesmente remover todo o código contido nessas diretivas, mas podem haver casos em que esse código deverá ser migrado para o Windows 10 usando API do Universal Windows. O nosso arquivo deverá ficar da seguinte forma:

namespace HubApp1
{
    public sealed partial class App : Application
    {
        public App()
        {
            this.InitializeComponent();
            this.Suspending += this.OnSuspending;
        }

        protected async override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                //Associate the frame with a SuspensionManager key                                
                SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

                // TODO: change this value to a cache size that is appropriate for your application
                rootFrame.CacheSize = 1;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    // Restore the saved session state only when appropriate
                    try
                    {
                        await SuspensionManager.RestoreAsync();
                    }
                    catch (SuspensionManagerException)
                    {
                        // Something went wrong restoring state.
                        // Assume there is no state and continue
                    }
                }

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // When the navigation stack isn't restored navigate to the first page,
                // configuring the new page by passing required information as a navigation
                // parameter
                if (!rootFrame.Navigate(typeof(HubPage), e.Arguments))
                {
                    throw new Exception("Failed to create initial page");
                }
            }

            // Ensure the current window is active
            Window.Current.Activate();
        }

       private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            await SuspensionManager.SaveAsync();
            deferral.Complete();
        }
    }
}

Além disso temos outros casos comuns de código específico Windows ou Windows Phone que teremos que rever:

HardwareButtons.BackPressed do Windows Phone

O tratamento do botão “back” do telefone também deve ser modificado para Universal Windows. No caso teremos que substituir esse evento por Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested. O tratamento para o evento pode ser o mesmo. Além disso, devemos mudar também o lugar onde definimos o tratamento do evento. Normalmente definíamos o evento BackPressed no construtor da classe App.xaml.cs. Mas agora definimos no método OnLaunched.

  • De:
    #if WINDOWS_PHONE_APP
    		Windows.Phone.UI.Input.HardwareButtons.BackPressed += this.HardwareButtons_BackPressed;
    #endif // WINDOWS_PHONE_APP
    
    ...
    
    #if WINDOWS_PHONE_APP
    		Windows.Phone.UI.Input.HardwareButtons.BackPressed -= this.HardwareButtons_BackPressed;
    #endif // WINDOWS_PHONE_APP
    
    ...
    
    #if WINDOWS_PHONE_APP
    	void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
    	{
    		// handle the event.
    	}
    #endif // WINDOWS_PHONE_APP
    
  • Para:
       Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested +=
            this.BackRequested;
    
    ...
    
       Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested -=
            this.BackRequested;
    
    ...
    
    private void BackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e)
    {
        // handle the event.
    }
    

Temas e Estilos específicos Windows ou Windows Phone

Um outro ponto para ser revisto nos projetos Windows 8.1 e Windows Phone 8.1 é o uso de Temas e Estilos que não existem no Windows 10. No nosso projeto HubApp, por exemplo, o projeto Windows Phone utiliza os seguintes estilos e temas que deversão ser substituídos:

  • {ThemeResource PhoneMidBrush}
  • {ThemeResource ListViewItemSubheaderTextBlockStyle}
  • {ThemeResource ListViewItemContentTextBlockStyle}
  • {ThemeResource ListViewItemTextBlockStyle}

PickSingleFileAndContinue do Windows Phone

Esta chamada era usada no Windows Phone para obter uma imagem da câmera ou do álbum do usuário. Além disso era necessário tratar o evento OnActivated no arquivo App.xaml para obter a imagem selecionada pelo usuário. Mas agora você usa somente a chamada PickSingleFileAsync. Não precisamos também tratar o evento continue:

  • De:
    public sealed partial class MainPage : Page, IFileOpenPickerContinuable
    {
        ...
    
        private void CameraAppBarButton_Click(object sender, RoutedEventArgs e)
        {
            FileOpenPicker openPicker = new FileOpenPicker();
            openPicker.PickMultipleFilesAndContinue();
        }
    
        /// <summary> 
        /// Handle the returned files from file picker 
        /// This method is triggered by ContinuationManager based on ActivationKind 
        /// </summary> 
        /// <param name="args">File open picker continuation activation argment. It cantains the list of files user selected with file open picker </param> 
        public void ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs args)
        {
            ...
        } 
    }
      
  • Para:
    public sealed partial class MainPage : Page
    {
        ...
    
        private void CameraAppBarButton_Click(object sender, RoutedEventArgs e)
        {
            FileOpenPicker openPicker = new FileOpenPicker(); 
            StorageFile file = await openPicker.PickSingleFileAsync(); 
    
            ...
        }
    }
      

Botão de câmera do Windows Phone

Temos no Windows Phone o botão físico de câmera. Mas quando migramos para Universal Windows, o Desktop e outras famílias de dispositivos não tem o mesmo botão. Portanto, temos que verificar a existência do botão e tratá-lo de outra forma. Agora devemos verificar se a API está disponível.

  • De:
    private void SetHandlers() 
    {
    #if WINDOWS_PHONE_APP
        Windows.Phone.UI.Input.HardwareButtons.CameraPressed += this.HardwareButtons_CameraPressed;
    #endif // WINDOWS_PHONE_APP
    }
    
    #if WINDOWS_PHONE_APP
    void HardwareButtons_CameraPressed(object sender, Windows.Phone.UI.Input.CameraEventArgs e)
    {
        // handle the event.
    }
    #endif // WINDOWS_PHONE_APP
    
  • Para:
    private void SetHandlers() 
    {
        // note, cache the value instead of querying it more than once.
        bool isHardwareButtonsAPIPresent = Windows.Foundation.Metadata.ApiInformation.IsTypePresent
            ("Windows.Phone.UI.Input.HardwareButtons");
    
        if (isHardwareButtonsAPIPresent)
        {
            Windows.Phone.UI.Input.HardwareButtons.CameraPressed +=
                this.HardwareButtons_CameraPressed;
        }
    }
    
    private void HardwareButtons_CameraPressed(object sender, Windows.Phone.UI.Input.CameraEventArgs e)
    {
        // handle the event.
    }
      

Ao término dessas etapas, você deverá ser capaz de executar o projeto tanto no Windows 10 desktop como Windows 10 mobile:

Projeto HubApp migrado para Windows 10
Projeto HubApp migrado para Windows 10

Note que devemos ainda rever o layout para que funcione bem tanto no Windows 10 desktop como no windows 10 mobile.

Layout e Interface

Praticamente todos os recursos visuais usados no Windows 8.1 ou Windows Phone 8.1 estão também disponíveis no Windows 10 e provavelmente poucas alterações terão que ser feitas para você conseguir visualizar seu projeto na nova versão. Contudo, ajustes são necessários para adequar a interface.

Nesse link temos detalhes das mudanças feitas no XAML. Mas iremos citar aqui alguns deles.

Por exemplo, temos recursos de sistema, além de temas, específicos no Windows 8.1 e Windows Phone 8.1 que agora não existem mais no Windows 10. Para esses casos você deve procurar um estilo correspondente ou até criar seu próprio estilo compatível com o valor desejado. Exemplo de estilo não existente no Windows 10: TextStyleMediumFontSize

Outra mudança é na forma como os pixels são tratados. Agora no Windows 10 temos os chamados Effective Pixels (epx). Portanto, uma imagem com tamanho 50X50 tem Effective Pixels diferentes para Mobile, Desktop, Xbox e Surface Hub.

Temos, portanto, telas com largura de 320 epx até 1024 epx. E pode ser necessário criar imagens que são melhor visualizadas para os diferentes tamanhos de tela usando um fator de escala. Sugere-se imagens com escala 100%, 200% e 400% que já são suficientes. Mas o Windows 10 suporta as seguintes escalas: 100%, 125%, 150%, 200%, 250%, 300%, e 400%.

Nota: A escala 240%, que era usada no Windows 8.1 e Windows Phone 8.1, não é mais válida. Você deve substituir para uma das escalas citadas acima.

Deste modo, a imagem será exibida de forma adequada nos diferentes tamanhos de tela.

Outro aspecto importante é quanto as telas adaptáveis. Podemos usar os Adaptive Triggers para alterar a disposição de elementos de acordo com o tamanho da tela. Lembrando que o tamanho da tela é em Effective Pixels. Nesse caso no XAML teremos algo tipo:

    <Grid Name="LayoutRoot">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="VisualStateGroup">
                <VisualState x:Name="VisualState000min">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="UserName.(Grid.Column)" Value="0"/>
                        <Setter Target="UserName.(Grid.ColumnSpan)" Value="2"/>
                        <Setter Target="Surname.(Grid.Row)" Value="1"/>
                        <Setter Target="Surname.(Grid.Column)" Value="0"/>
                        <Setter Target="UserName.(Grid.ColumnSpan)" Value="2"/>
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualState500min">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="501"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="UserName.(Grid.Column)" Value="0"/>
                        <Setter Target="Surname.(Grid.Row)" Value="0"/>
                        <Setter Target="Surname.(Grid.Column)" Value="1"/>
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualState800min">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="801"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!-- Specific Setters for 801 epx displays -->
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

Notas:

  • Podemos alterar inclusive a coluna ou a linha da Grid onde está localizado um elemento.
  • Normalmente é suficiente definir triggers para tamanhos 0, 501 e 801 epx. Mas se necessário, é só definir novos valores para diferentes triggers.

Data Context e Data Binding

O Windows 10 trouxe como novidade o Compiled Binding – o {x:bind} – que proporciona uma melhora significativa no desempenho do seu aplicativo. Mas para tanto, ele faz o Binding diretamente com o Code Behind, tendo definido o tipo da classe do ViewModel bem definido.

Portanto, para usar o Compiled Binding no XAML, devemos fazer o binding para propriedades do Code Behind. Para não termos que mudar a forma como programamos no Windows 8.1, podemos fazer uma pequena alteração no nosso Code Behind:

    public MainPage()
    {
        this.InitializeComponent();
    
        this.DataContextChanged += (s, e) =>
        {
            ViewModel = DataContext as ViewModels.MainPageViewModel;
        };
    }

E depois no código basta especificarmos no XAML:

    <TextBlock Text="{x:Bind ViewModel.Nome}"/>
    <Button Content="Botão" Click="{x:Bind ViewModel.ButtonClicked}"/>                        

Notas:

  • O Compiled Binding tem como modo padrão o “OneTime”, em que a informação será obtida do ViewModel para o View somente uma vez. Mas podemos alterar este modo caso necessário.
  • Com Compiled Binding, podemos fazer binding de eventos, como o Click. No ViewModel definimos então o método com ou sem parâmetros.

Exemplos Práticos

A Microsoft disponibiliza estudos de caso (Bookstore1, Bookstore2 e QuizGame) que demonstram a atualização de aplicativos Windows 8.1 e Windows Phone 8.1 para Windows 10. Lá você poderá obter várias dicas de atualização que podem ser úteis antes de iniciar a atualização do seu aplicativo.

Considerações Finais

É difícil prever a dificuldade na atualização do seu aplicativo para Windows 10. Mas o certo é que muito do que temos já funcionará na nova versão. Portanto, podemos ser positivamente surpreendidos.

Basta estarmos atentos a essas novas mudanças e ajustarmos gradualmente o código para alcançar todos os usuários do Windows 10.

Teve problemas ou alguma dúvida, entre em contato conosco! E continue nos acompanhando no talkitbr.

Deixe um comentário