2017 年 12 月

第 32 卷,第 12 期

本文章是由機器翻譯。

通用 Windows 平台 - Windows 10 新版漢堡功能表的開發人員指南

Jerry 小明

Windows XAML 小組發行 NavigationView 控制項與 Windows 10 年秋季建立者更新。之前的控制項,開發人員負責實作漢堡 」 功能表只限於 SplitView 控制項的基本功能的。產生的介面已視覺化簡報和行為不一致。即使第一方 Microsoft 應用程式,例如 Groove、 Xbox、 新聞和郵件喜歡的使用方式視覺化對齊產品組合。通常發生內部問題磁碟機外部方案,使用 NavigationView 以下的案例。

控制項提供 XAML 開發人員全新美麗視覺效果,跨裝置具有完整支援彈性調整、 當地語系化、 存取範圍,以一致的方式實作和簽章 Windows 經驗例如 Microsoft 的新 fluent 應用程式開發設計系統。控制項是美觀,和使用者會繫結遺失無止盡的小時,就重複叫用控制項的選取項目動畫的產能。它是 mesmerizing。圖 1顯示 NavigationView 基本樣式。

NavigationView Basic 樣式
圖 1 NavigationView Basic 樣式

基本知識

NavigationView 加入應用程式很簡單。一般而言,NavigationView 是在專用的頁面中,例如 ShellPage.xaml。只需要幾行的 XAML 提供功能表、 按鈕和處理常式來回應一般使用者動作。下列程式碼,請參閱在 MenuItems NavigationViewItem:

<NavigationView SelectionChanged="SelectionChanged">
  <NavigationView.MenuItems>
    <NavigationViewItemHeader Content="Section A" />
    <NavigationViewItem Content="Item 01" />
    <NavigationViewItem Content="Item 02" />
  </NavigationView.MenuItems>
  <Frame x:Name="NavigationFrame" />
</NavigationView>

這些是主要導覽按鈕。支援的其他項目是 「 NavigationViewItemHeader 和 NavigationViewItemSeparator,這兩者,放在一起,讓開發人員撰寫美觀且精緻的功能表。有幾件事,您應該會想到當您完成 [導覽] 檢視。

剖析NavigationView 的部分所示圖 2。每個區域建置出完整的經驗頁首、 頁尾] 窗格,自動建議及設定] 按鈕是選擇性的視您的應用程式的設計需求而定。

NavigationView 的組件
圖 2 NavigationView 組件

模式控制項有三種可能的模式:最小,壓縮及擴充,如中所示圖 3。每一個都是自動選取內建和自訂檢視臨界值為基礎。模式可讓維持 NavigationView 可用且實用的應用程式或裝置的變更大小。

NavigationView 模式
圖 3 NavigationView 模式

實際問題

NavigationView 是簡單明瞭,但不是一定容易實作,尤其是內容中的複雜,真實世界案例。控制項可為每個開發人員使用大小寫通常需要進行一些 clear-headed 編碼。以下是取消期間的問題和項目開發人員必須能夠辨識。我將會處理每個這些本文稍後。

資料繫結個人,我認為次數如此頻繁資料繫結功能表項目至最上層導覽控制項,但我不知道所有開發人員同意。為了這個目的,從程式碼後置 NavigationView 繫結是相當簡單。從 [檢視模型繫結必須中斷參考 UI 命名空間這類的基本規則。

瀏覽開發人員可能會感到驚訝 NavigationView 不巡覽。它是只視覺化功能可見性的導覽。它不會有框架,或了解其功能表項目應有的行為。開發人員必須解決的第一件事是與某個邏輯周圍重新載入網頁的簡單導覽。

上一頁按鈕Windows 10 提供殼層繪製一步] 按鈕,在某些情況下,選擇性的但需要在其他 tablet 模式類似。[上一頁] 按鈕儲存畫布不動產,並建立向後巡覽的整合的點。附加至通用 WinRT BackRequested 事件是直接的但同步處理 NavigationView 的選取項目是另一個需求。

設定按鈕NavigationView 提供預設值,[功能表] 窗格底部的當地語系化的設定按鈕。它是出色。按鈕會建立單一、 標準的點,常見的使用者動作的引動過程。它是排序的項目設計工具和開發人員應該從了解並快速為了以視覺方式對齊 UX 採用跨越生態系統。

[設定] 按鈕的實作既簡單又乾淨的但是它是另一個需求就不會傳遞現成 NavigationView。問題出在每個 XAML 開發人員想来宣告控制項的行為,而不是它的程式碼。

標頭項目NavigationView MenuItem 屬性會接受用來以視覺方式書夾按鈕 NavigationViewItemHeader 物件,特別是磁碟分割 NavigationViewItems 很有用。但是,開頭和結尾 NavigationView 的功能表] 窗格會截斷標頭的內容。開發人員必須能夠控制功能表外觀和窄和寬模式中的結構。

真實世界的解決方案

XAML 開發人員有數個工具解決問題。繼承自控制項可讓開發人員擴充其行為 (bit.ly/2gQ4vN4),擴充方法會增強甚至密封控制項的基底實作 (bit.ly/2ik1rfx) 和附加屬性可以擴大控制項的功能 (bit.ly/2giDGAn),即使支援 XAML 中宣告。

資料繫結自 2006,當 XAML 小組上市它時,模型-檢視-ViewModel (MVVM) 已親愛的 XAML 開發人員而言,包括 Microsoft 的第一方應用程式模式。設計模式的一個原則,就是避免依賴上和 UI 中檢視模型的命名空間的參考。有許多原因,這是智慧。下列程式碼片段所示,NavigationView 支援的資料繫結 NavigationViewItems MenuItemsSource 屬性類似於 ListView.ItemsSource,但無法對它進行 UI 命名空間。很好的程式碼後置,但檢視模型所解決的問題:

public IEnumerable<object> MenuItems
{
  get
  {
    return new[]
    {
      new NavigationViewItem { Content = "Home" },
      new NavigationViewItem { Content = "Reports" },
      new NavigationViewItem { Content = "Calendar" },
    };
  }
}

以端步驟參考 Windows.UI.Xaml.Controls 我的檢視模型中,我抽象至 DTO NavigationViewItem。我可以重複此程序針對每個潛在的對等物件。每個項目序數位置檢視模型的責任,並且應該維持檢視邏輯。這些抽象概念是簡單且容易檢視模型提供,這段程式碼所示:

public class NavItemEx
{
  public string Icon { get; set; }
  public string Text { get; set; }
}
public class NavItemHeaderEx
{
  public string Text { get; set; }
}
public class NavItemSeparatorEx { }

