본문으로 바로가기

Tcl/Tk 둘러보기

category 카테고리 없음 2024. 4. 8. 12:40

Tcl/Tk가 무엇인지 궁금하신 분들을 위해, 간단히 둘러보도록 하겠습니다. 다음은 Tcl/Tk를 개발하기 위해 필요한 최소한의 조건 두가지입니다.

간단한 프로그램 만들어 보기

Tcl/Tk를 여러분의 시스템에 설치를 하셨다면 우선 간단한 프로그램을 짜보도록 하겠습니다.

#!/usr/local/bin/wish
label .label1 -text "안녕하세요."
button .button1 -text 종료 -command exit
pack .label1 .button1 -side top

첫번째 줄은 유닉스 시스템에서만 필요하며, 인스톨한 Tcl/Tk의 인터프리터인 wish의 패스를 적어줍니다. Tcl/Tk의 기본은, 위와 같이 라벨(label)이나 버튼(button)등의 위젯을 만든 후 순서대로 pack을 함으로써 이루어집니다.

우선 위와 같이 소스코드를 작성해서 demo.tcl로 저장합니다. 실행방법은 여러 가지가 있는데, 윈도즈에서는 탐색기에서 더블클릭으로 실행할 수 있고, 도스 커맨드창에서 wish demo.tcl로 실행을 할수 있습니다. 유닉스 시스템에서는

chmod 755 demo.tcl

을 한 다음 셸에서 ./demo.tcl 로 실행할 수 있습니다.

실행을 하면 위와 같은 화면이 나올 것입니다. 그러나 여기서 기뻐하지 말고 조금 더 생각해 보면 다른 프로그램을 작성할 때 와는 다른 것을 느낄 수 있을 것인데, Tcl/Tk의 프로그램은 실행을 한 그대로의 상태로 정지를 하고 있습니다. 다른 언어는 소스코드 끝까지 실행을 하면 프로그램을 종료하는데 반해, Tcl/Tk는 자동적으로 그 처리를 해주고 있습니다. 정말로 프로그램을 종료를 하고 싶으면, Tcl의 exit 커맨드를 내리면 됩니다. 위의 예에서는 종료 버튼을 누르면 프로그램이 종료됩니다.

button .button1 -text 종료 -command exit

여기서 -command 옵션은 버튼이 눌리면 실행할 명령을 지정하는 것이며, 예제에서는 exit 라는 명령을 내리고 있습니다. 즉 버튼이 눌리면 종료한다. 입니다.

이렇게 버튼에는 -command라는 옵션이 있듯이, label이나 entry 등의 위젯에도 각 위젯의 컬러나 기타 다양한 옵션이 존재합니다.

#!/usr/local/bin/wish

label .label1 -text "안녕하세요." \
   -foreground white -background red \
   -font {굴림체 16 normal}
button .button1 -text 종료 -command exit \
   -background midnightblue \
   -foreground white
pack .label1 .button1 -side top

위와 같이 폰트의 사이즈와 체도 굴림체로 바뀌었으며, 바탕색도 붉은색으로 변경되었습니다. Tk의 위젯은 이런 방법으로 UI를 꾸며 나갈 수 있습니다.

Procedure와 Event

이번에는 위젯 위에 다른 위젯을 올리는 방법과 procedure, event에 대하여 간단히 알아보도록 하겠습니다.

label .label1 -text "식사 시간 체크" \
    -foreground red -font {굴림체 16 normal}
frame .frame1 -relief groove -borderwidth 3
button .frame1.button1 -text "체크" -command procedure1
button .frame1.button2 -text "종료" -command exit
pack .frame1.button1 .frame1.button2 -side left
pack .label1 .frame1 -side top

proc procedure1 {} {
    set hour [clock format [clock seconds] -format %H]
    if {$hour >= 6 && $hour <= 8} {
        set message1 { " 아침 식사 시간입니다." }
    } elseif {$hour >= 12 && $hour <= 13} {
        set message1 { "점심 시간입니다." }
    } elseif {$hour >=16 && $hour <= 20} {
        set message1 { "저녁 시간입니다." }
    } else {
        set message1 { "식사시간이 아니군요.." }
    }

    tk_messageBox -message $message1
}

몇 개의 위젯을 배치하고 싶을 때에는 frame 위젯을 아래에 깔고, 그 위에 버튼이나 리스트 박스 같은 위젯을 올립니다. 위젯의 이름은 파일의 패스(path)처럼 .위젯이름.위젯이름.위젯이름 .... 같은 형식을 하고 있으며, 위의 예에서는 .frame1.button1은 frame1 위에 올려진 button1 입니다.

추가로 설명하자면, Tcl/Tk의 wish를 실행하면, 기본으로 뜨는 윈도우 자체의 이름이 공문자( )로, 처음에 .label1, .button1 인 것은 이와 같은 이유 때문입니다.

앞의 예제에서는 첫 번째 예제와는 다르게 버튼을 하나 더 생성했습니다. 버튼을 누르면 현재시간을 얻어내어 알맞은 식시시간을 알려줍니다. 이 경우에는 프로시져(procedure)의 형태를 두고, 버튼의 -command를 누르면 procedure1을 호출합니다. 또 ‘파일이 이미 존재합니다. 덮어쓸까요? (y/n)’, ‘파일을 저장할 수 없습니다. 중지(A), 재시도(R), 무시(I)’와 같은 메시지를 내거나, 뭔가 확인을 하려 할 때, tk_messageBox로 간단하게 처리할 수 있습니다.

다른 위젯들

Tcl/Tk에는 button, label, frame 이외에도 많은 위젯들이 있습니다. 이번에는 그중에 엔트리(entry), 리스트 박스(list box)을 사용해 보겠습니다. 또, 리스트 박스라 하면 당연히 따르게 마련인, 스크롤 바(scroll bar)도 같이 사용해 보도록 하겠습니다.

label .lname -text "이름:" -anc e -font {굴림체 10 normal}
entry .ename -width 40
label .lblo -text "혈액형:" -anc e -font {굴림체 10 normal}
frame .fblo
set lb [listbox .fblo.lsta -selectmode single \
     -yscrollcommand ".fblo.scrv set" -width 15]
foreach e { "A형" "B형" "AB형" "O형" } {
        $lb insert end $e
}
$lb selection set 0
scrollbar .fblo.scrv -orient vertical -command ".fblo.lsta yview"
pack .fblo.lsta .fblo.scrv -side left -fill both

frame .fb -relief groove -borderwidth 3
button .fb.c1 -text "인사" -font {굴림체 10 normal} \
     -command "procedure1 .fblo.lsta .ename"
button .fb.c2 -text "종료" -font {굴림체 10 normal} \
     -command exit

bind .fb.c1 {
        set name [.ename get]
        if {"$name" == ""} {
                tk_messageBox -message "이름을 입력하세요."
        }
}
pack .fb.c1 .fb.c2 -side left -fill both

. configure -width 250 -height 100
place .lname -relx 0.0 -rely 0.0 -relw 0.3 -relh 0.2
place .ename -relx 0.3 -rely 0.0 -relw 0.5 -relh 0.2
place .lblo -relx 0.0 -rely 0.3 -relw 0.3 -relh 0.2
place .fblo -relx 0.3 -rely 0.2 -relw 0.5 -relh 0.5
place .fb -relx 0.3 -rely 0.75 -relw 0.32 -relh 0.25

proc procedure1 {listbox entry} {
        set hour [clock format [clock seconds] -format %H]
        if {$hour >= 6 && $hour <= 8} {
                set message1 { " 아침 식사 시간입니다." }
        } elseif {$hour >= 12 && $hour <= 13} {
                set message1 { "점심 시간입니다." }
        } elseif {$hour >=16 && $hour <= 20} {
                set message1 { "저녁 시간입니다." }
        } else {
                set message1 { "식사시간이 아니군요.." }
        }

        tk_messageBox -message "$message1,[$entry get]씨."
        set btype [$listbox get [$listbox curselection]]
        if {"$btype" != "불명"} {
                tk_messageBox -message "${btype}의 사람은 오늘 행운의 날입니다."
        }
}

entry는 간단히 사용할 수 있습니다.

entry .ename -width 30

-width로 문자 입력 폭을 설정할 수 있으며, 생략 시에는 적당한 크기가 지정됩니다.

set lb [listbox .fblo.lsta -selectmode single \
     -yscrollcommand ".fblo.scrv set" -width 15]
foreach e { "A형" "B형" "AB형" "O형" } {
     $lb insert end $e
}
$lb selection set 0
scrollbar .fblo.scrv -orient vertical \
     -command ".fblo.lsta yview"
pack .fblo.lsta .fblo.scrv -side left -fill both

bind 커맨드는 나중에 설명드리겠지만, 버튼의 -command 옵션처럼 위젯에 대하여 이벤트가 일어나면 그 이벤트에 해당하는 작업을 할 수 있게 하는 커맨드라 생각하시면 됩니다.

bind .fb.c1 {
    set name [.ename get]
    if {"$name" == ""} {
        tk_messageBox -message "이름을 입력하세요."
    }
}

위의 부분은 버튼 .fb.c1 에 마우스 커서가 들어온 순간에, 엔트리 위젯인 .ename의 입력한 내용(ename get)을 검사하여, 입력된 내용이 없으면 경고 메시지를 출력합니다. 위젯을 올리는 데는, pack 커맨드 이외에도, place, grid라는 커맨드가 있습니다. place는 위젯을 픽셀의 절대 좌표나, 위젯의 사이즈에 대한 비율(소수)로 지정하여 올립니다. 비율로 지정하면, 윈도우의 사이즈가 변경이 되더라도, 윈도우의 싸이즈 비율에 맞게 확대/축소되기 때문에, 보통은 비율로 지정하는 것이 좋습니다.

. configure -width 250 -height 100
place .lname -relx 0.0 -rely 0.0 -relw 0.3 -relh 0.2
place .ename -relx 0.3 -rely 0.0 -relw 0.5 -relh 0.2

-relx, -rely는 왼쪽상단의 위치를, -relw, -relh는 폭과 높이를 지정할 수 있습니다. 실제로 숫자를 바꾸어 테스트해보면서, 최적의 위치를 찾으시면 됩니다. Visual Basic, Borland C++ Builder 등의 통합 개발환경에서는, 마우스 조작만으로 확인하면서 최적의 위치를 찾으실 수 있습니다. Tcl/Tk로 복잡한 화면을 만들 생각이면, 방안지(오목판이라두...)라도 준비하고, 통합개발환경을 대신해야 할지도 모릅니다. 리스트 박스의 현재 상태를 얻는 방법은

  • 몇 번째 항목(들)이 선택되었는지를 얻는다.(curselection)
  • 그 항목에 쓰여 있는 텍스트를 얻는다.(get)

의 2단계로 처리를 합니다.

set btype [$listbox get [$listbox curselection]]
if {$btype != ""} {
     tk_messageBox -message "${btype}의 사람은 오늘 행운의 날입니다."
}

이름으로 점 보는 프로그램 만들기

아주 간단한 점(운세) 보는 프로그램을 만들어 볼 예정이며, 이번은 이름만을 가지고 점을 쳐보겠습니다.

wm title . "Tcl/Tk로 점치자."
label .la -text "이름을 영문자로 입력해 주세요." \
     -anchor w -font {굴림체 9 normal}
entry .e -textvariable name
button .cmda -text "확인" \
     -command judge_name -font {굴림체 9 normal}
button .cmde -text "종료" \
     -command exit -font {굴림체 9 normal}
text .ta -state disabled -font {굴림체 9 normal}

place .la -relx 0.1 -rely 0.0 -relw 1.0 -relh 0.15
place .e -relx 0.1 -rely 0.15 -relw 0.6 -relh 0.15
place .cmda -relx 0.75 -rely 0.15 -relw 0.1 -relh 0.15
place .cmde -relx 0.85 -rely 0.15 -relw 0.1 -relh 0.15
place .ta -relx 0.0 -rely 0.35 -relw 1.0 -relh 0.6

. configure -width 300 -height 130

proc nameis {pattern name} {
    if {[regexp -nocase "^(\\w+\\s+)?${pattern}(\\s+\\w+)?\$" \
        [string trim $name] all p1 p2]} {
        if {"$p1" != "" || "$p2" != ""} {
            return 1
        }
        return 0
    } else {
        return 0
    }
}

proc judge_name {} {
    global name
    .ta configure -state normal
    .ta delete 1.0 end

    if {[nameis min $name]} {
        .ta insert end "오늘은 행운이 가득한 날입니다."
    } elseif {[nameis kim $name]} {
        .ta insert end "오늘 친구에게 돈 빌려주지 마세요."
    } else {
        .ta insert end "오늘은 점을 칠수 없습니다. ^^"
    }
    .ta configure -state disabled
}

위의 예제는 단지 성이 ‘min’인지 ‘kim’인 지만을 검출하고 있습니다. Tcl/Tk는 다른 awk나 펄, 파이선등의 유닉스 기원의 스크립트언어와 같이 정규표현이라는 문자열 패턴 매치(pattern match)를 지원하고 있습니다. 이것으로 C 언어에서는 간단히 할 수 없는 체크를 할수 있습니다.

if {[regexp -nocase "^(\\w+\\s+)?${pattern}(\\s+\\w+)?\$" \
     [string trim $name] all p1 p2]} {
     if {"$p1" != "" || "$p2" != ""} {
         return 1
     }
     return 0
} else {
     return 0
}

생일로 점 보는 프로그램 만들기

이번에는 생일을 기준으로 점을 쳐보겠습니다.

wm title . "점을 보자."
label .la -text "생일을 입력하세요." \
     -font {굴림체 9 bold}
frame .f -rel groove -borderwidth 3
entry .f.ea -width 5
label .f.la -text 월(月)
entry .f.eb -width 5
label .f.lb -text 일(日)
button .f.cmda -text "점치기" \
     -command {procedure1 [.f.ea get] [.f.eb get] .ta}
button .f.cmdb -text "종료" -command exit
pack .f.ea .f.la .f.eb .f.lb .f.cmda .f.cmdb -side left
text .ta -height 5 -state disabled \
     -font {굴림체 9 normal} -width 30
pack .la .f .ta -side top -fill x

proc procedure1 {month day ta} {
    array set days {
        1 31 2 29 3 31 4 30 5 31 6 30 \
        7 31 8 31 9 30 10 31 11 30 12 31
    }
    set messages {
        {대수롭지 않은 발언으로, 상대방의 오해를삼.}
        {금전운이 없음.}
        {서울은행 옆을 지키고 있으면 도둑을 잡을수 있음.}
        {오늘은 의견을 분명히 말하자.}
        {오늘의 최고 음식은 김치볶음밥.}
        {오늘 방을 청소하면 좋은일이 생김.}
        {오늘 아주 이쁜 여자를 만날수 있음.}
    }
    if {$month>=1&&$month<=12&&$day>=1&&$day<=$days($month)} {
        set n [expr ($month+$day) % [llength $messages]]
        $ta configure -state normal
        $ta delete 1.0 end
        $ta insert end [lindex $messages $n]
        $ta configure -state disabled
    } else {
        tk_messageBox -message "잘못된 입력입니다."
    }
}

생일을 입력하면, 위의 스크린샷과 같이 훌륭한 아무 근거 없는 점을 보여줍니다. Tcl/Tk의 배열은 크게 리스트(list)와 배열(array)의 종류 두 가지 있습니다. Tcl/Tk의 리스트는 아래와 같은 형태가 대표적이지만,

set messages {
     {대수롭지 않은 발언으로, 상대방의 오해를삼.}
     {금전운이 없음.}
     {서울은행 옆을 지키고 있으면 도둑을 잡을수 있음.}
     {오늘은 의견을 분명히 말하자.}
     {오늘의 최고 음식은 김치볶음밥.}
     {오늘 방을 청소하면 좋은일이 생김.}
     {오늘 아주 이쁜 여자를 만날수 있음.}
}

언제나 이와 같은 형태는 아닙니다. 예를 들면, “This is the joy in the world.” 이것도 리스트입니다. 또한 {This is the joy in the world} 이것도 리스트입니다. 또한 {”joy in the world” “joy on your mind” “joy of your control”} 이것은 3개의 문자열로 이루어진 단순 리스트이지만, 동시에 각각이 4개의 단어로 이루어지는 3개의 리스트, 즉 리스트의 리스트이기도 합니다. 한편,

