C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[991] 지정한 시간 인터벌로 메소드를 실행해주는 클래스를 이용한 화면갱신.
김태선 [cppbuilder] 8500 읽음    2010-07-09 15:14
for(int c = 0; c <= 10000;  c++)
{
   Label1->Caption = c;
   Label1->Update();
}
이 코드는 어떻게 동작할까요?
어떻게 동작하긴요. 그냥 폼에 버턴 하나 레이블 하나 올려 놓고 위 코드를 복사해서 실행해 보면 금방 알죠.
예. 뭔가 이상하죠.

화면 갱신이 매우 부자유스럽고, 이렇게 값을 일일이 찍는다고 눈에 다 보이지도 않죠.

이 예는 극단적인 것이지만, 이런 코드를 만드는 사람이 의외로 많습니다.
아. 저만 그렇다구요? 예 그렇군요.

이를 화면에 보다 잘 보이게 개선해 보겠습니다.
    for(int c = 0; c <= 1000;  c++)
    {
        Sleep(1);
        Label1->Caption = c;
        Label1->Update();
    }
Sleep을 쓸 경우 10000까지 카운트하면 너무 값이 크니 1000으로 줄였습니다.
이제 좀 잘 보이고, 뭔가 제대로 된 것 같죠.

그런데 실무에서는 이런 코드를 쓸 일은 극히 없습니다.
Sleep을 써서 괜히 시간 낭비할 필요도 없고, 이런 식의 단순한 루틴 구조가 아니라
전체 복잡한 루프 중에 화면에 메시지를 찍는 것이 대부분입니다.
또 Sleep(1)을 쓰기는 했지만, 역시 눈에도 안보이는 수 많은 글자를 화면에 찍은 셈입니다.
인간의 눈은 대략 초당 30~60프레임만 표시해도 매우 자연스러운 화면을 보게 되는데
위 예제는 Sleep(1)이 들어 있어 1ms 쉬게 했으므로 초당 약 1000 프레임을 표시한 셈이죠.
아, 그게 아니죠. Sleep(1) 한다고 꼭 1ms 쉬는게 아니라 그 근사값으로 쉬게 됩니다.
그리고 화면에 찍는데 시간이 걸리므로
실제로 동작 시간을 체크해보면 약 몇백ms 정도 더 걸리는 현상이 생깁니다.

눈에 보이지도 않는 것을 무식하게 화면에 찍어대는 것도 문제이지만,
멀티쓰레드 하에서 이런 코드를 쓰게 될때  TCanvas에 Lock을 적절히 걸어 주지 않으면
화면이 깨지는 것을 경험하게 됩니다.

그러면 여기서 프로그래머가 취할 가장 적절한 조치는,
일정 시간마다 화면에 표시하게 하는 것이고, 멀티쓰레드인 경우 TCanvas에 lock을 잘 걸어주어야 한다는 점입니다.

이를 위해서 TThread에서 상속받은 쓰레드 클래스를 만들고 일정시간 마다
화면 갱신을 하게 하는 것도 좋은 방법이고, 또 간편하게 50ms 타이머 같은 것을 써서
화면을 갱신하게 하는 것도 좋은 방법입니다.

그런데 이를 위해서는 쓰레드나 타이머를 관리해주어야 하는 일이 생깁니다.
물론 이는 매우 쉬운 일이나,
여기서는 그냥 순차적 프로그래밍 코드 위에서 이러한 처리를 하게 만들어 주는 것이
더욱 편리하므로, 이것을 도와줄 클래스를 하나 만들어 봤습니다.
말이 클래스지 그냥 단순한 코드입니다.

원래의 목적은 화면 갱신이 아니라 특정한 인터벌로 지정한 클래스 메소드를 실행하는 것이지만,
아무래도 화면 갱신에 가장 많이 쓰일 것 같군요.

