how to get smartphone like scrolling for a winforms touchscreen app ( scrolling panel )

前端 未结 6 952
暖寄归人
暖寄归人 2020-12-30 04:15

After scouring the articles online I have come up with this design for a winforms based touchscreen app that needs smartphone like scrolling. The app itself will run on a ta

相关标签:
6条回答
  • 2020-12-30 04:42

    7 Years later but for anyone that is looking for a neat and tidy winforms solution:

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    
        /// <summary>
        /// Pass the panel into constructor and the control will be turned into a touch scrollable control.
        /// </summary>
        public class TouchScroll
        {
            private Point mouseDownPoint;
            private Panel parentPanel;
    
            /// <summary>
            /// pass in the panel you would like to be touch scrollable and it will be handled here.
            /// </summary>
            /// <param name="panel">The root panel you need to scroll with</param>
            public TouchScroll(Panel panel)
            {
                parentPanel = panel;
                AssignEvents(panel);
            }
    
            private void AssignEvents(Control control)
            {
                control.MouseDown += MouseDown;
                control.MouseMove += MouseMove;
                foreach (Control child in control.Controls)
                    AssignEvents(child);
            }
    
            private void MouseDown(object sender, MouseEventArgs e)
            {
                if (e.Button == MouseButtons.Left)
                    this.mouseDownPoint = Cursor.Position;
            }
    
            private void MouseMove(object sender, MouseEventArgs e)
            {
                if (e.Button != MouseButtons.Left)
                    return;
    
                Point pointDifference = new Point(Cursor.Position.X - mouseDownPoint.X, Cursor.Position.Y - mouseDownPoint.Y);
    
                if ((mouseDownPoint.X == Cursor.Position.X) && (mouseDownPoint.Y == Cursor.Position.Y))
                    return;
    
                Point currAutoS = parentPanel.AutoScrollPosition;
                parentPanel.AutoScrollPosition = new Point(Math.Abs(currAutoS.X) - pointDifference.X, Math.Abs(currAutoS.Y) - pointDifference.Y);
                mouseDownPoint = Cursor.Position; //IMPORTANT
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-30 04:44

    And as a Component:

    public partial class TouchableFlowLayoutPanel : FlowLayoutPanel
    {
        private bool _doTouchScroll;
        private Point _mouseStartPoint = Point.Empty;
        private Point _panelStartPoint = Point.Empty;
    
        /// <summary>
        ///     Initializes a new instance of the <see cref="TouchableFlowLayoutPanel" /> class.
        /// </summary>
        public TouchableFlowLayoutPanel()
        {
            InitializeComponent();
    
            Program.mouseFilter.MouseFilterDown += mouseFilter_MouseFilterDown;
            Program.mouseFilter.MouseFilterMove += mouseFilter_MouseFilterMove;
            Program.mouseFilter.MouseFilterUp += mouseFilter_MouseFilterUp;
        }
    
        /// <summary>
        ///     Initializes a new instance of the <see cref="TouchableFlowLayoutPanel" /> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public TouchableFlowLayoutPanel(IContainer container)
        {
            container.Add(this);
    
            InitializeComponent();
    
            Program.mouseFilter.MouseFilterDown += mouseFilter_MouseFilterDown;
            Program.mouseFilter.MouseFilterMove += mouseFilter_MouseFilterMove;
            Program.mouseFilter.MouseFilterUp += mouseFilter_MouseFilterUp;
        }
    
        /// <summary>
        ///     Handles the MouseFilterDown event of the mouseFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterDown(object sender, MouseFilterEventArgs e)
        {
            if (!_doTouchScroll && e.Button == MouseButtons.Left)
            {
                _mouseStartPoint = new Point(e.X, e.Y);
                _panelStartPoint = new Point(-AutoScrollPosition.X,
                                                     -AutoScrollPosition.Y);
            }
        }
    
        /// <summary>
        ///     Handles the MouseFilterMove event of the mouseFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterMove(object sender, MouseFilterEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (!_mouseStartPoint.Equals(Point.Empty))
                {
                    int dx = (e.X - _mouseStartPoint.X);
                    int dy = (e.Y - _mouseStartPoint.Y);
    
                    if (_doTouchScroll)
                    {
                        AutoScrollPosition = new Point(_panelStartPoint.X - dx,
                                                       _panelStartPoint.Y - dy);
                    }
                    else if (Math.Abs(dx) > 10 || Math.Abs(dy) > 10)
                    {
                        _doTouchScroll = true;
                    }
                }
            }
        }
    
        /// <summary>
        ///     Handles the MouseFilterUp event of the mouseFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterUp(object sender, MouseFilterEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (_doTouchScroll && !AutoScrollPosition.Equals(_panelStartPoint) &&
                    !_panelStartPoint.Equals(Point.Empty))
                {
                    // don't fire Click-Event
                    e.Handled = true;
                }
                _doTouchScroll = false;
                _mouseStartPoint = Point.Empty;
                _panelStartPoint = Point.Empty;
            }
        }
    }
    
    internal class MouseFilter : IMessageFilter
    {
        private const int WM_LBUTTONDOWN = 0x0201;
        private const int WM_LBUTTONUP = 0x0202;
        private const int WM_MOUSEMOVE = 0x0200;
    
        /// <summary>
        ///     Filters a message before sending it
        /// </summary>
        /// <param name="m">The message to be sent.This message can not be changed.</param>
        /// <returns>
        ///     true to filter the message and prevent it from being sent. false to allow the message to be sent to the next filter or control.
        /// </returns>
        public bool PreFilterMessage(ref Message m)
        {
            Point mousePosition = Control.MousePosition;
            var args = new MouseFilterEventArgs(MouseButtons.Left, 0, mousePosition.X, mousePosition.Y, 0);
    
            switch (m.Msg)
            {
                case WM_MOUSEMOVE:
                    if (MouseFilterMove != null)
                    {
                        MouseFilterMove(Control.FromHandle(m.HWnd), args);
                    }
                    break;
    
                case WM_LBUTTONDOWN:
                    if (MouseFilterDown != null)
                    {
                        MouseFilterDown(Control.FromHandle(m.HWnd), args);
                    }
                    break;
    
                case WM_LBUTTONUP:
                    if (MouseFilterUp != null)
                    {
                        MouseFilterUp(Control.FromHandle(m.HWnd), args);
                    }
                    break;
            }
    
            // Always allow message to continue to the next filter control
            return args.Handled;
        }
    
        /// <summary>
        ///     Occurs when [mouse filter up].
        /// </summary>
        public event MouseFilterEventHandler MouseFilterUp;
    
        /// <summary>
        ///     Occurs when [mouse filter down].
        /// </summary>
        public event MouseFilterEventHandler MouseFilterDown;
    
        /// <summary>
        ///     Occurs when [mouse filter move].
        /// </summary>
        public event MouseFilterMoveEventHandler MouseFilterMove;
    }
    
    internal delegate void MouseFilterEventHandler(object sender, MouseFilterEventArgs args);
    
    internal delegate void MouseFilterMoveEventHandler(object sender, MouseFilterEventArgs args);
    
    internal class MouseFilterEventArgs
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="MouseFilterEventArgs" /> class.
        /// </summary>
        /// <param name="mouseButton">The mouse button.</param>
        /// <param name="clicks">The clicks.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="delta">The delta.</param>
        public MouseFilterEventArgs(MouseButtons mouseButton, int clicks, int x, int y, int delta)
        {
            Button = mouseButton;
            Clicks = clicks;
            X = x;
            Y = y;
            Delta = delta;
            Handled = false;
        }
    
        /// <summary>
        ///     Gets or sets the button.
        /// </summary>
        /// <value>
        ///     The button.
        /// </value>
        public MouseButtons Button { get; set; }
    
        /// <summary>
        ///     Gets or sets a value indicating whether this <see cref="MouseFilterEventArgs" /> is handled.
        /// </summary>
        /// <value>
        ///     <c>true</c> if handled; otherwise, <c>false</c>.
        /// </value>
        public bool Handled { get; set; }
    
        /// <summary>
        ///     Gets or sets the X.
        /// </summary>
        /// <value>
        ///     The X.
        /// </value>
        public int X { get; set; }
    
        /// <summary>
        ///     Gets or sets the Y.
        /// </summary>
        /// <value>
        ///     The Y.
        /// </value>
        public int Y { get; set; }
    
        /// <summary>
        ///     Gets or sets the clicks.
        /// </summary>
        /// <value>
        ///     The clicks.
        /// </value>
        public int Clicks { get; set; }
    
        /// <summary>
        ///     Gets or sets the delta.
        /// </summary>
        /// <value>
        ///     The delta.
        /// </value>
        public int Delta { get; set; }
    }
    
    static class Program
    {
        public static MouseFilter mouseFilter = new MouseFilter();
    
        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.AddMessageFilter(mouseFilter);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
    
    0 讨论(0)
  • 2020-12-30 04:52

    To the original post, considering Cheap Funeral answer.

    This version is more simple and more fluid to the touch (considers the finger move speed but not considering the effect of still scrolling after removing the finger, like moving but speeding down(maybe i'll do that whem i have time))

    Works for a panel or FlowLayoutPanel

    Create a form and insert a FlowLayoutPanel on it.

    Form code:

    Public Class Form1
    
    Private Function GenerateButton(pName As String) As Button
        Dim mResult As New Button
        With mResult
            .Name = pName
            .Text = pName
            .Width = FlowPanel.Width
            .Height = 100
            .Margin = New Padding(0)
            .Padding = New Padding(0)
            .BackColor = Color.CornflowerBlue
            AddHandler .MouseDown, AddressOf Button_MouseDown
            AddHandler .MouseMove, AddressOf Button_MouseMove
        End With
    
        Return mResult
    End Function
    
    
    
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        FlowPanel.Padding = New Padding(0)
        FlowPanel.Margin = New Padding(0)
        Dim i As Integer
        For i = 1 To 100
            FlowPanel.Controls.Add(GenerateButton("btn" & i.ToString))
        Next
    End Sub
    
    Dim myMouseDownPoint As Point
    Dim myCurrAutoSMouseDown As Point
    Private Sub Button_MouseDown(sender As Object, e As MouseEventArgs) Handles FlowPanel.MouseDown
        myMouseDownPoint = PointToClient(Cursor.Position)
        myCurrAutoSMouseDown = FlowPanel.AutoScrollPosition
    End Sub
    
    Private Sub Button_MouseMove(sender As Object, e As MouseEventArgs) Handles FlowPanel.MouseMove
        If e.Button = Windows.Forms.MouseButtons.Left Then
            Dim mLocation As Point = PointToClient(Cursor.Position)
            If myMouseDownPoint <> mLocation Then
                Dim mCurrAutoS As Point
                Dim mDeslocation As Point = myMouseDownPoint - mLocation
                mCurrAutoS.X = Math.Abs(myCurrAutoSMouseDown.X) + mDeslocation.X
                mCurrAutoS.Y = Math.Abs(myCurrAutoSMouseDown.Y) + mDeslocation.Y
    
                FlowPanel.AutoScrollPosition = mCurrAutoS
    
            End If
        End If
    End Sub
    

    TIP: To hide the scrolls of the flowlayoutpanel(or panel), create a usercontrol that inherits from flowlayoutpanel(or panel) and:

    Imports System.Runtime.InteropServices
    
    Public Class FlowLayoutPanelExt
    Inherits FlowLayoutPanel
    
    
    <DllImport("user32.dll")> _
    Private Shared Function ShowScrollBar(hWnd As IntPtr, wBar As Integer, bShow As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function
    
    Private Enum ScrollBarDirection
        SB_HORZ = 0
        SB_VERT = 1
        SB_CTL = 2
        SB_BOTH = 3
    End Enum
    
    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        If Me.Visible Then
            ShowScrollBar(Me.Handle, CInt(ScrollBarDirection.SB_BOTH), False)
            MyBase.WndProc(m)
        End If
    End Sub
    
    Public Sub New()
    
        ' This call is required by the designer.
        InitializeComponent()
    
        ' Add any initialization after the InitializeComponent() call.
        Me.AutoScroll = True
    End Sub
    
    End Class
    
    0 讨论(0)
  • 2020-12-30 04:57

    I used the code the OP posted but found it doesnt work if there are things with in the panel like labels. To make this work more smoothly I changed .

    e.Location 
    

    to

    PointToClient(Cursor.Position) 
    

    and then had all objects inside the panel also call the mousedown and mousemove events. This way not matter where you click it should move.

    0 讨论(0)
  • 2020-12-30 05:08

    This´s my way using IMessageFilter. For everyone who´s looking for a solutiion. First you have to implement a Filter that will listen for all Application Events:

    internal class MouseFilter : IMessageFilter
    {
        private const int WM_LBUTTONDOWN = 0x0201;
        private const int WM_LBUTTONUP = 0x0202;
        private const int WM_MOUSEMOVE = 0x0200;
    
        /// <summary>
        ///     Filtert eine Meldung, bevor sie gesendet wird.
        /// </summary>
        /// <param name="m">Die zu sendende Meldung. Diese Meldung kann nicht geändert werden.</param>
        /// <returns>
        ///     true, um die Meldung zu filtern und das Senden zu verhindern. false, um das Senden der Meldung bis zum nächsten Filter oder Steuerelement zu ermöglichen.
        /// </returns>
        public bool PreFilterMessage(ref Message m)
        {
            Point mousePosition = Control.MousePosition;
            var args = new MouseFilterEventArgs(MouseButtons.Left, 0, mousePosition.X, mousePosition.Y, 0);
    
            switch (m.Msg)
            {
                case WM_MOUSEMOVE:
                    if (MouseFilterMove != null)
                    {
                        MouseFilterMove(Control.FromHandle(m.HWnd), args);
                    }
                    break;
    
                case WM_LBUTTONDOWN:
                    if (MouseFilterDown != null)
                    {
                        MouseFilterDown(Control.FromHandle(m.HWnd), args);
                    }
                    break;
    
                case WM_LBUTTONUP:
                    if (MouseFilterUp != null)
                    {
                        MouseFilterUp(Control.FromHandle(m.HWnd), args);
                    }
                    break;
            }
    
            // Always allow message to continue to the next filter control
            return args.Handled;
        }
    
        /// <summary>
        ///     Occurs when [mouse filter up].
        /// </summary>
        public event MouseFilterEventHandler MouseFilterUp;
    
        /// <summary>
        ///     Occurs when [mouse filter down].
        /// </summary>
        public event MouseFilterEventHandler MouseFilterDown;
    
        /// <summary>
        ///     Occurs when [mouse filter move].
        /// </summary>
        public event MouseFilterMoveEventHandler MouseFilterMove;
    }
    
    internal delegate void MouseFilterEventHandler(object sender, MouseFilterEventArgs args);
    
    internal delegate void MouseFilterMoveEventHandler(object sender, MouseFilterEventArgs args);
    
    internal class MouseFilterEventArgs
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="MouseFilterEventArgs" /> class.
        /// </summary>
        /// <param name="mouseButton">The mouse button.</param>
        /// <param name="clicks">The clicks.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="delta">The delta.</param>
        public MouseFilterEventArgs(MouseButtons mouseButton, int clicks, int x, int y, int delta)
        {
            Button = mouseButton;
            Clicks = clicks;
            X = x;
            Y = y;
            Delta = delta;
            Handled = false;
        }
    
        /// <summary>
        ///     Gets or sets the button.
        /// </summary>
        /// <value>
        ///     The button.
        /// </value>
        public MouseButtons Button { get; set; }
    
        /// <summary>
        ///     Gets or sets a value indicating whether this <see cref="MouseFilterEventArgs" /> is handled.
        /// </summary>
        /// <value>
        ///     <c>true</c> if handled; otherwise, <c>false</c>.
        /// </value>
        public bool Handled { get; set; }
    
        /// <summary>
        ///     Gets or sets the X.
        /// </summary>
        /// <value>
        ///     The X.
        /// </value>
        public int X { get; set; }
    
        /// <summary>
        ///     Gets or sets the Y.
        /// </summary>
        /// <value>
        ///     The Y.
        /// </value>
        public int Y { get; set; }
    
        /// <summary>
        ///     Gets or sets the clicks.
        /// </summary>
        /// <value>
        ///     The clicks.
        /// </value>
        public int Clicks { get; set; }
    
        /// <summary>
        ///     Gets or sets the delta.
        /// </summary>
        /// <value>
        ///     The delta.
        /// </value>
        public int Delta { get; set; }
    }
    

    Then you have to register this Filter to you Program:

    static class Program
    {
        public static MouseFilter mouseFilter = new MouseFilter();
    
        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.AddMessageFilter(mouseFilter);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
    

    And now you can listen for global Mouse Events and scroll your scrollable Panel like this:

    public partial class MainForm : Form
    {
        private bool _doTouchScroll;
        private Point _mouseStartPoint = Point.Empty;
        private Point _yourScrollablePanelStartPoint = Point.Empty;
    
        /// <summary>
        ///     Initializes a new instance of the <see cref="MainForm" /> class.
        /// </summary>
        public MainForm()
        {
            InitializeComponent();
    
            Program.mouseFilter.MouseFilterDown += mouseFilter_MouseFilterDown;
            Program.mouseFilter.MouseFilterMove += mouseFilter_MouseFilterMove;
            Program.mouseFilter.MouseFilterUp += mouseFilter_MouseFilterUp;
        }
    
        /// <summary>
        ///     Handles the MouseFilterDown event of the mudmFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterDown(object sender, MouseFilterEventArgs e)
        {
            if (!_doTouchScroll && e.Button == MouseButtons.Left)
            {
                _mouseStartPoint = new Point(e.X, e.Y);
                _yourScrollablePanelStartPoint = new Point(-yourScrollablePanel.AutoScrollPosition.X,
                                                     -yourScrollablePanel.AutoScrollPosition.Y);
            }
        }
    
        /// <summary>
        ///     Handles the MouseFilterMove event of the mudmFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterMove(object sender, MouseFilterEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (!_mouseStartPoint.Equals(Point.Empty))
                {
                    int dx = (e.X - _mouseStartPoint.X);
                    int dy = (e.Y - _mouseStartPoint.Y);
    
                    if (_doTouchScroll)
                    {
                        yourScrollablePanel.AutoScrollPosition = new Point(_yourScrollablePanelStartPoint.X - dx,
                                                                     _yourScrollablePanelStartPoint.Y - dy);
                    }
                    else if (Math.Abs(dx) > 10 || Math.Abs(dy) > 10)
                    {
                        _doTouchScroll = true;
                    }
                }
            }
        }
    
        /// <summary>
        ///     Handles the MouseFilterUp event of the mudmFilter control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        ///     The <see cref="MouseFilterEventArgs" /> instance containing the event data.
        /// </param>
        private void mouseFilter_MouseFilterUp(object sender, MouseFilterEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (_doTouchScroll && !yourScrollablePanel.AutoScrollPosition.Equals(_yourScrollablePanelStartPoint) &&
                    !_yourScrollablePanelStartPoint.Equals(Point.Empty))
                {
                    // dont fire Click-Event
                    e.Handled = true;
                }
                _doTouchScroll = false;
                _mouseStartPoint = Point.Empty;
                _yourScrollablePanelStartPoint = Point.Empty;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-30 05:08

    This solution seems to be the best one out there and the most commonly accepted one - however, if you scroll to the bottom and touch a the actual flowcontrol behind the buttons (I tried to make this so that there would be empty space), you then have to double tap-and-hold the button for the scrolling to resume. Restarting the application restores the phone-like scrolling functionality. I am wondering if anyone else has seen this or figured it out - try it with your apps and see if it is the case as well. I modified the snippet above so that you can start a new project, copy and paste this into form1's code, and hit run.

       Public Class Form1
            Dim FlowPanel As New FlowLayoutPanel
            Private Function GenerateButton(ByVal pName As String) As Button
                Dim mResult As New Button
                With mResult
                    .Name = pName
                    .Text = pName
                    .Width = 128
                    .Height = 128
                    .Margin = New Padding(0)
                    .Padding = New Padding(0)
                    .BackColor = Color.CornflowerBlue
                    AddHandler .MouseDown, AddressOf Button_MouseDown
                    AddHandler .MouseMove, AddressOf Button_MouseMove
                End With
    
                Return mResult
            End Function
    
    
    
            Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
    
                Me.Width = 806
                Me.Height = 480
                FlowPanel.Padding = New Padding(0)
                FlowPanel.Margin = New Padding(0)
                ' FlowPanel.ColumnCount = Me.Width / (128 + 6)
                FlowPanel.Dock = DockStyle.Fill
                FlowPanel.AutoScroll = True
                Me.Controls.Add(FlowPanel)
                Dim i As Integer
                For i = 1 To 98
                    FlowPanel.Controls.Add(GenerateButton("btn" & i.ToString))
                Next
            End Sub
    
            Dim myMouseDownPoint As Point
            Dim myCurrAutoSMouseDown As Point
            Private Sub Button_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs)
                myMouseDownPoint = PointToClient(Cursor.Position)
                myCurrAutoSMouseDown = FlowPanel.AutoScrollPosition
            End Sub
    
            Private Sub Button_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
                If e.Button = Windows.Forms.MouseButtons.Left Then
                    Dim mLocation As Point = PointToClient(Cursor.Position)
                    If myMouseDownPoint <> mLocation Then
                        Dim mCurrAutoS As Point
                        Dim mDeslocation As Point = myMouseDownPoint - mLocation
                        mCurrAutoS.X = Math.Abs(myCurrAutoSMouseDown.X) + mDeslocation.X
                        mCurrAutoS.Y = Math.Abs(myCurrAutoSMouseDown.Y) + mDeslocation.Y
    
                        FlowPanel.AutoScrollPosition = mCurrAutoS
    
                    End If
                End If
            End Sub
        End Class
    
    0 讨论(0)
提交回复
热议问题