1>:
获取电脑文档目录
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
2>:
获取电脑临时存储目录
GetTempPath
3>:
获取集合只读存储
AsReadOnly
Array.AsReadOnly<T>
List<T>.AsReadOnly
若要防止对数组进行任何修改,应该仅通过此包装公开数组。
只读集合只是一个具有用于防止修改的包装的集合;因此,如果更改基础集合,则只读集合将反映那些更改。
4>:Abstract方法(抽象方法)
abstract关键字只能用在抽象类中修饰方法,并且没有具体的实现。抽象方法的实现必须在派生类中使用override关键字来实现。
5>:C#中virtual和abstract的区别
virtual和abstract都是用来修饰父类的,通过覆盖父类的定义,让子类重新定义。
它们有一个共同点:如果用来修饰方法,前面必须添加public,要不然就会出现编译错误:虚拟方法或抽象方法是不能私有的。毕竟加上virtual或abstract就是让子类重新定义的,而private成员是不能被子类访问的。
但是它们的区别很大。(virtual是“虚拟的”,abstract是“抽象的").
(1)virtual修饰的方法必须有实现(哪怕是仅仅添加一对大括号),而abstract修饰的方法一定不能实现。如对于virtual修饰的方法如果没有实现:
public class Test1
{
public virtual void fun1();
}
错误 2 “Test1.fun1()”必须声明主体,因为它未标记为 abstract、extern 或 partial
对于abstract修饰的方法如果有实现:
public abstract class Test2
{
public abstract void fun2() { }
}
错误 1 “Test2.fun2()”无法声明主体,因为它标记为 abstract
(2)virtual可以被子类重写,而abstract必须被子类重写,
class BaseTest1
{
public virtual void fun() { }//必须有实现
}
class DeriveTest1:BaseTest1
{
//public override void fun() { }
abstract class BaseTest2
{
public abstract void fun();
}
class DeriveTest2 : BaseTest2
{
//public override void fun();错误1:没有实现
//public void fun() { } 错误2:重写时没有添加override
//override void fun() { }错误3:虚拟成员或者抽象成员不能是私有的(只要在父类中声明了虚拟成员或抽象成员,即便是继承的也要加上这个限制)
public override void fun() { }//如果重写方法; 错误:“A.DeriveTest2”不实现继承的抽象成员“A.BaseTest2.fun()”
}
(3)如果类成员被abstract修饰,则该类前必须添加abstract,因为只有抽象类才可以有抽象方法。
(4)无法创建abstract类的实例,只能被继承无法实例化,比如: BaseTest2 base2 = new BaseTest2();将出现编译错误:抽象类或接口不能创建实例。
(5)C#中如果要在子类中重写方法,必须在父类方法前加virtual,在子类方法前添加override,这样就避免了程序员在子类中不小心重写了父类方法。
(6)abstract方法必须重写,virtual方法必须有实现(即便它是在abstract类中定义的方法).
abstract public class Test
{
//public virtual void Prinf();错误:virtual方法必须有实现
public virtual void Prinf() //abstract类的virtual方法可以不重写;abstract方法必须重写。
{
Console.WriteLine("Abstract Printf...");
}
}
public class Class1 : Test
{
/*
public override void Prinf() //派生类中不重写abstract类的virtual方法照样可以运行,不过调用派生类对象的Printf方法时,调用的是父类的。
{
Console.WriteLine("Class One Override Printf...");
}
*/
}
6>:List与IList的区别
在我看一个源程序的时候看到这个例子使用了IList<T>返回类型,因为上午刚刚总结过List<T>的详细用法,突然出现了IList<T>,感觉很奇怪,于是上网搜集了很多东西才明白了
它们的区别,刚开始仅仅是看文字说明,但是怎么都不明白,后来看到了一个实例,然后接着
看文字说明,豁然开朗啊,现在我先把这个实例程序写出来:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace List
{
public class Users //类Users 用户
{
public string Name; // 姓名
public int Age; // 年龄
public Users(string _Name, int _Age)
{
Name = _Name;
Age = _Age;
}
}
class Program
{
static void Main(string[] args)
{
Users U = new Users("jiang", 24);
IList<Users> UILists = new List<Users>();
//千万要注意:等式的右边是List<Users>,而不是 IList<Users>,
//如果在List前面加一个I, 就会出现错误:抽象类或接口无法创建实例。
UILists.Add(U);
U = new Users("wang", 22);
UILists.Add(U);
List<Users> I = ConvertIListToList<Users>(UILists);
Console.WriteLine(I[0].Name);
Console.WriteLine(I[1].Name);
Console.Read();
}
// ** <summary>
/// 转换IList<T>为List<T> //将IList接口泛型转为List泛型类型
/// </summary>
/// <typeparam name="T">指定的集合中泛型的类型</typeparam>
/// <param name="gbList">需要转换的IList</param>
/// <returns></returns>
public static List<T> ConvertIListToList<T>(IList<T> gbList) where T : class //静态方法,泛型转换,
{
if (gbList != null && gbList.Count >= 1)
{
List<T> list = new List<T>();
for (int i = 0; i < gbList.Count; i++) //将IList中的元素复制到List中
{
T temp = gbList[i] as T;
if (temp != null)
list.Add(temp);
}
return list;
}
return null;
}
}
}
注意:
IList<Users> UILists = new List<Users>(); //千万要注意:等式的右边是List<Users>,
而不是 IList<Users>,如果在List前面加一个I, 就会出现错误:抽象类或接口无法创建实例。
下面说一下IList与List的区别:
(1)首先IList 泛型接口是 ICollection 泛型接口的子代,并且是所有泛型列表的基接口。
它仅仅是所有泛型类型的接口,并没有太多方法可以方便实用,如果仅仅是作为集合数据的承载体,确实,IList<T>可以胜任。
不过,更多的时候,我们要对集合数据进行处理,从中筛选数据或者排序。这个时候IList<T>就爱莫能助了。
1、当你只想使用接口的方法时,ILis<>这种方式比较好.他不获取实现这个接口的类的其他方法和字段,有效的节省空间.
2、IList <>是个接口,定义了一些操作方法这些方法要你自己去实现
List <>是泛型类,它已经实现了IList <>定义的那些方法
IList <Class1> IList11 =new List <Class1>();
List <Class1> List11 =new List <Class1>();
这两行代码,从操作上来看,实际上都是创建了一个List<Class1>对象的实例,也就是说,他们的操作没有区别。
只是用于保存这个操作的返回值变量类型不一样而已。
那么,我们可以这么理解,这两行代码的目的不一样。
List <Class1> List11 =new List <Class1>();
是想创建一个List<Class1>,而且需要使用到List<T>的功能,进行相关操作。
而
IList <Class1> IList11 =new List <Class1>();
只是想创建一个基于接口IList<Class1>的对象的实例,只是这个接口是由List<T>实现的。所以它只是希望使用到IList<T>接口规定的功能而已。
7>:LinkedList
//链表的优点是:如果要插入一个元素到链表的中间位置,会非常的快,
//原因是,如果插入,只需要修改上一个元素的Next 与 下一个元素的Previous的引用则可。
//像ArrayList列表中,如果插入,需要移动其后的所有元素。
//链表的缺点是,链表只能是一个接着一个的访问,这样就要用较长的时间来查找定位位于链表中间的元素。
8>:internal
internal关键字是类型和类型成员的访问修饰符。只有在同一个程序集的文件中,内部类型或者是成员才可以访问”。
(一个DLL文件就是一个程序集) 封装在dll文件中 仅供内部使用 不被外面调用
9>:C#中Array和ArrayList的区别及泛型
1.Array累心的变量在声明的同时必须进行实例化(至少的初始化数组的大小),而ArrayList可以只先声明。
2.Array只能存储同构的对象,ArrayList可以存储异构变量。
Array是始终连续存放的,ArrayList不一定是
4.初始化大小
Array对象的初始化必须制定大小,且创建后的数组大小是固定的,而ArrayList的大小可以动态指定.
关于泛型:
ArrayList是一个非泛型集合类,添加到ArrayList中的任何引用或值类型都将隐式向上强制转换为Object.如果项是值类型,则必须在将其添加到列表中是进行装箱操作.
泛型是最常见的用途是创建集合类。.Net Framework类库在Collection.Generic命名空间中包含ijge泛型集合类。List<T>类是ArrayList类的泛型等效类。使用大小可以按需动态增加的苏族实现IList泛型接口.动态数组的好处是不必须事先设置较大的数组,减少不必要的内存开销.
10>:c#中数组,array,arraylist三者的区别是什么
c#数组就是array,array就是数组
数组定义时要写明类型,是字符串还是整形,arraylist就不用,他可以存放任何类型的变量
数组的大小在初始化时就已经决定无法改变,arraylist是可以增加和减少,使用更灵活,操作更简单。就像一楼说的,效率会低一些。如果能够确定要存放的类型和个数的话建议使用数组,否则使用arraylist。
11>:volatile
volatile 关键字表示字段可能被多个并发执行线程修改。声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。这样可以确保该字段在任何时间呈现的都是最新的值。
volatile多用于多线程的环境,当一个变量定义为volatile时,读取这个变量的值时候每次都是从momery里面读取而不是从cache读。这样做是为了保证读取该变量的信息都是最新的,而无论其他线程如何更新这个变量。
12>:lock 语句
lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。此语句的形式如下:
Object thisLock = new Object();
lock (thisLock)
{
// Critical code section
}
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
13>:C# const, readonly, static readonly
Const 定义的是静态常在对象初始化的时候赋值.以后不能改变它的值.属于编译时常量。不能用new初始化。
Readonly 是只读变量.属于运行时变量.可以在类constructor里改变它的值.不能作用于局部变量。
const 和 static 不能在一起用,它已经是静态的了。
我们都知道,const和static readonly的确非常像:通过类名而不是对象名进行访问,在程式中只读等等。在多数情况下能混用。
二者本质的差别在于,const的值是在编译期间确定的,因此只能在声明时通过常量表达式指定其值。
而static readonly,在程式中只读, 不过它是在运行时计算出其值的,所以还能通过静态构造函数来对它赋值,
readonly只能用来修饰类的field,不能修饰局部变量,也不能修饰property等其他类成员.
明白了这个本质差别,我们就不难看出下面的语句中static readonly和const能否互换了:
1. static readonly MyClass myins = new MyClass();
2. static readonly MyClass myins = null;
3. static readonly A = B * 20;
static readonly B = 10;
4. static readonly int [] constIntArray = new int[] {1, 2, 3};
5. void SomeFunction()
{
const int a = 10;
...
}
1:不能换成const。new操作符是需要执行构造函数的,所以无法在编译期间确定
2:能换成const。我们也看到,Reference类型的常量(除了String)只能是Null。
3:能换成const。我们能在编译期间非常明确的说,A等于200。
4:不能换成const。道理和1是相同的,虽然看起来1,2,3的数组的确就是个常量。
5:不能换成readonly,readonly只能用来修饰类的field,不能修饰局部变量,也不能修饰property等其他类成员。
因此,对于那些本质上应该是常量,不过却无法使用const来声明的地方,能使用static readonly。例如C#规范中给出的例子:
public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);
private byte red, green, blue;
public Color(byte r, byte g, byte b)
{
red = r;
green = g;
blue = b;
}
}
static readonly需要注意的一个问题是,对于一个static readonly的Reference类型,只是被限定不能进行赋值(写)操作而已。而对其成员的读写仍然是不受限制的。
public static readonly MyClass myins = new MyClass();
…
myins.SomeProperty = 10; //正常
myins = new MyClass(); //出错,该对象是只读的
不过,如果上例中的MyClass不是个class而是个struct,那么后面的两个语句就都会出错。
//ReadOnly关键字修饰的变量可以修改,只是不能重新分配
readonly 关键字是可以在字段上使用的修饰符。当字段声明包括 readonly 修饰符时,该声明引入的字段赋值只能作为声明的一部分出现,或者出现在同一类的构造函数中.
很多初学者看完书就会认为,readonly修饰的变量在以后是不能修改的,在以后的开发中从不对ReadOnly的变量进行修改操作,形成思维定势,这个观念是错误的。
首先要明确一点:更改!=重新分配(赋值)
对于简单类型(如int),更改是等于重新赋值,因为默认操作符只有=, 但于对于复杂类型,就不一定了。
例如:
对于class类型,修改其字段属性值。
对于集合类型,增加,移除,清空内容。
//readonly修饰的字段,其初始化仅是固定了其引用(地址不能修改),但它引用的对象的属性是可以更改的。
14>: C# partial 关键字详解
Partial是局部类型的意思。允许我们将一个类、结构或接口分成几个部分,分别实现在几个不同的.cs文件中。C#编译器在编译的时候仍会将各个部分的局部类型合并成一个完整的类
局部类型适用于以下情况:
(1) 类型特别大,不宜放在一个文件中实现。
(2) 一个类型中的一部分代码为自动化工具生成的代码,不宜与我们自己编写的代码混合在一起。
(3) 需要多人合作编写一个类。
局部类型的限制
(1) 局部类型只适用于类、接口、结构,不支持委托和枚举。
(2) 同一个类型的各个部分必须都有修饰符 partial。
(3) 使用局部类型时,一个类型的各个部分必须位于相同的命名空间中。
(4) 一个类型的各个部分必须被同时编译。
// 如果一个类型有一个部分使用了abstract修饰符,那么整个类都将被视为抽象类。
// 如果一个类型有一个部分使用了 sealed 修饰符,那么整个类都将被视为密封类。
15>:C# where用法
where 子句用于指定类型约束,这些约束可以作为泛型声明中定义的类型参数的变量。
1.接口约束。
例如,可以声明一个泛型类 MyGenericClass,这样,类型参数 T 就可以实现 IComparable<T> 接口:
public class MyGenericClass<T> where T:IComparable { }
2.基类约束:指出某个类型必须将指定的类作为基类(或者就是该类本身),才能用作该泛型类型的类型参数。
这样的约束一经使用,就必须出现在该类型参数的所有其他约束之前。
class MyClassy<T, U>
where T : class
where U : struct
{
}
3.where 子句还可以包括构造函数约束。
可以使用 new 运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new() 的约束。new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。例如:
public class MyGenericClass <T> where T: IComparable, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
new() 约束出现在 where 子句的最后。
4.对于多个类型参数,每个类型参数都使用一个 where 子句,
例如:
interface MyI { }
class Dictionary<TKey,TVal>
where TKey: IComparable, IEnumerable
where TVal: MyI
{
public void Add(TKey key, TVal val)
{
}
}
5.还可以将约束附加到泛型方法的类型参数,例如:
public bool MyMethod<T>(T t) where T : IMyInterface { }
请注意,对于委托和方法两者来说,描述类型参数约束的语法是一样的:
delegate T MyDelegate<T>() where T : new()
-----------
protected static T CreateNewInstance<T>() where T : new()
{
return new T();
}
where T: new()
where后的称为泛型约束,这里约束泛型参数T必须具有无参的构造函数
//能解释一下下面两段代码中where的作用吗?
using System;
public class MyGenericClass <T> where T: IComparable, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
using System;
using System.Collections;
interface MyI
{
}
class Dictionary<TKey,TVal>
where TKey: IComparable, IEnumerable
where TVal: MyI
{
public void Add(TKey key, TVal val)
{
}
}
另外,这个特性具体有什么实际意义?怎么应用?希望举一个例子。
-----
泛型类定义时的where子句表示的是泛型类型约束,也就是对泛型需要满足的条件的一个限制。
比如
where T: IComparable, new()
说明,这里的泛型T必须实现IComparable接口,并且必须有公有无参数构造函数。
因为泛型为中可能会用到一些与泛型相关的操作,但因为是泛型,所以不能保证使用时的泛型一定能进行这项操作,所以有这么个约束,保证在程序编译时能够检查出泛型是否满足要求。
例如,你写的MyGenericClass类中,对T进行了实例化:new T(),这就要求T一定要有公有无参数构造函数,如果没有where中的泛型类型约束,是不能保证这样实例化能够成功的。
16>:C#怎样获得窗口句柄
函数型:HWND FindWindow(LPCTSTR IpClassName,LPCTSTR IpWindowName);
这个函数有两个参数,第一个是要找的窗口的类,第二个是要找的窗口的标题。在搜索的时候不一定两者都知道,但至少要知道其中的一个。有的窗口的标题是比较容易得到的,如"计算器",所以搜索时应使用标题进行搜索。但有的软件的标题不是固定的,如"记事本",如果打开的文件不同,窗口标题也不同,这时使用窗口类搜索就比较方便。如果找到了满足条件的窗口,这个函数返回该窗口的句柄,否则返回0。
eg:
IntPtr hwnd = FindWindow(null, "计算器");
if (hwnd != IntPtr.Zero)
{
MessageBox.Show("找到计算器窗口");
}
else
{
MessageBox.Show("没有找到计算器窗口");
}
hwnd = FindWindow("Notepad", null);
if (hwnd != IntPtr.Zero)
{
MessageBox.Show("找到记事本窗口");
}
else
{
MessageBox.Show("没有找到记事本窗口");
}
17>:Windows 消息驱动 事件驱动
Windows程序的进行依靠外部发生的事件来驱动。换句话说,程序不断等待(利用while循环),等待任何可能的输入,然后作出判断,然后做出适当的处理。上述的“输入”是由操作系统捕捉到的,以消息形式(一种数据结构)进入到程序之中,操作系统拥有自己的一套检测循环,掌管着各个外围驱动。
如果对应用程序获得的各种“输入”进行分类,可以分为由硬件装置所产生的消息(鼠标移动或者键盘被按下),放在系统队列中,以及由windows系统或者其他Windows程序发过来的消息,放在程序队列中。以应程序的眼光来看,消息来自哪里其实并没有太大的区别,反正程序中GetMessage API就获取一个消息,程序的生命都靠它来驱动。所有的GUI系统,包括UNIX以及OS/2都是这样,以消息为基础的事件驱动系统。
可想而知,每一个Windows程序都应该有一个如下的循环:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
18>:SendMessage
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int msg, uint wParam, uint lParam);
参数为 1窗口句柄 2消息类型 34消息内容 消息格式可以定义多种 可以用IntPtr String 等等
我们不仅可以传递系统已经定义好的消息,还可以传递自定义的消息(只需要发送消息端和接收消息端对自定义的消息值统一即可)消息统一采用4位16进制的数。
1.0x0000--0x0400是系统自定义的消息,0x0400以后的数值我们可以作为自定义的消息值。
2.自定义消息Message,代码如下:
class Message
{
public const int USER = 0x0400;
public const int WM_TEST = USER + 101;
public const int WM_MSG = USER + 102;
}
//发送消息
private void button1_Click(object sender, EventArgs e)
{
SendMessage(this.Handle, Message.WM_TEST, 100, 200);
}
//响应和处理自定义消息
protected override void DefWndProc(ref System.Windows.Forms.Message m)
{
string message;
switch (m.Msg)
{
case Message.WM_TEST://处理消息
message = string.Format("收到从应用程序发出的消息!参数为:{0}, {1}", m.WParam, m.LParam);
MessageBox.Show(message);
break;
//case Message.WM_MSG:
// message = string.Format("收到从DLL发出的消息!参数为:{0}, {1}", m.WParam, m.LParam);
// MessageBox.Show(message);
// break;
default:
base.DefWndProc(ref m);
break;
}
}
//---------------------
C#中如何编写使用SendMessage
在.net中,程序驱动采用了事件驱动而不是原来的消息驱动,
虽然.net框架提供的事件已经十分丰富,但是在以前的系统中定义了
丰富的消息对系统的编程提供了方便的实现方法,因此在.net中使用消
息有时候可以提高编程的效率的。
1 定义消息
在c#中消息需要定义成windows系统中的原始的16进制数字,比如
const int WM_Lbutton = 0x201; //定义了鼠标的左键点击消息
public const int USER = 0x0400; // 是windows系统定义的用户消息
2 消息发送
消息发送是通过windows提供的API函数SendMessage来实现的它的原型定义为
[DllImport("User32.dll",EntryPoint="SendMessage")]
private static extern int SendMessage(
int hWnd, // handle to destination window
int Msg, // mesage
int wParam, // first message parameter
int lParam // second message parameter
在.net中,任何一个窗口都有消息的接收处理函数,就是wndproc函数
你可以在form中重写该函数来处理消息
protected override void WndProc ( ref System.WinForms.Message m )
{
switch(m.msg)
{
case WM_Lbutton :
string message = string.Format("收到消息!参数为:{0},{1}",m.wParam,m.lParam);
MessageBox.Show(message);
break;
default:
base.DefWndProc(ref m);//调用基类函数处理非自定义消息。
break;
}
}
其实,C#中的事件也是通过封装系统消息来实现的,如果你在WndProc函数中不处理该消息
那么,它会被交给系统来处理该消息,系统便会通过代理来实现鼠标单击的处理函数,因此你可以通过
WndProc函数来拦截消息,比如你想拦截某个按钮的单击消息
//---------------------
消息实现进程间通信
//下面实例 启动一个进程 然后发送消息 到该进程
ProcessStartInfo startInfo = new ProcessStartInfo();
Process pro = new Process();
private void Form1_Load(object sender, EventArgs e)
{
startInfo.FileName = @"F:\Projects\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe";
pro.StartInfo = startInfo;
}
private void button1_Click(object sender, EventArgs e)
{
pro.Start();
}
private void button2_Click(object sender, EventArgs e)
{
pro.Kill();
}
private void button3_Click(object sender, EventArgs e)
{
IntPtr hWnd = pro.MainWindowHandle;
int data = Convert.ToInt32(this.textBox1.Text);
SendMessage(hWnd, 0x0100, data, 0);
SendMessage(hWnd, Message.WM_TEST, 300, 300);
}
//与PostMessage()区别
BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
PostMessage 是异步的,SendMessage 是同步的。
PostMessage 只把消息放入队列,不管消息是否被处理就返回,消息可能不被处理;而 SendMessage 等待消息被处理完了之后才返回,如果消息不被处理,发送消息的线程将一直被阻塞。
19>:protected override void WndProc(ref Message m) //窗口消息
对于处理所有消息.net 提供了wndproc进行重写
WndProc(ref Message m)
protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_CLOSE = 0xF060;
if (m.Msg == WM_SYSCOMMAND && (int) m.WParam == SC_CLOSE)
{
// 屏蔽传入的消息事件
this.WindowState = FormWindowState.Minimized;
return;
}
base.WndProc(ref m);
}
20>:protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, System.Windows.Forms.Keys keyData)//重新实现Form的键盘消息
protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, System.Windows.Forms.Keys keyData)
{
int WM_KEYDOWN = 256;
int WM_SYSKEYDOWN = 260;
if (msg.Msg == WM_KEYDOWN | msg.Msg == WM_SYSKEYDOWN)
{
switch (keyData)
{
case Keys.Escape:
this.Close();//Esc退出
break;
}
}
return false;
}
21>:实现屏幕截取
可以利用Graphics类的CopyFromScreen方法来实现屏幕截取,舍弃了比较麻烦的API函数,只要几句代码就能实现了,而且这个这个方法能实现只抓取部分区域的屏幕,可以实现类似qq抓屏的功能。
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
//获得当前屏幕的分辨率
Screen scr = Screen.PrimaryScreen;
Rectangle rc = scr.Bounds;
int iWidth = rc.Width;
int iHeight = rc.Height;
//创建一个和屏幕一样大的Bitmap
Image myImage = new Bitmap(iWidth, iHeight);
//从一个继承自Image类的对象中创建Graphics对象
Graphics g = Graphics.FromImage(myImage);
//抓屏并拷贝到myimage里
g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(iWidth, iHeight));
//保存为文件
myImage.Save(@"c:/1.jpeg");
}
}
}
22>:鼠标钩子使用
获取鼠标位置处窗口句柄,需要使用到Win32Api函数WindowFromPoint,用来根据坐标获取窗口句柄,C#引用如下:
[DllImport("user32.dll", EntryPoint = "WindowFromPoint")]//指定坐标处窗体句柄
public static extern int WindowFromPoint(
int xPoint,
int yPoint
);
只要能够获取鼠标的位置,然后调用该函数就可以得到窗口句柄。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
MouseHook mh;
private void Form1_Load(object sender, EventArgs e)
{
//安装鼠标钩子
mh = new MouseHook();
mh.SetHook();
mh.MouseMoveEvent += mh_MouseMoveEvent;
}
void mh_MouseMoveEvent(object sender, MouseEventArgs e)
{ //当前鼠标位置
int x = e.Location.X;
int y = e.Location.Y;
lb_p.Text = string.Format("({0},{1})", x, y);
int hwnd = Win32Api.WindowFromPoint(x, y);//获取指定坐标处窗口的句柄
lb_h.Text = hwnd.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
mh.UnHook();
}
}
23>:键盘钩子使用
使用钩子之前,需要使用SetWindowsHookEx()函数创建钩子,使用完毕之后要UnhookWindowsHookEx()函数卸载钩子,“钩”到消息后操作系统会自动调用在创建钩子时注册的回调函数来处理消息,处理完后调用CallNextHookEx()函数等待或处理下一条消息。
对于键盘钩子,钩子类型为WH_KEYBOARD_LL=13,只需要设置SetWindowsHookEx的idHook参数为13即可“钩”到键盘消息。关于钩子类型的资料见参考资料--钩子类型。
public class Win32Api
{
#region 常数和结构
public const int WM_KEYDOWN = 0x100;
public const int WM_KEYUP = 0x101;
public const int WM_SYSKEYDOWN = 0x104;
public const int WM_SYSKEYUP = 0x105;
public const int WH_KEYBOARD_LL = 13;
[StructLayout(LayoutKind.Sequential)] //声明键盘钩子的封送结构类型
public class KeyboardHookStruct
{
public int vkCode; //表示一个在1到254间的虚似键盘码
public int scanCode; //表示硬件扫描码
public int flags;
public int time;
public int dwExtraInfo;
}
#endregion
#region Api
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
//安装钩子的函数
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
//卸下钩子的函数
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
//下一个钩挂的函数
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
[DllImport("user32")]
public static extern int ToAscii(int uVirtKey, int uScanCode, byte[] lpbKeyState, byte[] lpwTransKey, int fuState);
[DllImport("user32")]
public static extern int GetKeyboardState(byte[] pbKeyState);
[DllImport("kernel32.dll", CharSet = CharSet.Auto,CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
#endregion
添加新建类KeyboardHook,封装键盘钩子,代码如下:
public class KeyboardHook
{
int hHook;
Win32Api.HookProc KeyboardHookDelegate;
public event KeyEventHandler OnKeyDownEvent;
public event KeyEventHandler OnKeyUpEvent;
public event KeyPressEventHandler OnKeyPressEvent;
public KeyboardHook() { }
public void SetHook()
{
KeyboardHookDelegate = new Win32Api.HookProc(KeyboardHookProc);
Process cProcess = Process.GetCurrentProcess();
ProcessModule cModule = cProcess.MainModule;
var mh = Win32Api.GetModuleHandle(cModule.ModuleName);
hHook = Win32Api.SetWindowsHookEx(Win32Api.WH_KEYBOARD_LL, KeyboardHookDelegate, mh, 0);
}
public void UnHook()
{
Win32Api.UnhookWindowsHookEx(hHook);
}
private List<Keys> preKeysList = new List<Keys>();//存放被按下的控制键,用来生成具体的键
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
//如果该消息被丢弃(nCode<0)或者没有事件绑定处理程序则不会触发事件
if ((nCode >= 0) && (OnKeyDownEvent != null || OnKeyUpEvent != null || OnKeyPressEvent != null))
{
Win32Api.KeyboardHookStruct KeyDataFromHook = (Win32Api.KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(Win32Api.KeyboardHookStruct));
Keys keyData = (Keys)KeyDataFromHook.vkCode;
//按下控制键
if ((OnKeyDownEvent != null || OnKeyPressEvent != null) && (wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN))
{
if (IsCtrlAltShiftKeys(keyData) && preKeysList.IndexOf(keyData) == -1)
{
preKeysList.Add(keyData);
}
}
//WM_KEYDOWN和WM_SYSKEYDOWN消息,将会引发OnKeyDownEvent事件
if (OnKeyDownEvent != null && (wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN))
{
KeyEventArgs e = new KeyEventArgs(GetDownKeys(keyData));
OnKeyDownEvent(this, e);
}
//WM_KEYDOWN消息将引发OnKeyPressEvent
if (OnKeyPressEvent != null && wParam == Win32Api.WM_KEYDOWN)
{
byte[] keyState = new byte[256];
Win32Api.GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (Win32Api.ToAscii(KeyDataFromHook.vkCode, KeyDataFromHook.scanCode, keyState, inBuffer, KeyDataFromHook.flags) == 1)
{
KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
OnKeyPressEvent(this, e);
}
}
//松开控制键
if ((OnKeyDownEvent != null || OnKeyPressEvent != null) && (wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP))
{
if (IsCtrlAltShiftKeys(keyData))
{
for (int i = preKeysList.Count - 1; i >= 0; i--)
{
if (preKeysList[i] == keyData) { preKeysList.RemoveAt(i); }
}
}
}
//WM_KEYUP和WM_SYSKEYUP消息,将引发OnKeyUpEvent事件
if (OnKeyUpEvent != null && (wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP))
{
KeyEventArgs e = new KeyEventArgs(GetDownKeys(keyData));
OnKeyUpEvent(this, e);
}
}
return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);
}
//根据已经按下的控制键生成key
private Keys GetDownKeys(Keys key)
{
Keys rtnKey = Keys.None;
foreach (Keys i in preKeysList)
{
if (i == Keys.LControlKey || i == Keys.RControlKey) { rtnKey = rtnKey | Keys.Control; }
if (i == Keys.LMenu || i == Keys.RMenu) { rtnKey = rtnKey | Keys.Alt; }
if (i == Keys.LShiftKey || i == Keys.RShiftKey) { rtnKey = rtnKey | Keys.Shift; }
}
return rtnKey | key;
}
private Boolean IsCtrlAltShiftKeys(Keys key)
{
if (key == Keys.LControlKey || key == Keys.RControlKey || key == Keys.LMenu || key == Keys.RMenu || key == Keys.LShiftKey || key == Keys.RShiftKey) { return true; }
return false;
}
}
在主窗体中添加代码,如下:
public MainForm()
{
InitializeComponent();
}
KeyboardHook kh;
private void Form1_Load(object sender, EventArgs e)
{
kh = new KeyboardHook();
kh.SetHook();
kh.OnKeyDownEvent += kh_OnKeyDownEvent;
}
void kh_OnKeyDownEvent(object sender, KeyEventArgs e)
{
if (e.KeyData == (Keys.S | Keys.Control)) { this.Show(); }//Ctrl+S显示窗口
if (e.KeyData == (Keys.H | Keys.Control)) { this.Hide(); }//Ctrl+H隐藏窗口
if (e.KeyData == (Keys.C | Keys.Control)) { this.Close(); }//Ctrl+C 关闭窗口
if (e.KeyData == (Keys.A | Keys.Control | Keys.Alt)) { this.Text = "你发现了什么?"; }//Ctrl+Alt+A
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
kh.UnHook();
}
}
24>: 复制粘贴功能 C# 操作Clipboard
C#定义了一个类System.Windows.Forms.Clipboard来简化剪切板操作,这个类有一个静态方法,主要有:
Clear 清除剪切板中的所有数据;
ContainsData,ContainsAudio,ContainsFlieDropList,ContainsText,ContainsImage,用于检查剪切板中是否存在相应的数据;
GetAudioStream,GetDataObject,GetText,GetImage,GetFileDropList,用于取得数据;
SetAudio,SetDataObject,SetText,SetImage,SetFileDropList,用于添加数据;
// 这里判断是否有数据被复制
object clipboardData = Clipboard.GetData("userEntites");
this.btnPaste.Enabled = (clipboardData != null);
//复制
private void btnCopy_Click(object sender, EventArgs e)
{
// 读取数据
List<BaseUserEntity> userEntites = new List<BaseUserEntity>();
for (int i=0; i<this.DTUser.Rows.Count; i++)
{
BaseUserEntity userEntity = new BaseUserEntity(this.DTUser.Rows[i]);
userEntites.Add(userEntity);
}
// 复制到剪切板
Clipboard.SetData("userEntites", userEntites);
this.btnPaste.Enabled = true;
}
//粘贴
private void btnPaste_Click(object sender, EventArgs e)
{
object clipboardData = Clipboard.GetData("userEntites");
if (clipboardData != null)
{
List<BaseUserEntity> userEntites = (List<BaseUserEntity>)clipboardData;
string[] addUserIds = new string[userEntites.Count];
for (int i = 0; i < userEntites.Count; i++)
{
addUserIds[i] = userEntites[i].Id.ToString();
}
// 添加用户到角色
ServiceManager.Instance.RoleService.AddUserToRole(this.UserInfo, this.TargetRoleId, addUserIds);
// 加载窗体
this.FormOnLoad();
// 设置按钮状态
this.SetControlState();
}
}
25>:C#中List.ForEach 方法是对 List 的每个元素执行指定操作。
eg:
namespace AppExample
{
class Program
{
static void Main(string[] args)
{
List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
numbers.Add(4);
numbers.Add(5);
numbers.Add(6);
//numbers.ForEach(Display);
//numbers.ForEach(number =>
//{
// Console.WriteLine(number);
//});
numbers.ForEach(delegate(int number)
{
Console.WriteLine(number);
});
}
static void Display(int number)
{
Console.WriteLine(number);
}
}
}
26>:C# 监测某个文件夹内是否有文件增加
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.IO;
using System.Diagnostics;
using System.Xml;
namespace WindowsFormsApplication1
{
public partial class FSWControl : Form
{
static FileSystemWatcher watcher = new FileSystemWatcher();
public FSWControl()
{
InitializeComponent();
string StrPath = ReadrXML("watchdirectory", "savedirectory");
WatcherStrat(StrPath,"*.*",true,true);
}
/// <summary>
/// 初始化监听
/// </summary>
/// <param name="StrWarcherPath">需要监听的目录</param>
/// <param name="FilterType">需要监听的文件类型(筛选器字符串)</param>
/// <param name="IsEnableRaising">是否启用监听</param>
/// <param name="IsInclude">是否监听子目录</param>
private static void WatcherStrat(string StrWarcherPath, string FilterType, bool IsEnableRaising, bool IsInclude)
{
//初始化监听
watcher.BeginInit();
//设置监听文件类型
watcher.Filter = FilterType;
//设置是否监听子目录
watcher.IncludeSubdirectories = IsInclude;
//设置是否启用监听?
watcher.EnableRaisingEvents = IsEnableRaising;
//设置需要监听的更改类型(如:文件或者文件夹的属性,文件或者文件夹的创建时间;NotifyFilters枚举的内容)
watcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Security | NotifyFilters.Size;
//设置监听的路径
watcher.Path = StrWarcherPath;
//注册创建文件或目录时的监听事件
watcher.Created += new FileSystemEventHandler(watch_created);
//注册当指定目录的文件或者目录发生改变的时候的监听事件
watcher.Changed += new FileSystemEventHandler(watch_changed);
//注册当删除目录的文件或者目录的时候的监听事件
watcher.Deleted += new FileSystemEventHandler(watch_deleted);
//当指定目录的文件或者目录发生重命名的时候的监听事件
watcher.Renamed += new RenamedEventHandler(watch_renamed);
//结束初始化
watcher.EndInit();
}
/// <summary>
/// 创建文件或者目录时的监听事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_created(object sender, FileSystemEventArgs e)
{
//事件内容
}
/// <summary>
/// 当指定目录的文件或者目录发生改变的时候的监听事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_changed(object sender, FileSystemEventArgs e)
{
//事件内容
}
/// <summary>
/// 当删除目录的文件或者目录的时候的监听事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_deleted(object sender, FileSystemEventArgs e)
{
//事件内容
}
/// <summary>
/// 当指定目录的文件或者目录发生重命名的时候的事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_renamed(object sender, RenamedEventArgs e)
{
//事件内容
}
/// <summary>
/// 启动或者停止监听
/// </summary>
/// <param name="IsEnableRaising">True:启用监听,False:关闭监听</param>
private void WatchStartOrSopt(bool IsEnableRaising)
{
watcher.EnableRaisingEvents = IsEnableRaising;
}
//-------------------
新建一个Console应用程序,项目名称为“FileSystemWatcher”,Copy代码进,编译后就可以用了。代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Permissions;
namespace MyFileSystemWatcher
{
public class Watcher
{
public static void Main(string[] args)
{
Run();
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public static void Run()
{
string[] args = System.Environment.GetCommandLineArgs();
if (args.Length != 2)
{
Console.WriteLine("使用方式: FileSystemWatcher.exe DirectoryPath");
return;
}
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = args[1];
/* 设置为监视 LastWrite 和 LastAccess 时间方面的更改,以及目录中文本文件的创建、删除或重命名。 */
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// 只监控.txt文件
watcher.Filter = "*.txt";
// 添加事件处理器。
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
// 开始监控。
watcher.EnableRaisingEvents = true;
// 输入q推出程序。
Console.WriteLine("按 \'q\' 推出程序。");
while (Console.Read() != 'q') ;
}
// 定义事件处理器。
private static void OnChanged(object source, FileSystemEventArgs e)
{
//如果更改、创建或删除文件,文件路径将被输出到控制台。
Console.WriteLine("文件: " + e.FullPath + " " + e.ChangeType);
}
private static void OnRenamed(object source, RenamedEventArgs e)
{
// 在文件重命名后,旧路径和新路径都输出到控制台。
Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
}
}
}
27>:C#winform动态添加控件
//查找页面名为myButton的控件
Button Button = (Button)Page.FindControl("myButton");
一般在loaded事件中处理
private void button1_Click(object sender, EventArgs e)
{
int num = int.Parse(textBox1.Text);
for (int i = 0; i < num; i++)
{
TextBox tb = new TextBox();
//设定位置
tb.Top = 50 + i * 30;
//添加控件
this.Controls.Add(tb);
}
}
比如button
button btn=new button();//初始化一个控件
btn.size=new size(100,100);//设置大小
btn.location=new postion(50,100);//设置坐标
btn.text="按钮1";//设置文本
this.controls.add(btn);//添加到窗体中
btn.Click += new System.EventHandler(btn_click);//将按钮的方法绑定到按钮的单击事件中b.Click是按钮的单击事件
下面我们来讲如何对新建的按钮添加对应的事件方法btn_click():
private void btn_click(object sender, System.EventArgs e)
{
Button b1 = (Button)sender;//将触发此事件的对象转换为该Button对象
MessageBox.Show(""+b1.Name);
}
28>:FlowLayoutPanel
FlowLayoutPanel是一个用于自动排列控件的panel,它会自动的排列在它里面的控件,默认是从左到右,从上到下,用这个控件,就是为了方便不用自己写代码控制button的位置
表示一个沿着水平或垂直方向动态排放其内容的面板。
FlowLayoutPanel是流式布局控件,添加到它上面的控件会按设定顺序依次排列,并且不支持拖拽到特定位置
29>:如何在winform中动态添加或删除控件
Controls.Add(new Label() { Size = new Size(100, 100), Text = "I'm new", name = "newlabel" });
Controls.Remove("newlabel");
Controls.Clear();
//添加控件
TextBox txt = new TextBox();
txt.Text = "我是自动添加的";
txt.Location = new Point(10, 10);
this.Controls.Add(txt);
//移除控件
this.Controls.Remove(txt);
txt.Dispose();
Label lb1 = Panel1.Controls[name] as Label;
31>:C# 中多线程 更新界面方法:
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。
正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。
而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而已,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。
举个简单例子说明下使用方法,比如你在启动一个线程,在线程的方法中想更新窗体中的一个TextBox..
using System.Threading;
//启动一个线程
Thread thread=new Thread(new ThreadStart(DoWork));
thread.Start();
//线程方法
private void DoWork()
{
this.TextBox1.Text="我是一个文本框";
}
如果你像上面操作,在VS2005或2008里是会有异常的...
正确的做法是用Invoke\BeginInvoke
using System.Threading;
namespace test
{
public partial class Form1 : Form
{
public delegate void MyInvoke(string str1,string str2);
public Form1()
{
InitializeComponent();
}
public void DoWork()
{
MyInvoke mi = new MyInvoke(UpdateForm);
this.BeginInvoke(mi, new Object[] {"我是文本框","haha"});
}
public void UpdateForm(string param1,string parm2)
{
this.textBox1.Text = param1+parm2;
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();
}
}
}
32>:Invoke方法
在用.NET Framework框架的WinForm构建GUI程序界面时,如果要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击之后按钮上的文本显示“关闭”,初学者往往会想当然地这么写:
void ButtonOnClick(object sender,EventArgs e)
{
button.Text="关闭";
}
这样的写法运行程序之后,可能会触发异常,异常信息大致是“不能从不是创建该控件的线程调用它”。注意这里是“可能”,并不一定会触发该种异常。造成这种异常的原因在于,控件是在主线程中创建的(比如this.Controls.Add(...);),进入控件的事件响应函数时,是在控件所在的线程,并不是主线程。在控件的事件响应函数中改变控件的状态,可能与主线程发生线程冲突。如果主线程正在重绘控件外观,此时在别的线程改变控件外观,就会造成画面混乱。不过这样的情况并不总会发生,如果主线程此时在重绘别的控件,就可能逃过一劫,这样的写法可以正常通过,没有触发异常。
正确的写法是在控件响应函数中调用控件的Invoke方法(其实如果大家以前用过C++ Builder的话,也会找到类似Invoke那样的激活到主线程的函数)。Invoke方法会顺着控件树向上搜索,直到找到创建控件的那个线程(通常是主线程),然后进入那个线程改变控件的外观,确保不发生线程冲突。正确写法的示例如下:
void ButtonOnClick(object sender,EventArgs e)
{
button.Invoke(new EventHandler(delegate
{
button.Text="关闭";
}));
}
Invoke方法需要创建一个委托。你可以事先写好函数和与之对应的委托。不过,若想直观地在Invoke方法调用的时候就看到具体的函数,而不是到别处搜寻的话,上面的示例代码是不错的选择。
这样的写法有一个烦人的地方:对不同的控件写法不同。对于TextBox,要TextBoxObject.Invoke,对于Label,又要LabelObject.Invoke。有没有统一一点的写法呢?
主窗口类本身也有Invoke方法。如果你不想对不同的控件写法不一样,可以全部用this.Invoke:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new EventHandler(delegate
{
button.Text="关闭";
}));
}
在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new Action(()=>
{
button.Text="关闭";
}));
}
33>:任务 Task
对于多线程,我们经常使用的是Thread。在我们了解Task之前,如果我们要使用多核的功能可能就会自己来开线程,然而这种线程模型在.net 4.0之后被一种称为基于“任务的编程模型”所冲击,因为task会比thread具有更小的性能开销,不过大家肯定会有疑惑,任务和线程到底有什么区别呢?
任务和线程的区别:
1、任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行。
2、任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。
-----
创建Task
创建Task的方法有两种,一种是直接创建——new一个出来,一种是通过工厂创建。下面来看一下这两种创建方法:
//第一种创建方式,直接实例化
var task1 = new Task(() =>
{
//TODO you code
});
//第二种创建方式,工厂创建
var task2 = Task.Factory.StartNew(() =>
{
//TODO you code
});
代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TaskDemo
{
class Program
{
static void Main(string[] args)
{
var task1 = new Task(() =>
{
Console.WriteLine("Hello,task");
});
task1.Start();
var task2 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Hello,task started by task factory");
});
Console.Read();
}
}
}
这里我分别用两种方式创建两个task,并让他们运行。可以看到通过构造函数创建的task,必须手动Start,而通过工厂创建的Task直接就启动了。
34>:
35>:
获取电脑文档目录
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
2>:
获取电脑临时存储目录
GetTempPath
3>:
获取集合只读存储
AsReadOnly
Array.AsReadOnly<T>
List<T>.AsReadOnly
若要防止对数组进行任何修改,应该仅通过此包装公开数组。
只读集合只是一个具有用于防止修改的包装的集合;因此,如果更改基础集合,则只读集合将反映那些更改。
4>:Abstract方法(抽象方法)
abstract关键字只能用在抽象类中修饰方法,并且没有具体的实现。抽象方法的实现必须在派生类中使用override关键字来实现。
5>:C#中virtual和abstract的区别
virtual和abstract都是用来修饰父类的,通过覆盖父类的定义,让子类重新定义。
它们有一个共同点:如果用来修饰方法,前面必须添加public,要不然就会出现编译错误:虚拟方法或抽象方法是不能私有的。毕竟加上virtual或abstract就是让子类重新定义的,而private成员是不能被子类访问的。
但是它们的区别很大。(virtual是“虚拟的”,abstract是“抽象的").
(1)virtual修饰的方法必须有实现(哪怕是仅仅添加一对大括号),而abstract修饰的方法一定不能实现。如对于virtual修饰的方法如果没有实现:
public class Test1
{
public virtual void fun1();
}
错误 2 “Test1.fun1()”必须声明主体,因为它未标记为 abstract、extern 或 partial
对于abstract修饰的方法如果有实现:
public abstract class Test2
{
public abstract void fun2() { }
}
错误 1 “Test2.fun2()”无法声明主体,因为它标记为 abstract
(2)virtual可以被子类重写,而abstract必须被子类重写,
class BaseTest1
{
public virtual void fun() { }//必须有实现
}
class DeriveTest1:BaseTest1
{
//public override void fun() { }
}
编译不会出现错误,如果重写了virtual修饰的方法,前面必须添加override(这样就告诉了编译器你要重写虚拟方法),而且必须有实现,否则编译出错;abstract class BaseTest2
{
public abstract void fun();
}
class DeriveTest2 : BaseTest2
{
//public override void fun();错误1:没有实现
//public void fun() { } 错误2:重写时没有添加override
//override void fun() { }错误3:虚拟成员或者抽象成员不能是私有的(只要在父类中声明了虚拟成员或抽象成员,即便是继承的也要加上这个限制)
public override void fun() { }//如果重写方法; 错误:“A.DeriveTest2”不实现继承的抽象成员“A.BaseTest2.fun()”
}
(3)如果类成员被abstract修饰,则该类前必须添加abstract,因为只有抽象类才可以有抽象方法。
(4)无法创建abstract类的实例,只能被继承无法实例化,比如: BaseTest2 base2 = new BaseTest2();将出现编译错误:抽象类或接口不能创建实例。
(5)C#中如果要在子类中重写方法,必须在父类方法前加virtual,在子类方法前添加override,这样就避免了程序员在子类中不小心重写了父类方法。
(6)abstract方法必须重写,virtual方法必须有实现(即便它是在abstract类中定义的方法).
abstract public class Test
{
//public virtual void Prinf();错误:virtual方法必须有实现
public virtual void Prinf() //abstract类的virtual方法可以不重写;abstract方法必须重写。
{
Console.WriteLine("Abstract Printf...");
}
}
public class Class1 : Test
{
/*
public override void Prinf() //派生类中不重写abstract类的virtual方法照样可以运行,不过调用派生类对象的Printf方法时,调用的是父类的。
{
Console.WriteLine("Class One Override Printf...");
}
*/
}
6>:List与IList的区别
在我看一个源程序的时候看到这个例子使用了IList<T>返回类型,因为上午刚刚总结过List<T>的详细用法,突然出现了IList<T>,感觉很奇怪,于是上网搜集了很多东西才明白了
它们的区别,刚开始仅仅是看文字说明,但是怎么都不明白,后来看到了一个实例,然后接着
看文字说明,豁然开朗啊,现在我先把这个实例程序写出来:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace List
{
public class Users //类Users 用户
{
public string Name; // 姓名
public int Age; // 年龄
public Users(string _Name, int _Age)
{
Name = _Name;
Age = _Age;
}
}
class Program
{
static void Main(string[] args)
{
Users U = new Users("jiang", 24);
IList<Users> UILists = new List<Users>();
//千万要注意:等式的右边是List<Users>,而不是 IList<Users>,
//如果在List前面加一个I, 就会出现错误:抽象类或接口无法创建实例。
UILists.Add(U);
U = new Users("wang", 22);
UILists.Add(U);
List<Users> I = ConvertIListToList<Users>(UILists);
Console.WriteLine(I[0].Name);
Console.WriteLine(I[1].Name);
Console.Read();
}
// ** <summary>
/// 转换IList<T>为List<T> //将IList接口泛型转为List泛型类型
/// </summary>
/// <typeparam name="T">指定的集合中泛型的类型</typeparam>
/// <param name="gbList">需要转换的IList</param>
/// <returns></returns>
public static List<T> ConvertIListToList<T>(IList<T> gbList) where T : class //静态方法,泛型转换,
{
if (gbList != null && gbList.Count >= 1)
{
List<T> list = new List<T>();
for (int i = 0; i < gbList.Count; i++) //将IList中的元素复制到List中
{
T temp = gbList[i] as T;
if (temp != null)
list.Add(temp);
}
return list;
}
return null;
}
}
}
注意:
IList<Users> UILists = new List<Users>(); //千万要注意:等式的右边是List<Users>,
而不是 IList<Users>,如果在List前面加一个I, 就会出现错误:抽象类或接口无法创建实例。
下面说一下IList与List的区别:
(1)首先IList 泛型接口是 ICollection 泛型接口的子代,并且是所有泛型列表的基接口。
它仅仅是所有泛型类型的接口,并没有太多方法可以方便实用,如果仅仅是作为集合数据的承载体,确实,IList<T>可以胜任。
不过,更多的时候,我们要对集合数据进行处理,从中筛选数据或者排序。这个时候IList<T>就爱莫能助了。
1、当你只想使用接口的方法时,ILis<>这种方式比较好.他不获取实现这个接口的类的其他方法和字段,有效的节省空间.
2、IList <>是个接口,定义了一些操作方法这些方法要你自己去实现
List <>是泛型类,它已经实现了IList <>定义的那些方法
IList <Class1> IList11 =new List <Class1>();
List <Class1> List11 =new List <Class1>();
这两行代码,从操作上来看,实际上都是创建了一个List<Class1>对象的实例,也就是说,他们的操作没有区别。
只是用于保存这个操作的返回值变量类型不一样而已。
那么,我们可以这么理解,这两行代码的目的不一样。
List <Class1> List11 =new List <Class1>();
是想创建一个List<Class1>,而且需要使用到List<T>的功能,进行相关操作。
而
IList <Class1> IList11 =new List <Class1>();
只是想创建一个基于接口IList<Class1>的对象的实例,只是这个接口是由List<T>实现的。所以它只是希望使用到IList<T>接口规定的功能而已。
7>:LinkedList
//链表的优点是:如果要插入一个元素到链表的中间位置,会非常的快,
//原因是,如果插入,只需要修改上一个元素的Next 与 下一个元素的Previous的引用则可。
//像ArrayList列表中,如果插入,需要移动其后的所有元素。
//链表的缺点是,链表只能是一个接着一个的访问,这样就要用较长的时间来查找定位位于链表中间的元素。
8>:internal
internal关键字是类型和类型成员的访问修饰符。只有在同一个程序集的文件中,内部类型或者是成员才可以访问”。
(一个DLL文件就是一个程序集) 封装在dll文件中 仅供内部使用 不被外面调用
9>:C#中Array和ArrayList的区别及泛型
1.Array累心的变量在声明的同时必须进行实例化(至少的初始化数组的大小),而ArrayList可以只先声明。
2.Array只能存储同构的对象,ArrayList可以存储异构变量。
/*这里有一个装箱和拆箱的概念,在以后会用到:如 将String,int等隐式转化为Object是装箱. 将Object强制转化为string,int是拆箱. 在向Arraylist添加数据的过程中,对象先被装箱为Object,需要调用时,再拆箱为对应的对象, 但在这个过程中,强制转化加上ArrayList的异构特性,可能导致很多问题,由此提出了泛型的概念*/
3.在CLR托管对中东存放方式.Array是始终连续存放的,ArrayList不一定是
4.初始化大小
Array对象的初始化必须制定大小,且创建后的数组大小是固定的,而ArrayList的大小可以动态指定.
关于泛型:
ArrayList是一个非泛型集合类,添加到ArrayList中的任何引用或值类型都将隐式向上强制转换为Object.如果项是值类型,则必须在将其添加到列表中是进行装箱操作.
泛型是最常见的用途是创建集合类。.Net Framework类库在Collection.Generic命名空间中包含ijge泛型集合类。List<T>类是ArrayList类的泛型等效类。使用大小可以按需动态增加的苏族实现IList泛型接口.动态数组的好处是不必须事先设置较大的数组,减少不必要的内存开销.
10>:c#中数组,array,arraylist三者的区别是什么
c#数组就是array,array就是数组
数组定义时要写明类型,是字符串还是整形,arraylist就不用,他可以存放任何类型的变量
数组的大小在初始化时就已经决定无法改变,arraylist是可以增加和减少,使用更灵活,操作更简单。就像一楼说的,效率会低一些。如果能够确定要存放的类型和个数的话建议使用数组,否则使用arraylist。
11>:volatile
volatile 关键字表示字段可能被多个并发执行线程修改。声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。这样可以确保该字段在任何时间呈现的都是最新的值。
volatile多用于多线程的环境,当一个变量定义为volatile时,读取这个变量的值时候每次都是从momery里面读取而不是从cache读。这样做是为了保证读取该变量的信息都是最新的,而无论其他线程如何更新这个变量。
12>:lock 语句
lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。此语句的形式如下:
Object thisLock = new Object();
lock (thisLock)
{
// Critical code section
}
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
13>:C# const, readonly, static readonly
Const 定义的是静态常在对象初始化的时候赋值.以后不能改变它的值.属于编译时常量。不能用new初始化。
Readonly 是只读变量.属于运行时变量.可以在类constructor里改变它的值.不能作用于局部变量。
const 和 static 不能在一起用,它已经是静态的了。
我们都知道,const和static readonly的确非常像:通过类名而不是对象名进行访问,在程式中只读等等。在多数情况下能混用。
二者本质的差别在于,const的值是在编译期间确定的,因此只能在声明时通过常量表达式指定其值。
而static readonly,在程式中只读, 不过它是在运行时计算出其值的,所以还能通过静态构造函数来对它赋值,
readonly只能用来修饰类的field,不能修饰局部变量,也不能修饰property等其他类成员.
明白了这个本质差别,我们就不难看出下面的语句中static readonly和const能否互换了:
1. static readonly MyClass myins = new MyClass();
2. static readonly MyClass myins = null;
3. static readonly A = B * 20;
static readonly B = 10;
4. static readonly int [] constIntArray = new int[] {1, 2, 3};
5. void SomeFunction()
{
const int a = 10;
...
}
1:不能换成const。new操作符是需要执行构造函数的,所以无法在编译期间确定
2:能换成const。我们也看到,Reference类型的常量(除了String)只能是Null。
3:能换成const。我们能在编译期间非常明确的说,A等于200。
4:不能换成const。道理和1是相同的,虽然看起来1,2,3的数组的确就是个常量。
5:不能换成readonly,readonly只能用来修饰类的field,不能修饰局部变量,也不能修饰property等其他类成员。
因此,对于那些本质上应该是常量,不过却无法使用const来声明的地方,能使用static readonly。例如C#规范中给出的例子:
public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);
private byte red, green, blue;
public Color(byte r, byte g, byte b)
{
red = r;
green = g;
blue = b;
}
}
static readonly需要注意的一个问题是,对于一个static readonly的Reference类型,只是被限定不能进行赋值(写)操作而已。而对其成员的读写仍然是不受限制的。
public static readonly MyClass myins = new MyClass();
…
myins.SomeProperty = 10; //正常
myins = new MyClass(); //出错,该对象是只读的
不过,如果上例中的MyClass不是个class而是个struct,那么后面的两个语句就都会出错。
//ReadOnly关键字修饰的变量可以修改,只是不能重新分配
readonly 关键字是可以在字段上使用的修饰符。当字段声明包括 readonly 修饰符时,该声明引入的字段赋值只能作为声明的一部分出现,或者出现在同一类的构造函数中.
很多初学者看完书就会认为,readonly修饰的变量在以后是不能修改的,在以后的开发中从不对ReadOnly的变量进行修改操作,形成思维定势,这个观念是错误的。
首先要明确一点:更改!=重新分配(赋值)
对于简单类型(如int),更改是等于重新赋值,因为默认操作符只有=, 但于对于复杂类型,就不一定了。
例如:
对于class类型,修改其字段属性值。
对于集合类型,增加,移除,清空内容。
//readonly修饰的字段,其初始化仅是固定了其引用(地址不能修改),但它引用的对象的属性是可以更改的。
14>: C# partial 关键字详解
Partial是局部类型的意思。允许我们将一个类、结构或接口分成几个部分,分别实现在几个不同的.cs文件中。C#编译器在编译的时候仍会将各个部分的局部类型合并成一个完整的类
局部类型适用于以下情况:
(1) 类型特别大,不宜放在一个文件中实现。
(2) 一个类型中的一部分代码为自动化工具生成的代码,不宜与我们自己编写的代码混合在一起。
(3) 需要多人合作编写一个类。
局部类型的限制
(1) 局部类型只适用于类、接口、结构,不支持委托和枚举。
(2) 同一个类型的各个部分必须都有修饰符 partial。
(3) 使用局部类型时,一个类型的各个部分必须位于相同的命名空间中。
(4) 一个类型的各个部分必须被同时编译。
// 如果一个类型有一个部分使用了abstract修饰符,那么整个类都将被视为抽象类。
// 如果一个类型有一个部分使用了 sealed 修饰符,那么整个类都将被视为密封类。
15>:C# where用法
where 子句用于指定类型约束,这些约束可以作为泛型声明中定义的类型参数的变量。
1.接口约束。
例如,可以声明一个泛型类 MyGenericClass,这样,类型参数 T 就可以实现 IComparable<T> 接口:
public class MyGenericClass<T> where T:IComparable { }
2.基类约束:指出某个类型必须将指定的类作为基类(或者就是该类本身),才能用作该泛型类型的类型参数。
这样的约束一经使用,就必须出现在该类型参数的所有其他约束之前。
class MyClassy<T, U>
where T : class
where U : struct
{
}
3.where 子句还可以包括构造函数约束。
可以使用 new 运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new() 的约束。new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。例如:
public class MyGenericClass <T> where T: IComparable, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
new() 约束出现在 where 子句的最后。
4.对于多个类型参数,每个类型参数都使用一个 where 子句,
例如:
interface MyI { }
class Dictionary<TKey,TVal>
where TKey: IComparable, IEnumerable
where TVal: MyI
{
public void Add(TKey key, TVal val)
{
}
}
5.还可以将约束附加到泛型方法的类型参数,例如:
public bool MyMethod<T>(T t) where T : IMyInterface { }
请注意,对于委托和方法两者来说,描述类型参数约束的语法是一样的:
delegate T MyDelegate<T>() where T : new()
-----------
protected static T CreateNewInstance<T>() where T : new()
{
return new T();
}
where T: new()
where后的称为泛型约束,这里约束泛型参数T必须具有无参的构造函数
//能解释一下下面两段代码中where的作用吗?
using System;
public class MyGenericClass <T> where T: IComparable, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
using System;
using System.Collections;
interface MyI
{
}
class Dictionary<TKey,TVal>
where TKey: IComparable, IEnumerable
where TVal: MyI
{
public void Add(TKey key, TVal val)
{
}
}
另外,这个特性具体有什么实际意义?怎么应用?希望举一个例子。
-----
泛型类定义时的where子句表示的是泛型类型约束,也就是对泛型需要满足的条件的一个限制。
比如
where T: IComparable, new()
说明,这里的泛型T必须实现IComparable接口,并且必须有公有无参数构造函数。
因为泛型为中可能会用到一些与泛型相关的操作,但因为是泛型,所以不能保证使用时的泛型一定能进行这项操作,所以有这么个约束,保证在程序编译时能够检查出泛型是否满足要求。
例如,你写的MyGenericClass类中,对T进行了实例化:new T(),这就要求T一定要有公有无参数构造函数,如果没有where中的泛型类型约束,是不能保证这样实例化能够成功的。
16>:C#怎样获得窗口句柄
函数型:HWND FindWindow(LPCTSTR IpClassName,LPCTSTR IpWindowName);
这个函数有两个参数,第一个是要找的窗口的类,第二个是要找的窗口的标题。在搜索的时候不一定两者都知道,但至少要知道其中的一个。有的窗口的标题是比较容易得到的,如"计算器",所以搜索时应使用标题进行搜索。但有的软件的标题不是固定的,如"记事本",如果打开的文件不同,窗口标题也不同,这时使用窗口类搜索就比较方便。如果找到了满足条件的窗口,这个函数返回该窗口的句柄,否则返回0。
eg:
IntPtr hwnd = FindWindow(null, "计算器");
if (hwnd != IntPtr.Zero)
{
MessageBox.Show("找到计算器窗口");
}
else
{
MessageBox.Show("没有找到计算器窗口");
}
hwnd = FindWindow("Notepad", null);
if (hwnd != IntPtr.Zero)
{
MessageBox.Show("找到记事本窗口");
}
else
{
MessageBox.Show("没有找到记事本窗口");
}
17>:Windows 消息驱动 事件驱动
Windows程序的进行依靠外部发生的事件来驱动。换句话说,程序不断等待(利用while循环),等待任何可能的输入,然后作出判断,然后做出适当的处理。上述的“输入”是由操作系统捕捉到的,以消息形式(一种数据结构)进入到程序之中,操作系统拥有自己的一套检测循环,掌管着各个外围驱动。
如果对应用程序获得的各种“输入”进行分类,可以分为由硬件装置所产生的消息(鼠标移动或者键盘被按下),放在系统队列中,以及由windows系统或者其他Windows程序发过来的消息,放在程序队列中。以应程序的眼光来看,消息来自哪里其实并没有太大的区别,反正程序中GetMessage API就获取一个消息,程序的生命都靠它来驱动。所有的GUI系统,包括UNIX以及OS/2都是这样,以消息为基础的事件驱动系统。
可想而知,每一个Windows程序都应该有一个如下的循环:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
18>:SendMessage
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int msg, uint wParam, uint lParam);
参数为 1窗口句柄 2消息类型 34消息内容 消息格式可以定义多种 可以用IntPtr String 等等
我们不仅可以传递系统已经定义好的消息,还可以传递自定义的消息(只需要发送消息端和接收消息端对自定义的消息值统一即可)消息统一采用4位16进制的数。
1.0x0000--0x0400是系统自定义的消息,0x0400以后的数值我们可以作为自定义的消息值。
2.自定义消息Message,代码如下:
class Message
{
public const int USER = 0x0400;
public const int WM_TEST = USER + 101;
public const int WM_MSG = USER + 102;
}
//发送消息
private void button1_Click(object sender, EventArgs e)
{
SendMessage(this.Handle, Message.WM_TEST, 100, 200);
}
//响应和处理自定义消息
protected override void DefWndProc(ref System.Windows.Forms.Message m)
{
string message;
switch (m.Msg)
{
case Message.WM_TEST://处理消息
message = string.Format("收到从应用程序发出的消息!参数为:{0}, {1}", m.WParam, m.LParam);
MessageBox.Show(message);
break;
//case Message.WM_MSG:
// message = string.Format("收到从DLL发出的消息!参数为:{0}, {1}", m.WParam, m.LParam);
// MessageBox.Show(message);
// break;
default:
base.DefWndProc(ref m);
break;
}
}
//---------------------
C#中如何编写使用SendMessage
在.net中,程序驱动采用了事件驱动而不是原来的消息驱动,
虽然.net框架提供的事件已经十分丰富,但是在以前的系统中定义了
丰富的消息对系统的编程提供了方便的实现方法,因此在.net中使用消
息有时候可以提高编程的效率的。
1 定义消息
在c#中消息需要定义成windows系统中的原始的16进制数字,比如
const int WM_Lbutton = 0x201; //定义了鼠标的左键点击消息
public const int USER = 0x0400; // 是windows系统定义的用户消息
2 消息发送
消息发送是通过windows提供的API函数SendMessage来实现的它的原型定义为
[DllImport("User32.dll",EntryPoint="SendMessage")]
private static extern int SendMessage(
int hWnd, // handle to destination window
int Msg, // mesage
int wParam, // first message parameter
int lParam // second message parameter
);
3 消息的接受在.net中,任何一个窗口都有消息的接收处理函数,就是wndproc函数
你可以在form中重写该函数来处理消息
protected override void WndProc ( ref System.WinForms.Message m )
{
switch(m.msg)
{
case WM_Lbutton :
string message = string.Format("收到消息!参数为:{0},{1}",m.wParam,m.lParam);
MessageBox.Show(message);
break;
default:
base.DefWndProc(ref m);//调用基类函数处理非自定义消息。
break;
}
}
其实,C#中的事件也是通过封装系统消息来实现的,如果你在WndProc函数中不处理该消息
那么,它会被交给系统来处理该消息,系统便会通过代理来实现鼠标单击的处理函数,因此你可以通过
WndProc函数来拦截消息,比如你想拦截某个按钮的单击消息
//---------------------
消息实现进程间通信
//下面实例 启动一个进程 然后发送消息 到该进程
ProcessStartInfo startInfo = new ProcessStartInfo();
Process pro = new Process();
private void Form1_Load(object sender, EventArgs e)
{
startInfo.FileName = @"F:\Projects\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe";
pro.StartInfo = startInfo;
}
private void button1_Click(object sender, EventArgs e)
{
pro.Start();
}
private void button2_Click(object sender, EventArgs e)
{
pro.Kill();
}
private void button3_Click(object sender, EventArgs e)
{
IntPtr hWnd = pro.MainWindowHandle;
int data = Convert.ToInt32(this.textBox1.Text);
SendMessage(hWnd, 0x0100, data, 0);
SendMessage(hWnd, Message.WM_TEST, 300, 300);
}
//与PostMessage()区别
BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
PostMessage 是异步的,SendMessage 是同步的。
PostMessage 只把消息放入队列,不管消息是否被处理就返回,消息可能不被处理;而 SendMessage 等待消息被处理完了之后才返回,如果消息不被处理,发送消息的线程将一直被阻塞。
19>:protected override void WndProc(ref Message m) //窗口消息
对于处理所有消息.net 提供了wndproc进行重写
WndProc(ref Message m)
protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_CLOSE = 0xF060;
if (m.Msg == WM_SYSCOMMAND && (int) m.WParam == SC_CLOSE)
{
// 屏蔽传入的消息事件
this.WindowState = FormWindowState.Minimized;
return;
}
base.WndProc(ref m);
}
20>:protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, System.Windows.Forms.Keys keyData)//重新实现Form的键盘消息
protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, System.Windows.Forms.Keys keyData)
{
int WM_KEYDOWN = 256;
int WM_SYSKEYDOWN = 260;
if (msg.Msg == WM_KEYDOWN | msg.Msg == WM_SYSKEYDOWN)
{
switch (keyData)
{
case Keys.Escape:
this.Close();//Esc退出
break;
}
}
return false;
}
21>:实现屏幕截取
可以利用Graphics类的CopyFromScreen方法来实现屏幕截取,舍弃了比较麻烦的API函数,只要几句代码就能实现了,而且这个这个方法能实现只抓取部分区域的屏幕,可以实现类似qq抓屏的功能。
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
//获得当前屏幕的分辨率
Screen scr = Screen.PrimaryScreen;
Rectangle rc = scr.Bounds;
int iWidth = rc.Width;
int iHeight = rc.Height;
//创建一个和屏幕一样大的Bitmap
Image myImage = new Bitmap(iWidth, iHeight);
//从一个继承自Image类的对象中创建Graphics对象
Graphics g = Graphics.FromImage(myImage);
//抓屏并拷贝到myimage里
g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(iWidth, iHeight));
//保存为文件
myImage.Save(@"c:/1.jpeg");
}
}
}
22>:鼠标钩子使用
获取鼠标位置处窗口句柄,需要使用到Win32Api函数WindowFromPoint,用来根据坐标获取窗口句柄,C#引用如下:
[DllImport("user32.dll", EntryPoint = "WindowFromPoint")]//指定坐标处窗体句柄
public static extern int WindowFromPoint(
int xPoint,
int yPoint
);
只要能够获取鼠标的位置,然后调用该函数就可以得到窗口句柄。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
MouseHook mh;
private void Form1_Load(object sender, EventArgs e)
{
//安装鼠标钩子
mh = new MouseHook();
mh.SetHook();
mh.MouseMoveEvent += mh_MouseMoveEvent;
}
void mh_MouseMoveEvent(object sender, MouseEventArgs e)
{ //当前鼠标位置
int x = e.Location.X;
int y = e.Location.Y;
lb_p.Text = string.Format("({0},{1})", x, y);
int hwnd = Win32Api.WindowFromPoint(x, y);//获取指定坐标处窗口的句柄
lb_h.Text = hwnd.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
mh.UnHook();
}
}
23>:键盘钩子使用
使用钩子之前,需要使用SetWindowsHookEx()函数创建钩子,使用完毕之后要UnhookWindowsHookEx()函数卸载钩子,“钩”到消息后操作系统会自动调用在创建钩子时注册的回调函数来处理消息,处理完后调用CallNextHookEx()函数等待或处理下一条消息。
对于键盘钩子,钩子类型为WH_KEYBOARD_LL=13,只需要设置SetWindowsHookEx的idHook参数为13即可“钩”到键盘消息。关于钩子类型的资料见参考资料--钩子类型。
public class Win32Api
{
#region 常数和结构
public const int WM_KEYDOWN = 0x100;
public const int WM_KEYUP = 0x101;
public const int WM_SYSKEYDOWN = 0x104;
public const int WM_SYSKEYUP = 0x105;
public const int WH_KEYBOARD_LL = 13;
[StructLayout(LayoutKind.Sequential)] //声明键盘钩子的封送结构类型
public class KeyboardHookStruct
{
public int vkCode; //表示一个在1到254间的虚似键盘码
public int scanCode; //表示硬件扫描码
public int flags;
public int time;
public int dwExtraInfo;
}
#endregion
#region Api
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
//安装钩子的函数
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
//卸下钩子的函数
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
//下一个钩挂的函数
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
[DllImport("user32")]
public static extern int ToAscii(int uVirtKey, int uScanCode, byte[] lpbKeyState, byte[] lpwTransKey, int fuState);
[DllImport("user32")]
public static extern int GetKeyboardState(byte[] pbKeyState);
[DllImport("kernel32.dll", CharSet = CharSet.Auto,CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
#endregion
添加新建类KeyboardHook,封装键盘钩子,代码如下:
public class KeyboardHook
{
int hHook;
Win32Api.HookProc KeyboardHookDelegate;
public event KeyEventHandler OnKeyDownEvent;
public event KeyEventHandler OnKeyUpEvent;
public event KeyPressEventHandler OnKeyPressEvent;
public KeyboardHook() { }
public void SetHook()
{
KeyboardHookDelegate = new Win32Api.HookProc(KeyboardHookProc);
Process cProcess = Process.GetCurrentProcess();
ProcessModule cModule = cProcess.MainModule;
var mh = Win32Api.GetModuleHandle(cModule.ModuleName);
hHook = Win32Api.SetWindowsHookEx(Win32Api.WH_KEYBOARD_LL, KeyboardHookDelegate, mh, 0);
}
public void UnHook()
{
Win32Api.UnhookWindowsHookEx(hHook);
}
private List<Keys> preKeysList = new List<Keys>();//存放被按下的控制键,用来生成具体的键
private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
//如果该消息被丢弃(nCode<0)或者没有事件绑定处理程序则不会触发事件
if ((nCode >= 0) && (OnKeyDownEvent != null || OnKeyUpEvent != null || OnKeyPressEvent != null))
{
Win32Api.KeyboardHookStruct KeyDataFromHook = (Win32Api.KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(Win32Api.KeyboardHookStruct));
Keys keyData = (Keys)KeyDataFromHook.vkCode;
//按下控制键
if ((OnKeyDownEvent != null || OnKeyPressEvent != null) && (wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN))
{
if (IsCtrlAltShiftKeys(keyData) && preKeysList.IndexOf(keyData) == -1)
{
preKeysList.Add(keyData);
}
}
//WM_KEYDOWN和WM_SYSKEYDOWN消息,将会引发OnKeyDownEvent事件
if (OnKeyDownEvent != null && (wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN))
{
KeyEventArgs e = new KeyEventArgs(GetDownKeys(keyData));
OnKeyDownEvent(this, e);
}
//WM_KEYDOWN消息将引发OnKeyPressEvent
if (OnKeyPressEvent != null && wParam == Win32Api.WM_KEYDOWN)
{
byte[] keyState = new byte[256];
Win32Api.GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (Win32Api.ToAscii(KeyDataFromHook.vkCode, KeyDataFromHook.scanCode, keyState, inBuffer, KeyDataFromHook.flags) == 1)
{
KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
OnKeyPressEvent(this, e);
}
}
//松开控制键
if ((OnKeyDownEvent != null || OnKeyPressEvent != null) && (wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP))
{
if (IsCtrlAltShiftKeys(keyData))
{
for (int i = preKeysList.Count - 1; i >= 0; i--)
{
if (preKeysList[i] == keyData) { preKeysList.RemoveAt(i); }
}
}
}
//WM_KEYUP和WM_SYSKEYUP消息,将引发OnKeyUpEvent事件
if (OnKeyUpEvent != null && (wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP))
{
KeyEventArgs e = new KeyEventArgs(GetDownKeys(keyData));
OnKeyUpEvent(this, e);
}
}
return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);
}
//根据已经按下的控制键生成key
private Keys GetDownKeys(Keys key)
{
Keys rtnKey = Keys.None;
foreach (Keys i in preKeysList)
{
if (i == Keys.LControlKey || i == Keys.RControlKey) { rtnKey = rtnKey | Keys.Control; }
if (i == Keys.LMenu || i == Keys.RMenu) { rtnKey = rtnKey | Keys.Alt; }
if (i == Keys.LShiftKey || i == Keys.RShiftKey) { rtnKey = rtnKey | Keys.Shift; }
}
return rtnKey | key;
}
private Boolean IsCtrlAltShiftKeys(Keys key)
{
if (key == Keys.LControlKey || key == Keys.RControlKey || key == Keys.LMenu || key == Keys.RMenu || key == Keys.LShiftKey || key == Keys.RShiftKey) { return true; }
return false;
}
}
在主窗体中添加代码,如下:
public MainForm()
{
InitializeComponent();
}
KeyboardHook kh;
private void Form1_Load(object sender, EventArgs e)
{
kh = new KeyboardHook();
kh.SetHook();
kh.OnKeyDownEvent += kh_OnKeyDownEvent;
}
void kh_OnKeyDownEvent(object sender, KeyEventArgs e)
{
if (e.KeyData == (Keys.S | Keys.Control)) { this.Show(); }//Ctrl+S显示窗口
if (e.KeyData == (Keys.H | Keys.Control)) { this.Hide(); }//Ctrl+H隐藏窗口
if (e.KeyData == (Keys.C | Keys.Control)) { this.Close(); }//Ctrl+C 关闭窗口
if (e.KeyData == (Keys.A | Keys.Control | Keys.Alt)) { this.Text = "你发现了什么?"; }//Ctrl+Alt+A
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
kh.UnHook();
}
}
24>: 复制粘贴功能 C# 操作Clipboard
C#定义了一个类System.Windows.Forms.Clipboard来简化剪切板操作,这个类有一个静态方法,主要有:
Clear 清除剪切板中的所有数据;
ContainsData,ContainsAudio,ContainsFlieDropList,ContainsText,ContainsImage,用于检查剪切板中是否存在相应的数据;
GetAudioStream,GetDataObject,GetText,GetImage,GetFileDropList,用于取得数据;
SetAudio,SetDataObject,SetText,SetImage,SetFileDropList,用于添加数据;
// 这里判断是否有数据被复制
object clipboardData = Clipboard.GetData("userEntites");
this.btnPaste.Enabled = (clipboardData != null);
//复制
private void btnCopy_Click(object sender, EventArgs e)
{
// 读取数据
List<BaseUserEntity> userEntites = new List<BaseUserEntity>();
for (int i=0; i<this.DTUser.Rows.Count; i++)
{
BaseUserEntity userEntity = new BaseUserEntity(this.DTUser.Rows[i]);
userEntites.Add(userEntity);
}
// 复制到剪切板
Clipboard.SetData("userEntites", userEntites);
this.btnPaste.Enabled = true;
}
//粘贴
private void btnPaste_Click(object sender, EventArgs e)
{
object clipboardData = Clipboard.GetData("userEntites");
if (clipboardData != null)
{
List<BaseUserEntity> userEntites = (List<BaseUserEntity>)clipboardData;
string[] addUserIds = new string[userEntites.Count];
for (int i = 0; i < userEntites.Count; i++)
{
addUserIds[i] = userEntites[i].Id.ToString();
}
// 添加用户到角色
ServiceManager.Instance.RoleService.AddUserToRole(this.UserInfo, this.TargetRoleId, addUserIds);
// 加载窗体
this.FormOnLoad();
// 设置按钮状态
this.SetControlState();
}
}
25>:C#中List.ForEach 方法是对 List 的每个元素执行指定操作。
eg:
namespace AppExample
{
class Program
{
static void Main(string[] args)
{
List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
numbers.Add(4);
numbers.Add(5);
numbers.Add(6);
//numbers.ForEach(Display);
//numbers.ForEach(number =>
//{
// Console.WriteLine(number);
//});
numbers.ForEach(delegate(int number)
{
Console.WriteLine(number);
});
}
static void Display(int number)
{
Console.WriteLine(number);
}
}
}
26>:C# 监测某个文件夹内是否有文件增加
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.IO;
using System.Diagnostics;
using System.Xml;
namespace WindowsFormsApplication1
{
public partial class FSWControl : Form
{
static FileSystemWatcher watcher = new FileSystemWatcher();
public FSWControl()
{
InitializeComponent();
string StrPath = ReadrXML("watchdirectory", "savedirectory");
WatcherStrat(StrPath,"*.*",true,true);
}
/// <summary>
/// 初始化监听
/// </summary>
/// <param name="StrWarcherPath">需要监听的目录</param>
/// <param name="FilterType">需要监听的文件类型(筛选器字符串)</param>
/// <param name="IsEnableRaising">是否启用监听</param>
/// <param name="IsInclude">是否监听子目录</param>
private static void WatcherStrat(string StrWarcherPath, string FilterType, bool IsEnableRaising, bool IsInclude)
{
//初始化监听
watcher.BeginInit();
//设置监听文件类型
watcher.Filter = FilterType;
//设置是否监听子目录
watcher.IncludeSubdirectories = IsInclude;
//设置是否启用监听?
watcher.EnableRaisingEvents = IsEnableRaising;
//设置需要监听的更改类型(如:文件或者文件夹的属性,文件或者文件夹的创建时间;NotifyFilters枚举的内容)
watcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Security | NotifyFilters.Size;
//设置监听的路径
watcher.Path = StrWarcherPath;
//注册创建文件或目录时的监听事件
watcher.Created += new FileSystemEventHandler(watch_created);
//注册当指定目录的文件或者目录发生改变的时候的监听事件
watcher.Changed += new FileSystemEventHandler(watch_changed);
//注册当删除目录的文件或者目录的时候的监听事件
watcher.Deleted += new FileSystemEventHandler(watch_deleted);
//当指定目录的文件或者目录发生重命名的时候的监听事件
watcher.Renamed += new RenamedEventHandler(watch_renamed);
//结束初始化
watcher.EndInit();
}
/// <summary>
/// 创建文件或者目录时的监听事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_created(object sender, FileSystemEventArgs e)
{
//事件内容
}
/// <summary>
/// 当指定目录的文件或者目录发生改变的时候的监听事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_changed(object sender, FileSystemEventArgs e)
{
//事件内容
}
/// <summary>
/// 当删除目录的文件或者目录的时候的监听事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_deleted(object sender, FileSystemEventArgs e)
{
//事件内容
}
/// <summary>
/// 当指定目录的文件或者目录发生重命名的时候的事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void watch_renamed(object sender, RenamedEventArgs e)
{
//事件内容
}
/// <summary>
/// 启动或者停止监听
/// </summary>
/// <param name="IsEnableRaising">True:启用监听,False:关闭监听</param>
private void WatchStartOrSopt(bool IsEnableRaising)
{
watcher.EnableRaisingEvents = IsEnableRaising;
}
//-------------------
新建一个Console应用程序,项目名称为“FileSystemWatcher”,Copy代码进,编译后就可以用了。代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Permissions;
namespace MyFileSystemWatcher
{
public class Watcher
{
public static void Main(string[] args)
{
Run();
}
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public static void Run()
{
string[] args = System.Environment.GetCommandLineArgs();
if (args.Length != 2)
{
Console.WriteLine("使用方式: FileSystemWatcher.exe DirectoryPath");
return;
}
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = args[1];
/* 设置为监视 LastWrite 和 LastAccess 时间方面的更改,以及目录中文本文件的创建、删除或重命名。 */
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// 只监控.txt文件
watcher.Filter = "*.txt";
// 添加事件处理器。
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
// 开始监控。
watcher.EnableRaisingEvents = true;
// 输入q推出程序。
Console.WriteLine("按 \'q\' 推出程序。");
while (Console.Read() != 'q') ;
}
// 定义事件处理器。
private static void OnChanged(object source, FileSystemEventArgs e)
{
//如果更改、创建或删除文件,文件路径将被输出到控制台。
Console.WriteLine("文件: " + e.FullPath + " " + e.ChangeType);
}
private static void OnRenamed(object source, RenamedEventArgs e)
{
// 在文件重命名后,旧路径和新路径都输出到控制台。
Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
}
}
}
27>:C#winform动态添加控件
//查找页面名为myButton的控件
Button Button = (Button)Page.FindControl("myButton");
一般在loaded事件中处理
private void button1_Click(object sender, EventArgs e)
{
int num = int.Parse(textBox1.Text);
for (int i = 0; i < num; i++)
{
TextBox tb = new TextBox();
//设定位置
tb.Top = 50 + i * 30;
//添加控件
this.Controls.Add(tb);
}
}
比如button
button btn=new button();//初始化一个控件
btn.size=new size(100,100);//设置大小
btn.location=new postion(50,100);//设置坐标
btn.text="按钮1";//设置文本
this.controls.add(btn);//添加到窗体中
btn.Click += new System.EventHandler(btn_click);//将按钮的方法绑定到按钮的单击事件中b.Click是按钮的单击事件
下面我们来讲如何对新建的按钮添加对应的事件方法btn_click():
private void btn_click(object sender, System.EventArgs e)
{
Button b1 = (Button)sender;//将触发此事件的对象转换为该Button对象
MessageBox.Show(""+b1.Name);
}
28>:FlowLayoutPanel
FlowLayoutPanel是一个用于自动排列控件的panel,它会自动的排列在它里面的控件,默认是从左到右,从上到下,用这个控件,就是为了方便不用自己写代码控制button的位置
表示一个沿着水平或垂直方向动态排放其内容的面板。
FlowLayoutPanel是流式布局控件,添加到它上面的控件会按设定顺序依次排列,并且不支持拖拽到特定位置
29>:如何在winform中动态添加或删除控件
Controls.Add(new Label() { Size = new Size(100, 100), Text = "I'm new", name = "newlabel" });
Controls.Remove("newlabel");
Controls.Clear();
//添加控件
TextBox txt = new TextBox();
txt.Text = "我是自动添加的";
txt.Location = new Point(10, 10);
this.Controls.Add(txt);
//移除控件
this.Controls.Remove(txt);
txt.Dispose();
Label lb1 = Panel1.Controls[name] as Label;
31>:C# 中多线程 更新界面方法:
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。
正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。
而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而已,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。
举个简单例子说明下使用方法,比如你在启动一个线程,在线程的方法中想更新窗体中的一个TextBox..
using System.Threading;
//启动一个线程
Thread thread=new Thread(new ThreadStart(DoWork));
thread.Start();
//线程方法
private void DoWork()
{
this.TextBox1.Text="我是一个文本框";
}
如果你像上面操作,在VS2005或2008里是会有异常的...
正确的做法是用Invoke\BeginInvoke
using System.Threading;
namespace test
{
public partial class Form1 : Form
{
public delegate void MyInvoke(string str1,string str2);
public Form1()
{
InitializeComponent();
}
public void DoWork()
{
MyInvoke mi = new MyInvoke(UpdateForm);
this.BeginInvoke(mi, new Object[] {"我是文本框","haha"});
}
public void UpdateForm(string param1,string parm2)
{
this.textBox1.Text = param1+parm2;
}
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();
}
}
}
32>:Invoke方法
在用.NET Framework框架的WinForm构建GUI程序界面时,如果要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击之后按钮上的文本显示“关闭”,初学者往往会想当然地这么写:
void ButtonOnClick(object sender,EventArgs e)
{
button.Text="关闭";
}
这样的写法运行程序之后,可能会触发异常,异常信息大致是“不能从不是创建该控件的线程调用它”。注意这里是“可能”,并不一定会触发该种异常。造成这种异常的原因在于,控件是在主线程中创建的(比如this.Controls.Add(...);),进入控件的事件响应函数时,是在控件所在的线程,并不是主线程。在控件的事件响应函数中改变控件的状态,可能与主线程发生线程冲突。如果主线程正在重绘控件外观,此时在别的线程改变控件外观,就会造成画面混乱。不过这样的情况并不总会发生,如果主线程此时在重绘别的控件,就可能逃过一劫,这样的写法可以正常通过,没有触发异常。
正确的写法是在控件响应函数中调用控件的Invoke方法(其实如果大家以前用过C++ Builder的话,也会找到类似Invoke那样的激活到主线程的函数)。Invoke方法会顺着控件树向上搜索,直到找到创建控件的那个线程(通常是主线程),然后进入那个线程改变控件的外观,确保不发生线程冲突。正确写法的示例如下:
void ButtonOnClick(object sender,EventArgs e)
{
button.Invoke(new EventHandler(delegate
{
button.Text="关闭";
}));
}
Invoke方法需要创建一个委托。你可以事先写好函数和与之对应的委托。不过,若想直观地在Invoke方法调用的时候就看到具体的函数,而不是到别处搜寻的话,上面的示例代码是不错的选择。
这样的写法有一个烦人的地方:对不同的控件写法不同。对于TextBox,要TextBoxObject.Invoke,对于Label,又要LabelObject.Invoke。有没有统一一点的写法呢?
主窗口类本身也有Invoke方法。如果你不想对不同的控件写法不一样,可以全部用this.Invoke:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new EventHandler(delegate
{
button.Text="关闭";
}));
}
在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new Action(()=>
{
button.Text="关闭";
}));
}
33>:任务 Task
对于多线程,我们经常使用的是Thread。在我们了解Task之前,如果我们要使用多核的功能可能就会自己来开线程,然而这种线程模型在.net 4.0之后被一种称为基于“任务的编程模型”所冲击,因为task会比thread具有更小的性能开销,不过大家肯定会有疑惑,任务和线程到底有什么区别呢?
任务和线程的区别:
1、任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行。
2、任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。
-----
创建Task
创建Task的方法有两种,一种是直接创建——new一个出来,一种是通过工厂创建。下面来看一下这两种创建方法:
//第一种创建方式,直接实例化
var task1 = new Task(() =>
{
//TODO you code
});
//第二种创建方式,工厂创建
var task2 = Task.Factory.StartNew(() =>
{
//TODO you code
});
代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TaskDemo
{
class Program
{
static void Main(string[] args)
{
var task1 = new Task(() =>
{
Console.WriteLine("Hello,task");
});
task1.Start();
var task2 = Task.Factory.StartNew(() =>
{
Console.WriteLine("Hello,task started by task factory");
});
Console.Read();
}
}
}
这里我分别用两种方式创建两个task,并让他们运行。可以看到通过构造函数创建的task,必须手动Start,而通过工厂创建的Task直接就启动了。
34>:
35>: