×
Namespaces

Variants
Actions

Novidades no Windows Phone 8

From Nokia Developer Wiki
Jump to: navigation, search
Featured Article
05 Nov
2012
See Also
WP Metro Icon WP8.png
WP Metro Icon Baby.png
SignpostIcon Code 52.png
Article Metadata

Exemplo de código
Compatibilidade
Plataforma(s): Windows Phone 8 and later
Windows Phone 8

Artigo
Tradução:
Por Vitor Pombeiro
Última alteração feita por hamishwillee em 04 Jul 2013

Introdução

O lançamento do Windows Phone 8 é um marco significativo na evolução do sistema operativo da Microsoft para telemóveis. Para os utilizadores esta nova versão significa uma expansão no acesso ao hardware, suporte para jogos de alta qualidade, e uma melhor integração com o Sistema Operativo - tudo isto pode ser visto nos telemóveis Nokia Lumia 920 e Nokia Lumia 820.

Para os programadores não são menos significativas, trazendo muitas APIs novas. Estas APIs simplificam o desenvolvimento de funcionalidades que já deve ter adicionado às suas apps para Windows Phone, mas também oferece novas. Este artigo irá introduzir as APIs mais significativas e alterações.

Durante o artigo iremos utilizar uma aplicação para mostrar o código, de uma forma pratica, que irá necessitar de escrever para poder usufruir das novas características. Para crir a sua versão desta aplicação irá necessitar do Windows Phone 8 SDK, que pode ser descarregado aqui http://go.microsoft.com/fwlink/?LinkID=261873. Pode obter a versão completa da aplicação aqui : Media:WhatsNewInWP8 FullExample.zip.

Contents


Nativo: DirectX, C++, e gráficos Direct3D

Foi introduzido no Windows Phone 8 uma parte do DirectX para permitir nativamente jogos de alta qualidade e performance. Foi introduzido também C++, principalmente para ser utilizado nesse jogos de alta qualidade.

No Visual Studio 2012 pode criar uma aplicação Direct3D para o Windows Phone (“Windows Phone Direct3D Application”) que utiliza estas novas características.

Nova aplicação Direct3D para Windows Phone

Execute o novo projeto criado por defeito e irá visualizar um cubo 3D a girar.

Cubo 3D a girar com Direct3D

Direct3D não é mágico. É muito fácil ir ao ficheiro CubeRenderer.cpp, procurar o local onde os 8 pontos são utilizados para definir o cubo

VertexPositionColor cubeVertices[] =
{
{XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 0.0f)},
{XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f)},
{XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3(0.0f, 1.0f, 0.0f)},
{XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3(0.0f, 1.0f, 1.0f)},
{XMFLOAT3( 0.5f, -0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f)},
{XMFLOAT3( 0.5f, -0.5f, 0.5f), XMFLOAT3(1.0f, 0.0f, 1.0f)},
{XMFLOAT3( 0.5f, 0.5f, -0.5f), XMFLOAT3(1.0f, 1.0f, 0.0f)},
{XMFLOAT3( 0.5f, 0.5f, 0.5f), XMFLOAT3(1.0f, 1.0f, 1.0f)},
};


É também muito simples reduzir para uma definição com 4 pontos e obter um tetraedro, como se segue:

VertexPositionColor cubeVertices[] =
{
{XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 0.0f)},
{XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f)},
{XMFLOAT3(-0.5f, 0.5f, -0.5f), XMFLOAT3(0.0f, 1.0f, 0.0f)},
{XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT3(0.0f, 1.0f, 1.0f)},
//{XMFLOAT3( 0.5f, -0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f)},
//{XMFLOAT3( 0.5f, -0.5f, 0.5f), XMFLOAT3(1.0f, 0.0f, 1.0f)},
//{XMFLOAT3( 0.5f, 0.5f, -0.5f), XMFLOAT3(1.0f, 1.0f, 0.0f)},
//{XMFLOAT3( 0.5f, 0.5f, 0.5f), XMFLOAT3(1.0f, 1.0f, 1.0f)},
};


Agora, executando a app, mostra um tetraedro plano a rodar em 3D.

Tetraedro 3D a rodar com Direct3D

Existem algumas ressalvas importantes a ter em conta com este novo conjunto de características.

  1. Linguagens de programação: C++ foi disponibilizado para ser utilizado juntamente com DirectX. C++ com XAML ou C# com DirectX não são suportados de uma forma direta. Apesar de ser possível a interoperabilidade entre as duas plataformas.
  2. Nível de características do Direct3D: O nível de características suportadas pelo Direct3D é Level9_3 para todos os dispositivos Windows Phone 8. Onde apps da Windows 8 Store podem suportar níveis de características adicionais. Isso limita o que se pode fazer com o Direct3D. Mas também permite criar modelos de programação mais simples para o DirectX para todos os dispositivos Windows Phone 8 e potencialmente a reutilização do código do jogo entre o Windows Phone 8 e o Windows 8. Aqui está uma lista parcial das características de nível 9_3 suportadas pelo Direct3D 11.1:
    Características de nível 9 3 suportadas pelo Direct3D
  3. Suporte para DirectX: Direct3D é só uma tecnologia numa grande família de tecnologias conhecidas por “DirectX”. Nem todas essas tecnologias são suportadas pelo Windows Phone 8 e mesmo as que são suportadas são parcialmente em relação à API do Windows 8. Pode ver a lista completa dessas tecnologias aqui.
    Tópico WP8 suporta? Descrição
    Direct2D
    Não
    Direct2D é acelerado por hardware, modo imediato, API de gráficos 2-D que providenciam renderização de alta performance e qualidade em geometria 2-D, bitmaps, e texto.
    Direct3D Sim Direct3D permite-lhe criar gráficos 3-D para jogos e aplicações cientificas.
    DirectWrite
    Não
    DirectWrite suporta a renderização de texto de alta qualidade, fonts independentes da resolução, e suporte total para texto Unicode e layouts.
    DirectXMath Sim DirectXMath providencia uma ótima interface transportável para operações aritméticas e de álgebra linear em vetores de virgula flutuante e de precisão simples (2D, 3D, e 4D) ou matrizes (3×3 e 4×4).
    XAudio2 Sim Providencia os fundamentos para processamento de sinal e mistura para jogos. XAudio2 substitui DirectSound.
    XInput
    Não
    Descreve como utilizar a API XInput para interagir com o controlador da Xbox 360 quando ligado a um computador com o Windows. XInput substitui DirectInput.
    Windows Imaging Component (WIC)
    Não
    A Windows Imaging Component (WIC) é uma plataforma extensível que providencia uma API de baixo nível para imagens digitais. WIC suporta os formatos de imagem standard utilizados na web, imagens de alta resolução, e dados raw da camera.
  4. C++ não tem acesso às managed APIs do Mango: Código C++ só pode utilizar a nova API do WP8 a partir do WinPRT. Como resultado o código C++ não tem acesso às APIs existentes do Mango. O que significa que as apps em C++ estão limitadas aos seguintes novos namespaces do windows phone:
Windows.ApplicationModel
Windows.ApplicationModel.Activation
Windows.ApplicationModel.Core
Windows.ApplicationModel.DataTransfer
Windows.ApplicationModel.Store
Windows.Devices.Geolocation
Windows.Devices.Input
Windows.Devices.Sensors
Windows.Foundation
Windows.Foundation.Collections
Windows.Foundation.Diagnostics
Windows.Foundation.Metadata
Windows.Graphics.Display
Windows.Management.Deployment
Windows.Networking
Windows.Networking.Connectivity
Windows.Networking.Proximity
Windows.Networking.Sockets
Windows.Phone.ApplicationModel
Windows.Phone.Devices.Notification
Windows.Phone.Devices.Power
Windows.Phone.Graphics.Interop
Windows.Phone.Input.Interop
Windows.Phone.Management.Deployment
Windows.Phone.Media.Capture
Windows.Phone.Media.Devices
Windows.Phone.Networking.NetworkOperators
Windows.Phone.Networking.Voip
Windows.Phone.PersonalInformation
Windows.Phone.PersonalInformation.Provisioning
Windows.Phone.Speech.Recognition
Windows.Phone.Speech.Synthesis
Windows.Phone.Speech.VoiceCommands
Windows.Phone.Storage.SharedAccess
Windows.Phone.System
Windows.Phone.System.Analytics
Windows.Phone.System.Memory
Windows.Phone.System.Power
Windows.Phone.System.Profile
Windows.Phone.System.UserProfile
Windows.Phone.System.UserProfile.GameServices.Core
Windows.Phone.UI.Core
Windows.Phone.UI.Input
Windows.Security.Authentication.OnlineId
Windows.Storage
Windows.Storage.FileProperties
Windows.Storage.Pickers
Windows.Storage.Search
Windows.Storage.Streams
Windows.System
Windows.System.Display
Windows.System.Threading
Windows.System.Threading.Core
Windows.UI
Windows.UI.Core
Windows.UI.Input
Windows.UI.Popups
Windows.UI.ViewManagement

Nativo: Interoperabilidade entre DirectX e C++, e XAML e C#

Uma das caracteristicas mais interessantes do novo suporte ao DirectX e C++ é a sua fácil integração com XAML e C#.

Podemos intrelaçar XAML e DirectX utilizando os novos controlos <DrawingSurface /> e <DrawingSurfaceBackgroundGrid />.

<DrawingSurface x:Name="DrawingSurface" />

Porque a superfície de desenho do DirectX pode ser um outro element XAML de uma página, podemos facilmente integrar com outro elemento XAML. Este exemplo trivial ilustra como colocar um botão em cima de uma DrawingSurface DirectX.

<Grid x:Name="LayoutRoot" Background="Transparent">
<DrawingSurface x:Name="DrawingSurface" />
<Button Margin="20" Content="Indica ao DirectX que este botão foi pressionado"
Height="70" VerticalAlignment="Top"/>
</Grid>

O resultado, com o botão sobreposto na DrawingSurface DirectX é mostrado em baixo. Até utiliza a cor de fundo.

Botão XAML sobreposto no DirectX

É até possivel para um assembly em C++ expor endpoints dedicados ao código C# para comunicação e vice versa.

Por exemplo, na app de exemplo pode expor o seguinte cabeçalho do método no ficheiro header Direct3DInterop.h:

public:
Direct3DInterop();
Windows::Phone::Graphics::Interop::IDrawingSurfaceContentProvider^ CreateContentProvider();
 
// Event Handlers
void MyButtonWasClicked();

Depois implementar o método no ficheiro Direct3DInterop.cpp para que mostre algo na consola de debug.

void Direct3DInterop::MyButtonWasClicked()
{
OutputDebugString(L"Button was clicked!");
}

E do código C# pode agora invocar este novo método.

private Direct3DInterop m_d3dInterop = new Direct3DInterop();
private void Button_Click_1(object sender, RoutedEventArgs e)
{
m_d3dInterop.MyButtonWasClicked();
}

Testando a app mostra o resultado na consola de debug. Mas primeiro precisa de depurar o código base nativo da app. Para o fazer tem de abrir as propriedades do projeto e alterar para o depurador nativo.

Changing application debugging to Native Only

Agora execute a app e carregue no botão, você irá ver a seguinte mensagem na janela de depuração:

Janela de depuração mostrando que ao pressionar o botão executa código C++

Isto diz-lhe que ao pressionar um botão XAML faz com que C# execute com sucesso via interop código base C++ (essa execução pode afetar código DirectX). A interoperabilidade entre o DirectX e C++ e XAML e C# é bastante poderosa.

Voz: Text-to-Speech

Text-to-speech (TTS) está agora incluído no Windows Phone 8.

TTS foi possível em versões anteriores do Windows Phone utilizando um serviço online do Bing. Se já experimentou trabalhar com este serviço já deve ter passado por várias dificuldades: Bing Translator não foi realmente criado para text-to-speech com qualidade — por isso a voz tinha uma aparência mecanizada — era necessário muito trabalho para configurar e requeria uma ligação de rede. Todos esses problemas estão resolvido com o WP8: TTS tem um som natural, suporta várias línguas, é adicionado com apenas 2 linhas de código, e trabalha offline sem uma ligação de dados.

O exemplo de código "Olá mundo" para TTS é bastante simples:

private async void TTS_HelloWorld(object sender, RoutedEventArgs e)
{
var text2speech = new SpeechSynthesizer();
await text2speech.SpeakTextAsync("OMG! Hello world!");
}

Quando executa a app irá ouvir este som: <mp3player>File:WhatsNewWP8 TTS HelloWorld.mp3</mp3player>

Algo bastante interessante sobre o TTS no WP8 é a possibilidade de alterar a voz utilizada para ler o texto. E tem várias línguas e vozes que pode utilizar. As vozes diferem uma da outra baseando no sexo e na cultura para a qual está otimizada. Este exemplo utiliza todas as vozes instaladas com uma única amostra

private async void TTS_AllVoices(object sender, RoutedEventArgs e)
{
foreach (var voice in InstalledVoices.All)
{
Debug.WriteLine(voice.DisplayName + ", " +
voice.Language + ", " +
voice.Gender + ", " +
voice.Description);
using (var text2speech = new SpeechSynthesizer())
{
text2speech.SetVoice(voice);
await text2speech.SpeakTextAsync("Hello world! I'm " + voice.DisplayName + ".");
}
}
}

