2018년 7월 2일 월요일

엑셀 데이터를 이용한 단어 연습기 만들기 (C# 소스, 프로그램 배포 포함)

오랜만에 돌아온 C# 이야기는 최근 일본어를 공부할때 단어 암기에 도움이 되기 위해 만든 단어 연습기에 대한 이야기 입니다.

처음에 일본어 한자를 외우기 위해서 엑셀에다 작업을 했었는데요.


이렇게 엑셀에다 단어의 한자, 뜻, 발음, 종류, 난이도 정도를


모르는 단어 위주로 6000여개를 작성하곤


처음엔 이런식으로 연습을 했는데 이게 너무 불편하고 느려서 만들게 되었습니다.


(완성된 단어 연습기 모습)

단어 연습기는 연습할 단어가 들어있는 엑셀 파일을 선택하면 그중에서 랜덤으로 한 단어를 표시하고 뜻 보기를 클릭하면 한글 뜻과 발음이 보이고, 안다나 모른다 버튼을 클릭하면 다음 단어로 넘어가는 형식입니다.

그리고 아래부분에는 현재 단어를 쉽게 사전에 검색할 수 있도록 단어를 스크립트로 제공하고, 실행되고 몇번째 단어를 보고있는지 정보와 단어를 알고 있는 비율이 어느정도인지에 대한 정보도 담아 연습이 좀 더 재미있게 만들었습니다 ^^

단어 하나하나 버튼으로 클릭하다보니 손가락이 아파서 아래 텍스트 박스에 커서를 둔 다음 키보드 방향키를 이용해 왼쪽 방향키는 안다 버튼을, 오른쪽 방향키는 모른다 버튼을, 아래 방향키는 뜻보기 버튼을 누르는 기능을 추가하여 무진장 빠른 단어 연습 효과를 보이게 되었습니다.

(단어연습기를 테스트 중인 영상)
위 영상은 단어 연습기를 실제로 사용하는걸 찍은 영상인데요.

보시다시피 처음에 실행되면 파일 찾기 버튼을 클릭해 엑셀 파일을 찾고, 엑셀 파일이 로드 된 뒤론 단어 암기를 연습하시면 됩니다.

단어 외우는건 반복이 중요함을 보여주듯 저도 대략 몰랐던 6000여개의 한자 단어들을 매일 2000개씩 보면서 한 달쯤 하니깐 정답 확률이 85%정도에 도달한 상태입니다.

그동안은 혼자 공부 하는동안 사용하며 기능을 하나씩 올리던 녀석인데 혹시 다른분께도 쓰임을 받지 않을까 싶어서 이렇게 작성하게 되었습니다 ^^

귀엽게 봐주세요 ㅎㅎ

(## 2019.06.20 다운로드 링크의 일부분이 지워져 있어서 링크 수정을 했습니다.)
단어 연습기 실행파일 = 다운로드
한자 엑셀 파일 = 다운로드

제 구글 드라이버 링크이니 악성 코드나 광고 낚시 링크가 아닙니다.

단어연습기의 공급자가 빠져서 SmartScreen에서 실행을 차단하는 경우가 발생할 수 있습니다.

그리고 엑셀 데이터는 한자 단어들을 제가 단어장에서 그대로 배낀거라 저작권이 있을꺼 같아 제가 쓰던 6000개 단어에서 앞에 100개정도만 짜른 데이터 입니다.

그럼 단어 연습기 설명은 이것으로 마치고 지금부턴 단어 연습기 소스 설명에 들어가겠습니다.

1. 디자인


전체 디자인은 아래 한자 스크립트를 제공할 텍스트 박스 1개를 제외하곤 모두 레이블이며 파일 찾기 버튼은


아래엔 이렇게 안다, 모른다 버튼이 있습니다. ^^

저는 일본어 한자 연습이라 발음 항목이 있는데 영단어일 경우엔 필요없는 항목이 되겠죠?

2. 소스

일단 전체 소스는
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
 
namespace vacaPractice
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            
            //전체 초기화
            Init();
            FormClosing += new FormClosingEventHandler(closing);
        }
        private void closing(object sender, FormClosingEventArgs e)
        {
            try
            {
                wb.Close(true);
                excelApp.Quit();
            }
            catch
            {
                //Application.Exit();
            }
            Application.Exit();
        }
 
        //excel file path
        string strPath = "";
 
        //current rowNum
        //2 ~ last row
        int intRow = 2;
        int intLastRow = 0;
 
        //today word
        float fltIndex = 0;
        float fltOk = -1;
        float fltNo = -1;
        static Excel.Application excelApp = null;
        static Excel.Workbook wb = null;
        static Excel.Worksheet ws = null;
        static object[,] data;
 
        //전테 초기화 메서드
        void Init()
        {
            //레이블 초기화
            this.label1.Text = "漢字";
            this.label2.Text = "뜻";
            this.label3.Text = "발음";
            this.label4.Text = "종류";
            this.label5.Text = "레벨";
            this.label6.Text = "순번";
            
            //path name
            //strPath = @"D:\공부\voca\nadel\data.xlsx";
            this.label2.ForeColor = Color.White;
            this.label3.ForeColor = Color.White;
            this.label1.AutoSize = false;
            this.label2.AutoSize = false;
            this.label3.AutoSize = false;
            this.label1.Width = 300;
            this.label2.Width = 300;
            this.label3.Width = 300;
        }
 
        //load excel
        void loadExcel()
        {
            try
            {
                excelApp = new Excel.Application();
                wb = excelApp.Workbooks.Open(strPath);
                ws = wb.Worksheets.get_Item(1as Excel.Worksheet;
                Excel.Range rng = ws.UsedRange;
                Excel.Range last = ws.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
                data = rng.Value;
                intLastRow = last.Row;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
 
        //data read
        void reExcal()
        {
            this.label2.ForeColor = Color.White;
            this.label3.ForeColor = Color.White;
 
            //random rowNumber
            rodmRow();
 
            try
            {
                for (int c = 1; c <= 7; c++)
                {
                    if (data[intRow, c] == null)
                    {
                        label1.Text = data[intRow, 3== null ? "" : data[intRow, 3].ToString();
                        label2.Text = data[intRow, 4== null ? "" : data[intRow, 4].ToString();
                        label3.Text = data[intRow, 5== null ? "" : data[intRow, 5].ToString();
                        label4.Text = data[intRow, 6== null ? "" : data[intRow, 6].ToString();
                        label5.Text = data[intRow, 1== null ? "" : data[intRow, 1].ToString();
                        label6.Text = data[intRow, 2== null ? "" : data[intRow, 2].ToString();
                        this.textBox1.Text = data[intRow, 3== null ? "" : data[intRow, 3].ToString();
                        continue;
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                //ReleaseExcelObject(ws);
                //ReleaseExcelObject(wb);
                //ReleaseExcelObject(excelApp);
                fltIndex++;
                intRow++;
                this.label7.Text = fltIndex.ToString();
            }
        }
 
        //뜻보기
        private void button1_Click(object sender, EventArgs e)
        {
            bt1();
        }
 
        void bt1()
        {
            this.label2.ForeColor = Color.Black;
            this.label3.ForeColor = Color.Black;
        }
 
        //안다 버튼
        private void button2_Click(object sender, EventArgs e)
        {
            bt2();
        }
 
        void bt2()
        {
            reExcal();
 
            if (fltOk.Equals(-1))
            {
                fltNo++;
                fltOk++;
            }
            else
            {
                fltOk++;
                claResult();
            }
        }
 
        //모른다 버튼
        private void button3_Click(object sender, EventArgs e)
        {
            bt3();
        }
 
        void bt3()
        {
            reExcal();
            if (fltNo.Equals(-1))
            {
                fltOk++;
                fltNo++;
            }
            else
            {
                fltNo++;
                claResult();
            }
        }
 
        //성적 결과 내기
        void claResult()
        {
            float floatResult = fltOk / (fltIndex -1* 100;
            this.label9.Text = floatResult.ToString("N1");
            this.label11.Text = fltOk.ToString();
            this.label13.Text = fltNo.ToString();
        }
 
        //행번호 랜덤으로 만들기
        void rodmRow()
        {
            Random r = new Random();
            intRow = r.Next(2, intLastRow);
        }
 
        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode.ToString() == "Left")
            {
                bt2();
            }
 
            if (e.KeyCode.ToString() == "Right")
            {
                bt3();
            }
 
            if (e.KeyCode.ToString() == "Down")
            {
                bt1();
            }
        }
 
        //파일찾기 버튼
        private void button4_Click(object sender, EventArgs e)
        {
            OpenFileDialog open = new OpenFileDialog();
            open.Filter = "Excel File(*.xlsx)|*.*";
            open.Title = "단어집 파일을 선택해 주세요.";
 
            if (open.ShowDialog() == DialogResult.OK)
            {
                strPath = open.FileName;
 
                //load excel
                loadExcel();
            }
 
            //성공시 버튼 지우기
            this.button4.Visible = false;
        }
    }//form
cs

이렇게 되고 실행 순서로 하나씩 살펴보면

1. 참조
1
2
3
4
5
6
7
8
9
10
11
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

다른건 다 평범한데 마지막 2개 참조는 엑셀 파일을 읽어야 하니 가져온 참조입니다.

Microsoft.Office.Interop에 관련한 dll 다운로드 링크도 준비했습니다~

Microsoft.Office.Interop.dll 다운로드

2. 변수 선언

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//excel file path
        string strPath = "";
 
        //current rowNum
        //2 ~ last row
        int intRow = 2;
        int intLastRow = 0;
 
        //today word
        float fltIndex = 0;
        float fltOk = -1;
        float fltNo = -1;
        static Excel.Application excelApp = null;
        static Excel.Workbook wb = null;
        static Excel.Worksheet ws = null;
        static object[,] data;
cs
위에서부터

엑셀 파일 경로
엑셀 파일 시작행 (2로 고정하였습니다.)
엑셀 파일 마지막행
연습한 단어 갯수
아는 단어 갯수
모르는 단어 갯수

마지막 4개는 엑셀 데이터 변환할 변수들입니다.

3. 폼 시작

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            
            //전체 초기화
            Init();
            FormClosing += new FormClosingEventHandler(closing);
        }
        private void closing(object sender, FormClosingEventArgs e)
        {
            try
            {
                wb.Close(true);
                excelApp.Quit();
            }
            catch
            {
                //Application.Exit();
            }
            Application.Exit();
        }
        //전체 초기화 메서드
        void Init()
        {
            //레이블 초기화
            this.label1.Text = "漢字";
            this.label2.Text = "뜻";
            this.label3.Text = "발음";
            this.label4.Text = "종류";
            this.label5.Text = "레벨";
            this.label6.Text = "순번";
            
            //path name
            //strPath = @"D:\공부\voca\nadel\data.xlsx";
            this.label2.ForeColor = Color.White;
            this.label3.ForeColor = Color.White;
            this.label1.AutoSize = false;
            this.label2.AutoSize = false;
            this.label3.AutoSize = false;
            this.label1.Width = 300;
            this.label2.Width = 300;
            this.label3.Width = 300;
        }
#7에서 초기화를 시작하여

#32~#37은 레이블에 초기 텍스트를 넣고

#42~#43은 뜻과 발음은 안보이게 글 색상을 변경했고

#45~#51은 단어가 큼직히 잘 보이고 길어도 짤리지 말라고 고정 사이즈 옵션을 죽였습니다.

#10은 단어연습기 종료 이벤트를 따와서

#13~#26동안 종료하기전 열려있는 엑셀 파일을 닫는 역할을 넣었습니다.


4. 파일 찾기와 엑셀 데이터 로드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
        //파일찾기 버튼
        private void button4_Click(object sender, EventArgs e)
        {
            OpenFileDialog open = new OpenFileDialog();
            open.Filter = "Excel File(*.xlsx)|*.*";
            open.Title = "단어집 파일을 선택해 주세요.";
            if (open.ShowDialog() == DialogResult.OK)
            {
                strPath = open.FileName;
                //load excel
                loadExcel();
            }
            //성공시 버튼 지우기
            this.button4.Visible = false;
        }
        //load excel
        void loadExcel()
        {
            try
            {
                excelApp = new Excel.Application();
                wb = excelApp.Workbooks.Open(strPath);
                ws = wb.Worksheets.get_Item(1) as Excel.Worksheet;
                Excel.Range rng = ws.UsedRange;
                Excel.Range last = ws.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
                data = rng.Value;
                intLastRow = last.Row;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

사용자가 파일 찾기 버튼을 클릭하면 #2가 실행되고 엑셀 파일 선택을 받으면 #18에서 버튼의 시각화를 false 시켜 안다, 모른다 버튼이 활성화 되도록 했습니다.

엑셀 파일이 선택되면 #14에서 엑셀 데이터 로드를 호출하여

#26~#31동안 엑셀의 첫번째 워크 시트에 있는 전체 데이터를 로드하여 변수로 가지고 있도록 합니다.

특히 #30은 데이터의 마지막 행을 구하여 전체 데이터 범위를 파악하도록 합니다.


5. 안다, 모른다 버튼 클릭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
        //안다 버튼
        private void button2_Click(object sender, EventArgs e)
        {
            bt2();
        }
        void bt2()
        {
            reExcal();
            if (fltOk.Equals(-1))
            {
                fltNo++;
                fltOk++;
            }
            else
            {
                fltOk++;
                claResult();
            }
        }
        //모른다 버튼
        private void button3_Click(object sender, EventArgs e)
        {
            bt3();
        }
        void bt3()
        {
            reExcal();
            if (fltNo.Equals(-1))
            {
                fltOk++;
                fltNo++;
            }
            else
            {
                fltNo++;
                claResult();
            }
        }
        //성적 결과 내기
        void claResult()
        {
            float floatResult = fltOk / (fltIndex -1) * 100;
            this.label9.Text = floatResult.ToString("N1");
            this.label11.Text = fltOk.ToString();
            this.label13.Text = fltNo.ToString();
        }
        
        //행번호 랜덤으로 만들기
        void rodmRow()
        {
            Random r = new Random();
            intRow = r.Next(2, intLastRow);
        }
        
        //load excel
        void loadExcel()
        {
            try
            {
                excelApp = new Excel.Application();
                wb = excelApp.Workbooks.Open(strPath);
                ws = wb.Worksheets.get_Item(1) as Excel.Worksheet;
                Excel.Range rng = ws.UsedRange;
                Excel.Range last = ws.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
                data = rng.Value;
                intLastRow = last.Row;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        //data read
        void reExcal()
        {
            this.label2.ForeColor = Color.White;
            this.label3.ForeColor = Color.White;
            //random rowNumber
            rodmRow();
            try
            {
                for (int c = 1; c <= 7; c++)
                {
                    if (data[intRow, c] == null)
                    {
                        label1.Text = data[intRow, 3] == null ? "" : data[intRow, 3].ToString();
                        label2.Text = data[intRow, 4] == null ? "" : data[intRow, 4].ToString();
                        label3.Text = data[intRow, 5] == null ? "" : data[intRow, 5].ToString();
                        label4.Text = data[intRow, 6] == null ? "" : data[intRow, 6].ToString();
                        label5.Text = data[intRow, 1] == null ? "" : data[intRow, 1].ToString();
                        label6.Text = data[intRow, 2] == null ? "" : data[intRow, 2].ToString();
                        this.textBox1.Text = data[intRow, 3] == null ? "" : data[intRow, 3].ToString();
                        continue;
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                //ReleaseExcelObject(ws);
                //ReleaseExcelObject(wb);
                //ReleaseExcelObject(excelApp);
                fltIndex++;
                intRow++;
                this.label7.Text = fltIndex.ToString();
            }
        }

두 버튼을 클릭하면 로드한 데이터를 적절하게 배치하여 뿌려줄 매서드를 호출하고 (#8, #29)

호출된 매서드는 가장 먼저 보여줄 단어를 랜덤으로 정하기 위해 랜덤 숫자를 만들 매서드를 호출하고 (#87)

호출된 매서드는 #56~#57동안 2~마지막 데이터가 있는 행 사이의 정수 중에서 하나를 선택해 변수에 저장합니다.

랜덤수를 받았으면 #91~#105동안 정해진 레이블에 데이터를 넣어주고, 몇번째 연습한 단어인지 작성한 다음 (#118~#119) 종료됩니다.

데이터를 다 뿌린 다음엔 첫 실행일 경우엔 변수만 바꿔주고, 그 외엔 아는 갯수, 모르는 갯수 변수를 이용해 현재 성적을 작성해 줄 매서드를 호출 합니다. (#18,#39)


6. 뜻보기 버튼과 키보드 이벤트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
        //뜻보기
        private void button1_Click(object sender, EventArgs e)
        {
            bt1();
        }
        void bt1()
        {
            this.label2.ForeColor = Color.Black;
            this.label3.ForeColor = Color.Black;
        }
        
        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode.ToString() == "Left")
            {
                bt2();
            }
            if (e.KeyCode.ToString() == "Right")
            {
                bt3();
            }
            if (e.KeyCode.ToString() == "Down")
            {
                bt1();
            }
        }
뜻보기 버튼이 클릭되면 해당 레이블 글자색을 바꿔주며 (#8~#9)

텍스트 박스에 키보드 클릭이벤트를 받아와서 방향키 클릭에 따라 해당 클릭 이벤트를 연결해 줍니다.

혼자 생각날때마다 기능을 달고 혼자 쓸 생각에 만들었던거라 예외처리가 매우 안되어 있음을 참조해주세요 ^^;;

이로써 단어 연습기 설명도 마치겠습니다.

긴 글 봐주셔서 감사하고, 단어연습기에 대한 질문이나 문의 개조 등등 모두 댓글로 남겨 주시면 제가 바로 해결하겠습니다. 감사합니다.!
Share: