방법: Windows Phone의 나침반 센서에서 데이터 가져오기

2012-02-09

이 항목에서는 나침반 데이터의 그래픽 표현 및 숫자 표시를 가능하게 하는 나침반 응용프로그램을 만드는 과정에 대해 설명합니다. 또한 이 연습에서는 나침반 보정 대화 상자를 구현하는 방법도 보여 줍니다.

나침반(또는 자기계) 센서를 사용하여 자북을 기준으로 한 장치의 회전 각도를 확인할 수 있습니다. 또한 응용프로그램은 원시 자기계 표시값을 사용하여 단말기 주위의 자력을 감지할 수 있습니다. 나침반 센서가 모든 Windows Phone 단말기에 필요한 것은 아니므로, 응용프로그램을 설계하고 구현할 때 이 점을 고려해야 합니다. 응용프로그램은 센서를 사용할 수 있는지와, 그렇지 않은 경우 대체 입력 메커니즘이 제공되는지 아니면 정상적으로 종료되는지 항상 확인해야 합니다.

나침반 API는 단말기의 방향에 따라 단일 축을 사용하여 기수를 계산합니다. 모든 축에서 단말기 방향을 사용하는 응용프로그램을 만들려면 Motion 클래스의 RotationMatrix 속성을 사용해야 합니다.

단말기의 나침반 센서는 특히 자기장에 노출될 경우 시간이 지날수록 정확도가 떨어질 수 있습니다. 이 경우 나침반을 보정하는 간단한 사용자 동작이 있습니다. Calibrate 이벤트는 기수 정확도가 +/-20도를 초과하는 것을 시스템에서 감지할 때마다 발생합니다. 이 예제는 사용자가 나침반을 보정할 수 있는 보정 대화 상자를 구현하는 방법을 보여 줍니다.

다음 단계는 나침반 응용프로그램을 만드는 방법을 보여 줍니다.

