跨平台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)));}}
}
相关文章:
跨平台WPF音乐商店应用程序
目录 一 简介 二 设计思路 三 源码 一 简介 支持在线检索音乐,支持实时浏览当前收藏的音乐及音乐数据的持久化。 二 设计思路 采用MVVM架构,前后端分离,子界面弹出始终位于主界面的中心。 三 源码 视窗引导启动源码: namesp…...
设计模式简述(一)
定义:设计模式指的是在软件开发过程中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。创建型设计模式关注对象的创建过程,提供了更灵活、可扩展的对象创建机制。结构型设计模式用于解决…...
OSI参考模型:解析网络通信的七层框架
引言 在现代计算机网络中,OSI(开放式系统互联)参考模型是理解和设计网络通信协议的基础。1978年由国际标准化组织(ISO)提出,OSI模型定义了网络通信的七层结构,每一层都承担着特定的功能&#x…...
QT通用配置文件库(QPreferences)
QT通用配置文件库(QPreferences) QPreferences项目是基于nlohmann/json的qt可视化配置文件库,将配置保存成json格式,并提供UI查看与修改,可通过cmake可快速添加进项目。默认支持基本类型、stl常用容器、基本类型与stl容器组成的结构体&#…...
如何搭建一个RADIUS服务器?
1. 系统环境 1.1.操作系统 Ubuntu-20.04.1 (kernel: 5.15.0-58-generic) 1.2.所需软件 FreeRADIUS MariaDB 1.3.注意事项 本文提到的所有操作,都是以root 身份执行; 2. FreeRADIUS的安装 2.1. 安装FreeRADIUS服务器程序 以…...
双机热备综合实验
1,对现有网络进行改造升级,将当个防火墙组网改成双机热备的组网形式,做负载分担模式,游客区和DMZ区走FW3,生产区和办公区的流量走FW1 2,办公区上网用户限制流量不超过100M,其中销售部人员在其基…...
Java和Python的图结构如何实现图的深度优先搜索算法
Java和Python的图结构如何实现图的深度优先搜索算法? 在Java和Python中,实现深度优先搜索(DFS)算法的基本思路都是通过递归或栈来探索图的各个节点。 Java实现DFS:Java import java.util.ArrayList; import java.uti…...
Web学习day05
html&css 目录 html&css 文章目录 一、web开发 1.1工作流程 1.2开发技术 二、HTML 2.1HTML规范 2.2基础标签 2.2.1标题 2.2.2水平线 2.2.3段落和换行 2.2.4文字效果 2.2.5超链接 2.2.6图像 2.2.7音频和视频 三、布局标签 3.1列表 3.2容器 3.3表格 3…...
LINUX客户端client(socket、connect)实现客户端发送,服务器接收
SERVICE端见前一篇文章 5. 客户端连接函数 connect()(与前面的bind一样) int connect (int sockfd, struct sockaddr * serv_addr, int addrlen) 参数: sockfd: 通过 socket() 函数拿到的 fd addr:struct sockaddr 的结构体变量地址 addr…...
【网络安全科普】勒索病毒 防护指南
勒索病毒简介 勒索病毒是一种恶意软件,也称为勒索软件(Ransomware),其主要目的是在感染计算机后加密用户文件,并要求用户支付赎金以获取解密密钥。这种类型的恶意软件通常通过电子邮件附件、恶意链接、下载的软件或漏洞…...
TFHE库,fftw和googletest库安装
点个关注吧!本文主要关注于TFHE的安装与常见的问题 1.TFHE的git链接: https://github.com/tfhe/tfhe git clone --recurse-submodules --branchmaster https://github.com/tfhe/tfhe.git 2.安装 mkdir build cd build cmake ../src -DENABLE_TESTSon -D…...
关于Spring Boot IOCDC,看这一篇就够了
一,Spring是什么及常用注解 先说什么是spring,在前面的博客中已经知道了,spring是一个开源框架,为了让我们开发更加简单,那关于ioc呢,一句话概况一下:Spring就是包含了众多工具方法的Ioc容器 …...
Model Import Settings
前言 在可视化3D世界中,模型是3D世界的核心,你可以没有贴图,可以没有特效,甚至可以没有用户交互界面,但必须得有模型来描述世界的基本样貌。 在3D世界中,由点线面构成了模型的轮廓;由UV和纹理&a…...
腾讯云COS托管静态网站,以及如何解决访问出现了下载网页的情况
腾讯云对象存储(Cloud Object Storage,简称COS),与其他云厂商所提供的云对象存储都是面向非结构化数据,只是每个云厂商的叫法有别于他家,或许是更能彰显厂商的品牌吧! 但不管云厂商怎么给云对象…...
软件设计模式: 抽象工厂
抽象工厂 一、解决的问题 抽象工厂模式主要解决了在具有多个产品族的情况下,如何统一管理创建相关产品对象的问题。 当系统需要创建一系列相互关联或相互依赖的对象,并且这些对象可以形成多个不同的产品族时,如果直接由客户端去分别创建这…...
使用Vuepress搭建个人网站
网站地址:bloggo.chat...
lua 写一个 不同时区之间转换日期和时间 函数
这个函数用于调整时间戳以适应不同的时区。它接受五个参数:format、timeStamp、dontFixForTimeOffset、currentServerTimeZone和showLog。返回 os.date,可以转化成指定格式的年月日时间 ### 功能 该函数的主要功能是根据给定的时区偏移量调整时间戳&am…...
谷粒商城——session共享
问题1 一个系统中不同微服务的session共享。 问题1的解决办法 1. session复制的方法:微服务的副本之间通过通信共享session。这样每一个微服务的副本都会保存所有的session。(缺点:造成大量的通信,多处额外的通信开销。&#x…...
Java 语言及其常用集合类的操作,以及反射机制与注解
目录 一、Java 语言概述 二、Java 集合框架 ArrayList 操作示例: HashMap 操作示例: 三、反射机制 1. 反射的示例 五、总结 Java 是一种广泛使用的高级编程语言,因其平台独立性、简洁性及丰富的 API 而备受开发者青睐。 一、Java 语言…...
《系统架构设计师教程(第2版)》第12章-信息系统架构设计理论与实践-02-信息系统架构
文章目录 1. 概述1.1 信息系统架构(ISA)1.2 架构风格 2. 信息系统架构分类2.1 信息系统物理结构2.1.1 集中式结构2.1.2 分布式结构 2.2 信息系统的逻辑结构1)横向综合2)纵向综合3)纵横综合 3. 信息系统架构的一般原理4…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
QT开发技术【ffmpeg + QAudioOutput】音乐播放器
一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音…...
