之前已经发了一篇博客简述了如何阅读这个项目,尽管这个项目已经开源很久了,但我找了很久都没有找到怎么从播放列表移除歌曲,那就自己动手实现,再提个 PR 吧。

运行起来应用后通过 Inspector(Ctrl+Shift+I)找到希望放置按钮的位置:专辑按钮的旁边。

第一步就是修改UI文件,把这个按钮显示出来。照葫芦画瓢,先加一个按钮。

<child>
<object class="GtkButton" id="remove_button">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="icon-name">app-remove-symbolic</property>
<property name="tooltip-text" translatable="yes">Remove song</property>
<signal name="clicked" handler="remove_button_clicked_cb" swapped="true" />
<style>
<class name="flat" />
</style>
</object>
</child>

每一个显示歌曲信息的行通常都有这样几个按钮:艺术家、收藏、专辑。然而在专辑的详细信息里面歌曲的信息就不需要专辑按钮了。行 Widget 是通用的,但是行中的一些 widget 是应该隐藏的。也就是说,移除歌曲按钮应该只在播放列表中才是可见的,移除按钮只显示在下面这个提交中实现:

https://github.com/wngtk/netease-cloud-music-gtk/commit/cb0a143f01c8d02a58b073677b94c14db0c18dfd

原来的代码中有大量的代码拷贝,我添加新的按钮的时候也尽可能的保持其他的地方不变,因此我需要在多处做拷贝粘贴再修改一下字符,导致了两处忘记修改而调试了两个小时。

而单独这一个 commit 中还没有很好的隐藏按钮,因为在代码拷贝的过程中有两处没有改名字,

f3d90c中修改好后这个按钮才显示在合理的地方。

第二步,实现移除正在播放歌曲。几乎所有的按钮的操作最后都是通过发送一个消息来通知 process_action(),因此我们也需要加入一个新的Action

有了新 Action 还只是能响应按钮,真正的业务逻辑还没实现。在这里简述实现的流程:

  1. 如果移除的歌曲是正在播放的歌曲,并且移除后还有歌曲可以播放,那么播放下一曲
  2. 从播放列表中移除想要移除的歌曲
  3. 更新播放列表的展示

流程确实简单,但是在实现的过程中遇到的细节却困扰了我很久。一个主要的问题是数据的更新并不会引发视图的更新。我需要指定调用更新才能更新视图。因为播放列表中完全存了另外一份数据,同样的歌曲列表的视图,和同样歌曲列表,播放列表视图完全拷贝了一份,所以要实现更新且不破坏原来的代码我只能重新初始化页面。

在胡乱加代码以测试到底为什么之前的修改没有失效的时候,还错误的设置了一个不存在的属性,引入了一个 bug,不过在调试器的帮助下轻松定位到崩溃发生的地方,删除不该有的代码便修复了。

总的来说实现过程是曲折的。最后附上一些在改代码的时候的吐槽。

点我看吐槽

因为代码大量使用了异步,调试的时候还是会有一些不方便,没有办法线性的跟踪,只能在处理信号的地方打断点,但是不知道信号是从哪里发起的。我的溯源的方法是查看按钮的回调函数,但是当自己尝试调用原有的 player_controls 的方法,就找不到额外的信号是怎么被发起的,例如我调用 player_controls 里面的 play() 方法播放器会发起一个播放下一曲的信号,但是实际上 play() 方法里面没有做下一曲的事情,那就是其他的注册的信号处理函数导致了播放下一曲,但是一时半会儿找不到具体的问题所在。或许响应按键操作用异步没什么不对,但是现在的代码中整个播放器的业务逻辑也依赖于异步通道(async_channel)传递信息。

因此,在现有的代码中,单纯的给 plyer_controls 指定一个歌曲去 play 不能正确做到,因为其中可能引发其他的异步信号,导致代码不能按照预期执行。这算是有隐藏的控制流。

