自制WPF侧边栏菜单导航

这是询问DeepSeek后,写的Wpf侧边栏菜单导航。
先用Nuget安装CommunityToolkit.Mvvm实现mvvm模式编程,安装Microsoft.Xaml.Behaviors.Wpf实现事件转命令。
先建Models文件夹,增加MenuItem.cs

namespace SidebarMenuDemo.Models
{
    class MenuItem
    {
        required public string Title { get; init; }

        required public string ImagePath { get; init; }

        required public Type ViewType { get; init; }
    }
}

增加NamedColor.cs模型

using System.Windows.Media;

namespace SidebarMenuDemo.Models
{
    class NamedColor
    {
        required public string Name { get; set; }

        required public Color Color { get; set; }
    }
}

创建ViewModels文件夹,为MainWindow.xaml创建MainWindowViewModel.cs。

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;

namespace SidebarMenuDemo.ViewModels
{
    partial class MainWindowViewModel : ObservableObject
    {
        [ObservableProperty]
        private object? _currentView;

        public ObservableCollection<Models.MenuItem> MenuItems { get; private set; } = [];

        [RelayCommand]
        private void SelectionChanged(Models.MenuItem? item)
        {
            if (item is null)
                return;
            if(item.ViewType is not null)
            {
                CurrentView = Activator.CreateInstance(item.ViewType);
            }            
        }

        private void InitializeMenu()
        {
            MenuItems.Add(new Models.MenuItem { Title = "命名颜色表", ImagePath = "/Images/Flower.png", ViewType = typeof(Views.NamedColorView) });
            MenuItems.Add(new Models.MenuItem { Title = "关于", ImagePath = "/Images/about.png", ViewType = typeof(Views.AboutView) });
        }

        public MainWindowViewModel()
        {
            InitializeMenu();

            SelectionChanged(MenuItems.First<Models.MenuItem>());
        }        
    }
}

把SelectionChanged事件的参数SelectionChangedEventArgs转换成MenuItem需要一个转换器SelectionChangedToMenuItem,

using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;

namespace SidebarMenuDemo.Converter
{
    class SelectionChangedToMenuItem: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value switch
            {
                SelectionChangedEventArgs e => e.AddedItems.OfType<Models.MenuItem>().First(),
                null => throw new ArgumentNullException(nameof(value)),
                _ => throw new ArgumentException("Unknown Type of a value")
            };
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

下面是MainWindow.xaml的内容

<Window x:Class="SidebarMenuDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SidebarMenuDemo"
        xmlns:viewmodel="clr-namespace:SidebarMenuDemo.ViewModels"
        xmlns:converter="clr-namespace:SidebarMenuDemo.Converter"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <converter:SelectionChangedToMenuItem x:Key="SelectionChangedToMenuItem"/>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">
                        <Border x:Name="Bd" Background="Transparent" BorderBrush="Transparent" BorderThickness="1"
                                CornerRadius="4" Padding="8,6" SnapsToDevicePixels="True">
                            <ContentPresenter/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <!--鼠标悬停效果-->
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#44FFA500"/>
                                <Setter TargetName="Bd" Property="BorderBrush" Value="#22FFA500"/>
                            </Trigger>
                            <!--选中效果-->
                            <Trigger Property="IsSelected" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#88FFA500"/>
                                <Setter TargetName="Bd" Property="BorderBrush" Value="#66FFA500"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="FontFamily" Value="Segoe UI"/>
            <Setter Property="Margin" Value="4"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.DataContext>
            <viewmodel:MainWindowViewModel/>
        </Grid.DataContext>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <!--左侧菜单-->
        <ListBox Margin="4" Background="Teal" ItemsSource="{Binding MenuItems}"
                 SelectionMode="Single">            
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <i:InvokeCommandAction Command="{Binding SelectionChangedCommand}"
                                           EventArgsConverter="{StaticResource SelectionChangedToMenuItem}"
                                           PassEventArgsToCommand="True"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>            
            <ListBox.ItemTemplate>
                <DataTemplate>                    
                    <StackPanel Orientation="Horizontal">                        
                        <Image Source="{Binding ImagePath}" Width="32" Margin="4"/>
                        <TextBlock Text="{Binding Title}" FontSize="18" Foreground="White" VerticalAlignment="Center" Margin="4"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <!--右侧内容视图-->
        <ContentControl Grid.Column="1" Content="{Binding CurrentView}" Margin="4"/>
    </Grid>
</Window>

添加Views文件夹,添加AboutView.xaml文件

<UserControl x:Class="SidebarMenuDemo.Views.AboutView"
             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:SidebarMenuDemo.Views"
             xmlns:viewmodel="clr-namespace:SidebarMenuDemo.ViewModels"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="600">    
    <Grid>
        <Grid.Background>
            <ImageBrush ImageSource="/Images/蓝底树叶.jpg"/>
        </Grid.Background>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>           
        </Grid.RowDefinitions>
        <TextBlock Text="SidebarMenuDemo 1.0" FontSize="22" Foreground="White" Margin="4"
                   Grid.Row="0"/>
        <TextBlock Text="学习WPF,自制菜单导航功能。" FontSize="18" Grid.Row="1" Foreground="White"
                   Margin="4"/>       
    </Grid>
</UserControl>

再添加一个NamedColorView.xaml文件

<UserControl x:Class="SidebarMenuDemo.Views.NamedColorView"
             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:SidebarMenuDemo.Views"
             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
             xmlns:viewModel="clr-namespace:SidebarMenuDemo.ViewModels"
             xmlns:converter="clr-namespace:SidebarMenuDemo.Converter"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="600">    
    <UserControl.DataContext>
        <viewModel:NamedColorViewModel/>
    </UserControl.DataContext>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding FillCollectionCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid Background="#FF0A1921">       
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Margin="4" Text="{Binding NamedColors.Count,StringFormat=命名颜色数:{0}}" FontSize="18" Grid.Row="0"
                   Foreground="White"/>
        <ListBox Grid.Row="1" Margin="4" ItemsSource="{Binding NamedColors}"
                 Background="LemonChiffon">            
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="200"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="{Binding Name}" Grid.Column="0" FontSize="16" VerticalAlignment="Center"/>
                        <Rectangle Height="30" Width="300" Grid.Column="1">
                            <Rectangle.Fill>
                                <SolidColorBrush Color="{Binding Color}"/>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
            
        </ListBox>
    </Grid>
</UserControl>

在ViewModels文件夹下为NamedColor.xaml增加一个NamedColorViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Windows.Media;

namespace SidebarMenuDemo.ViewModels
{
    partial class NamedColorViewModel : ObservableObject
    {
        public ObservableCollection<Models.NamedColor> NamedColors { get; set; } = [];

        private async IAsyncEnumerable<Models.NamedColor> GetColors()
        {
            foreach (PropertyInfo propertyInfo in typeof(Colors).GetRuntimeProperties())
            {
                yield return new Models.NamedColor
                {
                    Color = (Color)propertyInfo.GetValue(null)!,
                    Name = propertyInfo.Name
                };
                await Task.Delay(1);
            }

        }

        [RelayCommand]
        private async Task FillCollection()
        {
            NamedColors.Clear();
            await foreach (var item in GetColors())
            {
                NamedColors.Add(item);
            }
        }

        public NamedColorViewModel()
        {

        }       
    }
}

程序效果


DeepSeek按照我的意思设计的,还不错吧。

posted @ 2025-03-19 18:25  孤独的小苗  阅读(477)  评论(0)    收藏  举报