미디 분석 프로그램 – 5. 미디분석기 5.1 화면 배치 및 미디 파일 열기

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

1. 해야 할 일

이전강의까지 미디 파일을 분석하는 ehmidilib를 만들었습니다.

(*진행하면서 ehmidilib에 새로운 형식 및 기능을 추가합니다. *)

이번 강의부터 ehmidilib를 이용하는 미디분석기 프로그램을 작성하기로 합시다.

미디분석기 프로그램은 Windows Forms 앱(.NET Framework) 입니다.

이번 강의에서는 메인 폼에 컨트롤을 배치하고 미디 파일을 열어서 청크 목록을 보여주는 부분까지 구현할거예요.

미디 파일을 여는 작업을 위해 “파일 열기 대화상자”인 OpenFileDialog를 사용합니다.

미디 파일에 청크 목록을 분석하는 작업은 ehmidilib에 MidiParser 클래스를 추가하여 구현할 거예요.

MidiParser 클래스에서 미디 파일을 분석하는 작업은 Thread를 이용하여 비동기로 처리합니다.

(Thread를 이용한 비동기 처리에 관한 부분은 상세 설명하지 않습니다.)

MidiParser에서 청크를 발견할 때마다 이벤트 처리를 수행합니다.

이를 위해 청크 발견 이벤트 인자 형식과 대리자를 ehmidilib에 정의합니다.

2. 화면 배치

솔루션에 “미디분석기” 프로젝트를 추가하세요.

프로젝트 템플릿은 Windows Forms 앱(.NET Framework)입니다.

기본으로 제공하는 Form1.cs 파일 이름을 MainForm.cs로 바꿀게요.

[그림] MainForm 화면 배치

MenuStrip을 추가하고 메뉴 항목에 File을 추가합니다. 추가한 메뉴 항목 이름은 fmi라고 정할게요.

화면 왼쪽에 청크 목록을 보여줄 ListBox (lbox_chuck)를 추가합니다.

화면 오른쪽 위에 선택한 청크의 상세 정보를 계층 구조로 보여줄 TreeView(tv_midi)를 추가합니다.

화면 오른쪽 아래에 선택한 청크의 바이너리 정보를 16진수로 보여줄 DataGridView(dgv_hexa)를 추가합니다.

열 추가를 통해 No, 1~16까지 17개의 열을 추가합니다.

3. 파일 열기 구현

