Windows2012. 12. 26. 15:34

Using Tiles and Badges


타일은 시작메뉴상의 앱을 의미한다. 단순히 앱의 아이콘일뿐이다. 하지만 약간의 노력으로 앱의 상태나 어떤 동작을 의미하는 그림을 표시한다든가하는 작업을 타일에 추가해줄수 있다.

이 장에서는 어떻게 타일에 정보를 표시하는지에 대해 기술할것이다.

동적타일을 통해서 사용자가 앱을 실행하게 또는 만류(?)하게 할수 있다. 이는 서로 상충되는 의미이다. 재믿네..

사용자를 유인하기 위해 광고나 컨텐츠등을 표시되게 할 수 있고 이는 엔터테인먼트앱이나 뉴스같은 컨텐츠를 연계하는 앱에 적당하다.

실행중인 앱을 만류(?)하게 하는 동작은 물론 어색하겠지만 이는 UX면에서 중대한 요소이다.

내가 약속이나 해야할 일을 확인하기 위해 앱을 실행해야 한다는 것은 어떤 면에서는 굉장히 귀찮은 일이다. 중요한것은 내가 멀해야 하는가이다. 이는 사용자에게 중요한 정보만 타일에 노출함으로써 불필요하게 앱을 로딩하는 번거로움을 줄여준다.


이 두가지 목표는 아주 신중하게 선택되어야 한다. Metro app은 작고, 단순하며 직관적(불필요한작업을 중임)이다. 알아서 잘 기획해서 선택하여 사용하길... 주저리 주저리...


Improving Static Tiles


앱의 타일을 변경하면 가장 쉽게 시작메뉴에서 보여지는 애플리케이션의 모습을 개선할 수 있다. 타일의 다른 막강한 기능을 사용하지 않는다 하더라도 이작업은 하는것이 좋다.

이 작업은 3가지 사이즈의 이미지가 필요하다.


  30x30, 150x150, 310x150


로고와 텍스트를 포함하고 반투명하면 안된다.

VisualStudio project의 Asset폴더에 3가지 이미지를 작업하여 추가한다.

이미지를 적용하려면 package.appxmanifest 파일을 열어서 편집한다. Application UI탭의 Tile섹션에서 logo, wide logo 그리고 small logo에 이미지파일을 지정한다.


Creating Live Tiles


라이브타일은 사용자에 추가적인 정보를 제공한다. 데모에서는 grocery list의 몇개의 아이템에 대한 정보를 표시할것이다. 이 데모가 모든 라이브타일의 기능을 보여주지는 못하겠지만 데모수준은 된다.

타일업데이트는 미리설정된 템플릿기준으로 이루어진다. 템플릿은 그래픽과 텍스트의 조합으로 이루어져 있는데 표준과 와이드모드로 디자인되어 있다. 첫번째로 해야 하는것은 템플릿의 선택이다. Windows.UI.Notifications.TileTemplateType enum상수를 참조해서 선택하면 된다. 템플릿은 XML fragment로 작성되었고 XML구조도 확인할 수 있다. 여기서는 TileSquareText03 템플릿을 선택하였다. 이 템플릿은 사각형타일에 4라인의 nonwrapping text로 이미지는 없는 구성이다. XML fragment는 다음과 같다.


<tile>

 <visual lang="en-US">

   <binding template="TileSquareText03">

     <text id="1">Text Field 1</text>

     <text id="2">Text Field 2</text>

     <text id="3">Text Field 3</text>

     <text id="4">Text Field 4</text>

   </binding>

 </visual>

</tile>

원리는 애플리케이션에서 정보를 텍스트로 구성하고 그 결과를 Metro Tile notifications system에 전달하는 것이다. 나는 MainPage 클래스에서 타일업데이트를 설정하기를 원한다. 이는 앞에서 작성했던 코드의 리펙토링을 요구한다. ListPage에서 ViewModel을 다루었는데 이를 MainPage에서 하도록 변경해야 한다.

다음은 리팩토링된 MainPage 클래스코드이다.


using System;

using MetroGrocer.Data;

using Windows.Data.Xml.Dom;

using Windows.UI.Notifications;

using Windows.UI.Xaml;

using Windows.UI.Xaml.Controls;

using Windows.UI.Xaml.Controls.Primitives;

using Windows.UI.Xaml.Navigation;

namespace MetroGrocer.Pages {

  public sealed partial class MainPage : Page {

    private ViewModel viewModel;

    public MainPage() {

      this.InitializeComponent();

      viewModel = new ViewModel();

      // ...test data removed for brevity

      this.DataContext = viewModel;

      MainFrame.Navigate(typeof(ListPage), viewModel);

  viewModel.GroceryList.CollectionChanged += (sender, args) => {

     UpdateTile();

   };

   UpdateTile();

}


private void UpdateTile() {

  XmlDocument tileXml = TileUpdateManager.

    GetTemplateContent(TileTemplateType.TileSquareText03);

  XmlNodeList textNodes =

    tileXml.GetElementsByTagName("text");

  for (int i = 0; i < textNodes.Length &&

    i < viewModel.GroceryList.Count; i++) {

  textNodes[i].InnerText = viewModel.GroceryList[i].Name;

 }

  for (int i = 0; i < 5; i++) {

    TileUpdateManager.CreateTileUpdaterForApplication()

      .Update(new TileNotification(tileXml));

 }

}

protected override void OnNavigatedTo(NavigationEventArgs e) {

}

private void NavBarButtonPress(object sender, RoutedEventArgs e) {

  Boolean isListView = (ToggleButton)sender == ListViewButton;

  MainFrame.Navigate(isListView ? typeof(ListPage)

      : typeof(DetailPage), viewModel);

  ListViewButton.IsChecked = isListView;

  DetailViewButton.IsChecked = !isListView;

} }

}

