Em diversas situações o dispositivo é utilizado como entrada de dados para os aplicativos, como em jogos que utilizam movimentos como controles, e o Windows Phone suporta vários tipos de sensores que permitem que a aplicação capte a orientação e o movimento do aparelho.

A função do giroscópio é medir a velocidade nos eixos X, Y e Z, e possui uma sensibilidade a ponto de indicar a rotação até mesmo com o aparelho parado, porém o giroscópio é opcional nos aparelhos.

Smartphone da HTC com Windows Phone
Figura 1. Smartphone da HTC com Windows Phone
Nota: Por ser opcional, é essencial que as aplicações verifiquem se o aparelho possui o giroscópio e caso não possua, adaptem o aplicativo para o uso sem o mesmo. No Windows Phone existem recursos para verificar e decidir a melhor maneira de utilizá-los.

Infelizmente para testar o exemplo a ser mostrado neste artigo você necessitará de um device real, portanto fique avisado que caso vá testar o aplicativo no emulador, será mostrado no estado da aplicação que o device não possui giroscópio.

Abra o Visual Studio e crie um projeto do tipo Windows Phone Application chamado GiroscopioApplication, conforme a Figura 2.

Criação do projeto GiroscopioApplication no Visual Studio
Figura 2. Criação do projeto GiroscopioApplication no Visual Studio

Ao criar o projeto é exibida uma caixa com a opção de escolha da versão do Windows Phone, escolha a 7.1, pois assim o seu aplicativo funcionará em todas as versões de Windows Phone disponíveis.

Agora você deve adicionar uma nova referência, para isso vá ao Solution Explorer e clique com o botão direito em Reference, e depois Add Reference. Será exibida uma janela com diversas opções e você deverá selecionar o Microsoft.XNA.Framework e o Microsoft.Devices.Sensors, conforme a Figura 3.

Adicionando referência ao projeto
Figura 3. Adicionando referência ao projeto

Agora você deverá alterar o layout da página principal, para isso abra a MainPage.xaml e no ContentPanel adicione os controles necessários para a visualização dos dados. Você terá um label para informar o estado da aplicação, linhas que mostrarão os valores de X, Y e Z e etc. Veja como ficará a MainPage.xaml com os controles adicionados na Listagem 1.


<phone:PhoneApplicationPage 
x:Class="GiroscopioApplication.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!--TitlePanel contains the name of the application and page title-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="APLICATIVO SENSOR"
Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="giroscópio" Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Vertical">
                <TextBlock Height="30" Name="lblStatus" Text="Estado: "
VerticalAlignment="Top" />
                <TextBlock Height="30" Name="lblAtualizacoes"
Text="Intervalo de atualizações: " VerticalAlignment="Top" />
            </StackPanel>
            <TextBlock Text="Velocidade Rotacional:" />
            <Grid>
                <TextBlock Height="30" HorizontalAlignment="Left"
Name="lblXAtual" Text="X: 1.0" VerticalAlignment="Top"
Foreground="Red" FontSize="28" FontWeight="Bold" />
                <TextBlock Height="30" HorizontalAlignment="Center"
Name="lblYAtual" Text="Y: 1.0" VerticalAlignment="Top" Foreground="LightGreen"
FontSize="28" FontWeight="Bold" />
                <TextBlock Height="30" HorizontalAlignment="Right"
Name="lblZAtual" Text="Z: 1.0" VerticalAlignment="Top" Foreground="LightBlue"
FontSize="28" FontWeight="Bold" />
            </Grid>
            <Grid Height="140">
                <Line x:Name="currentXLine" X1="240" Y1="40" X2="240"
Y2="40" Stroke="Red" StrokeThickness="14" />
                <Line x:Name="currentYLine" X1="240" Y1="40" X2="240"
Y2="40" Stroke="LightGreen" StrokeThickness="14" />
                <Line x:Name="currentZLine" X1="240" Y1="40" X2="240"
Y2="40" Stroke="Blue" StrokeThickness="14" />                    
            </Grid>
            <TextBlock Text="cumulative rotation (degrees)"/>
            <Grid>
                <TextBlock Height="30" HorizontalAlignment="Left"
Name="lblXAcumulado" Text="X: 1.0" VerticalAlignment="Top" Foreground="Red"
FontSize="28" FontWeight="Bold" />
                <TextBlock Height="30" HorizontalAlignment="Center"
Name="lblYAcumulado" Text="Y: 1.0" VerticalAlignment="Top" Foreground="LightGreen"
FontSize="28" FontWeight="Bold" />
                <TextBlock Height="30" HorizontalAlignment="Right"
Name="lblZAcumulado" Text="Z: 1.0" VerticalAlignment="Top" Foreground="LightBlue"
FontSize="28" FontWeight="Bold" />
            </Grid>
            <Grid Height="200" Name="grdAcumulo">
                <Line x:Name="lnX" X1="240" Y1="100" X2="240" Y2="0"
Stroke="Red" StrokeThickness="14" />
                <Line x:Name="lnY" X1="240" Y1="100" X2="240" Y2="0"
Stroke="LightGreen" StrokeThickness="14" />
                <Line x:Name="lnZ" X1="240" Y1="100" X2="240" Y2="0"
Stroke="LightBlue" StrokeThickness="14" />
            </Grid>
            <Button Content="Start" Height="72" Name="btnStart"
