Streamreader 添加带有打开文件对话框的列

Streamreader adds Column with opened File Dialog

在下面的代码中,我想让我的 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);

即使数据没有“数据点”,您也会看到 DataTable 中填满了数据。在上面的代码行执行接下来的几行代码后,检查 DataTable 是否有 2 列或更多列数据。如果没有足够的列,则代码只是弹出一个消息框来指示这一点。

这看起来很简单,但是,InputData.DataTable 仍然有数据,即使它很糟糕。下次你调用上面的 Load 方法时,它只会将新的 table 添加到现有的 table。如果需要,它将添加列,并简单地将行添加到现有 DataTable 的底部。尝试打开几个坏文件,然后最终打开好文件,您会看到许多添加的列和行。

我假设您的印象是当您打电话时……

ev.Cancel = true;

代码就停在那里并返回到委托中的第一行……

int LineCounter = 0;

……这不是真的。代码在 ev.Cancel = true; 执行后继续。

这可以从每次尝试打开 BAD 文件时获得额外的列和行这一事实看出。一个简单的解决方案是在调用加载方法之前简单地创建一个“新”InputData 对象。像……

InputData = new InputData();
InputData.DataTable.Load(DataRead);

这将解决额外的列问题,但是,如果用户选择了一个错误的文件并弹出错误消息,并且用户单击确定按钮返回到打开的文件对话框……那么……如果用户然后单击打开文件对话框的“取消”按钮,BAD 文件仍会显示在网格中。我相信您可能不希望这种行为。

无需详细说明已发布代码的其他一些奇怪方面。如开头所述,我提供了另一种可能的更简单的解决方案。当然,下面的代码使用了多个 OpenFileDialog,但是用户在选择有效文件或取消对话框之前仍然无法逃脱。

下面的大部分代码取自现有的已发布代码,但结构不同。在我们开始无限循环之前,最初会创建一些变量。具体来说,CsvConfiguration 变量 config 有一些附加的属性集,在读取文件时忽略一些代码崩溃问题。我相信您会希望设置 CsvReader 以按照您希望的方式处理这些问题。

一旦进入无尽的 while 循环,代码就会创建一个新的 InputData 对象,初始化一个新的 OpenFileDialog 并设置其属性。然后代码显示 OpenFileDialog,当对话框 return 时,DialogResult result 变量设置为对话框 returned DialogResult

如果对话框 returns OK 则代码检查文件是否为“空”文件。如果文件为空,将显示一个消息框以通知用户,然后我们返回到循环的开头。如果对话结果是 Cancel,那么代码将 return 一个“新” InputData 对象。空检查的原因是线上会抛出异常(No header record was found)...

DataRead = new CsvDataReader(csv);

如果文件为空。

我相信可能有一些我错过的 CsvHelper 属性 可以防止这个“空”文件异常。如果有更好的方法来检查这个“空”文件或防止异常,我愿意接受建议。

如果文件不为空,我们继续打开文件并继续使用 CsvDataReader 按预期读取其数据。这个想法是......如果文件读取正确无误并且符合要求,那么我们已经设置了InputData.DataTable,剩下要做的就是设置它的FilePath 属性 return InputData 对象。

一旦我们有了 InputData.DataTable,我们就可以检查 InputData.DataTable 中的列数。如果列数少于两 (2),则向用户弹出错误消息框并循环回到 while 循环的开头。

如果 InputData.DataTable 满足两 (2) 列或更多列的要求,则通过遍历数据 table 中的所有列进行另一次检查。如果至少一 (1) 个列名是“Date/Time”,那么我们就完成了对要求的检查,只需设置 InputData.FileName 属性 和 return InputData对象。

如果InputData.DataTable列中的none列名为“Date/Time”,则再次弹出错误提示框,循环回到while循环。

应该注意的是,如果文件未通过列数测试或名为 Date/Time 的列测试……那么对于您的问题,InputData.DataTable 仍然有数据。这在这里没问题,因为当我们循环回到 while 循环的开头时,我们将重新初始化一个“新的”InputData 对象。

最后,您没有显示 InputData Class,但它似乎至少有两 (2) 个属性……1) a string FilePath 和2) 一个名为 DataTableDataTable???这看起来很奇怪而且模棱两可……我已经将我的 InputData 对象的 DataTable 属性 重命名为 DT。同样的“歧义”适用于我已更改为 TempInputData.

InputData 变量

由于每次用户选择 BAD 文件时代码可能“潜在地”创建大量 InputData 对象,因此我在 InputData [=124= 中实现了 IDisposable 接口].这样我们就可以在 using 语句中使用这个 Class 并正确处理代码创建的未使用的 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:\Test\CSV";
  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();
           }


我不知道这是否是最有效的解决方案,但我之前将关键变量读入另一个 class。这些是在取消之前重新读取文件时使用的。