본문 바로가기
Programming/Windows Phone

생애 첫 번째 윈도우 폰 7 애플리케이션 만들기 5회

by 강철 벼룩 2011. 5. 1.
멀티 터치 입력을 사용하면 사용자가 동시에 여러 손가락 제스처를 적용할 수 있고 애플리케이션에 복잡한 명령을 제공하는 한 단위로 해석할 수 있으며 페이지의 요소를 직접 조정하는 시뮬레이션(예를 들면 한번에 확대하고 축소하는 동작)을 할 수 있다. 이번 시간에는 윈도우 폰 퍼즐 게임을 업데이트하고, 빈 슬롯으로 퍼즐 조각을 태핑하고 드래그하는 멀티 터치 입력을 받아 사용자가 퍼즐의 조각을 움직이게 할 수 있다.

1.
솔루션 탐색기]PuzzlePage.xaml 코드 숨김 파일을 연다.

2.
PuzzlePage 클래스에서 기존 멤버 변수 아래에 다음 선언(아래 강조된 항목) 입력한다.

public partial class PuzzlePage : PhoneApplicationPage
{
  private const double DoubleTapSpeed = 500;
  private const int ImageSize = 435;
  private PuzzleGame game;
  private Canvas[] puzzlePieces;
  private Stream imageStream;

  private long lastTapTicks;
  private int movingPieceId = -1;
  private int movingPieceDirection;
  private double movingPieceStartingPosition;

 
  public PuzzlePage()
  {
    InitializeComponent();
    ...
}


3. 편집 창을 오른 클릭하고 View Designer 선택해 [디자인] 보기로 전환한다.

4. 디자이너 화면에서 에뮬레이터 이미지를 둘러싼 공간을 클릭해 PhoneApplicationPage  요소를 선택하고  F4 눌러 [속성] 창을 연다. [속성] 창에서 [이벤트] 탭을 클릭해 사용할 있는 이벤트 목록을 확인한다.

[그림 13] Microsoft Visual Studio 2010 Express for Windows Phone에서 이벤트 핸들러 만들기


5. ManipulationStarted 이벤트에 대한 이벤트 핸들러를 정의한다. 목록에서 해당 항목을 더블 클릭해 이벤트를 구독한다. 작업을 하면 비주얼 스튜디오는 이벤트 핸들러를 생성하고 코드 숨김 파일을 열어 생성한 해당 메서드 스텁을 표시한다. 다음의 강조한 코드를 PhoneApplicationPage_ManipulationStarted 메서드의 본문에 삽입한다.

private void PhoneApplicationPage_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
  if (this.game.IsPlaying && e.ManipulationContainer is Image && e.ManipulationContainer.GetValue(FrameworkElement.NameProperty).ToString().StartsWith("PuzzleImage_"))
  {
    int pieceIx = Convert.ToInt32(e.ManipulationContainer.GetValue(FrameworkElement.NameProperty).ToString().Substring(12));
    Canvas piece = this.FindName("PuzzlePiece_" + pieceIx) as Canvas;
    if (piece != null)
    {
      int totalPieces = this.game.ColsAndRows * this.game.ColsAndRows;
      for (int i = 0; i < totalPieces; i++)
      {
        if (piece == this.puzzlePieces[i] && this.game.CanMovePiece(i) > 0)
        {
          int direction = this.game.CanMovePiece(i);
          DependencyProperty axisProperty = (direction % 2 == 0) ? Canvas.LeftProperty : Canvas.TopProperty;
          this.movingPieceDirection = direction;
          this.movingPieceStartingPosition = Convert.ToDouble(piece.GetValue(axisProperty));
          this.movingPieceId = i;
          break;
        }
      }
    }
  }
}


ManipulationStarted 이벤트는 사용자가  UIElement 터치(또는 마우스로 클릭) 조작할 발생하며 MouseDown 이벤트와 유사하다 이벤트에 대한 핸들러는 게임이 현재 진행 중인지 여부와 이벤트가 발생한 퍼즐 조각을 나타내는 이미지 요소들 중의 하나를 확인하고, 그렇지 않으면 이벤트를 무시한다. 조각에 대한 해당 Canvas 요소를 찾은 게임의 로직을 호출 해당 조각이 움직일 있는지 여부를 결정한다. 이것이 발생하려면 인접한 슬롯이 해당 조각에 대해 존재해야 한다. 다음 선택된 조각을 저장하고 조각이 움직일 있는 /방향을 기록한다.

6. 다음으로 ManipulationDelta 이벤트에 대한 핸들러를 생성한다. 다시 한번 코드 편집 창을 오른 클릭하고 View Designer 선택해 [디자인] 보기로 전환한다. 다음 디자인 영역에서 PhoneApplicationPage 요소를 선택하고 F4 눌러 [속성] 창을 연뒤 Events 탭을 선택한다. 다음 이벤트 목록에서 ManipulationDelta 이벤트를 찾아 해당 항목을 더블 클릭해 이벤트를 구독하고 스텁 메서드를 선택한다. 코드 숨김 파일에서 다음 강조된 코드를 PhoneApplicationPage_ManipulationDelta 메서드의 본문에 삽입한다.

