博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
推荐一个winform 界面交互类库
阅读量:7211 次
发布时间:2019-06-29

本文共 17479 字,大约阅读时间需要 58 分钟。

这个类库是在codeproject上发现的,主要是用于在.net 2.0,3.5的框架下,不可以在非创建线程下雨控件进行交互的限制,类库封装得比较好,使用了匿名方法。

 

ContractedBlock.gif
ExpandedBlockStart.gif
Code
// Copyright (c) 2008 CodeToast.com and Nicholas Brookins
//This code is free to use in any application for any use if this notice is left intact.
//Just don't sue me if it gets you fired.  Enjoy!
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Windows.Forms;
ExpandedBlockStart.gifContractedBlock.gif
namespace CodeToast {
ExpandedSubBlockStart.gifContractedSubBlock.gif    
public static class Async {
        
static Dictionary<stringobject> methodLocks = new Dictionary<stringobject>();
ContractedSubBlock.gifExpandedSubBlockStart.gif        
Async 'Do' overloads, for ease of use#region Async 'Do' overloads, for ease of use
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate asyncronously, using the threadpool or a full managed thread if needed. 
        
/// This overload always tries the ThreadPool and DOES NOT check for reentrance.
        
/// </summary>
        
/// <param name="d">A delegate with a return value of some sort - can be cast to (DlgR) from an anonymous delgate with a return: Async.Do((DlgR)MyMethod);</param>
        
/// <param name="getRetVal">If true, and the method/delgete returns something, it is included in the AsyncRes returned (after the method completes)</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes Do(DlgR d, bool getRetVal) {
            
return Do(d, getRetVal, ReenteranceMode.Allow);
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate asyncronously, using the threadpool or a full managed thread if needed. 
        
/// This overload always tries the ThreadPool and DOES NOT check for reentrance.
        
/// </summary>
        
/// <param name="d">A void delegate - can be cast to (Dlg) from an anonymous delgate or method:  Async.Do((Dlg)MyVoidMethod)</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes Do(Dlg d) {
            
return Do(d, ReenteranceMode.Allow);
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate asyncronously, using the threadpool or a full managed thread if needed.
        
/// </summary>
        
/// <param name="d">A delegate with a return value of some sort - can be cast to (DlgR) from an anonymous delgate with a return: Async.Do((DlgR)MyMethod);</param>
        
/// <param name="rMode">If true, will make sure no other instances are running your method.</param>
        
/// <param name="getRetVal">If true, and the method/delgete returns something, it is included in the AsyncRes returned (after the method completes)</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, resturn and result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes Do(DlgR d, bool getRetVal, ReenteranceMode rMode) {
            
return Do(d, null, getRetVal, nulltrue, rMode, nulltrue);
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate asyncronously, using the threadpool or a full managed thread if needed.
        
/// </summary>
        
/// <param name="d">A void delegate - can be cast to (Dlg) from an anonymous delgate or method:  Async.Do((Dlg)MyVoidMethod);</param>
        
/// <param name="rMode">If true, will make sure no other instances are running your method.</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes Do(Dlg d, ReenteranceMode rMode) {
            
return Do(null, d, falsenulltrue, rMode, nulltrue);
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate asyncronously, using the threadpool or a full managed thread if needed.
        
/// </summary>
        
/// <param name="d">A delegate with a return value of some sort - can be cast to (DlgR) from an anonymous delgate with a return: Async.Do((DlgR)MyMethod);</param>
        
/// <param name="state">A user object that can be tracked through the returned result</param>
        
/// <param name="tryThreadPool">True to use the TP, otherwise just go to a ful lthread - good for long running tasks.</param>
        
/// <param name="rMode">If true, will make sure no other instances are running your method.</param>
        
/// <param name="getRetVal">If true, and the method/delgete returns something, it is included in the AsyncRes returned (after the method completes)</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, resturn and result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes Do(DlgR d, bool getRetVal, object state, bool tryThreadPool, ReenteranceMode rMode) {
            
return Do(d, null, getRetVal, state, tryThreadPool, rMode, nulltrue);
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate asyncronously, using the threadpool or a full managed thread if needed.
        
/// </summary>
        
/// <param name="d">A void delegate - can be cast to (Dlg) from an anonymous delgate or method:  Async.Do((Dlg)MyVoidMethod);</param>
        
/// <param name="state">A user object that can be tracked through the returned result</param>
        
/// <param name="tryThreadPool">True to use the TP, otherwise just go to a ful lthread - good for long running tasks.</param>
        
/// <param name="rMode">If true, will make sure no other instances are running your method.</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes Do(Dlg d, object state, bool tryThreadPool, ReenteranceMode rMode) {
            
return Do(null, d, false, state, tryThreadPool, rMode, nulltrue);
        }
        
#endregion
ContractedSubBlock.gifExpandedSubBlockStart.gif        
The Big Main private 'Do' method - called by all overloads.#region The Big Main private 'Do' method - called by all overloads.
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate asyncronously, using the threadpool or a full managed thread if needed.
        
/// </summary>
        
/// <param name="d">A void delegate - can be cast to (Dlg) from an anonymous delgate.</param>
        
/// <param name="dr">A delegate with a return value of some sort - can be cast to (DlgR) from an anonymous delgate with a return.</param>
        
/// <param name="state">A user object that can be tracked through the returned result</param>
        
/// <param name="getRetVal">If true, and the method/delgete returns something, it is included in the AsyncRes returned (after the method completes)</param>
        
/// <param name="tryThreadPool">True to use the TP, otherwise just go to a ful lthread - good for long running tasks.</param>
        
/// <param name="rMode">If true, will make sure no other instances are running your method.</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        private static AsyncRes Do(DlgR dr, Dlg d, bool getRetVal, object state, bool tryThreadPool, ReenteranceMode rMode, Control control, bool async) {
            
//get a generic MethodInfo for checks..
            MethodInfo mi = ((dr != null? dr.Method : d.Method);
            
//make a unique key for output usage
            string key = string.Format("{0}{1}{2}{3}", ((getRetVal) ? "<-" : ""), mi.DeclaringType, ((mi.IsStatic) ? ":" : "."), mi.Name);
            
//our custom return value, holds our delegate, state, key, etc.
            AsyncRes res = new AsyncRes(state, ((dr != null? (Delegate)dr : (Delegate)d), key, rMode);
            
//Create a delegate wrapper for what we will actually invoke..
ExpandedSubBlockStart.gifContractedSubBlock.gif
            Dlg dlg = (Dlg)delegate {
                
if (!BeforeInvoke(res)) return//checks for reentrance issues and sets us up
ExpandedSubBlockStart.gifContractedSubBlock.gif
                try {
                    
if (res.IsCompleted) return;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (dr != null{
                        res.retVal 
= dr();//use this one if theres a return
ExpandedSubBlockStart.gifContractedSubBlock.gif
                    }
 else {
                        d();
//otherwise the simpler dlg
                    }
ExpandedSubBlockStart.gifContractedSubBlock.gif                }
 catch (Exception ex) //we never want a rogue exception on a random thread, it can't bubble up anywhere
                    Console.WriteLine("Async Exception:" + ex);
ExpandedSubBlockStart.gifContractedSubBlock.gif                }
 finally {
                    FinishInvoke(res);
//this will fire our callback if they used it, and clean up
                }
            }
;
ExpandedSubBlockStart.gifContractedSubBlock.gif            
if (control != null{
                res.control 
= control;
                res.result 
= AsyncAction.ControlInvoked;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
if (!async) {
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (!control.InvokeRequired) {
                        res.completedSynchronously 
= true;
                        dlg();
ExpandedSubBlockStart.gifContractedSubBlock.gif                    }
 else {
                        control.Invoke(dlg);
                    }
ExpandedSubBlockStart.gifContractedSubBlock.gif                }
 else {
                    control.BeginInvoke(dlg);
                }
                
return res;
            }
 //don't catch these errors - if this fails, we shouldn't try a real thread or threadpool!
ExpandedSubBlockStart.gifContractedSubBlock.gif            
if (tryThreadPool) //we are going to use the .NET threadpool
ExpandedSubBlockStart.gifContractedSubBlock.gif
                try {
                    
//get some stats - much better than trying and silently failing or throwing an expensive exception
                    int minThreads, minIO, threads, ioThreads, totalThreads, totalIO;
                    ThreadPool.GetMinThreads(
out minThreads, out minIO);
                    ThreadPool.GetAvailableThreads(
out threads, out ioThreads);
                    ThreadPool.GetMaxThreads(
out totalThreads, out totalIO);
                    
//check for at least our thread plus one more in ThreadPool
ExpandedSubBlockStart.gifContractedSubBlock.gif
                    if (threads > minThreads) {
                        
//this is what actually fires this task off..
ExpandedSubBlockStart.gifContractedSubBlock.gif
                        bool result = ThreadPool.QueueUserWorkItem((WaitCallback)delegate { dlg(); });
ExpandedSubBlockStart.gifContractedSubBlock.gif                        
if (result) {
                            res.result 
= AsyncAction.ThreadPool;
                            
//this means success in queueing and running the item
                            return res;
ExpandedSubBlockStart.gifContractedSubBlock.gif                        }
 else {
                            
//according to docs, this "won't ever happen" - exception instead, but just for kicks.
                            Console.WriteLine( "Failed to queue in threadpool.""Method: " + key);
                        }
ExpandedSubBlockStart.gifContractedSubBlock.gif                    }
 else {
                        Console.WriteLine(String.Format(
"Insufficient idle threadpool threads: {0} of {1} - min {2}, Method: {3}", threads, totalThreads, minThreads, key));
                    }
ExpandedSubBlockStart.gifContractedSubBlock.gif                }
 catch (Exception ex) {
                    Console.WriteLine(
"Failed to queue in threadpool: " + ex.Message, "Method: " + key);
                }
            }
            
//if we got this far, then something up there failed, or they wanted a dedicated thread
ExpandedSubBlockStart.gifContractedSubBlock.gif
            Thread t = new Thread((ThreadStart)delegate { dlg(); });
            t.IsBackground 
= true//this or threadpriority are candidates for additional settings
            t.Name = "Async_" + key;
            res.result 
= AsyncAction.Thread;
            t.Start();
            
return res;
        }
        
#endregion
ContractedSubBlock.gifExpandedSubBlockStart.gif        
Before and after - helper methods#region Before and after - helper methods
ExpandedSubBlockStart.gifContractedSubBlock.gif        
private static bool BeforeInvoke(AsyncRes res) {
            
//if marked as completed then we abort.
            if (res.IsCompleted) return false;
            
//if mode is 'allow' there is nothing to check.  Otherwise
ExpandedSubBlockStart.gifContractedSubBlock.gif
            if (res.RMode != ReenteranceMode.Allow) {
                
//be threadsafe with our one and only member field
ExpandedSubBlockStart.gifContractedSubBlock.gif
                lock (methodLocks) {
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (!methodLocks.ContainsKey(res.Method)) {
                        
//make sure we have a generic locking object in the collection, it will already be there if we are reentering
                        methodLocks.Add(res.Method, new object());
                    }
                    
//if bypass mode and we can't get or lock, we dump out.
ExpandedSubBlockStart.gifContractedSubBlock.gif
                    if (res.RMode == ReenteranceMode.Bypass) {
ExpandedSubBlockStart.gifContractedSubBlock.gif                        
if (!Monitor.TryEnter(methodLocks[res.Method])) {
                            res.result 
= AsyncAction.Reenterant;
                            
return false;
                        }
ExpandedSubBlockStart.gifContractedSubBlock.gif                    }
 else {
                        
//Otherwise in 'stack' mode, we just wait until someone else releases it
                        Monitor.Enter(methodLocks[res.Method]);
                    }
                    
//if we are here, all is good.  
                    
//Set some properties on the result class to show when we started, and what thread we are on
                    res.isStarted = true;
                    res.startTime 
= DateTime.Now;
                    res.thread 
= Thread.CurrentThread;
                }
            }
            
return true;
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
private static void FinishInvoke(AsyncRes res) {
            
if (res == nullreturn;
ExpandedSubBlockStart.gifContractedSubBlock.gif            
try {
                
//finish a few more properties
                res.isCompleted = true;
                res.completeTime 
= DateTime.Now;
                
//set the resetevent, in case someone is using the waithandle to know when we have completed.
                res.mre.Set();
ExpandedSubBlockStart.gifContractedSubBlock.gif            }
 catch (Exception ex) {
                Console.WriteLine(
"Error setting wait handle on " + (res.Method ?? "NULL"+ ex);
            }
ExpandedSubBlockStart.gifContractedSubBlock.gif            
if (res.RMode != ReenteranceMode.Allow) {
                
//if mode is bypass or stack, then we must have a lock that needs releasing
ExpandedSubBlockStart.gifContractedSubBlock.gif
                try {
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
if (methodLocks.ContainsKey(res.Method)) {
                        Monitor.Exit(methodLocks[res.Method]);
                    }
ExpandedSubBlockStart.gifContractedSubBlock.gif                }
 catch (Exception ex) {
                    Console.WriteLine(
"Error releasing reentrant lock on " + (res.Method ?? "NULL")+ ex);
                }
            }
        }
        
#endregion
ContractedSubBlock.gifExpandedSubBlockStart.gif        
UI Overloads#region UI Overloads
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate, carefully using the correct UI thread
        
/// </summary>
        
/// <param name="d">A void delegate - can be cast to (Dlg) from an anonymous delgate or method:  Async.Do((Dlg)MyVoidMethod);</param>
        
/// <param name="async">Whether to run async, or try on current thread if invoke is not required.</param>
        
/// <param name="c">A control to Invoke upon GUI thread of, if needed. Null if unused.</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes UI(Dlg d, Control c, bool async) {
            
return Do(null, d, falsenullfalse, ReenteranceMode.Allow, c, async);
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate, carefully using the correct UI thread
        
/// </summary>
        
/// <param name="d">A delegate with a return value of some sort - can be cast to (DlgR) from an anonymous delgate with a return: Async.Do((DlgR)MyMethod);</param>
        
/// <param name="async">Whether to run async, or try on current thread if invoke is not required.</param>
        
/// <param name="getRetVal">If true, and the method/delgete returns something, it is included in the AsyncRes returned (after the method completes)</param>
        
/// <param name="c">A control to Invoke upon GUI thread of, if needed. Null if unused.</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes UI(DlgR d, bool getRetVal, Control c, bool async) {
            
return Do(d, null, getRetVal, nullfalse, ReenteranceMode.Allow, c, async);
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Fires off your delegate, carefully using the correct UI thread
        
/// </summary>
        
/// <param name="d">A delegate with a return value of some sort - can be cast to (DlgR) from an anonymous delgate with a return: Async.Do((DlgR)MyMethod);</param>
        
/// <param name="state">A user object that can be tracked through the returned result</param>
        
/// <param name="async">Whether to run async, or try on current thread if invoke is not required.</param>
        
/// <param name="getRetVal">If true, and the method/delgete returns something, it is included in the AsyncRes returned (after the method completes)</param>
        
/// <param name="rMode">If true, will make sure no other instances are running your method.</param>
        
/// <param name="c">A control to Invoke upon GUI thread of, if needed. Null if unused.</param>
        
/// <returns>AsyncRes with all kind o' goodies for waiting, result values, etc.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public static AsyncRes UI(DlgR d, bool getRetVal, Control c, object state, bool async, ReenteranceMode rMode) {
            
return Do(d, null, getRetVal, state, false, rMode, c, async);
        }
        
#endregion
    }
ContractedSubBlock.gifExpandedSubBlockStart.gif    
AsyncRes class#region AsyncRes class
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//// <summary>
    
/// Used with the Async helper class, This class is mostly a holder for a lot of tracking fields and properties, with a few things mandated by the IAsyncResult interface.
    
/// </summary>
ExpandedSubBlockStart.gifContractedSubBlock.gif    public class AsyncRes : IAsyncResult {
ExpandedSubBlockStart.gifContractedSubBlock.gif        
internal AsyncRes(object state, Delegate d, string key, ReenteranceMode rMode) {
            
this.state = state;
            
this.asyncDelegate = d;
            
this.key = key;
            
this.RMode = rMode;
        }
        
internal ReenteranceMode RMode = ReenteranceMode.Allow;
        
internal Thread thread = null;
        
private string key = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string Method get return key; } }
        
private Delegate asyncDelegate = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public Delegate AsyncDelegate get return asyncDelegate; } }
        
internal AsyncAction result = AsyncAction.Unknown;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public AsyncAction Result get return result; } }
        
internal Control control = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public Control Control get return control; } }
        
internal DateTime createTime = DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public DateTime TimeCreated get return createTime; } }
        
internal DateTime completeTime = DateTime.MinValue;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public DateTime TimeCompleted get return completeTime; } }
        
internal DateTime startTime = DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public DateTime TimeStarted get return startTime; } }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public TimeSpan TimeElapsed {
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get return ((completeTime > DateTime.MinValue) ? completeTime : DateTime.Now) - createTime; }
        }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public TimeSpan TimeRunning {
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get {
return (startTime == DateTime.MinValue) ? TimeSpan.Zero : ((completeTime > DateTime.MinValue) ? completeTime : DateTime.Now) - startTime;}
        }
        
internal object retVal = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public object ReturnValue get return retVal; } }
        
internal bool isStarted = false;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public bool IsStarted get return isStarted; } }
        
private object state = null;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public object AsyncState get return state; } }
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// Aborts a running associated thread.  If possible it will cancel if not yet started
        
/// </summary>
        
/// <returns>True if the thread could be cancelled before it started.</returns>
ExpandedSubBlockStart.gifContractedSubBlock.gif        public bool CancelOrAbort() {
            isCompleted 
= true;
            
if (!isStarted) return true;//cancelled
ExpandedSubBlockStart.gifContractedSubBlock.gif            
if (thread != null && thread.IsAlive) {
                thread.Abort();
            }
            
return false;
        }
        
internal ManualResetEvent mre = new ManualResetEvent(false);
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public WaitHandle AsyncWaitHandle get return mre; } }
        
internal bool completedSynchronously = false;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public bool CompletedSynchronously get return completedSynchronously; } }
        
internal bool isCompleted = false;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public bool IsCompleted get return isCompleted; } }
    }
    
#endregion
ContractedSubBlock.gifExpandedSubBlockStart.gif    
Definitions of enums and delegates#region Definitions of enums and delegates
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//// <summary>
    
/// Abreviated Empty Delegate for use in anonymous casting
    
/// </summary>
    public delegate void Dlg();
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//// <summary>
    
/// Abreviated Empty Delegate for use in anonymous methods when a return is needed
    
/// </summary>
    
/// <returns>Umm, anything you want.</returns>
    public delegate object DlgR();
ExpandedSubBlockStart.gifContractedSubBlock.gif    
public enum AsyncAction {
        Unknown 
= 0,
        ThreadPool 
= 1,
        Thread 
= 2,
        Failed 
= 4,
        Reenterant 
= 8,
        ControlInvoked 
= 16
    }
ExpandedSubBlockStart.gifContractedSubBlock.gif    
public enum ReenteranceMode {
        Allow 
= 1,
        Bypass 
= 2,
        Stack 
= 4,
    }
    
#endregion
}

异步获取控件值:

ContractedBlock.gif
ExpandedBlockStart.gif
Code
AsyncRes result = Async.UI(
    
//make sure the delegate/method returns a value:
ExpandedBlockStart.gifContractedBlock.gif
    delegate return textBox1.Text; },
    
true//yes, we want to get the return value
    myForm, //the control to invoke on
    null//the state object, we don't need to track anything.
    true//invoke asynchronously?
    ReenteranceMode.Allow); //don't worry about thread safety in this case.
// . do other things  //
// now make sure the task above has completed.. 
result.AsyncWaitHandle.WaitOne();
//and use the value
Console.WriteLine("The textbox says: " + result.ReturnValue);

 

同步设置控件值: 

ContractedBlock.gif
ExpandedBlockStart.gif
Code
Async.UI(delegate { textBox1.Text = "This is way easier!"; }, textBox1, true);

 

 

转载于:https://www.cnblogs.com/perfectdesign/archive/2009/03/02/1401569.html

你可能感兴趣的文章
互联网上的时光机器
查看>>
UML类图学习
查看>>
成为顶尖机器学习算法专家需要知道哪些算法?
查看>>
Lombok简介
查看>>
几款开源图像处理软件评测研究
查看>>
Fundebug是这样备份数据的
查看>>
HT for Web的HTML5树组件延迟加载技术实现
查看>>
apolloxlua include关键字
查看>>
Flutter教程app
查看>>
使用Hexo搭建专属Blog
查看>>
Swoole 2019 :化繁为简、破茧成蝶
查看>>
Android RTL 及小语种 适配
查看>>
走近webpack(1)--多入口及devServer的使用
查看>>
jquery-weui滚动加载问题解决
查看>>
SpringBoot整合Shiro使用Ehcache等缓存无效问题
查看>>
“产学合作勇创新·协同育人书新篇”贵州理工大数据学院数据科学训练营结题答辩报告会圆满举行...
查看>>
EDEN-MACE 1.4.0 更新,增加数据清理功能
查看>>
ASP.Net中实现上传过程中将文本文件转换成PDF的方法
查看>>
营收放缓、股价暴跌、高管离职,Facebook迎来至暗时刻?
查看>>
MySQL探秘(二):SQL语句执行过程详解
查看>>