WPF 命令绑定的各种方式

WPF 命令绑定的各种方式

引言

在WPF开发过程中,不得不学习的就是MVVM模式。但是在MVVM中又绕不开命令(Command)的使用。下面通过几种方式介绍我了解的WPF命令绑定方式。

如何使用

控件继承ICommand接口,直接使用Command

首先通过这里简单介绍Command在MVVM中的使用。

ViewModel类

// using System.ComponentModel;
// using System.Runtime.CompilerServices;

MainViewModel

	/// <summary>
    /// Interactive logic for MainWindow
    /// </summary>
    public class MainViewModel : INotifyPropertyChanged
    {
        public MainViewModel()
        {
            SignInCommand = new RelayCommand(() =>
            {
                MessageBox.Show($"Hello {Username},welcome to Melphily's world");
            });
        }
        private string username;

        /// <summary>
        /// User name
        /// </summary>
        public string Username
        {
            get { return username; }
            set
            {
                username = value;
                RaisePropertyChanged();
            }
        }

        /// <summary>
        /// Sign in command
        /// </summary>
        public ICommand SignInCommand { get; private set; }

        #region Notify Handler

        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

        public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }

Command类

// using System.Windows.Input;

RelayCommand

    /// <summary>
    /// the command without input parameter
    /// </summary>
    public class RelayCommand : ICommand
    {
        public event EventHandler CanExecuteChanged = (sender, e) => { };

        private Action mAction;

        public RelayCommand(Action action)
        {
            this.mAction = action;
        }

        public bool CanExecute(object parameter)
        {
            // Always allow the action can execute.
            return true;
        }

        public void Execute(object parameter)
        {
            // Take action
            mAction.Invoke();
        }
    }

View类

MainWindow.xaml

<Window x:Class="Melphily.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:Melphily"
        mc:Ignorable="d"
        Title="Melphily" Height="450" Width="800">
    <Grid Background="Black">
        <Border VerticalAlignment="Center" Width="300" Padding="20 60" Background="White" CornerRadius="5">
            <Border.Effect>
                <DropShadowEffect Color="#FF924AC3"/>
            </Border.Effect>
            <Border.BorderBrush>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF22B5CD" Offset="1"/>
                    <GradientStop Color="#FFB92020"/>
                </LinearGradientBrush>
            </Border.BorderBrush>
            <StackPanel >
                <TextBlock Text="Sign In" TextAlignment="Center" FontSize="22">
                    <TextBlock.Foreground>
                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                            <GradientStop Color="#FF310F89" Offset="0"/>
                            <GradientStop Color="#FF26C6EA" Offset="1"/>
                        </LinearGradientBrush>
                    </TextBlock.Foreground>
                </TextBlock>
                <TextBlock Text="Username" Margin="0 20 0 0"/>
                <TextBox Text="{Binding Username}" />

                <Button Command="{Binding SignInCommand}" Content="Sign In" Margin="0 20 0 0"/>
            </StackPanel>
        </Border>
    </Grid>
</Window>

MainWindow.xaml.cs

	/// <summary>
    /// Interactive logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // Bind the Binding source to the DataContext
            DataContext = new MainViewModel();
        }
    }
    

至此,MVVM的简单应用就完成了。这里面简单实现了输入用户名登陆,在登陆(Sign In)操作时,使用了命令绑定的形式****,在ViewModel类(MainViewModel)接收命令,并做相应的处理。

运行效果:

在这里插入图片描述

扩展使用

上面通过较为完整的实例展示了MVVM模式中的Command绑定使用。但是一些WPF控件(例如ListBox)并不支持Command命令绑定。下面我们通过几种方式讨论如何解决,以ListBox举例。

使用InputBinding

Model

MusicItemModel.cs

public class MusicItemModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Singer { get; set; }

        public MusicItemModel()
        {
            SelectedCommand = new RelayCommandWithParameter(p =>
            {
                MessageBox.Show($"Selected music is {Name} Singed by {Singer},the Id is {p}");
            });
        }

        /// <summary>
        /// Select command
        /// </summary>
        public ICommand SelectedCommand { get; private set; }

    }

Command

RelayCommandWithParameter.cs

	/// <summary>
    /// the command without input parameter
    /// </summary>
    public class RelayCommandWithParameter : ICommand
    {
        public event EventHandler CanExecuteChanged = (sender, e) => { };


        private Action<object> mActionWithParameter;

        public RelayCommandWithParameter(Action<object> action)
        {
            this.mActionWithParameter = action;
        }

        public bool CanExecute(object parameter)
        {
            // Always allow the action can execute.
            return true;
        }

        public void Execute(object parameter)
        {
            // Take action
            mActionWithParameter.Invoke(parameter);
        }
    }

View Model