속성 창을 이용해서 File 메뉴 항목인 fmi의 Click 이벤트 핸들러를 등록하세요.

        private void fmi_Click(object sender, EventArgs e)
        {

OpenFileDialog 개체를 생성하여 디폴트 확장자와 Filter를 지정합니다.

필터에 지정하는 내용은 “화면 표시1|확장자1|화면 표시2|확장자2…”처럼 화면 표시할 내용과 확장자를 나열합니다.

여기에서는 미디 파일만 열기 때문에 “미디 파일|*.mid”라고 설정합니다.

            OpenFileDialog ofd = new OpenFileDialog();
            ofd.DefaultExt = "미디 파일";
            ofd.Filter = "미디 파일|*.mid";

파일 열기 대화상자에서 열 파일을 선택하면 ShowDialog 메서드를 호출한 결과가 DialogResult.OK입니다.

            if(ofd.ShowDialog() == DialogResult.OK)
            {

선택한 파일 이름으로 MainForm의 타이틀을 설정합니다.

그리고 MidiParser 개체를 생성하고 Chunk 발견 이벤트 핸들러를 등록하고 비동기로 분석을 요청합니다.

                MidiParser mp = new MidiParser(ofd.FileName);
                mp.FindedChunk += Mp_FindedChunk;
                mp.AsyncParse();

다음은 fmi_Click 이벤트 핸들러 코드입니다.

        private void fmi_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.DefaultExt = "미디 파일";
            ofd.Filter = "미디 파일|*.mid";
            if(ofd.ShowDialog() == DialogResult.OK)
            {
                Text = ofd.FileName;
                MidiParser mp = new MidiParser(ofd.FileName);
                mp.FindedChunk += Mp_FindedChunk;
                mp.AsyncParse();
            }
        }

MP_FindChunk 메서드에서는 전달받은 이벤트 인자의 Chunk를 lbox_chunk의 아이템 항목에 추가합니다.

        private void Mp_FindedChunk(object sender, FindChunkEventArgs e)
        {
            lbox_chunk.Items.Add(e.Chunk);
        }

4. ehmidilib 추가 구현

먼저 청크를 발견하였을 때 이벤트처리에 사용할 인자와 대리자 형식을 정의합시다.

ehmidilib 프로젝트에 FindChunkEventArgs 클래스를 추가하세요.

이벤트에 관한 사항은 구현이 어려운 것이 아니라 왜 필요한 것인지 인지하고 필요한 것을 설계하는 것입니다.

이벤트를 잘 모르면 대리자이벤트 게시글을 참고하세요.

using System;

namespace ehmidi
{
    public delegate void FindChunkEventHandler(object sender, FindChunkEventArgs e);
    public class FindChunkEventArgs:EventArgs
    {
        public Chunk Chunk
        {
            get;
        }
        public FindChunkEventArgs(Chunk chunk)
        {
            Chunk = chunk;
        }
    }
}

MidiParser 클래스를 추가하세요.

    public class MidiParser
    {

청크 발견 이벤트 멤버를 캡슐화합니다.

        public event FindChunkEventHandler FindedChunk;

생성자에서 파일 이름을 전달받아 속성에 설정합니다.

        public string FileName { get; }
        public MidiParser(string fname)
        {
            FileName = fname;
        }

파일을 열어 파일 끝까지 Chunk를 분석하는 Parse 메서드를 정의합시다.

Chunk 개체를 하나 분석할 때마다 이벤트를 발생합니다.

        public void Parse()
        {
            FileStream fs = new FileStream(FileName, FileMode.Open);
            while(fs.Position<fs.Length)
            {
                Chunk chunk = Chunk.Parse(fs);
                if(FindedChunk !=null)
                {
                    FindedChunk(this, new FindChunkEventArgs(chunk));
                }
            }
        }

Parse 메서드 수행을 비동기로 수행할 수 있는 AsyncParse 메서드를 제공합시다.

여기에서는 Thread를 이용할게요.

        public void AsyncParse()
        {
            Thread thread = new Thread(Parse);
            thread.Start();
        }

다음은 MidiParser.cs 소스 코드의 내용입니다.

using System.IO;
using System.Threading;

namespace ehmidi
{
    public class MidiParser
    {
        public event FindChunkEventHandler FindedChunk;
        public string FileName { get; }
        public MidiParser(string fname)
        {
            FileName = fname;
        }
        public void AsyncParse()
        {
            Thread thread = new Thread(Parse);
            thread.Start();
        }
        public void Parse()
        {
            FileStream fs = new FileStream(FileName, FileMode.Open);
            while(fs.Position<fs.Length)
            {
                Chunk chunk = Chunk.Parse(fs);
                if(FindedChunk !=null)
                {
                    FindedChunk(this, new FindChunkEventArgs(chunk));
                }
            }
        }
    }
}

5. 테스트 및 문제점

현재 작성한 것으로 미디분석기를 실행하면 잘 동작합니다.

하지만 디버그 모드로 실행하면 크로스 스레드 문제가 발생합니다.

Windows Forms 앱에서는 Forms이나 컨트롤은 생성한 스레드가 아닌 다른 스레드에서 사용하면 크로스 스레드 문제가 발생합니다.

다음 강의에서는 이 문제를 해결하는 것부터 시작합니다.

크로스 스레드에 관해 좀 더 알고 싶으면 크로스 스레드 발생 원인 및 해결하기 게시글을 참고하세요.