C#实现运行时控件拖动操作指南

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文深入探讨了C#中控件拖动功能的实现细节,包括在Windows Forms和WPF环境下如何处理拖动事件、更新控件位置、以及相关边界和性能优化的技巧。文中通过实例解释了如何监听和响应拖动事件,以及如何通过限制控件拖动的边界来提升用户体验。此外,还讨论了多控件拖动和性能优化策略,旨在帮助开发者在应用程序中实现高效和流畅的用户界面交互。

1. Windows Forms控件拖动实现

Windows Forms应用程序提供了一种直观、交互式的方式来创建桌面应用程序。控件是Windows Forms应用程序的基础组成部分,它们提供用户界面的元素,例如按钮、文本框等。

1.1 Windows Forms基础与控件介绍

Windows Forms控件分为标准控件和自定义控件。标准控件如Button、Label、TextBox等,是.NET Framework提供的预定义控件。自定义控件可以由开发者根据需要创建和设计,扩展标准控件集的功能。理解控件的基本属性、方法和事件是实现拖动功能的前提。

1.2 通过事件驱动实现控件拖动

在Windows Forms中,控件拖动功能通常是通过事件驱动的方式实现的。具体来说,拖动动作通常涉及到以下几个关键事件:

  • MouseDown : 此事件标志着拖动操作的开始。当用户按下鼠标按钮时触发。
  • MouseMove : 此事件在鼠标指针移动时连续触发。通过捕获这些事件,可以计算鼠标移动的距离,并更新控件的位置。
  • MouseUp : 当鼠标按钮释放时触发,这表示拖动操作的结束。

1.3 实例演示:简单控件的拖动效果

以下是一个简单的示例,展示如何通过处理 MouseDown MouseMove MouseUp 事件来实现一个标签(Label)控件的拖动功能:

private void label1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
        label1.Capture = true;
}

private void label1_MouseMove(object sender, MouseEventArgs e)
{
    if (label1.Capture)
    {
        Point mousePos = this.PointToClient(Cursor.Position);
        label1.Location = mousePos;
    }
}

private void label1_MouseUp(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
        label1.Capture = false;
}

在上述代码中,当用户点击标签并且按下鼠标左键时,控件会捕获鼠标事件,并在用户移动鼠标时更新标签的位置。鼠标按钮释放后,标签会停止捕获事件,结束拖动操作。这只是一个基础的实现,根据实际需求,还可以添加边界检查、平滑动画等高级功能。

2. WPF控件拖动实现

2.1 WPF技术框架概述

WPF(Windows Presentation Foundation)是微软推出的一种用于构建Windows客户端应用程序的用户界面框架,它采用了XAML(可扩展应用程序标记语言)来定义用户界面。与传统的Windows Forms相比,WPF提供了更为丰富的界面效果和更高级的控件功能。

2.1.1 WPF与Windows Forms的对比

WPF和Windows Forms是两种不同的技术,它们在许多方面有着本质上的差异。Windows Forms更倾向于传统的控件布局和事件处理机制,而WPF则在数据绑定、样式、动画等方面提供了更多的灵活性和强大的功能。WPF支持更为复杂的布局,如Canvas、StackPanel、Grid等,允许开发者以更自由的方式组织用户界面。

2.1.2 XAML中的控件基础

在WPF中,控件被定义在XAML中,这使得UI的创建和修改更加直观和容易。XAML是一种声明式的标记语言,它允许开发者以标签的形式定义控件的结构和布局。控件本身通常包含在命名空间中,如 <Window> 标签定义窗口,而 <Button> <TextBox> 等标签则定义各种控件。

2.2 利用交互行为实现控件拖动

拖动控件是用户界面中常见的一种交互方式,WPF通过事件驱动的方式实现了这一功能。我们可以通过组合MouseLeftButtonDown、MouseLeftButtonUp和MouseMove事件来完成控件的拖动。

2.2.1 使用MouseLeftButtonDown事件

当用户按下鼠标左键时,MouseLeftButtonDown事件被触发。在这个事件的处理函数中,我们可以获取到鼠标当前的屏幕坐标,并记录下来作为拖动开始的参考点。

private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    lastScreenPosition = e.GetPosition(this);
}

在上述代码中, lastScreenPosition 变量用于存储上一次鼠标位置,以便后续计算拖动距离。 GetPosition 方法返回的是当前鼠标位置相对于事件源(本例中为Window)的坐标。

2.2.2 使用MouseLeftButtonUp事件

与MouseLeftButtonDown事件相呼应的是MouseLeftButtonUp事件,它在用户释放鼠标左键时触发。通常,我们在此事件中停止拖动动作,并执行一些收尾工作,如更新控件位置或触发其他事件。

2.2.3 使用MouseMove事件

MouseMove事件在鼠标指针移动时触发,这是实现拖动功能的核心事件。在这个事件的处理函数中,我们需要计算鼠标移动的距离,并相应地更新控件的位置。

private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed)
    {
        Point currentScreenPosition = e.GetPosition(this);
        double offsetX = currentScreenPosition.X - lastScreenPosition.X;
        double offsetY = currentScreenPosition.Y - lastScreenPosition.Y;

        // 更新控件位置
        Canvas.SetLeft(this.Rectangle, Canvas.GetLeft(this.Rectangle) + offsetX);
        Canvas.SetTop(this.Rectangle, Canvas.GetTop(this.Rectangle) + offsetY);

        // 更新最后的鼠标位置坐标
        lastScreenPosition = currentScreenPosition;
    }
}

在上述代码中,我们首先检查鼠标左键是否仍然处于按下的状态。如果是,我们获取当前鼠标位置并计算其与记录的位置之间的偏移量。然后使用 Canvas.SetLeft Canvas.SetTop 方法来更新控件的位置。最后,更新 lastScreenPosition 以备下次计算。

2.3 实例演示:WPF中控件的拖动实现

为了演示上述概念,我们将创建一个简单的WPF应用程序,其中包含一个可以拖动的矩形控件。我们将使用Canvas作为布局容器,并为矩形添加鼠标事件处理逻辑。

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF控件拖动示例" Height="350" Width="525">
    <Canvas>
        <Rectangle x:Name="Rectangle" Width="100" Height="100" Fill="Blue"/>
    </Canvas>
</Window>

我们首先定义了一个名为 Rectangle 的Rectangle控件,其大小为100x100,并填充为蓝色。然后在代码后台为 Rectangle 控件添加了三个事件处理函数,分别对应于前面章节中讨论的三个事件。

通过上述步骤,我们完成了一个基本的WPF控件拖动实现。接下来的章节将深入探讨依赖属性和数据绑定在拖动中的应用,并介绍如何通过事件处理实现控件精确拖动。

3. 依赖属性与数据绑定在拖动中的应用

在本章节中,我们将深入探讨依赖属性和数据绑定技术,并阐述其在拖动控件时的具体应用。这将为我们在后续章节实现复杂拖动功能提供理论基础和技术支持。

3.1 依赖属性机制深入解析

3.1.1 依赖属性的工作原理

依赖属性(DependencyProperty)是WPF中一种特殊的属性类型,它不仅可以存储数据,还能够支持数据的绑定、动画以及样式等高级特性。它与传统属性的主要区别在于其值的获取与设置不仅仅依赖于声明它的类,还可以通过WPF的属性系统从外部进行访问和修改。

一个依赖属性的定义通常会涉及到 DependencyProperty.Register 方法,通过它注册属性名称、类型、所属类以及该属性的一些行为。此外,依赖属性可以拥有依赖关系,也就是说,它的值可能依赖于其他对象的属性值。这样的依赖关系使得依赖属性可以参与到WPF的数据绑定、样式和动画等机制中。

3.1.2 在拖动中应用依赖属性