private void PhoneApplicationPage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
  if (this.movingPieceId > -1)
  {
    int pieceSize = ImageSize / this.game.ColsAndRows;
    Canvas movingPiece = this.puzzlePieces[this.movingPieceId];

// validate direction
    DependencyProperty axisProperty;
    double normalizedValue;

if (this.movingPieceDirection % 2 == 0)
    {
      axisProperty = Canvas.LeftProperty;
      normalizedValue = e.CumulativeManipulation.Translation.X;
    }
    else
    {
      axisProperty = Canvas.TopProperty;
      normalizedValue = e.CumulativeManipulation.Translation.Y;
    }

// enforce drag constraints
    // (top or left)
    if (this.movingPieceDirection == 1 || this.movingPieceDirection == 4)
    {
      if (normalizedValue < -pieceSize)
      {
        normalizedValue = -pieceSize;
      }
      else if (normalizedValue > 0)
      {
        normalizedValue = 0;
      }
    }
    // (bottom or right)
    else if (this.movingPieceDirection == 3 || this.movingPieceDirection == 2)
    {
      if (normalizedValue > pieceSize)
      {
        normalizedValue = pieceSize;
      }
      else if (normalizedValue < 0)
      {
        normalizedValue = 0;
      }
    }

// set position
    movingPiece.SetValue(axisProperty, normalizedValue + this.movingPieceStartingPosition);
  }
}


ManipulationDelta 이벤트는 사용자가 UIElement 조작하면서 손가락(또는 마우스 커서) 이동할 발생한다. 이벤트에 대한 핸들러는 현재 조각이 움직일지 여부를 확인한다. 움직일 있다면, 가능한 /방향과 델타 값을 잡아 낸다. 이렇게 하기 위해 해당 코드는 드래그 제약조건을 강제해서 가능한 경계 내에서 움직임을 검증해야 한다. 족각은 다른 조각들에 겹칠 수는 없다. 핸들러는 적절한 축에 대한 속성을 설정해서 해당 조각의 위치를 업데이트한다.

7. 마지막으로, 이전 단계를 반복하면서 이번에는 ManipulationCompleted 이벤트를 대신 선택한다. 코드 숨김 파일에서 다음의 강조한 코드를  PhoneApplicationPage_ManipulationCompleted 메서드의 본문에 집어 넣는다.

private void PhoneApplicationPage_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
  if (this.movingPieceId > -1)
  {
    int pieceSize = ImageSize / this.game.ColsAndRows;
    Canvas piece = this.puzzlePieces[this.movingPieceId];

// check for double tapping
    if (TimeSpan.FromTicks(DateTime.Now.Ticks - this.lastTapTicks).TotalMilliseconds < DoubleTapSpeed)
    {
      // force move
      this.game.MovePiece(this.movingPieceId);
      this.lastTapTicks = int.MinValue;
    }
    else
    {
      // calculate moved distance
      DependencyProperty axisProperty = (this.movingPieceDirection % 2 ==0) ?    
Canvas.LeftProperty : Canvas.TopProperty;

      double minRequiredDisplacement = pieceSize / 3;
      double diff = Math.Abs(Convert.ToDouble(piece.GetValue(axisProperty)) - this.movingPieceStartingPosition);

// did it get halfway across?
      if (diff > minRequiredDisplacement)
      {
        // move piece
        this.game.MovePiece(this.movingPieceId);
      }
      else
      {
        // restore piece
        this.AnimatePiece(piece, axisProperty, this.movingPieceStartingPosition);
      }
    }

    this.movingPieceId = -1;
    this.movingPieceStartingPosition = 0;
    this.movingPieceDirection = 0;
    this.lastTapTicks = DateTime.Now.Ticks;
  }
}


ManipulationCompleted 이벤트는 UIElement를 조작한 후 사용자가 화면에서 손가락을 (또는 마우스 버튼을 놓을 ) 발생하고 MouseUp 이벤트와 유사하다. 이벤트에 대한 핸들러는 퍼즐 조각이 현재 움직일지 여부를 확인한다. 움직일 있다면, 마지막 ManipulationCompleted 발생하는 시기를 결정하고 제공한 시간 간격은 DoubleTapSpeed 의해 지정된 보다 느리므로. 이벤트를 Double Tap 이벤트(또는 더블 클릭)으로 해석하고 해당 조각을 인접한 슬롯으로 이동한다. 그렇지 않으면 해당 조각에서 대상 위치로의 오프셋 거기를 계산하고. 조각이 적어도 자기 크기의 1/3 가까운 슬롯으로 옮겨갔다면, 해당 조각을 슬롯으로 이동을 완료한다. 그렇지 않다면 원리의 위치로 조각을 되돌린다.

8. 이전 단계를 완료한 , 해당 페이지의 [속성] 창을 열어 알맞은 이벤트 해늗러를 생성했는지를 확인한다. 해당 페이지에 대한 XAML 마크업을 살펴보면, PhoneApplicationPage  요소에 정의된 해당 특성들을 보게 것이다.

[그림 14] manipulation 이벤트 핸들러를 보여주는 속성


[
그림 15] 이벤트 구독을 보여주는 해당 페이지의 XAML 마크업


9. 추가된 멀티 터치에 대한 지원을 테스트하려면, [F5]를 눌러 애플리케이션을 빌드하고 에뮬레이터로 배포한다. 시작화면이 표시되기를 기다린 뒤 [START]를 클릭해 새로운 게임을 시작한다.

10. 보드의 조각을 드래그해서 퍼즐을 푼다. 조각을 움직이려면 해당 조각을 클릭하고 목적지로 드래그 한다. 조작을 더블클릭 하면 다음의 사용 가능한 슬롯으로 이동한다. 각 이동에 따라 퍼즐 보드 위의 카운터가 증가한다.

[그림 16] 퍼즐 풀기