【问题标题】:How To Go Back To Previous Line In .csv? [duplicate]如何返回 .csv 中的上一行? [复制]
【发布时间】:2019-12-03 17:58:59
【问题描述】:

我正在尝试弄清楚如何记录我所在的行,例如,line = 32,允许我在上一个记录按钮事件中添加 line-- 或找到更好的替代方案。

我目前有我的表单设置和工作,如果我单击“下一个记录”按钮,文件将递增到下一行并在其关联的文本框中正确显示单元格,但是我如何创建一个转到.csv 文件中的上一行?

StreamReader csvFile;

public GP_Appointment_Manager()
{
    InitializeComponent();
}

private void buttonOpenFile_Click(object sender, EventArgs e)
{
    try
    {
        csvFile = new StreamReader("patients_100.csv");
        // Read First line and do nothing
        string line;
        if (ReadPatientLineFromCSV(out line))
        {
            // Read second line, first patient line and populate form
            ReadPatientLineFromCSV(out line);
            PopulateForm(line);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

private bool ReadPatientLineFromCSV(out string line)
{
    bool result = false;
    line = "";
    if ((csvFile != null) && (!csvFile.EndOfStream))
    {
        line = csvFile.ReadLine();
        result = true;
    }
    else
    {
        MessageBox.Show("File has not been opened. Please open file before reading.");
    }
    return result;
}

private void PopulateForm(string patientDetails)
{
    string[] patient = patientDetails.Split(',');
    //Populates ID
    textBoxID.Text = patient[0];
    //Populates Personal 
    comboBoxSex.SelectedIndex = (patient[1] == "M") ? 0 : 1;
    dateTimePickerDOB.Value = DateTime.Parse(patient[2]);
    textBoxFirstName.Text = patient[3];
    textBoxLastName.Text = patient[4];
    //Populates Address 
    textboxAddress.Text = patient[5];
    textboxCity.Text = patient[6];
    textboxCounty.Text = patient[7];
    textboxTelephone.Text = patient[8];
    //Populates Kin
    textboxNextOfKin.Text = patient[9];
    textboxKinTelephone.Text = patient[10];
}

这是“下一条记录”按钮的代码

private void buttonNextRecord_Click(object sender, EventArgs e)
{
    string patientInfo;
    if (ReadPatientLineFromCSV(out patientInfo))
    {
        PopulateForm(patientInfo);
    }
}

【问题讨论】:

    标签: c# winforms csv streamreader


    【解决方案1】:

    现在,这是某种练习。此类使用标准StreamReader 并进行了一些修改,以实现简单的前移/后退功能。

    它还允许将控件数组/列表与从类 CSV 文件格式读取的数据相关联。请注意,这不是通用的 CSV 阅读器;它只是将字符串分成几部分,使用可以指定调用其AssociateControls() 方法的分隔符。

    该类有 3 个构造函数:

    (1) public LineReader(string filePath)
    (2) public LineReader(string filePath, bool hasHeader)
    (3) public LineReader(string filePath, bool hasHeader, Encoding encoding)
    
    1. 源文件第一行没有Header,应该自动检测文本编码
    2. 相同,但文件的第一行包含 Header if hasHeader = true
    3. 用于指定编码,如果自动发现无法正确识别。

    文本行的位置存储在Dictionary<long, long> 中,其中Key 是行号,Value 是行的起始位置。

    这有一些优点:没有字符串存储在任何地方,文件在读取时会被索引,但您可以使用后台任务来完成索引(此功能此处未实现,可能稍后...)。
    缺点是字典占用内存空间。如果文件非常大(不过只计算行数),它可能会成为一个问题。去测试。

    关于编码的说明
    仅当 Encoding 未设置为默认值 (UTF-8) 时,文本编码自动检测才足够可靠。这里的代码,如果不指定Encoding,设置为Encoding.ASCII。读取第一行时,自动功能会尝试确定实际编码。它通常是正确的。
    在默认的 StreamReader 实现中,如果我们指定Encoding.UTF8(或无,相同)并且文本编码为ASCII,则编码器将使用默认的(Encoding.UTF8)编码,因为UTF-8 映射到@ 987654338@优雅。
    但是,在这种情况下,[Encoding].GetPreamble() 将返回 UTF-8 BOM(3 个字节),从而影响基础流中当前位置的计算。


    要将控件与读取的数据相关联,只需将控件集合传递给LineReader.AssociateControls() 方法即可。
    这会将每个控件映射到同一位置的数据字段。
    要跳过数据字段,请指定 null 而不是控件引用。

    视觉示例是使用具有以下结构的 CSV 文件构建的:
    (注意:此数据是使用自动生成的on-line tool

    seq;firstname;lastname;age;street;city;state;zip;deposit;color;date
    ---------------------------------------------------------------------------
    1;Harriett;Gibbs;62;Segmi Center;Ebanavi;ID;57854;$4444.78;WHITE;05/15/1914
    2;Oscar;McDaniel;49;Kulak Drive;Jetagoz;IL;57631;$5813.94;RED;02/11/1918
    3;Winifred;Olson;29;Wahab Mill;Ucocivo;NC;46073;$2002.70;RED;08/11/2008
    

    我跳过了 seqcolor 字段,传递了这个控件数组:

    LineReader lineReader = null;
    
    private void btnOpenFile_Click(object sender, EventArgs e)
    {
        string filePath = Path.Combine(Application.StartupPath, @"sample.csv");
        lineReader = new LineReader(filePath, true);
        string header = lineReader.HeaderLine;
        Control[] controls = new[] { 
            null, textBox1, textBox2, textBox3, textBox4, textBox5, 
            textBox6, textBox9, textBox7, null, textBox8 };
        lineReader.AssociateControls(controls, ";");
    }
    

    空条目对应于未考虑的数据字段。

    功能的视觉示例:


    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    class LineReader : IDisposable
    {
        private StreamReader reader = null;
        private Dictionary<long, long> positions;
        private string m_filePath = string.Empty;
        private Encoding m_encoding = null;
        private IEnumerable<Control> m_controls = null;
        private string m_separator = string.Empty;
        private bool m_associate = false;
        private long m_currentPosition = 0;
        private bool m_hasHeader = false;
    
        public LineReader(string filePath) : this(filePath, false) { }
        public LineReader(string filePath, bool hasHeader) : this(filePath, hasHeader, Encoding.ASCII) { }
        public LineReader(string filePath, bool hasHeader, Encoding encoding)
        {
            if (!File.Exists(filePath)) {
                throw new FileNotFoundException($"The file specified: {filePath} was not found");
            }
            this.m_filePath = filePath;
            m_hasHeader = hasHeader;
            CurrentLineNumber = 0;
            reader = new StreamReader(this.m_filePath, encoding, true);
            CurrentLine = reader.ReadLine();
    
            m_encoding = reader.CurrentEncoding;
            m_currentPosition = m_encoding.GetPreamble().Length;
            positions = new Dictionary<long, long>() { [0]= m_currentPosition };
            if (hasHeader) { this.HeaderLine = CurrentLine = this.MoveNext(); }
        }
    
        public string HeaderLine { get; private set; }
        public string CurrentLine { get; private set; }
        public long CurrentLineNumber { get; private set; }
    
        public string MoveNext()
        {
            string read = reader.ReadLine();
            if (string.IsNullOrEmpty(read)) return this.CurrentLine;
            CurrentLineNumber += 1;
    
            if ((positions.Count - 1) < CurrentLineNumber) {
                AdjustPositionToLineFeed();
                positions.Add(CurrentLineNumber, m_currentPosition);
            }
            else {
                m_currentPosition = positions[CurrentLineNumber];
            }
            this.CurrentLine = read;
            if (m_associate) this.Associate();
            return read;
        }
    
        public string MovePrevious()
        {
            if (CurrentLineNumber == 0 || (CurrentLineNumber == 1 && m_hasHeader)) return this.CurrentLine;
            CurrentLineNumber -= 1;
            m_currentPosition = positions[CurrentLineNumber];
            reader.BaseStream.Position = m_currentPosition;
            reader.DiscardBufferedData();
    
            this.CurrentLine = reader.ReadLine();
            if (m_associate) this.Associate();
            return this.CurrentLine;
        }
    
        private void AdjustPositionToLineFeed()
        {
            long linePos = m_currentPosition + m_encoding.GetByteCount(this.CurrentLine);
            long prevPos = reader.BaseStream.Position;
            reader.BaseStream.Position = linePos;
    
            byte[] buffer = new byte[4];
            reader.BaseStream.Read(buffer, 0, buffer.Length);
            char[] chars = m_encoding.GetChars(buffer).Where(c => c.Equals((char)10) || c.Equals((char)13)).ToArray();
            m_currentPosition = linePos + m_encoding.GetByteCount(chars);
            reader.BaseStream.Position = prevPos;
        }
    
        public void AssociateControls(IEnumerable<Control> controls, string separator)
        {
            m_controls = controls;
            m_separator = separator;
            m_associate = true;
            if (!string.IsNullOrEmpty(this.CurrentLine)) Associate();
        }
    
        private void Associate()
        {
            string[] values = this.CurrentLine.Split(new[] { m_separator }, StringSplitOptions.None);
            int associate = 0;
            m_controls.ToList().ForEach(c => {
                if (c != null) c.Text = values[associate];
                associate += 1;
            });
        }
    
        public override string ToString() =>
            $"File Path: {m_filePath} Encoding: {m_encoding.BodyName} CodePage: {m_encoding.CodePage}";
    
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing) { reader?.Dispose(); }
        }
    }
    

    【讨论】:

      【解决方案2】:

      一般做法如下:

      像这样添加一个文本文件input.txt

      line 1
      line 2
      line 3
      

      并将 Copy to Output Directory 属性设置为 Copy if newer

      StreamReader创建扩展方法

      public static class StreamReaderExtensions
      {
          public static bool TryReadNextLine(this StreamReader reader, out string line)
          {
              var isAvailable = reader != null &&
                                !reader.EndOfStream;
              line = isAvailable ? reader.ReadLine() : null;
              return isAvailable;
          }
      
          public static bool TryReadPrevLine(this StreamReader reader, out string line)
          {
              var stream = reader.BaseStream;
              var encoding = reader.CurrentEncoding;
              var bom = GetBOM(encoding);
      
              var isAvailable = reader != null &&
                                stream.Position > 0;
      
              if(!isAvailable)
              {
                  line = null;
                  return false;
              }
      
              var buffer = new List<byte>();
              var str = string.Empty;
              stream.Position++;
              while (!str.StartsWith(Environment.NewLine))
              {
                  stream.Position -= 2;
                  buffer.Insert(0, (byte)stream.ReadByte());
                  var reachedBOM = buffer.Take(bom.Length).SequenceEqual(bom);
                  if (reachedBOM)
                      buffer = buffer.Skip(bom.Length).ToList();
                  str = encoding.GetString(buffer.ToArray());
                  if (reachedBOM)
                      break;
              }
              stream.Position--;
              line = str.Trim(Environment.NewLine.ToArray());
              return true;
          }
      
          private static byte[] GetBOM(Encoding encoding)
          {
              if (encoding.Equals(Encoding.UTF7))
                  return new byte[] { 0x2b, 0x2f, 0x76 };
              if (encoding.Equals(Encoding.UTF8))
                  return new byte[] { 0xef, 0xbb, 0xbf };
              if (encoding.Equals(Encoding.Unicode))
                  return new byte[] { 0xff, 0xfe };
              if (encoding.Equals(Encoding.BigEndianUnicode))
                  return new byte[] { 0xfe, 0xff };
              if (encoding.Equals(Encoding.UTF32))
                  return new byte[] { 0, 0, 0xfe, 0xff };
              return new byte[0];
          }
      }
      

      并像这样使用它:

      using (var reader = new StreamReader("input.txt"))
      {
          string na = "N/A";
          string line;
          for (var i = 0; i < 4; i++)
          {
              var isAvailable = reader.TryReadNextLine(out line);
              Console.WriteLine($"Next line available: {isAvailable}. Line: {(isAvailable ? line : na)}");
          }
          for (var i = 0; i < 4; i++)
          {
              var isAvailable = reader.TryReadPrevLine(out line);
              Console.WriteLine($"Prev line available: {isAvailable}. Line: {(isAvailable ? line : na)}");
          }
      }
      

      结果是:

      Next line available: True. Line: line 1
      Next line available: True. Line: line 2
      Next line available: True. Line: line 3
      Next line available: False. Line: N/A
      Prev line available: True. Line: line 3
      Prev line available: True. Line: line 2
      Prev line available: True. Line: line 1
      Prev line available: False. Line: N/A
      

      GetBOM 基于this

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-03
        相关资源
        最近更新 更多