在拖动功能的实现中,我们可以将控件的位置信息保存为依赖属性,这样就可以将这个属性绑定到控件的布局属性上,如 Canvas.Left Canvas.Top 。当拖动发生时,我们更新这个依赖属性,界面会自动响应这种变化,因为依赖属性会通知系统其值已经改变。

接下来,我们将通过一个实例来演示如何将依赖属性与拖动结合起来。

3.2 数据绑定技术概述

3.2.1 数据绑定基础

数据绑定(Data Binding)是将控件的属性与数据源进行关联的过程。当数据源的数据发生变化时,绑定的数据源的控件属性会自动更新以反映这些变化,反之亦然。

在WPF中,数据绑定功能是通过 Binding 类来实现的。一个绑定通常包含以下几个关键部分:

  • Source :数据源,即绑定数据的原始对象。
  • Path :源对象中要绑定的属性路径。
  • Mode :绑定的方向,例如单向绑定、双向绑定等。
  • UpdateSourceTrigger :指定何时更新绑定源。

数据绑定使得控件可以更加动态地响应用户操作或数据变化。

3.2.2 拖动控件时的数据绑定应用

在拖动控件的过程中,我们希望控件的实时位置能够反映到一个数据模型中。通过数据绑定,我们可以将控件的位置属性绑定到一个表示位置的数据模型上。拖动控件时,模型会自动更新位置数据;反过来,如果我们修改了模型中的位置数据,控件的位置也会相应改变。

接下来,我们将会演示如何结合依赖属性与数据绑定来实现拖动效果。

3.3 实例演示:依赖属性与数据绑定结合的拖动效果

在这个实例中,我们将创建一个简单的WPF应用程序,演示如何结合依赖属性和数据绑定来实现拖动控件。

首先,我们定义一个用户控件,并在其内部定义一个依赖属性来表示控件的位置。

public class DraggableControl : UserControl
{
    public static readonly DependencyProperty PositionProperty = DependencyProperty.Register(
        "Position", typeof(Point), typeof(DraggableControl),
        new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public Point Position
    {
        get { return (Point)GetValue(PositionProperty); }
        set { SetValue(PositionProperty, value); }
    }
}

在XAML中,我们设置控件的 Canvas.Left Canvas.Top 属性与依赖属性 Position 进行绑定。

<UserControl ...>
    <Canvas>
        <Rectangle Width="100" Height="100" Fill="Blue">
            <Rectangle.RenderTransform>
                <TranslateTransform x:Name="TranslateTransform"/>
            </Rectangle.RenderTransform>
        </Rectangle>
    </Canvas>
</UserControl>

接下来,我们在后台代码中绑定 TranslateTransform X Y 属性到我们的 Position 依赖属性。

public DraggableControl()
{
    InitializeComponent();
    Binding positionBindingX = new Binding("Position.X");
    Binding positionBindingY = new Binding("Position.Y");
    TranslateTransform.SetBinding(TranslateTransform.XProperty, positionBindingX);
    TranslateTransform.SetBinding(TranslateTransform.YProperty, positionBindingY);
}

在拖动过程中,我们更新 Position 属性来改变控件的位置。

private void UserControl_MouseMove(object sender, MouseEventArgs e)
{
    if (IsDragging)
    {
        Point currentPoint = e.GetPosition(Parent as UIElement);
        Position = new Point(currentPoint.X - _startPoint.X, currentPoint.Y - _startPoint.Y);
    }
}

通过这种方式,我们可以实现拖动控件时,控件位置的实时更新,并且这些更新会反映到依赖属性中,进一步触发数据绑定机制,实现界面与数据的同步更新。

以上展示了如何将依赖属性和数据绑定应用在WPF控件的拖动过程中,从而提高开发效率,增强控件动态交互的灵活性。

4. DragDelta和DragCompleted事件处理

4.1 了解DragDelta与DragCompleted事件

4.1.1 DragDelta事件的作用与使用

在WPF中,DragDelta事件是用于处理拖动操作的一个关键事件。当你开始拖动一个控件时,该事件被触发,并提供了在拖动过程中控制控件移动行为的能力。DragDelta事件属于Thumb控件的一部分,它能够响应拖动操作,并在拖动过程中对控件的位置进行更新。

使用DragDelta事件时,通常会处理它来改变控件的大小或位置。该事件传递一个DragDeltaEventArgs参数,该参数包含了当前鼠标的位置、控件最初的位置以及水平和垂直方向上的偏移量。

以下是一个简单示例,展示如何在WPF中为一个Thumb控件添加DragDelta事件处理逻辑:

private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
    Thumb thumb = sender as Thumb;

