热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C#文件读写系列二

读取文件原则上非常简单,但它不是通过FileInfo和DirectoryInfo来完成的,关于FileInfo和DirectoryInfo请参考C#文件操作系列一,在.NetFra

读取文件原则上非常简单,但它不是通过FileInfo和DirectoryInfo来完成的,关于FileInfo和DirectoryInfo请参考C# 文件操作系列一,在.Net Framework4.5中可以通过File类来读写文件,在.Net Framework2.0推出之前,读写文件是相当费劲的,但是在.Net Framework2.0推出之后,它对File类进行了扩展,只要编写一行代码,就能对文件进行读写,下面通过一个窗体应用程序来展示文件的读写功能。

一、文件读取

1、通过File类的静态方法ReadAllText()进行文件的读取。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace FileReadWrite
{
public partial class Form1 : Form{public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){ReadFile();}protected void ReadFile(){if (string.IsNullOrEmpty(textBox1.Text)){MessageBox.Show("请输入文件路径!");}else{string res = File.ReadAllText(textBox1.Text,Encoding.Default);textBox2.Text = res;}}}
}

2、通过File类的静态方法ReadAllLines()进行文件的读取,代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace FileReadWrite
{
public partial class Form1 : Form{public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){ReadFile();}protected void ReadFile(){if (string.IsNullOrEmpty(textBox1.Text)){MessageBox.Show("请输入文件路径!");}else{string[] res = File.ReadAllLines(textBox1.Text,Encoding.Default);StringBuilder result = new StringBuilder();foreach (string re in res){result.Append(re);}textBox2.Text = result.ToString();}}}
}

3、通过File类的静态方法ReadAllBytes()进行文件的读取,代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace FileReadWrite
{
public partial class Form1 : Form{public Form1(){InitializeComponent();}private void button1_Click(object sender, EventArgs e){ReadFile();}protected void ReadFile(){if (string.IsNullOrEmpty(textBox1.Text)){MessageBox.Show("请输入文件路径!");}else{byte[] res = File.ReadAllBytes(textBox1.Text);StringBuilder sb = new StringBuilder();foreach (var b in res){sb.Append(b.ToString());}textBox2.Text = sb.ToString();}}}
}

 

二、文件写入

File提供了ReadAllBytes()、ReadAllLines()、ReadAllText()等读取文件的方式,所以对应的就会有相对应的写入文件的方式WriteAllBytes()、WriteAllLines()、WriteAllText()。

这里就不多做阐述.

 

三、通过流来操作文件

1、流的概念相信大家都不陌生,无论是哪种语言、哪种平台都会有流的存在,流是一个用于传输数据的对象,流可以双向传输,分为读取流和写入流。

a、读取流:数据从外部源传输到程序中

b、写入流:数据从程序传输到外部源中

外部源通常是一个文件,但也不都是一个文件,它也可能是:

a、网络上的数据(可通过一些网络协议进行读写)

.Net提供了一个System.Net.Sockets.NetworkStream类,可以通过它来处理网络数据。

b、读写到内存区域上的数据

.Net提供了一个System.IO.MemoryStream类,可以通过它来读取内存.

c、读写到命名管道上的数据

读写管道没有提供基本的流类,但是有一个泛型流类System.IO.Stream,如果要编写这样一个类,就可以从这个基类继承.

d、另一个计算机上发送的数据

e、外部源甚至可以代码中的一个变量,使用流在变量之间传输数据是一个常用的技巧,可以在数据类型之间转换数据。

使用一个独立的对象来传输数据,比使用FileInfo和DirectoryInfo类更好,应为把传输数据(名词)的概念和特定数据源分离开来,可以更容易的交换数据源。流对象本身包含许多通用的代码,可以在外部数据源和代码中的变量之间移动数据,把这些代码与特定数据源的概念区分开来,可以实现不同环境下代码的重用(通过继承).例如像StringReader和StringWriter,StreamReader和StreamWriter一样,都是同一继承树的一部分,这些类的后台一定共享很多的代码.

 

