學習開發 Microsoft Band 的 App - Custom Layout

學習開發 Microsoft Band 的 App - Custom Layout

上一篇<學習開發 Microsoft Band 的 App>介紹開發 Microsoft Band App 的基本概念,

這一篇將針對更深入可以客製每一個 Tile 裡的 Pages 與 Layout。

 

〉CUSTOMIZING TILE LAYOUTS

  • 每一個 Tile 可包含最多 8 個 Page;
  • 當用戶點擊 Tile 時,第一個 Page 會顯示出來,再藉由手勢水平滑動切換到不同的 Page;
  • 每一個 Page 可建立自定的 Layout,最多可以 5 個 Layout;

 

藉由下列的範例畫面來說明:

image

  • (1) Back bar,固定有 40 px,永遠會出現,讓用戶可以離開 App 或回到 Start Strip。
  • (2) Content page,固定 245 px。
  • (3) 下一個 Page 的前端小部分,固定 35 px,提示用戶後面還有內容。
  • (4) 代表後面的內容,用戶可以藉由水平滑動讀取後面的內容。

 

要為了一個 Page 建立自定的 Layout ,首先要需知道有那些類型的控制項與容器。

 

〉Primitive Element Types

    用於呈現內容物,為 primitive visual element types,例如:按鈕、文字框、圖像…等。詳細如下:

Name Description Content Type
TextBlock Text that is clipped if too wide to fit within its bounds string
WrappedTextBlock Text that wraps if too wide to fit within its bounds string
Icon Single color bitmap consisting of alpha values controlling the opacity of each pixel Icon index (0-9)
Barcode Text encoded as a Code39 or PDF417 barcode
Code39:each character must be a valid Code 39 character: 0-9, A-Z, , ‘$’, ‘%’, ‘+’, ‘-‘, ‘.’, or ‘/’. The Band will allow a string of up to 39 characters to be assigned, but because of the Band screen width and resolution the effective upper limit that can be rendered is 12 characters.

PDF417:each character must be a digit (0-9). The maximum length of the string is 39 characters, but because of the Band screen width and resolution the effective upper limit that can be rendered is 20 characters.
string
TextButton White text label on black background button string
FilledButton Rectangular button filled with background color.
可以建立 Icon bitmap 放在 FilledButton 上面當成它的 mask,記得是背景透明的,讓 Button 被按下時可以切換不同的 Icon bitmap 。
color
  • TextButton 與 FilledButton 可註冊 events 來取得用戶點擊的事件,其他的 elements 就不行。
  • 一個 Layout 最多只可以放進 10 個 primitive elements

 

〉Container Element Types

    Layout 配置本身是個 tree 的概念,而 primitive elements 是葉節點,container elements 則是 根元素。

一個 Layout 只能有一個 container element。可用的元素有:

Name Description
FlowPanel Arranges its child elements sequentially on the page, either horizontally or vertically
ScrollFlowPanel Similar to FlowPanel with the ability to scroll between children that are not currently within the visible area
FilledPanel Rectangle filled with a background color
  • Container elements 可有 0 到多個 child elements;
  • child elements 可以是 FllowPanel、ScrollFlowPanel 或 primitive elements;
  • A FilledPanel can only be used as the root element of a layout, it cannot be used as a child of a FlowPanel or ScrollFlowPanel.
  • 一個 Layout 可被放入 container elements 與 primitive elements 總合最多 20 個;

 

每一個 elements 可使用的屬性:

Name Type
(default in bold)
Required Description Applicable Element Types
Rect PageRect 4 UInt16 (X, Y, Width, Height) Yes Define the X,Y location of the element’s upper left corner relative to its parent, and its width and height (in pixels) All
Margins Margins 4 Int16 (Left, Top, Right, Bottom) (0,0,0,0) No Define the margin sizes (in pixels) All
BackgroundColorSource ElementColorSource (Custom) No Specifies the color of the FilledPanel. If set to “Custom” then the color will be that of the “BackgroundColor” attribute. FilledPanel
BackgroundColor BandColor (Black) No The RGB color of the FilledPanel FilledPanel
ColorSource ElementColorSource (Custom) No Specifies the color of the core feature of the element. If set to “Custom” then the color will be that of the “Color” attribute. TextBlock, WrappedTextBlock, Icon, ScrollFlowPanel
Color BandColor (default varies depending on element type, see Colors) No The RGB color of the core feature of the element (e.g. text color) TextBlock, WrappedTextBlock, Icon, TextButton, FilledButton, ScrollFlowPanel
HorizontalAlignment HorizontalAlignment Left, Right, Centered No How content should be aligned horizontally within the element All
VerticalAlignment VerticalAlignment
Top, Bottom, Centered
No How content should be aligned vertically within the element All
Font TextBlockFont WrappedTextBlockFont
Small, Medium