Populating the XML Template


XML fragment template을 얻으려면 TileUpdateManager.GetTemplateContent메서드를 호출한다. 이 때 템플릿타입 상수로 타입을 결정한다. Windows.Data.Xml.Dom.XmlDocument객체를 얻을 수 있으며 DOM 메소드로 "text" 엘리먼트의 값을 변경할 수 있다. sort를 위해 GetElementById는 동작하지 않으며 그래서 GetElementByTagName을 사용하였다.


...

XmlNodeList textNodes = tileXml.GetElementsByTagName("text”);

...


텍스트 노드는 순서대로 리턴된다. 따라서 순서대로 다음과 같이 초기화 가능하다.


...

for (int i = 0; i < textNodes.Length && i < viewModel.GroceryList.Count; i++)

{

  textNodes[i].InnerText = viewModel.GroceryList[i].Name;

}

...


Applying the Tile Update


XML Document에 데이터를 초기화한 후 Application Tile을 위해 Update를 생성한 후 TimeUpdater 객체의 Update메소드에 인자로 넘긴다.


.

for (int i = 0; i < 5; i++) {

  TileUpdateManager.CreateTileUpdaterForApplication()

    .Update(new TileNotification(tileXml));

} ...


Calling the Tile Update Method


두가지 상태에서 UpdateTile 메소드를 호출하였다. 먼저 생성자에서 호출하여 앱구동시 데이터를 전달한다. 그리고 컨텐츠가 변경되었을 때 이다.


...

viewModel.GroceryList.CollectionChanged += (sender, args) => {

   UpdateTile();

};

...


CollectionChanged 이벤트는 item이 추가, 삭제, 변경될경우 호출된다.


Testing the Tile Update


시뮬레이터에서는 타입업데이트가 동작하지 않으므로 타겟을 Local Machine으로 맞춘 후 테스트해야 한다. 그렇지 않으면 테스트가 불가하다.



Updating Wide Tiles

사용자가 square 또는 wide tile중 멀 선택할지 모르기 때문에 두가지 다 지원하는 것이 좋다. 그를라믄 두개의 XML fragment를 하나의 fragment로 합쳐줄 필요가 있다. TileSquareText03과 TileWideBlockAndText01템플릿을 합쳐볼것이다. 다음은 그 예이다.


<tile>

   <visual lang="en-US">

     <binding template="TileSquareText03">

       <text id="1">Apples</text>

       <text id="2">Hotdogs</text>

       <text id="3">Soda</text>

       <text id="4"></text>

     </binding>

     <binding template="TileWideBlockAndText01">

       <text id="1">Apples (Whole Foods)</text>

       <text id="2">Hotdogs (Costco)</text>

       <text id="3">Soda (Costco)</text>

       <text id="4"></text>

       <text id="5">2</text>

       <text id="6">Stores</text>

     </binding>

   </visual>

</tile>


합쳐진 template을 다루는 API는 없다. 여기서 선택한 방법은 따로 핸들링하고 처리의 마지막에 합치는 방법이다. 아래 코드를 참조하라.


.

private void UpdateTile() {

    int storeCount = 0;

    List<string> storeNames = new List<string>();

    for (int i = 0; i < viewModel.GroceryList.Count; i++) {

      if (!storeNames.Contains(viewModel.GroceryList[i].Store)) {

        storeCount++;

        storeNames.Add(viewModel.GroceryList[i].Store);

      }

}

    XmlDocument wideTileXml = TileUpdateManager

      .GetTemplateContent(TileTemplateType.TileWideBlockAndText01);

      XmlNodeList narrowTextNodes = narrowTileXml.GetElementsByTagName("text");

      XmlNodeList wideTextNodes = wideTileXml.GetElementsByTagName("text");

      for (int i = 0; i < narrowTextNodes.Length

        && i < viewModel.GroceryList.Count; i++) {

        GroceryItem item = viewModel.GroceryList[i];

        narrowTextNodes[i].InnerText = item.Name;

    wideTextNodes[i].InnerText = String.Format("{0} ({1})", item.Name, item.Store);

    }

XmlDocument narrowTileXml = TileUpdateManager

      .GetTemplateContent(TileTemplateType.TileSquareText03);

} }

...

wideTextNodes[4].InnerText = storeCount.ToString();

wideTextNodes[5].InnerText = "Stores";

var wideBindingElement = wideTileXml.GetElementsByTagName("binding")[0];

var importedNode = narrowTileXml.ImportNode(wideBindingElement, true);

narrowTileXml.GetElementsByTagName("visual")[0].AppendChild(importedNode);

  for (int i = 0; i < 5; i++) {

      TileUpdateManager.CreateTileUpdaterForApplication()

.Update(new TileNotification(narrowTileXml));

}

}

...


Posted by 삼스