10장. 고급 폼
어제 메인메뉴에 이어서 시작
새 파일의 속성창에서 ShortcutKeys 프로퍼티에 각각 컨트롤로 시작하는 단축키를 지정해 준다
속성창에서 이미지를 넣어주면 VS의 메뉴들처럼 아이콘을 넣어줄 수 있다.
private void MnuNewFile_Click(object sender, EventArgs e) // 새 파일 클릭 이벤트
{
textBox1.Text += MnuNewFile.Text + Environment.NewLine;
//실제 새 파일 로직을 넣어야함
}
private void 열기OToolStripMenuItem_Click(object sender, EventArgs e) // 열기 클릭
{
textBox1.Text += 열기OToolStripMenuItem.Text + Environment.NewLine;
//실제 새 파일 로직을 넣어야함
}
private void 저장SToolStripMenuItem_Click(object sender, EventArgs e) // 저장 클릭
{
textBox1.Text += 저장SToolStripMenuItem.Text + Environment.NewLine;
MessageBox.Show("저장이 완료되었습니다");
}
private void 종료XToolStripMenuItem_Click(object sender, EventArgs e) // 종료 클릭
{
Application.Exit(); // 종료 명령
}
프로그램 정보(저작권, 만든사람 등)를 따로 띄우기위해 새 항목을 띄워보자
private void 프로그램정보AToolStripMenuItem_Click(object sender, EventArgs e)
{
AboutBox aboutBox = new AboutBox(); // 새 항목으로 만든 aboutBox 출력
aboutBox.ShowDialog();
}
프로그램 정보에 더블클릭해서 해당 메서드를 작성
상황 메뉴
컨트롤 위에서 마우스의 오른쪽 버튼을 클릭했을때 표시되는 팝업 메뉴이며, 현재 애플리케이션의 상태가 반영되면서
상황에 따라 독자적인 메뉴 항목을 가진다.
상황 메뉴의 작성
- ContextMenuStrip 컴포넌트의 추가
- 상황 메뉴는 메인 메뉴와 동일한 프로퍼티와 이벤트를 가진다.
- 완성된 상황 메뉴를 해당 폼 또는 컨트롤의 ContextMenu 프로퍼티에 설정
- 컨트롤마다 상황 메뉴를 가질 수 있기 때문에 적용하고자 하는 컨트롤의 ContextMenu 프로퍼티에 설정
- 폼의 ContextMenu 프로퍼티에 작성된 contextMenu1 컴포넌트를 지정한 예
private void textBox1_MouseClick(object sender, MouseEventArgs e) // 텍스트박스에 우클릭했을때 이벤트
{
if(e.Button == MouseButtons.Right)
{
contextMenuStrip1.Show(e.Location);
}
}
단축문자
- 메뉴항목의 이름에 &를 붙인 형태이다(파일(&F), 복사(&C))
- 메뉴 표시줄에 나타나는 메인 메뉴 사이에서는 반드시 유일해야한다.
- 메인 메뉴의 서로 다른 메뉴 항목에 대해서는 중복 사용 가능하다.
단축키
- 메뉴항목의 Shortcut 프로퍼티를 통해 설정
- 단축키는 하나의 애플리케이션에 포함
마우스 다루기
마우스 : 사용자가 마우스를 이동하거나 클릭하면 이벤트가 발생한다.
마우스 이벤트
이동 이벤트 : 사용자가 마우스의 위치를 이동시킬 경우 발생한다.
선택 이벤트 : 사용자가 마우스의 버튼을 클릭할 경우 발생한다.
마우스 이동 이벤트
MouseEnter : 마우스 포인터가 컨트롤이나 폼 영역에 들어올 때 발생
MouseHover : 마우스 포인터가 컨트롤이나 폼에서 이동하는 것을 멈출 때 발생, 매번 발생하지 않으며 처음 멈출 때만 발생
MouseLeave : 마우스 포인터가 컨트롤이나 폼 영역을 벗어날 때 발생
MouseMove : 마우스 포인터가 새로운 영역으로 이동할 때 발생
MouseWheel : 입력포커스를 가지고 있는 컨트롤이나 폼 위에서 마우스 휠 버튼을 회전시킬 때 발생
MouseEventArgs 클래스의 프로퍼티
Button : 마우스의 상태를 나타내는 MouseButtons 열거형 값, MouseButtons 열거형
- Left : 마우스 왼쪽 버튼을 클릭한 상태
- Middle : 마우스 중앙 버튼을 클릭한 상태
- None : 마우스를 누르지 않은 상태
- Right : 마우스 오른쪽 버튼을 클릭한 상태
- XButton1 : 첫 번째 X버튼을 클릭한 상태
- XButton2 : 두 번째 X버튼을 클릭한 상태
Clicks : 마우스 버튼을 클릭한 횟수
Delta : 마우스 휠의 회전수(휠을 1회 돌리는 것)를 나타내는 값
X : 클라이언트 좌표 내에서, 마우스 위치의 X좌표
Y : 클라이언트 좌표 내에서, 마우스 위치의 Y좌표
마우스의 좌표를 출력해보자
private void textBox1_MouseMove(object sender, MouseEventArgs e)
{
LblMouseLocation.Text = $"(X, Y)= ( {e.X}, { e.Y})";
}
키보드 다루기
- 윈폼 애플리케이션은 사용자로부터 직접 키보드 입력을 받지 않고 텍스트 박스와 같은 컨트롤을 이용하여 키보드 입력이 이루어진다.
- C#은 컨트롤을 이용한 키보드 입력 이외에도 사용자 입력을 직접 처리할 수 있는 방법을 제공한다.
입력 포커스
- 키보드를 통해 입력이 가능한 컨트롤을 표시해준다.
- 키보드를 이용한 사용자의 입력은 여러 개의 컨트롤에서 동시에 사용할 수 없다.
- 입력 포커스를 가지는 컨트롤만이 키보드를 통해 사용자의 입력을 받을 수 있다.
- 입력 포커스를 가지는 컨트롤은 자신의 형태를 변경한다.
private void Form1_Activated(object sender, EventArgs e)
{
button1.Focus();
}
키보드 이벤트
KeyDown : 사용자가 키를 누를 때 발생하며, 키 상태와 보조키를 위한 Keys 열거형 정보를 사용할 수 있다.
KeyPress : 키가 완전히 눌러진 상태에서 발생하며, 키 문자에 대한 정보를 사용할 수 있다.
KeyUp : 키를 떼었을 때 발생하며 키 상태와 보조키를 위한 Keys 열거형 정보를 사용할 수 있다.
이벤트 발생순서
KeyDown ㅡ> KeyPress ㅡ> KeyUp
전에 했던 체크리스트 박스 예제에서 값입력 후 엔터를 쳐보자
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if(e.KeyChar == 13)
{
button1_Click(sender, new EventArgs());
}
}
툴바(ToolStrip)
private void toolStripButton1_Click(object sender, EventArgs e) // 툴바 버튼1 클릭 이벤트
{
MnuNewFile_Click(sender, e); // 새 파일 눌렀을때 이벤트 똑같이나옴(메서드니까)
}
StatusStrip을 깔아보고 툴바에 기능을 더 추가해보자
private void MnuNewFile_Click(object sender, EventArgs e) // 새 파일 클릭 이벤트
{
textBox1.Text += MnuNewFile.Text + Environment.NewLine;
toolStripStatusLabel1.Text = MnuNewFile.Text;
//실제 새 파일 로직을 넣어야함
}
private void Form1_Load(object sender, EventArgs e)
{
toolStripComboBox1.Items.Add("Python");
toolStripComboBox1.Items.Add("C");
toolStripComboBox1.Items.Add("C++");
toolStripComboBox1.Items.Add("Java");
toolStripComboBox1.Items.Add("C#");
}
// 이 구문들은 메인메뉴있던 구문에서 메서드를 추가했다
11장. 고급 컨트롤
리스트 뷰
트리 뷰
업다운 컨트롤
트랙 바
프로그레스 바
타이머 컴포넌트
리스트 뷰
리스트 상자와 유사한 형태를 지니며 목록을 구조적으로 장식할 수 있는 컨트롤
리스트 뷰의 형태
- View 프로퍼티의 값에 따라 다양한 형태를 가진다.
- System.Windows.Forms 네임스페이스에 포함된 View열거형을 값으로가짐
SelectedItems 프로퍼티
리스트 뷰에서 선택된 항목을 저장하는 프로퍼티
반환형
ListViewItem 클래스형 : 리스트 뷰의 MultiSelect 프로퍼티가 거짓일 경우
ListViewItem 클래스의 배열형 : 리스트 뷰의 MultiSelect 프로퍼티가 참일 경우
ImageList를 추가하고 아이콘을 넣어보자
이벤트 메서드를 작성해보자
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
switch (comboBox1.SelectedIndex)
{
case 0: // LargeIcon
listView1.View = View.LargeIcon;
break;
case 1: // Details
listView1.View = View.Details;
break;
case 2: // SmallIcon
listView1.View = View.SmallIcon;
break;
case 3: // list
listView1.View = View.List;
break;
case 4: // Tiles
listView1.View = View.SmallIcon;
break;
default:
listView1.View = View.Details;
break;
}
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
foreach (ListViewItem item in listView1.SelectedItems)
{
ListViewSubItemCollection subItem = item.SubItems;
label1.Text = $"{item.Text}의 국가 번호는 {subItem[1].Text}";
}
}
트리뷰
목록을 계층적으로 보여주기 위한 컨트롤이며, 노드를 계층적으로 표시해주고 노드에 이미지 아이콘을 추가할 수 있다.
(파일같은거 다운 받을때 폴더가 +로 묶여져있고 +를 클릭하면 폴더 안의 폴더나 파일이 있는 것을 생각하면된다)
트리뷰의 노드
- 트리 노드 편집기를 통해 생성하며 TreeNode 클래스의 객체이다.
- TreeView 컨트롤의 Nodes 프로퍼티에 TreeNodeCollection 형으로 저장
- TreeNodeCollection 클래스의 메소드를 통해 노드의 편집이 가능하다.
트리 뷰의 노드는 TreeNode 클래스의 객체
TreeNodeCollection 클래스의 메서드를 사용할 경우 TreeNode 클래스의 객체를 생성해야 함
TreeNode 클래스의 생성자
- public TreeNode(string label);
- public TreeNode(string label, int idx1, int idx2);
1. label : 노드 이름에 해당하는 문자열
2. idx1 : 노드가 선택되지 않았을 때의 이미지 인덱스
3. idx2 : 노드가 선택되었을 때의 이미지 인덱스
예제를 작성해보자
노드 편집에서 루트를 3개 추가해주고 3개의 Text를 바꿔주자
업다운 컨트롤
주어진 목록에서 항목을 선택할 수 있는 컨트롤
- 업다운 버튼을 이용하여 필요한 값을 선택
- 스핀 컨트롤(spin control)
영역 업다운 컨트롤
- 문자열로 이루어진 항목에서 특정한 항목을 선택할 수 있는 컨트롤
수치적 업다운 컨트롤
- 지정한 범위 내에서 수치적 값을 선택할 수 있는 컨트롤
트랙바
범위 내에서 값을 선택할 수 있는 컨트롤 (바를 옮기면 최소값에서 최대값까지 선택해서 쓸 수 있다)
슬라이더와 눈금으로 구성되어있다.
슬라이더의 이동
- 마우스 드래그
- 슬라이더의 좌우 공간 클릭
- 마우스 휠의 회전
- 키보드의 좌우 방향키, 페이지 업다운키
프로그레스바
1. 프로그레스바는 작업의 진행상황을 보여주는 컨트롤이다.
2. 좌측에서 우측으로 사각형의 조각을 채우면서 진행된다.
3. 애플리케이션의 설치과정이나 파일 복사과정에서 사용한다.
프로그레스 바의 추가
【도구상자】 ㅡ> 【ProgressBar】를 선택하여 폼에 추가
프로그레스 바의 값에 대한 범위를 설정
프로그레스 바의 프로퍼티를 통해 설정
Maximum : 프로그레스 바의 최대값
Minimum : 프로그레스 바의 최소값
private void button1_Click(object sender, EventArgs e)
{
for(int i = 0; i<100000; i++)
{
progressBar1.Value = i;
}
}
타이머
타이머는 주기적인 간격으로 이벤트를 발생시키는 컴포넌트이다.
- 배경작업을 처리할 때 주로 사용
- 일정한 간격에 따라 Tick 이벤트를 발생
1. Interval 프로퍼티를 통해 간격을 설정
2. 밀리 초(millisecond, 1/1000초)를 사용
- 주기적으로 발생시키기 위해서는 Enable 프로퍼티를 참으로 설정
- 항상 Interval 프로퍼티의 간격에 따라 Tick 이벤트가 발생하는 것은 아니다.
- Tick 이벤트가 다른 이벤트에 비해 우선순위가 낮기 때문
- 타이머 컴포넌트의 추가 : 도구상자 ㅡ> Timer를 선택하여 폼에 추가
- 반복문을 사용하면 난리가 나기때문에 쓰면 안된다.
새가 펄럭이는 애니메이션을 만들어보자
새 윈폼에 레이블을 하나 깔아주고 AutoSize를 False로 바꿔주고 Dock를 Fill로 바꿔준다.
새 이미지를 넣어보자
private int index = 0;
private void timer1_Tick(object sender, EventArgs e)
{
index %= imageList1.Images.Count;
label1.Image = imageList1.Images[index++];
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
12장. 그래픽
그리기 개요
그리기 관련 자료형
도형 그리기
문자열 그리기
이미지 그리기
그리기 개요
Graphics(System.Drawing.Graphics) 클래스
System.Drawing 네임스페이스에 포함
선, 사각형, 타원 등과 같은 도형을 그리는데 필요한 기본적인 메소드들이 존재한다.
그래픽 객체
- 도형을 그리기 위해 필요한 Graphics 클래스의 객체
- 그리기판이 되는 대상
- 객체를 얻거나 생성하는 방법
① Paint 이벤트 처리기의 매개변수
② Control 클래스의 CreateGraphics() 메소드
③ Graphics.FromImage() 메소드
Paint 이벤트의 매개변수
- Paint 이벤트: 폼을 다시 그려야 할 때 발생하는 이벤트.
- 처리기의 두 번째 매개변수에 그래픽 객체가 들어 있음.
- 여기에 그리기 작업을 하면 폼에 그려짐.
Paint 이벤트 처리기의 메소드 형식)
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
// 그래픽 객체를 이용한 그리기 작업
// ...
}
PaintEventArgs 클래스
- Graphics 프로퍼티: 그리기에 필요한 Graphics 클래스의 객체
- ClipRectangle 프로퍼티는 새로 그려야 하는 영역
CreateGraphics()
Control 클래스의 CreateGraphics() 메소드
- Paint 이벤트 처리기가 아닌 다른 곳에서 그리기를 하고자 할 때 사용.
- 메소드를 사용하여 그래픽 객체를 생성
- Control 클래스의 메소드이기 때문에 파생된 모든 클래스에서 그래픽 객체를 만들 수 있음.
사용 방법)
private void DrawPrivateObject()
{
// 단계 1: 그래픽 객체를 생성한다.
Graphics g = CreateGraphics();
// ...
// 단계 2: 그래픽 객체를 이용하여 그리기를 한다.
// ...
}
그리기 관련 자료형
- 좌표와 관련있는 Point 구조체와 Size 구조체, Rectangle 구조체
- 색상을 나타내는 Color 구조체
- 도형을 그리거나 채우는데 사용되는 Pen 클래스와 Brush 클래스
- 글꼴을 나타내는 Font 클래스
- 이미지를 나타내는 Image 클래스
Point 구조체
- 평면상의 한 점을 표시하기 위한 자료형
- x 좌표와 y 좌표의 형식으로 위치를 표시
- 그리기뿐만 아니라 폼이나 컨트롤에서 위치를 지정하는데도 사용
생성자
Point pt = new Point(); // (0, 0)을 나타냄.
Point pt = new Point(x, y); // (x, y) 좌표를 나타냄.
주요 프로퍼티
- X: X 좌표 또는 수평 위치.
- Y: Y 좌표 또는 수직 위치.
- IsEmpty: 빈 구조체 여부.
관련 구조체
PointF 구조체: 값을 실수로 표현.
PointF -> Point 변환 메소드
public static Point Ceiling(PointF value); // 올림
public static Point Round(PointF value); // 반올림
public static Point Truncate(PointF value); // 내림
Rectangle 구조체
- 사각형 모양을 갖는 영역의 위치와 크기를 나타내기 위해서 사용
- Point 구조체와 Size 구조체의 개념을 모두 가지고 있는 구조체
- 사각형이나 또는 폼과 컨트롤의 위치와 크기를 동시에 나타내기 위해서 사용
생성자
Rectangle r = new Rectangle();
Rectangle r = new Rectangle(Point, Size);
Rectangle r = new Rectangle(X, Y, Width, Height);
주요 프로퍼티
X: 사각 영역의 왼쪽 상단의 X 좌표.
Y: 사각 영역의 왼쪽 상단의 Y 좌표.
Width: 사각 영역의 가로 폭.
Height: 사각 영역의 세로 높이.
Left/Top: 사각 영역의 왼쪽 X 좌표/위쪽의 Y 좌표
Right/Bottom: 사각 영역의 오른쪽 X 좌표/아래쪽의 Y 좌표
Location: 사각 영역의 시작 위치 (Point(X, Y))
Size: 사각 영역의 크기 (Size(Width, Height))
IsEmpty: 빈 구조체 여부.
Color 구조체
- 색을 RGB(Red, Green, Blue) 형식으로 나타낸 구조체
- RGB마다 각각 0~255 값을 가짐.
- 투명도 값인 A(Alpha)을 가질 수도 있음.
KnownColor 열거형
시스템 색상
: 윈도우의 구성 요 소의 색을 나타내는 색상.
생성자
Color c = Color.FromArgb(R, G, B);
Color c = Color.FromArgb(A, R, G, B);
Color c = Color.FromKnownColor(KnownColor.Member);
Color c = Color.FromName("ColorName");
사용법
// ① 프로퍼티를 이용하여 객체 생성.
Color c = Color.White;
// ② 미리 정의된 열거형 상수를 이용하여 객체 생성.
Color c = Color.FromKnownColor(KnownColor.White);
// ③ 미리 정의된 색상 이름을 이용하여 객체 생성.
Color c = Color.FromName("White");
// ④ RGB 값을 이용하여 객체 생성.
Color c = Color.FromArgb(255, 255, 255);
프로퍼티
R: 빨간색(red)의 값 (0∼255).
G: 녹색(green)의 값 (0∼255).
B: 파란색(blue)의 값 (0∼255).
A: 투명도(Alpha)의 값 (0∼255).
IsEmpty: 빈 구조체 여부.
Pen 클래스
직선이나 도형을 그릴 때 색상이나 굵기 등 선에 관한 정보를 가지는 펜을 위한 클래스
생성자
Pen p = new Pen(Brush b);
Pen p = new Pen(Color c);
Pen p = new Pen(Brush b, float width);
Pen p = new Pen(Color c, float width);
Pens 클래스
- 미리 정의된 펜들을 가진 프로퍼티로 가진 클래스
- 프로퍼티 이름은 KnownColor 열거형 상수(시스템 색상 이름을 제외)의 이름과 동일
- 펜의 두께는 1.0이고 이름과 같은 색상을 가진 펜을 정의.
Width 프로퍼티
펜의 굵기를 나타내는 프로퍼티
public float Width { get; set; }
DashStyle 프로퍼티
점선이나 파선처럼 선의 모양을 나타내는 프로퍼티
public DashStyle DashStyle {get; set;}
DashStyle 열거형
System.Drawing.Drawing2D 네임스페이스에 포함.
StartCap/EndCap 프로퍼티
선 시작과 끝의 모양을 나타내는 프로퍼티
public LineCap StartCap {get; set;}
public LineCap EndCap {get; set;}
LineCap 열거형
System.Drawing.Drawing2D 네임스페이스에 포함.
메인 폼의 이벤트 속성중에 Paint를 더블클릭해서 그려보자
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = CreateGraphics();
Pen pen = new Pen(Color.DeepPink);
pen.Width = 6.8f;
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
Point starPoint = new Point(45, 45);
Point endPoint = new Point(180, 150);
g.DrawLine(pen, starPoint, endPoint);
g.DrawLine(pen, 190, 60, 65, 170);
}
Brush 클래스
백그라운드 컬러를 쓰면됨(생략)
사각형 그리기1 DrawRectangle()
(메서드 바로위에서 /// (슬래시 3번)을 입력하면 요약 주석을 달 수 있다)
/// <summary>
/// Form1_Paint, 폼에 이미지 그리기 메서드
/// created date : 2020.06.17
/// creator : SDW
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = CreateGraphics();
Pen pen = new Pen(Color.DeepPink);
pen.Width = 6.8f;
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
//Point starPoint = new Point(45, 45);
//Point endPoint = new Point(180, 150);
//g.DrawLine(pen, starPoint, endPoint);
//g.DrawLine(pen, 190, 60, 65, 170);
Rectangle rect = new Rectangle(50, 50, 150, 100);
g.FillRectangle(Brushes.BlueViolet, rect);
g.DrawRectangle(pen, rect);
}
사각형 그리기2 DrawRectangles()
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = CreateGraphics();
Pen pen = new Pen(Color.DeepPink);
pen.Width = 6.8f;
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
//Point starPoint = new Point(45, 45);
//Point endPoint = new Point(180, 150);
//g.DrawLine(pen, starPoint, endPoint);
//g.DrawLine(pen, 190, 60, 65, 170);
//Rectangle rect = new Rectangle(50, 50, 150, 100);
Rectangle[] rects = new Rectangle[]
{
new Rectangle(40, 40, 40, 100),
new Rectangle(100, 40, 100, 40),
new Rectangle(100, 100, 100, 40)
};
g.FillRectangles(Brushes.BlueViolet, rects);
g.DrawRectangles(pen, rects);
}
폐곡선 그리기
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = CreateGraphics();
Pen pen = new Pen(Color.DeepPink);
pen.Width = 6.8f;
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
//Point starPoint = new Point(45, 45);
//Point endPoint = new Point(180, 150);
//g.DrawLine(pen, starPoint, endPoint);
//g.DrawLine(pen, 190, 60, 65, 170);
//Rectangle rect = new Rectangle(50, 50, 150, 100);
Rectangle[] rects = new Rectangle[]
{
new Rectangle(40, 40, 40, 100),
new Rectangle(100, 40, 100, 40),
new Rectangle(100, 100, 100, 40)
};
g.FillRectangles(Brushes.BlueViolet, rects);
g.DrawRectangles(pen, rects);
Point[] pts =
{
new Point(515, 30), new Point(540, 90),
new Point(600, 115), new Point(540, 140),
new Point(515, 200), new Point(490, 140),
new Point(430, 115), new Point(490, 90)
};
g.FillClosedCurve(Brushes.YellowGreen, pts);
g.DrawClosedCurve(pen, pts);
}
문자열 그리기
내일은 비가 빡세게 온다고하니 우산을 꼭 챙겨가도록합시다!
=============================================================
P.S) Anchor 속성
실행한 윈폼창을 늘리거나 줄이면 버튼의 크기도 늘리거나 줄여줄 수 있다.
'개발자과정준비 > WinForm' 카테고리의 다른 글
[WinForm] 5. 윈폼과 SSMS 연동(2) 로그인폼 구현 (0) | 2020.06.21 |
---|---|
[WinForm] 4. Chart 컨트롤, 윈폼과 SSMS 연동하기 (0) | 2020.06.18 |
[WinForm] 2. 컨트롤, 대화상자, 텍스트박스, 메인 메뉴, 고급 폼 (0) | 2020.06.16 |
[WinForm] 1. C# 제네릭, 예외, 윈폼 입문 (0) | 2020.06.16 |
[WinForm] 0. GitHub 설치, C#과 연동하기 (0) | 2020.06.16 |