2、FileStream类

(1)、FileStream类的作用

a、这个类只要用于读取二进制文件中的二进制数据,当然也可以使用它读取任何文件,通常读取二进制文件要使用FileStream

b、FileStream对象实例表示在磁盘或网络路径上指向文件的流,这个类提供了在文件中读取字节的方法,但是经常使用StreamReader和StreamWriter来执行这些功能,因为FileStream操作的是字节和字节数组,而Stream类操作的是字符数据.

 

(2)、FileStream的构造方式

虽然.Net 提供了15种方式构建一个FileStream对象实例,但是构建一个FileStream对象实例,主要需要以下4条信息(也就是构造函数的参数):

a、要访问的文件

b、表示如何打开文件的模式。例如,新建一个文件或者打开一个现有的文件。如果打开一个现有的文件,写入操作是覆盖文件,还是追加到文件的末尾。

c、表示文件的访问方式------是只读,只写,还是读写?

d、共享访问------表示访问是否独占文件.如果允许其他流同时访问文件,则这些流是只读还是只写还是读写文件。

.Net提供的FileStream构造函数主要分为两类

一类是构造函数的第一个参数是一个文件的完整路径的字符串,其余的参数大致是FileMode、FileAcess、FileShare等...

一类是构造函数用老式的windows-api分格的windows句柄来处理文件...

本文主要用的是第一类,第一个参数是文件的完整路径的字符串的这一类的构造函数,构造形式如下:

接下来主要介绍如上构造函数中的三个参数:FileMode、FileAcess、FileShare

a、FileAccess枚举

注意,该枚举有一个Flags特性标签,说明FileAccess的枚举值可以进行按位的"|"或者按位的"&"运算,关于这个不清楚,请参考C# 特性(Attribute)之Flag特性

b、FileMode枚举

namespace System.IO
{
using System;using System.Runtime.InteropServices;[Serializable, ComVisible(true)]public enum FileMode{/** 创建新文件,此操作需要 System.Security.Permissions.FileIOPermissionAccess.Write权限* 如果文件已存在,将引发System.IO.IOException*/CreateNew &#61; 1,/** 指定操作系统打开现有文件,文件一旦打开,就会截断成零字节大小* 此操作需要 System.Security.Permissions.FileIOPermissionAccess.Write* 尝试读取通过Truncate打开文件将导致异常.*/Truncate &#61; 5,/** 指定操作系统创建新文件.如果文件已存在,那么它将被覆盖* 此操作需要 System.Security.Permissions.FileIOPermissionAccess.Write* FileMode.Create<&#61;>相当于如果文件不存在则使用System.IO.FileMode.CreateNew* 如果文件存在,则使用System.IO.FileMode.Truncate.* 注:如果文件已存在但是为隐藏文件,则将引发System.UnauthorizedAccessException*/Create &#61; 2,/** 若文件存在,则找到文件并定位到文件结尾.如果文件不存在,则创建一个新文件* FileMode.Append只能与FileAccess.Write一起使用.尝试查找文件尾之前的位置会引发System.IO.IOException* 并且任何尝试读取的操作都会失败并引发 System.NotSupportedException*/Append &#61; 6,/** 指定操作系统打开现有的文件,打开文件的能力取决于System.IO.FileAccess.* 如果文件不存在,则引发System.IO.FileNotFoundException.*/Open &#61; 3,/** 指定操作系统应打开文件.打开文件的能力取决于 System.IO.FileAccess 所指定的值.* 如果该文件不存在,则引发System.IO.FileNotFoundException.*/OpenOrCreate &#61; 4,}
}

c、FileShare枚举

这个枚举主要实现的是"文件读写锁"的功能,在开发过程中,我们往往需要大量读写文件的操作,在本地往往能完美运行(单线程),但是项目上线后,就会出现一系列的问题.(.Net本身是多线程环境),下面简单列举一些在多线程环境中会出现的问题.

i、写入一些内容到一个文件中,另一个线程/进程要读取文件的内容时报异常,提示System.IO.IOException:文件真由另一进程使用,因此该进程无法访问该文件.

ii、和上面i的顺序相反,在对一个文件进行读操作时,此时另一个线程/进程向该文件进行追加内容操作,也会报i中的异常.

iii、对一个文件进行简单的读写操作后,想删除文件,依然报上述的错误.

出现上述异常有两种可能的原因

第一种是对文件进行完一个操作后,没有进行资源的释放

第二种是当进行完一个操作后,可能要到某个时刻才真正结束,亦或是需要指定某个触发机制,操作才会真正结束

下面开始逐个解析FileShare的每个枚举值

// Generated by .NET Reflector from C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
namespace System.IO
{
using System;using System.Runtime.InteropServices;[Serializable, Flags, ComVisible(true)]public enum FileShare{/** 谢绝共享当前文件.文件关闭前,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常*/None &#61; 0,/** 允许随后打开文件读取,如果未指定此标记,则文件关闭前,,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常* 但是,即使指定了该标记,仍可能需要附加权限才能够访问该文件.*/Read &#61; 1,/** 允许随后打开文件写入,如果未指定此标记,则文件关闭前,,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常* 但是,即使指定了该标记,仍可能需要附加权限才能够访问该文件.*/Write &#61; 2,/** 允许随后打开文件写入或读取,如果未指定此标记,则文件关闭前,,打开该文件的任何请求(由此进程或者另一进程)都将抛出异常* 但是,即使指定了该标记,仍可能需要附加权限才能够访问该文件.*/ReadWrite &#61; 3,/** 允许随后删除文件*/Delete &#61; 4,/** 使文件可由子进程继承,Win32不直接支持此功能.*/Inheritable &#61; 0x10}
}

在讲解各个枚举值之前,先提供两个方法,用于测试,一个是写文件的方法

static void WriteFile(FileMode filemode,FileAccess fileacess,FileShare fileshare)
{
var content &#61; "测试";FileStream fs &#61; new FileStream(FilePath, filemode, fileacess, fileshare);var buffer&#61;Encoding.Default.GetBytes(content);fs.Write(buffer, 0,buffer.Length);fs.Flush();
}

一个是读文件的方法

static void ReadFile(FileAccess fileaccess,FileShare fileshare)
{FileStream fs
&#61;default(FileStream);try{fs &#61; new FileStream(FilePath, FileMode.Open, fileaccess, fileshare);var buffer &#61; new byte[fs.Length];fs.Position &#61; 0;fs.Read(buffer, 0, buffer.Length);Console.WriteLine(Encoding.Default.GetString(buffer));}catch (Exception ex){Console.WriteLine(ex);}
}

FileShare.Read

允许随后打开文件读取,所以有如下示例代码:

WriteFile(FileMode.Create, FileAccess.Write, FileShare.Read);
ReadFile(FileAccess.Read, FileShare.Read);
Console.ReadKey();

按照FileShare.Read的关法用法说明,出现上述问题只有一个原因,就是"允许随后打开文件读取"之前的操作可能不能是WriteFile,亦或只能是ReadFile,修改代码如下:

ReadFile(FileAccess.Read, FileShare.Read);
ReadFile(FileAccess.Read, FileShare.Read);
Console.ReadKey();

到这一步还不能确认上面的假设,现在修改代码如下:

ReadFile(FileAccess.Read, FileShare.Read);
WriteFile(FileMode.Create, FileAccess.Write, FileShare.Read);
Console.ReadKey();

ok,通过上面的3个demo说明FileShare.Read(只读共享)只有在连续读取文件才有效.

FileShare.Write

允许随后打开文件写入,和FileShare.Read一样,FileShare.Write(只写共享)只有在连续写入文件是才有效,代码如下:

WriteFile(FileMode.Create, FileAccess.Write, FileShare.Write,"测试");
WriteFile(FileMode.Append, FileAccess.Write, FileShare.Write,
"追加内容");

注:设置文件的共享方式为Write后,使用windows记事本也无法打开了.

FileShare.ReadWrite

综合FileShare.Read和FileShare.Write的特性

 

FileShare.None/FileShare.Delete

有了上面的经验&#xff0c;相信这两个你也很容易的就理解了&#xff0c;None则为不允许后续有任何操作&#xff0c;而Delete则是允许你随后进行删除操作。

 

(3)、通过FileInfo构建FileStream

a、通过FileInfo的实例方法OpenRead构建FileStream,代码如下:

FileInfo fi &#61; new FileInfo(FilePath);
FileStream fs
&#61; fi.OpenRead();
byte[] bt &#61; new byte[fs.Length];
fs.Read(bt,
0, bt.Length);
Console.WriteLine(Encoding.Default.GetString(bt));

查看OpenRead()的源代码,代码如下:

public FileStream OpenRead()
{
return new FileStream(base.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, false);
}

现在一目了然,其实FileInfo的实例方法OpenRead其实是构建了一个FileStream的实例.

b、通过FileInfo的实例方法OpenWrite构建FileStream,代码如下:

FileInfo fi &#61; new FileInfo(FilePath);
FileStream fs
&#61; fi.OpenWrite();
byte[] bt &#61; Encoding.Default.GetBytes("测试");
fs.Write(bt,
0, bt.Length);

查看OpenWrite方法的源码,如下:

public FileStream OpenWrite()
{
return new FileStream(base.FullPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
}

和OpenRead()一样,它的内部原理也是构建了一个FileStream实例.

c、通过FileInfo的实例方法Open构建FileStream,代码如下:

FileInfo fi &#61; new FileInfo(FilePath);
FileStream fs
&#61; fi.Open(FileMode.Create, FileAccess.Write, FileShare.None);
byte[] bt &#61; Encoding.Default.GetBytes("测试");
fs.Write(bt,
0, bt.Length);

和OpenRead()、OpenWrite()一样,它的内部原理也是构建了一个FileStream实例.但是Open的参数是可配置的,不像OpenRead()、OpenWrite()

Open源码如下:

public FileStream Open(FileMode mode){return this.Open(mode, FileAccess.ReadWrite, FileShare.None);}public FileStream Open(FileMode mode, FileAccess access){return this.Open(mode, access, FileShare.None);}public FileStream Open(FileMode mode, FileAccess access, FileShare share){return new FileStream(base.FullPath, mode, access, share);}

 

(4)、关闭流

通过上面的内容了解了构建一个FileStream其实就是构建了一个文件流,通过查阅源码可以发现FileStream继承了Stream,而Stream实现了IDisposable接口,所以当我们使用完FileStream,必须显示的调用Dispose或者使用using语句块,来释放资源.

FileStream提供了一个Close实例方法,来释放资源,和完成GC的回收,Close源码如下:

public virtual void Close()
{
this.Dispose(true);GC.SuppressFinalize(this);
}

关闭流会释放与它相关的资源,允许其他应用程序为同一个文件设置流,这个操作也会刷新缓冲区.在打开和关闭流之间,可以读写其中的数据.

 

(4)、通过FileStream的实例方法读取流

a、ReadByte()

ReadByte()是读取流数据的最简单的方式,他从流中读取一个字节,把结果转换成0~255之间的整数.如果达到该流的末尾,就返回-1,ReadByte()返回的是下一个流中的下一个字节,代码如下:

int nextByte &#61; fs.ReadByte();

b、Read()

如果要一次读取多个字节,就调用Read(),把特定数量的字节读入到一个数组中,Read()返回实际读取的字节数,如果这个数是0,就表示达到了流的末尾,代码如下:

int nBytes&#61;10;
int nextByte &#61; fs.Read(bt, 0, nBytes);

 

(5)、通过FileStream的实例方法写入流(给文件写入内容流)

a、WriteByte()

将一个字节写入流中,代码如下:

FileStream fs &#61; new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None);
byte bt &#61; 100;
fs.WriteByte(bt);

b、Write()

将一个字节数组写入流中,代码如下:

FileStream fs &#61; new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None);
byte[] bytes &#61; Encoding.Default.GetBytes("测试");
fs.Write(bytes,
0, bytes.Length);

 

(6)、二进制文件读取器

新建一个windows窗体应用程序,主要是将选中的文件,转换成二进制形式.

代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace BinaryFileReader
{
public partial class Form1 : Form{private readonly FileDialog choseOpenFileDialog &#61; new OpenFileDialog();private string choseFilePath;public Form1(){InitializeComponent();menuFileOpen.Click &#43;&#61; menuFileOpen_Click;choseOpenFileDialog.FileOk &#43;&#61; choseOpenFileDialog_FileOk;}private void choseOpenFileDialog_FileOk(object sender, CancelEventArgs e){choseFilePath &#61; choseOpenFileDialog.FileName;this.Text &#61; Path.GetFileName(choseFilePath);textBox1.Text &#61; choseFilePath;DisplayFileName();}private void menuFileOpen_Click(object sender, EventArgs e){choseOpenFileDialog.ShowDialog();}private void DisplayFileName(){int sizePerRow &#61; 16;//每行显示的字符数FileStream fs &#61; new FileStream(choseFilePath, FileMode.Open, FileAccess.Read);long nBytesToRead &#61; fs.Length;if (nBytesToRead > 65536 / 4)nBytesToRead &#61; 65536 / 4;//需要显示的字符数int nLines &#61; (int)(nBytesToRead / sizePerRow) &#43; 1;//总行数string[] lines &#61; new string[nLines];int nBytesRead &#61; 0;for (int i &#61; 0; i ){StringBuilder nextLine &#61; new StringBuilder();nextLine.Capacity &#61; 4 * sizePerRow;for (int j &#61; 0; j ){int nextByte &#61; fs.ReadByte();nBytesRead&#43;&#43;;if (nextByte &#61;&#61; 0 || nBytesRead > 65535)break;char nextChar &#61; (char)nextByte;if (nextChar <16)//char值小于16的字符都不是可打印字符nextLine.Append(" x0" &#43; string.Format("{0,1:X}", (int)nextChar));else if (char.IsLetterOrDigit(nextChar) || char.IsPunctuation(nextChar))//字母数字十进制标点正常显示nextLine.Append(" " &#43; nextChar &#43; " ");elsenextLine.Append(" x" &#43; string.Format("{0,2:X}", (int)nextChar));}lines[i] &#61; nextLine.ToString();}fs.Close();this.textBox2.Lines &#61; lines;}}
}

 

3、关于流缓存的问题

如果一个C#或者.Net程序需要读取Windows操作系统下面的一个文件,那么就可以通过文件流的方式,而如果需要读取文件流中的两个字节,那么该流则会把请求传递给Windows,注意此时Windows不会直接连接文件系统,在定位文件,并完成读取操作。而是在一次读取过程中,检索文件中的一个大块,并把该块保存到一个内存区域即缓冲区上。(后面系列的StreamReader和StreamWriter将会用到缓冲区.)以后对流中数据的请求,就会从该缓冲区中读取,直到读取完该缓冲区位置。此时windows会从文件中在获取另一个数据块.写入文件的方式与此相同,对于文件,操作系统会自动完成读写操作。

注:如果需要编写一个流类从没有缓存的设备中读取数据。如果是这样,就应从BufferedStream派生一个类,它实现一个缓冲区(但BufferedStream并不适用于应用程序频繁的切换读数据和写数据的情况).

C# 文件读写系列三

 


转载于:https://www.cnblogs.com/GreenLeaves/p/7192078.html


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 导出功能protectedvoidbtnExport(objectsender,EventArgse){用来打开下载窗口stringfileName中 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • 本文详细介绍了使用C#实现Word模版打印的方案。包括添加COM引用、新建Word操作类、开启Word进程、加载模版文件等步骤。通过该方案可以实现C#对Word文档的打印功能。 ... [详细]
author-avatar
撒哈拉2011的马甲_978
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有