作者没有使用 MVC,播放器的功能和 UI 是强耦合的,切换下一曲严重依赖图形界面上的按钮的回调函数。要避免意外情况出现,所有的功能,最好通过模拟 UI 操作进行(尽管这样不好,但是原来的代码里面就是这样做的)。尽管所有的异步的信号最终都会汇聚到 process_action() 函数里面来处理,但是如果单纯的操作 Action 往往和期望不一样,因为 Action 执行之前,有一些副作用发生。不通过模拟 UI 操作来实现移除歌曲的功能,实现的效果往往不如人意。例如

很遗憾,原有的代码并没有做视图(View)和数据(Model)的绑定, 数据改变后,视图并不能自动更新。换句话说,在删除播放列表的歌曲的业务逻辑实现后,UI 并没有自动更新。原来的代码用的是 ListBox,

花时间最多的部分是界面的显示,标记正在播放的图标要显示在正确的行,删除了的歌曲应该不再展示,删除播放列表歌曲的过程中导致歌词不显示了。在已有的接口和设计上,播放列表(playlist_lyrics)可能没有考虑过要修改,播放列表就提供的方法有:初始化,更新歌词,切换播放行。

脑袋中想着既然这里没有 MVC, 我就要手动更新视图。因此我尝试直接修改表达视图的对象,可惜的是最终以失败告终,因为一直存在着小问题。或许那种方法的效果是可行的,但是并没有很好的将已有的代码利用起来。因为整个播放器的控制是由 player_controls 和 window 两个对象控制的,有的操作放到 player_controls 里面完成更方便,而为了利用现有的 window 对象已有的方法有的操作放到 window 会更方便。如果严格一点按照设计模式来做,PlyerControl 应该负责所有的数据的控制,不应该有些东西放到了 window 去做。

而移除的歌曲不再显示是通过手动做页面路由,将 Action::ToSongListPage 执行的部分逻辑再执行一次,而不退出当前的播放列表页面。为了避免破坏原有的代码,不修改 Action::ToSongListPage,然而即便是模拟了重新进入页面,依旧会出现没有正确标记正在播放的歌曲。我没有找到问题的根源,因为一切看起来都是正常的,也没有什么头绪去定位问题到底是什么。

最后没有正确标记的解决方法是由 player_controls 移除歌曲后发起一个Action::UpdatePlayListStatus(playlist.get_position(),因为 player_controls 已经有代码使用了 playlist.get_position(),而 UpdatePlayListStatus 又需要一个 position,就只好将这一部分的代码放在 player_controls。相当于是最后又委派给全局来实现。这里的做法来自于下一曲按钮的回调函数会发送 UpdatePlayListStatus。

sender
.send_blocking(
Action::UpdatePlayListStatus(playlist.get_position())
).unwrap();

改进NeteaseCloudMusicGtk4:添加移除歌曲按钮的更多相关文章

  1. VC6IDE环境宏辅助添加移除注释

    VC6很老了(15年),当年的IDE功能不如现在的各种IDE功能丰富. 比如自动添加注释,就需要借助第三方插件或自己动手实现. 最近做些code试验,新装上了VC6,但是改代码时不能自动添加注释,很不 ...

  2. a标签添加移除事件及开启禁用事件

    一.添加移除点击事件 <script type="text/javascript" src="jquery.min.js"></script& ...

  3. Android标题栏上添加多个Menu按钮

    最近项目中碰到要在Android Menu旁边再添加一个按钮,而不是点击menu按钮然后在弹出一些选项. MainActivity代码: public class MainActivity exten ...

  4. magento产品成功添加到购物车后跳转到不同页面 添加 add to cart 按钮

    1 添加产品到购物车成功后是跳转到购物车页面或不跳转.这个在后台可以设置 system -> configuration -> After Adding a Product Redirec ...

  5. 如何为 Drupal 7 网站添加悬浮的反馈按钮?

    最近有客户咨询我们要怎么为 Drupal 网站添加悬浮按钮,方便访客能够链接到反馈表单页面.很幸运,使用 Feedback Simple 模块可以很容易实现. 在这篇短教程中,我将和大家分享如何添加链 ...

  6. 6. 添加messager.alert()确定按钮的回调函数,即点完确定按钮后触发的事件

    添加messager.alert()确定按钮的回调函数,即点完确定按钮后触发的事件: $.messager.alert('提示信息', "请联系管理员处理!", 'info', f ...

  7. WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮

    原文:WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮 在边框中加入一些元素,在应用程序的界面设计中,已经开始流行起来.特别是在浏览器(Crome,IE,Firefox,Opera)中都有应用. ...

  8. 海思3531添加移远EC20 4g模块(转)

    源: 海思3531添加移远EC20 4g模块 Hi3798移植4G模块(移远EC20)

  9. Jquery添加移除样式

    获取与设置样式 获取class和设置class都可以使用attr()方法来完成.例如使用attr()方法来获取p元素的class,JQuery代码如下: var p_class = $("p ...

  10. JS添加/移除事件

    事件的传播方式 <div id="father"> <div id="son"></div> </div> &l ...

随机推荐

  1. nginx 简单实践:Web 缓存【nginx 实践系列之三】

    〇.前言 本文为 nginx 简单实践系列文章之二,主要简单实践了两个内容:正向代理.反向代理,仅供参考. 关于 Nginx 基础,以及安装和配置详解,可以参考博主过往文章: https://www. ...

  2. 《Vue2 框架第二课:组件结构与模板语法详解》

    写在开头:Vue.js 是一个流行的前端框架,广泛应用于构建用户界面和单页应用(SPA).然而,需要注意的是,Vue2 已经于 2023 年底 正式停止维护.这意味着官方团队将不再为 Vue2 提供功 ...

  3. MyBatis mapper.xml中SQL处理小于号与大于号 和小于等于号

    这种问题在xml处理sql的程序中经常需要我们来进行特殊处理. 其实很简单,我们只需作如下替换即可避免上述的错误: < <= > >= & ' " < ...

  4. luogu-P3262题解

    简要题意 有一棵不超过十层的满二叉树,需要对每个节点进行染色.每个叶子节点会对其颜色相同的祖先节点产生贡献且黑白贡献不同.求最大贡献. 题解 首先我会暴力!我如果直接暴力枚举每个节点的颜色,复杂度就是 ...

  5. 使用QT开发远程linux服务器过程

    1.添加设备为通用linux 2.设置ip用户名 3.创建私钥文件,原来有的qtc那俩个文件删掉. 4.部署公钥,前提是测试链接要出现成功 5.在kits里添加编译环境设置编译器为32位或者64 6. ...

  6. netcore后台服务慎用BackgroundService

    在 .NET Core 开发中,BackgroundService 是一个非常方便的后台任务运行方式,但它并不适用于所有场景. BackgroundService 一时爽,并发火葬场. Backgro ...

  7. Windows 提权-MSSQL

    本文通过 Google 翻译 MSSQL – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充. 导航 0 前言 ...

  8. 【数学公式】mathtype和word2016集成

    mathtype 安装好了以后,word 没有相应的选项卡怎么办? 问题 解决办法 找到word的启动路径 2. 找到mathtype 安装好后的mathpage文件夹 进入文件夹,找到MathPag ...

  9. The selected directory is not a valid home for Go SDK

    前言 The selected directory is not a valid home for Go SDK 出现这个错误的原因是 idea 的 Go-plugin 插件,和 Go 的sdk版本不 ...

  10. go generate

    介绍 go generate 命令是go 1.4版本里面新添加的一个命令,当运行 go generate 时,它将扫描与当前包相关的源代码文件,找出所有包含 //go:generate 的特殊注释,提 ...