初始化

This commit is contained in:
尚美 2025-03-14 22:30:46 +08:00
parent 5dd06ac4fd
commit d1f082cc80
24 changed files with 3173 additions and 2 deletions

51
.gitignore vendored Normal file
View File

@ -0,0 +1,51 @@
# Visual Studio files
.vs/
bin/
obj/
*.user
*.suo
*.cache
*.pdb
*.exe
*.dll
# Build results
[Dd]ebug/
[Rr]elease/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio cache/options directory
.vs/
.vscode/
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# Local History
.localhistory/
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Application specific
shortcuts.json
settings.json

24
MusicTool.sln Normal file
View File

@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicTool", "MusicTool\MusicTool.csproj", "{20B3B6A3-E1F5-40CE-A210-933943F7A930}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{20B3B6A3-E1F5-40CE-A210-933943F7A930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20B3B6A3-E1F5-40CE-A210-933943F7A930}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20B3B6A3-E1F5-40CE-A210-933943F7A930}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20B3B6A3-E1F5-40CE-A210-933943F7A930}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B1C2D3E4-F5G6-H7I8-J9K0-L1M2N3O4P5Q6}
EndGlobalSection
EndGlobal

43
MusicTool/App.xaml Normal file
View File

@ -0,0 +1,43 @@
<Application x:Class="MusicTool.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MusicTool"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- 自定义颜色 -->
<SolidColorBrush x:Key="PrimaryHueDarkBrush" Color="#673AB7"/>
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#9575CD"/>
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#D1C4E9"/>
<SolidColorBrush x:Key="SecondaryAccentBrush" Color="#7CB342"/>
<SolidColorBrush x:Key="BackgroundBrush" Color="#121212"/>
<SolidColorBrush x:Key="ForegroundBrush" Color="#FFFFFF"/>
<!-- 自定义样式 -->
<Style TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
<Setter Property="Margin" Value="4"/>
<Setter Property="Padding" Value="16,8"/>
</Style>
<Style TargetType="TextBlock" x:Key="TitleTextStyle">
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Margin" Value="0,8"/>
</Style>
<Style TargetType="TextBlock" x:Key="SubtitleTextStyle">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Opacity" Value="0.8"/>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

32
MusicTool/App.xaml.cs Normal file
View File

