Windows Runtime 8.x에서 UWP로 포팅 사례 연구: QuizGame 샘플 앱

이 토픽은 작동하는 피어 투 피어 퀴즈 게임 WinRT 8.1 샘플 앱을 Windows 10 UWP(Universal Windows Platform) 앱으로 포팅하는 사례 연구를 제공합니다.

Universal 8.1 앱은 Windows 8.1에 대한 하나의 앱 패키지 및 Windows Phone 8.1에 대한 다른 하나의 앱 패키지라는 두 가지 버전의 동일한 앱을 빌드하는 앱입니다. WinRT 8.1 버전의 QuizGame은 Universal Windows 앱 프로젝트 정렬을 사용하지만, 다른 접근 방식을 취하며 두 플랫폼에 대해 기능적으로 고유한 앱을 빌드합니다. Windows 8.1 앱 패키지는 퀴즈 게임 세션의 호스트 역할을 하고 Windows Phone 8.1 앱 패키지는 호스트에 대한 클라이언트 역할을 합니다. 퀴즈 게임 세션에서 위와 같이 둘로 구분되며 피어 투 피어 네트워킹을 통해 통신합니다.

이 두 패키지를 각각 PC와 휴대폰에 조정하는 것이 좋습니다. 하지만 선택한 장치에서 호스트와 클라이언트를 모두 실행할 수 있다면 더 좋지 않을까요? 이 사례 연구는 두 앱을 Windows 10으로 포팅하여 사용자가 다양한 장치에 설치할 수 있는 단일 앱 패키지로 빌드합니다.

앱은 보기 및 보기 모델을 사용하는 패턴을 사용합니다. 이 깨끗한 분리의 결과로, 이 앱의 포팅 프로세스는 매우 간단합니다.

참고 이 샘플은 네트워크가 사용자 지정 UDP 그룹 멀티캐스트 패킷을 보내고 받도록 구성되어 있다고 가정합니다(대부분의 홈 네트워크는 회사 네트워크가 아닐 수 있음). 또한 샘플은 TCP 패킷을 보내고 받습니다.

 

참고 Visual Studio에서 QuizGame10을 열 때, “Visual Studio 업데이트 필요”라는 메시지가 표시되면 TargetPlatformVersion의 단계를 수행합니다.

 

다운로드

QuizGame Universal 8.1 앱을 다운로드합니다. 이는 포팅하기 전의 앱의 초기 상태입니다.

QuizGame10 Windows 10 앱을 다운로드합니다. 이는 포팅 직후의 앱의 상태입니다.

이 샘플의 최신 버전을 GitHub에서 참조하세요.

WinRT 8.1 솔루션

포팅하려는 QuizGame 앱의 모습은 다음과 같습니다.

the quizgame host app running on windows

Windows에서 실행되는 QuizGame 호스트 앱

the quizgame client app running on windows phone

Windows Phone에서 실행되는 QuizGame 클라이언트 앱

사용 중인 QuizGame의 연습

이는 사용 중인 앱의 간단한 가상 계정이지만, 무선 네트워크를 통해 직접 앱을 사용해 보려는 경우 유용한 정보를 제공합니다.

바에서 재미있는 퀴즈 게임이 벌어지고 있습니다. 바에는 모든 사람이 볼 수 있는 큰 TV가 있습니다. 출력이 TV에 표시되는 PC가 quizmaster에 있습니다. 해당 PC에 "호스트 앱"이 실행되고 있습니다. 퀴즈에 참여하려는 사람은 "클라이언트 앱"을 휴대폰 또는 Surface에 설치하기만 하면 됩니다.

호스트 앱은 로비 모드에 있으며, 대형 TV는 클라이언트 앱이 연결할 준비가 되었다고 알립니다. Joan은 클라이언트 앱을 모바일 장치에서 시작합니다. 플레이어 이름 텍스트 상자에 자신의 이름을 입력하고 게임 참가를 탭합니다. 호스트 앱은 Joan이 자신의 이름을 표시하여 합류했다는 것을 인지하고, Joan의 클라이언트 앱은 게임이 시작되기를 기다리고 있음을 나타냅니다. 다음으로, Maxwell은 자신의 같은 단계를 모바일 장치에서 수행합니다.

