针对近期写的一个C#程序对一些不太了解的知识进行简单总结。
1.打开多个文件
与打开单个文件使用OpenFileDialog类似,对于打开多个文件,只需要设置OpenFileDialog的Multiselect
属性为true即可。
VS的描述信息如下:
这是一个bool类型的属性,当为true时表示支持文件多选,false表示不支持多选。此外,还需要接收多个文件路径。
在之前打开单个文件时,一般通过直接获取FileName
属性来得到路径。在多选的时候则需要通过FileNames
来获取。
获取到的是个string类型的数组。因此我们需要事先建立一个string数组用于接收文件路径。部分代码如下:
String[] inputFiles;
OpenFileDialog open = new OpenFileDialog();
open.Multiselect = true;
open.Title = "请选择文件";
open.Filter = "JPG(*.jpg)|*.jpg|PNG(*.png)|*.png";
if (open.ShowDialog() == DialogResult.OK)
{
inputFiles = open.FileNames;
}
else
{
return;
}
2.以字节方式读取文件
在C#中可以通过Stream和字节数组来读取文件的字节,具体代码如下:
Stream stream = File.OpenRead(inputfiles[i]);
int length = (int)stream.Length;
byte[] buffer = new byte[length];
stream.Read(buffer, 0, length);
在使用这些之前别忘了添加引用using System.IO;
。
这几行代码表示的意思是,首先,通过File中的OpenRead函数打开inputfiles[i]
指向的文件,
并将结果返回给流。然后获取流的长度并将其转换为int型。基于流的长度新建一个byte数组。
最后通过Stream的Read()函数将文件内容以字节形式读取到buffer数组中来。这样便完成了文件的读取。
3.字节数组读取
(1)单字节解析
在上面我们已经读取了字节数组,现在如需要对某个字节进行输出或转换,可以直接利用ToString()
函数完成。
如对buffer[0]进行输出,只需要如下代码即可:
buffer[0].ToString();
则会以十进制来输出该字节,这里读取的结果为137。当然也可以以十六进制输出,则可以在ToString()
函数中
增加格式字符串来格式化输出,如“X”表示十六进制输出,“X2”表示将输出对齐为2位。若x为小写,则输出的字母也为小写。
假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定”X2”,这样显示出来就是:0x0A、0x1A。
(2)多字节解析
对于一个占两个字节的数据,我们可以通过将两个字节转换成十六进制再拼成字符串,便能得到对应16进制表示。
String str = buffer[0].ToString("X2") + buffer[1].ToString("X2");
如读取某文件,校验码为文件的前两位字节,因此我们可以依次读取字节数组的前两个元素,再拼成字符串便能得到校验码。 在这里第一个字节对应的十六进制表示为“EB”,第二个是“90”。因此校验码对应的字符串即为“EB90”。
(3)多字节转int
在获取了多字节对应的十六进制string后,我们可以利用int类型的parse()
函数实现转换。
String time = UInt32.Parse(buffer[i].ToString("X2") + buffer[i+1].ToString("X2") + buffer[i+2].ToString("X2") + buffer[i+3].ToString("X2"), System.Globalization.NumberStyles.HexNumber).ToString();
上述代码解析了一个四个字节的整型变量,然后再将其变成字符串赋给time
。对于Parse()函数,说明如下:
(4)多字节转float
对于多个字节转float与之前有些不太一样。需要用到BitConverter
这个类,用于将基础数据类型与字节数组相互转换。
byte[] bytetemp = new byte[4];
bytetemp[0] = buffer[i];
bytetemp[1] = buffer[i+1];
bytetemp[2] = buffer[i+2];
bytetemp[3] = buffer[i+3];
float ftemp = BitConverter.ToSingle(bytetemp, 0);
首先建立了一个临时的数组用于存放4个字节。然后调用ToSingle()
函数将4个字节转换成对应的float类型。
第一个参数为要转换的数组,第二个参数为起始偏移量。
4.多线程
在WinForm中与Android类似,如果在UI线程中进行了大量计算,那么这些大量的计算就会阻塞UI线程。直观的反映就是造成界面假死,
待计算完成后又重新恢复。解决这个问题可以效仿Android编程思路,再开一个子线程专门用于计算,形成“UI线程+子线程”的模式。
UI线程只负责对计算进度的更新和结果的显示。同时与Android类似的是,在子线程中操作UI组件会被认为是不安全的而被阻止。
在Android中是利用Handler解决。但在C#中并没有Handler。在C#中采用托管的方式处理。
首先,我们在需要启动子线程的按钮的点击事件中这样写:
progressBar1.Maximum = inputfiles.Length;
Thread t1 = new Thread(new ThreadStart(SaveFunc));
t1.IsBackground = true;
t1.Start();
progressBar1用于显示完成进度。这里设置最大值为输入文件的个数。然后新建一个线程,使用之前需要using System.Threading;
。
其中SaveFunc()
是我们自定义的一个保存文件的函数。代码如下:
void SaveFunc()
{
Action<String> task = delegate(string n)
{
progressBar1.Value = Int16.Parse(n);
};
for (int j = 0; j < inputfiles.Length; j++)
{
//your code...
progressBar1.Invoke(task, new object[] { (j + 1).ToString() });
}
}
在循环中,调用了progressBar1的Invoke()
函数,每次执行后,便会将progressBar1的进度加一,直到100%。当然task内容还可以更加丰富:
Action<String> task = delegate(string n)
{
groupBox2.Text="处理进度 "+((float.Parse(n) / inputfiles.Length) * 100).ToString("f2") + "%";
label1.Text = "当前处理第 " + n + " 个, 共 " + inputfiles.Length + " 个";
progressBar1.Value = Int16.Parse(n);
};
这里给出了当前处理的文件序号、百分比以及进度条,效果如图所示。 这样便能简单实现不阻塞UI线程的计算了。
C#文件路径
(1)判断文件夹是否存在
在保存文件时,有时需要新建一个文件夹。这个时候可以使用Directory类判断(包含在System.IO)中。
if (!System.IO.Directory.Exists(savepath))
{
System.IO.Directory.CreateDirectory(savepath);
}
上述代码利用Exist()
函数判断savepath路径是否存在,如果不存在,就调用CreateDirectory()
函数创建一个。
(2)路径拼接、截取
例如我们打开的文件路径为”E:\test.png”。现在我们读取了1.png并对其进行了一系列处理得到了一幅新的影像。想将其 存放到指定路径如”E:\output\test.jpg”下。首先需要判断E盘中是否有output文件夹,如果没有需要新建一个。然后需要对 输入路径进行截取,截取最后一个“\”到“.”之间的字符串,也就是文件名。然后再拼接上”.bmp”。代码如下:
string savepath_img;
String savepath = inputfiles[0].Substring(0, inputfiles[0].LastIndexOf('\\'));
savepath += "\\output";
if (!System.IO.Directory.Exists(savepath))
{
System.IO.Directory.CreateDirectory(savepath);
}
savepath_img = savepath + inputfiles[j].Substring(inputfiles[j].LastIndexOf('\\'), inputfiles[j].LastIndexOf('.') - inputfiles[j].LastIndexOf('\\')) + ".bmp";
需要注意的是Substring()
中的参数。第一个参数为起始索引,第二个参数是截取长度,而不是结束索引,这点要注意。
本文作者原创,未经许可不得转载,谢谢配合