Quando executado, você irá ouvir este som: <mp3player>File:WhatsNewWP8 TTS AllVoices.mp3</mp3player>

Irá também ver o seguinte na janela do depurador:

Microsoft Zira Mobile, en-US, Female, Microsoft Zira Mobile - English (United States)
Microsoft Stefan Mobile, de-DE, Male, Microsoft Stefan Mobile - German (Germany)
Microsoft George Mobile, en-GB, Male, Microsoft George Mobile - English (United Kingdom)
Microsoft Susan Mobile, en-GB, Female, Microsoft Susan Mobile - English (United Kingdom)
Microsoft Heera Mobile, en-IN, Female, Microsoft Heera Mobile - English (India)
Microsoft Ravi Mobile, en-IN, Male, Microsoft Ravi Mobile - English (India)
Microsoft Mark Mobile, en-US, Male, Microsoft Mark Mobile - English (United States)
Microsoft Katja Mobile, de-DE, Female, Microsoft Katja Mobile - German (Germany)
Microsoft Laura Mobile, es-ES, Female, Microsoft Laura Mobile - Spanish (Spain)
Microsoft Pablo Mobile, es-ES, Male, Microsoft Pablo Mobile - Spanish (Spain)
Microsoft Raul Mobile, es-MX, Male, Microsoft Raul Mobile - Spanish (Mexico)
Microsoft Sabina Mobile, es-MX, Female, Microsoft Sabina Mobile - Spanish (Mexico)
Microsoft Julie Mobile, fr-FR, Female, Microsoft Julie Mobile - French (France)
Microsoft Paul Mobile, fr-FR, Male, Microsoft Paul Mobile - French (France)
Microsoft Cosimo Mobile, it-IT, Male, Microsoft Cosimo Mobile - Italian (Italy)
Microsoft Elsa Mobile, it-IT, Female, Microsoft Elsa Mobile - Italian (Italy)
Microsoft Ayumi Mobile, ja-JP, Female, Microsoft Ayumi Mobile - Japanese (Japan)
Microsoft Ichiro Mobile, ja-JP, Male, Microsoft Ichiro Mobile - Japanese (Japan)
Microsoft Adam Mobile, pl-PL, Male, Microsoft Adam Mobile - Polish (Poland)
Microsoft Paulina Mobile, pl-PL, Female, Microsoft Paulina Mobile - Polish (Poland)
Microsoft Daniel Mobile, pt-BR, Male, Microsoft Daniel Mobile - Portuguese (Brazil)
Microsoft Maria Mobile, pt-BR, Female, Microsoft Maria Mobile - Portuguese (Brazil)
Microsoft Irina Mobile, ru-RU, Female, Microsoft Irina Mobile - Russian (Russia)
Microsoft Pavel Mobile, ru-RU, Male, Microsoft Pavel Mobile - Russian (Russia)
Microsoft Kangkang Mobile, zh-CN, Male, Microsoft Kangkang Mobile - Chinese (Simplified, PRC)
Microsoft Yaoyao Mobile, zh-CN, Female, Microsoft Yaoyao Mobile - Chinese (Simplified, PRC)
Microsoft Danny Mobile, zh-HK, Male, Microsoft Danny Mobile - Chinese (Traditional, Hong Kong S.A.R.)
Microsoft Tracy Mobile, zh-HK, Female, Microsoft Tracy Mobile - Chinese (Traditional, Hong Kong S.A.R.)
Microsoft Yating Mobile, zh-TW, Female, Microsoft Yating Mobile - Chinese (Traditional, Taiwan)
Microsoft Zhiwei Mobile, zh-TW, Male, Microsoft Zhiwei Mobile - Chinese (Traditional, Taiwan)

Para um controlo mais afinado do text-to-speech pode utilizar o formato SSML para otimizar a voz e tornando-a mais humana. Utilize a amostra SSML do website W3C:

private async void TTS_SSML(object sender, RoutedEventArgs e)
{
var text2speech = new SpeechSynthesizer();
await text2speech.SpeakSsmlAsync(@"<speak version=""1.0""
xmlns="
"http://www.w3.org/2001/10/synthesis"" xml:lang=""en-US"">
<voice gender="
"female"">
Hi, this is Justin's computer...
</voice>
<voice age="
"6"">
Hello <prosody contour="
"(0%,+20Hz)(10%,+30%)(40%,+10Hz)"">world</prosody>
</voice>
</speak>"
);
}

Execute esta amostra e irá ouvir este som: <mp3player>File:WhatsNewWP8 TTS SSML.mp3</mp3player>


Voz: Speech to text

Voz para texto era muito difícil de se alcançar com sucesso. Mesmo nas soluções mais artilhadas nas apps WP7 era esquisito e a tecnologia nunca foi bem adotada pelos utilizadores. No Windows Phone 8, a Microsoft tomou a liderança e criou uma API speech to text de topo. A maioria do processamento é efetuado por um servidor remoto, por isso irá necessitar de uma ligação de dados para que funcione.

O seguinte código é uma simples sessão de voz para texto executada diretamente da interface do utilizador (IU).

private async void STT_Freeform(object sender, RoutedEventArgs e)
{
SpeechRecognizerUI speechRecognizer = new SpeechRecognizerUI();
speechRecognizer.Settings.ExampleText = "Fine thanks";
speechRecognizer.Settings.ListenText = "How's it goin', eh?";
speechRecognizer.Settings.ReadoutEnabled = true;
speechRecognizer.Settings.ShowConfirmation = true;
SpeechRecognitionUIResult result = await speechRecognizer.RecognizeWithUIAsync();
if (result.ResultStatus == SpeechRecognitionUIStatus.Succeeded)
{
MessageBox.Show(result.RecognitionResult.Text);
}
}


O fragmento de código comporta-se como pode ver no seguinte video: <mediaplayer width='360' height='600'>File:WhatsNewWP8 STT Freeform.mp4</mediaplayer>

Uma característica útil, que ajuda a melhorar mais o reconhecimento é que você pode limitar as respostas suportadas efetuando o carregamento da gramática de uma lista ou de um ficheiro, como exemplificado em baixo:

private async void STT_FromList(object sender, RoutedEventArgs e)
{
SpeechRecognizerUI speechRecognizer = new SpeechRecognizerUI();
speechRecognizer.Settings.ExampleText = "Me, You, Everyone";
speechRecognizer.Settings.ListenText = "Who's awesome?";
speechRecognizer.Settings.ReadoutEnabled = true;
speechRecognizer.Settings.ShowConfirmation = true;
 
speechRecognizer.Recognizer.Grammars.AddGrammarFromList("answer",
new string[] { "You", "Me", "Everyone" });
 
SpeechRecognitionUIResult result = await speechRecognizer.RecognizeWithUIAsync();
if (result.ResultStatus == SpeechRecognitionUIStatus.Succeeded)
{
MessageBox.Show(result.RecognitionResult.Text);
}
}

Agora quando executa o código o sistema só reconhece as palavras da lista gramatical.

É também possível executar uma conversão speech-to-text sem mostrar na IU. Por exemplo, este exemplo de código irá analisar qualquer ficheiro de audio:

private async void STT_NonVisual(object sender, RoutedEventArgs e)
{
SpeechRecognizer speechRecognizer = new SpeechRecognizer();
SpeechRecognitionResult result = await speechRecognizer.RecognizeAsync();
MessageBox.Show("Heard \"" + result.Text + "\" with a confidence of " + result.TextConfidence);
}

Como pode ver o motor consegue lidar com o meu sotaque Canadiano pitoresco (video neste video).

WP8 Voice Recognition confirming spoken text dictation
<mediaplayer width='360' height='600'>File:WhatsNewWP8 STT NonVisual.mp4</mediaplayer>


Voz: Comandos por voz

Apps podem agora registar comandos por voz os quais podem "acordar" a app ou serem utilizados pela própria app. Cada comando de voz pode ser dividido em três partes: o nome da app, o comando e a lista de frases.

Dividir a estrutura do comando de voz: nome da app, commando e frases

Quando constrói comandos de voz sera necessário começar por criar um ficheiro VCD (Voice Command Definition - Definição de comando de voz). Para um comando de voz simples, crie este simples VCD que irá demonstrar a utilização dos três elementos do comando.

<?xml version="1.0" encoding="utf-8"?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.0">
<CommandSet xml:lang="en-us">
 
<Example>Compute Pii to a billion places</Example>
 
<Command Name="ComputePii">
<Example>Compute Pii to a billion places</Example>
<ListenFor>Compute Pii to a {number} places</ListenFor>
<ListenFor>Compute Pii NOW</ListenFor>
<Feedback>Computing Pii...</Feedback>
<Navigate Target="MainPage.xaml"/>
</Command>
 
<PhraseList Label="number">
<Item>one</Item>
<Item>two</Item>
<Item>billion</Item>
</PhraseList>
 
</CommandSet>
</VoiceCommands>

Como pode ver, o propósito deste ficheiro é definir um comando o qual está ligado a um link URI interno e listar as frases que o comando pode utilizar.

Note.pngNote: Deve se estar a questionar porque é que o VCD não inclui “My App”. É derivado do facto do nome da app ser obtido da informação providenciada pelo ficheiro WmAppManfiest. Depois da app ser submetida à WP8 store, Dev Center sobrepõe essa informação com o nome da app que é apresentado na Windows Phone 8 store.

Em seguida, irá ter de registar este ficheiro no SO para que consiga reconhecer o(s) comando(s) de voz. Preferencialmente é efetuado no arranque da app e uma só vez, mas neste exemplo pode simplesmente registar o ficheiro XML num event handler.

private async void VoiceCommands(object sender, RoutedEventArgs e)
{
await VoiceCommandService.InstallCommandSetsFromFileAsync(
new Uri("file://" +
Windows.ApplicationModel.Package.Current.InstalledLocation.Path +
@"/ComputePiiVCD.xml",
UriKind.RelativeOrAbsolute));
MessageBox.Show(@"Hit the home key to go to the start screen.
Then press and hold the Windows button and say:
My App, Compute Pii to a Billion Places"
,
"Voice Commands registered.",
MessageBoxButton.OK);
}

Finalmente, irá ter de lidar com o evento quando um comando de voz invocar a app. É efetuado substituindo o handler OnNavigatedTo e mostrando uma MessageBox com todos os parâmetros das querystrings URI. Nota, uma implementação no mundo real iria criar um UriMapper próprio e utilizar para redirecionar o utilizador para a página certa.

protected async override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigationContext.QueryString.Any())
{
StringBuilder sb = new StringBuilder();
foreach (var key in NavigationContext.QueryString.Keys)
{
sb.AppendLine(key + ": " + NavigationContext.QueryString[key]);
}
MessageBox.Show(sb.ToString(), "Page Querystring", MessageBoxButton.OK);
}
}

Finalmente execute a app, registe os comandos, vá ao ecrã principal, e diga "My App, Compute Pi to a Billion places".

Pode fazer o download do vídeo mostrando os comandos de voz em ação ou veja o vídeo em baixo: <mediaplayer width='360' height='600'>File:WhatsNewWP8 SpeechCommands.mp4</mediaplayer>


Ecrã bloqueado: Imagem de fundo, contador, e texto

Como parte do Windows Phone 8 os utilizadores podem dar controle às apps sobre a aparência do ecrã bloqueado. Especificamente, apps podem alterar a imagem de fundo do ecrã bloqueado, os cinco contadores no fundo e o texto mostrado no ecrã principal. No Windows Phone 7 a imagem tinha de ser especificada pelo utilizador, o texto tem de ser o próximo compromisso no outlook e só apps de sistema poderiam ter contadores. Portanto isto é um grande passo em frente na personalização do SO.

Exemplo de um ecrã de bloqueio com imagem de fundo (background image), texto (text) e contadores (counters)

Para começar tem de declarar uma extensão para o ecrã bloqueado para que a sua app nos ecrãs de configuração. Adicione o seguinte código ao ficheiro WmAppManfiest.xml imediatamente depois do elemento <Tokens />:

<Extensions>
<Extension ExtensionName="LockScreen_Notification_IconCount"
ConsumerID="{111DFF24-AA15-4A96-8006-2BFF8122084F}" TaskID="_default" />
<Extension ExtensionName="LockScreen_Notification_TextField"
ConsumerID="{111DFF24-AA15-4A96-8006-2BFF8122084F}" TaskID="_default" />
<Extension ExtensionName="LockScreen_Wallpaper"
ConsumerID="{111DFF24-AA15-4A96-8006-2BFF8122084F}" TaskID="_default" />
</Extensions>

Especifique um ficheiro PNG transparente com 24x24 pixeis com alguns pixeis em branco, para os icons dos contadores da app.

<DeviceLockImageURI IsRelative="true" IsResource="false">24x24.png</DeviceLockImageURI>

Agora os utilizadores podem ver a app no ecrã de configurações do ecrã bloqueado e escolher que a app pode alterar o ecrã bloqueado.

Em vez de usar o ecrã de configuração, o utilizador pode optar por imagens para o ecrã bloqueado fornecidas pela app. Adicione esta capacidade com o seguinte código:

