当前位置: 首页 > news >正文

跨平台WPF音乐商店应用程序

目录

一 简介

二 设计思路

三 源码


一 简介

支持在线检索音乐,支持实时浏览当前收藏的音乐及音乐数据的持久化。

二 设计思路

采用MVVM架构,前后端分离,子界面弹出始终位于主界面的中心。

三 源码

视窗引导启动源码:

namespace Avalonia.MusicStore
{public class ViewLocator : IDataTemplate{public Control? Build(object? data){if (data is null)return null;var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);var type = Type.GetType(name);if (type != null){var control = (Control)Activator.CreateInstance(type)!;control.DataContext = data;return control;}return new TextBlock { Text = "Not Found: " + name };}public bool Match(object? data){return data is ViewModelBase;}}
}using Avalonia;
using Avalonia.ReactiveUI;
using System;namespace Avalonia.MusicStore
{internal sealed class Program{// Initialization code. Don't use any Avalonia, third-party APIs or any// SynchronizationContext-reliant code before AppMain is called: things aren't initialized// yet and stuff might break.[STAThread]public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);// Avalonia configuration, don't remove; also used by visual designer.public static AppBuilder BuildAvaloniaApp()=> AppBuilder.Configure<App>().UsePlatformDetect().WithInterFont().LogToTrace().UseReactiveUI();}
}

模型源码:

using iTunesSearch.Library;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;namespace Avalonia.MusicStore.Models
{public class Album{private static iTunesSearchManager s_SearchManager = new();public string Artist { get; set; }public string Title { get; set; }public string CoverUrl { get; set; }public Album(string artist, string title, string coverUrl){Artist = artist;Title = title;CoverUrl = coverUrl;}public static async Task<IEnumerable<Album>> SearchAsync(string searchTerm){var query = await s_SearchManager.GetAlbumsAsync(searchTerm).ConfigureAwait(false);return query.Albums.Select(x =>new Album(x.ArtistName, x.CollectionName,x.ArtworkUrl100.Replace("100x100bb", "600x600bb")));}private static HttpClient s_httpClient = new();private string CachePath => $"./Cache/{Artist} - {Title}";public async Task<Stream> LoadCoverBitmapAsync(){if (File.Exists(CachePath + ".bmp")){return File.OpenRead(CachePath + ".bmp");}else{var data = await s_httpClient.GetByteArrayAsync(CoverUrl);return new MemoryStream(data);}}public async Task SaveAsync(){if (!Directory.Exists("./Cache")){Directory.CreateDirectory("./Cache");}using (var fs = File.OpenWrite(CachePath)){await SaveToStreamAsync(this, fs);}}public Stream SaveCoverBitmapStream(){return File.OpenWrite(CachePath + ".bmp");}private static async Task SaveToStreamAsync(Album data, Stream stream){await JsonSerializer.SerializeAsync(stream, data).ConfigureAwait(false);}public static async Task<Album> LoadFromStream(Stream stream){return (await JsonSerializer.DeserializeAsync<Album>(stream).ConfigureAwait(false))!;}public static async Task<IEnumerable<Album>> LoadCachedAsync(){if (!Directory.Exists("./Cache")){Directory.CreateDirectory("./Cache");}var results = new List<Album>();foreach (var file in Directory.EnumerateFiles("./Cache")){if (!string.IsNullOrWhiteSpace(new DirectoryInfo(file).Extension)) continue;await using var fs = File.OpenRead(file);results.Add(await Album.LoadFromStream(fs).ConfigureAwait(false));}return results;}}
}

模型视图源码:

using Avalonia.Media.Imaging;
using Avalonia.MusicStore.Models;
using ReactiveUI;
using System.Threading.Tasks;namespace Avalonia.MusicStore.ViewModels
{public class AlbumViewModel : ViewModelBase{private readonly Album _album;public AlbumViewModel(Album album){_album = album;}public string Artist => _album.Artist;public string Title => _album.Title;private Bitmap? _cover;public Bitmap? Cover{get => _cover;private set => this.RaiseAndSetIfChanged(ref _cover, value);}public async Task LoadCover(){await using (var imageStream = await _album.LoadCoverBitmapAsync()){Cover = await Task.Run(() => Bitmap.DecodeToWidth(imageStream, 400));}}public async Task SaveToDiskAsync(){await _album.SaveAsync();if (Cover != null){var bitmap = Cover;await Task.Run(() =>{using (var fs = _album.SaveCoverBitmapStream()){bitmap.Save(fs);}});}}}
}
using Avalonia.MusicStore.Models;
using ReactiveUI;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Windows.Input;namespace Avalonia.MusicStore.ViewModels
{public class MainWindowViewModel : ViewModelBase{public ICommand BuyMusicCommand { get; }public Interaction<MusicStoreViewModel, AlbumViewModel?> ShowDialog { get; }public ObservableCollection<AlbumViewModel> Albums { get; } = new();public MainWindowViewModel(){ShowDialog = new Interaction<MusicStoreViewModel, AlbumViewModel?>();BuyMusicCommand = ReactiveCommand.CreateFromTask(async () =>{var store = new MusicStoreViewModel();var result = await ShowDialog.Handle(store);if (result != null){Albums.Add(result);await result.SaveToDiskAsync();}});RxApp.MainThreadScheduler.Schedule(LoadAlbums);}private async void LoadAlbums(){var albums = (await Album.LoadCachedAsync()).Select(x => new AlbumViewModel(x));foreach (var album in albums){Albums.Add(album);}foreach (var album in Albums.ToList()){await album.LoadCover();}}}
}
using Avalonia.MusicStore.Models;
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading;namespace Avalonia.MusicStore.ViewModels
{public class MusicStoreViewModel : ViewModelBase{private string? _searchText;private bool _isBusy;public string? SearchText{get => _searchText;set => this.RaiseAndSetIfChanged(ref _searchText, value);}public bool IsBusy{get => _isBusy;set => this.RaiseAndSetIfChanged(ref _isBusy, value);}private AlbumViewModel? _selectedAlbum;public ObservableCollection<AlbumViewModel> SearchResults { get; } = new();public AlbumViewModel? SelectedAlbum{get => _selectedAlbum;set => this.RaiseAndSetIfChanged(ref _selectedAlbum, value);}public MusicStoreViewModel(){this.WhenAnyValue(x => x.SearchText).Throttle(TimeSpan.FromMilliseconds(400)).ObserveOn(RxApp.MainThreadScheduler).Subscribe(DoSearch!);BuyMusicCommand = ReactiveCommand.Create(() =>{return SelectedAlbum;});}private async void DoSearch(string s){IsBusy = true;SearchResults.Clear();_cancellationTokenSource?.Cancel();_cancellationTokenSource = new CancellationTokenSource();var cancellationToken = _cancellationTokenSource.Token;if (!string.IsNullOrWhiteSpace(s)){var albums = await Album.SearchAsync(s);foreach (var album in albums){var vm = new AlbumViewModel(album);SearchResults.Add(vm);}if (!cancellationToken.IsCancellationRequested){LoadCovers(cancellationToken);}}IsBusy = false;}private async void LoadCovers(CancellationToken cancellationToken){foreach (var album in SearchResults.ToList()){await album.LoadCover();if (cancellationToken.IsCancellationRequested){return;}}}private CancellationTokenSource? _cancellationTokenSource;public ReactiveCommand<Unit, AlbumViewModel?> BuyMusicCommand { get; }}
}
using ReactiveUI;namespace Avalonia.MusicStore.ViewModels
{public class ViewModelBase : ReactiveObject{}
}

视图源码:

<UserControlx:Class="Avalonia.MusicStore.Views.AlbumView"xmlns="https://github.com/avaloniaui"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:vm="using:Avalonia.MusicStore.ViewModels"Width="200"d:DesignHeight="450"d:DesignWidth="800"x:DataType="vm:AlbumViewModel"mc:Ignorable="d"><StackPanel Width="200" Spacing="5"><Border ClipToBounds="True" CornerRadius="10"><Panel Background="#7FFF22DD"><ImageWidth="200"Source="{Binding Cover}"Stretch="Uniform" /><Panel Height="200" IsVisible="{Binding Cover, Converter={x:Static ObjectConverters.IsNull}}"><PathIconWidth="75"Height="75"Data="{StaticResource music_regular}" /></Panel></Panel></Border><TextBlock HorizontalAlignment="Center" Text="{Binding Title}" /><TextBlock HorizontalAlignment="Center" Text="{Binding Artist}" /></StackPanel>
</UserControl>
<Windowx:Class="Avalonia.MusicStore.Views.MainWindow"xmlns="https://github.com/avaloniaui"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:views="clr-namespace:Avalonia.MusicStore.Views"xmlns:vm="using:Avalonia.MusicStore.ViewModels"Title="Avalonia.MusicStore"d:DesignHeight="450"d:DesignWidth="800"x:DataType="vm:MainWindowViewModel"Background="Transparent"ExtendClientAreaToDecorationsHint="True"Icon="/Assets/avalonia-logo.ico"TransparencyLevelHint="AcrylicBlur"WindowStartupLocation="CenterScreen"mc:Ignorable="d"><Panel><ExperimentalAcrylicBorder IsHitTestVisible="False"><ExperimentalAcrylicBorder.Material><ExperimentalAcrylicMaterialBackgroundSource="Digger"MaterialOpacity="0.65"TintColor="Black"TintOpacity="1" /></ExperimentalAcrylicBorder.Material></ExperimentalAcrylicBorder><Panel Margin="40"><ButtonHorizontalAlignment="Right"VerticalAlignment="Top"Command="{Binding BuyMusicCommand}"><PathIcon Data="{StaticResource store_microsoft_regular}" /></Button><ItemsControl Margin="0,40,0,0" ItemsSource="{Binding Albums}"><ItemsControl.ItemsPanel><ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate></ItemsControl.ItemsPanel><ItemsControl.ItemTemplate><DataTemplate><views:AlbumView Margin="0,0,20,20" /></DataTemplate></ItemsControl.ItemTemplate></ItemsControl></Panel></Panel></Window>
using Avalonia.MusicStore.ViewModels;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System.Threading.Tasks;namespace Avalonia.MusicStore.Views
{public partial class MainWindow : ReactiveWindow<MainWindowViewModel>{public MainWindow(){InitializeComponent();this.WhenActivated(action => action(ViewModel!.ShowDialog.RegisterHandler(DoShowDialogAsync)));}private async Task DoShowDialogAsync(InteractionContext<MusicStoreViewModel,AlbumViewModel?> interaction){var dialog = new MusicStoreWindow();dialog.DataContext = interaction.Input;var result = await dialog.ShowDialog<AlbumViewModel?>(this);interaction.SetOutput(result);}}
}
<UserControlx:Class="Avalonia.MusicStore.Views.MusicStoreView"xmlns="https://github.com/avaloniaui"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:vm="using:Avalonia.MusicStore.ViewModels"d:DesignHeight="450"d:DesignWidth="800"x:DataType="vm:MusicStoreViewModel"mc:Ignorable="d"><DockPanel><StackPanel DockPanel.Dock="Top"><TextBox Text="{Binding SearchText}" Watermark="Search for Albums...." /><ProgressBar IsIndeterminate="True" IsVisible="{Binding IsBusy}" /></StackPanel><ButtonHorizontalAlignment="Center"Command="{Binding BuyMusicCommand}"Content="Buy Album"DockPanel.Dock="Bottom" /><ListBoxMargin="0,20"Background="Transparent"ItemsSource="{Binding SearchResults}"SelectedItem="{Binding SelectedAlbum}"><ListBox.ItemsPanel><ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate></ListBox.ItemsPanel></ListBox></DockPanel>
</UserControl>
<Windowx:Class="Avalonia.MusicStore.Views.MusicStoreWindow"xmlns="https://github.com/avaloniaui"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:views="using:Avalonia.MusicStore.Views"Title="MusicStoreWindow"Width="1000"Height="550"ExtendClientAreaToDecorationsHint="True"TransparencyLevelHint="AcrylicBlur"WindowStartupLocation="CenterOwner"mc:Ignorable="d"><Panel><ExperimentalAcrylicBorder IsHitTestVisible="False"><ExperimentalAcrylicBorder.Material><ExperimentalAcrylicMaterialBackgroundSource="Digger"MaterialOpacity="0.65"TintColor="Black"TintOpacity="1" /></ExperimentalAcrylicBorder.Material></ExperimentalAcrylicBorder><Panel Margin="40"><views:MusicStoreView /></Panel></Panel>
</Window>
using Avalonia.MusicStore.ViewModels;
using Avalonia.ReactiveUI;
using ReactiveUI;
using System;namespace Avalonia.MusicStore.Views
{public partial class MusicStoreWindow : ReactiveWindow<MusicStoreViewModel>{public MusicStoreWindow(){InitializeComponent();this.WhenActivated(action => action(ViewModel!.BuyMusicCommand.Subscribe(Close)));}}
}

http://www.lryc.cn/news/404087.html

相关文章:

  • 设计模式简述(一)
  • OSI参考模型:解析网络通信的七层框架
  • QT通用配置文件库(QPreferences)
  • 如何搭建一个RADIUS服务器?
  • 双机热备综合实验
  • Java和Python的图结构如何实现图的深度优先搜索算法
  • Web学习day05
  • LINUX客户端client(socket、connect)实现客户端发送,服务器接收
  • 【网络安全科普】勒索病毒 防护指南
  • TFHE库,fftw和googletest库安装
  • 关于Spring Boot IOCDC,看这一篇就够了
  • Model Import Settings
  • 腾讯云COS托管静态网站,以及如何解决访问出现了下载网页的情况
  • 软件设计模式: 抽象工厂
  • 使用Vuepress搭建个人网站
  • lua 写一个 不同时区之间转换日期和时间 函数
  • 谷粒商城——session共享
  • Java 语言及其常用集合类的操作,以及反射机制与注解
  • 《系统架构设计师教程(第2版)》第12章-信息系统架构设计理论与实践-02-信息系统架构
  • 用html做python教程01
  • PHP接口与性状的优雅应用
  • R语言模型评估网格搜索
  • Haproxy服务
  • Unity VR开发入门:探索虚拟现实世界的无限可能
  • 系统架构设计师教程(清华第二版) 第3章 信息系统基础知识-3.2 业务处理系统-解读
  • 32_ConvNeXt网络详解
  • Langchain[3]:Langchain架构演进与功能扩展:流式事件处理、事件过滤机制、回调传播策略及装饰器应用
  • java导出PDF详细教程+各种踩坑
  • 【博士每天一篇文献-算法】连续学习算法之HNet:Continual learning with hypernetworks
  • 使用 tcpdump 进行网络流量捕获与分析