    // 计算X和Y方向上的偏移量
    double horizontalChange = e.HorizontalChange;
    double verticalChange = e.VerticalChange;

    // 更新Thumb控件的位置
    Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + horizontalChange);
    Canvas.SetTop(thumb, Canvas.GetTop(thumb) + verticalChange);
}

上述代码块中, e.HorizontalChange e.VerticalChange 提供了控件在水平和垂直方向上的移动量。通过获取Thumb控件当前位置,并加上偏移量,可以更新控件的位置。注意,此操作是针对支持Canvas布局的控件。

4.1.2 DragCompleted事件的作用与使用

DragCompleted事件在拖动操作完成后触发。这个事件为完成拖动后的逻辑处理提供了一个机会,比如可以在此事件中保存用户对控件位置的最终调整,或者执行拖动操作完成后的其他任务。

DragCompleted事件的典型用途包括:

  • 验证用户拖动后控件的新位置是否满足应用的业务规则。
  • 执行拖动操作的后续逻辑,如更新UI或者调用后台处理函数。
  • 在进行多控件拖动时同步控件位置。

以下是一个DragCompleted事件处理函数的简单示例:

private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e)
{
    Thumb thumb = sender as Thumb;
    // 此处可以添加代码来处理拖动完成后的逻辑
    // 比如,更新控件的最终位置,或者记录日志等
}

在这个示例中,当拖动完成后,你可以进行必要的处理,如更新控件位置或其他业务逻辑。

4.2 拖动过程中的坐标计算与处理

4.2.1 坐标变换算法

在拖动控件时,坐标变换算法对于正确地计算新位置至关重要。这通常涉及对控件当前位置和鼠标拖动偏移量的计算。

在WPF中,坐标变换算法经常使用到Canvas布局。Canvas布局允许控件通过Canvas.Left和Canvas.Top附加属性来定位。坐标变换算法可能需要考虑不同的坐标系统,如屏幕坐标、相对于父容器的坐标或相对于元素自身的坐标。

坐标变换的一个简单示例可能如下:

// 计算新的位置坐标
double newLeft = Canvas.GetLeft(thumb) + horizontalChange;
double newTop = Canvas.GetTop(thumb) + verticalChange;
// 设置控件的新位置
Canvas.SetLeft(thumb, newLeft);
Canvas.SetTop(thumb, newTop);

上述代码示例展示了如何在Canvas布局中对控件位置进行更新。这里, Canvas.GetLeft Canvas.GetTop 函数获取当前控件的位置,然后加上从DragDelta事件中获得的偏移量,最后使用 Canvas.SetLeft Canvas.SetTop 将新位置应用到控件上。

4.2.2 坐标计算在拖动中的实现

在实现拖动功能时,坐标计算是核心组成部分。如何正确计算并更新控件的位置,将直接影响到用户拖动的体验和控件在UI中的表现。

实现坐标计算时,需要考虑以下因素:

  • 相对父容器的偏移量:计算时应确保正确处理控件相对于父容器的位置。
  • 父容器的边框和剪切限制:需要防止控件被拖动到父容器边界之外。
  • 控件大小变化:在拖动改变大小时,需要注意控件边界的约束。

以下是一个坐标计算更新控件位置的示例:

// 假设我们有一个Thumb控件在Canvas中
// 新位置基于上一次的位置和用户拖动的距离
double currentLeft = Canvas.GetLeft(thumb);
double currentTop = Canvas.GetTop(thumb);
double newLeft = currentLeft + horizontalChange;
double newTop = currentTop + verticalChange;

// 确保控件在父容器内部
// 假设parentCanvas是父Canvas控件
if (newLeft < 0) newLeft = 0;
if (newTop < 0) newTop = 0;
if (newLeft + thumb.ActualWidth > parentCanvas.ActualWidth)
    newLeft = parentCanvas.ActualWidth - thumb.ActualWidth;
if (newTop + thumb.ActualHeight > parentCanvas.ActualHeight)
    newTop = parentCanvas.ActualHeight - thumb.ActualHeight;

// 设置新的位置
Canvas.SetLeft(thumb, newLeft);
Canvas.SetTop(thumb, newTop);

在这个示例中,我们确保了控件不会被拖动到父容器边界之外,并根据父容器的尺寸更新控件位置。这种计算是必要的,以避免控件在拖动时出现不可预见的行为。

4.3 实例演示:利用事件实现控件精确拖动

在本章节中,我们将通过实例演示如何使用DragDelta和DragCompleted事件来实现精确的控件拖动功能。我们将构建一个简单的WPF应用程序,演示拖动控件,并展示如何通过坐标变换来精确控制控件的位置。

首先,我们定义XAML布局,其中包含一个Thumb控件,它将响应拖动事件:

<Window x:Class="DragExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Drag Example" Height="350" Width="525">
    <Canvas Name="MainCanvas">
        <Thumb Name="draggableThumb" DragDelta="Thumb_DragDelta" DragCompleted="Thumb_DragCompleted" Width="100" Height="100" Canvas.Left="100" Canvas.Top="100" Background="Blue"/>
    </Canvas>
</Window>

然后,我们在C#代码后台中定义事件处理函数:

private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
    Thumb thumb = sender as Thumb;
    double horizontalChange = e.HorizontalChange;
    double verticalChange = e.VerticalChange;
    double newLeft = Canvas.GetLeft(thumb) + horizontalChange;
    double newTop = Canvas.GetTop(thumb) + verticalChange;

    // 限制控件不超出Canvas边界
    if (newLeft < 0) newLeft = 0;
    if (newTop < 0) newTop = 0;
    if (newLeft + thumb.ActualWidth > MainCanvas.ActualWidth)
        newLeft = MainCanvas.ActualWidth - thumb.ActualWidth;
    if (newTop + thumb.ActualHeight > MainCanvas.ActualHeight)
        newTop = MainCanvas.ActualHeight - thumb.ActualHeight;

    // 更新控件位置
    Canvas.SetLeft(thumb, newLeft);
    Canvas.SetTop(thumb, newTop);
}

private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e)
{
    Thumb thumb = sender as Thumb;
    // 拖动完成后的逻辑处理
}

在上述C#代码中,我们首先计算控件的新位置,然后确保这些位置不会超出Canvas的边界。当DragDelta事件被触发时,Thumb控件将响应鼠标拖动,而当拖动完成后,DragCompleted事件将被触发,允许我们进行任何必要的清理或最终状态记录工作。

通过本例演示,我们可以看到如何利用事件驱动的方式实现控件的精确拖动,以及如何处理坐标变换来确保控件在拖动过程中位置的精确性。

5. 控件拖动的边界限制处理与性能优化

在进行用户界面开发时,控件的拖动功能是非常常见的交互方式。然而,实现一个高效且用户体验良好的拖动效果,需要考虑到边界限制和性能优化两个方面。这一章节将深入探讨如何在控件拖动中添加边界限制,并对性能进行优化。

5.1 控件拖动的边界限制技术

5.1.1 边界检测的逻辑实现

在用户拖动控件的过程中,合理的边界限制可以防止控件移出可视区域,从而提升界面的整洁性和用户的操作体验。实现边界检测,通常需要获取控件的位置坐标,并与父容器的边界坐标进行比较。