private async void LockScreen_ChangeImage(object sender, RoutedEventArgs e)
{
if (!LockScreenManager.IsProvidedByCurrentApplication)
{
await LockScreenManager.RequestAccessAsync();
}
}

Executando o código irá ver a app a solicitar permissão ao utilizador para providenciar imagens ao ecrã bloqueado.

Solicitar permissão ao utilizador para que a app possa alterar o ecrã bloqueado.

Agora a app pode alterar a imagem do ecrã bloqueado, como demonstrado em baixo:

private async void LockScreen_ChangeImage(object sender, RoutedEventArgs e)
{
if (!LockScreenManager.IsProvidedByCurrentApplication)
{
await LockScreenManager.RequestAccessAsync();
}
 
if (LockScreenManager.IsProvidedByCurrentApplication)
{
LockScreen.SetImageUri(
new Uri("ms-appx:///CustomizedPersonalWalleper.jpg", UriKind.RelativeOrAbsolute));
}
MessageBox.Show("Lock screen changed. Click F12 or go to lock screen.");
}

Executando a app irá ver a nova imagens no ecrã bloqueado. A imagem pode ser o que pretender e pode ser gerada dinamicamente.

Ecrã bloqueado mais interessante com conteúdo real (imagem cedida pela Windows Phone 7 app Live Tile News).

Pode adicionar texto e um contador ao ecrã bloqueado, atribuindo valores ao azulejo da app.

private void LockScreen_ChangeCounterAndText(object sender, RoutedEventArgs e)
{
ShellTile.ActiveTiles.First().Update(
new FlipTileData()
{
Count = 99,
WideBackContent = "Lock screen text",
SmallBackgroundImage = new Uri(@"Assets\Tiles\FlipCycleTileSmall.png", UriKind.Relative),
BackgroundImage = new Uri(@"Assets\Tiles\FlipCycleTileMedium.png", UriKind.Relative),
BackBackgroundImage = new Uri(@"Assets\Tiles\FlipCycleTileMedium.png", UriKind.Relative)
});
}

O resultado é o seguinte ecrã bloqueado:

Ecrã bloqueado com um contador personalizado, icon e texto


Live Tiles: Novos tamanhos e novos modelos

O ecrã principal do Windows Phone 8 foi redesenhado para que as apps tenham azuleijos normais, grandes, e pequenos. pode atribuir estes modelos à sua app como parte do PrimaryToken do ficheiro WmAppManfiest, atualizando através de código o TileData, e utilizando modelos XML de push notifications. Neste artigo vamos explorer a sintaxe do ficheiro WmAppManifest.


Modelo Flip

No Mango foi introduzida a classe StandardTileData, FlipTileData é a classe equivalente para o WP8 mas com novas propriedades. TemplateFlip tem conteúdo em ambos os lados para os dois tamanhos e suporta todos os novos tamanhos de azuleijo.

Resumo das propriedades do modelo Flip

Pode especificar um azuleijo flip especificando o PrimaryToken como “FlipTemplate” ou utilizando a classe FlipDat no código. Exemplo da utilização do XML TemplateFlip.

<PrimaryToken TokenID="WP8_Beta_22Token" TaskName="_default">
<TemplateFlip>
<SmallImageURI IsRelative="true" IsResource="false">159x159.png</SmallImageURI>
<Count>5</Count>
<BackgroundImageURI IsRelative="true" IsResource="false">336x336.png</BackgroundImageURI>
<Title>Title</Title>
<BackContent>Back Content</BackContent>
<BackBackgroundImageURI IsRelative="true" IsResource="false">336x336.png</BackBackgroundImageURI>
<BackTitle>Back Title</BackTitle>
<LargeBackgroundImageURI IsRelative="true" IsResource="false">691x336.png</LargeBackgroundImageURI>
<LargeBackContent>Hello World</LargeBackContent>
<LargeBackBackgroundImageURI IsRelative="true" IsResource="false">691x336.png</LargeBackBackgroundImageURI>
<DeviceLockImageURI IsRelative="true" IsResource="false">24x24.png</DeviceLockImageURI>
<HasLarge>True</HasLarge>
</TemplateFlip>
</PrimaryToken>

O exemplo em baixo mostra o mesmo azulejo, cinco vezes para mostrar todos os tamanhos e o conteúdo frontal e traseiro.

Azulejo pequeno, normal e largo no modelo Flip


Modelo Iconic

Uma das utilizações mais comuns dos azulejos no WP7 era a recriação da aparência de azulejos de apps internas. Era uma utilização t~eo comum que projetos de terceiros, como o MSP Toolkit, surgiram para preencher o vazio. As boas noticias é que o Windows Phone 8 providencia suporte interno para modelos de azulejos com aparência similar a azulejos nativos. As várias propriedades que podem ser utilizadas neste novo tipo de modelo são mostradas em baixo:

Resumo das propriedades do modelo Iconic

Utilize a tag XML TemplateIconic no ficheiro WmAppManifest ou a classe IconicTileData para especificar o conteúdo. Este exemplo utiliza a tag XML TemplateIconic:

<PrimaryToken TokenID="WP8_Beta_22Token" TaskName="_default">
<TemplateIconic>
<SmallImageURI IsRelative="true" IsResource="false">110x110.png</SmallImageURI>
<Count>5</Count>
<IconImageURI IsRelative="true" IsResource="false">202x202.png</IconImageURI>
<Title>Title</Title>
<Message>Message</Message>
<BackgroundColor>#123456</BackgroundColor>
<HasLarge>True</HasLarge>
<LargeContent1>LargeContent1</LargeContent1>
<LargeContent2>LargeContent2</LargeContent2>
<LargeContent3>LargeContent3</LargeContent3>
<DeviceLockImageURI IsRelative="true" IsResource="false">24x24.png</DeviceLockImageURI>
</TemplateIconic>
</PrimaryToken>

Os três azulejos que este código produz são mostrados em baixo:

Azulejo pequeno, normal e largo no modelo Iconic


Azulejo Cyclic

Um dos recursos mais interessantes nos azulejos do WP7 é o azulejo largo com fotografia, que mostra as suas fotografias ciclicamente. Esse modelo de azulejo está agora disponível para o poder utilizar. Especifique simplesmente as nove imagens locais que deseja que o azulejo mostre ciclicamente como pode ver em baixo:

<PrimaryToken TokenID="WP8_Beta_22Token" TaskName="_default">
<TemplateCycle>
<SmallImageURI IsRelative="true" IsResource="false">159x159.png</SmallImageURI>
<Title>Title</Title>
<Photo01ImageURI IsRelative="true" IsResource="false">691x336_N1.png</Photo01ImageURI>
<Photo02ImageURI IsRelative="true" IsResource="false">691x336_N2.png</Photo02ImageURI>
<Photo03ImageURI IsRelative="true" IsResource="false">691x336_N3.png</Photo03ImageURI>
<Photo04ImageURI IsRelative="true" IsResource="false">691x336_N4.png</Photo04ImageURI>
<Photo05ImageURI IsRelative="true" IsResource="false">691x336_N5.png</Photo05ImageURI>
<Photo06ImageURI IsRelative="true" IsResource="false">691x336_N6.png</Photo06ImageURI>
<Photo07ImageURI IsRelative="true" IsResource="false">691x336_N7.png</Photo07ImageURI>
<Photo08ImageURI IsRelative="true" IsResource="false">691x336_N8.png</Photo08ImageURI>
<Photo09ImageURI IsRelative="true" IsResource="false">691x336_N9.png</Photo09ImageURI>
<Count>0</Count>
<HasLarge>True</HasLarge>
<DeviceLockImageURI IsRelative="true" IsResource="false">24x24.png</DeviceLockImageURI>
</TemplateCycle>
</PrimaryToken>

E o azulejo é exibido como exemplificado em baixo (note que só são utilizadas duas imagens):

Azulejo mostra duas imagens locais ciclicamente utilizando o modelo Cycle


Mapas

Nokia em parceria com a Microsoft resultou num novo controlo de mapas integrado no Windows Phone 8. No WP7 o control Bing Maps estava disponível através de um SDK de terceiros. Contudo o controlo Bing maps tinha informação limitada de cartografia, era lento, e faltavam alguns recursos. O novo controlo Maps é um controlo de mapas com todos os recursos, alimentado pela informação da Nokia Maps e incrivelmente rápido pois faz parte do SO. A diferença mais impressionante entre o controlo WP7 Bing Maps e o controlo WP8 Nokia Maps é que o controlo do WP8 utiliza conteúdo vetorial e não imagens bitmap. Aumentar ou reduzir o zoom é agora suave e não tem aquela aparência estranha, pixelizada e de ter de esperar que as imagens sejam exibidas.

Aqui está um exemplo de um controlo Map básico e as permissões necessárias para o ativar:

    <Capability Name="ID_CAP_MAP" />
<maps:Map />
Controlo Map básico


Os muitos, muitos recursos dos mapas…

O controlo Nokia Maps trás muitos novos recursos, só alguns são abordados aqui. Utilizando a propriedade Center especifica o GeoLocation do controlo do mapa. Utilizando o recurso Zoom é possível aumentar e diminuir o zoom do mapa. A propriedade Heading roda o mapa em torno do seu centro. A propriedade pitch altera a elevação do mapa em relação ao horizonte. É possível alterar o tipo de mapa que se mostra alterando o CartographicMode para Road (estrada), Terrain (terreno), Aerial (aéreo), ou Hybrid (híbrido). A propriedade ColorMode pode ter os valores Light (conduzir na luz do dia) ou Dark (para conduzir à noite). É possível adicionar pontos de referencia em 3D utilizando LandmarksEnabled. É também possível adicionar caraterísticas no terreno úteis para pedestres (como escadas) utilizando o PedestrianFeaturesEnabled. Este código utiliza todas estas caraterísticas num controlo de mapas:

<maps:Map
x:Name="myMap"
Center="37.792878,-122.39641"
ZoomLevel="17"
Heading="45"
Pitch="25"
CartographicMode="Road"
ColorMode="Dark"
PedestrianFeaturesEnabled="True"
LandmarksEnabled="True"
/>

Resultando é o mapa exibido em baixo:

Controlo do Map com as caraterísticas ativadas


Camadas do mapa

É possível acrescentar camadas ao seu mapa facilmente utilizando um elemento XAML com GeoCoordinates e o controlo do mapa irá colocar esses elementos nas localizações apropriadas. Com este código pode adicionar um ponto vermelho em frente ao edifício de embarque dos ferrys de San-Francisco:

myMap.Layers.Add(new MapLayer()
{
new MapOverlay()
{
GeoCoordinate = new GeoCoordinate(37.795032,-122.394927),
Content = new Ellipse
{
Fill = new SolidColorBrush(Colors.Red),
Width = 40,
Height = 40
}
}
});

O ponto é mostrado independentemente da rotação ou centro do mapa.


Rotas

Outro grande recurso do mapa é a habilidade para adicionar rotas ao controlo do mapa. Por exemplo, pode adicionar uma rota entre o edifício dos ferrys de San-Francisco até à Transamerica Pyramid. Isto é feito definindo uma RouteQuery que corre no servidor e quando está pronta adicione ao controlo do mapa. Algo a ter em atenção é que o TravelMode suporta a criação de rotas para veículos e pedestres.

public Maps_Routing()
{
InitializeComponent();
RouteQuery query = new RouteQuery()
{
TravelMode = TravelMode.Driving,
Waypoints = new List<GeoCoordinate>()
{
new GeoCoordinate(37.79547,-122.393129), // ferry building
new GeoCoordinate(37.794911,-122.402871) // Transamerica Pyramid
}
};
query.QueryCompleted += query_QueryCompleted;
query.QueryAsync();
}
void query_QueryCompleted(object sender, QueryCompletedEventArgs<Route> e)
{
myMap.AddRoute(new MapRoute(e.Result));
}

Este código resulta na seguinte rota entre o edifício dos ferrys e a Transamerica Pyramid:

Rota entre o edifício dos ferrys e a Transamerica Pyramid

Pode até ter as instruções base para a rota sem ter de mostrar ao utilizador independentemente do controlo do mapa.

void query_QueryCompleted(object sender, QueryCompletedEventArgs<Route> e)
{
myMap.AddRoute(new MapRoute(e.Result));
StringBuilder sb = new StringBuilder();
sb.AppendLine("Distance to destination: " + e.Result.LengthInMeters);
sb.AppendLine("Time to destination: " + e.Result.EstimatedDuration);
foreach (var maneuver in e.Result.Legs.SelectMany(l => l.Maneuvers))
{
sb.AppendLine("At " + maneuver.StartGeoCoordinate + " " +
maneuver.InstructionKind + ": " +
maneuver.InstructionText + " for " +
maneuver.LengthInMeters + " meters");
}
MessageBox.Show(sb.ToString());
}

Pode ver, em baixo, que tem toda a informação necessária para construir a sua app de navegação.

Toda a informação necessária para construir uma app de navegação.


Geocoding

Como o exemplo das rotas mostra, é possível utilizar os novos serviços dos mapas sem ser necessário utilizar o controlo dos mapas. Outro desses serviços é o serviço de GeoCoding. GeoCoding é a habilidade de transformar um texto em coordenadas geográficas potenciais e outra informação geolocalizada. Este código demonstra que utilizando a pesquisa por "Ferry Building, San-Francisco":

private void Maps_GeoCoding(object sender, RoutedEventArgs e)
{
GeocodeQuery query = new GeocodeQuery()
{
GeoCoordinate = new GeoCoordinate(0, 0),
SearchTerm = "Ferry Building, San-Francisco"
};
query.QueryCompleted += query_QueryCompleted;
query.QueryAsync();
}
 
void query_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Ferry Building Geocoding results...");
foreach (var item in e.Result)
{
sb.AppendLine(item.GeoCoordinate.ToString());
sb.AppendLine(item.Information.Name);
sb.AppendLine(item.Information.Description);
sb.AppendLine(item.Information.Address.BuildingFloor);
sb.AppendLine(item.Information.Address.BuildingName);
sb.AppendLine(item.Information.Address.BuildingRoom);
sb.AppendLine(item.Information.Address.BuildingZone);
sb.AppendLine(item.Information.Address.City);
sb.AppendLine(item.Information.Address.Continent);
sb.AppendLine(item.Information.Address.Country);
sb.AppendLine(item.Information.Address.CountryCode);
sb.AppendLine(item.Information.Address.County);
sb.AppendLine(item.Information.Address.District);
sb.AppendLine(item.Information.Address.HouseNumber);
sb.AppendLine(item.Information.Address.Neighborhood);
sb.AppendLine(item.Information.Address.PostalCode);
sb.AppendLine(item.Information.Address.Province);
sb.AppendLine(item.Information.Address.State);
sb.AppendLine(item.Information.Address.StateCode);
sb.AppendLine(item.Information.Address.Street);
sb.AppendLine(item.Information.Address.Township);
}
MessageBox.Show(sb.ToString());
}

Este código retorna muita informação, que pode ser exibida ao utilizador para verificar o resultado, mas mais importante a sua app irá ter as coordenadas geográficas para o local pesquisado.

Informação geográfica


Câmara: apps Lenses

Um dos mais excitantes pontos de integração do Windows Phone 8 é a habilidade de criar apps para a app da câmara. Os utilizadores podem executar as apps “Lenses” diretamente da app da câmara. Apps “Lenses” são assim chamadas porque elas "devem" mostrar uma imagem da câmara numa moldura e atuar como se de uma "lente" se trata-se. Contudo, apps de Lenses não são filtros, são apps de pleno direito.

Fluxo da utilização de uma app de lente

Para ver como isto fnciona, crie uma app “Lenses” trivial, faça-a aparecer na Lens Picker, e depois grave uma imagem sem aplicar um filtro.

Começa por declarar a app como uma app “Lenses” e certificar-se que tem permissões de acesso à câmara e gravar as imagens na biblioteca de media.

<Capabilities>
<Capability Name="ID_CAP_ISV_CAMERA" />
Capability Name="ID_CAP_MEDIALIB_PHOTO" />
</Capabilities>
 
<Extensions>
<Extension ExtensionName="Camera_Capture_App" ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5631}" TaskID="_default" />
</Extensions>

Em seguida, na pasta "Images", tem de adicionar icons para as três resoluções de ecrãs suportadas. Aqui está a utilizada pela app Lens.Screen-WVGA.png.

Paint.net com o icon Lens

Quando a app iniciar irá obter um link interno para /MainPage.xaml?Action=ViewfinderLaunch. O seu código pode agora intercetar esse link utilizando um UriMapper personalizado e enviar para /Lens.xaml.

RootFrame.UriMapper = new MyAppUriMapper();
class MyAppUriMapper : UriMapperBase
{
public override Uri MapUri(Uri uri)
{
string tempUri = uri.ToString();
if (tempUri.Contains("ViewfinderLaunch"))
{
return new Uri("/Lens.xaml", UriKind.Relative);
}
else
{
return uri;
}
}
}

Agora pode adicionar algum código ao Lens.xaml para mostrar a câmara traseira e depois gravar a imagem, não modificada, na biblioteca de fotos.

<Button Content="Snap Picture" Click="SaveImage" />
<Grid x:Name="ContentPanel" Grid.Row="1" >
<Grid.Background>
<VideoBrush x:Name="viewfinderBrush" />
</Grid.Background>
</Grid>
public partial class Lense : PhoneApplicationPage
{
public Lense()
{
InitializeComponent();
this.Loaded += Lense_Loaded;
}
 
PhotoCamera cam;
MediaLibrary library = new MediaLibrary();
 
void Lense_Loaded(object sender, RoutedEventArgs e)
{
if (PhotoCamera.IsCameraTypeSupported(CameraType.Primary))
{
cam = new Microsoft.Devices.PhotoCamera(CameraType.Primary);
 
cam.CaptureImageAvailable +=cam_CaptureImageAvailable;
viewfinderBrush.SetSource(cam);
}
}
 
void cam_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
{
library.SavePictureToCameraRoll(new Random().Next(1, 1000) + ".jpg", e.ImageStream);
}
 
private void SaveImage(object sender, RoutedEventArgs e)
{
cam.CaptureImage();
}
}

Executando a app irá vê-la na lista de apps para a app da câmara e pode seguir o fluxo de uma app "Lenses" e geral.

GIF animado mostrando o fluxo da utilização de uma app Lens personalizada


Câmara: gravação de vídeo

Com o WP7 era possível para as apps gravarem vídeos mas era uma tarefa muito laboriosa. com a nova API winPRT do WP8 para captura de vídeo é agora bastante simples capturar vídeo e gravar em qualquer lugar. Um pequeno exemplo de captura de 2 segundos de vídeo, gravar para o IsoStore, e depois reproduzir o mesmo.

private async void RecordAndPlayVideo(object sender, RoutedEventArgs e)
{
StorageFolder isoStore = await ApplicationData.Current.LocalFolder.GetFolderAsync("IsolatedStore");
var file = await isoStore.CreateFileAsync("foo.wmv", CreationCollisionOption.ReplaceExisting);
using (var s = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var avDevice = await AudioVideoCaptureDevice.OpenAsync(CameraType.RearFacing,
AudioVideoCaptureDevice.GetAvailableCaptureResolutions(CameraType.RearFacing).First());
await avDevice.StartRecordingToStreamAsync(s);
Thread.Sleep(2000);
await avDevice.StopRecordingAsync();
}
 
new MediaPlayerLauncher()
{
Media = new Uri(file.Path, UriKind.Relative),
}.Show();
}

Visto que o exemplo do código pode parecer bastante complexo é na realidade bastante simples. Primeiro, abre o ficheiro Foo.wmv no IsoStore para gravar o vídeo.

private async void RecordAndPlayVideo(object sender, RoutedEventArgs e)
{
StorageFolder isoStore = await ApplicationData.Current.LocalFolder.GetFolderAsync("IsolatedStore");
var file = await isoStore.CreateFileAsync("foo.wmv", CreationCollisionOption.ReplaceExisting);
using (var s = await file.OpenAsync(FileAccessMode.ReadWrite))
{
}
}

Depois, obtém a câmara frontal.

private async void RecordAndPlayVideo(object sender, RoutedEventArgs e)
{
StorageFolder isoStore = await ApplicationData.Current.LocalFolder.GetFolderAsync("IsolatedStore");
var file = await isoStore.CreateFileAsync("foo.wmv", CreationCollisionOption.ReplaceExisting);
using (var s = await file.OpenAsync(FileAccessMode.ReadWrite))
{
 
var avDevice = await AudioVideoCaptureDevice.OpenAsync(CameraType.RearFacing,
AudioVideoCaptureDevice.GetAvailableCaptureResolutions(CameraType.RearFacing).First());
 
}
}

Em seguida o código para simular alguém a carregar no botão que diz “start capture” espera 2 segundos antes de carregar no botão que diz “stop capture”. Obviamente que num cenário real iria ter um botão “start recording” e um outro “stop recording”, mas este código irá simular essas funcionalidades para exemplificar.

private async void RecordAndPlayVideo(object sender, RoutedEventArgs e)
{
StorageFolder isoStore = await ApplicationData.Current.LocalFolder.GetFolderAsync("IsolatedStore");
var file = await isoStore.CreateFileAsync("foo.wmv", CreationCollisionOption.ReplaceExisting);
using (var s = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var avDevice = await AudioVideoCaptureDevice.OpenAsync(CameraType.RearFacing,
AudioVideoCaptureDevice.GetAvailableCaptureResolutions(CameraType.RearFacing).First());
 
await avDevice.StartRecordingToStreamAsync(s);
Thread.Sleep(2000);
await avDevice.StopRecordingAsync();
 
}
}

E finalmente a app pode reproduzir o ficheiro foo.wmv que se encontra no IsoStore.

private async void RecordAndPlayVideo(object sender, RoutedEventArgs e)
{
StorageFolder isoStore = await ApplicationData.Current.LocalFolder.GetFolderAsync("Shared");
var file = await isoStore.CreateFileAsync("foo.wmv", CreationCollisionOption.ReplaceExisting);
using (var s = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var avDevice = await AudioVideoCaptureDevice.OpenAsync(CameraSensorLocation.Back,
AudioVideoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back).First());
await avDevice.StartRecordingToStreamAsync(s);
Thread.Sleep(2000);
await avDevice.StopRecordingAsync();
}
 
new MediaPlayerLauncher()
{
Media = new Uri(file.Path, UriKind.Relative),
}.Show();
}

Ao executar a app irá capturar 2 segundos de vídeo e reproduzir-lo:

GIF animado mostrando a captura de 2 segundos de vídeo


Câmara: informações do dispositivo

Utilizando a nova API para o dispositivo da câmara do Windows Phone 8 é possível obter informações adicionais dos parâmetros da câmara em modo fotografia ou vídeo. Informação como resoluções suportadas, área de foco, balanço de brancos, ISO, som do obturador, e ainda mais estão disponíveis através da nova API.

Existe muita informação e características disponíveis através da API, mas para ser breve só vamos cobrir algumas aqui.

private async void PhotoCameraProperties(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("*** PhotoCaptureDevice statics ***");
sb.AppendLine("PhotoCaptureDevice.AvailableSensorLocations: "
+ PhotoCaptureDevice.AvailableSensorLocations.First()
+ " " + PhotoCaptureDevice.AvailableSensorLocations.Skip(1).First());
sb.AppendLine("PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back): "
+ PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back).First());
sb.AppendLine("PhotoCaptureDevice.GetAvailablePreviewResolutions(CameraSensorLocation.Back): "
+ PhotoCaptureDevice.GetAvailablePreviewResolutions(CameraSensorLocation.Back).First());
sb.AppendLine("PhotoCaptureDevice.IsFocusRegionSupported(CameraSensorLocation.Back): " +
PhotoCaptureDevice.IsFocusRegionSupported(CameraSensorLocation.Back));
sb.AppendLine("PhotoCaptureDevice.IsFocusSupported(CameraSensorLocation.Back): " +
PhotoCaptureDevice.IsFocusSupported(CameraSensorLocation.Back));
 
sb.AppendLine("*** PhotoCaptureDevice ***");
var photoDevice = await PhotoCaptureDevice.OpenAsync(CameraSensorLocation.Back,
PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back).First());
await photoDevice.ResetFocusAsync();
photoDevice.FocusRegion = new Windows.Foundation.Rect(200, 200, 200, 200);
await photoDevice.FocusAsync();
 
sb.AppendLine("*** PhotoCaptureDevice Properties ***");
sb.AppendLine("FocusRegion: " + photoDevice.FocusRegion);
sb.AppendLine("CaptureResolution: " + photoDevice.CaptureResolution);
sb.AppendLine("PreviewResolution: " + photoDevice.PreviewResolution);
sb.AppendLine("SensorLocation: " + photoDevice.SensorLocation);
sb.AppendLine("SensorRotationInDegrees: " + photoDevice.SensorRotationInDegrees);
 
