首页 > 技术文章 > 实现winfrom进度条及进度信息提示,winfrom程序假死处理

zhaoshujie 2018-09-08 22:50 原文

方法一:使用线程

功能描述:在用c#做WinFrom开发的过程中。我们经常需要用到进度条(ProgressBar)用于显示进度信息。这时候我们可能就需要用到多线程,如果不采用多线程控制进度条,窗口很容易假死。下面简单结合例子给大家做个介绍。

第一步:设计界面,注意需要引用 using System.Threading;

控件名称分别为:progressBar1;label1;textBox1;button1;

第二步:定义一个代理,用于更新ProgressBar的值Value及在执行方法的时候,返回方法的处理信息。

private delegate void SetPos(int ipos,string vinfo);//代理

第三步:进度条值更新函数(参数必须跟声明的代理参数一样

private void SetTextMesssage(int ipos,string vinfo)
{
    if (this.InvokeRequired)
    {
        SetPos setpos = new SetPos(SetTextMesssage);
        this.Invoke(setpos, new object[] { ipos,vinfo });
    }
    else
    {
        this.label1.Text = ipos.ToString() + "/1000";
        this.progressBar1.Value = Convert.ToInt32(ipos);
        this.textBox1.AppendText(vinfo);
    }
}

第四步:函数实现

private void button1_Click(object sender, EventArgs e)
{
    Thread fThread = new Thread(new ThreadStart(SleepT));
    fThread.Start();
}

第五步:新的线程执行函数:

private void SleepT()
{
    for (int i = 0; i < 500; i++)
    {
        System.Threading.Thread.Sleep(10);
        SetTextMesssage(100*i/500,i.ToString()+"\r\n");
    }
}

方法二:控件BackgroundWorker

功能描述:该种方法通过控件BackgroundWorker实现进度条,进度条在一个模板窗体内。

第一步:主窗体设计:

  控件名称:button1;backgroundWorker1;对backgroundWorker1控件,属性设置。

第二步:主页面后台代码:

using System.Threading;

private void button1_Click(object sender, EventArgs e)
{
    this.backgroundWorker1.RunWorkerAsync(); // 运行 backgroundWorker 组件
    ProcessForm form = new ProcessForm(this.backgroundWorker1);// 显示进度条窗体
    form.ShowDialog(this);
    form.Close();
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
        MessageBox.Show(e.Error.Message);
    }
    else if (e.Cancelled)
        { }
        else
        { }
}
//可以在这个方法内,实现调用,方法等。
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(100);
        worker.ReportProgress(i);
        if (worker.CancellationPending)  // 如果用户取消则跳出处理数据代码 
        {
            e.Cancel = true;
            break;
        }
    }
}

分别为button控件和backgroundWorker1控件选好事件。

第三步:设置子窗体(及显示进度条的窗体):

控件名称:progressBar1;button1

第四步:子窗体,后台代码:

private BackgroundWorker backgroundWorker1; //ProcessForm 窗体事件(进度条窗体)

public ProcessForm(BackgroundWorker backgroundWorker1)
{
    InitializeComponent();
    this.backgroundWorker1 = backgroundWorker1;
    this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
    this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
}

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //this.Close();//执行完之后,直接关闭页面
}

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
}

private void button1_Click(object sender, EventArgs e)
{
    this.backgroundWorker1.CancelAsync();
    this.button1.Enabled = false;
    this.Close();
}

方法三:方法二增强

功能描述:在处理大量数据的时候,有时候方法的执行需要一定的时间,这时候往往会造成页面或程序的“假死”状态,给用户的体验度也不是很好。为了避免出现“假死”提高用户的体验度,在这里为这类型的方法加了一个进度条和一个文本框,进度条用于显示程序处理的进度,文本框用于显示在处理过程中,给与的提示。本方法主要使用了控件:backgroundWorker1;说明:本方法与上面的方法(方法二)基本类型,主要是设计和代码进行了一些修改。

第一步:主窗体设计:

  控件名称:button1;backgroundWorker1;对backgroundWorker1控件,属性设置。

第二步:主窗体 后台代码:

private void button1_Click(object sender, EventArgs e)
{
    this.backgroundWorker1.RunWorkerAsync(); // 运行 backgroundWorker 组件
    ProcessForm form = new ProcessForm(this.backgroundWorker1);// 显示进度条窗体
    form.ShowDialog(this);
    form.Close();
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
        MessageBox.Show(e.Error.Message);
    }
    else if (e.Cancelled)
    { }
    else
    { }
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(100);
        worker.ReportProgress(i, i.ToString() + "  你好!\r\n"); //注意:这里向子窗体返回信息值,这里是两个值,一个用于进度条,一个用于文本框的。
        if (worker.CancellationPending)  // 如果用户取消则跳出处理数据代码 
        {
            e.Cancel = true;
            break;
        }
    }
}

第三步:设置子窗体(及显示进度条的窗体):

控件:progressBar1;textBox1;button1

第四步:子窗体后台代码:

private BackgroundWorker backgroundWorker1; //ProcessForm 窗体事件(进度条窗体)

public ProcessForm(BackgroundWorker backgroundWorker1)
{
    InitializeComponent();
    this.backgroundWorker1 = backgroundWorker1;
    this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
    this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
}

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //this.Close();//执行完之后,直接关闭页面
}

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
    this.textBox1.AppendText(e.UserState.ToString());//主窗体传过来的值,通过e.UserState.ToString()来接受
}