不過,NavigationView 並不知道我的自訂類別,而需要轉換成正確的 NavigationView 控制項轉譯。繫結至自訂類別需要強制轉譯,所以我們將可避免這在 NavigationView 中重要的自訂程式碼。注意:我故意避免的自訂範本,因此我不小心不要小孩子協助工具或錯過範本在後續的平台的版本中的增強功能。為了方便轉換,請在介紹我可以在 [我的 XAML 繫結中參考的值轉換器。圖 4顯示的程式碼負責接受我列舉的自訂類別,且傳回 NavigationView 預期的物件。

圖 4 NavItems 轉換器

public class INavConverter : IvalueConverter
{
  public object Convert(object v, Type t, object p, string l)
  {
    var list = new List<object>();
    foreach (var item in (v as Ienumerable<object>))
    {
      switch (item)
      {
        case NavItemEx dto:
          list.Add(ToItem(dto));
          break;
        case NavItemHeaderEx dto:
          list.Add(ToItem(dto));
          break;
        case NavItemSeparatorEx dto:
          list.Add(ToItem(dto));
          break;
      }
    }
    return list;
  }
  object IvalueConverter.ConvertBack(object v, Type t, object p, string l)
    throw new NotImplementedException();
  NavigationViewItem ToItem(NavItemEx item)
    new NavigationViewItem
    {
      Content = item.Text,
      Icon = ToFontIcon(item.Icon),
    };
  FontIcon ToFontIcon(string glyph)
    new FontIcon { Glyph = glyph, };
  NavigationViewItemHeader ToItem(NavItemHeaderEx item)
    new NavigationViewItemHeader { Content = item.Text, };
  NavigationViewItemSeparator ToItem(NavItemSeparatorEx item)
    new NavigationViewItemSeparator { };
}

在參考此轉換器當做全應用程式或網頁層級的資源之後, 的語法不像其他任何轉換器一樣簡單。我想要花一點時間傳達如何瘋狂我認為將資料繫結的最上層瀏覽,但此延伸的方案合作無間,示這裡:

 

MenuItemsSource=”{x:Bind ViewModel.Items, Converter={StaticResource NavConverter}}”

瀏覽瀏覽通用 Windows 平台 (UWP) 開頭 XAML 框架。但是,NavigationView 沒有在範圍內。此外,沒有任何方法,來宣告具有功能表按鈕,也就是說,我意圖頁面我想要開啟。這是容易解決的 XAML 附加屬性,如下所示:

public partial class NavProperties : DependencyObject
{
  public static Type GetPageType(NavigationViewItem obj)
    => (Type)obj.GetValue(PageTypeProperty);
  public static void SetPageType(NavigationViewItem obj, Type value)
    => obj.SetValue(PageTypeProperty, value);
  public static readonly DependencyProperty PageTypeProperty =
    DependencyProperty.RegisterAttached("PageType", typeof(Type),
      typeof(NavProperties), new PropertyMetadata(null));
}

PageType NavigationViewItem 上之後,我可以宣告在 XAML 中的 [目標] 頁面,或將它繫結至我的檢視模型。注意:我可以加入額外的參數和 TransitionInfo 屬性如果我的設計需要它。此範例著重於基本的瀏覽實作。我可讓處理導覽,擴充的 NavigationView 中所示,然後圖 5

圖 5 NavViewEx,擴充的 NavigationView

public class NavViewEx : NavigationView
{
  Frame _frame;
  public Type SettingsPageType { get; set; }
  public NavViewEx()
  {
    Content = _frame = new Frame();
    _frame.Navigated += Frame_Navigated;
    ItemInvoked += NavViewEx_ItemInvoked;
    SystemNavigationManager.GetForCurrentView()
      .BackRequested += ShellPage_BackRequested;
  }
  private void NavViewEx_ItemInvoked(NavigationView sender,
    NavigationViewItemInvokedEventArgs args)
  {
    if (args.IsSettingsInvoked)
      SelectedItem = SettingsItem;
    else
      SelectedItem = Find(args.InvokedItem.ToString());
  }
  private void Frame_Navigated(object sender, NavigationEventArgs e)
    => SelectedItem = (e.SourcePageType == SettingsPageType)
       ? SettingsItem : Find(e.SourcePageType) ?? base.SelectedItem;
  private void ShellPage_BackRequested(object sender, BackRequestedEventArgs e)
    => _frame.GoBack();
  NavigationViewItem Find(string content)
    => MenuItems.OfType<NavigationViewItem>()
      .SingleOrDefault(x => x.Content.Equals(content));
  NavigationViewItem Find(Type type)
    => MenuItems.OfType<NavigationViewItem>()
      .SingleOrDefault(x => type.Equals(x.GetValue(NavProperties.PageTypeProperty)));
  public virtual void Navigate(Frame frame, Type type)
    => frame.Navigate(type);
  public new object SelectedItem
  {
    set
    {
      if (value == SettingsItem)
      {
        Navigate(_frame, SettingsPageType);
        base.SelectedItem = value;
        _frame.BackStack.Clear();
      }
      else if (value is NavigationViewItem i && i != null)
      {
        Navigate(_frame, i.GetValue(NavProperties.PageTypeProperty) as Type);
        base.SelectedItem = value;
        _frame.BackStack.Clear();
      }
      UpdateBackButton();
      }
  }
  private void UpdateBackButton()
  {
    SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
      (_frame.CanGoBack) ? AppViewBackButtonVisibility.Visible
        : AppViewBackButtonVisibility.Collapsed;
  }
}

查看圖 5 ,您會注意到四個重要增強功能。其中一個,XAML 框架注入控制具現化。兩個,已針對 Frame.Navigated、 ItemInvoked 和 BackRequested 新增處理常式。三個,SelectedItem 已新增上一頁堆疊和 BackButton 邏輯覆寫。和四個,新的 SettingsPageType 屬性已加入至類別。

上一頁按鈕新的明確框架不只為了方便起見,提供了我的來源導覽事件。這很重要。NavigationView 叫用時瀏覽,更新的可見性殼層繪製一步] 按鈕。應使用者瀏覽另一種方法,不過,我不知道更新沒有某種事件的 [上一頁] 按鈕。Frame.Navigated 事件是極佳,通用的選擇。

尋找NavigationView ItemInvoked 事件的非預期的行為會是自訂的事件引數中傳遞的 InvokedItem 屬性 NavigationViewItem 和不在項目本身的物件參考的字串內容。如此一來,此自訂控制項中的 Find 方法找出正確的 NavigationViewItem ItemInvoked 中傳遞的內容為基礎或 PageType 傳遞 Frame.Navigated 事件。

值得注意的 NavigationViewItem 內容可以使用裝置上的當地語系化設定動態變更。處理 ItemInvoked 硬式編碼,switch 陳述式,在線上文件中所示 (bit.ly/2xQodCM) 會僅適用於英文喇叭或需要以指數方式展開的語言加入至參數支援的 UWP 應用程式。請試著避免 magic 數字和您的程式碼中的任何位置的 magic 字串。不是具有重大的程式碼基底相容。