//---------------------------------------------------------------------------
//
// 지정한 시간 이후에만 화면에 메시지를 찍는다.
// 이는 사실상 1초에 30장. 즉 33ms 안에 연속해서 찍어봐야
//    사람 눈에는 마지막 것만 보이기 때문인데, 이렇게 빠르게 찍는 것은
//   TCanvas가 lock이 안걸려 있으면 메모리 중복이나 어긋남 현상 때문에
//   화면이 좀 엉망이 되는 일도 있는데(특히 멀티하에서는 많이 생김), 이것도 방지한다.
//    보통은 40ms = 1초에 25장, 50ms = 1초에 20장 정도가 적합하다.
//
// 이 클래스는 화면 갱신 뿐만 아니라 정기적으로 어떤 루틴을 실행해야 할때도
//   유용하게 사용할 수 있다. 주의할 것은, Run을 호출하는 시점에 시간이 되면 지정한 루틴을 실행하는 것이므로,
//    정확하게 지정한 Term을 가지는 것은 아님에 유의한다.
// Written by KTS.

class CRunWaitTime
{
private:
    int        WaitTime;
    TTime    LastTime;

public:
    typedef void (__closure *RunMethod)(void);

    CRunWaitTime(int waittime_millisecond)
    {
        WaitTime = waittime_millisecond;
        LastTime = Now();
    }
    // waittime_millisecond == 0이면 시간무시하고 표시하기.
    void    Run(RunMethod method, int waittime_millisecond = -1)
    {
        int waittime = waittime_millisecond;
        if (waittime == -1)
            waittime = WaitTime;
        TTime now = Now();
        if (MilliSecondsBetween(now, LastTime) >= waittime)
        {
            method();
            LastTime = now;
        }
    }
};


그러면 처음 예로 든 예제를 고쳐보죠
우선 화면에 찍을 데이타를 전달해 줄 구조체가 있어야 겠죠.
struct
{
    int     Count;
} Data;

다음은 33ms 즉 초당 30장 갱신하도록 설정합니다.

CRunWaitTime RunWaitTime(33);

다음은 테스트.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    TTime start = Now();
    for(int c = 0; c <= 1000;  c++)
    {
        Sleep(1);
        Data.Count = c;
        RunWaitTime.Run(MsgOut);
    }
    MsgOut();
    TimeOut(start);
}

void TForm1::MsgOut()
{
    Label1->Caption = Data.Count;
    Label1->Update();
}

void    TForm1::TimeOut(TTime& start)
{
    int time = MilliSecondsBetween(Now(), start);
    Caption = time;
}

TimeOut 메소드는 실행시간이 얼마나 걸렸는지 검사해서 화면에 찍어주는 역할을 합니다.
루틴 실행 시간은 얼마나 걸리나 체크하기 위해서 넣어 놓은 코드입니다.

위에 Sleep(1)이 여전히 들어가 있는데, 이걸 빼면 너무 빨리 지나가 버려서
눈으로 확인하기 좋으라고 넣어 놓은 코드일 뿐, 실무에서는 빼야 합니다.

이렇게 하면 눈이 보이지 않는데 찍어대는 현상을 없앨 수 있습니다.
또 그 만큼 화면 찍는데 시간 낭비를 하지 않고, 화면 출력과 관계된 문제도 줄일수 있습니다.

하나 주의할 것은 위의 예처럼 갱신시간을 33ms로 주어 화면을 갱신하라고 해도 이는 전적으로
delay 시간 없이 지속적으로 MsgOut 메소드를 호출한 경우에만 해당하고 실제로는
중간 중간에 MsgOut 루틴을 호출하게 되므로 약간의 차이가 날수 있으므로 최종적으로 눈으로 확인하고
적정한 갱신 시간을 결정하는 것이 필요합니다.


아주 간단한 건데 설명하려니 길군요.

그럼.

+ -

관련 글 리스트
991 지정한 시간 인터벌로 메소드를 실행해주는 클래스를 이용한 화면갱신. 김태선 8500 2010/07/09
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.