다음은 통신 설정 윈도우 CComSettingsDialog 가 띄워진 이후의 동작을 만들어보겠습니다.
클래스위저드를 띄운 후 아래 그림과 같이 왼쪽 Object IDs 에서 CComSettingsDialog 를 선택한 다음 오른쪽 Messages 란에서 WM_INITDIALOG 를 선택 후 Add Function 을 누르면 아래 Member functions 란에 OnInitDialog 라는 항목이 리스트에 새로 올라오는데 OK 버튼을 누르면 OnInitDialog 메소드가 생성됩니다.
이 메소드는 다이얼로그가 생성 될 때 초기화 작업을 해주도록 윈도우가 발생시키는 WM_INITDIALOG 메세지를 받는 함수이므로 여기에 이 다이얼로그에 쓰이는 변수들 초기화에 대한 코딩을 합니다.
앞서 도큐먼트 클래스에 선언한 접속 상태 데이터를 저장하는 CComInfo 자료형의 m_ComInfo 라는 변수를 가져와서 포트번호 등 화면 구성 요소들을 초기화 해주되, 대신 이 변수를 직접 조작하지 말고 같은 자료형으로 이 다이얼로그에 속한 멤버변수를 하나 더 만들어 값을 복사하여 사용할 것입니다. 왜냐하면 사용자가 설정값들을 조작 한 후 취소를 할 수도 있으니까요.
그래서 CComSettingsDialog 헤더파일 클래스 정의부에 아래와 같이 CComInfo 자료형의 아래와 같은 변수를 public 나 protected 선언 영역에 하나 만들어줍니다.
CComInfo m_ComInfoInThis;
|
이 변수가 통신 설정 다이얼로그 내에서 설정값들의 표시와 사용자 조작을 기억할 변수입니다.
OnInitDialog 메소드를 다음과 같이 코딩합니다.
BOOL CComSettingsDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
// CMainFrame 클래스 인스턴스를 얻어옴
CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
// CMainFrame 인스턴스로부터 도큐먼트 클래스 인스턴스를 얻어옴
CComConstructorDoc *pDoc = (CComConstructorDoc*)pFrame->GetActiveDocument();
ASSERT(pDoc);
CopyMemory(&m_ComInfoInThis, &pDoc->m_ComInfo, sizeof(CComInfo));
// 통신포트 선택에 대한 저장값 적용
CButton* pRadioButton = NULL;
switch(m_ComInfoInThis.nPortNum)
{
case 1: pRadioButton = (CButton*)GetDlgItem(IDC_COM1); break;
case 2: pRadioButton = (CButton*)GetDlgItem(IDC_COM2); break;
case 3: pRadioButton = (CButton*)GetDlgItem(IDC_COM3); break;
case 4: pRadioButton = (CButton*)GetDlgItem(IDC_COM4); break;
}
if(pRadioButton)
pRadioButton->SetCheck(TRUE);
// 보레이트 선택에 대한 저장값 적용
pRadioButton = NULL;
switch(m_ComInfoInThis.eBaudrate)
{
case CSerial::EBaud1200 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_1200); break;
case CSerial::EBaud2400 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_2400); break;
case CSerial::EBaud9600 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_9600); break;
case CSerial::EBaud14400 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_14400); break;
case CSerial::EBaud19200 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_19200); break;
case CSerial::EBaud38400 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_38400); break;
case CSerial::EBaud56000 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_56000); break;
case CSerial::EBaud57600 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_57600); break;
case CSerial::EBaud115200 : pRadioButton = (CButton*)GetDlgItem(IDC_BAUDRATE_115200); break;
}
if(pRadioButton)
pRadioButton->SetCheck(TRUE);
// 데이터비트 선택에 대한 저장값 적용
pRadioButton = NULL;
switch(m_ComInfoInThis.eDataBits)
{
case CSerial::EData5: pRadioButton = (CButton*)GetDlgItem(IDC_DATA_5); break;
case CSerial::EData6: pRadioButton = (CButton*)GetDlgItem(IDC_DATA_6); break;
case CSerial::EData7: pRadioButton = (CButton*)GetDlgItem(IDC_DATA_7); break;
case CSerial::EData8: pRadioButton = (CButton*)GetDlgItem(IDC_DATA_8); break;
}
if(pRadioButton)
pRadioButton->SetCheck(TRUE);
// 패리트비트 선택에 대한 저장값 적용
pRadioButton = NULL;
switch(m_ComInfoInThis.eParity)
{
case CSerial::EParNone: pRadioButton = (CButton*)GetDlgItem(IDC_PARITY_NONE); break;
case CSerial::EParOdd: pRadioButton = (CButton*)GetDlgItem(IDC_PARITY_ODD); break;
case CSerial::EParEven: pRadioButton = (CButton*)GetDlgItem(IDC_PARITY_EVEN); break;
case CSerial::EParMark: pRadioButton = (CButton*)GetDlgItem(IDC_PARITY_MARK); break;
case CSerial::EParSpace: pRadioButton = (CButton*)GetDlgItem(IDC_PARITY_SPACE); break;
}
if(pRadioButton)
pRadioButton->SetCheck(TRUE);
// 스톱비트 선택에 대한 저장값 적용
pRadioButton = NULL;
switch(m_ComInfoInThis.eStopBits)
{
case CSerial::EStop1: pRadioButton = (CButton*)GetDlgItem(IDC_STOP_1); break;
case CSerial::EStop1_5: pRadioButton = (CButton*)GetDlgItem(IDC_STOP_15); break;
case CSerial::EStop2: pRadioButton = (CButton*)GetDlgItem(IDC_STOP_2); break;
}
if(pRadioButton)
pRadioButton->SetCheck(TRUE);
// 핸드쉐이크(플로우컨트롤) 선택에 대한 저장값 적용
pRadioButton = NULL;
switch(m_ComInfoInThis.eHandshake)
{
case CSerial::EHandshakeOff: pRadioButton = (CButton*)GetDlgItem(IDC_HANDSHAKING_OFF); break;
case CSerial::EHandshakeHardware: pRadioButton = (CButton*)GetDlgItem(IDC_HANDSHAKING_HARDWARE); break;
case CSerial::EHandshakeSoftware: pRadioButton = (CButton*)GetDlgItem(IDC_HANDSHAKING_SOFTWARE); break;
}
if(pRadioButton)
pRadioButton->SetCheck(TRUE);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
|
위 함수 처음 부분을 보시면 아래 다시 적은것 처럼 AfxGetMainWnd() 함수를 호출하여 메인프레임에 대한 포인터를 가져와서 다시 이 포인터를 사용하여 GetActiveDocument() 함수를 호출해 도큐먼트 클래스의 포인터를 얻어내고 있습니다.
CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
CComConstructorDoc *pDoc = (CComConstructorDoc*)pFrame->GetActiveDocument();
|
이 도큐먼트 클래스 포인터로 앞서 선언한 통신 설정을 기억하는 m_ComInfo 변수를 가지고 이 다이얼로그에만 쓰이는 변수인 m_ComInfoThis 에 값들을 복사한 후 다이얼로그 화면 초기화를 해주고 있습니다.
후에 다이얼로그 내 버튼들을 조작하면 m_ComInfoThis 변수가 바뀌게 하고 다이얼로그를 닫으면 m_ComInfo 로 복사하여 바뀐 최종값을 적용시키게끔 함으로써 다시 다이얼로그를 띄웠을 때는 위 코드의 실행으로 이전에 바뀐 설정값으로 화면이 초기화 될 것입니다.
위 코딩에서 메인프레임 클래스와 도큐먼트 클래스를 참조했으니 이 파일 ComSettingsDialog.cpp 상단에 아래와 같이 이 두 클래스가 정의된 헤더를 인클루드 해주세요.
#include "MainFrm.h";
#include "ComConstructorDoc.h";
|
이제 컴파일 하고 메뉴의 [통신포트 설정] 을 선택하면 아래와 같이 각 콤포넌트 요소들에 m_ComInfo 의 초기값이 적용된 화면을 보실 수 있습니다.
그러나 아직 포트를 바꾼다든가 조작을 하여 메뉴의 [접속] 을 실행해도 바뀐 값이 적용되지 않을것입니다. 다이얼로그 내 버튼들에 대한 이벤트 처리와 다이얼로그를 닫았을때 바뀐 설정 저장 변수값이 적용되어야 하니까요. 이 동작을 위한 코딩을 하겠습니다.
다이얼로그에서 통신 설정을 바꾸기 위해 각 라디오 버튼들을 클릭 했을 때에 대한 이벤트 처리입니다. 이런 조작을 하게되면 이 다이얼로그가 닫힐 때까지는 이 다이얼로그가 갖고있는 설정 저장 변수인 m_ComInfoThis 에 기억되게 한다고 얘기했었습니다.
클래스위저드를 띄운 후 아래 그림과 같이 왼쪽 Object IDs 에서 CComSettingsDialog 를 선택한 다음 오른쪽 Messages 란에서 OnCommand 를 선택 후 Add Function 을 누르면 아래 Member functions 란에 OnCommand 라는 항목이 리스트에 새로 올라오는데 OK 버튼을 누르면 OnCommand 메소드가 생성됩니다.
그럼 OnCommand 함수에다 이 다이얼로그의 라디오 버튼을 조작할 때의 메세지 처리를 코딩해보겠습니다.
BOOL CComSettingsDialog::OnCommand(WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
INT idControl = (int)LOWORD(wParam);
INT nEvent = HIWORD(wParam);
HWND hControl = (HWND)lParam;
if(nEvent == BN_CLICKED)
{
switch(idControl)
{
case IDC_COM1:
case IDC_COM2:
case IDC_COM3:
case IDC_COM4:
m_ComInfoInThis.nPortNum = idControl - IDC_COM1 + 1;
break;
case IDC_BAUDRATE_1200: m_ComInfoInThis.eBaudrate = CSerial::EBaud1200; break;
case IDC_BAUDRATE_2400: m_ComInfoInThis.eBaudrate = CSerial::EBaud2400; break;
case IDC_BAUDRATE_9600: m_ComInfoInThis.eBaudrate = CSerial::EBaud9600; break;
case IDC_BAUDRATE_14400: m_ComInfoInThis.eBaudrate = CSerial::EBaud14400; break;
case IDC_BAUDRATE_19200: m_ComInfoInThis.eBaudrate = CSerial::EBaud19200; break;
case IDC_BAUDRATE_38400: m_ComInfoInThis.eBaudrate = CSerial::EBaud38400; break;
case IDC_BAUDRATE_56000: m_ComInfoInThis.eBaudrate = CSerial::EBaud56000; break;
case IDC_BAUDRATE_57600: m_ComInfoInThis.eBaudrate = CSerial::EBaud57600; break;
case IDC_BAUDRATE_115200: m_ComInfoInThis.eBaudrate = CSerial::EBaud115200; break;
case IDC_DATA_5:
case IDC_DATA_6:
case IDC_DATA_7:
case IDC_DATA_8:
m_ComInfoInThis.eDataBits = (CSerial::EDataBits)(idControl - IDC_DATA_5 + CSerial::EData5);
break;
case IDC_PARITY_NONE:
case IDC_PARITY_ODD:
case IDC_PARITY_EVEN:
case IDC_PARITY_MARK:
case IDC_PARITY_SPACE:
m_ComInfoInThis.eParity = (CSerial::EParity)(idControl - IDC_DATA_5 + CSerial::EParNone);
break;
case IDC_STOP_1:
case IDC_STOP_15:
case IDC_STOP_2:
m_ComInfoInThis.eStopBits = (CSerial::EStopBits)(idControl - IDC_STOP_1 + CSerial::EStop1);
break;
case IDC_HANDSHAKING_OFF:
case IDC_HANDSHAKING_HARDWARE:
case IDC_HANDSHAKING_SOFTWARE:
m_ComInfoInThis.eHandshake = (CSerial::EHandshake)(idControl - IDC_HANDSHAKING_OFF + CSerial::EHandshakeOff);
break;
}
}
return CDialog::OnCommand(wParam, lParam);
}
|
클래스 위저드를 사용하면 각 라디오버튼 하나하나에 대한 메세지 처리 함수를 선언할 수 있습니다만 코드가 일목요연하게 보일 수 있도록 OnCommand 함수안에 버튼에 대한 이벤트 처리 기능을 모두 넣었습니다.
이제 다이얼로그를 OK 버튼을 통해 닫았을 때 접속 명령 시 참조되는 도큐먼트 클래스가 갖고있는 저장변수의 내용이 갱신되게끔 하겠습니다.
리소스뷰의 통신 설정 다이얼로그 화면의 OK 버튼에다 마우스 클릭을 하면 아래 그림과 같이 OnOK 메세지 함수를 정의할 수 있게 박스가 뜰 것입니다.
여기에 OK 버튼을 누르면 코드 에디터로 전환되고 OnOK 함수에 대한 기능을 넣습니다.
void CComSettingsDialog::OnOK()
{
// CMainFrame 클래스 인스턴스를 얻어옴
CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
// CMainFrame 인스턴스로부터 도큐먼트 클래스 인스턴스를 얻어옴
CComConstructorDoc *pDoc = (CComConstructorDoc*)pFrame->GetActiveDocument();
// pDoc 포인터에 대한 검사 : 포인터를 못얻어오면 (== NULL) 런타임에러에 의한 프로그램 종료
ASSERT(pDoc);
// 도큐먼트의 m_ComInfo 변수로 내용 복사
CopyMemory(&pDoc->m_ComInfo, &m_ComInfoInThis, sizeof(CComInfo));
CDialog::OnOK();
}
|
이 다이얼로그가 사용자 조작 시 데이터 저장을 위해 임시로 사용하고 있던 m_ComInfoInThis 값을 도큐먼트의 인스턴스 포인터를 갖고와서 m_ComInfo 에다가 복사해주고 있습니다.
OnOK 함수는 사용자가 다이얼로그를 IDOK 로 정의된 ID 값을 갖고있는 버튼을 눌렀을 때의 메세지 처리 함수입니다. 이 메세지 처리도 OnCommand 함수에 넣어줄 수 있지만 특별한 버튼이므로 이렇게 따로 클래스 위저드의 메세지함수 생성 절차를 따라 만들어진 함수에 넣어봤습니다. 함수의 실행 끝에서 호출되는 부모클래스의 CDialog::OnOK() 함수에 의해 다이얼로그가 닫히고 메모리에서도 제거될 것입니다.
이로써 이전에 만들었던 접속과 접속 종료만 되는 시리얼 통신 프로그램에다 통신 세부 설정을 선택해줄 수 있는 기능까지 넣었습니다.
”통신포트 설정” 메뉴를 통해 포트번호를 바꾼다던가 한 후 접속을 누르면 바뀐 포트번호로 접속되거나 포트가 존재하지 않으면 오류내용이 메세지 박스로 표시되도록 돼 있습니다.
여기까지 짜여진 프로그램을 사용하다 보면 미흡한점, 혹은 개선해야 할 점을 발견하게 될 것입니다. 예를 들면 현재는 수신되는 데이터의 모니터링만 되는데 송신창을 만든다든가 수신데이터 형식을 스트링 뿐만 아니라 16진수 바이너리로 표시하게 한다든가 그런것들이지요.
지금까지 만든 이 통신 프로그램을 좀더 쓸만하게 다듬고 싶다는 생각이 있으시면 MFC 프로그래밍을 익히시면서 여러분들이 직접 기능을 보완해보시길 바랍니다.
그런 작업을 하다가 막히는 부분이 있으시다면 다른 동호회나 이곳 사이트에 물어보셔두 되고요.
이 글을 모두 보신 분들 중 혹시 발견하셨는지 모르겠는데 첨부된 캡쳐그림이 처음에는 윈도우XP 였다가 글 후미에는 윈도우7 화면으로 바뀌어있습니다.
사실 시리얼 통신 프로그램 만들기라는 글을 올린 후 통신 설정 기능 넣는 방법에 대한 요청이 있어서 글을 임시로 작성해 놨었는데 그게 거의 10년전의 일입니다.
그동안 제 홈페이지를 내팽겨쳐놨다가 블로그로 리뉴얼 하면서 정리차원에서 이제서야 글을 대충 마무리를 했지만, 처음에는 비주얼 C++ 6.0 기반으로 씌어졌는데 시간이 지나 비주얼 스튜디오도 여러번 버전이 업데이트되어 6.0은 이제 잘 쓰지 않는 버전이 되어버렸습니다.
그래서 후에 시간이 되면 본 시리얼 통신 프로그램 만들기 글을 최신의 Visual Studio 버전으로 업데이트 해볼까 합니다. 이 글을 보러 찾아오는 분들이 얼마 안되지만 그래도 요청이 있다면 다시 글을 써보겠습니다.
여태까지의 이 글 내용을 구현한 프로그램 소스를 첨부하겠습니다.






Leave a Reply
댓글을 달기 위해서는 로그인해야합니다.