TextBlock Only:
Large, ExtraLargeNumbers, ExtraLargeNumbersB old
Yes Specifies the set of glyphs to render a string.
Note:
Not all character codes have glyphs in each font. Characters with no corresponding glyph in the font will be rendered with a default placeholder glyph.
TextBlock, WrappedTextBlock
BaselineAlignment TextBlockBaselineAlign ment
Automatic, Relative
No “Automatic”: use the TextBlock’s Vertical Alignment attribute to position the text
“Relative”: The “Baseline” attribute will position the text baseline
TextBlock
Baseline Int16 (0) No If Baseline Alignment is “Relative”, then Baseline specifies the vertical offset (in pixels) from the top of the TextBlock at which to draw the string TextBlock
AutoWidth bool (true) No “true” causes the TextBlock to expand horizontally to fit the text content. “false” means the width of the TextBlock is fixed by the Rect attribute. TextBlock
AutoHeight bool (true) No “true” causes the WrappedTextBlock to expand vertically to fit the text content.
“false” means the height of the WrappedTextBlock is fixed by the Rect attribute.
WrappedTextBlock
Orientation Horizontal, Vertical No Defines whether child elements of the list are arranged horizontally or vertically FlowPanel, ScrollFlowPanel
ElementId Int16 No Defines a unique (within the layout) identifier, used when setting the element content and determining which button was pressed in a button event All

相關元素對應於畫面上的關係 ( Element Rectangle and Margins ) 如下圖:

image

建立一個 element 需要指定 Rect (“X,Y”) 、Width /Height 與 Margin 將它排列至指定的位置。

如果 element 是被放在 FlowPanel 或 ScrollFlowPanel 的話,要將 x, y 設定為 0,因為 parent 元素會自動排序 child elements

 

根據<Microsoft Band SDK Documentation>舉一個畫面配置之後的樹狀階層,如下:

1. 有一個 PageLayout 裡面放一個 ScrollFlowPanel;

2. 該 ScrollFlowPanel 內容使用 vertical 排列子元素,子元素放了二個 FlowPanel;

3. 二個 FlowPanel 分別放了一個 Icon 、WrappedTextBlock;

排列結果如下:

image

 

另外有幾個需要注意的排版:

a. Negative Margins

     當 element 的 parent 是 FlowPanel 或 ScrollFlowPanel,可設定  negative margins 為 element 重新定位它內容與 top 的對應。

例如:有一個 icon 想要疊在 button 上面,則需要這樣做:

  1. 建立一個 FlowPanel,並且給予二個 elements:
  2. FilledButton 的 width=100, height=50
  3. Icon 的 width of 80, height=50, left_margin=-90, right_margin=-90

設定 –90 則是讓 Icon 可以對於應 FlowPanel 的 left_margin,讓 Icon 重疊於 FilledButton 的 width = 100 之上。

 

b. Colors

     大部分的 elements 支援 ColorSource 與 Color 屬性,如果 ColorSource 需要用到自定義的,請使用 RGB 的值來設定。

預設它們可操作 6 種 Band themes colors 或 6 種 Tile Theme colors。其 Color 屬性對應於 element 預設顏色的影響:

Element Type Default Color Feature Controlled
TextBlock White Font color
WrappedTextBlock White Font color
Icon White Render color
TextButton Gray Button background color when pressed
FilledButton White Button background color when not pressed
FilledPanel Black Color for the rectangle background
ScrollFlowPanel White Scroll bar color