Quizmaster가 게임 시작을 클릭하면 문제 및 가능한 답변이 호스트 앱에 표시됩니다(일반 글꼴 무게, 색이 지정된 회색으로 조인된 플레이어 목록도 표시됨). 해당 답변은 조인된 클라이언트 장치의 버튼에 동시에 표시됩니다. Joan이 "1975"라는 대답이 있는 버튼을 탭하면 모든 버튼이 비활성화됩니다. Joan의 이름은 호스트 앱에서 녹색으로 칠해져 있으며 대답을 받는 것을 인지하여 대담해집니다. Maxwell도 대답합니다. Quizmaster는 모든 플레이어의 이름이 녹색임을 알리고 다음 문제를 클릭합니다.

문제는 이 같은 주기로 계속 질문과 대답을 합니다. 호스트 앱에 마지막 문제가 표시될 때, 결과 표시다음 문제가 아니라 버튼의 내용입니다. 결과 표시를 클릭하면 결과가 표시됩니다. 로비로 돌아가기를 클릭하면 참가한 플레이어가 계속 참가하는 예외를 제외하고 게임 수명 주기의 시작 부분으로 돌아갑니다. 그러나 로비로 돌아가는 것은 새로운 플레이어가 참가할 수 있는 기회가 되며 참가한 플레이어가 떠날 수 있는 편리한 시간이 됩니다(하지만 참가한 플레이어는 게임 떠나기를 탭하여 언제든지 떠날 수 있음).

로컬 테스트 모드

장치 간 분산되는 대신 단일 PC에서 앱과 해당 상호 작용을 시도하려면 로컬 테스트 모드에서 호스트 앱을 빌드할 수 있습니다. 이 모드는 네트워크 사용을 완전히 건너뜁니다. 대신 호스트 앱의 UI에는 창의 왼쪽에 호스트 부분이 표시되고, 오른쪽에는 클라이언트 앱 UI의 두 복사본이 세로로 누적됩니다(이 버전에서는 PC 디스플레이에 대해 로컬 테스트 모드 UI가 고정되며, 이는 작은 장치에 적응하지 않음). 이러한 UI 세그먼트는 모두 동일한 앱에 있으며, 모의 클라이언트 통신기를 통해 서로 통신하고 네트워크를 통해 수행되는 상호 작용을 시뮬레이션합니다.

로컬 테스트 모드를 활성화하려면 (프로젝트 속성에서) LOCALTESTMODEON을 조건부 컴파일 기호로 정의하고 다시 빌드합니다.

Windows 10 프로젝트로 포팅하기

다음과 같은 부분이 QuizGame에 있습니다.

  • P2PHelper. 이는 피어 투 피어 네트워킹 논리를 포함하는 포팅 가능한 클래스 라이브러리입니다.
  • QuizGame.Windows. 이는 Windows 8.1을 대상으로 하는 호스트 앱에 대한 앱 패키지를 빌드하는 프로젝트입니다.
  • QuizGame.WindowsPhone. 이는 Windows Phone 8.1을 대상으로 하는 클라이언트 앱에 대한 앱 패키지를 빌드하는 프로젝트입니다.
  • QuizGame.Shared. 이는 소스 코드, 태그 파일, 기타 자산과 리소스를 포함하는 프로젝트로, 다른 두 프로젝트 모두에서 사용되는 프로젝트입니다.

이 사례 연구는 지원할 장치와 관련하여 Universal 8.1 앱을 보유하고 있는 경우의 일반적인 옵션을 설명합니다.

이러한 옵션을 기반으로, QuizGameHost라는 이름의 새로운 Windows 10 프로젝트로 QuizGame.Windows를 포팅합니다. 또한 QuizGameClient라는 새로운 Windows 10 프로젝트로 QuizGame.WindowsPhone을 포팅합니다. 이러한 프로젝트는 유니버설 장치 패밀리를 대상으로 하므로, 모든 장치에서 실행됩니다. 또한 자체 폴더에 QuizGame.Shared 원본 파일 등을 보관하며, 두 개의 새 프로젝트에 이러한 공유 파일을 연결합니다. 이전과 마찬가지로 모든 것을 하나의 솔루션에 보관하고 QuizGame10이라고 이름을 지정합니다.