MusicListViewModel.cs

 /// <summary>
    /// Interactive logic for MusicListPage
    /// </summary>
    public class MusicListViewModel : INotifyPropertyChanged
    {

        public MusicListViewModel()
        {
            Musics = new ObservableCollection<MusicItemModel>()
            {
                new MusicItemModel(){ Id=1, Name="甜甜的", Singer="周杰伦"},
                new MusicItemModel(){ Id=1, Name="说好的幸福呢", Singer="周杰伦"},
                new MusicItemModel(){ Id=1, Name="彩虹", Singer="周杰伦"},
                new MusicItemModel(){ Id=1, Name="一路向北", Singer="周杰伦"},
                new MusicItemModel(){ Id=1, Name="Married You", Singer="布鲁诺·马尔斯"},
            };
        }

        private ObservableCollection<MusicItemModel> musics;

        public ObservableCollection<MusicItemModel> Musics
        {
            get { return musics; }
            set { musics = value; RaisePropertyChanged(); }
        }

        #region Notify Handler

        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

        public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }

View

MusicListPage.xaml.cs

	/// <summary>
    /// Interactive logic for MusicListPage.xaml 
    /// </summary>
    public partial class MusicListPage : Page
    {
        public MusicListPage()
        {
            InitializeComponent();
            DataContext = new MusicListViewModel();
        }
    }

MusicListPage.xaml

<Page x:Class="Melphily.MusicListPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Melphily"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="MusicListPage">

    <Grid>
        <ListBox ItemsSource="{Binding Musics}">
            <ListBox.ItemTemplate>
                <DataTemplate >
                    <Grid>
                        <Grid.InputBindings>
                            <MouseBinding MouseAction="LeftClick" Command="{Binding SelectedCommand}" CommandParameter="{Binding Id}"/>
                        </Grid.InputBindings>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="300"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                        <TextBlock Grid.Column="1" Text="{Binding Singer}"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>

命令绑定是使用Grid控件的InputBinding实现的。

<Grid.InputBindings>
	<MouseBinding MouseAction="LeftClick" Command="{Binding SelectedCommand}" CommandParameter="{Binding Id}"/>
</Grid.InputBindings>

在这里插入图片描述

使用System.Windows.Interactivity.dll

呈现的内容和上面一样,只需要修改MusicListPage.xaml中的命令绑定即可。

System.Windows.Interactivity.dll这个时微软后期提供的。

1.将System.Windows.Interactivity.dll引入到项目中。

在这里插入图片描述

在这里插入图片描述

2.XAML中声明

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

3.XAML中使用

						<i:Interaction.Triggers>
                            <i:EventTrigger EventName="MouseLeftButtonDown">
                                <i:InvokeCommandAction Command="{Binding SelectedCommand}" CommandParameter="{Binding Id}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>

MusicListPage.xaml

<Page x:Class="Melphily.MusicListPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Melphily"
      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="MusicListPage">

    <Grid>
        <ListBox ItemsSource="{Binding Musics}">
            <ListBox.ItemTemplate>
                <DataTemplate >
                    <Grid>
                        <!--<Grid.InputBindings>
                            <MouseBinding MouseAction="LeftClick" Command="{Binding SelectedCommand}" CommandParameter="{Binding Id}"/>
                        </Grid.InputBindings>-->
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="MouseLeftButtonDown">
                                <i:InvokeCommandAction Command="{Binding SelectedCommand}" CommandParameter="{Binding Id}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="300"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                        <TextBlock Grid.Column="1" Text="{Binding Singer}"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>

在这里插入图片描述

自定义模板使用含有ICommand接口的控件传递命令

对于没有继承ICommand接口的控件,可以重定义控件的模板,并在模板中加入可以使用Command绑定的控件例如Button来实现命令的绑定。

使用上面的示例,只修改数据模板,如下所示。

MusicListPage.xaml

<Page x:Class="Melphily.MusicListPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Melphily"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="MusicListPage">

    <Grid>
        <ListBox ItemsSource="{Binding Musics}">
            <ListBox.ItemTemplate>
                <DataTemplate >
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="300"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                        <TextBlock Grid.Column="1" Text="{Binding Singer}"/>

                        <!-- 可以绑定命令的控件 -->
                        <Button Grid.ColumnSpan="2" 
                                Command="{Binding SelectedCommand}" 
                                CommandParameter="{Binding Id}"
                                Foreground="Transparent" Background="Transparent" />
                    </Grid>
                </DataTemplate>

            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>

虽然这种方式比较简单粗暴,但由于是直接在模板上面操作,所有控件是否有焦点变得非常重要了,还有就是需要隐藏控件的一些不需要显示的样式。

样式没设置好,会出现意想不到的情况。如下:

在这里插入图片描述

自定义模板使用用户控件(UserControl)【添加Command依赖属性】传递命令

鉴于上面自定义模板使用含有ICommand接口的控件传递命令的方式存在的一些弊端,我们可以自定义带有命令的控件来解决不便利的地方。

1.自定义CommandControl控件

CommandControl.xaml

<UserControl x:Class="Melphily.UiCore.CommandControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Melphily.UiCore"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             Background="Transparent">
</UserControl>