set days(10) 31
set days(11) 30
set days(12) 31

위와 같이 괄호 안의 문자열과(10,11,12)과 값(31,30,31)을 대입시키는 데에도 사용합니다. 여기에서는 array set 커맨드를 써서 한 번에 대입을 하였습니다.

array set days {
     1 31 2 29 3 31 4 30 5 31 6 30 \
     7 31 8 31 9 30 10 31 11 30 12 31
}

만약 사용자에게 tcl의 소스파일을 직접 제공해 준다면, 사용자는 소스를 볼 수 있기 때문에 이 점이 가짜인 것이 발각되어, 난처해질 것입니다. 해결책은 여러 가지가 있지만, 보통의 해결 책은 ‘C언어로 작성한다.’ 입니다. C 언어는 기계어로 컴파일을 하기 때문에, 보통은 해석을 할 수가 없습니다. 하지만 이렇게 되면, Tcl/Tk의 장점인 ‘간단하게 윈도우 어플리케이션을 만들수 있다’ 라는 장점이 없어집니다. 또한 윈도우즈 API나 X 윈도우즈 시스템의 두꺼운 메뉴얼을 보지 않으면 안됩니다. 우선, 윈도우 어플리케이션이 아닌, 보통의 콘솔 어플리케이션으로써 C언어로 점치는 프로그램을 만들어 보도록 하겠습니다. 이때 커맨드 라인에서 생일을 옵션으로 입력받을 수 있게 작성해야 할 것입니다.

int main(int argc, char *argv[]) {
    int month, day;

    int days[13] = {
        31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    char *messages[7] = {
        "대수롭지 않은 발언으로, 상대방의 오해를삼.",
        "금전운이 없음.",
        "서울은행 옆을 지키고 있으면 도둑을 잡을수 있음.",
        "오늘은 의견을 분명히 말하자.",
        "오늘의 최고 음식은 김치볶음밥.",
        "오늘 방을 청소하면 좋은일이 생김.",
        "오늘 아주 이쁜 여자를 만날수 있음.",};
    if(argc<=2) {
        fprintf(stdout, "잘못된 입력입니다.\n");
        exit(-1);
    }

    month = atoi(argv[1]);
    day = atoi(argv[2]);

    if(month>=1&&month<=12&&day>=1&&day<=days[month]) {
        int n;
        n = (month+day) % 7;
        fprintf(stdout, "%s", messages[n]);
    }

    return 0;
}

위와 같이 작성을 했다면, 전에 예로 들었던 Tcl/Tk 소스를 수정하여, C로 만든 실행파일을 호출해 결과를 받아 표시하도록 해야 합니다.

wm title . "점을 보자."
label .la -text "생일을 입력하세요." \
     -font {굴림체 9 bold}
frame .f -rel groove -borderwidth 3
entry .f.ea -width 5
label .f.la -text 월(月)
entry .f.eb -width 5
label .f.lb -text 일(日)
button .f.cmda -text "점치기" \
     -command {procedure1 [.f.ea get] [.f.eb get] .ta}
button .f.cmdb -text "종료" -command exit
pack .f.ea .f.la .f.eb .f.lb .f.cmda .f.cmdb -side left
text .ta -height 5 -state disabled \
     -font {굴림체 9 normal} -width 30
pack .la .f .ta -side top -fill x

proc procedure1 {month day ta} {
     array set days {
         1 31 2 29 3 31 4 30 5 31 6 30 \
         7 31 8 31 9 30 10 31 11 30 12 31
     }
     if {$month>=1&&$month<=12&&$day>=1&&$day<=$days($month)} {
         set command "demo.exe $month $day"
         set result [eval exec $command]
         $ta configure -state normal
         $ta delete 1.0 end
         $ta insert end $result
         $ta configure -state disabled
     } else {
         tk_messageBox -message "잘못된 입력입니다."
     }
}

이와 같이 exec 커맨드로 유닉스나, 윈도우즈의 다른 프로그램을 실행하여, 그 프로그램의 표준출력을 결과로 받을 수 있습니다. 이것으로 진정한 Tcl/Tk의 점보는 프로그램이 완성되었다 볼 수 있습니다.