精华内容
下载资源
问答
  • C# 跨线程访问控件

    2013-03-23 20:01:06
    在用CS结构开发系统时,我们常会使用到多线程来处理程序,其中一个就是使用跨线程访问控件或设置控件属性。如果这样,在调试时会提示出错,不能在其它线程中修改创建控件的线程的控件的值。这是因为.net 2.0以后加强...

    在用CS结构开发系统时,我们常会使用到多线程来处理程序,其中一个就是使用跨线程访问控件或设置控件属性。如果这样,在调试时会提示出错,不能在其它线程中修改创建控件的线程的控件的值。这是因为.net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。

     

    我们先看一下传统的程序:

    设计界面如下:在窗体中添加一个Label控件(用来显示时间)

     

    源程序:

    using System;

    using System.Windows.Forms;

    using System.Threading;

     

    namespace KingChat

    {

        public partial class SystemLogin : DevExpress.XtraEditors.XtraForm

        {

                public SystemLogin()

                 {

                       InitializeComponent();

             }

            

                  private void SystemLogin_Load(object sender, EventArgs e)

                  {

             Thread newThread = new Thread(newThreadStart(threadFuntion));

                       newThread.IsBackground = true;

                       newThread.Start();

             }

            

    private void threadFuntion()

                  {

                       while (true)

                       {

                           this.label1.Text =DateTime.Now.ToString();

                           Thread.Sleep(1000);

                       }

            }

    }

    }

     

    当调试上面的程序是会提示出错。 

    原因是:.NET 原则上禁止跨线程访问控件,因为这样可能造成错误的发生。

     

    那么我们有什么方法解决C#跨线程访问控件问题:

    1)禁止编译器对跨线程访问作检查,可以实现访问,但是出不出错不敢保证;

    2)推荐的解决方法是采用代理,用代理方法来间接操作不是同一线程创建的控件。

     

    解决方案一:

    利用Control.CheckForIllegalCrossThreadCalls = false;加入这句代码调试时也不会提示出错,可以正常执行下面的程序。这句代码就是说在这个类中我们不检查跨线程的调用是否合法(如果没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。我们查看CheckForIllegalCrossThreadCalls这个属性的定义,就会发现它是一个static的,也就是说无论我们在项目的什么地方修改了这个值,他就会在全局起作用。而且像这种跨线程访问是否存在异常,我们通常都会去检查。如果项目中其他人修改了这个属性,那么我们的方案就失败了,我们要采取另外的方案。

     

    具体做法如下:

    在窗体的Load方法中加入代码,其他不变

    private void SystemLogin_Load(object sender, EventArgs e)

        {

             Control.CheckForIllegalCrossThreadCalls =false;

             Thread newThread = new Thread(newThreadStart(threadFuntion));

             newThread.IsBackground = true;

             newThread.Start();

    }

     

     

    解决方案二:

    就是使用delegateinvoke来从其他线程中控制控件信息。

    源代码如下:

    using System;

    using System.Windows.Forms;

    using System.Threading;

     

    namespace KingChat

    {

        public partial class SystemLogin : DevExpress.XtraEditors.XtraForm

    {

        private delegate void DelegateFunction();//代理

                public SystemLogin()

                 {

                       InitializeComponent();

             }

            

                  private void SystemLogin_Load(object sender, EventArgs e)

                  {

                       Thread thread =new Thread(CrossThreadFlush);

                       thread.IsBackground = true;

                       thread.Start();

             }

            

    private void CrossThreadFlush ()

                  {

                       while (true)

                       {

                           Thread.Sleep(1000);

                           ThreadFunction();

                       }

            }

            

             private void ThreadFunction()

    {

         if (this.label1.InvokeRequired)//等待异步

                               {

                           DelegateFunction df =new DelegateFunction(ThreadFunction);

                           this.Invoke(df);//通过代理调用刷新方法

                       }

                       else

                       {

                           this.label1.Text =DateTime.Now.ToString();

                       }

    }

    }

    }

          

    我们可以看到问题已经被解决了,通过等待异步,我们就不会总是持有主线程的控制,这样就可以在不发生跨线程调用异常的情况下完成多线程对winform多线程控件的控制了。

     

    刚运行可执行文件时会先显示Label控件的Text值,大概一秒后就可以看到时间:

     

    4                           5

     

    解决方案二的优化:

    利用了delegate的异步调用。

    using System;

    using System.Windows.Forms;

    using System.Threading;

     

    namespace KingChat

    {

        public partial class SystemLogin : DevExpress.XtraEditors.XtraForm

    {

        private delegate void DelegateFunction();//代理

                public SystemLogin()

                 {

                       InitializeComponent();

             }

            

                  private void SystemLogin_Load(object sender, EventArgs e)

                  {

                       Thread thread =new Thread(CrossThreadFlush);

                       thread.IsBackground = true;

                       thread.Start();

             }

            

    private void CrossThreadFlush ()

                  {

                       DelegateFunction df =new DelegateFunction(ThreadFunction);

                       df.BeginInvoke(null,null);

            }

            

             private void ThreadFunction()

    {

         while (true)

           {

                           this.label1.Text =DateTime.Now.ToString();

                           Thread.Sleep(1000);

                       }

    }

    }

    }

     

    展开全文
  • c#跨线程访问控件

    2016-05-26 16:57:01
    在C#中,在线程中(包括 BackgroundWorker 控件)访问主线程UI时会抛出异常:因为.net禁止了跨线程调用控件。下面用一个办法来进行跨线程的调用,且不阻塞控件。以给控件赋Text值为例,因为这个比较实用。 private ...

    在C#中,在线程中(包括 BackgroundWorker 控件)访问主线程UI时会抛出异常:因为.net禁止了跨线程调用控件。下面用一个办法来进行跨线程的调用,且不阻塞控件。以给控件赋Text值为例,因为这个比较实用。

    private delegate void Thread_SetControl_Text(Control c,string str);
            private void ThreadSetControlText(Control c,string str)
            {
                if (c.InvokeRequired)//等待异步 
                {
                    Thread_SetControl_Text fc = new Thread_SetControl_Text(ThreadSetControlText);
                    this.Invoke(fc, new object[2] {c, str});
    
                }
                else
                {
                    c.Text = str;
                    c.Refresh();
    
                }
            }
    在线程中如下调用(假设有 textBox1) :

    ThreadSetControlText(this.textBox1,"ABC");




    展开全文
  • C#中跨线程访问控件

    2019-10-07 23:57:12
    net 原则上禁止跨线程访问控件,因为这样可能造成错误的发生,推荐的解决方法是采用代理,用代理方法来间接操作不是同一线程创建的控件。第二种方法是禁止编译器对跨线程访问作检查,可以实现访问,但是出不出错不敢...

    net 原则上禁止跨线程访问控件,因为这样可能造成错误的发生,推荐的解决方法是采用代理,用代理方法来间接操作不是同一线程创建的控件。 

    第二种方法是禁止编译器对跨线程访问作检查,可以实现访问,但是出不出错不敢保证Control.CheckForIllegalCrossThreadCalls = false;

    最近我在做一个项目,遇到了跨线程要去访问页面控件.但是总是提示出错,不能在其它线程中修改创建控件的线程的控件的值,后来采用了匿名代理,结果很轻松地解决了.解决过程如下:
    首先在窗体上,创建一个listbox,lable.

     1 using System;
     2 using System.Collections.Generic;
     3 using System.ComponentModel;
     4 using System.Data;
     5 using System.Drawing;
     6 using System.Text;
     7 using System.Windows.Forms;
     8 using System.Threading;
     9 
    10 namespace AccessControl
    11 {
    12     public partial class Form1 : Form
    13     {      
    14         public Form1()
    15         {
    16             InitializeComponent();
    17         }
    18 
    19         private void Form1_Load(object sender, EventArgs e)
    20         {  
    21             Thread newthread = new Thread(new ThreadStart(BackgroundProcess));
    22             newthread.Start();         
    23 
    24         }
    25 
    26         /// <summary> 
    27         /// 定义一个代理 
    28         /// </summary> 
    29         private delegate void CrossThreadOperationControl();
    30 
    31         private void BackgroundProcess()
    32         {
    33             // 将代理实例化为一个匿名代理 
    34             CrossThreadOperationControl CrossDelete = delegate()          
    35             {            
    36                 int i = 1;
    37                 while (i<5)
    38                 {
    39                    // 向列表框增加一个项目 
    40                     listBox1.Items.Add("Item " + i.ToString());                    
    41                     i++;
    42                 }
    43                 label1.Text = "我在新线程里访问这个lable!";
    44                 listBox1.Items.Add(label1.Text);
    45             }  ;
    46             listBox1.Invoke(CrossDelete);           
    47         }       
    48 
    49     }
    50 }

     

    希望这个小技巧能够对你的的学习和工作有所帮助.若有更好的办法来解决跨线程访问控件的问题,不防也拿出来大家分享一下.

    C#跨线程访问控件运行时错误,使用MethodInvoker即可解决:

    原代码:

            

     1 private void btnOK_Click(object sender, EventArgs e)
     2         {
     3             tslInfo.Text = "请稍候...";
     4 
     5 
     6             Thread td = new Thread(new ThreadStart(run));
     7             td.Start();
     8         }
     9 
    10 
    11         /// <summary>
    12         /// 线程方法
    13         /// </summary>
    14         private void run()
    15         {
    16             this.tslInfo.Text = "就绪";
    17         }

     

    修改后:


            

     1 private void btnOK_Click(object sender, EventArgs e)
     2         {
     3             tslInfo.Text = "请稍候...";
     4 
     5 
     6             Thread td = new Thread(new ThreadStart(threadRun));
     7             td.Start();
     8         }
     9 
    10 
    11         /// <summary>
    12         /// 原线程方法
    13         /// </summary>
    14         private void run()
    15         {
    16             this.tslInfo.Text = "就绪";
    17         }
    18 
    19         /// <summary>
    20         /// 线程方法
    21         /// </summary>
    22         private void threadRun()
    23         {
    24             MethodInvoker In = new MethodInvoker(run);
    25             this.BeginInvoke(In);
    26         }

     

    我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题。然而我们并不能用传统方法来做这个问题,下面我将详细的介绍。

          首先来看传统方法:

         

     1  public partial class Form1 : Form
     2     {
     3         public Form1()
     4         {
     5             InitializeComponent();
     6         }
     7         private void Form1_Load(object sender, EventArgs e)
     8         {
     9             Thread thread = new Thread(ThreadFuntion);
    10             thread.IsBackground = true;
    11             thread.Start();
    12         }
    13         private void ThreadFuntion()
    14         {
    15             while (true)
    16             {
    17                 this.textBox1.Text = DateTime.Now.ToString();
    18                 Thread.Sleep(1000);
    19             }
    20         }
    21     }

     

           运行这段代码,我们会看到系统抛出一个异常:Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on . 这是因为.net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。那么怎么解决这个问题呢,下面提供几种方案。

          第一种方案,我们在Form1_Load()方法中加一句代码:

          

    1 private void Form1_Load(object sender, EventArgs e)
    2       {
    3             Control.CheckForIllegalCrossThreadCalls = false;
    4             Thread thread = new Thread(ThreadFuntion);
    5             thread.IsBackground = true;
    6             thread.Start();
    7         }


          加入这句代码以后发现程序可以正常运行了。这句代码就是说在这个类中我们不检查跨线程的调用是否合法(如果没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。我们查看CheckForIllegalCrossThreadCalls 这个属性的定义,就会发现它是一个static的,也就是说无论我们在项目的什么地方修改了这个值,他就会在全局起作用。而且像这种跨线程访问是否存在异常,我们通常都会去检查。如果项目中其他人修改了这个属性,那么我们的方案就失败了,我们要采取另外的方案。

          下面来看第二种方案,就是使用delegate和invoke来从其他线程中控制控件信息。网上有很多人写了这种控制方式,然而我看了很多这种帖子,表明上看来是没有什么问题的,但是实际上并没有解决这个问题,首先来看网络上的那种不完善的方式:

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11 
    12             thread.IsBackground=true;
    13             thread.Start();
    14         }
    15 
    16         private void CrossThreadFlush()
    17         {
    18             //将代理绑定到方法 
    19             FlushClient fc = new FlushClient(ThreadFuntion);
    20             this.BeginInvoke(fc);//调用代理
    21         }
    22         private void ThreadFuntion()
    23         {
    24             while (true)
    25             {
    26                 this.textBox1.Text = DateTime.Now.ToString();
    27                 Thread.Sleep(1000);
    28             }
    29         }
    30     }

     

           使用这种方式我们可以看到跨线程访问的异常没有了。但是新问题出现了,界面没有响应了。为什么会出现这个问题,我们只是让新开的线程无限循环刷新,理论上应该不会对主线程产生影响的。其实不然,这种方式其实相当于把这个新开的线程“注入”到了主控制线程中,它取得了主线程的控制。只要这个线程不返回,那么主线程将永远都无法响应。就算新开的线程中不使用无限循环,使可以返回了。这种方式的使用多线程也失去了它本来的意义。

           现在来让我们看看推荐的解决方案(建议用该方案):

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11             thread.IsBackground = true;
    12             thread.Start();
    13         }
    14 
    15         private void CrossThreadFlush()
    16         {
    17             while (true)
    18             {
    19                 //将sleep和无限循环放在等待异步的外面
    20                 Thread.Sleep(1000);
    21                 ThreadFunction();
    22             }
    23         }
    24         private void ThreadFunction()
    25         {
    26             if (this.textBox1.InvokeRequired)//等待异步
    27             {
    28                 FlushClient fc = new FlushClient(ThreadFunction);
    29                 this.Invoke(fc);//通过代理调用刷新方法
    30             }
    31             else
    32             {
    33                 this.textBox1.Text = DateTime.Now.ToString();
    34             }
    35         }
    36     }

     

           运行上述代码,我们可以看到问题已经被解决了,通过等待异步,我们就不会总是持有主线程的控制,这样就可以在不发生跨线程调用异常的情况下完成多线程对winform多线程控件的控制了。

           对于深山老林提出的问题,我最近找到了更优的解决方案,利用了delegate的异步调用,大家可以看看:

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11             thread.IsBackground = true;
    12             thread.Start();
    13         }
    14 
    15         private void CrossThreadFlush()
    16         {
    17 
    18              FlushClient=new FlushClient(ThreadFunction);
    19 
    20              FlushClient.BeginInvoke(null,null);
    21         }
    22         private void ThreadFunction()
    23         {
    24 
    25               while (true)
    26             {
    27                 this.textBox1.Text = DateTime.Now.ToString();
    28                 Thread.Sleep(1000);
    29             }
    30 
    31         }
    32     }

     

         这种方法也可以直接简化为(因为delegate的异步就是开了一个异步线程):

     1 public partial class Form1 : Form
     2     {
     3         private delegate void FlushClient();//代理
     4         public Form1()
     5         {
     6             InitializeComponent();
     7         }
     8         private void Form1_Load(object sender, EventArgs e)
     9         {
    10             Thread thread = new Thread(CrossThreadFlush);
    11              FlushClient=new FlushClient(ThreadFunction);
    12 
    13              FlushClient.BeginInvoke(null,null);
    14         }
    15 
    16          private void ThreadFunction()
    17         {
    18 
    19               while (true)
    20             {
    21                 this.textBox1.Text = DateTime.Now.ToString();
    22                 Thread.Sleep(1000);
    23             }
    24 
    25         }
    26     }

     

    转载于:https://www.cnblogs.com/hanc/p/3685995.html

    展开全文
  • C# WinForm 跨线程访问控件 在做WinFrom开发的时候,经常会遇到跨线程访问控件的问题,即从不是创建控件的线程去访问控件。百度里面搜索,会有各种各样的解决方案。在诸多方案中,我认为没有一个方案是特别简单,...

    C# WinForm 跨线程访问控件
    在做WinFrom开发的时候,经常会遇到跨线程访问控件的问题,即从不是创建控件的线程去访问控件。百度里面搜索,会有各种各样的解决方案。在诸多方案中,我认为没有一个方案是特别简单,而且代码量少的。以前我也傻傻的创建委托(当然也尝试过其它方法),当数量多了以后,发现代码不整洁。今天,我就跟大家分享一下我的方法(至少我没发现别人用这种方法),不正之处,欢迎批评指正。
    演示代码

            /// <summary>
            /// 摘要:
            ///    修改Button控件文字(仅供演示)
            /// </summary>
            private void ChangeBtnText(string Text)
            {
                Action DoAction = delegate()
                {
                    Btn.Text = Text;
                };
    
                if (this.InvokeRequired)
                {
                    ControlExtensions.UIThreadInvoke(this, delegate
                    {
                        DoAction();
                    });
                }
                else
                {
                    DoAction();
                }
            }
    

    辅助静态类代码

        /// <summary>
        /// 摘要:
        ///    跨线程访问UI通用组件,在此类外的Try Catch语句不能捕获委托中的错误。
        /// </summary>
        static class ControlExtensions
        {
            /// <summary>
            /// 同步执行 注:外层Try Catch语句不能捕获Code委托中的错误
            /// </summary>
            static public void UIThreadInvoke(this Control control, Action Code)
            {
                try
                {
                    if (control.InvokeRequired)
                    {
                        control.Invoke(Code);
                        return;
                    }
                    Code.Invoke();
                }
                catch 
                {
                    /*仅捕获、不处理!*/
                }
            }
    
            /// <summary>
            /// 异步执行 注:外层Try Catch语句不能捕获Code委托中的错误
            /// </summary>
            static public void UIThreadBeginInvoke(this Control control, Action Code)
            {
                if (control.InvokeRequired)
                {
                    control.BeginInvoke(Code);
                    return;
                }
                Code.Invoke();
            }
        }
    

    我认为这种写法是一劳永逸的,在解决问题的同时,代码量是最少的而且逻辑清楚代码整洁。关键是用这个方法去重构或者优化以前的代码是最省事的。你只需要将你以前的代码全部放到Action委托中,按这种模式套就可以了。

    主要研发方向:Tcp,TcpNat,服务前端,反向连接,大规范并发
    QQ交流群:697622527 欢迎交流

    展开全文
  • 同时我们又需要在工作线程中更新UI界面上的控件,如果直接在线程中更新控件,就会出现错误 一. 问题描述 线程间操作无效 界面上有一个button和一个label,点击button会启动一个线程来更新Label的值 private ...
  • 很多winform开发的新人,在子线程(非UI线程,线程id不为1)要更新控件的text属性时经常会遇到不允许跨线程访问控件的异常:Cross-thread operation not valid. Control “” accessed from a thread other than the...
  • C#异步委托,跨线程访问控件

    千次阅读 2018-11-24 21:24:45
    C#异步委托,跨线程访问控件欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、...
  • C#中跨线程访问控件处理方式

    千次阅读 2018-06-19 14:40:23
    C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件A(由线程Tread A创建)的InvokeRequired属性值为真时,说明有一个创建它以外的线程(Tread B)想访问它。此时,若Tread B线程...
  • C#中跨线程访问控件问题解决方案

    万次阅读 2012-07-26 15:34:21
    net 原则上禁止跨线程访问控件,因为这样可能造成错误的发生,推荐的解决方法是采用代理,用代理方法来间接操作不是同一线程创建的控件。 第二种方法是禁止编译器对跨线程访问作检查,可以实现访问,但是出不出错不...
  • 没有系统地学过C#,所以一直以为C#只是拖拖拽拽,后来发现在程序实现过程中需要注意好多,标题说类的做法我不是很赞同,一种好的方法应该是把其他类的方法封装起来...就像前几天写了一个基于TCP的C/S自由收发消息的
  • C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。此时它将会在内部调用newMethodInvoker(LoadGlobalImage)...
  • C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。 Windows窗体中的控件被绑定到特定的线程,不具备线程...
  • 初试C#多线程_跨线程访问控件

    千次阅读 2015-03-17 20:08:44
    C#里创建线程的方式是 Thread t = new Thread(new ThreadStart(this.DoSomething));  t.Start(); 里面的DoSomething是主线程里的一个函数,在里面做自己需要的操作。 那我就建了一个winForm程序来试一下,画了一...
  • 之前写winform用多线程,在子线程访问窗体控件需要用委托,由于委托使用不熟练,在网上找的各种方法都觉得太复杂看不懂,后来发现一种写法如下:if (pictureBox1.InvokeRequired) { pictureBox1.BeginInvoke(new ...
  • 博主刚接触线程没多久,需要实现将线程中计算出的数据传递给winform的label显示出来,但是C#中禁止跨线程直接访问控件,首先想到方法一(用委托实现) : 具体拿一个案例(内部计算已省略)说明:需要实现,按button ...
  • 看CefSharp的时候好像发现一个很好用用来跨线程访问控件的方法。在这个异步编程的时代,应该还是很好用的。 代码: /// <summary> /// Executes the Action asynchronously on the UI thread, does not...
  • C#中默认是要线程安全的,即在访问控件时需要首先判断是否跨线程,如果是跨线程的直接访问,在运行时会抛出异常。 解决办法有两个: 1、不进行线程安全的检查 2、通过委托的方式,在控件的线程上执行 ...
  • 在做跨线程访问之前我们先了解下我们所做的需要达到的效果: 这个是批量的将xml文件导入sqlite数据库 每个xml有将近3000的节点 每个节点有5个属性,如果我们不用线程那么在数据导入的过程中 程序很可能卡死 而且...
  • Winform跨线程操作控件

    2019-09-19 14:24:09
    为了区别是否是创建该控件线程访问控件,Windows窗体控件中的每个控件都有一个InvokeRequired属性,这个属性就是用来检查本控件是否被其他线程调用的属性,当被创建该线程外的线程调用的时候InvokeRequired就为true...
  • 跨线程调用控件的四种方式.rar
  • C# 跨线程调用控件

    2016-06-01 12:07:11
    C# 跨线程调用控件 ... ...在C# 的应用程序开发中, 我们经常要把UI线程和工作线程分开,防止界面停止响应。 同时我们又需要在工作线程...线程间操作无效第一种办法:禁止编译器对跨线程访问做检查第二种办法: 使用del
  • C#非控件创建线程调用控件的四种使用方式,从而避免跨线程调用的异常
  • 我有一个场景。 (Windows窗体,C#、. NET) 有一个主窗体可以承载一些用户控件。 用户控件执行一些繁重的数据操作,因此,如果我直接调用UserControl_Loa
  • 第一种就是直接在在控件加载界面加载之前添加 Control.CheckForIllegalCrossThreadCalls = false;含义是设置一个值指示是否 捕获对错误线程的调用; public Form1() { InitializeComponent(); Control....
  • C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它。此时它将会在内部调用newMethodInvoker(LoadGlobalImage)...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,454
精华内容 5,781
关键字:

c跨线程访问控件