프로젝트/미디 분석 프로그램

[C#] 미디 분석 프로그램 만들기– 5. 미디분석기 5.2 크로스 스레드 문제 해결 및 Hexa 값 보기

언휴 2024. 1. 17. 08:28

1. 유튜브 동영상 강의

미디 분석 프로그램 - 미디 분석기

2. 해야 할 일

이번 강의에서는 지난 강의에서 청크 목록을 ListBox에 추가할 때 발생하는 크로스스레드 문제를 해결할 거예요.

그리고 ListBox에 청크를 선택하면 청크의 원본 이진 데이터를 Hexa 값으로 DataGridView에 보여주는 작업을 할 거예요.

이 외에도 Header 클래스에 Division에 관한 코드를 수정합니다.

3. 크로스 스레드 문제 해결

크로스 스레드 문제는 폼이나 컨트롤을 생성한 스레드가 아닌 스레드에서 폼이나 컨트롤의 속성을 변경하는 등의 작업을 할 때 발생합니다.

이에 관한 자세한 사항은 크로스 스레드 발생 원인 및 해결하기를 참고하세요.

폼이나 컨트롤에는 InvokeRequired 속성을 갖고 있습니다.

이 값이 True일 때 크로스 스레드 문제가 발생한다는 것을 의미합니다. 현재 스레드가 폼이나 컨트롤을 생성한 스페드가 아니라는 것이죠.

이 때 폼과 컨트롤에 있는 Invoke 메서드를 호출하여 대리자와 대리자에 전달할 인자 목록을 전달하면 .NET Framework에게 폼(혹은 컨트롤)을 생성한 스레드가 대리자를 수행하게 해 줍니다.

다음 코드는 이를 반영한 코드입니다.

지난 강의 코드에서는 lbox_chunk에 청크 개체를 보관하는 것까지만 수행했었죠.

여기에서는 TreeNode를 생성하고 청크와 TreeNode를 매핑한 ChnukNode 개체를 생성하여 이를 보관하게 하였습니다.

        private void Mp_FindedChunk(object sender, FindChunkEventArgs e)
        {
            if (this.InvokeRequired)//현재 스레드는 폼을 생성한 스레드가 아니다. 크로스 스레드다!
            {
                FindChunkEventHandler dele = Mp_FindedChunk;
                object[] objs = new object[] { sender, e };
                this.Invoke(dele, objs);//.Net Framework에게 폼을 생성한 스레드가 대리자를 수행하도록 지시
            }
            else
            {
                TreeNode tn = MakeChunkNode(e.Chunk);
                ChunkNode cn = new ChunkNode(e.Chunk, tn);
                lbox_chunk.Items.Add(cn);
            }
        }

MakeChunkNode는 다음처럼 간략하게 정의하게 상세 구현은 마지막 강의인 다음 강의에서 할게요.

        private TreeNode MakeChunkNode(Chunk chunk)
        {
            TreeNode tn = new TreeNode(chunk.ToString());
            tn.Tag = chunk;
            //기타 사항은 to be defined
            return tn;
        }

4. ChunkNode 클래스 정의

미디 분석기 프로그램에 ChunkNode 클래스를 추가하세요.

이 부분은 lbox_chunk에 보관할 개체 형식을 정의하는 것입니다.

이와 같이 정의하는 이유는 lbox_chunk에 항목을 선택하였을 때 TreeView의 정보를 바꿔주기 위함입니다.

using ehmidi;
using System.Windows.Forms;

namespace 미디분석기
{
    public class ChunkNode
    {
        public TreeNode Node
        {
            get;
        }
        public Chunk Chunk
        {
            get;
        }
        public ChunkNode(Chunk chunk, TreeNode node)
        {
            Chunk = chunk;
            Node = node;
        }
        public override string ToString()
        {
            return Chunk.ToString();
        }
    }
}

5. lbox_chunk 선택 항목 변경 이벤트 핸들러 구현

속성 창을 이용해서 lbox_chunk의 SelectIndexChanged 이벤트 핸들러를 등록하세요.

tv_midi의 Nodes 컬렉션을 비워줍니다.

그리고 선택 항목을 ChnukNode 형식으로 참조합니다.

청크 노드의 노드로 TreeView를 설정하고 청크의 Buffer로 Hexa 코드를 보여줍니다.

        private void lbox_chunk_SelectedIndexChanged(object sender, EventArgs e)
        {
            tv_midi.Nodes.Clear();
            if(lbox_chunk.SelectedIndex == -1)
            {
                return;
            }
            ChunkNode cn = lbox_chunk.SelectedItem as ChunkNode;
            SetTreeNode(cn.Node);
            SetHexaView(cn.Chunk.Buffer);
        }

트리 노드를 설정하는 SetTreeNode에서는 tv_midi의 Nodes 컬렉션에 node를 추가하고 펼쳐줍니다.

        private void SetTreeNode(TreeNode node)
        {
            tv_midi.Nodes.Add(node);
            node.Expand();
        }

Hexa 코드를 보여주는 SetHexaView 메서드를 구현합시다.

먼저 dgv_hexa에 Rows 컬렉션을 비워줍니다.

16개의 데이터를 하나의 행으로 보여주게 구현합니다.

        private void SetHexaView(byte[] buffer)
        {
            dgv_hexa.Rows.Clear();
            int len = buffer.Length - 16;
            int i, n;
            for(i=0,n=1;i<len;i+=16,n++)
            {
                MakeDataGridRow(buffer, i, 16, n);
            }
            MakeDataGridRow(buffer, i, buffer.Length-i, n);
        }

        private void MakeDataGridRow(byte[] buffer, int offset, int length, int n)
        {
            int index = dgv_hexa.Rows.Add();
            DataGridViewRow dr = dgv_hexa.Rows[index];
            dr.Cells[0].Value = n.ToString();
            for(int i = 0; i<length;i++)
            {
                dr.Cells[i + 1].Value = string.Format("{0:X2}", buffer[offset + i]);
            }
        }

현재까지 작성한 MainForm.cs 소스 코드입니다.

using ehmidi;
using System;
using System.Windows.Forms;

namespace 미디분석기
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

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

        private void Mp_FindedChunk(object sender, FindChunkEventArgs e)
        {
            if (this.InvokeRequired)//현재 스레드는 폼을 생성한 스레드가 아니다. 크로스 스레드다!
            {
                FindChunkEventHandler dele = Mp_FindedChunk;
                object[] objs = new object[] { sender, e };
                this.Invoke(dele, objs);//.Net Framework에게 폼을 생성한 스레드가 대리자를 수행하도록 지시
            }
            else
            {
                TreeNode tn = MakeChunkNode(e.Chunk);
                ChunkNode cn = new ChunkNode(e.Chunk, tn);
                lbox_chunk.Items.Add(cn);
            }
        }

        private TreeNode MakeChunkNode(Chunk chunk)
        {
            TreeNode tn = new TreeNode(chunk.ToString());
            tn.Tag = chunk;
            //기타 사항은 to be defined
            return tn;
        }

        private void lbox_chunk_SelectedIndexChanged(object sender, EventArgs e)
        {
            tv_midi.Nodes.Clear();
            if(lbox_chunk.SelectedIndex == -1)
            {
                return;
            }
            ChunkNode cn = lbox_chunk.SelectedItem as ChunkNode;
            SetTreeNode(cn.Node);
            SetHexaView(cn.Chunk.Buffer);
        }

        private void SetHexaView(byte[] buffer)
        {
            dgv_hexa.Rows.Clear();
            int len = buffer.Length - 16;
            int i, n;
            for(i=0,n=1;i<len;i+=16,n++)
            {
                MakeDataGridRow(buffer, i, 16, n);
            }
            MakeDataGridRow(buffer, i, buffer.Length-i, n);
        }

        private void MakeDataGridRow(byte[] buffer, int offset, int length, int n)
        {
            int index = dgv_hexa.Rows.Add();
            DataGridViewRow dr = dgv_hexa.Rows[index];
            dr.Cells[0].Value = n.ToString();
            for(int i = 0; i<length;i++)
            {
                dr.Cells[i + 1].Value = string.Format("{0:X2}", buffer[offset + i]);
            }
        }

        private void SetTreeNode(TreeNode node)
        {
            tv_midi.Nodes.Add(node);
            node.Expand();
        }
    }
}

6. Header 클래스 내용 수정

Header 클래스에 Division 부분은 값이 음수일 때 “프레임 당 틱 수 * 초당 프레임 수”입니다.

이에 맞게 Tick 수인지 판별하는 IsTicks 속성을 추가하고 생성자에서 이를 구분하여 Division을 설정하게 수정합니다.

namespace ehmidi
{
    public class Header : Chunk
    {
        public int Format
        {
            get
            {
                return StaticFuns.ConvertHostorderS(Data, 0);
            }
        }
        public int TrackCount
        {
            get
            {
                return StaticFuns.ConvertHostorderS(Data, 2);
            }
        }
        public int Division
        {
            get;            
        }
        public bool IsTicks
        {
            get;
        }
        public Header(int ctype, int length, byte[] buffer) : base(ctype, length, buffer)
        {
            short dd = StaticFuns.ConvertHostorderS(buffer, 4);
            if(dd>=0)
            {
                IsTicks = false;
                Division = dd;
            }
            else
            {
                IsTicks = true;//프레임 당 틱 수 * 초당 프레임 수
                Division = (buffer[4] & 0x7F) * buffer[5];
            }
        }
    }
}