[VC++] 시리얼 통신 프로그램 만들기[3-4] – 통신 설정 기능 구현하기

완성된 시리얼 통신 프로그램

다음은 통신 설정 윈도우 CComSettingsDialog 가 띄워진 이후의 동작을 만들어보겠습니다.

클래스위저드를 띄운 후 아래 그림과 같이 왼쪽 Object IDs 에서 CComSettingsDialog 를 선택한 다음 오른쪽 Messages 란에서 WM_INITDIALOG 를 선택 후 Add Function 을 누르면 아래 Member functions 란에 OnInitDialog 라는 항목이 리스트에 새로 올라오는데 OK 버튼을 누르면 OnInitDialog 메소드가 생성됩니다.

ComSettingsDialog_INITDIALOG

이 메소드는 다이얼로그가 생성 될 때 초기화 작업을 해주도록 윈도우가 발생시키는 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 함수 생성

 

그럼 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 메세지 함수를 정의할 수 있게 박스가 뜰 것입니다.

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 버전으로 업데이트 해볼까 합니다. 이 글을 보러 찾아오는 분들이 얼마 안되지만 그래도 요청이 있다면 다시 글을 써보겠습니다.

여태까지의 이 글 내용을 구현한 프로그램 소스를 첨부하겠습니다.

 

 

이 글을 공유하기:

Be the first to comment

Leave a Reply