@ -0,0 +1,32 @@
using System;
using System.Windows;
namespace MusicTool
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 全局异常处理
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
Exception ex = (Exception)args.ExceptionObject;
MessageBox.Show($"发生了未处理的异常: {ex.Message}\n\n{ex.StackTrace}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
};
// 应用程序级别的异常处理
Application.Current.DispatcherUnhandledException += (sender, args) =>
{
MessageBox.Show($"发生了未处理的异常: {args.Exception.Message}\n\n{args.Exception.StackTrace}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
args.Handled = true;
};
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Globalization;
using System.Windows.Data;
using MaterialDesignThemes.Wpf;
namespace MusicTool
{
public class BoolToHeartIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool isFavorite && isFavorite)
{
return PackIconKind.Heart;
}
else
{
return PackIconKind.HeartOutline;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
namespace MusicTool.Equalizer
{
public class EqualizerSampleProvider : ISampleProvider
{
private readonly ISampleProvider sourceProvider;
private readonly float[] bandGains;
public EqualizerSampleProvider(ISampleProvider sourceProvider)
{
this.sourceProvider = sourceProvider;
this.bandGains = new float[10];
}
public WaveFormat WaveFormat => sourceProvider.WaveFormat;
public void SetBand(int band, float gain)
{
if (band >= 0 && band < bandGains.Length)
{
// 将分贝值转换为线性增益
bandGains[band] = (float)Math.Pow(10, gain / 20);
}
}
public int Read(float[] buffer, int offset, int count)
{
int samplesRead = sourceProvider.Read(buffer, offset, count);
if (samplesRead > 0)
{
// 应用均衡器增益
for (int i = 0; i < samplesRead; i++)
{
int sampleIndex = offset + i;
float sample = buffer[sampleIndex];
// 对每个频段应用增益
for (int band = 0; band < bandGains.Length; band++)
{
// 这里应该使用实际的滤波器实现
// 为了演示,我们简单地应用增益
sample *= bandGains[band];
}
buffer[sampleIndex] = sample;
}
}
return samplesRead;
}
}
}

View File

@ -0,0 +1,81 @@
<Window x:Class="MusicTool.Equalizer.EqualizerWindow"
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:MusicTool.Equalizer"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Title="均衡器" Height="400" Width="600"
WindowStartupLocation="CenterOwner"
Background="{StaticResource BackgroundBrush}"
TextElement.Foreground="{StaticResource ForegroundBrush}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
FontFamily="{materialDesign:MaterialDesignFont}">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<TextBlock Text="均衡器" FontSize="20" FontWeight="Bold" Margin="0,0,0,16"/>
<!-- 均衡器滑块 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 频率标签 -->
<StackPanel Grid.Column="0" Margin="0,0,16,0">
<TextBlock Text="60 Hz" Margin="0,0,0,8"/>
<TextBlock Text="170 Hz" Margin="0,0,0,8"/>
<TextBlock Text="310 Hz" Margin="0,0,0,8"/>
<TextBlock Text="600 Hz" Margin="0,0,0,8"/>
<TextBlock Text="1 kHz" Margin="0,0,0,8"/>
<TextBlock Text="3 kHz" Margin="0,0,0,8"/>
<TextBlock Text="6 kHz" Margin="0,0,0,8"/>
<TextBlock Text="12 kHz" Margin="0,0,0,8"/>
<TextBlock Text="14 kHz" Margin="0,0,0,8"/>
<TextBlock Text="16 kHz" Margin="0,0,0,8"/>
</StackPanel>
<!-- 滑块 -->
<StackPanel Grid.Column="1">
<Slider x:Name="Band0Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band1Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band2Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band3Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band4Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band5Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band6Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band7Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band8Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
<Slider x:Name="Band9Slider" Margin="0,0,0,8" Minimum="-20" Maximum="20" Value="0"
ValueChanged="BandSlider_ValueChanged"/>
</StackPanel>
</Grid>
<!-- 底部按钮 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0">
<Button Content="重置" Style="{StaticResource MaterialDesignOutlinedButton}"
Margin="8,0" Click="ResetButton_Click"/>
<Button Content="关闭" Style="{StaticResource MaterialDesignRaisedButton}"
Click="CloseButton_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,79 @@
using System;
using System.Windows;
using System.Windows.Controls;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
namespace MusicTool.Equalizer
{
public partial class EqualizerWindow : Window
{
private readonly MainWindow mainWindow;
private readonly double[] bands;
public EqualizerWindow(MainWindow owner)
{
InitializeComponent();
mainWindow = owner;
Owner = owner;
// 获取当前的均衡器设置
bands = new double[10];
mainWindow.GetEqualizerSettings(bands);
// 设置滑块的值
Band0Slider.Value = bands[0];
Band1Slider.Value = bands[1];
Band2Slider.Value = bands[2];
Band3Slider.Value = bands[3];
Band4Slider.Value = bands[4];
Band5Slider.Value = bands[5];
Band6Slider.Value = bands[6];
Band7Slider.Value = bands[7];
Band8Slider.Value = bands[8];
Band9Slider.Value = bands[9];
}
private void BandSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (sender is Slider slider)
{
int bandIndex = int.Parse(slider.Name.Replace("Band", "").Replace("Slider", ""));
bands[bandIndex] = e.NewValue;
// 更新主窗口的均衡器设置
mainWindow.ApplyEqualizerSettings(bands);
}
}
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
// 重置所有滑块
Band0Slider.Value = 0;
Band1Slider.Value = 0;
Band2Slider.Value = 0;
Band3Slider.Value = 0;
Band4Slider.Value = 0;
Band5Slider.Value = 0;
Band6Slider.Value = 0;
Band7Slider.Value = 0;
Band8Slider.Value = 0;
Band9Slider.Value = 0;
// 重置均衡器设置
for (int i = 0; i < bands.Length; i++)
{
bands[i] = 0;
}
// 更新主窗口的均衡器设置
mainWindow.ApplyEqualizerSettings(bands);
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
}

320
MusicTool/MainWindow.xaml Normal file
View File

@ -0,0 +1,320 @@
<Window x:Class="MusicTool.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:MusicTool"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Title="音乐播放器" Height="650" Width="1000"
WindowStartupLocation="CenterScreen"
Background="{StaticResource BackgroundBrush}"
TextElement.Foreground="{StaticResource ForegroundBrush}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
FontFamily="{materialDesign:MaterialDesignFont}"
AllowDrop="True"
Drop="Window_Drop"
DragOver="Window_DragOver">
<Window.Resources>
<Style x:Key="PlayControlButton" TargetType="Button" BasedOn="{StaticResource MaterialDesignFloatingActionMiniButton}">
<Setter Property="Margin" Value="8"/>
<Setter Property="Width" Value="40"/>
<Setter Property="Height" Value="40"/>
</Style>
<local:BoolToHeartIconConverter x:Key="BoolToHeartIconConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 顶部工具栏 -->
<materialDesign:ColorZone Mode="PrimaryDark" Padding="16" materialDesign:ShadowAssist.ShadowDepth="Depth2">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
<materialDesign:PackIcon Kind="MusicNote" Height="24" Width="24" VerticalAlignment="Center" />
<TextBlock Text="方晨鑫音乐播放器" VerticalAlignment="Center" Margin="8,0,0,0" FontSize="18" FontWeight="Bold"/>
</StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" HorizontalAlignment="Right">
<Button Style="{StaticResource MaterialDesignToolButton}" ToolTip="设置">
<materialDesign:PackIcon Kind="Settings" />
</Button>
<Button Style="{StaticResource MaterialDesignToolButton}" ToolTip="关于">
<materialDesign:PackIcon Kind="Information" />
</Button>
</StackPanel>
</DockPanel>
</materialDesign:ColorZone>
<!-- 主内容区 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧导航栏 -->
<Grid Background="#1F1F1F">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 导航菜单 -->
<StackPanel Margin="16,16,16,0">
<TextBlock Text="音乐库" Style="{StaticResource TitleTextStyle}"/>
<ListBox x:Name="NavigationList" Margin="0,8">
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="MusicNote" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="所有音乐"/>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Album" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="专辑"/>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="AccountMusic" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="艺术家"/>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="PlaylistMusic" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="播放列表"/>
</StackPanel>
</ListBoxItem>
</ListBox>
<Separator Margin="0,8"/>
<TextBlock Text="我的收藏" Style="{StaticResource TitleTextStyle}"/>
<ListBox Margin="0,8">
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Heart" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="我喜欢的音乐"/>
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="History" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="最近播放"/>
</StackPanel>
</ListBoxItem>
</ListBox>
<!-- 添加音乐文件夹按钮 -->
<Button Margin="0,16,0,0" Style="{StaticResource MaterialDesignFlatButton}" HorizontalAlignment="Stretch" Click="AddMusicFolder_Click">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FolderMusic" Margin="0,0,8,0"/>
<TextBlock Text="添加音乐文件夹"/>
</StackPanel>
</Button>
</StackPanel>
<!-- 当前播放专辑封面 -->
<Grid Grid.Row="1" VerticalAlignment="Bottom" Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Width="200" Height="200" Background="#2D2D2D" CornerRadius="8">
<Image x:Name="AlbumCover" Stretch="UniformToFill"/>
</Border>
<StackPanel Grid.Row="1" Margin="0,8,0,0">
<TextBlock x:Name="NowPlayingSong" Text="未播放" FontWeight="SemiBold" TextTrimming="CharacterEllipsis"/>
<TextBlock x:Name="NowPlayingArtist" Text="未知艺术家" Opacity="0.7" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Grid>
</Grid>
<!-- 右侧内容区 -->
<Grid Grid.Column="1" Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 搜索栏 -->
<Grid Margin="0,0,0,16">
<TextBox x:Name="SearchTextBox"
materialDesign:HintAssist.Hint="搜索音乐、艺术家或专辑..."
Style="{StaticResource MaterialDesignOutlinedTextBox}"
materialDesign:TextFieldAssist.HasClearButton="True"
materialDesign:TextFieldAssist.PrefixText="🔍"
TextChanged="SearchTextBox_TextChanged"/>
</Grid>
<!-- 音乐列表 -->
<DataGrid Grid.Row="1" x:Name="SongsDataGrid" AutoGenerateColumns="False"
CanUserAddRows="False" CanUserDeleteRows="False"
Background="Transparent" BorderThickness="0"
GridLinesVisibility="None"
HeadersVisibility="Column"
IsReadOnly="True"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="播放" Click="PlayMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Play" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="添加歌曲" Click="AddSongsMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="FileMusic" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="删除歌曲" Click="RemoveSongMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Delete" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="清空播放列表" Click="ClearPlaylistMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="PlaylistRemove" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="查看歌曲信息" Click="SongInfoMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Information" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="打开文件位置" Click="OpenFileLocationMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="FolderOpen" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTemplateColumn Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Style="{StaticResource MaterialDesignIconButton}" Width="30" Height="30">
<materialDesign:PackIcon Kind="Play" Width="20" Height="20"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="标题" Width="*" Binding="{Binding Title}"/>
<DataGridTextColumn Header="艺术家" Width="150" Binding="{Binding Artist}"/>
<DataGridTextColumn Header="专辑" Width="150" Binding="{Binding Album}"/>
<DataGridTextColumn Header="时长" Width="80" Binding="{Binding Duration}"/>
<DataGridTemplateColumn Width="50">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Style="{StaticResource MaterialDesignIconButton}" Width="30" Height="30"
Click="FavoriteButton_Click">
<materialDesign:PackIcon Kind="{Binding IsFavorite, Converter={StaticResource BoolToHeartIconConverter}}"
Width="20" Height="20"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
<!-- 底部播放控制栏 -->
<Grid Grid.Row="2" Background="#1F1F1F" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 播放控制按钮 -->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Style="{StaticResource PlayControlButton}" ToolTip="随机播放" Click="ShuffleButton_Click">
<materialDesign:PackIcon Kind="Shuffle" Width="24" Height="24"/>
</Button>
<Button Style="{StaticResource PlayControlButton}" ToolTip="上一曲" Click="PreviousButton_Click">
<materialDesign:PackIcon Kind="SkipPrevious" Width="24" Height="24"/>
</Button>
<Button x:Name="PlayPauseButton" Style="{StaticResource MaterialDesignFloatingActionButton}"
Margin="8" Width="56" Height="56" ToolTip="播放/暂停" Click="PlayPauseButton_Click">
<materialDesign:PackIcon x:Name="PlayPauseIcon" Kind="Play" Width="32" Height="32"/>
</Button>
<Button Style="{StaticResource PlayControlButton}" ToolTip="下一曲" Click="NextButton_Click">
<materialDesign:PackIcon Kind="SkipNext" Width="24" Height="24"/>
</Button>
<Button x:Name="RepeatButton" Style="{StaticResource PlayControlButton}" ToolTip="播放模式" Click="RepeatButton_Click">
<materialDesign:PackIcon x:Name="RepeatIcon" Kind="Repeat" Width="24" Height="24"/>
<Button.ContextMenu>
<ContextMenu>
<MenuItem x:Name="SequentialMenuItem" Header="顺序播放" Click="SequentialMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="ArrowRightBold" />
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="RepeatMenuItem" Header="列表循环" Click="RepeatMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Repeat" />
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="RepeatOneMenuItem" Header="单曲循环" Click="RepeatOneMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="RepeatOnce" />
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="ShuffleMenuItem" Header="随机播放" Click="ShuffleMenuItem_Click">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Shuffle" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
<!-- 进度条和音量控制 -->
<StackPanel Grid.Column="0" Margin="16,0" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="CurrentTime" Text="00:00" VerticalAlignment="Center"/>
<Slider x:Name="ProgressSlider" Grid.Column="1" Margin="8,0" VerticalAlignment="Center"
Minimum="0" Maximum="100" Value="0"
PreviewMouseDown="ProgressSlider_PreviewMouseDown"
PreviewMouseUp="ProgressSlider_PreviewMouseUp"
ValueChanged="ProgressSlider_ValueChanged"/>
<TextBlock Grid.Column="2" x:Name="TotalTime" Text="00:00" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="16,0">
<materialDesign:PackIcon Kind="VolumeHigh" VerticalAlignment="Center"/>
<Slider x:Name="VolumeSlider" Width="100" Margin="8,0" Value="80" Maximum="100" VerticalAlignment="Center"
ValueChanged="VolumeSlider_ValueChanged"/>
<Button Style="{StaticResource MaterialDesignIconButton}" ToolTip="均衡器" Click="EqualizerButton_Click">
<materialDesign:PackIcon Kind="Equalizer"/>
</Button>
<Button Style="{StaticResource MaterialDesignIconButton}" ToolTip="播放列表" Click="PlaylistButton_Click">
<materialDesign:PackIcon Kind="PlaylistMusic"/>
</Button>
</StackPanel>
</Grid>
</Grid>
</Window>

1279
MusicTool/MainWindow.xaml.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MusicTool
{
public class SongModel : INotifyPropertyChanged
{
private string _title = string.Empty;
private string _artist = string.Empty;
private string _album = string.Empty;
private string _duration = string.Empty;
private string _filePath = string.Empty;
private bool _isFavorite;
public string Title
{
get => _title;
set
{
if (_title != value)
{
_title = value;
OnPropertyChanged();
}
}
}
public string Artist
{
get => _artist;
set
{
if (_artist != value)
{
_artist = value;
OnPropertyChanged();
}
}
}
public string Album
{
get => _album;
set
{
if (_album != value)
{
_album = value;
OnPropertyChanged();
}
}
}
public string Duration
{
get => _duration;
set
{
if (_duration != value)
{
_duration = value;
OnPropertyChanged();
}
}
}
public string FilePath
{
get => _filePath;
set
{
if (_filePath != value)
{
_filePath = value;
OnPropertyChanged();
}
}
}
public bool IsFavorite
{
get => _isFavorite;
set
{
if (_isFavorite != value)
{
_isFavorite = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\Icons\**" />
<None Remove="Resources\Images\**" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Icons\**" />
<Resource Include="Resources\Images\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
<Folder Include="ViewModels\" />
<Folder Include="Views\" />
<Folder Include="Services\" />
<Folder Include="Resources\Icons\" />
<Folder Include="Resources\Images\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,40 @@
<Window x:Class="MusicTool.Playlist.InputDialog"
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:MusicTool.Playlist"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Title="输入" Height="200" Width="400"
WindowStartupLocation="CenterOwner"
Background="{StaticResource BackgroundBrush}"
TextElement.Foreground="{StaticResource ForegroundBrush}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
FontFamily="{materialDesign:MaterialDesignFont}">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<TextBlock x:Name="TitleTextBlock" Text="标题" FontSize="18" FontWeight="Bold" Margin="0,0,0,16"/>
<!-- 提示文本 -->
<TextBlock x:Name="PromptTextBlock" Grid.Row="1" Text="提示文本" Margin="0,0,0,8"/>
<!-- 输入框 -->
<TextBox x:Name="ResponseTextBox" Grid.Row="2" Margin="0,0,0,16" VerticalAlignment="Top"/>
<!-- 按钮 -->
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="取消" Style="{StaticResource MaterialDesignOutlinedButton}" Margin="8,0" Click="CancelButton_Click"/>
<Button Content="确定" Style="{StaticResource MaterialDesignRaisedButton}" Click="OkButton_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,35 @@
using System.Windows;
namespace MusicTool.Playlist
{
public partial class InputDialog : Window
{
public string ResponseText
{
get { return ResponseTextBox.Text; }
set { ResponseTextBox.Text = value; }
}
public InputDialog(string title, string prompt)
{
InitializeComponent();
TitleTextBlock.Text = title;
Title = title;
PromptTextBlock.Text = prompt;
// 设置焦点到输入框
ResponseTextBox.Focus();
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.ObjectModel;
namespace MusicTool.Playlist
{
public class PlaylistModel
{
public string Name { get; set; } = string.Empty;
public ObservableCollection<SongModel> Songs { get; set; } = new ObservableCollection<SongModel>();
public int SongCount => Songs.Count;
}
}

View File

@ -0,0 +1,71 @@
<Window x:Class="MusicTool.Playlist.PlaylistWindow"
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:MusicTool.Playlist"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Title="播放列表" Height="450" Width="800"
WindowStartupLocation="CenterOwner"
Background="{StaticResource BackgroundBrush}"
TextElement.Foreground="{StaticResource ForegroundBrush}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
FontFamily="{materialDesign:MaterialDesignFont}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 顶部工具栏 -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10">
<Button x:Name="AddPlaylistButton" Content="新建播放列表" Click="AddPlaylistButton_Click" Margin="0,0,10,0"/>
<Button x:Name="AddSongsButton" Content="添加歌曲" Click="AddSongsButton_Click" Margin="0,0,10,0"/>
<Button x:Name="PlayPlaylistButton" Content="播放" Click="PlayPlaylistButton_Click" Margin="0,0,10,0"/>
<Button x:Name="DeletePlaylistButton" Content="删除播放列表" Click="DeletePlaylistButton_Click" Margin="0,0,10,0"/>
</StackPanel>
<!-- 主要内容区域 -->
<Grid Grid.Row="1" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧播放列表列表 -->
<ListBox x:Name="PlaylistsListBox" Grid.Column="0" Margin="0,0,10,0"
SelectionChanged="PlaylistsListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding SongCount, StringFormat=' ({0})'}"
Foreground="Gray" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- 右侧歌曲列表 -->
<DataGrid x:Name="SongsDataGrid" Grid.Column="1" AutoGenerateColumns="False"
IsReadOnly="True" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Header="标题" Binding="{Binding Title}" Width="*"/>
<DataGridTextColumn Header="艺术家" Binding="{Binding Artist}" Width="150"/>
<DataGridTextColumn Header="专辑" Binding="{Binding Album}" Width="150"/>
<DataGridTextColumn Header="时长" Binding="{Binding Duration}" Width="80"/>
<DataGridTemplateColumn Width="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="删除" Click="RemoveSongButton_Click"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using TagLib;
using File = System.IO.File;
using TagFile = TagLib.File;
using System.Runtime.Versioning;
namespace MusicTool.Playlist
{
[SupportedOSPlatform("windows")]
public partial class PlaylistWindow : Window
{
private readonly MainWindow mainWindow;
private ObservableCollection<PlaylistModel> playlists;
private PlaylistModel? selectedPlaylist;
public PlaylistWindow(MainWindow owner)
{
InitializeComponent();
mainWindow = owner;
Owner = owner;
// 初始化播放列表集合
playlists = new ObservableCollection<PlaylistModel>();
PlaylistsListBox.ItemsSource = playlists;
// 加载保存的播放列表
LoadPlaylists();
}
private void LoadPlaylists()
{
try
{
if (File.Exists("playlists.json"))
{
string json = File.ReadAllText("playlists.json");
var loadedPlaylists = JsonSerializer.Deserialize<PlaylistModel[]>(json);
if (loadedPlaylists != null)
{
playlists.Clear();
foreach (var playlist in loadedPlaylists)
{
playlists.Add(playlist);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show($"加载播放列表时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void SavePlaylists()
{
try
{
string json = JsonSerializer.Serialize(playlists.ToArray());
File.WriteAllText("playlists.json", json);
}
catch (Exception ex)
{
MessageBox.Show($"保存播放列表时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void AddPlaylistButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new InputDialog("新建播放列表", "请输入播放列表名称:");
if (dialog.ShowDialog() == true && !string.IsNullOrWhiteSpace(dialog.ResponseText))
{
var playlist = new PlaylistModel
{
Name = dialog.ResponseText,
Songs = new ObservableCollection<SongModel>()
};
playlists.Add(playlist);
SavePlaylists();
}
}
private void AddSongsButton_Click(object sender, RoutedEventArgs e)
{
if (selectedPlaylist == null)
{
MessageBox.Show("请先选择一个播放列表", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var dialog = new OpenFileDialog
{
Filter = "音乐文件|*.mp3;*.wav;*.flac;*.m4a;*.aac;*.ogg;*.wma",
Multiselect = true
};
if (dialog.ShowDialog() == true)
{
foreach (string filePath in dialog.FileNames)
{
try
{
var file = TagLib.File.Create(filePath);
var song = new SongModel
{
Title = file.Tag.Title ?? Path.GetFileNameWithoutExtension(filePath),
Artist = file.Tag.Performers.Length > 0 ? string.Join(", ", file.Tag.Performers) : "未知艺术家",
Album = file.Tag.Album ?? "未知专辑",
Duration = TimeSpan.FromSeconds(file.Properties.Duration.TotalSeconds).ToString(@"mm\:ss"),
FilePath = filePath
};
selectedPlaylist.Songs.Add(song);
}
catch (Exception ex)
{
Console.WriteLine($"加载文件 {Path.GetFileName(filePath)} 时出错: {ex.Message}");
}
}
SavePlaylists();
}
}
private void PlayPlaylistButton_Click(object sender, RoutedEventArgs e)
{
if (selectedPlaylist != null && selectedPlaylist.Songs.Count > 0)
{
mainWindow.LoadPlaylist(selectedPlaylist);
Close();
}
else
{
MessageBox.Show("请先选择一个包含歌曲的播放列表", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private void DeletePlaylistButton_Click(object sender, RoutedEventArgs e)
{
if (selectedPlaylist != null)
{
if (MessageBox.Show($"确定要删除播放列表 '{selectedPlaylist.Name}' 吗?", "确认删除",
MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
playlists.Remove(selectedPlaylist);
selectedPlaylist = null;
SongsDataGrid.ItemsSource = null;
SavePlaylists();
}
}
else
{
MessageBox.Show("请先选择一个播放列表", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private void RemoveSongButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is SongModel song && selectedPlaylist != null)
{
selectedPlaylist.Songs.Remove(song);
SavePlaylists();
}
}
private void PlaylistsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedPlaylist = PlaylistsListBox.SelectedItem as PlaylistModel;
SongsDataGrid.ItemsSource = selectedPlaylist?.Songs;
}
}
public class PlaylistsData
{
public List<PlaylistData> Playlists { get; set; } = new List<PlaylistData>();
}
public class PlaylistData
{
public string Name { get; set; } = string.Empty;
public List<SongModel> Songs { get; set; } = new List<SongModel>();
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace MusicTool.Playlist
{
public class SongModel
{
public string Title { get; set; } = string.Empty;
public string Artist { get; set; } = string.Empty;
public string Album { get; set; } = string.Empty;
public string Duration { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public bool IsFavorite { get; set; }
public DateTime LastPlayed { get; set; }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -0,0 +1,149 @@
using System;
using System.IO;
using NAudio.Wave;
namespace MusicTool.Services
{
public class AudioPlayerService : IDisposable
{
private IWavePlayer? wavePlayer;
private AudioFileReader? audioFileReader;
private bool isDisposed = false;
public event EventHandler<PlaybackStoppedEventArgs>? PlaybackStopped;
public event EventHandler<EventArgs>? PlaybackStarted;
public event EventHandler<EventArgs>? PlaybackPaused;
public bool IsPlaying { get; private set; }
public TimeSpan CurrentTime => audioFileReader?.CurrentTime ?? TimeSpan.Zero;
public TimeSpan TotalTime => audioFileReader?.TotalTime ?? TimeSpan.Zero;
public float Volume
{
get => audioFileReader?.Volume ?? 1.0f;
set
{
if (audioFileReader != null)
{
audioFileReader.Volume = Math.Clamp(value, 0.0f, 1.0f);
}
}
}
public AudioPlayerService()
{
InitializePlayer();
}
private void InitializePlayer()
{
wavePlayer = new WaveOutEvent();
wavePlayer.PlaybackStopped += (s, e) =>
{
IsPlaying = false;
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs { Exception = e.Exception });
};
}
public void Play(string filePath)
{
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
throw new FileNotFoundException("音频文件不存在", filePath);
}
Stop();
try
{
audioFileReader = new AudioFileReader(filePath);
if (wavePlayer != null)
{
wavePlayer.Init(audioFileReader);
wavePlayer.Play();
IsPlaying = true;
PlaybackStarted?.Invoke(this, EventArgs.Empty);
}
}
catch (Exception ex)
{
throw new Exception($"播放音频文件时出错: {ex.Message}", ex);
}
}
public void Pause()
{
if (wavePlayer != null && IsPlaying)
{
wavePlayer.Pause();
IsPlaying = false;
PlaybackPaused?.Invoke(this, EventArgs.Empty);
}
}
public void Resume()
{
if (wavePlayer != null && !IsPlaying && audioFileReader != null)
{
wavePlayer.Play();
IsPlaying = true;
PlaybackStarted?.Invoke(this, EventArgs.Empty);
}
}
public void Stop()
{
if (wavePlayer != null)
{
wavePlayer.Stop();
}
if (audioFileReader != null)
{
audioFileReader.Dispose();
audioFileReader = null;
}
IsPlaying = false;
}
public void SetPosition(double positionPercent)
{
if (audioFileReader != null)
{
double positionSeconds = (positionPercent / 100.0) * audioFileReader.TotalTime.TotalSeconds;
audioFileReader.CurrentTime = TimeSpan.FromSeconds(positionSeconds);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
if (disposing)
{
Stop();
wavePlayer?.Dispose();
wavePlayer = null;
}
isDisposed = true;
}
}
~AudioPlayerService()
{
Dispose(false);
}
}
public class PlaybackStoppedEventArgs : EventArgs
{
public Exception? Exception { get; set; }
}
}

View File

@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using TagLib;
using File = System.IO.File;
using TagFile = TagLib.File;
namespace MusicTool.Services
{
public class MusicLibraryService
{
private ObservableCollection<SongModel> _songs = new ObservableCollection<SongModel>();
private ObservableCollection<string> _artists = new ObservableCollection<string>();
private ObservableCollection<string> _albums = new ObservableCollection<string>();
private List<string> _supportedExtensions = new List<string> { ".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg" };
public ObservableCollection<SongModel> Songs => _songs;
public ObservableCollection<string> Artists => _artists;
public ObservableCollection<string> Albums => _albums;
public async Task ScanDirectoryAsync(string directoryPath)
{
if (!Directory.Exists(directoryPath))
{
throw new DirectoryNotFoundException($"目录不存在: {directoryPath}");
}
await Task.Run(() =>
{
var files = Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories)
.Where(file => _supportedExtensions.Contains(Path.GetExtension(file).ToLower()))
.ToList();
foreach (var file in files)
{
try
{
var song = CreateSongFromFile(file);
if (song != null)
{
App.Current.Dispatcher.Invoke(() =>
{
_songs.Add(song);
if (!string.IsNullOrEmpty(song.Artist) && !_artists.Contains(song.Artist))
{
_artists.Add(song.Artist);
}
if (!string.IsNullOrEmpty(song.Album) && !_albums.Contains(song.Album))
{
_albums.Add(song.Album);
}
});
}
}
catch (Exception ex)
{
// 记录错误但继续处理其他文件
Console.WriteLine($"处理文件 {file} 时出错: {ex.Message}");
}
}
});
}
public SongModel? CreateSongFromFile(string filePath)
{
if (!File.Exists(filePath))
{
return null;
}
try
{
var file = TagFile.Create(filePath);
var song = new SongModel
{
Title = !string.IsNullOrEmpty(file.Tag.Title)
? file.Tag.Title
: Path.GetFileNameWithoutExtension(filePath),
Artist = file.Tag.Performers.Length > 0
? string.Join(", ", file.Tag.Performers)
: "未知艺术家",
Album = !string.IsNullOrEmpty(file.Tag.Album)
? file.Tag.Album
: "未知专辑",
Duration = TimeSpan.FromSeconds(file.Properties.Duration.TotalSeconds).ToString(@"mm\:ss"),
FilePath = filePath
};
return song;
}
catch
{
// 如果无法读取标签,至少创建一个基本的歌曲对象
return new SongModel
{
Title = Path.GetFileNameWithoutExtension(filePath),
Artist = "未知艺术家",
Album = "未知专辑",
Duration = "00:00",
FilePath = filePath
};
}
}
public List<SongModel> SearchSongs(string searchTerm)
{
if (string.IsNullOrEmpty(searchTerm))
{
return _songs.ToList();
}
searchTerm = searchTerm.ToLower();
return _songs.Where(song =>
song.Title.ToLower().Contains(searchTerm) ||
song.Artist.ToLower().Contains(searchTerm) ||
song.Album.ToLower().Contains(searchTerm)
).ToList();
}
public List<SongModel> GetSongsByArtist(string artist)
{
return _songs.Where(song => song.Artist == artist).ToList();
}
public List<SongModel> GetSongsByAlbum(string album)
{
return _songs.Where(song => song.Album == album).ToList();
}
public void AddSong(SongModel song)
{
if (song != null && !_songs.Any(s => s.FilePath == song.FilePath))
{
_songs.Add(song);
if (!string.IsNullOrEmpty(song.Artist) && !_artists.Contains(song.Artist))
{
_artists.Add(song.Artist);
}
if (!string.IsNullOrEmpty(song.Album) && !_albums.Contains(song.Album))
{
_albums.Add(song.Album);
}
}
}
public void RemoveSong(SongModel song)
{
if (song != null)
{
_songs.Remove(song);
// 如果没有其他歌曲使用相同的艺术家或专辑,则从列表中移除
if (!_songs.Any(s => s.Artist == song.Artist))
{
_artists.Remove(song.Artist);
}
if (!_songs.Any(s => s.Album == song.Album))
{
_albums.Remove(song.Album);
}
}
}
public void ClearLibrary()
{
_songs.Clear();
_artists.Clear();
_albums.Clear();
}
}
}

View File

@ -0,0 +1,92 @@
<Window x:Class="MusicTool.Settings.SettingsWindow"
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:MusicTool.Settings"
mc:Ignorable="d"
Title="设置" Height="400" Width="600"
WindowStartupLocation="CenterOwner">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 启动设置 -->
<GroupBox Header="启动设置" Grid.Row="0" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="StartWithWindowsCheckBox" Content="开机自启动"
Checked="StartWithWindowsCheckBox_Checked"
Unchecked="StartWithWindowsCheckBox_Unchecked"/>
<CheckBox x:Name="StartMinimizedCheckBox" Content="启动时最小化"
Margin="0,10,0,0"
Checked="StartMinimizedCheckBox_Checked"
Unchecked="StartMinimizedCheckBox_Unchecked"/>
</StackPanel>
</GroupBox>
<!-- 播放设置 -->
<GroupBox Header="播放设置" Grid.Row="1" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="AutoPlayCheckBox" Content="自动播放"
Checked="AutoPlayCheckBox_Checked"
Unchecked="AutoPlayCheckBox_Unchecked"/>
<CheckBox x:Name="RememberPositionCheckBox" Content="记住播放位置"
Margin="0,10,0,0"
Checked="RememberPositionCheckBox_Checked"
Unchecked="RememberPositionCheckBox_Unchecked"/>
</StackPanel>
</GroupBox>
<!-- 界面设置 -->
<GroupBox Header="界面设置" Grid.Row="2" Margin="0,0,0,20">
<StackPanel Margin="10">
<CheckBox x:Name="ShowAlbumArtCheckBox" Content="显示专辑封面"
Checked="ShowAlbumArtCheckBox_Checked"
Unchecked="ShowAlbumArtCheckBox_Unchecked"/>
<CheckBox x:Name="ShowLyricsCheckBox" Content="显示歌词"
Margin="0,10,0,0"
Checked="ShowLyricsCheckBox_Checked"
Unchecked="ShowLyricsCheckBox_Unchecked"/>
</StackPanel>
</GroupBox>
<!-- 快捷键设置 -->
<GroupBox Header="快捷键设置" Grid.Row="3" Margin="0,0,0,20">
<StackPanel Margin="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="播放/暂停:" Grid.Row="0" Grid.Column="0" Margin="0,0,10,0"/>
<TextBox x:Name="PlayPauseHotkeyTextBox" Grid.Row="0" Grid.Column="1" Margin="0,0,0,10"/>
<TextBlock Text="下一首:" Grid.Row="1" Grid.Column="0" Margin="0,0,10,0"/>
<TextBox x:Name="NextTrackHotkeyTextBox" Grid.Row="1" Grid.Column="1" Margin="0,0,0,10"/>
<TextBlock Text="上一首:" Grid.Row="2" Grid.Column="0" Margin="0,0,10,0"/>
<TextBox x:Name="PreviousTrackHotkeyTextBox" Grid.Row="2" Grid.Column="1"/>
</Grid>
</StackPanel>
</GroupBox>
<!-- 按钮 -->
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="SaveButton" Content="保存" Width="80" Margin="0,0,10,0"
Click="SaveButton_Click"/>
<Button x:Name="CancelButton" Content="取消" Width="80"
Click="CancelButton_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,194 @@
using System;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using System.Text.Json;
using System.IO;
namespace MusicTool.Settings
{
public partial class SettingsWindow : Window
{
private readonly MainWindow mainWindow;
private SettingsData settings;
public SettingsWindow(MainWindow mainWindow)
{
InitializeComponent();
this.mainWindow = mainWindow;
LoadSettings();
}
private void LoadSettings()
{
try
{
if (File.Exists("settings.json"))
{
string json = File.ReadAllText("settings.json");
settings = JsonSerializer.Deserialize<SettingsData>(json) ?? new SettingsData();
// 应用设置到UI
StartWithWindowsCheckBox.IsChecked = settings.StartWithWindows;
StartMinimizedCheckBox.IsChecked = settings.StartMinimized;
AutoPlayCheckBox.IsChecked = settings.AutoPlay;
RememberPositionCheckBox.IsChecked = settings.RememberPosition;
ShowAlbumArtCheckBox.IsChecked = settings.ShowAlbumArt;
ShowLyricsCheckBox.IsChecked = settings.ShowLyrics;
PlayPauseHotkeyTextBox.Text = settings.PlayPauseHotkey;
NextTrackHotkeyTextBox.Text = settings.NextTrackHotkey;
PreviousTrackHotkeyTextBox.Text = settings.PreviousTrackHotkey;
}
else
{
settings = new SettingsData();
}
}
catch (Exception ex)
{
MessageBox.Show($"加载设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void SaveSettings()
{
try
{
// 从UI获取设置
settings.StartWithWindows = StartWithWindowsCheckBox.IsChecked ?? false;
settings.StartMinimized = StartMinimizedCheckBox.IsChecked ?? false;
settings.AutoPlay = AutoPlayCheckBox.IsChecked ?? false;
settings.RememberPosition = RememberPositionCheckBox.IsChecked ?? false;
settings.ShowAlbumArt = ShowAlbumArtCheckBox.IsChecked ?? false;
settings.ShowLyrics = ShowLyricsCheckBox.IsChecked ?? false;
settings.PlayPauseHotkey = PlayPauseHotkeyTextBox.Text;
settings.NextTrackHotkey = NextTrackHotkeyTextBox.Text;
settings.PreviousTrackHotkey = PreviousTrackHotkeyTextBox.Text;
// 保存到文件
string json = JsonSerializer.Serialize(settings);
File.WriteAllText("settings.json", json);
// 更新主窗口设置
mainWindow.UpdateSettings(settings);
// 更新开机自启动设置
UpdateStartupSettings(settings.StartWithWindows);
MessageBox.Show("设置已保存", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"保存设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void UpdateStartupSettings(bool enable)
{
try
{
using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true))
{
if (enable)
{
key?.SetValue("MusicTool", System.Reflection.Assembly.GetExecutingAssembly().Location);
}
else
{
key?.DeleteValue("MusicTool", false);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"更新开机自启动设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void StartWithWindowsCheckBox_Checked(object sender, RoutedEventArgs e)
{
UpdateStartupSettings(true);
}
private void StartWithWindowsCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
UpdateStartupSettings(false);
}
private void StartMinimizedCheckBox_Checked(object sender, RoutedEventArgs e)
{
settings.StartMinimized = true;
}
private void StartMinimizedCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
settings.StartMinimized = false;
}
private void AutoPlayCheckBox_Checked(object sender, RoutedEventArgs e)
{
settings.AutoPlay = true;
}
private void AutoPlayCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
settings.AutoPlay = false;
}
private void RememberPositionCheckBox_Checked(object sender, RoutedEventArgs e)
{
settings.RememberPosition = true;
}
private void RememberPositionCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
settings.RememberPosition = false;
}
private void ShowAlbumArtCheckBox_Checked(object sender, RoutedEventArgs e)
{
settings.ShowAlbumArt = true;
}
private void ShowAlbumArtCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
settings.ShowAlbumArt = false;
}
private void ShowLyricsCheckBox_Checked(object sender, RoutedEventArgs e)
{
settings.ShowLyrics = true;
}
private void ShowLyricsCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
settings.ShowLyrics = false;
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
SaveSettings();
Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
public class SettingsData
{
public bool StartWithWindows { get; set; }
public bool StartMinimized { get; set; }
public bool AutoPlay { get; set; }
public bool RememberPosition { get; set; }
public bool ShowAlbumArt { get; set; }
public bool ShowLyrics { get; set; }
public string PlayPauseHotkey { get; set; } = "Space";
public string NextTrackHotkey { get; set; } = "Right";
public string PreviousTrackHotkey { get; set; } = "Left";
}
}

View File

@ -1,3 +1,60 @@
# MusicTool
# 音乐播放器 (MusicTool)
方晨鑫的音乐播放器
一个使用C#和WPF开发的现代化音乐播放器应用程序
## 功能特点
- 美观现代的用户界面使用Material Design风格
- 支持多种音频格式 (MP3, WAV, FLAC, M4A, AAC, OGG)
- 音乐库管理
- 播放列表功能
- 专辑和艺术家分类浏览
- 音乐搜索功能
- 音量控制和均衡器
- 支持多种播放模式 (顺序播放、循环播放、单曲循环、随机播放)
## 系统要求
- Windows 7 或更高版本
- .NET 6.0 或更高版本
- Visual Studio 2019/2022 (用于开发)
## 如何使用
1. 克隆或下载此仓库
2. 使用Visual Studio 2022打开解决方案文件 (MusicTool.sln)
3. 构建并运行应用程序
4. 点击"添加音乐文件夹"按钮导入您的音乐
## 开发说明
此项目使用以下技术和库:
- C# 和 WPF 用于UI和应用程序逻辑
- Material Design In XAML Toolkit 用于UI组件和样式
- NAudio 用于音频播放和处理
- TagLibSharp 用于读取音乐文件元数据
## 项目结构
- `Models/` - 数据模型类
- `ViewModels/` - 视图模型类
- `Views/` - 用户界面视图
- `Services/` - 服务类 (音频播放、音乐库管理等)
- `Resources/` - 应用程序资源 (图标、图片等)
## 待实现功能
- 歌词显示
- 音乐可视化效果
- 云同步功能
- 多语言支持
- 皮肤/主题切换
## 许可证
MIT
## 贡献
欢迎提交问题报告和功能请求。如果您想贡献代码请先创建一个issue讨论您想要更改的内容。