QuizGame10 솔루션

  • 새로운 솔루션(새 프로젝트>기타 프로젝트 형식>Visual Studio 솔루션)을 만들고 이름을 QuizGame10으로 지정합니다.

P2PHelper

  • 솔루션에서 새로운 Windows 10 클래스 라이브러리 프로젝트(새 프로젝트>Windows Universal>클래스 라이브러리(Windows Universal)를 만들고 이름을 P2PHelper로 지정합니다.
  • 새로운 프로젝트에서 Class1.cs를 삭제합니다.
  • 새 프로젝트의 폴더에 P2PSession.cs, P2PSessionClient.cs 및 P2PSessionHost.cs를 복사하고 복사한 파일을 새 프로젝트에 포함합니다.
  • 프로젝트는 추가적인 변경 없이 빌드됩니다.

공유 파일

  • \QuizGame.Shared\에서 Common, Model, View 및 ViewModel 폴더를 \QuizGame10\로 복사합니다.
  • Common, Model, View 및 ViewModel은 디스크의 공유 폴더를 참조하는 경우를 의미합니다.

QuizGameHost

  • 새로운 Windows 10 앱 프로젝트(추가>새 프로젝트>Windows Universal>빈 애플리케이션(Windows Universal))를 만들고 이름을 QuizGameHost로 지정합니다.
  • P2PHelper에 참조를 추가합니다(참조 추가>프로젝트>솔루션>P2PHelper).
  • 솔루션 탐색기에서 디스크의 각 공유 폴더에 대한 새 폴더를 만듭니다. 그런 다음, 방금 만든 각 폴더를 마우스 우클릭하고 추가>기존 항목을 클릭한 다음 폴더를 탐색합니다. 적절한 공유 폴더를 열고 모든 파일을 선택한 다음, 링크로 추가를 클릭합니다.
  • \\QuizGame.Windows\\의 MainPage.xaml을 \\QuizGameHost\\에 복사하고 QuizGameHost로 네임스페이스를 변경합니다.
  • \QuizGame.Shared\의 App.xaml을 \QuizGameHost\에 복사하고 QuizGameHost로 네임스페이스를 변경합니다.
  • app.xaml.cs를 덮어쓰는 대신 새로운 프로젝트에서 버전을 유지하고, 대상을 하나만 변경하기 위해 로컬 테스트 모드를 지원합니다. app.xaml.cs에서 다음의 코드 줄을 바꿉니다.
rootFrame.Navigate(typeof(MainPage), e.Arguments);

다음의 코드로 바꿉니다.

#if LOCALTESTMODEON
    rootFrame.Navigate(typeof(TestView), e.Arguments);
#else
    rootFrame.Navigate(typeof(MainPage), e.Arguments);
#endif
  • 속성>빌드>조건부 컴파일 기호에서 LOCALTESTMODEON을 추가합니다.
  • 이제 app.xaml.cs에 추가한 코드로 돌아가 TestView 형식을 해결할 수 있습니다.
  • package.appxmanifest에서, 기능 이름을 internetClient에서 internetClientServer로 변경합니다.

QuizGameClient

  • 새로운 Windows 10 앱 프로젝트(추가>새 프로젝트>Windows Universal>빈 애플리케이션(Windows Universal))를 만들고 이름을 QuizGameClient로 지정합니다.
  • P2PHelper에 참조를 추가합니다(참조 추가>프로젝트>솔루션>P2PHelper).
  • 솔루션 탐색기에서 디스크의 각 공유 폴더에 대한 새 폴더를 만듭니다. 그런 다음, 방금 만든 각 폴더를 마우스 우클릭하고 추가>기존 항목을 클릭한 다음 폴더를 탐색합니다. 적절한 공유 폴더를 열고 모든 파일을 선택한 다음, 링크로 추가를 클릭합니다.
  • \QuizGame.WindowsPhone\의 MainPage.xaml을 \QuizGameClient\에 복사하고 QuizGameClient로 네임스페이스를 변경합니다.
  • \QuizGame.Shared\의 App.xaml을 \QuizGameClient\에 복사하고 QuizGameClient로 네임스페이스를 변경합니다.
  • package.appxmanifest에서, 기능 이름을 internetClient에서 internetClientServer로 변경합니다.

이제 빌드 및 실행이 가능합니다.

적응형 UI

넓은 창에서 앱을 실행 중인 경우(큰 화면이 있는 장치에서만 가능), QuizGameHost Windows 10 앱이 잘 보입니다. 앱의 창이 좁은 경우(작은 장치에서 발생하며 큰 장치에서도 발생할 수 있음), UI가 너무 많이 스쿼시되어 읽을 수가 없습니다.

사례 연구: Bookstore2에서 설명한 대로, 이 문제를 적응형 Visual State Manager 기능을 사용하여 해결할 수 있습니다. 먼저, 시각적 개체 요소에 속성을 설정하여 기본적으로 UI가 좁은 상태로 배치되도록 합니다. 이러한 모든 변경 내용은 \View\HostView.xaml에서 발생합니다.

  • 기본 Grid에서 첫 번째 RowDefinition높이를 "140"에서 "Auto"로 변경합니다.
  • 이름이 pageTitle(으)로 지정된 TextBlock을 포함하는 Grid에서, x:Name="pageTitleGrid"Height="60"을(를) 설정합니다. 처음 두 단계는 시각적 개체 상태의 setter를 통해 해당 RowDefinition의 높이를 효과적으로 제어하기 위한 것입니다.
  • pageTitle에서 Margin="-30,0,0,0"을(를) 설정합니다.
  • <!-- Content --> 주셕 옆에 표시된 그리드에서, x:Name="contentGrid"Margin="-18,12,0,0"을(를) 설정합니다.
  • <!-- Options --> 주석 바로 위의 TextBlock에서 Margin="0,0,0,24"을(를) 설정합니다.
  • TextBlock 스타일 기본값(파일의 첫 번째 리소스)에서 FontSize setter의 값을 "15"로 변경합니다.
  • OptionContentControlStyle에서 FontSize setter의 값을 "20"으로 변경합니다. 이 단계 및 이전 단계는 모든 장치에서 잘 작동하는 좋은 형식 램프를 제공합니다. 이는 Windows 8.1 앱에 사용했던 "30"보다 훨씬 더 유연한 크기입니다.
  • 마지막으로, 적절한 Visual State Manager 태그를 루트 그리드에 추가합니다.
<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="WideState">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="548"/>
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="pageTitleGrid.Height" Value="140"/>
                <Setter Target="pageTitle.Margin" Value="0,0,30,40"/>
                <Setter Target="contentGrid.Margin" Value="40,40,0,0"/>
            </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

유니버설 스타일 지정

Windows 10에서는 버튼의 템플릿에 터치 대상 패딩이 동일하지 않음에 유의하세요. 이러한 문제는 두 가지 작은 변화로 해결할 수 있습니다. 먼저, QuizGameHost 및 QuizGameClient 모두에서 이 태그를 app.xaml에 추가합니다.

<Style TargetType="Button">
    <Setter Property="Margin" Value="12"/>
</Style>

두 번째로, 이 setter를 \View\ClientView.xaml의 OptionButtonStyle에 추가합니다.

<Setter Property="Margin" Value="6"/>

마지막 조정을 사용하면 앱이 동작하고 포팅 이전과 동일하게 표시되며, 이제 모든 곳에서 실행될 추가 값이 있습니다.

결론

이 사례 연구에서 포팅한 앱은 비교적 복잡한 앱으로, 여러 프로젝트, 클래스 라이브러리 및 상당히 많은 양의 코드 및 사용자 인터페이스를 포함합니다. 그럼에도 불구하고 포팅은 간단합니다. 포팅의 용이성 중 일부는 Windows 10 개발자 플랫폼과 Windows 8.1 및 Windows Phone 8.1 플랫폼 간의 유사성에 직접적으로 기인합니다. 일부는 원래 앱이 모델, 보기 모델 및 보기를 별도로 유지하도록 디자인된 방식 때문입니다.