FileStream 개체로 입출력 [Windows Forms with C#]

안녕하세요. 언제나휴일입니다.

이번에는 FileStream 개체로 파일 입출력 실습을 해 볼게요.

보통 C# 문법을 다룰 때 나오는 내용입니다. 이미 알고 있는 내용이면 스킵하세요.

1. 시나리오

프로그램 시작할 때 파일에 내용을 로딩하여 ListView에 보여준다.

회원 정보를 입력하여 버튼 클릭 시 ListView에 추가한다.

프로그램 종료할 때 ListView에 있는 회원 정보를 파일에 저장한다.

2. Form1에 자식 컨트롤 배치

Windows Forms 앱(.NET Framework) 프로젝트를 생성한 후 Form1에 자식 컨트롤을 배치하세요.

실행 화면
실행 화면

회원 정보를 보여주는 ListView는 lv_member로 정하고 열을 추가하세요.

아이디를 입력하는 TextBox는 tbox_id로 정할게요.

이름을 입력하는 TextBox는 tbox_name으로 정할게요.

나이를 표시할 TrackBar는 tbox_age로 지정하고 Maximum을 200으로 지정하세요.

나이를 표시할 Label은 lb_age로 정할게요.

추가 버튼은 btn_add입니다.

3. 이벤트 핸들러 등록

Form1의 Load와 FormClosed 이벤트 핸들러를 등록하세요.

btn_add의 Click 이벤트 핸들러를 등록하세요.

tbar_age의 Scroll 이벤트 핸들러를 등록하세요.

4. Form1.cs 구현

입출력에 사용할 파일명을 상수 멤버로 정할게요.

    public partial class Form1 : Form
    {
        const string fname = "data";
        ...중략...
    }

4.1 btn_add의 Click 이벤트 핸들러 구현

입력한 정보를 얻어와서 회원 정보를 ListView에 추가합니다.

회원 정보를 ListView에 추가하는 부분은 별도의 메서드(AddMember)를 만들기로 할게요.

        private void btn_add_Click(object sender, EventArgs e)
        {
            string id = tbox_id.Text;
            string name = tbox_name.Text;
            int age = tbar_age.Value;
            AddMember(id, name, age);
            tbox_id.Text = tbox_name.Text = "";
            tbar_age.Value = 0;
            lb_age.Text = "0";
        }

다음은 회원 정보를 ListView에 추가하는 AddMember 메서드입니다.

아이디, 이름, 나이 정보를 문자열 배열로 만듭니다.

이를 입력인자로 전달하여 ListViewItem 개체 생성하여 ListView에 항목 추가합니다.

        private void AddMember(string id, string name, int age)
        {
            string[] sitems = new string[] { id, name, age.ToString() };
            ListViewItem lvi = new ListViewItem(sitems);
            lv_member.Items.Add(lvi);
        }

4.2 tbar_age의 Scroll 이벤트 핸들러 구현

lb_age의 Text 속성을 tbar_age의 Value 값으로 설정합니다.

        private void tbar_age_Scroll(object sender, EventArgs e)
        {
            lb_age.Text = tbar_age.Value.ToString();
        }

4.3 Form1의 FormClosed 이벤트 핸들러 구현

File.Create 메서드로 파일을 생성하면 FileStream 개체를 반환합니다.

                    FileStream fs = File.Create(fname);
    

lv_member에 있는 모든 항목 정보를 모두 파일에 기록해야 합니다.

            foreach(ListViewItem lvi in lv_member.Items)
            {
                ... 파일에 기록 ...
            }

ListViewItem 개체에서 아이디, 이름, 나이 정보를 얻어옵니다.

                string id = lvi.SubItems[0].Text;
                string name = lvi.SubItems[1].Text;
                int age = int.Parse(lvi.SubItems[2].Text);

FileStream 개체로 파일에 기록할 때는 byte 배열에 있는 내용을 저장합니다.

로딩할 때도 byte 배열에 로딩합니다.

주의할 점은 아이디나 이름처럼 String 형식 데이터는 가변 길이의 byte 배열로 변환할 수 있습니다.

파일에 기록하더라도 로딩할 때 길이를 모르면 로딩할 크기를 알 수 없어 문제가 발생합니다.

이에 가변 길이의 byte 배열 길이를 먼저 구하고 이를 다시 byte 배열로 변환하여 기록합니다.

그리고 아이디를 기록합니다.

                byte[] idbs = Encoding.Default.GetBytes(id);
                int len_id = idbs.Length;//가변 길이(ID)
                byte[] lenbs = BitConverter.GetBytes(len_id);
                fs.Write(lenbs, 0, lenbs.Length);
                fs.Write(idbs, 0, idbs.Length);

이름도 같은 원리로 기록합니다.

                byte[] namebs = Encoding.Default.GetBytes(name);
                int len_name = namebs.Length;
                byte[] lenbs2 = BitConverter.GetBytes(len_name);
                fs.Write(lenbs2, 0, lenbs2.Length);
                fs.Write(namebs, 0, namebs.Length);

나이는 int 형식이므로 byte 배열로 변환하면 언제나 4바이트로 고정입니다.

변환한 byte 배열을 파일에 기록합니다.

                byte[] agebs = BitConverter.GetBytes(age);
                fs.Write(agebs, 0, agebs.Length);

반복문이 끝나면 파일 스트림 개체를 닫습니다.

            fs.Close();

다음은 Form1의 FormClosed 이벤트 핸들러 코드입니다.

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            FileStream fs = File.Create(fname);
            foreach(ListViewItem lvi in lv_member.Items)
            {
                string id = lvi.SubItems[0].Text;
                string name = lvi.SubItems[1].Text;
                int age = int.Parse(lvi.SubItems[2].Text);

                byte[] idbs = Encoding.Default.GetBytes(id);
                int len_id = idbs.Length;//가변 길이(ID)
                byte[] lenbs = BitConverter.GetBytes(len_id);
                fs.Write(lenbs, 0, lenbs.Length);
                fs.Write(idbs, 0, idbs.Length);
                byte[] namebs = Encoding.Default.GetBytes(name);
                int len_name = namebs.Length;
                byte[] lenbs2 = BitConverter.GetBytes(len_name);
                fs.Write(lenbs2, 0, lenbs2.Length);
                fs.Write(namebs, 0, namebs.Length);
                byte[] agebs = BitConverter.GetBytes(age);
                fs.Write(agebs, 0, agebs.Length);
            }
            fs.Close();
        }