private void Control_MouseMove(object sender, MouseEventArgs e)
{
    if (e.ButtonState == MouseButtonState.Pressed)
    {
        Point currentPoint = e.GetPosition(this);
        double x = Math.Max(Math.Min(currentPoint.X, this.ActualWidth - this.Width), 0);
        double y = Math.Max(Math.Min(currentPoint.Y, this.ActualHeight - this.Height), 0);
        Canvas.SetLeft(this, x);
        Canvas.SetTop(this, y);
    }
}

上述代码展示了如何在WPF中对一个在Canvas上绘制的控件进行边界限制。通过计算当前鼠标位置与控件边界的关系,确保控件不会移出Canvas的可视区域。

5.1.2 边界限制对用户体验的影响

适当的边界限制能够避免控件拖动到不合理的区域,这不仅可以保持界面的整洁,也减少了用户的操作错误。然而,边界限制如果过于严格,可能会导致用户感觉操作受阻,甚至影响到拖动的流畅性。因此,需要在保持用户界面友好与控件操作自由度之间找到一个平衡点。

5.2 多控件拖动的技术实现

5.2.1 多控件拖动的场景分析

在某些应用场景下,用户可能需要同时拖动多个控件,例如调整布局时。此时,就需要实现多控件拖动的逻辑。要完成这一功能,首先需要选择一个拖动的起始点,并据此来计算其他控件的移动距离和方向。

5.2.2 多控件拖动的实现策略

多控件拖动的实现关键在于同步更新所有选中控件的位置。可以定义一个集合来存储所有选中的控件,并在拖动事件中统一更新这些控件的位置坐标。

List<UIElement> selectedElements = new List<UIElement>();

private void StartMultiDrag()
{
    // 这里是选中控件的逻辑
    selectedElements.Add(selectedControl);
}

private void PerformMultiDrag(Point currentPoint)
{
    foreach (var element in selectedElements)
    {
        Point offset = currentPoint - dragStartPoint;
        Canvas.SetLeft(element, Canvas.GetLeft(element) + offset.X);
        Canvas.SetTop(element, Canvas.GetTop(element) + offset.Y);
    }
}

以上代码示例中, StartMultiDrag 方法用于开始多控件拖动时的初始化操作,而 PerformMultiDrag 方法则在每次鼠标移动事件中调用,用于更新所有选中控件的位置。

5.3 控件拖动性能优化策略

5.3.1 性能瓶颈分析

控件拖动时,如果频繁进行屏幕刷新和重绘,将会导致性能瓶颈。这在多个控件同时进行拖动时尤为明显。性能问题通常表现为拖动过程中的卡顿和延迟。

5.3.2 性能优化方法与技巧

优化控件拖动性能的方法有多种,如减少不必要的重绘,使用双缓冲技术,以及合理地管理渲染更新。在WPF中,可以通过启用硬件加速来提升性能。

<Window ...
          AllowsTransparency="False"
          WindowStyle="None"
          Background="Transparent"
          SnapsToDevicePixels="true">

上述XAML属性中, SnapsToDevicePixels 设置为true,可以减少渲染时的模糊,并提升性能。

5.4 实例演示:综合应用与性能测试

在这一小节中,我们将展示如何将边界限制、多控件拖动与性能优化结合在一起,并进行实际的性能测试。性能测试的结果将帮助我们验证优化策略的效果,并指导我们进一步调整和改进。

通过上述章节的详细解读,我们了解了如何在实现控件拖动时添加边界限制,处理多控件拖动的逻辑,并应用性能优化的策略。在下一章节中,我们将继续探讨如何在拖动完成后进行事件处理,以及如何在拖动结束时保持控件状态的一致性。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文深入探讨了C#中控件拖动功能的实现细节,包括在Windows Forms和WPF环境下如何处理拖动事件、更新控件位置、以及相关边界和性能优化的技巧。文中通过实例解释了如何监听和响应拖动事件,以及如何通过限制控件拖动的边界来提升用户体验。此外,还讨论了多控件拖动和性能优化策略,旨在帮助开发者在应用程序中实现高效和流畅的用户界面交互。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值