안녕하세요. 언제나휴일입니다.
1. 해야 할 일
이번 강의에서는 지난 강의에서 청크 목록을 ListBox에 추가할 때 발생하는 크로스스레드 문제를 해결할 거예요.
그리고 ListBox에 청크를 선택하면 청크의 원본 이진 데이터를 Hexa 값으로 DataGridView에 보여주는 작업을 할 거예요.
이 외에도 Header 클래스에 Division에 관한 코드를 수정합니다.
2. 크로스 스레드 문제 해결
크로스 스레드 문제는 폼이나 컨트롤을 생성한 스레드가 아닌 스레드에서 폼이나 컨트롤의 속성을 변경하는 등의 작업을 할 때 발생합니다.
이에 관한 자세한 사항은 크로스 스레드 발생 원인 및 해결하기를 참고하세요.
폼이나 컨트롤에는 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; }
3. 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(); } } }
4. 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(); } } }
5. 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]; } } } }