> For the complete documentation index, see [llms.txt](https://wnsgml972.gitbook.io/midas_log/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wnsgml972.gitbook.io/midas_log/contents/2018.09/09.md).

# MIDAS LOG

## go home! [:house\_with\_garden:](https://github.com/wnsgml972/midas_log)

## 09.03

* 적응
  * help.midasit.com (네트워크 환경 구성 등등)
  * 모니터 신청, RAM 신청
  * 각종 회원가입
  * 개발환경 구성
* C++ 공부

## 09.04

* 자기소개 PPT 발표 준비
* MFC 공부

## 09.05

* MFC로 전화번호부 구현 시작

### Visual Assist

`Alt + O`

* Header file과 Source file같은 전환\~같은 전환

`Alt + G`

* 함수의 선언과 구현부를 왔다갔다 함

`Ctrl + G`

* 원하는 줄로 이동함

`Alt + Shift + S`

* 심볼 찾기

`Alt + Shift + O`

* 파일 찾기

`Alt + M`

* 현재 파일의 함수 찾기

`Shift + Alt + F`

* Reference 찾기

## 09.06

* [MFC 전화번호부 완성](https://github.com/wnsgml972/MFCPhoneBook)

### 컴파일 경고는 되도록 모두 잡고 가자

* 필요 없는 컴파일 경고 없애는 방법

```
#pragma warning(push)
#pragma warning(disable: 4351)
#pragma warning(pop)
```

* 밑에 처럼 하면 Test 경고는 출력이 안 되고, SubTest 경고는 출력된다.

```
#pragma warning(push)
#pragma warning(disable: 4351)

class Test
{
    int m_ints[10];
    double m_doubles[10];

public:
    Test()
        : m_ints() // C4351
        , m_doubles() // C4351
    {
    }
};

#pragma warning(pop)

class SubTest : public Test
{
    bool bools[10];

public:
    SubTest()
        : bools() // C4351
    {
    }
};
```

### CString <-> string

```
string CStringToString(CString cstr)
{
    char cpResult[200];

#pragma warning(push)
#pragma warning(disable: 4996)
    strcpy(cpResult, CT2A(cstr));
    if (cpResult == NULL)
    {
        strcpy(cpResult, "");
    }
    cpResult[strlen(cpResult) + 1] = '\0';
#pragma warning(pop)

    return cpResult;
}

CString StringToCString(string str)
{
    CString strResult;

    strResult = str.c_str();
    strResult += '\0';

    return strResult;
}
```

## 09.07

* mday 자기소개 발표!
* 이번 주 마무리
  * OpenSource 경진대회 휴가 계획 짜기
  * 밀렸던 자료 정리
  * 김남윤 교수님께 메일?
  * 다음 주 할 일 미리 정리

## 09.10

* MFC 인테리어 프로그램 개발 시작
* 먼저 설계부터 하자. MVC 생각하면서 설계하면 좀 편함
* [단일 문서 기본 틀 생성](http://ryumin13.tistory.com/entry/mfc로-툴만들기-기본-뼈대-만들기?category=388236) : 진짜 어려움... FormView가 생성이 안 되서 따로 만들어 줘야 함, 만들어진 자료 확인
* MVC 패턴에 기반한 기본 틀 완성!

## 09.11

* [MFC InteriorProgram](https://github.com/wnsgml972/MFC-InteriorProgram) : 첫 커밋, **Controller의 싱글톤과 FileManager, Main의 define**
* MVC 패턴에 기반한 기본 틀 함수 추가, MVC에 따른 패키징!
* DrawRoom, Select 구현
* SAFE DELETE, 원하는 뷰 포인터 얻어오기

```
#define SAFE_DELETE(p) { if(p) {delete p; p = NULL;} }
```

```
☞ 헤더파일 포함 순서.
#include <stdafx.h>
#include <Example.h>
#include <MainFrm.h>
#include <ExampleDoc.h>
#include <ExampleView.h>

== SDI의 경우 ===

[ 뷰클래스의 포인터 받기 ]
1) SDI의 경우
CMainFrame *pMainFrame = (CMainFrame *)AfxGetMainWnd();
CTempView *pView = (CTempView *)pMainFrame->GetActiveView();


[정리하자면]
어플리케이션 클래스
1) CXXXXApp *pAPP = (CXXXXApp *)AfxGetApp();

메인플레임 클래스 얻기
1) CMainFrame *pMainFrame =(CMainFrame*) AfxGetApp()->m_pMainWnd;

View클래스 얻기
1) 메인플레임 클래스를 먼저 얻음.
2) CXXXXView *pView = (CXXXXView *)pMainFrame->GetActiveView();

Document 클래스 얻기
1) 메인플레임 클래스를 먼저 얻음.
2) CXXXXDoc *pDoc = (CXXXXDoc *)pMainFrame->GetActiveDocument();

[실제 프로그램 예시]
CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();  // 프레임 윈도우 포인터 구하고...
//CInteriorProgramView* pView = (CInteriorProgramView*)pFrame->GetActiveView(); // 활성화된 뷰의 포인터 구한다.
CInteriorProgramView* pView = (CInteriorProgramView*)pFrame->m_wndSplitter.GetPane(0, 0); // 활성화된 뷰의 포인터 구한다.

pView->RedrawWindow();
```

## 09.12

* MFC 환경에서 console에 log 출력하는 법

```
-> 이것을 stdafx.h의 제일 위에다 넣으면 됨

#ifdef _DEBUG
#ifdef UNICODE
#pragma comment(linker, "/entry:wWinMainCRTStartup /subsystem:console")
#else
#pragma comment(linker, "/entry:WinMainCRTStartup /subsystem:console")
#endif
#endif   <a__>
```

### 소멸자 virtual, c++의 instanceof인 typeid

* c++에서 virtual을 붙여주게 되면 상속 관계에서 파생 클래스의 함수를 부르게 된다.
* 상속 관계에서 소멸자에 virtual을 붙여놓지 않는다면, 만약 업캐스팅한 객체는 해당 가리키고 있는 객체의 소멸자를 호출하지 않게된다. 그러므로 통상적으로 상속관계에 있는 클래스중 제일 상위 클래스의 소멸자에는 보통 virtual을 붙인다.
* 여기에서 하나 문제가 있다. 해당 객체의 type을 알 수 있는 c++의 typeid에서, 업캐스팅한 객체의 타입을 알고 싶었는데 가리키고 있는 객체의 타입이 아닌 자기 자신만 가리키는 결과가 나왔다. \
  &#x20;-> 해결방법으로 상위 클래스에 소멸자를 붙여주게 되면 하위 클래스의 소멸자를 호출하여 올바르게 가리키는 객체를 참조하는 결과가 나왔다. 그러므로 typeid를 쓰려면

```
1. 헤더파일 typeinfo 선언
2. 상위 클래스의 소멸자에 virtual 선언
3. typeid(m_CaShape.at(i)) == typeid(DoorShape)
다음과 같이 비교하면 된다.
```

### Select  (어려움...)

1. 알고리즘
2. 기준 꼭지점을 정해야함

### Move  (핵 어려움...)

1. MouseDown 시 선택된 Shape의 기준 꼭지점과 클릭한 point와의 차이점을 구해놓는다.
2. MouseMove에서 구해놓은 차이와 현재 드래그 되는 좌표값을 더해준다!
3. 그 값은 InteriorProgramView를 넘으면 안 된다.
4. 그 외 프로그램 상 각종 예외처리

### 진행상황

**\[09.12] Issue 1. 80% 해결**

1. Move 성공!
2. Update 성공!
3. HowManySeleted 성공!
4. Current Selected 성공!
5. AddShape 미완
6. DeleteShape 미완

**현재까지 각종 Error 체크 했을 시 오류 없었음!**

## 09.13

> 천천히 생각하자. 오늘은 계속 어려운 것만 해결하다보니 많이 힘들었다. \
> &#x20;그래도 천천히 많이 생각하고, 코드를 깨끗하게 구조를 깨끗하게 하다보니 오래 생각하면 계속 방법이 나왔다. \
> &#x20;급하게 짜지 말고 좋은 방법을 찾을 것!

**\[09.13] Issue 2**

1. DrawDoor
2. MoveDoor
3. DrawWindow
4. MoveWindow
5. AddShape
6. DeleteShape

**마우스 우클릭 삭제 이벤트 구현했음**

### SetDoorWindowRange (개 핵핵핵핵 어려움)  다음날 알고리즘 다 바꿈.. 주의

* Door와 Window는 Room의 범위내에서 특정 크기 한정으로 그려져야한다.
* 그려지는 범위가 특정 어느 룸의 DrawRange 범위인지 찾고 거기서 각각 그려지는 방향에 맞춰서 고정할 부분을 고정하고 움직일 부분은 움직이며 그린다.
* 그 상황 속에서 선택된 룸은 그 문을 가지고 있다는 뜻으로 룸의 ShapeVector 인덱스를 기억하다가 마우스를 때면 전체 벡터에 문을 넣고, 해당 룸의 Door나 Window 벡터에도 객체를 담는다. `m_nRememberIndexForDoorWindowVector`
* 문과 창문의 자세한 알고리즘은 너무 기므로 생략 **SetDoorWindowRange** 라는 ShapeHandler의 멤버함수에 있다.
* 방을 삭제할 때는 그 방이 가지고 있는 Door와 Window 벡터를 모두 삭제하고 전체 벡터 상에서도 Door와 벡터를 삭제해야한다. 즉 둘다 삭제해야 하는데, 둘다 넣고 삭제하는 것이 쉽지 않았다.
* 물론 하나만 삭제할 때도 각각 다 삭제해줘야 한다.
* 그룹화 삭제 알고리즘 `DeleteSelectedShape`의 함수를 보면 코드가 있다.

```

[ 그룹화 삭제 알고리즘 ]
찾아서 삭제하는데! 하나 삭제하면 전체 갯수가 줄어드므로 벡터의 인덱스가 바뀜!
그렇기 때문에 다음 루프에서 또 삭제할 때 오류가 남
해결 방법으로 벡터의 맨 뒤에서부터 검색해 삭제할 것을 찾으면 됨
근데 창문과 문을 섞어서 만들경우 결국 생성 인덱스가 섞여버림...
정렬해야 됨, 정렬해서 뒤에서부터 삭제해야 됨...
새로운 벡터를 생성해 값을 다 넣고 정렬한다음 삭제한다.

[ Room에 속한 Door, Window 삭제 알고리즘 ]
Door나 Window만 삭제할 때도 그냥 삭제하면 안 됨, 해당 Room에서도 삭제해야 함
1. 해당 Door나 Window가 속한 Room의 ShapeVector에서의 Index에서
2. 그 룸에서의 벡터에서 또 몇 번째에 위치하고 있는지까지 알아야 함
3. 모두 다 알았으면 해당 Door나 Window가 속한 Room에서의 벡터를 찾고, 그 벡터의 해당 위치에서 삭제
4. 전체 Shape Vector에서 삭제
```

### 해결한 것

1. DrawDoor 해결
2. MoveDoor 미완 -> 룸의 범위를 벗어나게 하면 안 됨
3. DrawWindow 해결
4. MoveWindow 미완 -> 룸의 범위를 벗어나게 하면 안 됨
5. AddShape 해결
6. DeleteShape 해결

### 해야할 것

1. MoveDoor
2. MoveWindow
3. MoveGroup
4. Redo, Undo ( 딜리트 시 스택에 담으면 됨 )
5. Scale ( 마우스 휠 이벤트 처리기로 하기 )
6. Edit, Copy, Rotate ( 이벤트 처리기 리스너 만들어놈 )
7. User Object ( 마그네틱, 스냅 등등 )
8. Material ( 그냥 이미지 덮기 )

## 09.14

### for auto문

* auto type은 말그대로 자동 할당이라는 말이다. c++의 컴파일러는 오른쪽에서 왼쪽으로 코드를 읽어들이는데 auto type은 대입문에서 오른쪽 변수의 타입을 자동으로 읽어 갖다 놓는다는 것으로 활용할 수 있다.
* C++, 단순 반복문일 경우 for auto문을 활용하자, 속도면에서도 더 빠름
* C++ 벡터에서 at 이런 거 안써도 \[] 대괄호로 사용 가능했었음...ㅎㅎ
* 밑의 문장은 똑같은 일을 하는 반복문이다. 밑의 for auto문을 사용한 것이 훨신 간결하다!!  ( iterator를 리턴함 )

```
for (int i = 0; i < dynamic_cast<RoomShape*>(m_CaShape.at(nSelectedIndex))->m_CaWindow.size(); i++)
{
  dynamic_cast<RoomShape*>(m_CaShape.at(nSelectedIndex))->m_CaWindow.at(i) = NULL;
}
for (auto nIdx : dynamic_cast<RoomShape*>(m_CaShape[nSelectedIndex])->m_CaWindow  )
{
  *nIdx = NULL;  <a*>
}
```

* 어제 사용했던 삭제 알고리즘은 너무 많은 의존성과 벡터를 인덱스로 가지고 삭제한다는게 너무 바뀌어서 다 삭제!
* 새로운 알고리즘 도입

### Index 의존성을 제거한 포인터 위주의 방식!!  조금 어렵지만 확실히 오류도 없고 괜찮은 듯

* 이상한 index 다 가질 필요 없이, 룸에 속한 문이나 창문은 룸을 포인터로 가지고 있는다. `pInRoomShapePointer`
* **항상 벡터의 삭제 구현은 뒤에서부터 할 것!!!!**
* 코드가 정말 간결하고 깔끔해졌다...

### \[그룹화 삭제 알고리즘]

1. 선택된 방의 ID 값을 쓰레기 값으로 변경 `tmpInRoomPtr->SetId(-600000);`
2. 먼저 방안의 문 벡터 창문 벡터를 모두 동적할당 해제 후 삭제
3. 전체 Shape 벡터에서는 아이디 값이 쓰레기 값인 것을 삭제 후, 동적할당 해제
4. 같은 요소를 벡터에 포인터로 넣은 것이기 때문에 모든 것이 끝난 후 동적할당 한번만 하면 됨 주의

### \[특정 요소 삭제 알고리즘]

* 주의! 특정 방 안의 문이나 창문 벡터는 포인터가 아닌 그냥 변수이기 때문에 다른 곳에서 값을 바꿔도 값이 바뀌지 않는다 때문에 값을 받을 때 `&dynamic_cast<RoomShape *>` 위와 같이 주솟 값을 받아야 한다!!
* 이것 때문에 3시간 해멘듯ㅎ

```
auto tmpDoorPtr = &dynamic_cast<RoomShape *>(dynamic_cast<DoorShape*>(m_CaShape[nSelectedIndex])->pInRoomShapePointer)->m_CaDoor;
```

1. 선택된 문이나 창문의 ID 값을 쓰레기 값으로 변경 `tmpSelectedDoorPtr->SetId(-600000);`
2. 전체 Shape 벡터에서는 아이디 값이 쓰레기 값인 것을 삭제
3. 문 벡터에서는 아이디 값이 쓰레기 값인 것을 삭제
4. 같은 요소를 벡터에 포인터로 넣은 것이기 때문에 모든 것이 끝난 후 동적할당 한번만 하면 됨 주의

### 더 좋은 방법!

1. 내가 어차피 각 Shape 마다 고유의 id 값을 만들어 놨다.
2. ShapeHandler가 map 이라는 것을 가지게 만들어서 key 값은 고유 id, value 값은 각 Shape의 포인터를 리턴하게 하면 정말 사용하기 쉬을 듯!

### 해결한 것

1. 삭제의 각종 오류 해결, 자료구조 정리!!

추가 : 야근으로 해결 더 함ㅎㅎ, 설계를 잘 해놓으니 금방했음!

1. 창문이나 문을 그릴 때 갖다 붙이면 두번째 Room에는 안 들어감! 코드 수정 해결!
2. Group Move 해결!!, 방안의 문이나 창문이 이상한 모양이 되지 못하게 해결 함

### 해야할 것

1. MoveDoor, MoveWindow 미완 -> 룸의 범위를 벗어나게 하면 안 됨
2. Redo, Undo ( 딜리트 시 스택에 담으면 됨 )
3. Scale ( 마우스 휠 이벤트 처리기로 하기 )
4. Edit, Copy, Rotate ( 이벤트 처리기 리스너 만들어놈 )
5. User Object ( 마그네틱, 스냅 등등 )
6. Material ( 그냥 이미지 덮기 )
7. Save Load As JSon ( 저장 로드 )

## 09.17

### **버그 발견**

#### Room 크기에 맞춰 Door나 Window를 그릴경우

* 멘탈 나갔었지만 해결

#### Wheel 할 시 커졌다 작아지면 오류

* 로직 문제

#### 교훈

* 이제 점점 프로그램이 커지다 보니 생각할 것이 매우 많아졌다. 하지만 범위를 계속 좁혀나가고 무슨 문제가 오류일까를 근본적으로 해결할 것!!
* 수학적인 공식을 대충하려 하지말고 완벽하게 해서 대입할 것, 그 부분이 나 스스로 확신이 안 들면 나중에 계속 거기 문제라고 생각함ㅠㅠㅠㅠ
* 포기하지 말고 다시 생각해보고 꾸준히 생각해보자

### 해결한 것

1. MoveDoor, MoveWindow
2. Copy
3. Scale

### 해야할 것

1. Redo, Undo ( 딜리트 시 스택에 담으면 됨 )
2. Edit, Rotate ( 이벤트 처리기 리스너 만들어놈 )
3. User Object ( 마그네틱, 스냅 등등 )
4. Material ( 그냥 이미지 덮기 )
5. Save Load As JSon ( 저장 로드 )

## 09.18

### 해결한 것

1. Magnetic 알고리즘 설계 (정말 사용자가 필요할 것이라고 생각되는 모든 경우에서 Magnetic이 됨!), 구현은 70%

#### Magnetic 알고리즘 설명

| 알고리즘 설명                                  |
| ---------------------------------------- |
| ![Magnetic](/files/-LczQc6JvgVxYYGNtk45) |
| ![Magnetic](/files/-LczQc6Lxqv6GEFMkqji) |
| ![Magnetic](/files/-LczQc6NIghT-SflAYve) |

### 해야할 것

1. Redo, Undo ( 딜리트 시 스택에 담으면 됨 )
2. Edit, Rotate ( 이벤트 처리기 리스너 만들어놈 )
3. User Object ( 마그네틱, 스냅 등등 )
4. Material ( 그냥 이미지 덮기 )
5. Save Load As JSon ( 저장 로드 )

## 09.19

### 불필요한 중괄호?

```
{ // 방 안의 창문과 문도 Update
  //In Room Door
  for (auto CDoorShape : tmpRoomShape->m_CaDoor)
  {

  }
  //In Room Window
  for (auto CWindowShape : tmpRoomShape->m_CaWindow)
  {

  }
}
```

#### C++에선 다음과 같이 특정 조건이 없는 중괄호도 허용한다. 이것은 어디에 쓰일끼?

* 블록 범위의 문법을 사용할 수 있다.
  * 중괄호 내에서 변수를 만들면 중괄호가 끝나면 사라지게 된다.
  * `for문` 내에서 `int i`를 선언하고 끝나면 `i`가 사라지는 것과 같다.
  * 동일한 변수 이름을 사용할 수도 있지만 그건 좋은 생각은 아니다.
* 블록 범위의 개념을 사용할 수 있다.
  * 특정 (하위) 목적을 달성하는 코드 블록을 단순히 분리하는 것이다.
  * 조금 더 자세히 말하면, 한 덩어리는 일관된 개념과 목적을 가지고 사용할 수 있다.
  * 이것을 이용하면 조금 더 가독성 있는 코드를 만들 수 있으며, 주석을 이용하면 남이 봐도 이해하기 쉬운 코드를 만들 수 있다.
* 예시
  * 필요에 따라 리팩토링하는 코드, 테스트하는 코드를 넣어 사용할 수 있다. &#x20;
  * UI 관련 클래스에서도 유용하다.

```
{ //test1
   <...>
}
{//start video btn
   <...>
}
```

* 기타 장점
  * 편집자가 접기 / 펼치기 기능을 쓸 수 있다.

**일회용 메소드를 만들 기에는 좋지 않지만 개념적으로 나누어야 할 때, 이 문법을 이용해서 블럭 단위로 나누고, 다른 개념이 되는 코드 덩어리를 구성하여 사용하면 유용하다.**

### 해결한 것

1. Magnetic, 구현 완벽히 다 됨!
2. 각종 코드 정리, 구조 정리, 필요없는 변수 정리
3. UI, 필요없는 버튼 지우기

### 해야할 것

1. Redo, Undo ( 딜리트 시 스택에 담으면 됨 )
2. Edit, Rotate ( 이벤트 처리기 리스너 만들어놈 )
3. Save Load As JSon ( 저장 로드 )
4. User Object ( 마그네틱, 스냅 등등 ) -> 그냥 간단히, 색깔 다른 Rect로 만들 것
5. PNG 사진 찍는 기능 -> 단순한 api 이용

## 09.20

### isNumber

```
if (s.size() == 0)
  return false;
for (int i = 0; i < s.size(); i++)
{
  if ((s[i] >= '0' && s[i] <= '9') == false)
  {
    return false;
  }
}
return true;
```

### 해결한 것

* 키보드 단축기
  * ctrl+c : 복사 구현
  * ctrl+v : 붙여넣기 구현
  * ctrl+x : 삭제 구현
* Edit 구현
  * dbclick 구현
  * Menu 마우스 오른쪽 구현, 둘다 다 됨

### 해야할 것

* Redo, Undo ( 딜리트 시 스택에 담으면 됨 )
* Save Load As JSon ( 저장 로드 )
* User Object ( 마그네틱, 스냅 등등 ) -> 그냥 간단히, 색깔 다른 Rect로 만들 것
* PNG 사진 찍는 기능 -> 단순한 api 이용
* 키보드 단축기
  * ctrl+z : undo ( 이벤트 처리기 리스너만 만들어놈 )
  * ctrl+y : redo ( 리스너 만들었는데 안 불림ㅠ )
  * ctrl+s : 저장 ( 리스너 만들었는데 딴 거 불림 )
* 마우스 우클릭 메뉴
  * Rotate ( 이벤트 처리기 리스너만 만들어놈 )

## 09.21

### 원하는 작업 영역 캡쳐

```
// 원하는 작업 영역 캡쳐!!!
// 개좋음~~~ 그냥 캡쳐 하고 싶은 Frame에다가 이 코드 넣으면 됨
{
  // View::OnLButtonDown
  // 바탕 화면 윈도우 객체에 대한 포인터를 얻는다.
  CWnd* pWndDesktop = GetDesktopWindow();
  // 바탕 화면 윈도우 DC
  CWindowDC ScrDC(pWndDesktop);
  // 뷰 윈도우 DC
  CClientDC dc(this);

  // Rect를 사용해서 작업 영역에 대한 좌표를 얻고,
  CRect Rect;
  GetClientRect(&Rect);

  // 그 위치를 현재 윈도우의 절대 좌표로 변경해 주자.
  CWnd::GetWindowRect(&Rect);

  // CImage를 하나 만들고
  CImage Image;
  // 스캔을 시작할 x, y 위치와
  int sx = Rect.left;
  int sy = Rect.top;
  // 작업 영역에 대한 크기를 각각 임시로 저장해 두자.
  int cx = Rect.Width();
  int cy = Rect.Height();

  // 작업 영역의 크기만큼, 현재 바탕화면의 속성과 동일한 image를 생성한다.
  Image.Create(cx, cy, ScrDC.GetDeviceCaps(BITSPIXEL));
  // 이미지 DC에 현재 작업 영역의 절대 좌표를 사용해 그 크기만큼 저장하게 한다.
  CDC* pDC = CDC::FromHandle(Image.GetDC());
  pDC->BitBlt(0, 0, cx, cy, &ScrDC, sx, sy, SRCCOPY);
  Image.ReleaseDC();

  // 저장된 이미지를 원하는 파일명, 포멧타입을 지정해서 저장한다.
  Image.Save(TEXT("media\\capture\\image.png"), Gdiplus::ImageFormatJPEG);

  // 그 파일을 실행해 준다.
    // 그 파일을 실행해 준다.
    // 응용 프로그램을 실행시킬 수 있음!
    ShellExecute(NULL, TEXT("open"), TEXT("media\\capture\\image.png"), NULL, NULL, SW_SHOW);
}
```

### 해결한 것

* UserObject
  * SetImageIcon, Add, Edit, Delete, Wheel, Copy Paste, Move, Magnetic, Select 구현
* PNG 사진 찍는 기능 -> 단순한 api 이용

### 해야할 것

* Redo, Undo ( 딜리트 시 스택에 담으면 됨 )
* Save Load As JSon ( 저장 로드 )
* 마우스 우클릭 메뉴
  * Rotate ( 이벤트 처리기 리스너만 만들어놈 )
* 키보드 단축기
  * ctrl+z : undo ( 이벤트 처리기 리스너만 만들어놈 )
  * ctrl+y : redo ( 리스너 만들었는데 안 불림ㅠ )
  * ctrl+s : 저장 ( 리스너 만들었는데 딴 거 불림 )
* UserObject
  * 추 후 Room 그룹화 고민해 볼 것
  * 이미지 Width, Height Edit 가능하게 하기 (비트맵 이미지 크기를 바꿔야됨)

## 09.27

* [Viusal 개발 기본 Setting](/midas_log/contents/basiceducation/visualbasicset.md)
* 코딩 규칙
  * 클래스를 넘길 때, 값에 의한 호출을 하면 클래스가 생성이 되므로, 복사생성자를 만들지 않았을 시 올바른 복사가 이루어지지 않는다. 때문에 클래스는 참조를 통해 넘기거나 복사생성자를 만들어야하는데, 값에 의한 호출의 경우 어차피 값이 바뀌지 않으니 참조와 const를 이용해 값을 넘겨 사용한다.
* 박종봉 선배님
  * Filter의 개념
  * Feature, Member
  * 개발 시작

    \
    &#x20;CIM -> CIMc\_ui, Feature, 3D, Filling

## 09.28

* [Viusal 개발 기본 Setting](/midas_log/contents/basiceducation/visualbasicset.md)
* [Basic Education](/midas_log/contents/basiceducation.md)
