Mvvm Light Toolkit for wpf/silverlight系列之Messenger

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

在开发Wpf/SL应用时,经常会遇到不同页面和窗体之间的参数传递的问题。对于这类问题,我们一般通过事件实现数据传递,也可以定义全局静态变量来进行数据共享。这里我们则使用了另外一种非常高效而优雅的方法来进行消息传递,这里我称之为Messenger,事实上,Messenger并非mvvm的专利,我们可以把它看作一种设计模式,你可以在其它.net程序中使用它。

 

一、Mvvm Light Messenger是什么

 

通过Mvvm Light源码我们可以知道Messenger的实现细节,如果你现在还不能理解这些代码也没关系,很多东西理解起来远比使用起来难,Messenger也是如此,它使用起来很简单,由于Messenger只公开了一些消息注册和发送方法,使用者一看便知方法的功能,而只需关注要发送的数据和接收的对象就可以了。

发送: 

[c-sharp]  view plain copy
  1. Messenger.Default.Send<bool?>(true);  

接收:

[c-sharp]  view plain copy
  1. Messenger.Default.Register<bool?>(this, m => this.DialogResult = m);  

这是最基本的用法,发送方发送了一个bool?类型的对象(值为true),这样任何只要注册了bool?类型消息的地方都可以接收到这个消息。

  • Send泛型方法很好理解,只是发送一个值为true的bool?类型的对象;
  • Register泛型方法接受2个参数,第一个是接受者,也就是消息的载体,通常是对象本身(this),当然也可以是其他已实例化的对象,第二个参数是Action类型的对象,是接收到消息后执行的方法委托

Register方法实际上将对象和Action方法添加到全局的字典集合当中,只不过他们关系是弱引用的关系,在Send方法获取对象引用,同时执行Action方法,有关弱引用的介绍,参考弱引用

 

Messenger通过全局的字典集合来保存弱引用关系,因此在对象不使用时,我们要养成清理的习惯,调用Unregister来从字典集合中移除引用关系。

[c-sharp]  view plain copy
  1. Messenger.Default.Unregister(this);  

 

二、应用示例

 

下面我们会通过登录界面实现和简单的列表增删改的功能来演示Messenger的用法:

1、登录部分:

首先创建LoginViewModel,类定义如下:

WPF:

[c-sharp]  view plain copy
  1. #region ICommand  
  2.   
  3.       public RelayCommand<object> LoginCommand  
  4.       {  
  5.           get   
  6.           {  
  7.               return new RelayCommand<object>(  
  8.                   (p) =>   
  9.                   {  
  10.                       System.Windows.Controls.PasswordBox pb = p as System.Windows.Controls.PasswordBox;  
  11.   
  12.                       bool isLogon = false;  
  13.   
  14.                       // 登录成功  
  15.                       if (_userName == "admin" && pb.Password == "123")  
  16.                           isLogon = true;  
  17.                       else  
  18.                           isLogon = false;  
  19.                         
  20.                       // 发送消息  
  21.                       Messenger.Default.Send<bool?>(isLogon);   
  22.                   }  
  23.                   );   
  24.           }  
  25.       }  
  26.   
  27.       public RelayCommand CancelCommand  
  28.       {  
  29.           get  
  30.           {  
  31.               return new RelayCommand(  
  32.                   () =>   
  33.                   {  
  34.                       // 发送消息  
  35.                       Messenger.Default.Send<bool?>(null);   
  36.                   }  
  37.                   );  
  38.           }  
  39.       }  
  40.        
  41.       #endregion  
  42.  
  43.       #region 公共属性  
  44.       public const string UserNamePropertyName = "UserName";  
  45.   
  46.       private string _userName = "";  
  47.   
  48.       public string UserName  
  49.       {  
  50.           get  
  51.           {  
  52.               return _userName;  
  53.           }  
  54.           set  
  55.           {  
  56.               if (_userName == value)  
  57.               {  
  58.                   return;  
  59.               }  
  60.   
  61.               _userName = value;  
  62.   
  63.               // Update bindings, no broadcast  
  64.               RaisePropertyChanged(UserNamePropertyName);  
  65.           }  
  66.       }  
  67.       #endregion  