나침반 응용프로그램을 만들려면

  1. Visual Studio 에서 새로운 Windows Phone 응용프로그램 프로젝트를 만듭니다. 이 템플릿은 Windows Phone용 Silverlight 카테고리에 있습니다.

  2. 이 응용프로그램에는 센서 API 및 XNA Framework를 포함하는 어셈블리에 대한 참조가 필요합니다. 나침반 데이터의 일부가 XNA Framework Vector3 개체 형태로 전달되기 때문입니다. 프로젝트 메뉴에서 참조 추가…를 클릭하고 Microsoft.Devices.SensorsMicrosoft.Xna.Framework를 선택한 다음 확인을 클릭합니다.

  3. MainPage.xaml 파일에서 "ContentPanel"이라는 Grid 요소에 다음 XAML 코드를 넣습니다. 이 XAML 코드는 나침반 데이터를 표시할 UI를 만듭니다. TextBlock 요소는 나침반 데이터를 수치로 표시하는 데 사용됩니다. Line 요소는 기수 및 원시 나침반 데이터를 그래픽으로 표시하는 데 사용됩니다.

    <StackPanel Orientation="Vertical">
      <StackPanel Orientation="Horizontal">
        <TextBlock>status: </TextBlock>
        <TextBlock Name="statusTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>time between updates:</TextBlock>
        <TextBlock Name="timeBetweenUpdatesTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>magnetic heading: </TextBlock>
        <TextBlock Name="magneticTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>true heading: </TextBlock>
        <TextBlock Name="trueTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>heading accuracy: </TextBlock>
        <TextBlock Name="accuracyTextBlock"></TextBlock>
      </StackPanel>
      <StackPanel Orientation="Horizontal">
        <TextBlock>compass orientation mode:</TextBlock>
        <TextBlock Name="orientationTextBlock"></TextBlock>
      </StackPanel>
      <Grid Height="200" Name="headingGrid">
        <TextBlock Foreground="Yellow" FontSize="16">magnetic heading</TextBlock>
        <TextBlock Foreground="Orange" FontSize="16" Margin="0,18">true heading</TextBlock>
        <Line x:Name="magneticLine" X1="240" Y1="100" X2="240" Y2="0" Stroke="Yellow" StrokeThickness="4"></Line>
        <Line x:Name="trueLine" X1="240" Y1="100" X2="240" Y2="0" Stroke="Orange" StrokeThickness="4"></Line>
      </Grid>
      <TextBlock Text="raw magnetometer data:"></TextBlock>
      <Grid>
        <TextBlock Height="30" HorizontalAlignment="Left" Name="xTextBlock" Text="X: 1.0" VerticalAlignment="Top" Foreground="Red" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Center" Name="yTextBlock" Text="Y: 1.0" VerticalAlignment="Top" Foreground="Green" FontWeight="Bold"/>
        <TextBlock Height="30" HorizontalAlignment="Right"  Name="zTextBlock" Text="Z: 1.0" VerticalAlignment="Top"  Foreground="Blue" FontWeight="Bold"/>
      </Grid>
      <Grid Height="140">
        <Line x:Name="xLine" X1="240" Y1="40" X2="240" Y2="40" Stroke="Red" StrokeThickness="14"></Line>
        <Line x:Name="yLine" X1="240" Y1="70" X2="240" Y2="70" Stroke="Green" StrokeThickness="14"></Line>
        <Line x:Name="zLine" X1="240" Y1="100" X2="240" Y2="100" Stroke="Blue" StrokeThickness="14"></Line>
      </Grid>
    </StackPanel>
    
    
    

    다음 그림은 UI가 표시되는 방식입니다.

    나침반 응용프로그램 UI
  4. 다음으로 나침반 보정 대화 상자 UI를 정의하는 XAML을 추가합니다. 이 UI는 사용자에게 이미지를 표시하며 나침반을 보정하기 위해 단말기를 이동하는 방법을 설명합니다. 외부 StackPanel은 사용자에게 보이지 않도록 Visibility.Collapsed로 설정됩니다. UI를 표시하는 코드는 이 연습의 뒷부분에 나옵니다.

    <!--Calibration UI-->
    <StackPanel Name="calibrationStackPanel" Background="Black" Opacity="1" Visibility="Collapsed">
      <Image Source="/Images/calibrate_compass.png" Opacity=".95" HorizontalAlignment="Center"/>
      <TextBlock TextWrapping="Wrap" TextAlignment="Center">The compass on your device needs to be calibrated.
      Hold the device in front of you and sweep it through a figure 8 pattern as shown
      until the calibration is complete.</TextBlock>
      <StackPanel Orientation="Horizontal" Margin="0,10" HorizontalAlignment="Center">
        <TextBlock>heading accuracy:</TextBlock>
        <TextBlock Name="calibrationTextBlock">0.0°</TextBlock>  
      </StackPanel>
      <Button Name="calibrationButton" Content="Done" Click="calibrationButton_Click"></Button>
    </StackPanel>
    <!--End Calibration UI-->
    
    
    

    다음 그림은 보정 UI가 표시되는 방식입니다.

    나침반 보정 UI 화면
  5. 위에서 정의한 나침반 보정 대화 상자는 나침반을 보정하기 위해 사용자가 단말기를 움직여야 할 패턴을 보여 주는 이미지를 사용합니다. 이미지를 솔루션에 추가하려면 프로젝트 디렉터리의 "Images"라는 하위 폴더에 이미지를 복사합니다. 솔루션 탐색기에서 응용프로그램 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 추가 -> 기존 항목...을 선택합니다. 이미지를 선택하고 추가를 클릭합니다. 이미지를 추가한 후 솔루션 탐색기에서 이미지 아이콘을 마우스 오른쪽 버튼으로 클릭하고 속성을 선택합니다. Build Action 속성이 Content로 설정되어 있는지 확인합니다. 이 샘플에 사용된 이미지에는 Windows Phone용 코드 샘플에서 다운로드할 수 있는 원시 센서 데이터 샘플이 포함되어 있습니다.

  6. MainPage.xaml에 추가할 마지막 UI 코드는 나침반으로부터 데이터 가져오기를 시작하거나 중지할 버튼 하나가 있는 응용프로그램 모음의 정의입니다. 주석으로 처리된 응용프로그램 모음 코드(프로젝트 템플릿과 함께 포함) 위에 다음 코드를 붙여 넣습니다.

    <phone:PhoneApplicationPage.ApplicationBar>
      <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton IconUri="/Images/onoff.png" Text="on/off" Click="ApplicationBarIconButton_Click"/>
      </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>
    
    
    
  7. 이제 MainPage.xaml.cs 코드 숨김 페이지를 열고 센서 및 XNA Framework 네임스페이스에 대한 using 지시문을 해당 페이지의 맨 위에 있는 다른 using 지시문에 추가합니다. 이 예제는 타이머를 사용하여 UI를 업데이트하므로, System.Windows.Threading 네임스페이스도 포함합니다.

    using Microsoft.Devices.Sensors;
    using Microsoft.Xna.Framework;
    using System.Windows.Threading;
    
    
  8. MainPage 클래스 정의의 맨 위에 있는 일부 멤버 변수를 선언합니다.

    public partial class MainPage : PhoneApplicationPage
    {
      Compass compass;
      DispatcherTimer timer;
    
      double magneticHeading;
      double trueHeading;
      double headingAccuracy;
      Vector3 rawMagnetometerReading;
      bool isDataValid;
    
      bool calibrating = false;
    
    

    첫 번째 변수는 Compass 유형의 개체로, 나침반 센서에서 데이터를 가져오는 데 사용됩니다. 다음으로 정기적으로 UI를 업데이트하는 데 사용될 DispatcherTimer가 선언됩니다. 그리고 나침반 데이터를 포함할 변수 집합이 있습니다. 이러한 변수는 나침반 API를 사용하여 설정되고 DispatcherTimerTick 이벤트에 표시됩니다. 마지막으로, 부울 변수 calibrating은 보정 대화 상자가 현재 표시되고 있는지 여부를 추적하는 데 사용됩니다.

  9. 페이지 생성자에서 응용프로그램이 실행되고 있는 단말기의 나침반 센서 지원 여부를 확인합니다. 모든 단말기가 모든 센서를 지원하지는 않으므로 센서를 사용하기 전에 항상 확인해야 합니다. 나침반이 지원되지 않을 경우 메시지가 표시되고 응용프로그램 모음이 숨겨집니다. 나침반이 지원되는 경우 DispatcherTimer가 초기화되고 이벤트 처리기가 할당되지만 타이머는 이 시점에 시작되지 않습니다. 기존 페이지 생성자를 다음 코드로 바꿉니다.

    // Constructor
    public MainPage()
    {
      InitializeComponent();
    
      if (!Compass.IsSupported)
      {
        // The device on which the application is running does not support
        // the compass sensor. Alert the user and hide the
        // application bar.
        statusTextBlock.Text = "device does not support compass";
        ApplicationBar.IsVisible = false;
      }
      else
      {
        // Initialize the timer and add Tick event handler, but don't start it yet.
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(30);
        timer.Tick += new EventHandler(timer_Tick);
      }
    }
    
    
  10. 응용프로그램 모음 버튼에 대한 클릭 이벤트의 처리기를 추가합니다. 이 항목의 앞부분에서 XAML 코드를 추가한 방식에 따라 Visual Studio 가 이 처리기를 추가했을 수도 있습니다. 이 경우 처리기 내부에서 모든 코드를 제거합니다. 처리기가 자동으로 추가되지 않았으면 다음 빈 함수를 복사하여 MainPage 클래스 정의에 붙여 넣습니다.

    private void ApplicationBarIconButton_Click(object sender, EventArgs e)
    {
              
    }
    
    
  11. 응용프로그램 모음 버튼 클릭 처리기에서 먼저 Compass 개체가 null이 아니고 데이터를 수신 중인지 확인합니다. 그렇다면 사용자가 나침반을 중지하기 위해 버튼을 클릭하는 중이므로 CompassDispatcherTimer 둘 다에 대해 Stop()()()()을 호출합니다. 다음 코드를 빈 버튼 클릭 처리기 내부에 붙여 넣습니다.

      if (compass != null && compass.IsDataValid)
      {
        // Stop data acquisition from the compass.
        compass.Stop();
        timer.Stop();
        statusTextBlock.Text = "compass stopped.";
      }
    
    
    
  12. 그러면 코드는 사용자가 나침반을 시작하는 경우를 처리하게 됩니다. Compass 개체가 null이면 새 인스턴스를 만듭니다. 원하는 업데이트 간 시간을 설정합니다. 단말기별로 센서가 지원하는 업데이트 간격이 서로 다르므로 이 예에서는 센서의 실제 간격을 사용자에게 표시하기 위해 속성이 설정된 후 쿼리됩니다. 다음으로 이벤트 처리기는 나침반에 새 데이터가 있을 때마다 발생하는 CurrentValueChanged 이벤트 및 나침반을 보정해야 할 때 발생하는 Calibrate 이벤트에 대해 추가됩니다. 이 코드를 이전 코드 섹션 뒤에 나오는 버튼 클릭 처리기 내부에 붙여 넣습니다.

      else
      {
        if (compass == null)
        {
          // Instantiate the compass.
          compass = new Compass();
    
          // Specify the desired time between updates. The sensor accepts
          // intervals in multiples of 20 ms.
          compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
    
          // The sensor may not support the requested time between updates.
          // The TimeBetweenUpdates property reflects the actual rate.
          timeBetweenUpdatesTextBlock.Text = compass.TimeBetweenUpdates.TotalMilliseconds + " ms";
    
    
          compass.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<CompassReading>>(compass_CurrentValueChanged);
          compass.Calibrate += new EventHandler<CalibrationEventArgs>(compass_Calibrate);
        }
    
    
    
  13. 이제 Start()()()() 메서드를 사용하여 나침반을 시작합니다. Start에 대한 호출이 실패할 수 있으므로 이 호출을 try 블록에 넣어야 합니다. catch 블록에서 나침반을 시작할 수 없음을 사용자에게 경고할 수 있습니다. 또한 이 코드는 DispatcherTimer도 시작합니다. 이 코드를 이전 코드 섹션 뒤에 나오는 시작 버튼 클릭 처리기에 붙여 넣습니다.

        try
        {
          statusTextBlock.Text = "starting compass.";
          compass.Start();
          timer.Start();
    
          // Start accelerometer for detecting compass axis
          accelerometer = new Accelerometer();
          accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);
          accelerometer.Start();
        }
        catch (InvalidOperationException)
        {
          statusTextBlock.Text = "unable to start compass.";
        }
    
      }
    
    
  14. 이제 CurrentValueChanged 이벤트 처리기를 구현합니다. 이 메서드는 TimeBetweenUpdates를 사용하여 지정한 빈도로 새 나침반 데이터가 있는 시스템에서 호출됩니다. 처리기는 나침반 데이터가 포함된 CompassReading 개체를 수신합니다. 이 처리기는 UI에 대한 액세스 권한이 없는 백그라운드 스레드에서 호출됩니다. 따라서 이 메서드에서 UI를 수정하려면 Dispatcher.BeginInvoke 메서드를 사용하여 UI 스레드에서 코드를 호출해야 합니다. 이 예제에서는 디스패처 타이머를 사용하여 UI를 업데이트하므로, 이 메서드는 간단히 클래스 멤버 변수의 값을 CompassReading 개체의 값으로 설정합니다. 이 응용프로그램은 정확도의 부호가 아니라 크기하고만 관련이 있으므로 기수 정확도의 절대값을 가져오는 데 Abs 함수가 사용됩니다.

    void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e)
    {
      // Note that this event handler is called from a background thread
      // and therefore does not have access to the UI thread. To update 
      // the UI from this handler, use Dispatcher.BeginInvoke() as shown.
      // Dispatcher.BeginInvoke(() => { statusTextBlock.Text = "in CurrentValueChanged"; });
    
    
      isDataValid = compass.IsDataValid;
    
      trueHeading = e.SensorReading.TrueHeading;
      magneticHeading = e.SensorReading.MagneticHeading;
      headingAccuracy = Math.Abs(e.SensorReading.HeadingAccuracy);
      rawMagnetometerReading = e.SensorReading.MagnetometerReading;
                
    }
    
    
  15. DispatcherTimer Tick 이벤트 처리기를 구현하여 현재 나침반 표시값으로 UI를 업데이트합니다. 이 메서드는 나침반이 현재 보정되고 있는지 여부에 따라 다른 동작을 수행합니다. 보정이 진행되고 있으면 데이터가 수신 중임을 나타내기 위해 상태 TextBlock이 업데이트됩니다. 다음으로 TextBlock 개체가 업데이트되어 자북극을 기준으로 하는 기수인 자방위, 지리적 북극을 기준으로 하는 기수인 진방위, 나침반 표시값의 오류를 보여 주는 기수 정확도를 표시합니다. 그러면 Line 개체가 업데이트되어 나침반 표시값을 그래픽으로 나타냅니다. Microsoft.Xna.Framework 라이브러리의 MathHelper 클래스는 측정값을 삼각 함수와 함께 사용할 수 있도록 도 단위에서 라디안으로 변환합니다. 다음으로 원시 자기계 표시값이 수치와 그래픽으로 표시됩니다. 다음 코드를 MainPage.xaml.cs에 붙여 넣습니다. 이 메서드의 미리 알림은 다음 단계에서 표시됩니다.

    void timer_Tick(object sender, EventArgs e)
    {
      if (!calibrating)
      {
        if (isDataValid)
        {
          statusTextBlock.Text = "receiving data from compass.";
        }
    
        // Update the textblocks with numeric heading values
        magneticTextBlock.Text = magneticHeading.ToString("0.0");
        trueTextBlock.Text = trueHeading.ToString("0.0");
        accuracyTextBlock.Text = headingAccuracy.ToString("0.0");
    
        // Update the line objects to graphically display the headings
        double centerX = headingGrid.ActualWidth / 2.0;
        double centerY = headingGrid.ActualHeight / 2.0;
        magneticLine.X2 = centerX - centerY * Math.Sin(MathHelper.ToRadians((float)magneticHeading));
        magneticLine.Y2 = centerY - centerY * Math.Cos(MathHelper.ToRadians((float)magneticHeading));
        trueLine.X2 = centerX - centerY * Math.Sin(MathHelper.ToRadians((float)trueHeading));
        trueLine.Y2 = centerY - centerY * Math.Cos(MathHelper.ToRadians((float)trueHeading));
    
        // Update the textblocks with numeric raw magnetometer readings
        xTextBlock.Text = rawMagnetometerReading.X.ToString("0.00");
        yTextBlock.Text = rawMagnetometerReading.Y.ToString("0.00");
        zTextBlock.Text = rawMagnetometerReading.Z.ToString("0.00");
    
        // Update the line objects to graphically display raw data
        xLine.X2 = xLine.X1 + rawMagnetometerReading.X * 4;
        yLine.X2 = yLine.X1 + rawMagnetometerReading.Y * 4;
        zLine.X2 = zLine.X1 + rawMagnetometerReading.Z * 4;
      }
    
    
  16. Tick 이벤트 처리기의 두 번째 부분은 나침반 보정을 위해 UI를 업데이트합니다. 이 UI는 사용자에게 기본적으로 표시됩니다. UI를 표시할 코드는 다음 단계에 나옵니다. 이 코드는 간단히 HeadingAccuracy 값을 계산하며, 이 값이 10도 이하인 경우 나침반은 보정할 것으로 간주되므로, 텍스트가 녹색으로 설정되고 사용자는 보정이 완료되었다는 알림을 받습니다. 그렇지 않은 경우 텍스트는 빨강이고 기수 정확도의 숫자 값이 표시됩니다. 이전 단계의 코드 뒤에 다음 코드를 붙여 넣어 Tick 이벤트 처리기를 완료합니다.

      else
      {
        if (headingAccuracy <= 10)
        {
          calibrationTextBlock.Foreground = new SolidColorBrush(Colors.Green);
          calibrationTextBlock.Text = "Complete!";
        }
        else
        {
          calibrationTextBlock.Foreground = new SolidColorBrush(Colors.Red);
          calibrationTextBlock.Text = headingAccuracy.ToString("0.0");
        }
      }
    }
    
    
  17. 이제 Calibrate 이벤트 처리기를 구현합니다. 이 이벤트는 나침반 기수 정확도가 +/-20도를 초과하는 것을 시스템에서 감지하면 발생합니다. 이 이벤트 처리기에서 간단히 보정 UI를 보이게 하고 calibrating 멤버 변수를 true로 설정합니다. 이 이벤트 처리기는 UI 스레드에서 호출되지 않으므로 UI를 업데이트하는 코드는 Dispatcher.Invoke를 사용하여 호출됩니다.

    void compass_Calibrate(object sender, CalibrationEventArgs e)
    {
      Dispatcher.BeginInvoke(() => { calibrationStackPanel.Visibility = Visibility.Visible; });
      calibrating = true;
    }
    
    
    
  18. 마지막으로, 보정 UI에서 Click 버튼에 대한 이벤트 처리기를 구현합니다. 사용자는 보정이 완료되면 이 버튼을 탭합니다. 이 메서드는 간단히 보정 UI를 숨기고 calibrating 멤버 변수를 false로 설정합니다.

    private void calibrationButton_Click(object sender, RoutedEventArgs e)
    {
      calibrationStackPanel.Visibility = Visibility.Collapsed;
      calibrating = false;
    }
    
    
    