TextButton 與 FilledButton 不支援 ColorSource 屬性,只能藉由設定 Color 屬性給予 RGB 色碼。

 

c. Icons

When you create a Tile, you provide the normal (unbadged) and small (badged) icons that will be shown on the Start Strip.

When you create a custom layout for a tile, you can provide up to 8 additional icons that can be used within the layout.

You can create Icons from native bitmaps using the helper method provided in the Band SDK.

This helper method will use the alpha component of each pixel in the native bitmap to construct the Band Icon object.

The red, green, and blue components of each pixel in the native bitmap are ignored.

The alpha value of each pixel in the Icon is used to determine the transparency of the color rendered on the screen.

Each pixel of an Icon element will use the same color as set in the Icon element’s Color attribute in the layout.

The page content specifies which particular icon is displayed within an Icon element.

The content of an Icon element is the index value of the Icon to be displayed.

The normal and small tile Icons are at index values 0 and 1, respectively, and additional icons that were provided start at index value 2.

The layout can make use of any of these Icons.

 

 

藉由下列的範例來暸解如何 Custom Tile with Layouts:

建立一個 Tile 且有一個 PageLayout 內容,該 PageLayout 內容有二個 TextBlock 與一個 Barcode,搭配 FlowPanel 將它們裝起來,並且指定 PageData 更新內容資料。

往下便加以說明如何實作:

public async Task<Boolean> CreateCustomLayout1()
{
    // 建立一個 Band Tile
    if (MainTile == null)
    {
        // Band tile id 是唯一識別值
        MainTile = new BandTile(BandTileId)
        {
            TileIcon = await this.LoadIcon("ms-appx:///Assets/Band/Logo_46x46.png"),
            SmallIcon = await this.LoadIcon("ms-appx:///Assets/Band/Logo_24x24.png"),
            Name = "My badn tile"
        };
    }
    if (MainTile != null)
    {
        // 移除現有的 Band Tile
        await bandClient.TileManager.RemovePagesAsync(BandTileId);

 
        // 建立一個 page layout 並指定要顯示的內容物
        TextBlock myCardTextBlock = new TextBlock()
        {
            Color = Colors.Blue.ToBandColor(),
            // 每一個 element 的 id 是唯一識別值,也是填寫 page data 的識別值
            ElementId = 1,
            Rect = new PageRect(0, 0, 200, 25)
        };
        Barcode barcode = new Barcode(BarcodeType.Code39)
        {
            ElementId = 2,
            Rect = new PageRect(0, 0, 250, 50)
        };
        TextBlock digitsTextBlock = new TextBlock()
        {
            ElementId = 3,
            Rect = new PageRect(0, 0, 200, 25)
        };

 
        // 建立一個放置 TextBlock 的容器
        FlowPanel panel = new FlowPanel(myCardTextBlock, barcode, digitsTextBlock)
        {
            Orientation = FlowPanelOrientation.Vertical,
            Rect = new PageRect(0, 0, 250, 100)
        };

 
        // 為 Band Tile 建立一個 PageLayout 並且加入容器
        MainTile.PageLayouts.Add(new PageLayout(panel));

 
        // 建立新的 Band Tile
        await bandClient.TileManager.AddTileAsync(MainTile);

 
        PageData page = new PageData(
                // 每一個 PageData 更新也要給 GUID
                Guid.NewGuid(),
                // 指定 PageLayout 的 index,
                0,
                // 分別指定 Layout 中那些 element 的資料
                new TextBlockData(myCardTextBlock.ElementId.Value, "MY CARD"),
                new BarcodeData(barcode.BarcodeType, barcode.ElementId.Value, "123456789"),
                new TextBlockData(digitsTextBlock.ElementId.Value, "123456789"));

 
        await bandClient.TileManager.SetPagesAsync(MainTile.TileId, page);

 
        return true;
    }
    else
    {
        return false;
    }
}

在上述的程式範例裡,需要注意 SetPageLayout 後如果馬上去看 Band 裡建好的 BandTile 其內容會是空的,只會得到一段「Nothing new…check back in a few.」,

因為它需要等到與 SetPageData 後才會把內容顯示出來。所以建議在建立 BandTile 時把 SetPageLayout 後馬上給一個 PageData 當作預設畫面

 

從上述的範例列出以下幾個重要的元素:

〉PageLayout

    用於呈現 BandTile 中 Page 的樣式,它搭配 PagePanel 中所定義的 elements 來加以顯示。而 PagePanel 是個 Container type elements。

 

〉PageData

    屬於資料模組,建立時需要指定它是專屬於那一個 PageLayout 的 Index 才能更新。主要屬性如下:

Name Description
PageId 指定 PageData 的 Id。
PageLayoutIndex 指定該 PageData 屬於那一個 BandTile 中的 PageLayouts。
Values IList<PageElementData>,指定 PageLayout 中定義的那些 elements 要更新的資料內容。
PageElementData 中 ElementId 屬性需要與 PageLayout 建立內容 Element 時給予的 ElementId 要一致才能更新。

 

〉BandTile

     被建立於呈現在 Band 中可操作的單位,裡面可定義最多 8 個 Page (每一個 Page 可以最多 5 個 Layout),並且 TileIcon 也可以指定多種以支持各種用途。

以下是該類別重要的元素:

Name Description
AdditionalIcons Read-only。設定該 BandTile 可用的其他 TileIcon。
IsBadgingEnabled 設定該 BandTile 是否可以顯示 badge。
Name 設定該 BandTile 要顯示的名稱。
PageLayouts Read-only。它是一個集合,藉由加入/移除後的 Index 來操作,例如:要更新某一個 PageLayout 要指定它的 Index。
注意:PageLayouts 的 Add 是採用 Stack 的方式,所以第一個加入的會在畫面的最後一個。
SmallIcon 設定 BandTile 的 SmallTile,用於當 BandTile 支援 badge 的使用時,如下圖:
image
Theme 設定 BandTile 使用的 BandTheme。可自定義該 BandTile 要顯示的 BandColor (包括:Highlight、Lowlight、HighContrast、Muted、SecondaryText)。
TileIcon 設定顯示於 Band 中的主要 TileIcon。
TileId Read-only。 BandTile 的識別值,在  BandTile 建構子給予。

 

 

上述介紹如何建立 Band Tile 的 PageLayout 與指定 PageData 來更新其內容,接著往下討論如何捕捉 Band Tile 的事件與 PageLayout 中 Button 的事件。

〉TileManager

    負責補捉接收來自 BandTile 或 PageLayout 中 Button elements 被按下的事件,分別以下三種:

Type Name Description
Event TileButtonPressed 註冊當 Band Tile 被按下時的事件。
其中按下的事件可能來自 BandTile 或是 BandTile 中 PageLayout 裡的 Element。
BandTileEventArgs<IBandTileButtonPressedEvent>
  TileClosed 註冊當 Band Tile 被關閉時的事件。BandTileEventArgs<IBandTileClosedEvent>
  TileOpened 註冊當 Band Tile 被開啟時的事件。BandTileEventArgs<IBandTileOpenedEvent>
Method StartReadingsAsync 開始讀取 BandTile 的事件。
  StopReadingsAsync 關閉讀取 BandTile 的事件。

上述三個事件均會收到一個 TileEvent 裡面分別為 TileId,藉由得知是那一個 BandTile 被按到或是開啟/關閉,

其中 TileButtonPressed 則增加更多來識別事件來源是 BandTile 還是來自 BandTile 中的那一個 PageLayout 裡面的 Button。

這些事件需要藉由 StartReadingsAsync 與 StopReadingsAsync 開啟與關閉讀取才會被觸發

如下程式碼:

