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:
- Baixar o ZIP do repositório GIT que contém os scripts para atualização.
- Descompactar o arquivo e abrir o prompt de comando (Cmd).
- 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:
- Fechar o projeto. Para tanto, selecione o projeto e, no menu “Project”, e então a opção “Unload Project”.
- Editar o arquivo .csproj através de editor de arquivos.
- Para o primeiro Property Group, caso o conteúdo do elemento
seja AnyCPU, altere para x86.
- Remova depois quaisquer elementos
PropertyGroup
em que o conteúdo do elementoseja AnyCPU.
- Clicar no projeto e, através do menu Project, selecione a opção “Reload Project”.
- 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:
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.