sb.AppendLine("*** KnownCameraPhotoProperties Properties ***");
sb.AppendLine("ExposureCompensation: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.ExposureCompensation));
sb.AppendLine("ExposureTime: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.ExposureTime));
sb.AppendLine("FlashMode: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.FlashMode));
sb.AppendLine("FlashPower: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.FlashPower));
sb.AppendLine("FocusIlluminationMode: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.FocusIlluminationMode));
sb.AppendLine("Iso: " +
 
photoDevice.GetProperty(KnownCameraPhotoProperties.Iso));
sb.AppendLine("LockedAutoFocusParameters: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.LockedAutoFocusParameters));
sb.AppendLine("ManualWhiteBalance: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.ManualWhiteBalance));
sb.AppendLine("SceneMode: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.SceneMode));
sb.AppendLine("WhiteBalancePreset: " +
photoDevice.GetProperty(KnownCameraPhotoProperties.WhiteBalancePreset));
 
sb.AppendLine("*** KnownCameraGeneralProperties Properties ***");
sb.AppendLine("AutoFocusRange: " +
photoDevice.GetProperty(KnownCameraGeneralProperties.AutoFocusRange));
sb.AppendLine("IsShutterSoundEnabledByUser : " +
 
photoDevice.GetProperty(KnownCameraGeneralProperties.IsShutterSoundEnabledByUser ));
sb.AppendLine("IsShutterSoundRequiredForRegion: " +
photoDevice.GetProperty(KnownCameraGeneralProperties.IsShutterSoundRequiredForRegion));
sb.AppendLine("ManualFocusPosition: " +
photoDevice.GetProperty(KnownCameraGeneralProperties.ManualFocusPosition));
photoDevice.Dispose();
MessageBox.Show(sb.ToString());
}

Executando este código exibe o seguinte resultado no emulador do WP8:

Também pode obter informação similar da câmara de vídeo:

private async void AudioVideoCameraProperties(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("*** AudioVideoCaptureDevice statics ***");
sb.AppendLine("AudioVideoCaptureDevice.AvailableSensorLocations: "
+ AudioVideoCaptureDevice.AvailableSensorLocations.First()
+ " " + AudioVideoCaptureDevice.AvailableSensorLocations.Skip(1).First());
sb.AppendLine("AudioVideoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back): "
+ AudioVideoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back).First());
sb.AppendLine("AudioVideoCaptureDevice.GetAvailablePreviewResolutions(CameraSensorLocation.Back): "
+ AudioVideoCaptureDevice.GetAvailablePreviewResolutions(CameraSensorLocation.Back).First());
sb.AppendLine("AudioVideoCaptureDevice.SupportedAudioEncodingFormats: " +
AudioVideoCaptureDevice.SupportedAudioEncodingFormats.First());
sb.AppendLine("AudioVideoCaptureDevice.SupportedVideoEncodingFormats: " +
AudioVideoCaptureDevice.SupportedVideoEncodingFormats.First());
 
sb.AppendLine("*** AudioVideoCaptureDevice ***");
var avDevice = await AudioVideoCaptureDevice.OpenAsync(CameraSensorLocation.Back,
AudioVideoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back).First());
 
sb.AppendLine("*** AudioVideoCaptureDevice properties ***");
sb.AppendLine("AudioEncodingFormat: " + avDevice.AudioEncodingFormat);
sb.AppendLine("CaptureResolution: " + avDevice.CaptureResolution);
sb.AppendLine("FocusRegion: " + avDevice.FocusRegion);
sb.AppendLine("PreviewResolution: " + avDevice.PreviewResolution);
sb.AppendLine("SensorLocation: " + avDevice.SensorLocation);
sb.AppendLine("SensorRotationInDegrees: " + avDevice.SensorRotationInDegrees);
sb.AppendLine("VideoEncodingFormat: " + avDevice.VideoEncodingFormat);
 
sb.AppendLine("*** KnownCameraPhotoProperties properties ***");
sb.AppendLine("UnmuteAudioWhileRecording: " +
avDevice.GetProperty(KnownCameraAudioVideoProperties.UnmuteAudioWhileRecording));
sb.AppendLine("VideoFrameRate: " +
avDevice.GetProperty(KnownCameraAudioVideoProperties.VideoFrameRate));
sb.AppendLine("VideoTorchMode: " +
avDevice.GetProperty(KnownCameraAudioVideoProperties.VideoTorchMode));
sb.AppendLine("VideoTorchPower: " +
avDevice.GetProperty(KnownCameraAudioVideoProperties.VideoTorchPower));
 
sb.AppendLine("*** KnownCameraGeneralProperties properties ***");
sb.AppendLine("AutoFocusRange: " +
avDevice.GetProperty(KnownCameraGeneralProperties.AutoFocusRange));
sb.AppendLine("IsShutterSoundEnabledByUser: " +
avDevice.GetProperty(KnownCameraGeneralProperties.IsShutterSoundEnabledByUser));
sb.AppendLine("PlayShutterSoundOnCapture: " +
avDevice.GetProperty(KnownCameraGeneralProperties.PlayShutterSoundOnCapture));
sb.AppendLine("IsShutterSoundRequiredForRegion: " +
avDevice.GetProperty(KnownCameraGeneralProperties.IsShutterSoundRequiredForRegion));
sb.AppendLine("ManualFocusPosition: " +
avDevice.GetProperty(KnownCameraGeneralProperties.ManualFocusPosition));
 
avDevice.Dispose();
 
MessageBox.Show(sb.ToString());
}

Executando este código exibe a seguinte informação no emulador do WP8:


Wallet: Ofertas

A nova app Wallet do Windows Phone 8 é uma grande oportunidade para ser reconhecido. A app Wallet pode ser vista como um hub (ponto central) para apps oferecerem produtos e serviços locais: Pense num hub como algo construido para que os utilizadores possam fazer “qualquer coisa em qualquer lado”. O wallet é isso. Como parte da API do wallet programadores podem adicionar ofertas únicas, instrumentos de pagamento multi-usos, ou só artigos genéricos na wallet.

Para ilustrar, comece por adicionar uma nova oferta no dispositivo do wallet. No mundo real isto seria uma oferta do estilo Groupon, onde pode adicionar ofertas locais ao Wallet para serem utilizadas mais tarde. Neste exemplo trivial irá adicionar “um artigo grátis” ao wallet. Este exemplo utiliza todas as propriedades possíveis para demonstrar a versatilidade do Wallet, mas só algumas são obrigatórias.

private async void Wallet_CreateNewDeal(object sender, RoutedEventArgs e)
{
var item = new Deal()
{
MerchantName = "JustinAngel.net",
DisplayName = "Free Blog article!",
Description = "Justin will give you a free blog post.",
CustomerName = "You",
ExpirationDate = DateTime.Now.AddDays(14),
IssuerName = "JustinAngel.net Inc.",
IssuerWebsite = new Uri("http://JustinAngel.net"),
 
NavigationUri = new Uri("/mainpage.xaml?wallet=Deal", UriKind.Relative),
 
Notes = "I like turtles.",
OfferWebsite = new Uri("http://JustinAngel.net/wp7"),
TermsAndConditions = "May only be used once. OK, twice.",
Logo99x99 = GetBitmapSource("99x99.png"),
Logo159x159 = GetBitmapSource("159x159.png"),
Logo336x336 = GetBitmapSource("336x336.png"),
BarcodeImage = GetBitmapSource("Barcode.png")
};
 
await item.SaveAsync();
 
Launcher.LaunchUriAsync(new Uri("wallet://", UriKind.RelativeOrAbsolute));
}
 
private BitmapSource GetBitmapSource(string url)
{
var bmp = new BitmapImage();
bmp.SetSource(Application.GetResourceStream(new Uri(url, UriKind.Relative)).Stream);
return bmp;
}

Existem algumas coisas interessantes neste código. Primeiro cria um novo artigo “Deal” no wallet. Depois preenche algumas propriedades como um endereço de um site, um ID, algumas propriedades para visualização, e um código de barras. O artigo é então gravado para a wallet e a app da wallet é executada.

Também necessita de adicionar permissões que permitam a app adicionar artigos na Wallet.

<Capability Name="ID_CAP_WALLET" />

E aqui pode ver o resultado final da aparência final da wallet:

Os utilizadores podem carregar no botão "open app" e irá abrir a app associada ao artigo da Wallet: O código especificou um link interno (deeplink) para “/mainpage.xaml?wallet=Deal” pertencendo ao artigo da wallet. Quando o utilizador carrega nesse botão pode ver que esse é o link utilizado.

Janela de messagem mostrando o parâmetro wallet=Deal

Existem muitas formas do utilizador lucrar com oportunidades únicas. Pode ser feito no site Offer localizado num web server, como pertencente a uma app, lendo o código de barras de uma oferta (Deal), por ai fora. Tudo depende na especificidade do negocio para a utilização da Wallet.


Wallet: Agentes em pano de fundo (background)

Como parte da funcionalidade da Wallet foi introduzido no WP8 um novo agente em pano de fundo. Este agente ajuda a atualizar os artigos da wallet, recebe ativações/desativações de elementos seguros do NFC associado ao artigo da wallet, e efetua alterações no pagamento feitas ao artigo da wallet.

Aqui está um exemplo de um simples agente de pano de fundo que responde a pedidos de “atualização” a artigos da wallet. Isto é feito para que a app tenha a última informação do artigo da wallet obtida da cloud. Este simples agente de pano de fundo vai sempre falhar na obtenção da informação (porque não existe informação na cloud) e marca todos os artigos como “requer atenção do utilizador”.

<Tasks>
<DefaultTask Name="_default" NavigationPage="MainPage.xaml" />
<ExtendedTask Name="BackgroundTask">
<BackgroundServiceAgent Specifier="WalletAgent"
Name="myWalletAgent"
Source="WP8_Beta_22"
Type="WP8_Beta_22.myWalletAgent" />
</ExtendedTask>
</Tasks>
public class myWalletAgent : WalletAgent
{
protected override void OnRefreshData(RefreshDataEventArgs args)
{
foreach (WalletItem item in args.Items)
{
item.SetUserAttentionRequiredNotification(true);
}
 
base.OnRefreshData(args);
NotifyComplete();
}
}

Após "atualizar" os detalhes do artigo, quer manualmente quer automaticamente, irá ver que o artigo da wallet requer atenção:

Em vez de marcar o artigo como “requer atenção” pode facilmente atualizar as propriedades da informação obtida da cloud.


Wallet: Instrumento de Pagamento

Pessoalmente, sempre quis ter a minha própria moeda e chamar-lhe “Justin Smiley Dollars”. Bem, existem muitas apps que incluem moeda virtual e faz sentido que a mesma pertença à wallet do utilizador. Instrumento de pagamentos são diferentes de “ofertas (deals)”, pois mantêm o balanço de uma conta mantida pelo seu serviço.

Quando adiciona PaymentInstruments à Wallet o utilizador tem de aprovar a adição de artigos à wallet e a sua app necessita de capacidades adicionais.

<Capability Name="ID_CAP_WALLET_PAYMENTINSTRUMENTS" />

Agora adicione 50$? (Justin Smiley dollars) à wallet do utilizador. (O símbolo oficial do Justin Dollars é “$?”)

private void Wallet_CreateNewPaymentInstrument(object sender, RoutedEventArgs e)
{
var item = new PaymentInstrument()
{
IssuerName = "JustinAngel.net",
CustomerName = "You",
ExpirationDate = DateTime.Now.AddDays(1),
PaymentInstrumentKinds = PaymentInstrumentKinds.Debit,
DisplayName = "Justin Smiley Dollars $?",
Logo99x99 = GetBitmapSource("99x99.png"),
Logo336x336 = GetBitmapSource("336x336.png"),
Logo159x159 = GetBitmapSource("159x159.png"),
DisplayAvailableBalance = "50$?",
DisplayBalance = "100$?",
DisplayCreditLimit = "200$?",
DisplayAvailableCredit = "200$?",
BackgroundColor = Color.FromArgb(255, 70, 150, 250),
Nickname = "Justin Dollars $?",
Message = "I like turtles.",
MemberSince = DateTime.Now.AddYears(-1),
AccountNumber = "12345679",
NavigationUri = new Uri("/mainpage.xaml?wallet=PaymentInstrument", UriKind.Relative)
};
 
AddWalletItemTask task = new AddWalletItemTask()
{
Item = item
};
task.Completed += (s, args) => MessageBox.Show(args.TaskResult.ToString());
task.Show();
 
Launcher.LaunchUriAsync(new Uri("wallet://", UriKind.RelativeOrAbsolute));
}

Executando este código pode ver que a wallet pede permissão para gravar o artigo. Carregar “save” irá gravar o artigo na Wallet. Carregar em “review” irá ser levado para um ecrã de analise onde pode gravar, editar e cancelar.

Pode agora abrir a wallet e ver como é o instrumento de pagamento.

Como pode ver ao carregar no botão “Open app” abre a app utilizando uma querystring especifica relacionada com esta app. Na vida real o artigo da Wallet pode ser qualquer coisa como um “Starbucks card” que pode ser digitalizado quando quer uma chvena de café, moeda virtual para jogos, ou créditos para um site de partilha de ficheiros.


Wallet: Artigos genéricos na wallet

O terceiro tipo de artigo que pode adicionar à wallet é o WalletTransactionItem é realmente utilizado para apanhar tudo o que não seja "deal" e instrumento de pagamento. Este exemplo mostra como efetuar compras através da app para adicionar um bilhete de cinema à wallet.

private void Wallet_CreateNewWalletTransactionItem(object sender, RoutedEventArgs e)
{
var item = new WalletTransactionItem()
{
DisplayName = "Dark Knight Rises",
IssuerName = "Justin's Movie Theater",
CustomerName = "You",
Notes = "Admits one person to see Batman Dark Knight Rises.",
ExpirationDate = DateTime.Now.AddDays(14),
Logo99x99 = GetBitmapSource("99x99Batman.png"),
Logo336x336 = GetBitmapSource("336x336Batman.png"),
Logo159x159 = GetBitmapSource("159x159Batman.png"),
BarcodeImage = GetBitmapSource("Barcode.png"),
NavigationUri = new Uri("/mainpage.xaml?wallet=WalletTransactionItem", UriKind.Relative)
};
 
AddWalletItemTask task = new AddWalletItemTask()
{
Item = item
};
task.Completed += (s, args) => MessageBox.Show(args.TaskResult.ToString());
 
task.Show();
 
Launcher.LaunchUriAsync(new Uri("wallet://", UriKind.RelativeOrAbsolute));
}

Executando o código pode ver que a wallet mostra a mesma mensagem para gravar/rever como acontece com um instrumento de pagamento:

Quando abre a Wallet pode ver a aparência de um artigo genérico na wallet.

E se tocar no botão “open app” pode ver que utiliza o link interno especificado para este artigo.

Mensagem a mostrar o parâmetro wallet=WalletTransactinItem


Apps monitorizar geolocalização em plano de fundo

Com o Windows Phone 8 um novo modelo de processamento em plano de fundo é suportado para apps que utilizam a geoloalização. Apps que utilizam geolocalização podem solicitar para que toda a app fique viva em memória e receba notificações de geolocalização. Um cenário é que o utilizador ao iniciar uma ação de geolocalização na app, não pare mesmo que a app deixe de estar em primeiro plano. Navegação curva-a-curva é um grande exemplo de algo que o utilizador não espera perder até que chegue ao destino. Existem algumas limitações que deve ter em atenção. Só pode existir uma app a monitorizar a localização em pano de fundo, pode ser terminada a qualquer momento, executar a app em pano de fundo consome bateria, e está limitado a um pequeno conjunto de APIs que trabalham bem em pano de fundo.

Para que a app possa utilizar a geolocalização, tem que primeiro especificar a permissão de segurança requerida.

<Capability Name="ID_CAP_LOCATION" />

Para ter a certeza que a sua app não é desligada enquanto monitoriza a geolocalização, indique o BackgroundExecution na DefaultTask do WmAppManfiest da app.

<DefaultTask Name="_default" NavigationPage="MainPage.xaml">
<BackgroundExecution>
<ExecutionType Name="LocationTracking" />
</BackgroundExecution>
</DefaultTask>

Agora registe para uma notificação quando a app vai para o modo de execução em pano de fundo, registando no evento RunningInBackground no ficheiro App.xaml. Neste exemplo irá ser mostrado uma notificação toast ao utilizador, para o avisar que a app está a monitorizar. Uma app em situações normais iria utilizar este evento para fechar todos os recursos voláteis (câmaras, sensores, etc.).

<shell:PhoneApplicationService
Launching="Application_Launching" Closing="Application_Closing"
Activated="Application_Activated" Deactivated="Application_Deactivated"
RunningInBackground="Application_RunningInBackground"
/>
private void Application_RunningInBackground(object sender, RunningInBackgroundEventArgs e)
{
new ShellToast()
{
Title = "My App",
Content = "Is tracking you"
}.Show();
}

De forma a que a app possa executar em pano de fundo, irá ter de utilizar uma das novas classes GeoLocator para iniciar a monitorização da localização do utilizador.

private Geolocator geolocator = null;
private void Location_ContinuousLocation(object sender, RoutedEventArgs e)
{
geolocator = new Geolocator();
geolocator.DesiredAccuracy = PositionAccuracy.High;
geolocator.MovementThreshold = 1;
geolocator.PositionChanged += geolocator_PositionChanged;
 
MessageBox.Show("Hit the home button and start walking around. " +
"Watch out for ShellTile notfications and changes to the app's pinned tile.");
}

Neste exemplo irá atualizar o azulejo fixado para mostrar a notificação toast sempre que a geolocalização do utilizador mudar.

void geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
{
string geoLoc = args.Position.Coordinate.Latitude.ToString() + ", " +
args.Position.Coordinate.Longitude.ToString();
 
ShellToast t = new ShellToast()
{
Title = "Location @ ",
Content = geoLoc,
NavigationUri = new Uri("/MainPage.xaml?start=geoloc", UriKind.Relative)
};
t.Show();
 
ShellTile.ActiveTiles.First()
.Update(new IconicTileData()
{
Title = geoLoc
});
}

Executando este código inicia a monitorização da geolocalização e irá ver a notificação toast mostrando que a app está a monitorizar a sua localização.

Agora abra o painel de localização do emulador e veja o que acontece quando muda a geolocalização.

Monitorização da geolocalização alterando o azulejo e mostrando uma notificação push


SO acesso de escrita: Criar compromissos no calendário

Como parte do Windows Phone 8 pode mostrar uma mensagem ao utilizador a perguntar se pode adicionar um compromisso ao calendário. A sua app pode preencher a mensagem com detalhes sobre o compromisso. A app pode também determinar quando irá tocar o alarme e como o compromisso irá ser visualizado no calandário.

private void WriteAccess_Meeting(object sender, RoutedEventArgs e)
{
new SaveAppointmentTask()
{
Subject = "Meet with Justin!",
Details = "Spend quality time with Justin",
Location = "At Justin's Home",
AppointmentStatus = AppointmentStatus.Busy,
StartTime = DateTime.Now.AddHours(1),
EndTime = DateTime.Now.AddHours(2),
IsAllDayEvent = false,
Reminder = Reminder.FiveMinutes
}.Show();
}

Executando este código irá ver uma mensagem a solicitar confirmação para adicionar um compromisso. Também permite alterar detalhes antes do compromisso ser gravado.

Mensagem de confirmação para adicionar um compromisso ao calendário

Uma vez gravado pode abrir o calendário e ver o compromisso.


SO acesso de escrita: Contactos

No Windows Phone 8 pode adicionar contactos ao People Hub sem a autorização explicita do utilizador: Apps têm o seu próprio ContactStore onde podem adicionar contactos. Se a app for desinstalada todos os contactos associados à app serão removidos. Isso será razão suficiente para não efetuar spam no People Hub.

No seguinte código a sua app obtém o ContactStore em modo leitura/escrita, adiciona um novo contacto, e grava-o.

private async void WriteAccess_Contact(object sender, RoutedEventArgs e)
{
var store = await ContactStore.CreateOrOpenAsync(
ContactStoreSystemAccessMode.ReadWrite,
ContactStoreApplicationAccessMode.LimitedReadOnly);
 
var contact = new StoredContact(store)
{
DisplayName = "myJustin",
GivenName = "Justin",
FamilyName = "Angel",
HonorificPrefix = "Captain ",
HonorificSuffix = "III",
RemoteId = "12345"
};
 
contact.SetDisplayPictureAsync(await GetInputStreamForContent("99x99.png"));
 
await contact.SaveAsync();
 
MessageBox.Show("Open up the People hub and check it out",
"Contact saved",
MessageBoxButton.OK);
}

Executando este código pode ver um novo contacto no People Hub, o qual pode ser colocado no menu inicial do equipamento.


App2app: Protocolos personalizados

No Windows Phone 8 uma app pode expor endpoints personalizados que outras apps podem utilizar para executar essa app. Uma app pode expor esse endpoint registando-o como um “protocolo personalizado” (similar aos familiares protocolos HTTP e HTTPS). O protocolo personalizado pode ser qualquer protocolo que não esteja reservado pelo SO WP8. Outras apps podem invocar esse protocolo personalizado com uma string.

É interessante de se notar que se nenhuma app que suporte o protocolo personalizado esteja instalada a store irá abrir a listar apps que utilizam o protocolo. Se existir pelo menos uma app instalada que utilize esse protocolo então irá abrir essa app diretamente. E se duas ou mais apps estão instaladas que suportem o protocolo personalizado, o utilizador irá ver um ecrã onde pode escolher a app a abrir.

Aqui está um exemplo de como expor um endpoint de um protocolo personalizado app2app . Regista o protocolo “foo” para que qualquer invocação de um URI “foo://qualquer_coisa” irá abrir a sua app. No WmAppManifest adicione o seguinte elemento <Protocol />.

<Extensions>
<Protocol Name="foo" NavUriFragment="encodedLaunchUri=%s" TaskID="_default" />
</Extensions>

Irá ter de apanhar para a sua app qualquer navegação que comece por “/Protocol”. Para fazer isso, adicione um UriMapper personalizado à sua app para encontrar o link interno utilizado para a app e enviar esse link para MainPage.xaml.

RootFrame.UriMapper = new MyAppUriMapper();
public class MyAppUriMapper : UriMapperBase
{
public override Uri MapUri(Uri uri)
{
string tempUri = uri.ToString();
if (tempUri.StartsWith("/Protocol?encodedLaunchUri="))
{
string deeplink = tempUri.Substring(tempUri.IndexOf("foo"));
return new Uri("/MainPage.xaml?deepLink=" + deeplink, UriKind.Relative);
}
else
{
return uri;
}
}
}

Agora obtenha o link interno e mostre-o em uma MessageBox ou faça o que entender com ele.

protected async override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigationContext.QueryString.Any())
{
StringBuilder sb = new StringBuilder();
foreach (var key in NavigationContext.QueryString.Keys)
{
sb.AppendLine(key + ": " + NavigationContext.QueryString[key]);
}
MessageBox.Show(sb.ToString(), "Page Querystring", MessageBoxButton.OK);
}
}

Pode agora ativar a app utilizando este link onde entender. Por exemplo, pode ser incluído numa webpage.

<a href="foo://qualquer_coisa">Abrir protocolo personalizado foo://qualquer_coisa</a>

Quando abrir no browser uma página com este link, ao carregar no link irá abrir a sua app com os parâmetros corretos.

Outras apps podem também lançar o seu endpoint app2app, utilizando a classe Launcher.

private void App2app_CustomProtocol(object sender, RoutedEventArgs e)
{
Launcher.LaunchUriAsync(new Uri("foo://SomeText"));
}


App2app: Associação de ficheiros

Windows Phone 8 Apps podem registar handlers para extensões de ficheiros especificas. A app que expõe este endpoint app2app irá ter que especificar qual a extensão que quer tratar e qual o icon que irá mostrar no cliente de email, office hub, e browser. Associação de ficheiros só pode registar extensões que não estão a ser utilizados pelo sistema operativo e muito poucas estão reservadas.

Quando nenhuma app suporta abrir um ficheiro, a store irá abrir e permitir aos utilizadores efetuar o download de apps que suportam essa extensão de ficheiro. Se só um app está instalada que suporte essa extensão então essa app irá abrir com uma cópia do ficheiro em modo de leitura. E se mais de uma app que suporte essa extensão, o utilizador irá ver um ecrã onde poderá escolher a app a abrir.

Aqui está um exemplo de registar a extensão “foo”. Irá adicionar o seguinte registo ao WmAppManifest.

<Extensions>
<FileTypeAssociation Name="foo" TaskID="_default" NavUriFragment="fileToken=%s">
<Logos>
<Logo Size="small">33x33.png</Logo>
<Logo Size="medium">69x69.png</Logo>
<Logo Size="large">176x176.png</Logo>
</Logos>
<SupportedFileTypes>
<FileType ContentType="application/foo">.foo</FileType>
</SupportedFileTypes>
</FileTypeAssociation>
<Protocol Name="foo" NavUriFragment="encodedLaunchUri=%s" TaskID="_default" />
</Extensions>

Em seguida irá ter de gerir toda a navegação para a app que comece por “/FileTypeAssociation”: Utilize um UriMapper personalizado para direcionar um token do ficheiro para MainPage.xaml.

public class MyAppUriMapper : UriMapperBase
{
public override Uri MapUri(Uri uri)
{
string tempUri = uri.ToString();
if (tempUri.StartsWith("/FileTypeAssociation?fileToken="))
{
string token = tempUri.Substring(tempUri.IndexOf("fileToken=") + 10);
return new Uri("/MainPage.xaml?fileToken=" + token, UriKind.Relative);
}
else
{
return uri;
}
}
}

Na página em si, irá ter um link interno utilizado pelo token do ficheiro. Uma vez tendo o FileToken pode copiar o ficheiro para a IsoStore e ler o seu conteúdo.

protected async override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
 
if (NavigationContext.QueryString.ContainsKey("fileToken"))
{
string token = NavigationContext.QueryString["fileToken"];
string fileName = SharedStorageAccessManager.GetSharedFileName(token);
 
StorageFolder isoStore = ApplicationData.Current.LocalFolder;
var file = await SharedStorageAccessManager.CopySharedFileAsync(
isoStore, fileName, NameCollisionOption.ReplaceExisting, token);
using (var read = await file.OpenReadAsync())
using (var s = read.AsStreamForRead())
using (var sr = new StreamReader(s))
{
string contents = sr.ReadToEnd();
 
MessageBox.Show(
"token: " + token + Environment.NewLine +
"fileName: " + fileName + Environment.NewLine +
"contents: " + contents + Environment.NewLine);
}
}
}

Se enviar para si um ficheiro *.foo por email irá ver a app a executar como app associada e mostrar o token do ficheiro, nome do ficheiro e o seu conteúdo.

Esta associação de ficheiro app2app irá ser invocada sem que o utilizador navegue por ficheiros com o seu browser, office, ou via email. Pode até lançar a associação de ficheiro app2app de outras apps utilizando a classe Launcher.

private async void App2app_FileExtensions(object sender, RoutedEventArgs e)
{
await Launcher.LaunchFileAsync(
await Package.Current.InstalledLocation.GetFileAsync("myCustomFile.foo"));
MessageBox.Show("This will only work when executed from another app into this app.");
}


Cartão mini-SD: ler ficheiros

Dispositivos WP8 suporta armazenamento adicional utilizando cartões mini-SD. WP8 apps têm acesso de leitura aos tipos de ficheiros associados à app via app2app.

Para ter acesso aos ficheiros armazenados no cartão mini-SD tem de especificar o acesso à nova capacidade ID_CAP_REMOVEABLE_STORAGE.

<Capability Name="ID_CAP_REMOVABLE_STORAGE" />

Agora adicione um ficheiro com a extensão “.foo” ao seu cartão SD em “Justin's awesome folder”. Lembre-se, as extensões dos ficheiros têm de ser iguais às que se encontram associadas na app2app.

Cartão SD no Explorer do Windows Phone a mostrar a diretoria com o ficheiro

Quando tiver ficheiros no cartão SD a app pode iterar recursivamente e obter os ficheiros. Pode fazer isso mas primeiro obtenha todos os ExternalStorageDevices.

private async void SDCard_iterateOverStuff(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
 
foreach (ExternalStorageDevice device in await ExternalStorage.GetExternalStorageDevicesAsync())
{
sb.AppendLine(device.ExternalStorageID + ": " + device.RootFolder.Name);
 
await SD_IterateOverFolders(sb, device.RootFolder);
}
MessageBox.Show(sb.ToString(), "External Storage", MessageBoxButton.OK);
}

Agora que já tem o RootFolder para os ExternalStorageDevices a app pode iterar recursivamente sobre todas as diretorias e procurar todos os ficheiros disponíveis em cada diretoria. Quando a app ter encontrado todos os ficheiros, eles podem ser abertos e o seu conteúdo exibido.

private static async Task SD_IterateOverFolders(StringBuilder sb, ExternalStorageFolder folder)
{
sb.AppendLine(folder.Path);
 
var files = await folder.GetFilesAsync();
if (!files.Any())
{
sb.AppendLine("No files in " + folder.Path);
}
 
foreach (ExternalStorageFile file in files)
{
sb.AppendLine(file.Name);
using (var s = await file.OpenForReadAsync())
using (var sr = new StreamReader(s))
{
sb.AppendLine(sr.ReadToEnd());
}
}
 
foreach (ExternalStorageFolder nestedFolder in await folder.GetFoldersAsync())
{
SD_IterateOverFolders(sb, nestedFolder);
}
}

Execute este código num dispositivo com cartão SD, configuração correta do app2app, e para os ficheiros sugeridos irá ver uma MessageBox que mostra o cartão SD, o nome da diretoria, o nome do ficheiro, e o conteúdo do ficheiro.

MessageBox a mostrar o cartão SD, o nome da diretoria, o nome do ficheiro e o ficheiro


Bluetooth: telemóvel para telemóvel

Windows Phone 8 providencia uma nova API Bluetooth para programadores. Pode utilizar a API para enviar e receber informação entre telemóveis e dispositivos. Aqui está um simples exemplo do envio e receção de mensagens entre dois telemóveis WP8.

Comece por adicionar as permissões de segurança ao ficheiro com o manifesto da app. A capacidade ID_CAP_PROXIMITY é utilizada para ativar o NFC e o Bluetooth.

<Capability Name="ID_CAP_PROXIMITY" />

Em qualquer relação telemóvel-para-telemóvel um telemóvel será o “servidor” — à espera de pedidos e responder aos mesmos — e um outro telemóvel será o “cliente” — ligando-se ao servidor e emitindo pedidos.

Aqui está como se configura o servidor para escutar por pedidos em telemóveis com o Bluetooth ativado. Tudo o que tem de fazer é iniciar o PeerFinder para anunciar que o telemóvel está à escuta por pedidos.

private async void Bluetooth_ListenToIncomingSocket(object sender, RoutedEventArgs e)
{
PeerFinder.Start();
 
PeerFinder.ConnectionRequested += PeerFinder_OpenConnectionAndListen;
 
MessageBox.Show("Listening to Bluetooth_connections...");
}

Executando este código irá ver uma MessageBox confirmando que o servidor está à escuta por ligações Bluetooth.

MessageBox confirmando que o servidor está à escuta por ligações Bluetooth

Agora que um telemóvel está à escuta por ligações, o telemóvel cliente pode ligar-se ao telemóvel servidor.

private StreamSocket writeToSocket;
private async void Bluetooth_OpenSocket(object sender, RoutedEventArgs e)
{
PeerFinder.Start();
 
var peers = await PeerFinder.FindAllPeersAsync();
 
if (peers.Count == 0)
{
MessageBox.Show("No bluetooth peers found. Are you sure you have a listener nearby?");
}
else
{
writeToSocket = await PeerFinder.ConnectAsync(peers[0]);
 
Dispatcher.BeginInvoke(() =>
MessageBox.Show("Connected to " + peers[0].DisplayName + " bluetooth service."));
}
}

Este código é bastante simples. Inicia o seu próprio PeerFinder no cliente, encontra todos os pares, e tenta abrir um socket com o primeiro par. Executando o código irá ver uma MessageBox confirmando que a app cliente está ligada ao servidor WP8 via Bluetooth.

MessageBox confirmando que a app cliente está ligada ao servidor WP8 via Bluetooth

Nesta altura existe um servidor à escuta por pedidos e um cliente com um socket aberto com ele. O próximo passo será enviar uma mensagem através do socket aberto para o servidor que se encontra a aguardar.

private IBuffer GetBufferFromString(string str)
{
using (var dw = new DataWriter())
{
dw.UnicodeEncoding = UnicodeEncoding.Utf16LE;
dw.WriteString(str);
return dw.DetachBuffer();
}
}
 
private async void Bluetooth_SendMessage(object sender, RoutedEventArgs e)
{
if (writeToSocket == null) return;
 
await writeToSocket.OutputStream.WriteAsync(GetBufferFromString(Hello world!));
 
Dispatcher.BeginInvoke(() =>
MessageBox.Show("Sent \"Hello World\"."));
}

Envia texto como parte de um Buffer. Utilizando o DataWriter pode enviar dados de qualquer tipo, tais como string, int, ou um array de byte. Executando este código irá ver uma MessageBox confirmando que o cliente enviou o texto “Hello World”.

MessageBox confirmação que enviamos o texto “Hello World”

O último passo para criar a relação Bluetooth servidor-cliente é ler o texto recebido. Visto que sabe que a app irá receber um texto no formato UTF16 com 12 caracteres, a app deve ler 24 bytes do buffer. Com tudo, no mundo real comunicações telemóvel-para-telemóvel é recomendado que os primeiros bytes em todas as mensagens devem notificar o tamanho total da mensagem. Dessa forma o servidor sabe quantos bytes deve ler.

private StreamSocket readStreamSocket = null;
private async void PeerFinder_OpenConnectionAndListen(object sender, ConnectionRequestedEventArgs args)
{
readStreamSocket = await PeerFinder.ConnectAsync(args.PeerInformation);
Dispatcher.BeginInvoke(() => MessageBox.Show("Acting as server to " + args.PeerInformation.DisplayName + "!"));
 
while (true)
{
// length of "Hello World!" packet
var buffer = await readStreamSocket.InputStream.ReadAsync(new Buffer(24), 24, InputStreamOptions.Partial);
var text = GetStringFromBuffer(buffer).Replace("\0", "");
 
if (!string.IsNullOrEmpty(text))
Dispatcher.BeginInvoke(() => MessageBox.Show(text, "Received text", MessageBoxButton.OK));
}
}

Como irá ver o código escuta por ligações e abre um socket quando o servidor fica disponível. Depois entra num ciclo while(true) com um async wait até que uma app tenha uma mensagem totalmente formada. Então irá mostrar a mensagem no ecrã.

MessageBox a mostrar a mensagem recebida


Bluetooth: Telemóvel para dispositivo

A comunicação por Bluetooth no Windows Phone 8 pode também ser utilizada para comunicar entre Windows Phone e uma panóplia de dispositivos com Bluetooth. Neste exemplo irá utilizar a bola robótica Sphero.

Bolas robóticas com Bluetooth Sphero

Como precaução de segurança o Windows Phone 8 só pode comunicar com dispositivos Bluetooth que já tenham sido emparelhados anteriormente: Por isso certifique que o telemóvel tem o Bluetooth ativo e emparelhado com o dispositivo.

Para enviar comandos ao Sphero, irá ver na lista todos os dispositivos Bluetooth emparelhados, encontre o primeiro dispositivo Sphero e abra um socket para ele.

private StreamSocket spheroSocket;
private async void Bluetooth_ConnectToSphero(object sender, RoutedEventArgs e)
{
PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";
 
var peers = await PeerFinder.FindAllPeersAsync();
 
if (!peers.Any(p => p.DisplayName.Contains("Sphero")))
{
MessageBox.Show("Sphero not found. Is bluetooth on? Is Sphero paired?");
}
else
{
PeerInformation spheroPeer = peers.First(p => p.DisplayName.Contains("Sphero"));
 
spheroSocket = new StreamSocket();
 
await spheroSocket.ConnectAsync(spheroPeer.HostName, "1");
 
MessageBox.Show("Connected to " + peers[0].DisplayName + " bluetooth service.");
}
}

Algo que vai notar é que vai ter de saber logo de inicio que o nome do serviço Bluetooth para o Sphero é “1”.

Executando este código com um Sphero emparelhado por perto pode ver o WP8 ligar-se com sucesso ao Sphero.

MessageBox confirmando que o WP8 está ligado ao Sphero

Em seguida envie ao Sphero qualquer comando que queira, talvez um simples comando para alteração da cor para uma cor aleatória. Note, como os comandos byte[] do Sphero são calculados não estão totalmente cobertos, só a parte final do byte[] é enviado para o dispositivo. (Pode saber mais sobre os comandos byte do Sphero no site de desenvolvimento do Sphero.)

private async void Bluetooth_SpheroToRandomColour(object sender, RoutedEventArgs e)
{
if (spheroSocket == null)
{
MessageBox.Show("Connect to Sphero first.");
return;
}
 
Random random = new Random();
Color randomColor = Color.FromArgb(254,
(byte)random.Next(1, 254),
(byte)random.Next(1, 254),
(byte)random.Next(1, 254));
 
// sample package is 255, 255, 2, 32, 1, 4, 0, 0, 0, 216
byte[] package = new ColorLedCommand(randomColor).ToPacket();
spheroSocket.OutputStream.WriteAsync(GetBufferFromByteArray(package));
}
 
private IBuffer GetBufferFromByteArray(byte[] package)
{
using (DataWriter dw = new DataWriter())
{
dw.WriteBytes(package);
return dw.DetachBuffer();
}
}

Irá ver que pode enviar qualquer tipo de dados para um socket ligado via Bluetooth. Neste caso enviou um pacote BT-SPP esperado pelo Sphero. Executando este código três vezes altera a cor do Sphero três vezes.

A bola Sphero com uma cor diferente após o WP8 ter enviado um comando para alterar a cor


NFC: "Tocar e executar" / Ler e escrever etiquetas

No Windows Phone 8 existe uma nova API para NFC. NFC é uma tecnologia de comunicação com um alcance de 2cm-4cm e uma largura de banda teórica de 200kbs-400kbs. NFC é mais conhecido pelo seu papel em soluções do estilo Wallet, (similares aos discutidos anteriormente) que utilizam o toque como significado da intenção de comprar. NFC é uma tecnologia bastante interessante que providencia comunicação entre telemóveis ou entre telemóvel e etiquetas NFC.

Para este exemplo qualquer etiqueta NFC suportada pelo Windows Phone 8 irá servir. Pode obter um pack de etiquetas NFC aqui.

Imagem de etiquetas NFC

Para escrever numa etiqueta NFC irá necessitar de ter um ProximityDevice. Verifique se o telemóvel suporta (confirmando que ProximityDevice.GetDefault() não está a null) e, se suporta, publique uma mensagem.

private void NFC_WriteText(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
 
if (device != null)
{
var id = device.PublishBinaryMessage("Windows:WriteTag.JustinAngel",
GetBufferFromString("Hello World!"),
UnregisterOnSend);
 
MessageBox.Show("Published Message: Hello World. ID is " + id);
}
}
 
 
private void UnregisterOnSend(ProximityDevice sender, long messageid)
{
sender.StopPublishingMessage(messageid);
}

O tipo de mensagem publicada determina muito o que pode ser escrito na etiqueta. Este código escreve a mensagem “JustinAngel” o que não é uma tipo de mensagem padrão: Portanto só a app de exemplo a irá utilizar. Execute o código e irá ver a confirmação de que “Hello World!” foi escrito na etiqueta.

MessageBox de confirmação

Em seguida pode intercetar o tipo de mensagem “Windows.JustinAngel” e mostrar o conteúdo no ecrã. O handler da mensagem só será invocado quando uma etiqueta NFC que utilize esse tipo de mensagem tocar no telemóvel.

private void NFC_ReadText(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
 
if (device != null)
{
device.SubscribeForMessage("Windows.JustinAngel", textMessageRecieved);
 
MessageBox.Show("Registered to NFC tag. Tap with NFC tag.");
}
}
 
private void textMessageRecieved(ProximityDevice sender, ProximityMessage message)
{
sender.StopSubscribingForMessage(message.SubscriptionId);
 
Dispatcher.BeginInvoke(() =>
MessageBox.Show("NFC data: " + message.DataAsString.Replace("\0", ""),
message.MessageType,
MessageBoxButton.OK));
}

Executando o código e tocando com uma etiqueta NFC na qual a mensagem foi escrita o telemóvel irá mostrar a seguinte MessageBox confirmando o conteúdo da etiqueta NFC.

MessageBox mostrando os dados da etiqueta NFC

Em vez de escrever apps com mensagens personalizadas, pode escrever também tipos utilizados pelo WP8 OS tais como URIs. O comportamento padrão do WP8 quando toca numa etiqueta NFC com HTTP URI é pedir permissão para abrir o website e abrir o IE10.

Comece por escrever o URI numa etiqueta NFC.

private void NFC_WriteNdefUri(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
 
if (device != null)
{
long id = device.PublishBinaryMessage("WindowsUri:WriteTag",
GetBufferFromString("http://JustinAngel.net"),
UnregisterOnSend);
 
MessageBox.Show("Published Uri Message to JustinAngel.net. Tap to verify. ID is " + id);
}
}

Execute o código e irá ver a confirmação de que a mensagem foi publicada na etiqueta NFC.

MessageBox confirmando que a mensagem foi publicada na etiqueta NFC

Agora, com a mesma etiqueta NFC, toque no telemóvel, e o telemóvel irá abrir o URI no browser IE10.


NFC: "Tocar e partilhar" / Telemóvel para telemóvel

O NFC é muito utilizado para iniciar um canal de comunicação com uma largura de banda superior entre dois telemóveis, como o Bluetooth. No Windows Phone 8 pode publicar mensagens NFC que pode ser recebidas por outros dispositivos e não escrever numa etiqueta NFC. Aqui está um exemplo disso. Irá ter um dispositivo WP8 a publicar uma mensagem e um segundo dispositivo que a irá mostrar no ecrã.

Pode facilmente publicar uma mensagem a qual irá ser recebida por uma app num outro dispositivo. Pode ver que as instruções “WriteTag” não estão incluídas neste tipo de mensagem.

private void NFC_SendMessageToSameAppOnAnotherDevice(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
 
if (device != null)
{
var id = device.PublishBinaryMessage("Windows.JustinAngel",
GetBufferFromString("Hello World!"),
UnregisterOnSend);
 
MessageBox.Show("Published Message: Hello World. ID is " + id);
}
}

No outro dispositivo irá registar todas as mensagens de um tipo personalizado.

private void NFC_ReadText(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
 
if (device != null)
{
device.SubscribeForMessage("Windows.JustinAngel", textMessageRecieved);
 
MessageBox.Show("Registered to NFC tag. Tap with NFC tag.");
}
}
 
private void textMessageRecieved(ProximityDevice sender, ProximityMessage message)
{
sender.StopSubscribingForMessage(message.SubscriptionId);
Dispatcher.BeginInvoke(() =>
MessageBox.Show("NFC data: " + message.DataAsString.Replace("\0", ""),
message.MessageType,
MessageBoxButton.OK));
}

Execute este código e junto os dois dispositivos, irá ver um dispositivo publicar a mensagem e o outro a receber essa mensagem.


Multi resolução

Windows Phone 8 suporta telemóveis com três resoluções de ecrã e deve ter isso em conta quando está a desenvolver as apps. As resoluções são: WVGA (480x800 pixeis), também utilizado no Windows Phone 7; WXGA (768x1280 pixeis), essencialmente uma versão HD do WVGA; e o universal 720P (720x1280 pixeis) que utiliza relação proporcional diferente do WVGA e WXGA. Deve ter em conta estas diferentes resoluções, certifique-se que utiliza <Grid /> com posicionamento relativo ao desenhar os ecrãs, e potenciais recursos de media diferentes para cada resolução.

Aqui está uma representação perfeita das resoluções suportadas pelo Windows Phone 8.

Renderização perfeita das diferentes resoluções do WP8

Windows Phone 8 introduz três novas propriedades que podem ser utilizadas para verificar a resolução do telemóvel.

private void MutliRes_ScreenSize(object sender, RoutedEventArgs e)
{
MessageBox.Show(
"ActualHeight: " + Application.Current.Host.Content.ActualHeight + Environment.NewLine +
"ActualWidth: " + Application.Current.Host.Content.ActualWidth + Environment.NewLine +
"ScaleFactor : " + Application.Current.Host.Content.ScaleFactor + Environment.NewLine);
}

Windows Phone 8 também vem com um emulador para cada resolução de ecrã. Pode escolher executar no emulador em modo WVGA, WXGA ou 720P.

Menu do emulador para escolher a resolução do ecrã

Executando o código em todos os emuladores irá ver algo inexperado.

3 emuladores com resoluções diferentes mostrando as propriedades da resolução

Como esperado a resolução WVGA usado no Mango tem 480x800 pixeis e não é dimensionada. Mas para WXGA em vez de ter apps com superfícies de 768x1280 ainda têm os mesmos 480x800 pixeis mas dimensionados para 160%. Portanto não tem que se preocupar com a sua interface refluir em WXGA. Para 720 é uma história similar; apps não têm superfícies de 720x1280 pixeis mas sim uma superfície de 853x480 dimensionada a 150%.

Esta implementação única de suporte a multi resoluções tem um enorme beneficio para apps do WP7: não têm de redesenhar as suas interfaces do zero para suportar multi resoluções. Apps WVGA do Mango são dimensionadas até 160% para WXGA. Essas mesmas apps irão ser dimensionadas até 150% para 720p e têm uma faixa lateral com 26~ pixeis em cada um dos lados.

Esta informação sobre as resoluções dos ecrãs e o dimensionamento é útil, já deve ter notado que não sabe exatamente qual a resolução que está a ser usada. Mas pode utilizar as propriedades para inferir qual é a resolução que está a ser usada.

private void MultiRes_Resolution(object sender, RoutedEventArgs e)
{
MessageBox.Show(
"IsHD: " + MultiRes.IsHighResolution + Environment.NewLine +
"Resolution: " + MultiRes.CurrentResolution);
}
 
public static class MultiRes
{
public static bool IsHighResolution
{
get { return Application.Current.Host.Content.ScaleFactor > 100; }
}
 
public static bool IsLowResolution
{
get { return Application.Current.Host.Content.ScaleFactor <= 100; }
}
 
public static bool IsWvga
{
get
{
return Application.Current.Host.Content.ActualHeight == 800
&& Application.Current.Host.Content.ScaleFactor == 100;
}
}
 
public static bool IsWxga
{
get { return Application.Current.Host.Content.ScaleFactor == 160; }
}
 
public static bool Is720p
{
get { return Application.Current.Host.Content.ScaleFactor == 150; }
}
 
public static String CurrentResolution
{
get
{
if (IsWvga) return "WVGA";
else if (IsWxga) return "WXGA";
else if (Is720p) return "HD720p";
else throw new InvalidOperationException("Unknown resolution");
}
}
}

Executando este código num emulador em todas as resoluções e irá ver que reporta corretamente a resolução usada e se é uma resolução HD ou SD.

Emulador com as 3 resoluções diferentes mostrando as propriedades MultiRes de ajuda

Utilizando esta informação numa classe de ajuda MultiRes pode alterar os recursos de media no ecrã. Mesmo sabendo que as apps irão ser dimensionadas automaticamente, recursos de bitmap podem não ser dimensionados tão bem como espera. Recursos vetoriais são são muito bem dimensionados, mas recursos de bitmap devem ser substituídos por versões especificas à resolução.

Dependendo do seu recurso bitmap e se é bem dimensionado, pode escolher a estratégia correta de multi resolução para a sua app.

Uma opção seria criar três cópias de cada recurso de media, uma para cada resolução:

Image myImage = new Image();
if (MultiRes.Is720p)
myImage.Source = new BitmapImage(new Uri("puppies.720p.jpg"));
else if (MultiRes.IsWvga)
myImage.Source = new BitmapImage(new Uri("puppies.wvga.jpg"));
else if (MultiRes.IsWxga)
myImage.Source = new BitmapImage(new Uri("puppies.wxga.jpg"));

Outra opção seria a utilização de uma imagem diferente para cada relação proporcional:

if (MultiRes.Is720p)
myImage.Source = new BitmapImage(new Uri(@"assets\16by9AspectRatio\puppies.jpg"));
else
myImage.Source = new BitmapImage(new Uri(@"assets\15by9AspectRatio\puppies.jpg"));

E ainda outra estratégia seria ter recursos para SD e HD:

if (MultiRes.IsHighResolution)
myImage.Source = new BitmapImage(new Uri(@"assets\HD\puppies.jpg"));
else
myImage.Source = new BitmapImage(new Uri(@"assets\SD\puppies.jpg"));

Se a sua app realmente não funciona bem com uma resolução em particular, pode optar por excluir a app dessa resolução em particular quando a adiciona à store. Por exemplo, se utilizou <Canvas /> para desenhar a sua app pode não posicionar corretamente em 720P, portanto pode excluir a sua app para essa resolução. Por essa razão é sempre recomendado a utilização de <Grid /> com posicionamento relativo e não <Canvas /> com posicionamento absoluto para a criação da interface do utilizador.

Editor do WmAppManifest permitindo ao programador excluir resoluções


Compras na app

Com o Windows Phone 8 a sua app pode oferecer-se para vender produtos virtuais utilizando IAP (in-app purchase). Tais produtos podem incluir “bens duráveis” tais como novos níveis para um jogo, revistas, e filmes ou bens de “consumo” tais como acessórios/extras para jogos e acesso temporário a conteúdos (ex. aluguer de filmes). Precisa de especificar o catalogo de compras na app no Developer Center durante a submissão da app, comprar algo desse catalogo em runtime, e a Microsoft irá providenciar a infraestrutura comercial de suporte em 190 países. Note que a API IAP não providencia mecanismos de inventário de produtos ou subscrições portanto tem de desenvolver os seus próprios processos.

De forma a testar a IAP crie uma nova app beta com alguns produtos IAP e submeta esses produtos com a app beta. Note que no Developer Center os produtos IAP de apps beta são sempre gratuitos, mas são úteis para testar do principio ao fim a API IAP. Neste próximo print screen pode ver como introduzir detalhes para um novo produto IAP durável.

Introduzir detalhes para um novo produto IAP durável

Aqui está uma app com dois produtos IAP: um consumível com o ID “123Consumable” e um durável com o ID “123Durable”.

Dois produtos IAP: o consumível com o ID “123Consumable” e o durável com o ID “123Durable”.

De forma a ter acesso a estes produtos IAP da app de testes irá necessitar de alterar o ProductID no WmAppManfiest.xml para corresponder à app beta.

<App xmlns="" ProductID="{331fbc13-d523-4117-a36c-2b0adc49e5af}" PublisherID="{144cccdf-12c2-4e7b-9527-87db1df42eeb}">

Agora pode iterar pelos produtos IAP associados ao ProductID currente.

private async void IAP_IterateOverProducts(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
 
var listing = await CurrentApp.LoadListingInformationAsync();
foreach (var product in listing.ProductListings)
{
sb.AppendLine(string.Format("{0}, {1}, {2},{3}, {4}",
product.Key,
product.Value.Name,
product.Value.FormattedPrice,
product.Value.ProductType,
product.Value.Description));
}
 
MessageBox.Show(sb.ToString(), "List all products", MessageBoxButton.OK);
}

Executando este código irá ver no ecrã todos os produtos IAP incluindo o consumível e o durável.

Lista dos produtos IAP dos quais 1 consumível e 1 durável

Para o próximo passo, tente comprar o produto consumível. Neste exemplo irá testar com o consumível pois é mais fácil de testar várias vezes em relação ao durável, que só pode sem comprado uma vez. Assim que tiver o recibo o mesmo pode ser exibido no ecrã.

private async void IAP_BuyConsumables(object sender, RoutedEventArgs e)
{
var listing = await CurrentApp.LoadListingInformationAsync();
var firstConsumable =
listing.ProductListings.FirstOrDefault(p => p.Value.ProductType == ProductType.Consumable);
 
var recipet = await CurrentApp.RequestProductPurchaseAsync(firstConsumable.Value.ProductId, true);
 
if (CurrentApp.LicenseInformation.ProductLicenses[firstConsumable.Value.ProductId].IsActive)
{
// do something
CurrentApp.ReportProductFulfillment(firstConsumable.Value.ProductId);
}
 
MessageBox.Show(recipet, "Reciept", MessageBoxButton.OK);
}

O código é bastante simples. Primeiro obtém a lista correta de produtos, depois inicio processo de compra, verifique que o produto foi comprado, efetue algo se o produto foi comprado, e finalmente imprima o recibo. A parte “efetue algo se o produto foi comprado” é onde o código de negócio especifico deve estar.

Running this code snippet in the emulator you will see the following purchasing screen and receipt.

A parte realmente interessante do código é o comentário “//efetue algo”, o qual deve ser substituído pelo seu código. Para consumiveis deve gerir um inventário persistente de produtos. Para duráveis pode resolver verificando se CurrentApp.LicenseInformation.ProductLicenses[IdDoMeuProduto].IsActive. De qualquer forma quando um produto é adquirido deve confirmar a realização e efetuar algo na sua app.

This page was last modified on 4 July 2013, at 08:42.
1197 page views in the last 30 days.
×