如何提高程序的可交互性

来源:互联网 发布:淘宝网美容泛石刮沙板 编辑:程序博客网 时间:2024/04/30 16:36

一直以来都有一个疑问,既然只有一个UI线程,且非UI线程也不应该更新UI控件的状态,那我能不能做这么一个程序,就是画面上有一个文本区域,程序不停的往这个文本区域中写特定的内容,同时还能提供一个暂停的按钮,在按下暂停按钮时,程序可以被暂停。

 

其实不用考虑,这样的程序肯定可以做出来。看看你的杀毒软件就知道了。杀毒软件在扫描时,会不停的在画面上显示当前正在扫描的文件,同时还可以暂停扫描。而且,交互性很好。按下暂停按钮后就可以马上暂停住程序。

 

自己做了个小实验,一个画面上添加了两个按钮(Start,Stop)和一个文本框。按下Start时,不停的往文本框中写内容。

Start_Click()

{

    while(!stop)

    {

         txbData.AppendText(DateTime.Now.ToString() + "/n");

     }

}

 

Stop_Click()

{

    stop = true;

}

 

结果发现,按下Stop按钮时,程序根本就没有反应。

 

后来考虑到用多线程来做。在非UI线程中使用Invoke来调用UI线程更新文本框内容。发现还是停不下来。

 

后来到网上搜搜,终于有了点眉目(网址)。根本的玄机就在于,别把UI线程逼的太狠了。不要在循环体中不停的调用Invoke来改变控件状态,而是每次改变一点状态之后,调用一下sleep(),让当前线程让出CPU,交给UI去更新画面。

而且,也不用Invoke了,用BeginInvoke,异步操作。

 

下面是自己仿造上面网址的例子做了一个小例子,发现效果还不错:

 

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;

namespace ImproveInteractive
{
    public partial class Form1 : Form
    {
        private Thread t = null;
        private string tempString = string.Empty;
        private Stack<string> folders = new Stack<string>();

        public Form1()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (btnStart.Text == "Start")
            {
                if (!Directory.Exists(this.txbFilePath.Text))
                {
                    MessageBox.Show("Please set a valid *Directory*");
                    return;
                }

                btnStart.Text = "Pause";
                btnStop.Enabled = true;
                txbData.Clear();
                folders.Push(this.txbFilePath.Text);
                AbortThread(t);
                t = new Thread(new ThreadStart(LoopShow));
                t.Start();
            }
            else if (btnStart.Text == "Pause")
            {
                btnStart.Text = "Continue";
                    t.Suspend();
            }
            else if (btnStart.Text == "Continue")
            {
                btnStart.Text = "Pause";
                    t.Resume();
            }

        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            AbortThread(t);
            btnStop.Enabled = false;
            btnStart.Text = "Start";
        }

        private void AbortThread(Thread t)
        {
            if (t != null && t.IsAlive)
            {
                if (t.ThreadState == ThreadState.Suspended)
                {
                    t.Resume();
                }
                t.Abort();
            }
        }

        private void LoopShow()
        {
            while (folders.Count > 0)
            {
                string folder = folders.Pop();
                if (folder.Contains("System Volume Information") || folder.Contains("RRbackups"))
                {
                    continue;
                }
                foreach (string subFolder in Directory.GetDirectories(folder))
                {
                    folders.Push(subFolder);
                }

                foreach (string file in Directory.GetFiles(folder))
                {
                    tempString = file;
                    if (!this.IsDisposed)
                    {
                        this.BeginInvoke(new MethodInvoker(SetText));
                        Thread.Sleep(10);
                    }
                }
            }
        }

        private void SetText()
        {
            this.txbData.AppendText(tempString + "/n");
        }

        private void btnFolderChoose_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog folderBroser = new FolderBrowserDialog();
            DialogResult result = folderBroser.ShowDialog();
            if (result == DialogResult.OK || result == DialogResult.Yes)
            {
                this.txbFilePath.Text = folderBroser.SelectedPath;
            }
            folderBroser.Dispose();
        }

    }
}

需要注意的地方,大概有三点:

  •  使用异步操作更新控件后,Sleep一小段时间。10ms就够了。主要是这会让进程重新调度下,让UT线程得到执行。this.BeginInvoke(new MethodInvoker(SetText));     Thread.Sleep(10);
  •  当点了关闭按钮,调用this.BeginInvoke时,非UI线程可能会抱错。所以要加个判断if (!this.IsDisposed)
  • 文中使用的suspend,resume 已经不丢弃了,因为suspend可能会引起线程死锁。有个好同志给了一个替代方案,以后可以参考下(网址)