前言
当客户端需要进行网络下载操作时如果只是简单的用多线程这么一个操作而不给用户知道当前的下载进度的话那么用户将不知道已经下载了多少,甚至有可能直接关闭了主应用程序。那就杯具了。
那么如何在另外的线程中来更新UI?
讨论
WPF却明确的规定:UI元素只能由其主线程来操作,其他任何线程都不可以直接操作UI。
WPF中的UI控件,如果我们探究本质,他们都是从DispatcherObject继承,所以都必须由UI线程进行调度和使用,如果我们在其他的后台线程中操作界面相关的元素时,就会出现如下的异常信息:
调用线程无法访问此对象,因为另一个线程拥有该对象。
这时候就是Dispatcher.Invoke方法上场的时间了。。。
解决
在 WPF 中,只有创建 DispatcherObject 的线程才能访问该对象。 例如,一个从主 UI 线程派生的后台线程不能更新在该 UI 线程上创建的 Button 的内容。 为了使该后台线程能够访问 Button 的 Content 属性,该后台线程必须将此工作委托给与该 UI 线程关联的 Dispatcher。
使用 Invoke 或 BeginInvoke 来完成此操作。 Invoke 是同步操作,而 BeginInvoke 是异步操作。 该操作将按指定的 DispatcherPriority 添加到 Dispatcher 的事件队列中。
代码
private delegate void SetTipsValue_dg(long solength, long stlength);
private void SetTipsValue(long solength, long stlength)
{
block_Tips.Text = "下载中...." + solength + "/" + stlength;
}
private bool DownloadFile(string URL, string filename)
{
try
{
System.Net.HttpWebRequest Myrq = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(URL);
System.Net.HttpWebResponse myrp = (System.Net.HttpWebResponse)Myrq.GetResponse();
System.IO.Stream st = myrp.GetResponseStream();
long stll = myrp.ContentLength;
System.IO.Stream so = new System.IO.FileStream(filename, System.IO.FileMode.Create);
byte[] by = new byte[1024];
int osize = st.Read(by, 0, (int)by.Length);
while (osize > 0)
{
long sol = so.Length;
long stl = stll;
this.block_Tips.Dispatcher.Invoke(new SetTipsValue_dg(SetTipsValue), sol, stl);
so.Write(by, 0, osize);
osize = st.Read(by, 0, (int)by.Length);
}
so.Close();
st.Close();
myrp.Close();
Myrq.Abort();
return true;
}
catch (System.Exception e)
{
return false;
}
}
上面代码首先定义了一个委托:该委托接受两个参数分别代表当前下载量和总下载量,然后定义了一个具体的实现该委托的方法,该方法调用UI来显示数据。
在下载数据的主函数DownloadFile中调用了this.block_Tips.Dispatcher.Invoke方法并将实现了委托的方法SetTipsValue方法和当前下载量及总下载量的数值传进去我们就完成了整个操作。这样我们在下载数据的时候用另外的线程开启了DownloadFile方法就可以实时的显当前的下载进度了。