private void button1_Click(object sender, EventArgs e)
{
    this.backgroundWorker1.CancelAsync();
    this.button1.Enabled = false;
    this.Close();
}

注意:如果在程序为执行完,就点击取消的话,很有可能会报错的,这时候,你就修改一个方法:backgroundWorker1_ProgressChanged如下:

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.progressBar1.Value = e.ProgressPercentage;
    this.textBox1.Text += e.UserState.ToString(); //主窗体传过来的值,通过e.UserState.ToString()来接受
}

​ 把这个方法,替换上边的那个方法,就可以了。

方法四:线程与代理

功能描述:本方法实现进度条显示,方法执行信息反馈显示。用到的技术点为 线程与代理。

步骤一:添加主页面。

  控件名称:button1;

步骤二:主页面后台代码

using System.Threading;

//创建代理
private Form6 myProcessBar = null;//弹出的子窗体(用于显示进度条)
private delegate bool IncreaseHandle(int nValue,string vinfo);//代理创建
private IncreaseHandle myIncrease = null;//声明代理,用于后面的实例化代理
private int vMax = 100;//用于实例化进度条,可以根据自己的需要,自己改变

private void button1_Click(object sender, EventArgs e)
{
    Thread thdSub = new Thread(new ThreadStart(ThreadFun));
    thdSub.Start();
}
private void ThreadFun()
{
    MethodInvoker mi = new MethodInvoker(ShowProcessBar);
    this.BeginInvoke(mi);
    Thread.Sleep(100);
    object objReturn = null;
    for (int i = 0; i < vMax; i++)
    {
        objReturn = this.Invoke(this.myIncrease, new object[] { 2, i.ToString() + "\r\n" });
        Thread.Sleep(50);
    }
}
private void ShowProcessBar()
{
    myProcessBar = new Form6(vMax);
    myIncrease = new IncreaseHandle(myProcessBar.Increase);
    myProcessBar.ShowDialog();
    myProcessBar = null;
}

步骤三:创建子窗体

  控件名称:progressBar1;textBox1

步骤四:子窗体,后台代码

public Form6(int vMax)
{
    InitializeComponent();
    this.progressBar1.Maximum = vMax;
}
public bool Increase(int nValue,string nInfo)
{
    if (nValue > 0)
    {
        if (progressBar1.Value + nValue < progressBar1.Maximum)
        {
            progressBar1.Value += nValue;
            this.textBox1.AppendText(nInfo);
            Application.DoEvents();
            progressBar1.Update();
            progressBar1.Refresh();
            this.textBox1.Update();
            this.textBox1.Refresh();
            return true;
        }
        else
        {
            progressBar1.Value = progressBar1.Maximum;
            this.textBox1.AppendText(nInfo);
            //this.Close();//执行完之后,自动关闭子窗体
            return false;
        }
    }
    return false;
}

execute相信大家都用的用熟了,简写为exec,除了用来执行存储过程,一般都用来执行动态Sql
sp_executesqlsql2005中引入的新的系统存储过程,也是用来处理动态sql的,如:

exec sp_executesql @sql, N'@count int out,@id varchar(20)', @cou out ,@id 
  • @sql为拼成的动态sql
  • N'@count int out,@id varchar(20)'为拼成的动态sql内的参数列表
  • @cou out,@id为动态sql内参数列表提供值的外部参数列表

那么它们之间有什么区别呢?

1、它们之间最大的区别是嵌入式的参数,如下面一个语句

DECLARE @sql NVARCHAR(2000); 
DECLARE @id VARCHAR(20); 
SET @id = '1'; 
SET @sql = 'select count(*) from emp where id=' + @id; 
EXEC @sql; 

我想把得到的count(*)传出来,用传统的exec是不好办到的,但是用sp_executesql则很容易就办到了:

DECLARE @sql NVARCHAR(2000); 
DECLARE @cou INT; 
DECLARE @id VARCHAR(20); 
SET @id = '1'; 
SET @sql = 'select @count=count(*) from emp where id=@id'; 
EXEC sp_executesql @sql, N'@count int out,@id varchar(20)', @cou OUT, @id; 
PRINT @cou; 

2、性能
可以看到,如果用exec,由于每次传入的@id不一样,所以每次生成的@sql就不一样,这样每执行一次就必须重新将要执行的动态Sql重新编译一次 。但是sp_executesql则不一样,由于将数值参数化,要执行的动态Sql永远不会变化,只是传入的参数值在变化,那每次执行的时候就秒用重新编译,速度自然快多了哈!

注意:
1.sp_executesql要求动态Sql和动态Sql参数列表必须是Nvarchar,比如上个例子的@sql,N'@count int out,@id varchar(20)',必须为Nvarchar
2.动态Sql的参数列表与外部提供值的参数列表顺序必需一致,如: N'@count int out,@id varchar(20)', @cou out,@id@count 对应 @cou@id对应@id 。如果不一致,必须显式标明,如: N'@count int out,@id varchar(20)', @id=@id, @count=@cou out
3.动态SQl的参数列表与外部提供参数的参数列表参数名可以同名

推荐阅读