SL:

[c-sharp]  view plain copy
  1. #region ICommand  
  2.   
  3.  public RelayCommand<string> LoginCommand  
  4.  {  
  5.      get   
  6.      {  
  7.          return new RelayCommand<string>(  
  8.              (p) =>   
  9.              {  
  10.                  bool isLogon = false;  
  11.   
  12.                  // 登录成功  
  13.                  if (_userName == "admin" && p == "123")  
  14.                      isLogon = true;  
  15.                  else  
  16.                      isLogon = false;  
  17.                    
  18.                  // 发送消息  
  19.                  Messenger.Default.Send<bool>(isLogon);   
  20.              }  
  21.              );   
  22.      }  
  23.  }  
  24.   
  25.  public RelayCommand CancelCommand  
  26.  {  
  27.      get  
  28.      {  
  29.          return new RelayCommand(  
  30.              () =>   
  31.              {   
  32.                  System.Windows.Browser.HtmlPage.Window.Invoke("close");   
  33.              }  
  34.              );  
  35.      }  
  36.  }  
  37.   
  38.  #endregion  
  39.  
  40.  #region 公共属性  
  41.  public const string UserNamePropertyName = "UserName";  
  42.   
  43.  private string _userName = "";  
  44.   
  45.  public string UserName  
  46.  {  
  47.      get  
  48.      {  
  49.          return _userName;  
  50.      }  
  51.      set  
  52.      {  
  53.          if (_userName == value)  
  54.          {  
  55.              return;  
  56.          }  
  57.   
  58.          _userName = value;  
  59.   
  60.          // Update bindings, no broadcast  
  61.          RaisePropertyChanged(UserNamePropertyName);  
  62.      }  
  63.  }  
  64.  #endregion  

接着创建Login窗体,将按钮命令绑定到LoginViewModel对应的Command,注意WPF中不能绑定PasswordBox的Password属性,因此我们将PasswordBox作为参数传递给LoginViewModel,这种写法不符合mvvm的思想,不过基本只有这里需要这么写,也无伤大雅,页面代码如下:

WPF:

[xhtml]  view plain copy
  1. <StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal"   
  2.             HorizontalAlignment="Center">  
  3.   <TextBlock Text="用户名:" VerticalAlignment="Center"/>  
  4.   <TextBox Text="{Binding UserName,Mode=TwoWay}"  
  5.          Width="150" VerticalAlignment="Center"  
  6.          Margin="5,2,5,2"/>  
  7. </StackPanel>  
  8.   
  9. <StackPanel Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal"   
  10.             HorizontalAlignment="Center">  
  11.   <TextBlock Text="密  码:" VerticalAlignment="Center"/>  
  12.   <PasswordBox x:Name="password" Width="150" VerticalAlignment="Center"   
  13.                Margin="5,2,5,2" PasswordChar="*" />  
  14. </StackPanel>  
  15. <StackPanel Grid.Row="3" Grid.ColumnSpan="2"   
  16.             Orientation="Horizontal"   
  17.             HorizontalAlignment="Center" VerticalAlignment="Center">  
  18.   <Button Content="登录" Command="{Binding LoginCommand}"   
  19.           CommandParameter="{Binding ElementName=password}"   
  20.           Width="100" Margin="5,2,5,2"/>  
  21.   <Button Content="取消" Command="{Binding CancelCommand}"   
  22.           Width="100" Margin="5,2,5,2"/>  
  23. </StackPanel>  

SL中只有传递参数不一样:

[xhtml]  view plain copy
  1. <Button Content="登录" Command="{Binding LoginCommand}"   
  2.         CommandParameter="{Binding Password,ElementName=password}"   
  3.         Width="100" Margin="5,2,5,2"/>  

 

最后在app.xaml.cs中添加登录逻辑

WPF(需要在xaml中去除StartupUri):

