初始化
This commit is contained in:
parent
5dd06ac4fd
commit
d1f082cc80
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal 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
24
MusicTool.sln
Normal 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
43
MusicTool/App.xaml
Normal 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
32
MusicTool/App.xaml.cs
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
27
MusicTool/Converters/BoolToHeartIconConverter.cs
Normal file
27
MusicTool/Converters/BoolToHeartIconConverter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
MusicTool/Equalizer/EqualizerSampleProvider.cs
Normal file
56
MusicTool/Equalizer/EqualizerSampleProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
MusicTool/Equalizer/EqualizerWindow.xaml
Normal file
81
MusicTool/Equalizer/EqualizerWindow.xaml
Normal 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>
|
||||
79
MusicTool/Equalizer/EqualizerWindow.xaml.cs
Normal file
79
MusicTool/Equalizer/EqualizerWindow.xaml.cs
Normal 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
320
MusicTool/MainWindow.xaml
Normal 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
1279
MusicTool/MainWindow.xaml.cs
Normal file
File diff suppressed because it is too large
Load Diff
101
MusicTool/Models/SongModel.cs
Normal file
101
MusicTool/Models/SongModel.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
36
MusicTool/MusicTool.csproj
Normal file
36
MusicTool/MusicTool.csproj
Normal 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>
|
||||
40
MusicTool/Playlist/InputDialog.xaml
Normal file
40
MusicTool/Playlist/InputDialog.xaml
Normal 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>
|
||||
35
MusicTool/Playlist/InputDialog.xaml.cs
Normal file
35
MusicTool/Playlist/InputDialog.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
MusicTool/Playlist/PlaylistModel.cs
Normal file
13
MusicTool/Playlist/PlaylistModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
71
MusicTool/Playlist/PlaylistWindow.xaml
Normal file
71
MusicTool/Playlist/PlaylistWindow.xaml
Normal 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>
|
||||
192
MusicTool/Playlist/PlaylistWindow.xaml.cs
Normal file
192
MusicTool/Playlist/PlaylistWindow.xaml.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
15
MusicTool/Playlist/SongModel.cs
Normal file
15
MusicTool/Playlist/SongModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
BIN
MusicTool/Resources/Icons/app.ico
Normal file
BIN
MusicTool/Resources/Icons/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
149
MusicTool/Services/AudioPlayerService.cs
Normal file
149
MusicTool/Services/AudioPlayerService.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
184
MusicTool/Services/MusicLibraryService.cs
Normal file
184
MusicTool/Services/MusicLibraryService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
92
MusicTool/Settings/SettingsWindow.xaml
Normal file
92
MusicTool/Settings/SettingsWindow.xaml
Normal 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>
|
||||
194
MusicTool/Settings/SettingsWindow.xaml.cs
Normal file
194
MusicTool/Settings/SettingsWindow.xaml.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
61
README.md
61
README.md
@ -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讨论您想要更改的内容。
|
||||
Loading…
x
Reference in New Issue
Block a user