4.4 Form1의 Load 이벤트 핸들러 구현

먼저 파일이 있는지 확인하여 없다면 메서드를 종료합니다.

            if(File.Exists(fname)==false)
            {
                return;
            }

읽기 모드로 파일을 개방합니다.

            FileStream fs = File.OpenRead(fname);

파일의 작업 위치(Position)이 파일의 길이(Length)보다 작으면 자료가 남은 것으므로 반복합니다.

            while(fs.Position<fs.Length)
            {
                ... 로딩 ....
            }
            fs.Close();
        }

아이디는 가변 길이여서 길이, 아이디 순으로 기록하였습니다.

먼저 길이를 얻어온 후에 해당 크기의 byte 배열을 만들어서 아이디를 로딩합니다.

                byte[] len_ids = new byte[4];
                fs.Read(len_ids, 0, len_ids.Length);
                int idlen = BitConverter.ToInt32(len_ids,0);
                byte[] idbs = new byte[idlen] ;
                fs.Read(idbs, 0, idbs.Length);
                string id = Encoding.Default.GetString(idbs);

이름도 같은 형태로 읽어옵니다.

                byte[] len_names = new byte[4];
                fs.Read(len_names, 0, len_names.Length);
                int namelen = BitConverter.ToInt32(len_names, 0);
                byte[] namebs = new byte[namelen];
                fs.Read(namebs, 0, namebs.Length);
                string name = Encoding.Default.GetString(namebs);

나이를 읽어온 후에 회원 정보를 ListView에 추가합니다.

                byte[] agebs = new byte[4];
                fs.Read(agebs, 0, agebs.Length);
                int age = BitConverter.ToInt32(agebs,0);
                AddMember(id, name, age);

