如何返回到 .csv 中的上一行? [英] How To Go Back To Previous Line In .csv?

查看:45
本文介绍了如何返回到 .csv 中的上一行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

StreamReader csvFile;公共 GP_Appointment_Manager(){初始化组件();}private void buttonOpenFile_Click(object sender, EventArgs e){尝试{csvFile = new StreamReader("patients_100.csv");//读取第一行,什么都不做字符串线;if (ReadPatientLineFromCSV(out line)){//读取第二行,第一个患者行并填充表单ReadPatientLineFromCSV(out line);填充表格(行);}}捕获(异常前){MessageBox.Show(ex.Message);}}private bool ReadPatientLineFromCSV(输出字符串行){布尔结果 = 假;行 = "";if ((csvFile != null) && (!csvFile.EndOfStream)){行 = csvFile.ReadLine();结果=真;}别的{MessageBox.Show("文件尚未打开,阅读前请先打开文件.");}返回结果;}私人无效 PopulateForm(字符串患者详细信息){string[] 患者 = 患者详细信息.Split(',');//填充IDtextBoxID.Text = 患者 [0];//填充个人comboBoxSex.SelectedIndex = (患者[1] == "M") ?0 : 1;dateTimePickerDOB.Value = DateTime.Parse(patient[2]);textBoxFirstName.Text = 患者 [3];textBoxLastName.Text = 患者 [4];//填充地址textboxAddress.Text = 患者 [5];textboxCity.Text = 患者 [6];textboxCounty.Text = 患者 [7];textboxTelephone.Text = 患者[8];//填充KintextboxNextOfKin.Text = 患者 [9];textboxKinTelephone.Text = 患者 [10];}

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

private void buttonNextRecord_Click(object sender, EventArgs e){字符串患者信息;if (ReadPatientLineFromCSV(outpatientInfo)){填充表格(患者信息);}}

解决方案

现在,这是某种练习.此类使用标准

<小时>

using System.Collections.Generic;使用 System.IO;使用 System.Linq;使用 System.Text;使用 System.Windows.Forms;类 LineReader : IDisposable{私有 StreamReader 阅读器 = null;私人字典<长,长>职位;私有字符串 m_filePath = string.Empty;私有编码 m_encoding = null;私有 IEnumerable<Control>m_controls = null;私有字符串 m_separator = string.Empty;private bool m_associate = false;私人长 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){如果 (!File.Exists(filePath)) {throw new FileNotFoundException($"指定的文件:未找到{filePath}");}this.m_filePath = 文件路径;m_hasHeader = hasHeader;当前行数 = 0;reader = new StreamReader(this.m_filePath, encoding, true);CurrentLine = reader.ReadLine();m_encoding = reader.CurrentEncoding;m_currentPosition = m_encoding.GetPreamble().Length;position = new Dictionary() { [0]= m_currentPosition };if (hasHeader) { this.HeaderLine = CurrentLine = this.MoveNext();}}公共字符串 HeaderLine { 获取;私人订制;}公共字符串 CurrentLine { 获取;私人订制;}公共长 CurrentLineNumber { 获取;私人订制;}公共字符串 MoveNext(){字符串读取 = reader.ReadLine();if (string.IsNullOrEmpty(read)) 返回 this.CurrentLine;当前行数 += 1;if ((positions.Count - 1) < CurrentLineNumber) {AdjustPositionToLineFeed();位置.添加(当前行号,m_currentPosition);}别的 {m_currentPosition = 位置[CurrentLineNumber];}this.CurrentLine = 读取;if (m_associate) this.Associate();回读;}公共字符串 MovePrevious(){if (CurrentLineNumber == 0 || (CurrentLineNumber == 1 && m_hasHeader)) 返回 this.CurrentLine;当前行号 -= 1;m_currentPosition = 位置[CurrentLineNumber];reader.BaseStream.Position = m_currentPosition;reader.DiscardBufferedData();this.CurrentLine = reader.ReadLine();if (m_associate) this.Associate();返回 this.CurrentLine;}私有无效 AdjustPositionToLineFeed(){long linePos = m_currentPosition + m_encoding.GetByteCount(this.CurrentLine);long prevPos = reader.BaseStream.Position;reader.BaseStream.Position = linePos;字节[]缓冲区=新字节[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 控件,字符串分隔符){m_controls = 控制;m_separator = 分隔符;m_associate = 真;if (!string.IsNullOrEmpty(this.CurrentLine)) Associate();}private void Associate(){string[] values = this.CurrentLine.Split(new[] { m_separator }, StringSplitOptions.None);内部关联 = 0;m_controls.ToList().ForEach(c => {if (c != null) c.Text = values[associate];关联 += 1;});}公共覆盖字符串 ToS​​tring() =>$"文件路径:{m_filePath} 编码:{m_encoding.BodyName} 代码页:{m_encoding.CodePage}";公共无效处置(){this.Dispose(true);GC.SuppressFinalize(this);}受保护的虚拟无效处置(布尔处置){if (disposing) { reader?.Dispose();}}}

I'm trying to figure out how to either Record which line I'm in, for example, line = 32, allowing me to just add line-- in the previous record button event or find a better alternative.

I currently have my form setup and working where if I click on "Next Record" button, the file increments to the next line and displays the cells correctly within their associated textboxes, but how do I create a button that goes to the previous line in the .csv file?

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];
}

Here's the code for the "Next Record" Button

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

解决方案

Now, this is some sort of exercise. This class uses the standard StreamReader with a couple of modification, to implement simple move-forward/step-back functionalities.

It also allows to associate an array/list of Controls with the data read from a CSV-like file format. Note that this is not a general-purpose CSV reader; it just splits a string in parts, using a separator that can be specified calling its AssociateControls() method.

The class has 3 constructors:

(1) public LineReader(string filePath)
(2) public LineReader(string filePath, bool hasHeader)
(3) public LineReader(string filePath, bool hasHeader, Encoding encoding)

  1. The source file has no Header in the first line and the text Encoding should be auto-detected
  2. Same, but the first line of the file contain the Header if hasHeader = true
  3. Used to specify an Encoding, if the automatic discovery cannot identify it correctly.

The positions of the lines of text are stored in a Dictionary<long, long>, where the Key is the line number and Value is the starting position of the line.

This has some advantages: no strings are stored anywhere, the file is indexed while reading it but you could use a background task to complete the indexing (this feature is not implemented here, maybe later...).
The disadvantage is that the Dictionary takes space in memory. If the file is very large (just the number of lines counts, though), it may become a problem. To test.

A note about the Encoding:
The text encoding auto-detection is reliable enough only if the Encoding is not set to the default one (UTF-8). The code here, if you don't specify an Encoding, sets it to Encoding.ASCII. When the first line is read, the automatic feature tries to determine the actual encoding. It usually gets it right.
In the default StreamReader implementation, if we specify Encoding.UTF8 (or none, which is the same) and the text encoding is ASCII, the encoder will use the default (Encoding.UTF8) encoding, since UTF-8 maps to ASCII gracefully.
However, when this is the case, [Encoding].GetPreamble() will return the UTF-8 BOM (3 bytes), compromising the calculation of the current position in the underlying stream.


To associate controls with the data read, you just need to pass a collection of controls to the LineReader.AssociateControls() method.
This will map each control to the data field in the same position.
To skip a data field, specify null instead of a control reference.

The visual example is built using a CSV file with this structure:
(Note: this data is generated using an automated 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

I skipped the seq and color fields, passing this array of Controls:

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, ";");
}

The null entries correspond to the data fields that are not considered.

Visual sample of the functionality:


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

这篇关于如何返回到 .csv 中的上一行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