본문 바로가기
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] 퍼즐 풀기