다음은 Form1의 Load 이벤트 핸들러 코드입니다.

        private void Form1_Load(object sender, EventArgs e)
        {
            if(File.Exists(fname)==false)
            {
                return;
            }
            FileStream fs = File.OpenRead(fname);

            while(fs.Position<fs.Length)
            {
                byte[] len_ids = new byte[4];
                fs.Read(len_ids, 0, len_ids.Length);
                int idlen = BitConverter.ToInt32(len_ids,0);
                byte[] idbs = new byte[idlen] ;
                fs.Read(idbs, 0, idbs.Length);
                string id = Encoding.Default.GetString(idbs);
                byte[] len_names = new byte[4];
                fs.Read(len_names, 0, len_names.Length);
                int namelen = BitConverter.ToInt32(len_names, 0);
                byte[] namebs = new byte[namelen];
                fs.Read(namebs, 0, namebs.Length);
                string name = Encoding.Default.GetString(namebs);
                byte[] agebs = new byte[4];
                fs.Read(agebs, 0, agebs.Length);
                int age = BitConverter.ToInt32(agebs,0);
                AddMember(id, name, age);
            }
            fs.Close();
        }

5. Form1.cs 소스 코드

using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace 날것
{
    public partial class Form1 : Form
    {
        const string fname = "data";
        public Form1()
        {
            InitializeComponent();
        }

        private void btn_add_Click(object sender, EventArgs e)
        {
            string id = tbox_id.Text;
            string name = tbox_name.Text;
            int age = tbar_age.Value;
            AddMember(id, name, age);
            tbox_id.Text = tbox_name.Text = "";
            tbar_age.Value = 0;
            lb_age.Text = "0";
        }
        private void AddMember(string id, string name, int age)
        {
            string[] sitems = new string[] { id, name, age.ToString() };
            ListViewItem lvi = new ListViewItem(sitems);
            lv_member.Items.Add(lvi);
        }
        private void tbar_age_Scroll(object sender, EventArgs e)
        {
            lb_age.Text = tbar_age.Value.ToString();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            if(File.Exists(fname)==false)
            {
                return;
            }
            FileStream fs = File.OpenRead(fname);

            while(fs.Position<fs.Length)
            {
                byte[] len_ids = new byte[4];
                fs.Read(len_ids, 0, len_ids.Length);
                int idlen = BitConverter.ToInt32(len_ids,0);
                byte[] idbs = new byte[idlen] ;
                fs.Read(idbs, 0, idbs.Length);
                string id = Encoding.Default.GetString(idbs);
                byte[] len_names = new byte[4];
                fs.Read(len_names, 0, len_names.Length);
                int namelen = BitConverter.ToInt32(len_names, 0);
                byte[] namebs = new byte[namelen];
                fs.Read(namebs, 0, namebs.Length);
                string name = Encoding.Default.GetString(namebs);
                byte[] agebs = new byte[4];
                fs.Read(agebs, 0, agebs.Length);
                int age = BitConverter.ToInt32(agebs,0);
                AddMember(id, name, age);
            }
            fs.Close();
        }

        
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            FileStream fs = File.Create(fname);
            foreach(ListViewItem lvi in lv_member.Items)
            {
                string id = lvi.SubItems[0].Text;
                string name = lvi.SubItems[1].Text;
                int age = int.Parse(lvi.SubItems[2].Text);

                byte[] idbs = Encoding.Default.GetBytes(id);
                int len_id = idbs.Length;//가변 길이(ID)
                byte[] lenbs = BitConverter.GetBytes(len_id);
                fs.Write(lenbs, 0, lenbs.Length);
                fs.Write(idbs, 0, idbs.Length);
                byte[] namebs = Encoding.Default.GetBytes(name);
                int len_name = namebs.Length;
                byte[] lenbs2 = BitConverter.GetBytes(len_name);
                fs.Write(lenbs2, 0, lenbs2.Length);
                fs.Write(namebs, 0, namebs.Length);
                byte[] agebs = BitConverter.GetBytes(age);
                fs.Write(agebs, 0, agebs.Length);
            }
            fs.Close();
        }
    }
}