一、多余Using
程序首部的using很多没有使用(灰色),有什么影响?
在已经编译成功后,没有任何影响。它会影响编译时间和Intellisense/IDE响应能力。
二、Directory操作文件夹
File、Path、FileStream、StreamReader、StreamWriter
公开用于通过目录和子目录进行创建、移动和枚举的静态方法。
Direcory.CreateDirectory(string path)创建文件夹
Directory.Delete(string path) 删除文件夹(必须为空否则异常)
Directory.Delete(string path,bool recursive) 第二参为true强行删除
注意:删除操作是彻底删除。回收站是没有东西的。
Directory.Move(string scrDir,string desDir) 移动
Directory.GetFiles(string Path) 获取目录下所有文件,返回全路径的数组
Directory.GetFiles(string path,string searchPattern) 同上,指定匹配
Directory.GetDirectories(string path) 获取所有子目录,返回数组
Directory.GetDirectories(string path,string searchPattrern)同上,匹配
Directory.Exist(string path) 是否存在目录,返回bool
注意:File.Create()与File.CreateText()创建的文件,必须用返回值进行关闭
否则,在后面进行删除时会提示异常。
private static void Main(string[] args)
{
Directory.CreateDirectory(@"E:\a");
for (int i = 0; i < 6; i++)
{
Directory.CreateDirectory(@"E:\a\" + i);
}
FileStream fs = File.Create(@"E:\a\cc.doc");
fs.Close();
for (int i = 1; i < 5; i++)
{
fs = File.Create(@"E:\a\" + i + ".txt");
fs.Close();
}
fs = File.Create(@"E:\a\0\dd.txt");
fs.Close();
string[] d = Directory.GetDirectories(@"E:\a");
foreach (string s in d) { Console.WriteLine(s); }
Directory.Move(@"E:\a\0", @"E:\a\7");//前面必须有fs.close否则文件未关闭前无法移动,异常
for (int i = 1; i < 6; i++)
{
Directory.Delete(@"E:\a\" + i);
}
Directory.Delete(@"E:\a\7", true);
string[] f = Directory.GetFiles(@"E:\a", "*.txt");
foreach (string s in f) { Console.WriteLine(s); }
Directory.Delete(@"E:\a", true);//同样创建的文件也必须关闭
Console.ReadKey();
}
三、Webbrowser 浏览器控件
Url Web浏览器控件导航到的URL
不懂问题的学习方法:
例如:
string s=textBox1.Text;
webBrowser1.Url = s;
在VS中,s出现红色错误:无法将string转为uri,鼠标指向等号左侧Url,提示是一个Uri类型.
新起一行,写上Uri,变成绿色,鼠标指向它提示Class,说明是一个类;接F12进入内部查看
Uri定义,如果代码是展开情况,就先折叠(Ctrl+M,Ctrl+O),这样查看就方便多了。
可以看到Uri类的定义,构造函数有三个,其中带string,就是我们需要的。里面还有
一些字段和方法,可以大体看一下。
还有一种直接对Uri用F1到MSDN中查看帮助。
回到前面构造函数: public Uri(string name) 这样就可以构造由string组成的Url.
string s=textBox1.Text;
webBrowser1.Url = new Uri(s);
特别小心:Url与Uri是不一样的
webBrowser1.Url = new Uri("http://" + textBox1.Text);
或者直接:
webBrowser1.Navigate("http://www.baidu.com");
小技巧:
当有时多添加了控件,要删除控件,或者修改控件名,或者删除控件事件等等,当删除后
返回切换到设计界面时,会提示错误,某行某错误,可以直接点进去,删除红线部分行。
另一个比较好的方法就是,当在界面删除控件后,在菜单中:生成->重新生成解决方法,
查看下面的错误提示,对应删除相关问题,再一次:生成->重新生成解决方法,直到没有红色
错误提示。
前面的方法有时会使整个程序崩溃,只有重建程序。后者目前还没发现过崩溃,比较高效
安全。
四、ComboBox下拉条
添加下拉数据:Items集合
comboBox1.Items.Add("张三");
comboBox1.Items.Add("李四");
comboBox1.Items.Add("王五");
comboBox1.Items.Add("张三")
删除数据:
comboBox1.Items.Remove("张三");//删除第一个张三,无张三不会有错
comboBox1.Items.RemoveAt(1); //删除索引1的项,无该索引出错.
下拉样式DropDownStyle
DropDown 下拉样式(默认),可修改Text属性.一行
Simple 展开样式,可修改Text属性.多行
DropDownList 下拉样式,不可修改Text属性.一行
控制下拉条的高度
先将IntegralHeight设置为false,以便下面设置生效:
comboBox1.Height = 10; //设置高度(像素)
comboBox1.MaxDropDownItems = 5;//设置最大显示项
练习题:三鼐下拉框,分别为年、月、日的选择。根据选择分别显示。
界面:
private void Form1_Load(object sender, EventArgs e)
{
cboYear.IntegralHeight = false;//这样才能控制高度
cboMonth.IntegralHeight = false;
cboDay.IntegralHeight = false;
cboYear.MaxDropDownItems = 12;
cboMonth.MaxDropDownItems = 12;
cboDay.MaxDropDownItems = 12;
int year = DateTime.Now.Year;
for (int i = year; i >= 1949; i--)//倒序符合人性
{
cboYear.Items.Add(i + "年");
}
}
private void cboMonth_SelectedIndexChanged(object sender, EventArgs e)
{
string strD = cboDay.Text.TrimEnd('日');
string y = cboYear.SelectedItem.ToString().TrimEnd('年');
string m = cboMonth.SelectedItem.ToString().TrimEnd('月');
int days = DateTime.DaysInMonth(Convert.ToInt32(y), Convert.ToInt32(m));
cboDay.Items.Clear();
for (int i = 1; i <= days; i++)
{
cboDay.Items.Add(i + "日");
}
if (strD != "")//切换月数时,保持日数
{
if (Convert.ToInt32(strD) > days) cboDay.Text = days + "日";
else cboDay.Text = strD + "日";
}
}
private void cboYear_SelectedIndexChanged(object sender, EventArgs e)
{
string strM = cboMonth.Text;
cboMonth.Items.Clear();
for (int i = 1; i < 13; i++)
{
cboMonth.Items.Add(i + "月");
}
cboMonth.Text = strM;//切换年时保持月份
}
五、ListBox列表框
selectedIndex 已选定项的索引
selectedItem 已经选定的项
练习:
列出文件夹中所有图片,点击其一则显示图片。
界面:
private List<string> lst = new List<string>();
private void Form1_Load(object sender, EventArgs e)
{
string[] p = Directory.GetFiles(@"E:\pic", "*.png");
for (int i = 0; i < p.Length; i++)
{
string f = Path.GetFileName(p[i]);
listBox1.Items.Add(f);
lst.Add(p[i]);
}
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
pictureBox1.Image = Image.FromFile(lst[listBox1.SelectedIndex]);
}
练习题:剪刀石头布,玩家与电脑玩
石头1,剪刀2,布3
玩家赢:1-2=-1,2-3=-1,3-1=2
玩家平:0
玩家胜:其余
界面:
构建三个类。玩家类:出拳方法。电脑类:出拳方法。电脑类:比较。
internal class Player
{
public int ShowFist(string fist)
{
switch (fist)
{
case "石头": return 1; break;
case "剪刀": return 2; break;
default: return 3; break;
}
}
}
电脑类,需要一个属性,以显示在lable上
internal class Computer
{
public string Fist
{
get; set;
}
public int ShowFist()
{
Random r = new Random();
int n = r.Next(1, 4);
switch (n)
{
case 1: Fist = "石头"; break;
case 2: Fist = "剪刀"; break;
default: Fist = "布"; break;
}
return n;
}
}
裁判类,加入枚举。
public enum Result
{
玩家胜,
电脑胜,
平手
}
internal class Judgment
{
public static Result Judge(int player, int computer)//静态
{
int n = player - computer;
switch (n)
{
case -1:
case 2:
return Result.玩家胜;
break;
case 0:
return Result.平手;
break;
default:
return Result.电脑胜;
break;
}
}
}
主程序,初始化后,用一个通用方法比较:
技巧:对于vs2022,发现里面可以写成通用方法时,选择这些代码右击->快速操作与重构。
弹出提示中选择:提取方法,回车,重命名方法,即得通用方法。
private void Form1_Load(object sender, EventArgs e)
{
lblComp.Text = "";
lblPlayer.Text = "";
lblResult.Text = "";
this.CenterToScreen();
}
private void btnStone_Click(object sender, EventArgs e)
{
string strFist = "石头";
GetResult(strFist);
}
private void btnCut_Click(object sender, EventArgs e)
{
string strFist = "剪刀";
GetResult(strFist);
}
private void btnCloth_Click(object sender, EventArgs e)
{
string strFist = "布";
GetResult(strFist);
}
private void GetResult(string strFist)
{
Player p = new Player();
int np = p.ShowFist(strFist);
lblPlayer.Text = strFist;
Computer c = new Computer();
int nc = c.ShowFist();
lblComp.Text = c.Fist;
lblResult.Text = Judgment.Judge(np, nc).ToString();
}
六、DialogBox对话框
1、OpenFileDialog 打开文件对话框
Title 对话框标题
Multiselect 可以多选文件bool,为true
InitialDirectory 打开的初始目录位置
Filter 打开文件的类型筛选,"文本文件|*.txt|媒体文件|*.wav|图片文件|*.jpg"
FileName 获得的文件,单个
FileNames 获得的文件,多个
练习:打开一个文本文件。
除去对话框外,读文件用两种:一个是StreamReader适合字符;另一个是FileStream适合所有。
本例主要说明读取大文件时的问题。
当用FileStream最后一次读取时,一般会出现不满字节数组buffer的问题,这里有一个返
回值count显示了读取的真实长度,如果小于规定的buffer的长度,说明这是最后一次了。如
果直接用buffer读取,则真实长度count后面的长度(buffer.length-count)就是倒数第二次读
取时填充的数据。
为了避免这种情况,应该在每次填充数据时进行清空Array.Clear(),这样每次字节数组
都是空的"\0"。但转换的s(s2)是由buffer而来,所以它是buffer.length的整数倍。
要查看就在最后次显示最后的数组。txtText中显示了最后一次最后几个字符的ASC码,所
以看到回车换行(13,10)以及最后的z(122)后面的结尾"\0".
界面:
代码:
private void Form1_Load(object sender, EventArgs e)
{
this.CenterToScreen();
}
private void btnBrowser_Click(object sender, EventArgs e)
{
dlgOpenFile.InitialDirectory = @"E:\";
dlgOpenFile.Filter = "文本文件|*.txt";
string s1 = "", s2 = "", s = "";
if (dlgOpenFile.ShowDialog() == DialogResult.OK)
{
string f = dlgOpenFile.FileName;
using (StreamReader rs = new StreamReader(f, Encoding.Default))
{
s1 = rs.ReadToEnd();
}
txtText1.Text = s1;
int count = 0;
using (FileStream fs = new FileStream(f, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[8];
do
{
Array.Clear(buffer, 0, buffer.Length);
count = fs.Read(buffer, 0, buffer.Length);
s = Encoding.Default.GetString(buffer);
if (count < buffer.Length)//显示最后一次读取的末尾数据
{
for (int i = 0; i < count; i++)
{
txtText3.Text += s[i].ToString();
}//count=6,,-121-13-10-122 13:回车,10:换行,超出部分的ASC为0
txtText3.Text += "-" + (int)s[2] + "-" + (int)s[3] + "-" + (int)s[4] + "-" + (int)s[5] + "-" + (int)s[6];
}
s2 += s;
} while (count == buffer.Length);
}
txtText2.Text = s2;
MessageBox.Show(count + "_" + s1.Length + "_" + txtText1.Text.Length + "_" + s2.Length + "_" + txtText2.Text.Length);
}
}
2、SaveFileDialog 保存文件对话框
Title 对话框标题
InitialDirectory 初始目录
Filter 类型筛选器
Filename 返回的文件的全路径,已经存在会提示覆盖
练习:保存textBox中的字符到文件.
界面:
代码:
private void button1_Click(object sender, EventArgs e)
{
dlgSave.Title = "请选择保存的文件";
dlgSave.InitialDirectory = @"E:\";
dlgSave.Filter = "文本文件|*.txt";
if (dlgSave.ShowDialog() == DialogResult.OK)
{
using (FileStream fs = new FileStream(dlgSave.FileName, FileMode.OpenOrCreate, FileAccess.Write))
{
byte[] buffer = Encoding.Default.GetBytes(textBox1.Text);
fs.Write(buffer, 0, buffer.Length);
}
}
}
3、FontDialog与ColorDialog 字体对话框与颜色对话框
一般使用对应的类,用ShowDialog与对象的赋值来操作。
练习:设置textBox的字体与颜色
界面:
代码:
private void button1_Click(object sender, EventArgs e)
{
FontDialog fd = new FontDialog();
fd.ShowDialog();
textBox1.Font = fd.Font;
}
private void button2_Click(object sender, EventArgs e)
{
ColorDialog cd = new ColorDialog();
cd.ShowDialog();
textBox1.ForeColor = cd.Color;
}
七、Panel容器
panel本身不显示,只作容器。visible显示时其内控件显示,否则隐藏。
练习:简单记事本。
文件:打开,保存;格式:颜色与字体;换行,最后一个历史记录。
历史记录:在每次打开时,在全局list<string> lst中保存全路径。这样查找历史记录list1时
双击selectedItem就可直接调用lst的全路径,从而打开文件到文本框中。
界面:
代码:
private List<string> lst = new List<string>();//装入各个历史打开文件全路径
private void Form1_Load(object sender, EventArgs e)
{
this.CenterToScreen();
panel1.Visible = false;
textBox1.WordWrap = false;
}
private void 字体ToolStripMenuItem_Click(object sender, EventArgs e)
{
FontDialog fd = new FontDialog();
fd.ShowDialog();
textBox1.Font = fd.Font;
}
private void 颜色ToolStripMenuItem_Click(object sender, EventArgs e)
{
ColorDialog cd = new ColorDialog();
cd.ShowDialog();
textBox1.ForeColor = cd.Color;
}
private void 自动换行ToolStripMenuItem_Click(object sender, EventArgs e)
{
if (自动换行ToolStripMenuItem.Text == "自动换行")
{
textBox1.WordWrap = true;
自动换行ToolStripMenuItem.Text = "取消换行";
}
else
{
textBox1.WordWrap = false;
自动换行ToolStripMenuItem.Text = "自动换行";
}
}
private void 显示ToolStripMenuItem_Click(object sender, EventArgs e)
{
panel1.Visible = true;
}
private void 隐藏ToolStripMenuItem_Click(object sender, EventArgs e)
{
panel1.Visible = false;
}
private void button1_Click(object sender, EventArgs e)
{
panel1.Visible = false;
}
private void 打开ToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = "打开文本文件";
ofd.InitialDirectory = @"E:\";
ofd.Filter = "文本文件|*txt|全部文件|*.*";
ofd.Multiselect = false;
if (ofd.ShowDialog() == DialogResult.OK)
{
string f = Path.GetFileName(ofd.FileName);
listBox1.Items.Add(f);
lst.Add(ofd.FileName);
OpenToText(ofd.FileName);
}
}
private void 保存ToolStripMenuItem_Click(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Title = "保存文件";
sfd.InitialDirectory = @"E:\1";
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() == DialogResult.OK)
{
using (FileStream fs = new FileStream(sfd.FileName, FileMode.OpenOrCreate, FileAccess.Write))
{
byte[] buffer = Encoding.Default.GetBytes(textBox1.Text);
fs.Write(buffer, 0, buffer.Length);
}
}
}
private void OpenToText(string f)
{
using (FileStream fs = new FileStream(f, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
int count = fs.Read(buffer, 0, buffer.Length);
textBox1.Text = Encoding.Default.GetString(buffer, 0, count);
}
}
private void listBox1_DoubleClick(object sender, EventArgs e)
{
string s = lst[listBox1.SelectedIndex];
OpenToText(s);
}
八、Process进程
1、关于进程与线程的形象比喻:
操作系统的设计,可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,
另一方面允许进程之间和线程之间共享资源。
CPU:承担所有计算任务,如同一座工作,时刻在运行。(内有多个车间)
进程:工厂的一个车间。电力有限时只能供一车间(进程)使用,表示单核CPU只能运行一个任务。
线程 :车间里的工人。一个车间(进程)可以有多个工人(线程).
车间的空间(进程的内存空间)是工人(线程)共享的。车间里工人们可共享的洗澡间,厕所等。
车间(进程)里有些房间只能容纳1个工人(线程),如厕所、澡堂等。只有使用完了,后面工人(线程)才能
使用。代表有些共享内存存在线程占用时,其它线程不得访问。
一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,
等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 MutEx),防止多个线程同时读写某一块内
存区域。
还有些房间,可以同时容纳n个工人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。
这好比某些内存区域,只能供给固定数目的线程使用。
这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现
钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore)。
信号量用来保证多个线程不会互相冲突。MutEx是semaphore的一种特殊情况(n=1时)。即,完全可以用后者
替代前者。但是,因为MutEx较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
信号量Semaphore:车间内可以同时进多人的多人间,门口挂n把开门钥匙,以此判断工人(线程)是否等待。
2、Process类
Process.GetProcess()
Kill() 强制终止基础进程。方法 Kill 强制终止进程,而CloseMainWindow 仅请求终止。当具有图形界面的
进程正在执行时,其消息循环处于等待状态。 每当操作系统将 Windows 消息发送到进程时,都会执
行消息循环。 调用 CloseMainWindow 发送关闭主窗口的请求,该请求在格式正确的应用程序中关闭
子窗口并撤消应用程序的所有正在运行的消息循环。 通过调用 CloseMainWindow 退出进程的请求不
会强制应用程序退出。 应用程序可以在退出前要求用户验证,也可以拒绝退出。 若要强制退出应用
程序,请使用 Kill 方法。
CloseMainWindow() 通过向进程的主窗口发送关闭消息来关闭拥有用户界面的进程。
Process.Star()
练习一:列举进程,调用常用程序
private static void Main(string[] args)
{
Process[] p = Process.GetProcesses();
foreach (Process p2 in p)
{
//p2.Kill();
Console.WriteLine(p2);
}
Process.Start("IExplore.exe");//IE
Process.Start("iexplore");
Process.Start("iexplore", "https://www.baidu.com");
Process.Start("notepad.exe");//记事本
Process.Start("notepad");
Process.Start("notepad", "E:\\1.txt");
Process.Start("calc"); //计算器
Process.Start("calc.exe");
Process.Start("mspaint");//画图
Console.ReadKey();
}
练习二:调用进程打开种类文件
private static void Main(string[] args)
{
//ProcessStartInfo psi = new ProcessStartInfo(@"E:\1.txt");
//Process p = new Process();
//p.StartInfo = psi;
//p.Start();
Process p = new Process();
p.StartInfo = new ProcessStartInfo(@"E:\1.txt");
p.Start();
Console.ReadKey();
}
九、线程
1、单线程的问题:假死。
VS中当运行程序时,会分配一个主线程来运行这个程序,主线程结束时,程序就结束了。
如果主线程忙于计算而“抽不出身”时,对窗体或控件就来不及响应,出现假死现象。
例:一窗体添加一button,内加代码,当点击button时,一直显示输出窗体,这时移动窗体
程序不会“响应”,因为它“忙”于显示去了。
小技巧:如何显示窗体中的Console.WriteLine()?
vs2022: 调试->窗口->输出,再运行程序,就可以看到输出窗口中的Console.WriteLine.
private void button1_Click(object sender, EventArgs e)
{
Test();
}
private void Test()
{
for (int i = 0; i < 1000000; i++)
{
Console.WriteLine(i);
}
}
主线程一个人在完成移动窗体、窗体大小变化、控件事件、程序运行、计算等等,只要有一个
任务比较“繁重”时,主线程就会“忙“不过来,对其它的响应“看似”不响应,就出现“假死”现象。
2、新创建一个线程
using System.Threading;
实例Thread类,并传入一个指向线程所要运行方法的委托。这时候线程已经产生,但还未用运行。
调用Thread实例的Start方法,并不是开始执行。它是所CPU说明我已经准备OK了,具体执行时间
由CPU决定,也许CPU很忙,就得等待了。
当关于主窗体时,可能会发现线程还在继续执行,Why?
线程分类:
1)前台线程:只有所有的前台线程都关闭,才能完成的程序关闭。
2)后台线程:只要所有的前台程序结束,后台线程自动马上结束。
默认情况下,创建的线程都是前台线程。虽然关闭了主窗体,但前台线程没有结束,所以程序还
没有关闭。所以还可以看到下面输出窗体的显示。
因为要避免上面线程还在继续的情况,可以把线程设置为后台线程,只要前台一结束,这个后台
的线程就会马上结束。
在.Net下,是不允许跨线程访问的。
取消跨线程的访问:
Control.CheckForIllegalCrossThreadCalls = false;//不检测跨线程
当线程在执行时,关闭主程序。主窗体中的控件释放,而线程仍然在访问主窗体里的控件。
这样会出现异常。
解决办法:在关闭主窗体时,对线程进行检测,如果不为null应该强行关闭线程,以防止
它对窗体控件的继续访问。即在FormClosing事件中写入判断。
Abort() 中止线程。中止完成后不能再重启Start()
t.Start();
t.Abort();
t.Start();//错误,不能重启
Name 线程名
Thread.CurrentThread 获得当前的线程引用
Thread.Sleep(1000) 将当前线程挂起指定的毫秒数。
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(Test);
t.IsBackground = true;//设置为后台线程
t.Start();
}
private void Test()
{
for (int i = 0; i < 100000; i++)
{//主线程textBox1不能被新线程访问
textBox1.Text = i.ToString();
}
}
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;//不检测跨线程
}