[c-sharp]  view plain copy
  1. protected override void OnStartup(StartupEventArgs e)  
  2. {  
  3.     base.OnStartup(e);  
  4.   
  5.     // 首先显示登录控件  
  6.     Login login = new Login();  
  7.     LoginViewModel loginViewModel = new LoginViewModel();  
  8.     login.DataContext = loginViewModel;  
  9.   
  10.     // 将Login设置为主窗体  
  11.     this.MainWindow = login;  
  12.     MainWindow.Show();  
  13.   
  14.     // 注册消息,接收bool类型的参数,true为登录成功  
  15.     Messenger.Default.Register<bool?>(  
  16.         this,  
  17.         m =>  
  18.         {  
  19.             // 登录成功后,显示主页面  
  20.             if (m.HasValue && m.Value)  
  21.             {  
  22.                 // 更改主窗体  
  23.                 this.MainWindow = new MainWindow();  
  24.   
  25.                 // 关闭登录窗体  
  26.                 login.Close();  
  27.                 // 清理释放Login资源  
  28.                 loginViewModel.Cleanup();  
  29.                 login = null;  
  30.   
  31.                 MainWindow.Show();  
  32.             }  
  33.             else if (!m.HasValue)  
  34.             {  
  35.                 MainWindow.Close();  
  36.             }  
  37.         }  
  38.         );  
  39. }  
  40.   
  41. protected override void OnExit(ExitEventArgs e)  
  42. {  
  43.     Messenger.Default.Unregister(this);  
  44.     base.OnExit(e);  
  45. }  

SL:

[c-sharp]  view plain copy
  1. private void ApplicationStartup(object sender, StartupEventArgs e)  
  2. {  
  3.     Grid rootvisual = new Grid();  
  4.   
  5.     // 首先显示登录控件  
  6.      Login login = new Login();  
  7.     LoginViewModel loginViewModel = new LoginViewModel();  
  8.     login.DataContext = loginViewModel;  
  9.   
  10.     rootvisual.Children.Add(login);  
  11.     RootVisual = rootvisual;  
  12.   
  13.     // 注册消息,接收bool类型的参数,true为登录成功  
  14.     Messenger.Default.Register<bool>(  
  15.         this,   
  16.         m =>   
  17.         {  
  18.             // 登录成功后,显示主页面  
  19.             if (m)  
  20.             {  
  21.                 // 移除登录控件  
  22.                 rootvisual.Children.Clear();  
  23.                 // 添加主页面  
  24.                 rootvisual.Children.Add(new MainPage());  
  25.                 // 清理释放Login资源  
  26.                 loginViewModel.Cleanup();  
  27.                 login = null;  
  28.             }  
  29.         }  
  30.         );  
  31.       
  32.     DispatcherHelper.Initialize();  
  33. }  
  34.   
  35. private static void ApplicationExit(object sender, EventArgs e)  
  36. {  
  37.     Messenger.Default.Unregister(sender);  
  38.     ViewModelLocator.Cleanup();  
  39. }  

 

到这里登录功能就实现了,关键地方就是在添加登录逻辑的地方,通过匿名方法和Lamda表达式,注册一个消息的执行方法就像写方法代码一样简单,只不过消息里的方法要等到send命令发送后才会执行

 

2、通过ChildWindow实现列表增删改

SL中模式对话框通过ChildWindow来实现,WPF通过Window的ShowDialog方法实现,这里我通过模拟SL的ChildWindow来实现WPF的模式对话框,有关如何在WPF中模拟SL的ChildWindow,参考:在WPF中模拟SL的ChildWindow效果

代码比较多,这里就不贴代码了,我的示例代码中都有详细的注释,下面主要说说一些需要关键的地方,也算是我的一些心得:

 

首先是Messenger的一些方法重载:

void Send<TMessage>(TMessage message);

发送值为message的TMessage类型的消息

 

void Send<TMessage, TTarget>(TMessage message);

 

发送值为message的TMessage类型的消息,但是接收对象必须是TTarget类型的对象

 

public virtual void Send<TMessage>(TMessage message, object token)

发送值为message的TMessage类型的消息,与前面不同的是接收对象注册的消息方法拥有相同的token值才能接收到消息值

 

void Register<TMessage>(object recipient, Action<TMessage> action);

 

