我们要做的效果是这样的,左侧是可折叠的菜单栏,右侧是内容区域,点击左侧的菜单项右侧内容区域则相应地切换。这篇博客标题起得比较随意了,因为很多软件、网站都有这种布局效果,所以请忽略。
wpf实现的话,我的办法是用一个tabcontrol,修改tabcontrol的样式模板,首先将控件的TabStripPlacement设置为left使tabcontrol的item header部分靠左内容靠右,然后用一个Expander将TabPanel包住实现可折叠菜单效果,最后就是把用到的控件样式修改一下即可。当然网易云音乐肯定不是这么做的,因为整个软件似乎是用了一种html的布局框架。
先看下网音的效果图:
WPF做出来的效果图:
未完善的问题:
不能添加多个可折叠菜单,我暂时没想到比较好的办法。
新建一个项目,名字随你,新建一个自定义用户控件前台修改为:
<TabControl x:Class="cloundmusic_left.controls.itabcontrol" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:cloundmusic_left.controls" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <TabControl.Resources> <!--折叠菜单的箭头按钮--> <ControlTemplate x:Key="ExpanderToggleButton" TargetType="{x:Type ToggleButton}"> <Border Background="{TemplateBinding Background}" Width="{TemplateBinding Width}" x:Name="Border" > <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CheckStates"> <VisualState x:Name="Checked"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="CollapsedArrow"> <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="ExpandededArrow"> <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Unchecked" /> <VisualState x:Name="Indeterminate" /> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid Margin="0,0,13,0" HorizontalAlignment="Right" VerticalAlignment="Center"> <Path x:Name="CollapsedArrow" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 4 4 L 8 0 Z"> <Path.Fill> <SolidColorBrush Color="#7d7d7d" /> </Path.Fill> </Path> <Path x:Name="ExpandededArrow" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed" Data="M 0 4 L 4 0 L 8 4 Z"> <Path.Fill> <SolidColorBrush Color="#7d7d7d" /> </Path.Fill> </Path> </Grid> </Border> </ControlTemplate> <Style TargetType="{x:Type Expander}"> <Setter Property="Cursor" Value="Hand"/> <Setter Property="Foreground" Value="#7d7d7d"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Expander}"> <Border BorderBrush="#e1e1e2" BorderThickness="0,0,1,0"> <Grid Background="#f5f5f7"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition x:Name="ContentRow" Height="0" /> </Grid.RowDefinitions> <Border MinHeight="32" x:Name="Border" Grid.Row="0" > <Grid VerticalAlignment="Center"> <ContentPresenter Margin="4" ContentSource="Header" RecognizesAccessKey="True" /> <ToggleButton Background="Transparent" Width="{Binding ElementName=Border,Path=ActualWidth}" Panel.ZIndex="1" OverridesDefaultStyle="True" Template="{StaticResource ExpanderToggleButton}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"> </ToggleButton> </Grid> </Border> <Border x:Name="Content" Grid.Row="1" > <ContentPresenter/> </Border> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsExpanded" Value="True"> <Setter TargetName="ContentRow" Property="Height" Value="{Binding DesiredHeight, ElementName=Content}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type TabItem}"> <Setter Property="OverridesDefaultStyle" Value="True"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TabItem}"> <Grid SnapsToDevicePixels="true"> <Border Cursor="Hand" MinWidth="199" MinHeight="32" x:Name="Bd" CornerRadius="0" Background="Transparent" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="3,0,0,0" Padding="0" Margin="0"> <Grid VerticalAlignment="Center" HorizontalAlignment="Left"> <StackPanel Orientation="Horizontal"> <ContentPresenter x:Name="Content" Margin="17,0,0,0" ContentSource="Header" HorizontalAlignment="Left" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/> </StackPanel> </Grid> </Border> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="false"> <Setter Property="Foreground" Value="#5c5c5c"/> </Trigger> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Foreground" Value="#000000"/> </Trigger> <Trigger Property="IsSelected" Value="true"> <Setter Property="Panel.ZIndex" Value="1"/> <Setter Property="Background" TargetName="Bd" Value="#e6e7ea"/> <Setter Property="Foreground" Value="#000000"/> <Setter Property="BorderThickness" TargetName="Bd" Value="3,0,0,0"/> <Setter Property="BorderBrush" TargetName="Bd" Value="#c62f2f"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </TabControl.Resources> <TabControl.Style> <Style TargetType="{x:Type TabControl}"> <Setter Property="OverridesDefaultStyle" Value="True" /> <Setter Property="TabStripPlacement" Value="Left" /> <Setter Property="SnapsToDevicePixels" Value="True" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:itabcontrol}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="200" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!--可折叠菜单--> <Expander Header="{TemplateBinding iTitle}" Grid.Column="0"> <!--菜单项--> <TabPanel x:Name="HeaderPanel" IsItemsHost="True" KeyboardNavigation.TabIndex="1" Background="Transparent" /> </Expander> <!--右侧内容区域--> <Border x:Name="Border" Grid.Column="1" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.DirectionalNavigation="Contained" KeyboardNavigation.TabIndex="2"> <ContentPresenter x:Name="PART_SelectedContentHost" Margin="4" ContentSource="SelectedContent" /> </Border> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </TabControl.Style> </TabControl>
修改后你的vs也许会抱一个错:
这个报错就像vs错误列表里的警告一样,没啥卵用,不用理它,程序是可以运行的。
ok继续,后台代码加一个依赖属性:
public string iTitle { get { return (string)GetValue(iTitleProperty); } set { SetValue(iTitleProperty, value); } } public static readonly DependencyProperty iTitleProperty = DependencyProperty.Register("iTitle", typeof(string), typeof(itabcontrol));
然后只要在界面中使用这个控件即可。
呃我发现这个项目并没有什么难点,实现方法开头也说了,所以这里不一步步讲了。
值得一提的是,加入的Expander也就是可折叠菜单的header代表菜单名,这里我是把修改的这个TabControl写在一个自定义控件里的,在界面调用的时候默认是无法给它设置内容的,所以我们要在这个自定义控件中加入一个依赖属性,然后在样式中绑定这个属性。这里就涉及到一个比较有意思的东西,在样式中绑定一个属性也许你没接触过,但是你一定知道Binding的用法,而在样式中是没办法这么用的,那该怎么去做呢?相信你看完代码就知道了。
项目下载: