【问题标题】:Streamreader adds Column with opened File DialogStreamreader 添加带有打开文件对话框的列
【发布时间】:2021-10-12 22:06:57
【问题描述】:

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

            }

        }

【问题讨论】:

    标签: c# streamreader openfiledialog


    【解决方案1】:

    看来你让这变得比它必须的更复杂。对于初学者来说,您会打扰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 文件仍将显示在网格中。我相信您可能不想要这种行为。

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

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

    一旦进入无限的while 循环,代码将创建一个新的InputData 对象,初始化一个新的OpenFileDialog 并设置其属性。然后代码显示OpenFileDialog,当对话框返回时,DialogResultresult 变量设置为返回的对话框DialogResult

    如果对话框返回OK,则代码会检查文件是否为“空”文件。如果文件为空,则会显示一个消息框通知用户,然后我们分支回到循环的乞求。如果对话结果是Cancel,那么代码将返回一个“新”InputData 对象。空检查的原因是会抛出异常(No header record was found)就行了……

    DataRead = new CsvDataReader(csv);
    

    如果文件为空。

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

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

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

    如果InputData.DataTable 满足两 (2) 列或更多列的要求,则通过循环遍历数据表中的所有列进行另一次检查。如果至少一 (1) 列名称是“日期/时间”,那么我们就完成了对要求的检查,只需设置 InputData.FileName 属性并返回 InputData 对象。

    如果InputData.DataTable 列中没有一个列名为“Date/Time”,那么我们再次弹出错误消息框并循环回到while 循环的乞求。

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

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

    由于每次用户选择 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:\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();
              }
            }
          }
        }
      }
    }
    

    我希望这有意义并有所帮助。

    【讨论】:

    • 我还没有尝试过您的代码,但感谢您的详细回答。我也有新输入的想法,但仍然如您所描述的那样出现错误。我不是在我的代码中寻找一个“日期/时间”列,而是一个特定的行。 CSV 文件开头有未指定的行,我不需要。只有“日期/时间”中的值是相关的。
    • 非常感谢。我已经将它改编为我的代码,它工作正常。我只有一个小问题,我的流式阅读器可以识别空文件中的 4 或 2 个字符。我认为这与 Excel 中的默认设置有关。我通过在 Fi.Length >= 10 中使用 fi.Length != 0 设置您的规则来解决这个问题。IgnoreBlankLines 与 CSVconfig 相关。
    • 您好,已经有一段时间了,但不知何故,以下问题给我带来了麻烦。如果之前成功读取任何文件,我想在取消时保留数据。否则,进一步的计算将不起作用,并且取消按钮用作硬重置。
    • 抱歉,您能否澄清一下您的意思……“如果之前成功读取了任何文件,我想在取消时保留数据。”……? …
    • 我已将 getinputdatafro 附加到按钮上。这使我可以读取某些值并将它们用于计算。如果我现在想加载另一个文件并在输入对话框中取消,则使用 0 读取 tempiputdata。我希望能够继续使用先前加载的值进行计算。如果已经有数据,我该如何解决这个问题或避免他传递零。此外,如果有人想读入新数据,这个类应该保持原样。
    【解决方案2】:

    对于给您带来的不便,我深表歉意。有时我真的把它弄得太复杂了。我现在已经解决了如下:

     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();
               }
    
    
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-07
      • 2013-05-12
      • 2022-01-23
      • 2011-02-04
      相关资源
      最近更新 更多