CommandControl.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Melphily.UiCore
{
    /// <summary>
    /// CommandControl.xaml 的交互逻辑
    /// </summary>
    public partial class CommandControl : UserControl
    {
        public CommandControl()
        {
            InitializeComponent();
            MouseLeftButtonDown += OnMouseLeftButtonDown;
        }
        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            if (Command != null)
            {
                if (Command.CanExecute(CommandParameter))
                {
                    Command.Execute(CommandParameter);
                }
            }
        }

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand),
                typeof(CommandControl),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object),
                typeof(CommandControl),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }
    }
}

2.在模板中使用CommandControl

MusicListPage.xaml

<Page x:Class="Melphily.MusicListPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Melphily"
      xmlns:uicore="clr-namespace:Melphily.UiCore"
      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="MusicListPage">

    <Grid>
        <ListBox ItemsSource="{Binding Musics}">
            <ListBox.ItemTemplate>
                <DataTemplate >
					<uicore:CommandControl Command="{Binding SelectedCommand}"
                                           CommandParameter="{Binding Id}">

                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="300"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                            <TextBlock Grid.Column="1" Text="{Binding Singer}"/>
                        </Grid>
                        
					</uicore:CommandControl>
                </DataTemplate>

            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>

由于CommandControl是一个透明的控件,因此不用考虑样式的问题,可以直接使用。其次它是内容控件,因此该控件是始终存在焦点。

但是,如果将该控件作为模板(ControlTemplate/DataTemplate)时,发现无法对容器内的控件设置名称,因此,在设置样式模板时局限性会很大。

自定义模板使用自定义控件(CustomControl)【添加Command依赖属性】传递命令

鉴于用户控件带来的弊端,我们依然可以使用自定义控件(CustomControl)来解决。

创建一个自定义控件:

在项目中点击添加–》新建项

然后找到“CustomControl”或者“WPF自定义控件”,设置好名称,点击创建。

创建之后首先会存在一个对应.cs文件,然后会自动创建一个Theme(主题)文件夹,并创建一个Generic.xaml文件(通用样式文件)。

接下来我们编写自定义控件

1.加入我们的命令依赖属性和相关处理事件。

CommandControl.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Deamon.UiCore
{
    /// <summary>
    /// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
    ///
    /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:Deamon.UiCore"
    ///
    ///
    /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:Deamon.UiCore;assembly=Deamon.UiCore"
    ///
    /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用,
    /// 并重新生成以避免编译错误:
    ///
    ///     在解决方案资源管理器中右击目标项目,然后依次单击
    ///     “添加引用”->“项目”->[浏览查找并选择此项目]
    ///
    ///
    /// 步骤 2)
    /// 继续操作并在 XAML 文件中使用控件。
    ///
    ///     <MyNamespace:CommandControl/>
    ///
    /// </summary>
    public class CommandControl : ContentControl
    {
        static CommandControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CommandControl), new FrameworkPropertyMetadata(typeof(CommandControl)));
        }
        public CommandControl()
        {
            MouseLeftButtonDown += CommandControlCC_MouseLeftButtonDown;
        }

        private void CommandControlCC_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (Command != null)
            {
                if (Command.CanExecute(CommandParameter))
                {
                    Command.Execute(CommandParameter);
                }
            }
        }

        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            if (Command != null)
            {
                if (Command.CanExecute(CommandParameter))
                {
                    Command.Execute(CommandParameter);
                }
            }
        }

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand),
                typeof(CommandControl),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object),
                typeof(CommandControl),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }
    }
}

注释是默认加上的,是用来引导你如何设计和使用该自定义控件。默认情况下,自定义控件是继承自Control类的,因为我们的命令控件是需要包裹其它控件,所以直接继承ContentControl会减少我们对内容设计的逻辑代码。(PS:如果不这么做也可以自己添加Content属性,然后再面板中使用ContentPresenter将Content显示到界面上。)

2.在Generic.xaml文件中修改控件默认样式

Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Deamon"
    xmlns:uicore="clr-namespace:Deamon.UiCore"
    >
    <!-- 带命令绑定的内容控件 -->
    <Style TargetType="{x:Type uicore:CommandControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type uicore:CommandControl}">
                    <ContentControl>
                        <ContentPresenter/>
                    </ContentControl>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

ContentPresenter默认情况下已经将Content属性绑定到了改控件上,如果是其它控件,需要具体了解(绑定、样式、模板等)相关知识。

3.在模板中使用CommandControl

MusicListPage.xaml

<Page x:Class="Melphily.MusicListPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Melphily"
      xmlns:uicore="clr-namespace:Melphily.UiCore"
      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="MusicListPage">

    <Grid>
        <ListBox ItemsSource="{Binding Musics}">
            <ListBox.ItemTemplate>
                <DataTemplate >
					<uicore:CommandControl Command="{Binding SelectedCommand}"
                                           CommandParameter="{Binding Id}">

                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="300"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                            <TextBlock Grid.Column="1" Text="{Binding Singer}"/>
                        </Grid>
                        
					</uicore:CommandControl>
                </DataTemplate>

            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>

使用方式和UserControl一模一样,只是控件的定义方式不一样而已。

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值