Click="btnStart_Click" />
        </StackPanel>
    </Grid>
</Grid>

</phone:PhoneApplicationPage>
Listagem 1. Arquivo MainPage.xaml

Sua aplicação deverá estar conforme a Figura 4.

Layout da página principal, após alteração da MainPage.xaml
Figura 4. Layout da página principal, após alteração da MainPage.xaml

Agora você deverá tratar o método do evento de click do botão Start e adicionar mais dois métodos na classe, um para ser chamado a certo intervalo de tempo e alterar as informações e um outro método para exibir as informações após a coleta. Verifique como ficará a classe MainPage.xaml.cs na Listagem 2.

Você também deve adicionar a referência no topo para System.Windows.Threading, Microsoft.XNA.Framework, e Microsoft.Devices.Sensors.

Lembrando que como alguns devices não possuem giroscópio, então no construtor é feita a verificação e caso não possua, impossibilita o teste pelo click do botão.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Windows.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Devices.Sensors;

namespace GiroscopioApplication
{
    public partial class MainPage : PhoneApplicationPage
    {

        Gyroscope giroscopio;
        DispatcherTimer timer;

        Vector3 taxaRotacao = Vector3.Zero;
        Vector3 rotacaoAcumulada = Vector3.Zero;
        DateTimeOffset ultimaAtualizacao = DateTimeOffset.MinValue;
        bool isDataValid;

        // Constructor
        public MainPage()
        {
            InitializeComponent();
            if (!Gyroscope.IsSupported)
            {
                lblStatus.Text = "Não há suporte de giroscópio para o aparelho";
                btnStart.IsEnabled = false;
            }
            else
            {
                timer = new DispatcherTimer();
                timer.Interval = TimeSpan.FromMilliseconds(60);
                timer.Tick += timer_Tick;
            }
        }

        void timer_Tick(object sender, EventArgs e)
        {
            if (isDataValid)
            {
                lblStatus.Text = "Recebendo informações....";           
            }

            lblXAtual.Text = taxaRotacao.X.ToString("0.000");
            lblYAtual.Text = taxaRotacao.Y.ToString("0.000");
            lblZAtual.Text = taxaRotacao.Z.ToString("0.000");

            lblXAcumulado.Text =
 MathHelper.ToDegrees(rotacaoAcumulada.X).ToString("0.00");
            lblYAcumulado.Text =
 MathHelper.ToDegrees(rotacaoAcumulada.Y).ToString("0.00");
            lblZAcumulado.Text =
 MathHelper.ToDegrees(rotacaoAcumulada.Z).ToString("0.00");

            double centerX = grdAcumulo.ActualWidth / 2.0;
            double centerY = grdAcumulo.ActualHeight / 2.0;

            currentXLine.X2 = centerX + taxaRotacao.X * 100;
            currentYLine.X2 = centerX + taxaRotacao.Y * 100;
            currentZLine.X2 = centerX + taxaRotacao.Z * 100;

            lnX.X2 = centerX - centerY * Math.Sin(rotacaoAcumulada.X);
            lnX.Y2 = centerY - centerY * Math.Cos(rotacaoAcumulada.X);

            lnY.X2 = centerX - centerY * Math.Sin(rotacaoAcumulada.Y);
            lnY.Y2 = centerY - centerY * Math.Sin(rotacaoAcumulada.X);

            lnZ.X2 = centerX - centerY * Math.Cos(rotacaoAcumulada.X);
            lnZ.Y2 = centerY - centerY * Math.Sin(rotacaoAcumulada.Y);
        }

        void giroscopio_CurrentValueChanged(object sender,
 SensorReadingEventArgs<GyroscopeReading> e)
        {
            isDataValid = giroscopio.IsDataValid;
            if (ultimaAtualizacao.Equals(DateTimeOffset.MinValue))
            {
                ultimaAtualizacao = e.SensorReading.Timestamp;
            }
            else
            {
                taxaRotacao = e.SensorReading.RotationRate;
                TimeSpan timeSinceLastUpdate =
 e.SensorReading.Timestamp - ultimaAtualizacao;
                rotacaoAcumulada +=
 taxaRotacao * (float)(timeSinceLastUpdate.TotalSeconds);
                ultimaAtualizacao = e.SensorReading.Timestamp;
            }
        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            if (giroscopio != null && giroscopio.IsDataValid)
            {
                giroscopio.Stop();
                timer.Stop();
                lblStatus.Text = "Giroscópio parado";
            }
            else
            {
                if (giroscopio == null)
                {
                    giroscopio = new Gyroscope();
                    giroscopio.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
                    lblAtualizacoes.Text = "Intervalo de atualizações: " +
 giroscopio.TimeBetweenUpdates.TotalMilliseconds + " ms";

                    giroscopio.CurrentValueChanged += giroscopio_CurrentValueChanged;
                }
            }
            try
            {
                lblStatus.Text = "Iniciando o giroscópio";
                giroscopio.Start();
                timer.Start();
            }
            catch (Exception)
            {
                lblStatus.Text = "Não é possível iniciar o giroscópio";
            }
        }
    }
}
Listagem 2. Código da classe MainPage.xaml.cs

Basta agora conectar seu dispositivo, alterar o modo de debug para device, conforme a Figura 5, pressionar F5 e testar a aplicação.

Alterando o modo de debug para Device
Figura 5. Alterando o modo de debug para Device