zoukankan      html  css  js  c++  java
  • Win10 UWP 开发系列:使用SplitView实现汉堡菜单及页面内导航

    在Win10之前,WP平台的App主要有枢轴和全景两种导航模式,我个人更喜欢Pivot即枢轴模式,可以左右切换,非常方便。全景视图因为对设计要求比较高,自己总是做不出好的效果。对于一般的新闻阅读类App来说,Pivot更适合多个频道的展示,因为内容基本都是一样的。

    到了Win10,微软模仿其他平台也推出了汉堡菜单,但并没有提供现成的控件,而是需要开发者通过一个名为SplitView的控件来实现。我个人并不觉得左上角的菜单有多么方便,汉堡菜单的使用必然会改变以前的导航模式,比如以前底部的AppBar使用很频繁,现在可以通过汉堡菜单的按钮来切换不同的页面。因此之前的App的导航模式需要重新设计。

    假设有A、B、C三个平行的页面,可以在每个页面的左侧都放个汉堡菜单,也可以像web的框架页一样,做一个壳,汉堡菜单只放在外面的框架里,点击不同的按钮,在content里实现不同页面的导航。我比较倾向第二种,之前在做澎湃新闻uwp的时候就使用了这种方式,后来看了下Template10的模板,也是用的这种方式,在主页面外层套了一个Frame,而且还实现 了一个汉堡菜单控件。有兴趣的同学可以参考Template10来快速生成一个带汉堡菜单的基础App,Github地址:https://github.com/Windows-XAML/Template10 ,这个项目还带了很多好东西,比如一些常用的帮助类和一些behavior等,值得uwp开发者好好学习。

    我没有直接使用T10的模板,以下介绍的还是当时使用MVVM-Sidekick框架实现的页面内导航。

    首先通过MVVM-Sidekick提供的项目模板来新建一个UWP项目,命名为NavDemo。

    考虑我们要实现的目的:在主页面放置一个汉堡菜单,在右侧的content中实现不同页面的导航。

    先来看一下效果:

    PC版:

    手机版:

    一、创建菜单项类

    汉堡菜单每个选项一般是由一个图标和一个文字组成,我还是使用FontAwesomeFont这个字体来显示图标,如何使用这个字体来做图标,可参考我之前的blog。首先建立一个菜单的类NavMenuItem,放在Models目录下,使用provm代码段生成两个属性:

    public class NavMenuItem : BindableBase<NavMenuItem>

    {

    /// <summary>

    /// FontAwesomeFontFamily

    /// </summary>

    public string Glyph

    {

    get { return _GlyphLocator(this).Value; }

    set { _GlyphLocator(this).SetValueAndTryNotify(value); }

    }

    #region Property string Glyph Setup

    protected Property<string> _Glyph = new Property<string> { LocatorFunc = _GlyphLocator };

    static Func<BindableBase, ValueContainer<string>> _GlyphLocator = RegisterContainerLocator<string>("Glyph", model => model.Initialize("Glyph", ref model._Glyph, ref _GlyphLocator, _GlyphDefaultValueFactory));

    static Func<string> _GlyphDefaultValueFactory = () => { return default(string); };

    #endregion

     

    /// <summary>

    ///文字

    /// </summary>

    public string Label

    {

    get { return _LabelLocator(this).Value; }

    set { _LabelLocator(this).SetValueAndTryNotify(value); }

    }

    #region Property string Label Setup

    protected Property<string> _Label = new Property<string> { LocatorFunc = _LabelLocator };

    static Func<BindableBase, ValueContainer<string>> _LabelLocator = RegisterContainerLocator<string>("Label", model => model.Initialize("Label", ref model._Label, ref _LabelLocator, _LabelDefaultValueFactory));

    static Func<string> _LabelDefaultValueFactory = () => { return default(string); };

    #endregion

     

    }

     

    打开NavDemoViewModelsMainPage_Model.cs,使用propvm代码段生成一个列表:

    public ObservableCollection<NavMenuItem> NavMenuItemList

    {

    get { return _NavMenuItemListLocator(this).Value; }

    set { _NavMenuItemListLocator(this).SetValueAndTryNotify(value); }

    }

    #region Property ObservableCollection<HamburgerMenuItem> NavMenuItemList Setup

    protected Property<ObservableCollection<NavMenuItem>> _NavMenuItemList = new Property<ObservableCollection<NavMenuItem>> { LocatorFunc = _NavMenuItemListLocator };

    static Func<BindableBase, ValueContainer<ObservableCollection<NavMenuItem>>> _NavMenuItemListLocator = RegisterContainerLocator<ObservableCollection<NavMenuItem>>("NavMenuItemList", model => model.Initialize("NavMenuItemList", ref model._NavMenuItemList, ref _NavMenuItemListLocator, _NavMenuItemListDefaultValueFactory));

    static Func<ObservableCollection<NavMenuItem>> _NavMenuItemListDefaultValueFactory = () => default(ObservableCollection<NavMenuItem>);

    #endregion

     

    在vm的构造函数里,添加几个项:

    public MainPage_Model()

    {

    if (IsInDesignMode )

    {

    Title = "Title is a little different in Design mode";

    }

    NavMenuItemList = new ObservableCollection<NavMenuItem>();

    NavMenuItemList.Add(new NavMenuItem { Glyph = "uf015", Label = "首页" });

    NavMenuItemList.Add(new NavMenuItem { Glyph = "uf002", Label = "搜索" });

    NavMenuItemList.Add(new NavMenuItem { Glyph = "uf05a", Label = "关于" });

    }

     

    注意Glyph的赋值方式。

     二、显示汉堡菜单

    在项目中新建Resources目录,把FontAwesome.otf字体文件放在里面。在项目中新建CustomTheme目录,然后建立自定义的样式资源文件CustomStyles.xaml,代码如下:

    <ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="using:NavDemo">

    <FontFamily x:Key="FontAwesomeFontFamily">/Resources/FontAwesome.otf#FontAwesome</FontFamily>

     

    <Style x:Key="SplitViewTogglePaneButtonStyle" TargetType="ToggleButton">

    <Setter Property="FontSize" Value="20" />

    <Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />

    <Setter Property="MinHeight" Value="48" />

    <Setter Property="MinWidth" Value="48" />

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

    <Setter Property="Padding" Value="0" />

    <Setter Property="HorizontalAlignment" Value="Left" />

    <Setter Property="VerticalAlignment" Value="Top" />

    <Setter Property="HorizontalContentAlignment" Value="Center" />

    <Setter Property="VerticalContentAlignment" Value="Center" />

    <Setter Property="Background" Value="Transparent" />

    <Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />

    <Setter Property="Content" Value="&#xE700;" />

    <Setter Property="AutomationProperties.Name" Value="Menu" />

    <Setter Property="UseSystemFocusVisuals" Value="True"/>

    <Setter Property="Template">

    <Setter.Value>

    <ControlTemplate TargetType="ToggleButton">

    <Grid Background="{TemplateBinding Background}" x:Name="LayoutRoot">

    <VisualStateManager.VisualStateGroups>

    <VisualStateGroup x:Name="CommonStates">

    <VisualState x:Name="Normal" />

    <VisualState x:Name="PointerOver">

    <Storyboard>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(Grid.Background)">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightListLowBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    </Storyboard>

    </VisualState>

    <VisualState x:Name="Pressed">

    <Storyboard>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(Grid.Background)">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightListMediumBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    </Storyboard>

    </VisualState>

    <VisualState x:Name="Disabled">

    <Storyboard>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="(TextBlock.Foreground)">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    </Storyboard>

    </VisualState>

    <VisualState x:Name="Checked"/>

    <VisualState x:Name="CheckedPointerOver">

    <Storyboard>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(Grid.Background)">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightListLowBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    </Storyboard>

    </VisualState>

    <VisualState x:Name="CheckedPressed">

    <Storyboard>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(Grid.Background)">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightListMediumBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltBaseHighBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    </Storyboard>

    </VisualState>

    <VisualState x:Name="CheckedDisabled">

    <Storyboard>

    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="(TextBlock.Foreground)">

    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}"/>

    </ObjectAnimationUsingKeyFrames>

    </Storyboard>

    </VisualState>

    </VisualStateGroup>

    </VisualStateManager.VisualStateGroups>

    <ContentPresenter x:Name="ContentPresenter"

    Content="{TemplateBinding Content}"

    Margin="{TemplateBinding Padding}"

    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"

    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

    AutomationProperties.AccessibilityView="Raw" />

    </Grid>

    </ControlTemplate>

    </Setter.Value>

    </Setter>

    </Style>

    </ResourceDictionary>

     

    然后打开App.xaml文件,把这个资源引用进来:

    <Application.Resources>

    <ResourceDictionary>

    <ResourceDictionary.MergedDictionaries>

    <ResourceDictionary Source="CustomTheme/CustomStyles.xaml"/>

    </ResourceDictionary.MergedDictionaries>

    </ResourceDictionary>

    </Application.Resources>

     

    样式资源文件里主要定义了两个样式,一是定义了FontAwesomeFontFamily字体,二是定义了一个针对ToggleButton的按钮样式SplitViewTogglePaneButtonStyle,作为汉堡菜单的开关。这个开关键为什么要设置高度为48呢?参考https://msdn.microsoft.com/zh-cn/library/windows/apps/dn997787.aspx

    拆分视图控件具有一个可展开/可折叠的窗格和一个内容区域。内容区域始终可见。窗格可以展开和折叠或停留在打开状态,而且可以从应用窗口的左侧或右侧显示其自身。窗格中有三种模式:

    • 覆盖

      在打开之前隐藏窗格。在打开时,窗格覆盖内容区域。

    • 内联

      窗格始终可见,并且不会覆盖内容区域。窗格和内容区域划分可用的屏幕实际使用面积。

    • 精简

      在此模式下窗格始终可见,它仅足够宽以显示图标(通常 48 epx 宽)。窗格和内容区域划分可用的屏幕实际使用面积。尽管标准精简模式不覆盖内容区域,但它可以转化为更宽的窗格来显示更多内容,这将覆盖该内容区域。

     

    所以我就根据官方文档设置为48了。

    修改MainPage.xaml,把根Grid改为以下代码:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" DataContext="{StaticResource DesignVM}">

    <!-- Top-level navigation menu + app content -->

    <SplitView x:Name="RootSplitView" IsPaneOpen="True"

    DisplayMode="Inline"

    OpenPaneLength="256"

    IsTabStop="False">

    <SplitView.Pane>

    <!-- A custom ListView to display the items in the pane. The automation Name is set in the ContainerContentChanging event. -->

     

    <ListView ItemsSource="{Binding NavMenuItemList}">

    </ListView>

    </SplitView.Pane>

     

     

    <SplitView.Content>

    <Frame x:Name="mainFrame">

    </Frame>

    </SplitView.Content>

    </SplitView>

     

    <!-- Declared last to have it rendered above everything else, but it needs to be the first item in the tab sequence. -->

    <ToggleButton x:Name="TogglePaneButton"

    TabIndex="1"

    Style="{StaticResource SplitViewTogglePaneButtonStyle}"

    IsChecked="{Binding IsPaneOpen, ElementName=RootSplitView, Mode=TwoWay}"

     

    AutomationProperties.Name="Menu"

    ToolTipService.ToolTip="Menu" />

    </Grid>

     

    为了方便查看菜单展开的效果,暂时先把IsPaneOpen属性设置为true,OpenPaneLength设置的是菜单展开后的宽度。在Pane里放一个ListView,ItemSource绑定到之前做好的NavMenuItemList上。SplitView的Content设置为一个Frame,用来展示右侧的页面。

     

    注意,如果当SplitView的Content直接设置为Frame的时候,也就是把外层的<SplitView.Content>去掉后,会报一个错:

    这个错误可以不用理会,程序是可以正常运行的。

     

    此外 还要有一个按钮来控制菜单的展开关闭状态,用一个ToggleButton来实现,这个按钮的图标一般是三个横杠,设置其Style为SplitViewTogglePaneButtonStyle即可。

    然后,还要设置ListView的项模板,可以使用Blend来设计项模板,但因为这个比较简单,我就直接手写了,在Resources目录下添加一个资源文件CustomDataTemplates.xaml,项目所有的自定义模板都可以写在这里,代码如下:

    <ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"

    xmlns:Core="using:Microsoft.Xaml.Interactions.Core"

    xmlns:Behaviors="using:MVVMSidekick.Behaviors">

     

    <DataTemplate x:Key="NavMenuItemTemplate" >

    <Grid>

    <Grid.ColumnDefinitions>

    <ColumnDefinition MinWidth="48" />

    <ColumnDefinition />

    </Grid.ColumnDefinitions>

    <FontIcon x:Name="Glyph" FontFamily="{StaticResource FontAwesomeFontFamily}" FontSize="16" Margin="0" Glyph="{Binding Glyph}" VerticalAlignment="Center" HorizontalAlignment="Center" ToolTipService.ToolTip="{Binding Label}"/>

    <TextBlock x:Name="Text" Grid.Column="1" Text="{Binding Label}" VerticalAlignment="Center"/>

    </Grid>

    </DataTemplate>

    </ResourceDictionary>

     

    在这里定义一个项模板NavMenuItemTemplate,在里面放一个FontIcon,把Glyph属性绑定到NavMenuItem的Glyph属性,当然不要忘了把FontFamily设置为我们在自定义样式里定义好的FontAwesomeFontFamily,不然是不会生效的。

    再把这个项模板应用到页面的ListView控件上:

    ItemTemplate="{StaticResource NavMenuItemTemplate}"

     

    现在跑一下试试,报错了:

    原来忘了把刚才的模板文件引入进来,修改App.xaml,修改为以下的样子:

    <Application.Resources>

    <ResourceDictionary>

    <ResourceDictionary.MergedDictionaries>

    <ResourceDictionary Source="CustomTheme/CustomStyles.xaml"/>

    <ResourceDictionary Source="Resources/CustomDataTemplates.xaml" />

    </ResourceDictionary.MergedDictionaries>

    </ResourceDictionary>

    </Application.Resources>

     

    现在可以运行了:

    貌似左上角的按钮跟ListView重叠了,这样可不好看。

    三、调整显示效果

    左上角的按钮应用了SplitViewTogglePaneButtonStyle样式,最小高度为48,把ListView往下移动一点,添加一个Margin属性,顶部把开关按钮的空间空出来:

    <ListView Margin="0,48,0,0" ItemsSource="{Binding NavMenuItemList}"

    ItemTemplate="{StaticResource NavMenuItemTemplate}">

    现在列表位置正常了,但图标的位置貌似还是偏右了,那就再给ListView设置ItemContainerStyle样式,在CustomStyles.xaml文件里添加以下代码:

    <Style x:Key="NavMenuItemContainerStyle" TargetType="ListViewItem">

    <Setter Property="MinWidth" Value="{StaticResource SplitViewCompactPaneThemeLength}"/>

    <Setter Property="Height" Value="48"/>

    <Setter Property="Padding" Value="0"/>

    </Style>

    ListView应用此样式:

    <ListView Margin="0,48,0,0" ItemsSource="{Binding NavMenuItemList}"

    ItemTemplate="{StaticResource NavMenuItemTemplate}"

    ItemContainerStyle="{StaticResource NavMenuItemContainerStyle}">

    </ListView>

     

    再跑一下:

    现在样式正常了。

    四、增加新页面

    现在MainPage.xaml只是一个壳,右侧内容是空的,下面来添加几个页面。在项目里添加几个页面,比如可以命名为HomePage、SearchPage、AboutPage等:

    因为每个页面里已经默认添加了一个TextBlock,并且绑定到了vm的Title属性,这个属性默认取值就是当前页面的Name,所以我们就不用改了,知道当前页面是哪个就行了。

    现在的问题是,如何在MainPage载入时,自动在SplitView的Content里显示HomePage呢?

    这就需要用到MVVM-Sidekick的一个Behavior了,用Blend打开项目,找到行为:

    有一个叫做BaeconBehavior的行为,把它拖到……咦,怎么找不到Content呢?

     

    那就直接手写吧,把Frame部分的代码改成这样:

    <SplitView.Content>

    <Frame x:Name="mainFrame" mvvm:StageManager.Beacon="frameMain" x:FieldModifier="public">

     

    </Frame>

    </SplitView.Content>

     

    StageManager.Beacon属性是用来标识StageManager,MVVM-Sidekick已经把导航的功能封装到了StageManager里,以前我们一般使用this.StageManager.DefaultStage.Show(xxx)的方式来使用,即可实现整个页面的导航,如果要实现页面内某个区域的导航,就需要手动指定是哪个StageManager了,这就需要使用以下属性来标识某个区域:

    mvvm:StageManager.Beacon="frameMain"

     

    找到OnBindedViewLoad方法,取消默认的注释,将该方法改为以下的样子:

    protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)

    {

    await base.OnBindedViewLoad(view);

    await StageManager["frameMain"].Show(new HomePage_Model());

    }

     

    这里要注意,一定要等Bind完成后再Show,不然会显示不出来哦,因为要将整个页面Bind完后,才可以进行后续的动作。

    跑一下看看:

    很好,默认转到HomePage页了。

    五、实现其他页面导航

    现在可以处理菜单部分的导航了,点击不同的项导航到不同的页面。看到这里应该也有个大概了,处理不同项的点击事件,将名为frameMain的StageManager使用Show方法展示不同的ViewModel即可。

    使用ItemClick事件吗?No,还记得我之前提过的SendToEventRouterAction吗?如果不熟悉的话就翻翻我之前的blog吧,这里我还是用这个Action来实现。

    修改项模板为:

    <DataTemplate x:Key="NavMenuItemTemplate" >

    <Grid>

    <Interactivity:Interaction.Behaviors>

    <Core:EventTriggerBehavior EventName="Tapped">

    <Behaviors:SendToEventRouterAction IsEventFiringToAllBaseClassesChannels="True" EventRoutingName="NavToPage" EventData="{Binding}" />

    </Core:EventTriggerBehavior>

    </Interactivity:Interaction.Behaviors>

    <Grid.ColumnDefinitions>

    <ColumnDefinition MinWidth="48" />

    <ColumnDefinition />

    </Grid.ColumnDefinitions>

    <FontIcon x:Name="Glyph" FontFamily="{StaticResource FontAwesomeFontFamily}" FontSize="16" Margin="0" Glyph="{Binding Glyph}" VerticalAlignment="Center" HorizontalAlignment="Center" ToolTipService.ToolTip="{Binding Label}"/>

    <TextBlock x:Name="Text" Grid.Column="1" Text="{Binding Label}" VerticalAlignment="Center"/>

    </Grid>

    </DataTemplate>

    然后在MainPage_Model.cs文件中,添加一个方法:

    private void RegisterCommand()

    {

    //一般列表项点击事件

    MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()

    .Where(x => x.EventName == "NavToPage")

    .Subscribe(

    async e =>

    {

    NavMenuItem item = e.EventData as NavMenuItem;

    if (item != null)

    {

    switch (item.Label)

    {

    case "首页":

    await StageManager["frameMain"].Show(new HomePage_Model());

    break;

    case "搜索":

    await StageManager["frameMain"].Show(new SearchPage_Model());

    break;

     

    case "关于":

    await StageManager["frameMain"].Show(new AboutPage_Model());

    break;

    default:

    break;

    }

    }

    }

    ).DisposeWith(this);

     

     

    }

    别忘了在OnBindedViewLoad方法里调用一下:

    private bool isLoaded;

    /// <summary>

    /// This will be invoked by view when the view fires Load event and this viewmodel instance is already in view's ViewModel property

    /// </summary>

    /// <param name="view">View that firing Load event</param>

    /// <returns>Task awaiter</returns>

    protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)

    {

    if (!isLoaded)

    {

    this.RegisterCommand();

    this.isLoaded = true;

    }

    await base.OnBindedViewLoad(view);

    await StageManager["frameMain"].Show(new HomePage_Model());

    }

     

    添加一个isLoaded属性是避免重复调用。

    跑一下看看,咦,有时候好用,有时候不好用,点击图标和文字的时候好用,点击不到图标和文字就不好用,这是什么原因?

    熟悉ListView的同学可能会想到,ListViewItem默认是没有横向撑满的,所以虽然点击了项,但因为项模板里的Grid没有横向撑满,所以并没有触发Grid的Tapped事件,那我们可以设置ListItemStyle,让ListViewItem都横向撑满。在NavMenuItemContainerStyle里添加以下代码:

    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>

    <Setter Property="VerticalContentAlignment" Value="Stretch"/>

     

    这样就可以横向纵向撑满了,再跑下:

    又乱套了,再改哪里呢,修改项模板NavMenuItemTemplate,设置左侧列宽为Auto:

    <DataTemplate x:Key="NavMenuItemTemplate" >

    <Grid >

    <Interactivity:Interaction.Behaviors>

    <Core:EventTriggerBehavior EventName="Tapped">

    <Behaviors:SendToEventRouterAction IsEventFiringToAllBaseClassesChannels="True" EventRoutingName="NavToPage" EventData="{Binding}" />

    </Core:EventTriggerBehavior>

    </Interactivity:Interaction.Behaviors>

    <Grid.ColumnDefinitions>

    <ColumnDefinition MinWidth="48" Width="Auto" />

    <ColumnDefinition />

    </Grid.ColumnDefinitions>

    <FontIcon x:Name="Glyph" FontFamily="{StaticResource FontAwesomeFontFamily}" FontSize="16" Margin="0" Glyph="{Binding Glyph}" VerticalAlignment="Center" HorizontalAlignment="Center" ToolTipService.ToolTip="{Binding Label}"/>

    <TextBlock x:Name="Text" Grid.Column="1" Text="{Binding Label}" VerticalAlignment="Center" />

    </Grid>

    </DataTemplate>

     

    再运行一下:

    现在正常了。

    看一下手机上的样子:

     

    六、其他细节调整

    使用了一下感觉还是有点细节需要改进,比如菜单弹出后,点击项后应该让菜单自动缩回去,现在改一下吧。

    在MainPage的vm里添加一个属性:

    /// <summary>

    ///是否展开菜单

    /// </summary>

    public bool IsPaneOpen

    {

    get { return _IsPaneOpenLocator(this).Value; }

    set { _IsPaneOpenLocator(this).SetValueAndTryNotify(value); }

    }

    #region Property bool IsPaneOpen Setup

    protected Property<bool> _IsPaneOpen = new Property<bool> { LocatorFunc = _IsPaneOpenLocator };

    static Func<BindableBase, ValueContainer<bool>> _IsPaneOpenLocator = RegisterContainerLocator<bool>("IsPaneOpen", model => model.Initialize("IsPaneOpen", ref model._IsPaneOpen, ref _IsPaneOpenLocator, _IsPaneOpenDefaultValueFactory));

    static Func<bool> _IsPaneOpenDefaultValueFactory = () => default(bool);

    #endregion

     

    在vm的构造函数里将此值设置为false,默认为关闭。

    然后将SplitView的IsPaneOpen属性绑定到上面:

    <SplitView x:Name="RootSplitView" IsPaneOpen="{Binding IsPaneOpen,Mode=TwoWay}"

    DisplayMode="Inline"

    OpenPaneLength="256"

    IsTabStop="False">

     

    修改RegisterCommand方法,在点击每个项的部分,添加以下代码,关闭菜单:

    this.IsPaneOpen = false;

     

    现在点击菜单项后可以自动关闭菜单面板了。

    还可以继续针对PC版和手机版调整一下细节,PC版屏幕大,可以让菜单收起时留下图标的部分,这就需要调整PC版的DisplayMode属性为CompactInline,需要请StateTriggers出马了。

    在根Grid里添加以下代码:

    <!-- Adaptive triggers -->

    <VisualStateManager.VisualStateGroups>

    <VisualStateGroup>

    <VisualState>

    <VisualState.StateTriggers>

    <AdaptiveTrigger MinWindowWidth="720" />

    </VisualState.StateTriggers>

    <VisualState.Setters>

    <Setter Target="RootSplitView.DisplayMode" Value="CompactInline"/>

    <Setter Target="RootSplitView.IsPaneOpen" Value="True"/>

    <Setter Target="RootSplitView.CompactPaneLength" Value="48" />

    </VisualState.Setters>

    </VisualState>

    <VisualState>

    <VisualState.StateTriggers>

    <AdaptiveTrigger MinWindowWidth="0" />

    </VisualState.StateTriggers>

    <VisualState.Setters>

    <Setter Target="RootSplitView.DisplayMode" Value="Overlay"/>

    </VisualState.Setters>

    </VisualState>

    </VisualStateGroup>

    </VisualStateManager.VisualStateGroups>

     

    这段代码的意思是,如果宽度大于720,就将SplitView的DisplayMode设置为CompactInline,菜单收起的时候可以保留图标部分,这部分图标的宽度通过CompactPaneLength这个值来设定。

     

    还有一点,手机是有硬件返回键的,在菜单弹出的时候,如果用户点击了返回键,应该让菜单缩回去,所以还要额外处理一下手机的返回键。

    给项目添加Mobile Extensions引用:

    注意我安装了两个版本的SDK,这里需要根据项目的实际版本来选择对应的扩展。

    打开MainPage.xaml.cs,添加以下代码:

    protected override void OnNavigatedTo(NavigationEventArgs e)

    {

    if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))

    {

    HardwareButtons.BackPressed += HardwareButtons_BackPressed;

    }

    base.OnNavigatedTo(e);

    }

     

    protected override void OnNavigatedFrom(NavigationEventArgs e)

    {

    if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))

    {

    HardwareButtons.BackPressed -= HardwareButtons_BackPressed;

    }

    base.OnNavigatedFrom(e);

    }

     

    private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)

    {

    //throw new NotImplementedException();

    var vm = this.LayoutRoot.DataContext as MainPage_Model;

    if (vm != null)

    {

    if (vm.IsPaneOpen)

    {

    e.Handled = true;

    vm.IsPaneOpen = false;

    }

    }

    }

     

    至此,一个具有基本功能的汉堡菜单就完成了,可以通过修改背景色、前景色等方式再来改善展示效果。再来总结一下主要的知识点:

    1. 使用SplitView来区分菜单面板和内容部分;
    2. 使用FontAwesomeFont字体显示图标;
    3. 为区域使用mvvm:StageManager.Beacon属性来设置StageManager的标识,并通过StageManager["xxx"]形式来调用;
    4. 通过StateTriggers来为PC和手机端设置不同的菜单效果;
    5. 通过添加Mobile Extensions引用来支持手机硬件返回键;

    附demo下载地址:

    链接:http://pan.baidu.com/s/1pJRJcRh 密码:jofi

  • 相关阅读:
    微信小程序 单选按钮 最佳
    微信小程序 单选按钮的实现
    微信小程序 单选框实现
    Java Code To Create Pyramid and Pattern
    Java language
    npm Err! Unexpected end of JSON input while parsing near
    Node.js Express FrameWork Tutorial
    Higher-Order Function Examples
    Create First HTTP Web Server in Node.js: Complete Tutorial
    Node.js NPM Tutorial: Create, Publish, Extend & Manage
  • 原文地址:https://www.cnblogs.com/yanxiaodi/p/5034936.html
Copyright © 2011-2022 走看看