設定設定是唯一的按鈕參與 NavigationView 的選取項目邏輯在下方功能表窗格中。叫用,使用者瀏覽至 [設定] 頁面。若要簡化該實作,請注意自訂 SettingsPageType 屬性,其中包含所要的目標頁面類型的設定。覆寫的 SelectedItem setter 測試的 [設定] 按鈕,並因此巡覽宣告。

什麼是 NavigationViewItem PageType 屬性中未處理或 SettingsPageType 屬性以表示畫面格的巡覽方法的自訂 TransitionInfo 強制巡覽期間的轉換資訊。這可以是任何應用程式中,重要的自訂,而且無法加入其他自訂或附加的屬性,以允許針對這個額外的指令。若要完成此程式碼看起來像這樣:

<local:NavViewEx SettingsPageType="views:SettingsPage">
  <NavigationView.MenuItems>
    <NavigationViewItem Content="Item 01"
      local:NavProperties.PageType="views:Page01" />
    <NavigationViewItem Content="Item 02"
      local:NavProperties.PageType="views:Page02" />
    <NavigationViewItem Content="Item 03"
      local:NavProperties.PageType="views:Page03" />
  </NavigationView.MenuItems>
</local:NavViewEx>

這種擴充性可讓開發人員積極地擴充的控制項和類別的行為,而不會變更其基本的基礎實作。它是 C# 和 XAML 已經有年,而且可以讓程式碼撰寫簡短的語法和 XAML 宣告的純文字的功能。它是一個直覺式方法轉譯給其他開發人員清楚地以小指令。

起始頁應用程式載入時,任何功能表項目一開始會叫用。加入另一個附加的屬性,如下所示,可讓我宣告我在 XAML 中的對應方式,讓擴充的 NavigationView 可以初始化其框架中的第一頁。以下是此屬性:

public partial class NavProperties : DependencyObject
{
  public static bool GetIsStartPage(NavigationViewItem obj)
    => (bool)obj.GetValue(IsStartPageProperty);
  public static void SetIsStartPage(NavigationViewItem obj, bool value)
    => obj.SetValue(IsStartPageProperty, value);
  public static readonly DependencyProperty IsStartPageProperty =
    DependencyProperty.RegisterAttached("IsStartPage", typeof(bool),
      typeof(NavProperties), new PropertyMetadata(false));
}

在 NavigationView 中使用這個新的屬性是要尋找內 MenuItems NavigationViewItem 啟動屬性設定,然後瀏覽至它,當控制項已成功載入。這個邏輯是選擇性的支援的設定,但不是需要它,如下所示:

Loaded += (s, e) =>
{
  if (FindStart() is NavigationViewItem i && i != null)
    Navigate(_frame, i.GetValue(NavProperties.PageTypeProperty) as Type);
};
NavigationViewItem FindStart()
  => MenuItems.OfType<NavigationViewItem>()
    .SingleOrDefault(x => (bool)x.GetValue(NavProperties.IsStartPageProperty));

請注意,第一次我 FindStart 方法,而不是其選取器的同層級中的 LINQ SingleOrDefault 選取器使用。其中是 FirstOrDefault 會傳回第一個找到,SingleOrDefault 擲回例外狀況應該多個探索到其述詞。這有助於引導並甚至強制執行開發人員使用方式的屬性,因為應該曾經宣告只能有一個初始頁面。

頁面標頭中所示圖 2,NavigationView 標頭不是選擇性。這個區域上方 48px,固定高度的頁面,適用於通用的內容。實作簡單的標題就這麼簡單程式碼片段,標頭屬性附加至的頁面物件:

public partial class NavProperties : DependencyObject
{
  public static string GetHeader(Page obj)
    => (string)obj.GetValue(HeaderProperty);
  public static void SetHeader(Page obj, string value)
    => obj.SetValue(HeaderProperty, value);
  public static readonly DependencyProperty HeaderProperty =
    DependencyProperty.RegisterAttached("Header", typeof(string),
      typeof(NavProperties), new PropertyMetadata(null));
}

與畫面格的 Navigated 事件,NavViewEx 會尋找 [結果] 頁面中的屬性選擇性的值插入至 NavigationView 的標頭。新連接屬性可以只限於個別頁面,並透過 UWP X:uid 當地語系化子系統當地語系化的頁面。中的程式碼圖 6示範如何更新標頭有效地介紹只有兩個新的行程式碼擴充的控制項。

圖 6 更新標頭

private void Frame_Navigated(object sender,
  Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
  SelectedItem = Find(e.SourcePageType);
  UpdateHeader();
}
private void UpdateHeader()
{
  if (_frame.Content is Page p
    && p.GetValue(NavProperties.HeaderProperty) is string s
    && !string.IsNullOrEmpty(s))
  {
    Header = s;
  }
}

在這個簡單的範例被接受預設標頭中的 TextBlock。我的經驗,以及 corroborated 由 Microsoft 的第一方、 附隨應用程式,CommandBar 控制項通常會使用此寶貴螢幕使用空間。如果我想讓相同我的應用程式中,無法更新 HeaderTemplate 屬性與這個簡單的標記:

<NavigationView.HeaderTemplate>
  <DataTemplate>
    <CommandBar>
      <CommandBar.Content>
        <Grid Margin="12,5,0,11" VerticalAlignment="Stretch">
          <TextBlock Text="{Binding}"
            Style="{StaticResource TitleTextBlockStyle}"
            TextWrapping="NoWrap" VerticalAlignment="Bottom"/>
        </Grid>
      </CommandBar.Content>
    </CommandBar>
  </DataTemplate>
</NavigationView.HeaderTemplate>

該 TextBlock 樣式模擬控制項的預設標頭,將其放在全域可用的命令列、 頁面的頁面或全域內容上的應用程式可以透過程式設計方式來實作。如此一來,基本的設計是以視覺化方式相同,但其功能可能會大幅擴充。

窄的項目標頭問題

會保留一個問題。如這篇文章早所述,NavigationView 會有不同的顯示模式而異根據檢視的寬度。它也可以明確地開啟和關閉 [功能表] 窗格。開啟 [功能表] 窗格時,會將其寬度取決 OpenPaneLength 屬性的值。無法取得我使用長度而寬度不該屬性名稱上啟動。總之,以下是重要的部分:該屬性值不會關閉; 時影響功能表窗格的寬度在已關閉狀態,窗格的寬度會是硬式編碼至 48px 寬。

在這裡,NavigationViewItems 看起來很棒設 48px 很寬,其圖示,但 NavigationViewItemHeaders 只有一個內容屬性,和是相同的窗格是否開啟或關閉。當開啟時,很吸引人,文字會截斷關閉時,所示圖 7

在開啟和 (窄) NavigationViewHeader 關閉狀態
圖 7 NavigationViewHeader 在開啟和 (窄) 已關閉狀態

該怎麼辦?首次將圖示加入至標頭的思考但關閉窗格時它看起來會像 NavigationViewItem,但並未回應點選出現異常和可能令人沮喪行為。我以為關於替代文字,但內 48px 幾乎三個字元的空間。我最後登陸隱藏標頭窗格關閉時,如下列程式碼片段所示:

RegisterPropertyChangedCallback(IsPaneOpenProperty, IsPaneOpenChanged);
private void IsPaneOpenChanged(DependencyObject sender,
  DependencyProperty dp)
{
  foreach (var item in MenuItems.OfType<NavigationViewItemHeader>())
  {
    item.Opacity = IsPaneOpen ? 1: 0;
  }
}

在此情況下,變更其可見性,因此無法移動任何突然出現的清單中的項目。這不是只有簡單實作,它也是以視覺化方式愉快,且有些直覺式而發生的原因。開啟或關閉的事件,則不會公開 NavigationView,因為您註冊使用 RegisterPropertyChangedCallback,方便公用程式與 Windows 8 引進 IsPaneOpenProperty 相依性屬性變更。我要識別回呼,並切換每個標頭。如果我想要我無法將不同的標頭視為不同的方式;這個範例會處理所有標頭相同。

總結

什麼是美觀的通用 Windows 平台與 XAML 相關是豐富的問題的解決方案。控制項不符合每位開發人員的需求。沒有可用的 API 符合每個設計的需求。建置豐富的平台,以及廣泛愛其開發人員會變成方案為置放的程式碼和功夫意外的障礙。它可讓您以唯一的價值主張,設定您的應用程式生態系統中建立您自己的簽章經驗。現在,即使漢堡功能表是您的外觀及操作的每個角落周圍的擴充功能的機會簡單新增。


Jerry 小明是作者、 喇叭、 開發人員和福音傳播者 Colorado 中的。他定型和 inspires 世界各地的開發人員建置更好的應用程式,以製作的程式碼。小明花費大量的空閒時間教導他的三個女兒難題字元 backstories 和時段繪製。

非常感謝下列技術專家檢閱這篇文章:Daren 年
Daren 可能是由四年 Windows 開發 MVP,而執行 CustomMayd、 開發人員訓練和自訂開發的公司。


MSDN Magazine 論壇中的這篇文章的討論