注册接收TMessage类型消息的方法,recipient是消息载体,也就是接收消息的对象,action是消息执行方法的委托,该委托接受TMessage类型的参数,也就是Send发送的值

 

void Register<TMessage>(object recipient, bool receiveDerivedMessagesToo, Action<TMessage> action);

注册接收TMessage类型消息的方法,与上面不同的是receiveDerivedMessagesToo指定是否能够接收TMessage派生类型的对象作为消息的值

 

public virtual void Register<TMessage>(object recipient, object token, Action<TMessage> action)

注册接收TMessage类型消息的方法,与前面不同的是必须与Send方法相匹配的token才能接收该Send的消息值

 

public virtual void Register<TMessage>(object recipient, object token, bool receiveDerivedMessagesToo,Action<TMessage> action)

 

MvvmLight中还封装了一种特殊的消息类型NotificationMessageAction<TMessage>,通过它可以发送一些复杂的对象,并且可以包含回调函数,此示例中在对话框中发送确定的消息给主界面,主界面调用子对象的方法执行数据库操作,如果成功则关闭对话框,如果失败则执行回调函数,将错误信息返回给对话框并显示出来

 

最后需要主要注意的就是什么使用Messenger比较合适,例如在此示例中:

 

与弹出的对话框进行交互,我会将主界面作为消息的载体,原因如下:

弹出对话框的生命周期较短,因此将弹出对话框作为发送方,总能发送到它的宿主页面

弹出对话框一般需要重复打开,在弹出对话框中注册消息方法会增加消息清理的成本,即在每次关闭对话框时要对消息进行清理,否则每打开一次对话框,消息执行次数会递增

 

 

本章节示例代码下载地址:示例下载 

入解析 C# 中的发布-订阅模式:利用 Messenger.Default.Send/Register 实现高效消息传递 在C#开发中,Messenger.Default.Send 和 Messenger.Default.Register 是实现发布-订阅模式的关键工具,广泛应用于WPF、Xamarin、UWP等框架中。发布-订阅模式通过解耦发送者和接收者,提升系统的可维护性和扩展性。Messenger类允许开发者在不直接引用彼此的情况下,在不同组件间传递消息。本文详细介绍了如何使用这些方法,包括发送和接收消息、解除注册、在MVVM架构中的应用、传递复杂消息类型以及线程安全处理。同时,提出了避免过度使用、封装消息类型、及时清理 阅读详情

相关推荐

Messenger.Default.Send 所有重载参数说明

Messenger.Default.Send 是 MVVM 框架中实现消息传递的核心方法,其重载参数主要用于控制消息的发送范围和接收条件。以下是其所有重载形式及参数说明:

StevenChen的博客 282

Packt.MVVM.Survival.Guide.for.Enterprise.Architectures.in.Silverlight.And.WPF

Book Description Eliminate unnecessary code by taking advantage of the MVVM pattern in Silverlight and WPF using this book and eBook - less code, fewer bugs Build an enterprise application using Silverlight and WPF, taking advantage of the powerful MVVM pattern, with this book and e-book Discover the evolution of presentation patterns-by example-and see the benefits of MVVM in the context of the larger picture of presentation patterns Customize the MVVM pattern for your projects needs by comparing the various implementation styles In Detail MVVM (Model View View Model) is a Microsoft best practices pattern for working in WPF and Silverlight that is highly recommended by both Microsoft and industry experts alike. This book will look at the reasons for the pattern still being slow to become an industry standard, addressing the pain points of MVVM. It will help Silverlight and WPF programmers get up and running quickly with this useful pattern. MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF will help you to choose the best MVVM approach for your project while giving you the tools, techniques, and confidence that you will need to succeed. Implementing MVVM can be a challenge, and this book will walk you through the main issues you will come across when using the pattern in real world enterprise applications. This book will help you to improve your WPF and Silverlight application design, allowing you to tackle the many challenges in creating presentation architectures for enterprise applications. You will be given examples that show the strengths and weaknesses of each of the major patterns. The book then dives into a full 3 tier enterprise implementation of MVVM and takes you through the various options available and trade-offs for each approach. During your journey you will see how to satisfy all the demands of modern WPF and Silverlight enterprise applications including scalability, testability, extensibility, and blendability. Complete your transition from ASP.NET and WinForms to Silverlight and WPF by embracing the new tools of these platforms, and the new design style that they allow for. MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF will get you up to speed and ready to take advantage of this powerful new presentation platform. What you will learn from this book Maximize separation of concerns by taking advantage of WPF and Silverlight's rich binding system, templates, and commanding infrastructure Discover the built-in support for MVVM in Entity Framework and WCF Create unit testable user interfaces the MVVM way Work in parallel with minimal dependencies by creating blendable architectures Solve common MVVM problems both with and without frameworks depending on your preference Extend your architecture and test it by using inversion of control frameworks Tackle complex designs by using hierarchical view model design and mediators Reduce the amount of code in your user interface by letting the WPF and Silverlights binding system eliminate your need to do things like casting controls and dispatching Best practices for dealing with collections Create designs that allow for dramatically changing your user interface without having to change code outside the view using data templates Approach This book combines practical, real-world examples with all the background material and theory you need The concepts are explained with a practical LOB enterprise application that is gradually built through the course of this book. MVVM offers lots of design choices and the author shows examples of each of these approaches, by changing the code to achieve the same results. Who this book is written for This book will be a valuable resource for Silverlight and WPF developers who want to fully maximize the tools with recommended best practices for enterprise development. This is an advanced book and you will need to be familiar with C#, the .Net framework, and Silverlight or WPF.

MvvmLightMessenger

MvvmLightMessenger 的三个方法使用,是在别人的基础上改的,原例子为Messenger.Default.Send(messenger),自己写的有Messenger.Default.Send(msg); Messenger.Default.Send<NotificationMessage>(msg);还有Messenger.Default.Send<NotificationMessageAction>(msg);(带回调),例子比较简单,供初学者参考。

Messenger.Default.Send()

使用WPF开发,Messenger.Default.Send()是非常适合不同View之间的控件交互,尤其是在MVVM模式下非常有用。 贴一个demo记录下,省得以后忘记。只是demo,只是demo。 要想使用这个功能,需要在引用里加上头文件 using GalaSoft.MvvmLight.Messaging;和GalaSoft.MvvmLight.WPF4.dll 这个库; name...

weixin_34395205的博客 1347

WPF 不同界面交互之消息通知MvvLight

很类似于NetworkComms TcpIP通讯框架的方法 //在需要接受的类构造函数里面注册这段代码 Messenger.Default.Register<数据类型>(this, "发送方方法名", 方法名); //在发送放定义这段发送代码 Messenger.Default.Send<数据类型>(数据, "发送方方法名"); NetworkComms TcpIP通讯框架网络传输一般会放在服务器 NetworkComms.AppendGlobalIncomingPa

lymcx的博客 914

Mvvm Light Toolkit for wpf/silverlight系列Messenger[zhuan]

在开发Wpf/SL应用时,经常会遇到不同页面和窗体之间的参数传递的问题。对于这类问题,我们一般通过事件实现数据传递,也可以定义全局静态变量来进行数据共享。这里我们则使用了另外一种非常高效而优雅的方法来进行消息传递,这里我称之为Messenger,事实上,Messenger并非mvvm的专利,我们可以把它看作一种设计模式,你可以在其它.net程序中使用它。 一、Mvvm Light...

dixiannie4307的博客 227

【转】Mvvm Light Toolkit for wpf/silverlight系列Messenger

在开发Wpf/SL应用时,经常会遇到不同页面和窗体之间的参数传递的问题。对于这类问题,我们一般通过事件实现数据传递,也可以定义全局静态变量来进行数据共享。这里我们则使用了另外一种非常高效而优雅的方法来进行消息传递,这里我称之为Messenger,事实上,Messenger并非mvvm的专利,我们可以把它看作一种设计模式,你可以在其它.net程序中使用它。 一、Mvvm Light Mess...

weixin_30326741的博客 116

Silverlight/WPF/Windows Phone技术栈解析与MVVM实践

MVVM(Model-View-ViewModel)是客户端开发中的经典架构模式,通过数据绑定实现视图与业务逻辑的解耦。在微软技术栈中,WPFSilverlight基于XAML的声明式UI与MVVM天然契合,配合Prism等框架可构建复杂企业级应用。现代图形渲染技术如DirectX加速和可视化树优化,能显著提升数据可视化性能,适用于金融看板、医疗影像等高交互场景。本文通过Silverlight的Deep Zoom案例和WPF虚拟化面板实践,展示如何在这些渐趋沉寂但仍具参考价值的技术中实现高效开发。

circularr9834的博客 440

深入介绍 MVVM Light Messenger

转自 http://www.wxzzz.com/1229.html 此系列介绍了 Model-View-ViewModel (MVVM) 模式和 MVVM Light ToolkitMessenger 组件实际上是 MVVM Light Toolkit 的一个功能相当强大的元素,它由于简单易用而受到开发人员的青睐,但也由于误用会带来风险而引发了一些争议。此组件需要用一篇

葫芦娃的专栏 2328

MvvmLight学习篇—— Mvvm Light Toolkit for wpf/silverlight系列(导航)

系列一:看的迷迷糊糊的 一、Mvvm Light Toolkit for wpf/silverlight系列之准备工作 二、Mvvm Light Toolkit for wpf/silverlight系列之搭建mvvmlight开发框架 三、Mvvm Light Toolkit for wpf/silverlight系列之数据绑定 四、Mvvm Light Toolkit for wp...

weixin_34258838的博客 251

MVVMMVVMLightMVVMLight Toolkit之我见

我想,现在已经有不少朋友在项目中使用了MVVMLight了吧,如果你正在做WPFSilverlight,Windows Phone的开发,那么,你有十分必要的理由了解MVVMMVVMLight。我写这篇文章的目的,是给大家做一个总结,以便更多的朋友了解并掌握MVVM。 首先,要说一下MVVM的概念。MVVM严格来说,并不是一种框架,而是一个设计的模式吧。与它有关的设计模式还有MVC (现在...

weixin_30381793的博客 102

Mvvm Light 框架下的消息传递

1、Messenger.Default.Send<string>("Messenger");     原型:Public virtual void Send<TMessage>(TMssage message);     摘要:将消息发送到注册者。该消息将达到所有收件人,注册这个消息类型使用的注册方法。     类型参数:消息将发送的类型。     参数:消息发送到...

weixin_30466039的博客 357

详解 C# 中基于发布-订阅模式的 Messenger 消息传递机制:Messenger.Default.Send/Register

文章摘要: 本文详细解析了 C# 中基于发布-订阅模式的 Messenger 消息传递机制,重点介绍了 Messenger.Default.Send 和 Messenger.Default.Register 的使用方法及其在 MVVM 架构中的应用。Messenger 通过松耦合的方式实现跨组件通信,支持消息的发送与订阅,并通过令牌机制实现定向消息传递。文章结合历史对话中的代码示例,展示了如何通过 Messenger 发送和接收消息.

编程技术探索者,分享C/C++、C#、Java、数据库等开发经验,聚焦实战技巧与AI兴趣,助力编程爱好者成长。 2409

CommunityToolkit.Mvvm学习笔记(4)——Messenger

如果你对WPF有一定了解,你会发现WPF中的命令就是一个实现了ICommand接口的类。同样本文虽然标题是Messenger,但也要从IMessenger接口说起。至于Messenger的中文名,我觉得就叫它的直译“信使”好了,毕竟传递消息就是信使的能力嘛。命名空间:Microsoft.Toolkit.Mvvm.Messaging 程序集:Microsoft.Toolkit.Mvvm.dll 包:Microsoft.Toolkit.MvvmIMessenger接口使实现它的类具有在不同对象之间交换消息的能力

三千幻想乡 1万+

WPF中消息传递——MVVM Messenger的简单使用

C# WPF 中消息传递,信息交互,解耦处理。

abc1564984930的博客 1万+
上一篇: Mvvm Light Toolkit for wpf/silverlight系列之Command和Events
下一篇: 一步一步打造自己的Silverlight 初始屏幕
sam1012
博客等级 码龄18年 17粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值