나침반 API는 휴대폰의 방향에 따라 다른 축을 사용하여 기수를 계산합니다. 다음 코드는 런타임에 나침반이 사용 중인 방향을 결정하기 위해 예제 응용프로그램을 수정합니다.

나침반 방향 모드를 결정하려면

  1. 먼저 Accelerometer 형식의 멤버 변수를 다른 멤버 변수가 있는 클래스의 맨 위에 추가합니다.

    Accelerometer accelerometer;
    
    
    
  2. 그리고 응용프로그램 모음 버튼 클릭 처리기에서 CompassDispatcherTimerStop 메서드에 대한 호출 바로 뒤에 가속도계의 Stop()()()() 메서드를 호출합니다.

    
    …
    compass.Stop();
    timer.Stop();
    // Add the following line
    accelerometer.Stop();
    
    
    
  3. 다음으로 역시 응용프로그램 모음 버튼 클릭 처리기 내에서 Accelerometer 개체를 초기화하고 CurrentValueChanged에 대한 이벤트 처리기를 연결한 다음 Start()()()()를 호출합니다. 이 코드를 CompassDispatcherTimer에 대한 Start 호출 바로 뒤에 추가합니다.

    …
    statusTextBlock.Text = "starting compass.";
    compass.Start();
    timer.Start();
    
    // add the following lines
    accelerometer = new Accelerometer();
    accelerometer.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<AccelerometerReading>>(accelerometer_CurrentValueChanged);
    accelerometer.Start();
    
    
    
  4. 마지막으로, Accelerometer 개체의 CurrentValueChanged 이벤트에 대한 이벤트 처리기를 구현합니다. 이 메서드는 가속도 표시값을 나타내는 Vector3 개체를 가져온 다음 삼각 함수를 사용하여 단말기의 방향을 결정합니다. 마지막으로, UI가 현재 나침반 모드로 업데이트됩니다. 이 이벤트는 UI 스레드에서 호출되지 않으므로 Dispatcher.BeginInvoke는 UI를 업데이트하는 데 사용됩니다.

    void accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs<AccelerometerReading> e)
    {
      Vector3 v = e.SensorReading.Acceleration;
    
      bool isCompassUsingNegativeZAxis = false;
    
      if (Math.Abs(v.Z) < Math.Cos(Math.PI / 4) &&
                    (v.Y < Math.Sin(7 * Math.PI / 4)))
      {
        isCompassUsingNegativeZAxis = true;
      }
    
      Dispatcher.BeginInvoke(() => { orientationTextBlock.Text = (isCompassUsingNegativeZAxis) ? "portrait mode" : "flat mode"; });
    }
    
    
    

표시: