在下面的代码中,我希望在一个方法中打开我的OpenFileDialog,直到选择一个有效的文件。这只能在有条件的情况下起作用。出于某种原因,它在显示消息后添加了一列。如果我以前选择了一个不正确的文件,这会导致正确的数据表也被错误地读取。
public static InputData GetCSVData()
{
InputData InputData = new InputData();
OpenFileDialog OFDReader = new OpenFileDialog();
//Filter OpenFileDialog; show only CSV-Files
OFDReader.Filter = "CSV files|*.csv;";
// check if data contains "Date/Time" .
OFDReader.FileOk += delegate (object s, CancelEventArgs ev)
{
//search for Line to start reader
int LineCounter = 0;
var readertmp = new StreamReader(OFDReader.FileName);
while (true)
{
string LineTmp = readertmp.ReadLine();
string record = "Date/Time";
if (LineTmp.Contains(record))
{ break; }
else if (readertmp.EndOfStream)
{
MessageBox.Show("Data has no DataPoints !", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
ev.Cancel = true;
{ break; }
}
LineCounter++;
}
//read InputData
var reader = new StreamReader(OFDReader.FileName);
for (int i = 0; i < LineCounter; i++)
{
reader.ReadLine();
}
// settings CSVHelper
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ";", // Set delimiter
};
var csv = new CsvReader(reader, config);
var DataRead = new CsvDataReader(csv);
InputData.DataTable.Load(DataRead);
//check for enough columns
int ColumnCounter = 0;
ColumnCounter = InputData.DataTable.Columns.Count;
if (ColumnCounter <= 2)
{
MessageBox.Show("Data has not enough columns!", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
ev.Cancel = true;
}
};
if (OFDReader.ShowDialog() == DialogResult.OK)
{
InputData.FilePath = OFDReader.FileName;
}
return InputData;
}
}
你似乎让这件事变得比必须的更复杂。对于初学者来说,你会为FileOK
委托而烦恼似乎很奇怪(至少对我来说)。我看不出如果向用户呈现一次、两次或多次OpenFileDialog
会有什么不同。使用单个OpenFileDialog
似乎是在浪费精力。
如果用户选择了一个文件,但它不能满足必要的要求,那么只需打开另一个OpenFileDialog
并让用户重试。在一个对话框中这样做当然是可能的,但是,"在哪里"你会使用它?这个对话框似乎是"特定"于"特定"类型的文件,为什么要将对话框限制为我们需要的要求。我认为一个简单的方法会一直循环,直到用户选择一个有效的文件或取消OpenFileDialog
,这将是一个更容易的方法。
话虽如此,遵循您的代码有点奇怪。问题的原因是,无论文件是否具有数据点或足够的列,代码都会将文件读取到InputData.DataTable
中。在线路上设置断点…
InputData.DataTable.Load(DataRead);
即使数据没有"DataPoints",你也会看到DataTable
中充满了数据。在上面的代码行执行完接下来的几行之后,检查DataTable
是否有2列或更多列数据。如果没有足够的列,那么代码只会弹出一个消息框来指示这一点。
这看起来很直接,但是InputData.DataTable
仍然有数据,即使它很糟糕。下次调用上面的Load方法时,它将简单地将新表添加到现有表中。如果需要,它将添加列,并简单地将行添加到现有DataTable
的底部。尝试打开几个BAD文件,然后最终打开好的文件,您会看到许多添加的列和行。
我想你可能会有这样的印象,当你打电话给…时
ev.Cancel = true;
代码就停在那里,然后回到委托中的第一行…
int LineCounter = 0;
……这不是真的。代码在执行ev.Cancel = true;
之后继续。
这可以从以下事实中看出:每次试图打开BAD文件时,都会得到额外的列和行。一个简单的解决方案是在调用load方法之前创建一个"新"InputData
对象。类似…
InputData = new InputData();
InputData.DataTable.Load(DataRead);
这将解决额外列的问题,但是,如果用户选择了一个BAD文件,并弹出错误消息,并且用户单击"确定"按钮返回到打开的文件对话框…然后…如果用户单击打开的文件对话"取消"按钮,BAD文件仍将显示在网格中。我相信你可能不想要这种行为。
没有详细介绍发布代码的其他一些奇怪方面。我提供了另一个可能的更简单的解决方案,如开头所述。当然,下面的代码使用了多个OpenFileDialog
,但是用户在选择有效文件或取消对话框之前仍然无法转义。
下面的大部分代码都取自现有的发布代码,但是,它的结构不同。最初,在我们统计一个无休止的循环之前,会创建一些变量。具体来说,CsvConfiguration
变量config
添加了一些属性集,这些属性集在读取文件时忽略了一些代码崩溃问题。我相信您会希望设置CsvReader
以按照您希望的方式处理这些问题。
一旦进入无休止的while
循环,代码就会创建一个新的InputData
对象,初始化一个新OpenFileDialog
并设置其属性。然后,代码显示OpenFileDialog
,当对话框返回时,DialogResult
result
变量设置为对话框返回的DialogResult
。
如果对话框返回OK
,则代码将检查该文件是否为"空"文件。如果文件是空的,则会显示一个消息框来通知用户,然后我们分支回到循环的乞求。如果对话框结果为Cancel
,则代码将返回一个"新"InputData
对象。空检查的原因是将在线路上抛出异常(未找到标头记录)…
DataRead = new CsvDataReader(csv);
如果文件为空。
我相信,可能有一些CsvHelper
属性我错过了,可以防止这个"空"文件异常。如果有更好的方法来检查这个"空"文件或防止异常,我愿意接受建议。
如果该文件不是空的,我们将打开该文件并继续使用CsvDataReader
读取其数据。其思想是……如果文件读取正确且没有错误并且符合要求,那么我们就已经设置了InputData.DataTable
,剩下的就是设置其FilePath
属性并返回InputData
对象。
一旦我们有了InputData.DataTable
,我们就可以检查InputData.DataTable
中的列数。如果列数小于两(2),则向用户弹出错误消息框,并循环回while
循环的乞求。
如果InputData.DataTable
满足两(2)列或更多列的要求,则通过循环数据表中的所有列来进行另一次检查。如果至少有一(1)个列名是"Date/Time",那么我们就完成了对需求的检查,只需设置InputData.FileName
属性并返回InputData
对象。
如果InputData.DataTable
列中没有列名称命名为"日期/时间",那么我们再次弹出错误消息框,并循环回到while
循环的乞求。
需要注意的是,如果文件未通过列数测试或名为Date/Time
的列测试……那么与您的问题一样,InputData.DataTable
仍然有数据。这在这里是可以的,因为当我们循环回到while
循环的乞求时,我们将重新初始化一个"新的"InputData
对象。
最后,您没有显示InputData
类,但它似乎至少有两(2)个属性…1)一个string
FilePath
和2)一个名为DataTable
的DataTable
???这看起来很奇怪而且不明确…我已经将InputData
对象的DataTable
属性重命名为DT
。同样的"歧义"也适用于InputData
变量,我已将其更改为TempInputData
。
由于每次用户选择BAD文件时,代码可能"潜在地"创建大量InputData
对象,因此我在InputData
类中实现了IDisposable
接口。这样,我们就可以在using
语句中使用这个类,并正确地处理代码创建的未使用的InputData
对象。我希望我已经正确地执行了这一点。
public class InputData : IDisposable {
public DataTable DT;
public string FilePath;
private bool isDisposed;
public InputData() {
DT = new DataTable();
FilePath = "";
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (isDisposed) {
return;
}
if (disposing) {
DT?.Dispose();
FilePath = null;
}
isDisposed = true;
}
}
private InputData GetInputDataFromSCV() {
InputData TempInputData;
OpenFileDialog OFDReader;
string initialDirectory = @"D:TestCSV";
DialogResult result;
CsvConfiguration config = new CsvConfiguration(CultureInfo.InvariantCulture) {
Delimiter = ";",
IgnoreBlankLines = true,
MissingFieldFound = null,
BadDataFound = null
};
CsvReader csv;
CsvDataReader DataRead;
StreamReader readertmp;
FileInfo fi;
while (true) {
using (TempInputData = new InputData()) {
using (OFDReader = new OpenFileDialog()) {
OFDReader.Filter = "CSV files|*.csv;";
OFDReader.InitialDirectory = initialDirectory;
result = OFDReader.ShowDialog();
if (result == DialogResult.OK) {
fi = new FileInfo(OFDReader.FileName);
if (fi.Length != 0) {
using (readertmp = new StreamReader(OFDReader.FileName)) {
csv = new CsvReader(readertmp, config);
DataRead = new CsvDataReader(csv);
TempInputData.DT.Load(DataRead);
if (TempInputData.DT.Columns.Count > 2) {
foreach (DataColumn column in TempInputData.DT.Columns) {
if (column.ColumnName == "Date/Time") {
TempInputData.FilePath = OFDReader.FileName;
return TempInputData;
}
}
// if we get here we know a column named "Date/Time" was NOT found
MessageBox.Show("Data has no DataPoints !", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else {
MessageBox.Show("Data has less than 2 columns?", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
else {
MessageBox.Show("File is empty!", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
else {
if (result == DialogResult.Cancel) {
return new InputData();
}
}
}
}
}
}
我希望这是有意义和帮助的。
很抱歉给您带来不便。有时我真的把它弄得太复杂了。我现在已经解决了如下问题:
if (result == DialogResult.Cancel)
{
if (inputDataHistory.Loadcount != 0)
{
TempInputData.FilePath = inputDataHistory.FilePathCache;
TempInputData.LineCounter = inputDataHistory.LinecounterCache;
var reader = new StreamReader(TempInputData.FilePath);
for (int i = 0; i < TempInputData.LineCounter; i++)
{
reader.ReadLine();
}
csv = new CsvReader(reader, config);
DataRead = new CsvDataReader(csv);
TempInputData.DT.Load(DataRead);
TempInputData.IsDisposed = true;
return TempInputData;
}
else
{
return new InputData();
}
我不知道这是否是最有效的解决方案,但我之前曾将关键变量读入另一个类中。取消时使用这些选项来重新读取之前的文件。