public async Task<String> RequestGetBandConnection()
{
    var bands = await BandClientManager.Instance.GetBandsAsync();
    if (bands == null || bands.Length == 0)
    {
        return "No pairing off Band";
    }
    else
    {
        bandClient = await BandClientManager.Instance.ConnectAsync(bands[0]);
        bandClient.TileManager.TileButtonPressed += TileManager_TileButtonPressed;
        bandClient.TileManager.TileClosed += TileManager_TileClosed;
        bandClient.TileManager.TileOpened += TileManager_TileOpened;
        // Start listening for events
        bandClient.TileManager.StartReadingsAsync();
        // Stop listening for events
        bandClient.TileManager.StopReadingsAsync();
        return "connnected";
    }
}

 
void TileManager_TileOpened(object sender, BandTileEventArgs<IBandTileOpenedEvent> e)
{
    if (OnBandTileEventNotify != null)
    {
        OnBandTileEventNotify(String.Format("tile.opened: {0}", e.TileEvent.TileId));
    }
}

 
void TileManager_TileClosed(object sender, BandTileEventArgs<IBandTileClosedEvent> e)
{
    if (OnBandTileEventNotify != null)
    {
        OnBandTileEventNotify(String.Format("tile.closed: {0}", e.TileEvent.TileId));
    }
}

 
void TileManager_TileButtonPressed(object sender, BandTileEventArgs<IBandTileButtonPressedEvent> e)
{
    if (OnBandTileEventNotify != null)
    {
        if (e.TileEvent.ElementId != 0)
        {
            // 代表來自 PageLayout 中的 Element,可根據不同的 Element Id 來指定要觸發的任務
        }
        else
        {
            OnBandTileEventNotify(String.Format("tile.ButtonPressed: {0}", e.TileEvent.TileId));
        }
    }
}

 
public delegate void BandTileEventNotify(String msg);
public event BandTileEventNotify OnBandTileEventNotify;

 

了解事件原理後,舉一個藉由 BandTile 中 Button 的事件來操作 App 播放/暫停音樂的任務。

[範例程式]

1. 建立一個 BandTile,建立二個 PageLayout

   1-1. 一個 PageLayout 有 三個按鈕 控制播放/暫停、上一首與下一首;

            image

   1-2. 一個 PageLayout 放置目前播放的歌曲名稱、播放時間;

            image

// need keep Id, because identiy.
private Guid musicTileId = new Guid("8b7172b4-a23b-4655-a67a-609b61d8512d");
private Guid ContronPanalPageId = new Guid("8b7172b4-a23b-1000-a67a-609b61d8512d");
private Guid SongInfoPageId = new Guid("8b7172b4-a23b-0000-a67a-609b61d8512d");

 
public async Task<String> AddMusicBandTile()
{
    try
    {
        await bandClient.TileManager.RemoveTileAsync(musicTileId);

 
        #region // first page, include control buttons
        TextButton playWithpauseBtn = new TextButton
        {
            ElementId = 1,
            Rect = new PageRect(0, 30, 70, 56),
            PressedColor = new BandColor(0xB7, 0xB7, 0xB7)
        };
        TextButton previousBtn = new TextButton
        {
            ElementId = 2,
            Rect = new PageRect(0, 30, 70, 56),
            PressedColor = new BandColor(0xB7, 0xB7, 0xB7)
        };
        TextButton nextBtn = new TextButton
        {
            ElementId = 3,
            Rect = new PageRect(0, 30, 70, 56),
            Margins = new Margins(10, 0, 10, 0),
            PressedColor = new BandColor(0xB7, 0xB7, 0xB7)
        };
        FlowPanel firstPanel = new FlowPanel
        {
            Orientation = FlowPanelOrientation.Horizontal,
            Rect = new PageRect(0, 0, 250, 60),
            VerticalAlignment = VerticalAlignment.Center
        };
        firstPanel.Elements.Add(playWithpauseBtn);
        firstPanel.Elements.Add(nextBtn);
        firstPanel.Elements.Add(previousBtn);

 
        TextBlock controlTitle = new TextBlock
        {
            ElementId = 0,
            Rect = new PageRect(0, 0, 100, 30),
            Margins = new Margins(0, 5, 0, 5),
            Font = TextBlockFont.Medium,
            Color = new BandColor(0x8B, 0x61, 0xF2)
        };
        FlowPanel firstRoot = new FlowPanel(controlTitle, firstPanel)
        {
            Orientation = FlowPanelOrientation.Vertical,
            Rect = new PageRect(10, 0, 250, 100)
        };
        PageLayout firstPage = new PageLayout(firstRoot);
        #endregion

 
        #region // second page, include textblock display song name and position
        TextBlock songNameBlock = new TextBlock
        {
            ElementId = 0,
            Rect = new PageRect(5, 0, 200, 50),
            Margins = new Margins(0, 0, 0, 5),
            Font = TextBlockFont.Medium,
            ColorSource = ElementColorSource.BandLowlight,
        };
        TextBlock posisitionBlock = new TextBlock
        {
            ElementId = 1,
            Rect = new PageRect(0, 0, 100, 30),
            Margins = new Margins(0, 0, 0, 10),
            Font = TextBlockFont.Small,
            ColorSource = ElementColorSource.BandLowlight,
            Color = new BandColor(0x3A, 0x78, 0xDD)
        };

 
        FlowPanel secondPanel = new FlowPanel
        {
            Orientation = FlowPanelOrientation.Vertical,
            Rect = new PageRect(10, 0, 250, 100),
            VerticalAlignment =  VerticalAlignment.Center
        };
        secondPanel.Elements.Add(songNameBlock);
        secondPanel.Elements.Add(posisitionBlock);

 
        PageLayout secondPage = new PageLayout(secondPanel);
        #endregion

 
        BandTile musicBandTile = new BandTile(musicTileId)
        {
            TileIcon = await this.LoadIcon("ms-appx:///Assets/Band/Logo_46x46.png"),
            SmallIcon = await this.LoadIcon("ms-appx:///Assets/Band/Logo_24x24.png"),
            Name = "Music BandTile"
        };
        musicBandTile.PageLayouts.Clear();
        // stack mode, first page need lasted be added.
        musicBandTile.PageLayouts.Add(secondPage);
        musicBandTile.PageLayouts.Add(firstPage);

 
        //add music BandTile
        await bandClient.TileManager.AddTileAsync(musicBandTile);

 
        PageData pagedata1 = new PageData(
                ContronPanalPageId, 1,
                new TextBlockData(0, "Control Panel"),
                new TextButtonData(1, "  > "),
                new TextButtonData(2, " >> "),
                new TextButtonData(3, " << "));

 
        PageData pagedata2 = new PageData(
                SongInfoPageId, 0,
                new TextBlockData(0, "No Song"),
                new TextBlockData(1, "--:--"));

 
        await bandClient.TileManager.SetPagesAsync(musicTileId, new List<PageData> { pagedata2, pagedata1 });
        return "add music band tile completed.";
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
}

上述程式裡碼裡最為重要的就是 Tile Id 與 2 個 PageData 的 PageId,因為這些是識別值如果每次都用 GUID.New() 那你會有很多 Page

更要注意,BandTile 的 PageLayouts 增加時是採用 Stack 的模式,如果要呈現在畫面的第一個 Page 那一定要最後一個加入。

 

2. 在 BackgroundTask 增加處理 Windows Phone 的 背景播放音樂,並且給予監聽 BandTile 的事件

     2-1. 建立一個 Windows Phone 專用的 BackgroundTask for Audio,並且在 Run 時取得 Band connection;

public async void Run(IBackgroundTaskInstance taskInstance)
{
    systemControl = SystemMediaTransportControls.GetForCurrentView();

 
    systemControl.ButtonPressed -= SystemControls_ButtonPressed;
    systemControl.ButtonPressed += SystemControls_ButtonPressed;
    systemControl.IsPreviousEnabled = true;
    systemControl.IsNextEnabled = true;
    systemControl.IsPauseEnabled = true;
    systemControl.IsPlayEnabled = true;

 
    taskInstance.Canceled -= TaskInstance_Canceled;
    taskInstance.Canceled += TaskInstance_Canceled;
    taskInstance.Task.Completed -= Task_Completed;
    taskInstance.Task.Completed += Task_Completed;

 
    BackgroundMediaPlayer.MessageReceivedFromForeground -= BackgroundMediaPlayer_MessageReceivedFromForeground;
    BackgroundMediaPlayer.MessageReceivedFromForeground += BackgroundMediaPlayer_MessageReceivedFromForeground;

 
    BackgroundTaskStarted.Set();
    // 負責管理有多少佇列的歌曲
    trackManager = new SongTrackManager();
    // Get Band Connect and subscript event
    await BandManagerClient.Instance.RequestGetBandConnection();
    BandManagerClient.Instance.OnBandTileEventNotify += OnBandButtonEvent;
    // 用來更新 position 的 timer
    positionTimer = new Timer(positionTimer_Tick, null, -1, -1);
    positionTimer.Change(TimeSpan.Zero, TimeSpan.FromSeconds(1));

 
    deferral = taskInstance.GetDeferral();
}

             在 Run 裡馬上就取得 Connection 是不好的,因為一個 App 同時只能占住一個連線,如果同時取二次連線會造成 exception

  

     2-2. 建立取得來自 App 送來更新現在播放 index 與 註冊 timer 來更新 position:

private void positionTimer_Tick(object state)
{
    // 如果正在播放,通知 Band 更新 position
    if (BackgroundMediaPlayer.Current.CurrentState == MediaPlayerState.Playing)
    {
        String position = String.Format("{0:00}:{1:00}", BackgroundMediaPlayer.Current.Position.Minutes,
                                                         BackgroundMediaPlayer.Current.Position.Seconds);
        BandManagerClient.Instance.SetPositionToMusicTile(trackManager.currentSongName, position);
    }
}
async void BackgroundMediaPlayer_MessageReceivedFromForeground(object sender, MediaPlayerDataReceivedEventArgs e)
{
    if (e.Data != null && e.Data.Count > 0)
    {
        foreach (var key in e.Data.Keys)
        {
            switch (key)
            {
                case "play_index":
                    // 收到來自更新 foreground 現在播放的 index
                    trackManager.SetCurrentIndex(Int32.Parse(e.Data[key].ToString()));
                    break;
                case "request_band_connect":
                    // 建議把取得 band 的連線移動到這地方
                       break;
                case "remove_band_connect":
                    // 用戶回到 App 時就應該把 connection 權限交給前景
                       break;
                default:
                    break;
            }
        }
    }
}

             建議由 Foreground App 來通知 Background Task 是否要向 Band 取得連線或斷掉連線,這樣才不會造成重覆取得連結的問題。

 

     2-3. 註冊處理來自 BandTile 觸發 ButtonPress 的事件:

private void OnBandButtonEvent(String msg)
{
    switch (msg)
    {
        case "changePlayWithPause":
            if (BackgroundMediaPlayer.Current.CurrentState == MediaPlayerState.Playing)
            {
                BackgroundMediaPlayer.Current.Pause();
                BandManagerClient.Instance.SetPlayButtonStatusToMusicTile("||");
            }
            else
            {
                BackgroundMediaPlayer.Current.Play();
                BandManagerClient.Instance.SetPlayButtonStatusToMusicTile(">");
            }                    
            break;
        case "next":
            BackgroundMediaPlayer.Current.SetUriSource(trackManager.NextSong());
            break;
        case "previous":
            BackgroundMediaPlayer.Current.SetUriSource(trackManager.PreviousSong());
            break;
        default:
            break;
    }
}

 
public async Task<String> RequestGetBandConnection()
{
    if (bandClient == null)
    {
        var bands = await BandClientManager.Instance.GetBandsAsync();
        if (bands == null || bands.Length == 0)
        {
            return "No pairing off Band";
        }
        else
        {
            try
            {
                bandClient = await BandClientManager.Instance.ConnectAsync(bands[0]);
                bandClient.TileManager.TileButtonPressed += TileManager_TileButtonPressed;
                bandClient.TileManager.TileClosed += TileManager_TileClosed;
                bandClient.TileManager.TileOpened += TileManager_TileOpened;
                // Start listening for events
                bandClient.TileManager.StartReadingsAsync();
                // Stop listening for events
                //bandClient.TileManager.StopReadingsAsync();
            }
            catch (Exception ex)
            {
            }
            return "connnected";
        }
    }
    else
    {
        return "exist connection";
    }
}

 
void TileManager_TileOpened(object sender, BandTileEventArgs<IBandTileOpenedEvent> e)
{
    if (OnBandTileEventNotify != null)
    {
        OnBandTileEventNotify(String.Format("tile.opened: {0}", e.TileEvent.TileId));
    }
}

 
void TileManager_TileClosed(object sender, BandTileEventArgs<IBandTileClosedEvent> e)
{
    if (OnBandTileEventNotify != null)
    {
        OnBandTileEventNotify(String.Format("tile.closed: {0}", e.TileEvent.TileId));
    }
}

 
void TileManager_TileButtonPressed(object sender, BandTileEventArgs<IBandTileButtonPressedEvent> e)
{
    if (OnBandTileEventNotify != null)
    {
        if (e.TileEvent.ElementId != 0)
        {
            // 代表來自 PageLayout 中的 Element,可根據不同的 Element Id 來指定要觸發的任務
            String msg = String.Empty;
            switch (e.TileEvent.ElementId)
            {
                case 1:
                    msg = "changePlayWithPause";
                    break;
                case 2:
                    msg = "next";
                    break;
                case 3:
                    msg = "previous";
                    break;
                default:
                    break;
            }
            OnBandTileEventNotify(msg);
        }
        else
        {
            OnBandTileEventNotify(String.Format("tile.ButtonPressed: {0}", e.TileEvent.TileId));
        }
    }
}

           如果你擔心上述的處理機制還是會造成 connect 重覆取得的話,可以在 Tile 被 Close 的時候自動關掉連線也是可以的。

           該段程式識別被按下的 element Id 來觸發對應的任務交給 BackgroundTask 來操作 BackgroundMediaPlayer 的播放。

 

    2-4. 指定更新 BandTile 的 播放進度 與 控制鈕:

private Guid musicTileId = new Guid("8b7172b4-a23b-4655-a67a-609b61d8512d");
private Guid ContronPanalPageId = new Guid("8b7172b4-a23b-1000-a67a-609b61d8512d");
private Guid SongInfoPageId = new Guid("8b7172b4-a23b-0000-a67a-609b61d8512d");

 
public async void SetPositionToMusicTile(String songName, String positionStr)
{
    // 找到指定的 BandTile 才可以操作
    var existTiles = await bandClient.TileManager.GetTilesAsync();
    BandTile musicTile = null;
    foreach (var item in existTiles)
    {
        if (item.TileId == musicTileId)
        {
            musicTile = item;
            break;
        }
    }
    if (musicTile != null)
    {
        // 更新播放資訊的 song name 與 position
        PageData positionPageData = new PageData(
            SongInfoPageId, 0, new TextBlockData(0, songName), new TextBlockData(1, positionStr));
        await bandClient.TileManager.SetPagesAsync(musicTileId, positionPageData);
    }
}

 
public async void SetPlayButtonStatusToMusicTile(String type)
{
    // 找到指定的 BandTile 才可以操作
    var existTiles = await bandClient.TileManager.GetTilesAsync();
    BandTile musicTile = null;
    foreach (var item in existTiles)
    {
        if (item.TileId == musicTileId)
        {
            musicTile = item;
            break;
        }
    }
    if (musicTile != null)
    {
        // 更新 Control Panel 的 播放/暫停 按鈕
        PageData statusPage = new PageData(
            ContronPanalPageId, 1, new TextButtonData(1, String.Format("  {0} ", type)));
        await bandClient.TileManager.SetPagesAsync(musicTileId, statusPage);
    }
}

 

上述範例是實作於同時支持 App 與 BackgroundTask 的使用,但是建議不要拿到真的 Band 裡使用,

因為一直更新 BandTile 裡的 Page 是很花電力的,建議想其他的實際用途。

 

[補充]

a. 在操作 BandTheme 或 BandColor 可以比照下圖來做配置:

    image

    image

======

以上分享了相關 Custom Layout 與如何補捉 BandTile 開啟/開關與按下的事件,希望對自己的 App 可以搭配 Band 操作的話可以有所幫助。

另外有關於 Theme 或是 Personalized 的操作可以參考官方的文件<Microsoft Band SDK>。謝謝。

 

References:

Experience design guidelines

Get started with the Microsoft Band SDK.

Microsoft Band SDK Documentation

Introduzione a Band Studio per Visual Studio

Microsoft Band Development

Dotblogs Tags: