`event` — 가상 이벤트 정의하고 이벤트 생성하기

event — 가상 이벤트 정의하고 이벤트 생성하기

서로 다른 운영체제에서 같은 동작(복사, 붙여넣기 등)을 각자의 단축키로 하다 보면, 바인딩 코드가 플랫폼마다 갈라져요. Tk는 가상 이벤트(virtual event) 로 그 단축키들을 하나로 묶어 주고, event generate로 실제 키·마우스 이벤트를 프로그램이 스스로 만들어 낼 수도 있게 해 줘요.

출처: Tcl/Tk Manual — event

본문

시그니처

event option ?arg arg ...?

event 명령은 가상 이벤트 정의와 이벤트 합성 등 윈도우 시스템 이벤트를 다루는 여러 기능을 제공해요. 첫 인자에 따라 형태가 나뉘며, 현재 지원되는 형태는 다음과 같아요.

event add <<virtual>> sequence ?sequence ...?
event delete <<virtual>> ?sequence sequence ...?
event generate window event ?option value option value ...?
event info ?<<virtual>>?

하위 명령

event add <<virtual>> sequence ?sequence ...? — 가상 이벤트 virtualsequence 인자들이 주는 물리 이벤트 시퀀스와 연결해서, 그중 어느 시퀀스가 발생하든 가상 이벤트가 발동되게 해요. virtual은 아무 문자열 값이고, sequencebind 명령의 시퀀스 인자에 허용되는 값 중 아무거나 될 수 있어요. virtual이 이미 정의돼 있으면 새 물리 시퀀스가 기존 시퀀스에 추가돼요.

event delete <<virtual>> ?sequence sequence ...?virtual 가상 이벤트와 연결된 시퀀스 중 각각을 삭제해요. virtual은 아무 문자열, sequencebind 시퀀스 인자에 허용되는 값이면 됩니다. virtual에 현재 연결되지 않은 시퀀스는 무시돼요. sequence 인자를 주지 않으면 virtual의 모든 물리 시퀀스를 제거해서 가상 이벤트가 더 이상 발동하지 않게 해요.

event generate window event ?option value option value ...? — 창 이벤트를 생성하고, 마치 윈도우 시스템에서 온 것처럼 처리되게 해요. window는 이벤트가 생성될 창의 path name이며, 현재 애플리케이션의 창이면 winfo id가 반환하는 것 같은 식별자여도 돼요. event<Shift-Button-2><<Paste>> 같은 이벤트의 기본 설명이에요. window가 비어 있으면 화면 전체를 뜻하고 좌표는 화면 기준이에요. eventbind 명령의 시퀀스 인자에 허용되는 형태 중 아무거나 될 수 있지만, 하나의 단일 이벤트 패턴이어야 하고 시퀀스는 안 돼요.

옵션-값 쌍으로 x·y 마우스 위치 같은 이벤트의 추가 속성을 지정할 수 있어요(아래 EVENT FIELDS). -when 옵션을 지정하지 않으면 이벤트는 즉시 처리돼요. 즉 event generate 명령이 반환되기 전에 모든 핸들러가 완료돼요. -when을 지정하면 그 값이 언제 처리할지 결정해요. 키 이벤트 같은 일부 이벤트는 이벤트를 제대로 받으려면 창이 포커스를 가져야 해요.

event info ?<<virtual>>? — 가상 이벤트에 대한 정보를 돌려줘요. <<virtual>>을 생략하면 현재 정의된 모든 가상 이벤트 목록을 돌려줘요. 지정하면 그 가상 이벤트에 현재 정의된 물리 이벤트 시퀀스 목록을 돌려주고, 정의돼 있지 않으면 빈 문자열을 돌려줘요. 물리 이벤트 시퀀스에 바인딩되지 않은 가상 이벤트는 event info가 돌려주지 않아요.

이벤트 필드(EVENT FIELDS)

event generate 명령에 지원되는 옵션이에요. bind 명령의 바인딩 스크립트에서 허용되는 "%" 확장에 대응해요.

  • -above window — 이벤트의 above 필드. 창 path name이나 정수 창 id로 지정. Configure 이벤트에 유효. 바인딩 스크립트의 %a 치환에 해당.
  • -borderwidth sizeborder_width 필드. 화면 거리여야 함. Configure 이벤트에 유효. %B에 해당.
  • -button numberdetail 필드. 정수여야 하며, ButtonPress·ButtonRelease 이벤트의 기본 이벤트 인자에 있는 버튼 번호를 덮어씀. %b에 해당.
  • -count numbercount 필드. 정수. Expose 이벤트에 유효. %c에 해당.
  • -data stringuser_data 필드. 아무 값. 가상 이벤트에만 유효. 가상 이벤트의 %d 치환에 해당.
  • -delta number — MouseWheel 이벤트의 delta 필드. 정수. 마우스 휠이 회전한 방향·크기를 뜻하지만 화면 거리가 아니라 마우스 휠의 이동 단위예요. 보통 120의 배수예요. 예: 120은 text 위젯을 4줄 위로, -240은 8줄 아래로 스크롤. %D에 해당.
  • -detail detaildetail 필드로, 다음 중 하나여야 해요: NotifyAncestor, NotifyNonlinearVirtual, NotifyDetailNone, NotifyPointer, NotifyInferior, NotifyPointerRoot, NotifyNonlinear, NotifyVirtual. Enter·Leave·FocusIn·FocusOut 이벤트에 유효. %d에 해당.
  • -focus booleanfocus 필드. Enter·Leave 이벤트에 유효. %f에 해당.
  • -height sizeheight 필드. 화면 거리. Configure 이벤트에 유효. %h에 해당.
  • -keycode numberkeycode 필드. 정수. KeyPress·KeyRelease 이벤트에 유효. %k에 해당.
  • -keysym name — 유효한 keysym 이름(예: g, space, Return)이고, 그 대응 keycode 값이 이벤트의 keycode 필드로 쓰여 기본 이벤트 인자의 detail을 덮어씀. KeyPress·KeyRelease에 유효. %K에 해당.
  • -mode notifymode 필드로 NotifyNormal, NotifyGrab, NotifyUngrab, NotifyWhileGrabbed 중 하나. Enter·Leave·FocusIn·FocusOut에 유효. %m에 해당.
  • -override booleanoverride_redirect 필드. Map·Reparent·Configure 이벤트에 유효. %o에 해당.
  • -place whereplace 필드로 PlaceOnTop 또는 PlaceOnBottom. Circulate 이벤트에 유효. %p에 해당.
  • -root windowroot 필드. 창 path name 또는 정수 창 id. KeyPress·KeyRelease·ButtonPress·ButtonRelease·Enter·Leave·Motion 이벤트에 유효. %R에 해당.
  • -rootx coordx_root 필드. 화면 거리. 위 동일 이벤트들에 유효. %X에 해당.
  • -rooty coordy_root 필드. %Y에 해당.
  • -sendevent booleansend_event 필드. 모든 이벤트에 유효. %E에 해당.
  • -serial numberserial 필드. 정수. 모든 이벤트에 유효. %#에 해당.
  • -state statestate 필드. KeyPress·KeyRelease·ButtonPress·ButtonRelease·Enter·Leave·Motion에서는 정수여야 하고, Visibility 이벤트에서는 VisibilityUnobscured·VisibilityPartiallyObscured·VisibilityFullyObscured 중 하나여야 해요. 기본 이벤트에 지정된 Meta·Control 같은 수정자를 덮어씀. %s에 해당.
  • -subwindow windowsubwindow 필드. Tk 위젯 path name 또는 정수 창 id. 위 동일 이벤트들에 유효. %S와 유사.
  • -time integertime 필드. 정수. 특수값 current도 허용되며 이 값은 현재 이벤트 시간으로 대체돼요. KeyPress·KeyRelease·ButtonPress·ButtonRelease·Enter·Leave·Motion·Property 이벤트에 유효. %t에 해당.
  • -warp boolean — 화면 포인터도 함께 warp할지. KeyPress·KeyRelease·ButtonPress·ButtonRelease·Motion에 유효. Tk는 화면 root 창 기준, 그리고 Tk 창 기준 포인터 warp를 지원해요. 후자의 경우 창이 mapped일 때만 warp돼요.
  • -width sizewidth 필드. 화면 거리. Configure 이벤트에 유효. %w에 해당.
  • -when when — 이벤트 처리 시점. 다음 중 하나:
    • now — 명령이 반환되기 전에 즉시 처리. -when을 생략해도 같음.
    • tail — 이 애플리케이션에 이미 대기 중인 이벤트들 뒤로 Tcl 이벤트 큐에 넣음.
    • head — 이미 대기 중인 다른 이벤트보다 먼저 처리되도록 큐 앞에 넣음.
    • mark — 큐 앞에 넣되, -when mark로 이미 넣은 다른 이벤트 뒤로. 순서대로 처리해야 하지만 큐 앞에 놓아야 하는 이벤트 시리즈 생성에 유용.
  • -x coordx 필드. 화면 거리. KeyPress·KeyRelease·ButtonPress·ButtonRelease·Motion·Enter·Leave·Expose·Configure·Gravity·Reparent에 유효. %x에 해당. window가 비어 있으면 좌표는 화면 기준이고 %X에 해당.
  • -y coordy 필드. %y에 해당. window가 비어 있으면 화면 기준이고 %Y에 해당.

이벤트를 생성할 때 지정하지 않은 옵션은 0으로 채워져요. 단 serial은 다음 X 이벤트 시리얼 번호로 채워져요.

미리 정의된 가상 이벤트(PREDEFINED VIRTUAL EVENTS)

Tk는 알림 목적으로 다음 가상 이벤트를 정의해요.

  • <<AltUnderlined>>-underline 옵션으로 밑줄 그은(가속기 표시) 글자를 Alt 키와 함께 눌렀을 때 위젯에 전송. 보통 응답은 위젯(또는 관련 위젯)으로 포커스를 주거나 위젯을 호출하는 것.
  • <<Invoke>> — 일부 위젯(button, listbox, menu 등)에 <space>의 대안으로 보낼 수 있음.
  • <<ListboxSelect>> — listbox의 선택 항목 집합이 갱신될 때 listbox에 전송.
  • <<MenuSelect>> — 메뉴의 현재 선택 항목이 바뀔 때 메뉴에 전송. 상황별 도움말 시스템에 쓰기 위한 것.
  • <<Modified>> — text 위젯의 내용이 바뀔 때 그 위젯에 전송.
  • <<Selection>> — text 위젯의 선택이 바뀔 때 그 위젯에 전송.
  • <<ThemeChanged>> — ttt theme가 바뀔 때 모든 위젯에 전송. ttk 위젯은 이 이벤트를 듣고 다시 그리지만, 레거시 위젯은 무시.
  • <<TkWorldChanged>> — (예: [font configure]로) 폰트가 바뀔 때 모든 위젯에 전송. user_data 필드(%d)는 "FontChanged" 값. 그 외 시스템 전반 변경에도 보내지며 user_data가 원인을 나타내요. 모든 tk·ttk 위젯은 이미 내부적으로 이 이벤트를 처리함.
  • <<TraverseIn>> — 사용자 주도 "tab to widget" 동작으로 포커스가 위젯에 들어올 때 전송.
  • <<TraverseOut>> — 사용자 주도 tab 동작으로 포커스가 위젯에서 떠날 때 전송.
  • <<UndoStack>> — text 위젯의 undo·redo 스택이 비거나 차게 될 때 그 위젯에 전송.
  • <<WidgetViewSync>> — text 위젯의 내부 데이터가 낡았을 때, 그리고 다시 위젯 보기와 동기화될 때 전송. detail 필드(%d 치환)는 동기화되면 true, 아니면 false.

또한 Tk는 여러 플랫폼에서 바인딩을 통일하기 위해 다음 가상 이벤트를 정의해요. 사용자들은 다음과 같이 동작할 것으로 기대해요.

  • <<Clear>> — 현재 선택된 위젯 내용 삭제.
  • <<Copy>> — 현재 선택된 위젯 내용을 클립보드로 복사.
  • <<Cut>> — 현재 선택된 위젯 내용을 클립보드로 이동(잘라내기).
  • <<LineEnd>> — 선택 해제하며 현재 위젯의 줄 끝으로.
  • <<LineStart>> — 선택 해제하며 현재 위젯의 줄 시작으로.
  • <<NextChar>> — 선택 해제하며 다음 항목(보이는 문자)으로.
  • <<NextLine>> — 선택 해제하며 다음 줄로.
  • <<NextPara>> — 선택 해제하며 다음 문단으로.
  • <<NextWord>> — 선택 해제하며 다음 항목 묶음(보이는 단어)으로.
  • <<Paste>> — 현재 선택된 위젯 내용을 클립보드 내용으로 교체.
  • <<PasteSelection>> — 마우스 위치에 선택 내용 삽입(이 이벤트는 의미 있는 %x·%y 치환을 가짐).
  • <<PrevChar>> — 선택 해제하며 이전 항목으로.
  • <<PrevLine>> — 선택 해제하며 이전 줄로.
  • <<PrevPara>> — 선택 해제하며 이전 문단으로.
  • <<PrevWindow>> — 이전 창으로 순회.
  • <<PrevWord>> — 선택 해제하며 이전 항목 묶음으로.
  • <<Redo>> — 실행 취소한 동작 하나 다시 실행.
  • <<SelectAll>> — 선택 범위를 위젯 전체로.
  • <<SelectLineEnd>> — 선택 범위를 유지하며 줄 끝으로.
  • <<SelectLineStart>> — 선택 범위를 유지하며 줄 시작으로.
  • <<SelectNextChar>> — 선택 범위를 유지하며 다음 항목으로.
  • <<SelectNextLine>> — 선택 범위를 유지하며 다음 줄로.
  • <<SelectNextPara>> — 선택 범위를 유지하며 다음 문단으로.
  • <<SelectNextWord>> — 선택 범위를 유지하며 다음 항목 묶음으로.
  • <<SelectNone>> — 선택 범위를 비움.
  • <<SelectPrevChar>> — 선택 범위를 유지하며 이전 항목으로.
  • <<SelectPrevLine>> — 선택 범위를 유지하며 이전 줄로.
  • <<SelectPrevPara>> — 선택 범위를 유지하며 이전 문단으로.
  • <<SelectPrevWord>> — 선택 범위를 유지하며 이전 항목 묶음으로.
  • <<ToggleSelection>> — 선택 토글.
  • <<Undo>> — 마지막 동작 실행 취소.

예제

키를 가상 이벤트에 매핑하기

가상 이벤트 바인딩이 발동하려면 두 가지가 필요해요. 첫째, event add로 가상 이벤트를 정의하고, 둘째, bind 명령으로 그 가상 이벤트에 바인딩을 만들어야 해요.

event add <<Paste>> <Control-y>
event add <<Paste>> <Button-2>
event add <<Save>> <Control-X><Control-S>
event add <<Save>> <Shift-F12>
if {[tk windowingsystem] eq "aqua"} {
    event add <<Save>> <Command-s>
}

bind 명령에서 가상 이벤트는 다른 내장 이벤트 타입처럼 바인딩할 수 있어요.

bind Entry <<Paste>> {%W insert [selection get]}

겹꺾쇠(<<...>>)는 가상 이벤트를 바인딩한다는 뜻이에요. 사용자가 Control-y를 치거나 버튼 2를 누르거나, event generate<<Paste>> 가상 이벤트를 합성하면 <<Paste>> 바인딩이 호출돼요.

가상 바인딩이 별도의 물리 바인딩과 정확히 같은 시퀀스를 가지면, 물리 바인딩이 우선해요.

event add <<Paste>> <Control-y> <Meta-Control-y>
bind Entry <Control-y> {puts Control-y}
bind Entry <<Paste>> {puts Paste}

사용자가 Control-y를 치면 <Control-y> 바인딩이 호출돼요. 물리 이벤트가 다른 조건이 같을 때 가상 이벤트보다 더 구체적으로 간주되기 때문이에요. 하지만 사용자가 Meta-Control-y를 치면 <<Paste>> 바인딩이 호출돼요. 가상 바인딩과 연결된 물리 패턴의 Meta 수정자가 물리 이벤트의 <Control-y> 시퀀스보다 더 구체적이기 때문이에요.

가상 이벤트에 대한 바인딩은 그 가상 이벤트가 생기기 전에도 만들 수 있어요. 실제로는 특정 가상 이벤트가 무의미하거나 생성 불가능한 플랫폼에서는 그 이벤트가 정의될 필요조차 없어요. 실행 중에 가상 이벤트 정의가 바뀌면 모든 창이 즉시 새 정의에 반응해요.

bind Entry <Control-y> {}
event add <<Paste>> <Key-F6>

이렇게 하면 두 가지가 바뀌어요. 첫째, 가려져 있던 <<Paste>> 바인딩이 드러나서 Control-y가 더는 <Control-y> 바인딩을 호출하지 않고 대신 가상 이벤트 <<Paste>>를 호출해요. 둘째, F6 키를 눌러도 이제 <<Paste>> 바인딩을 호출해요.

마우스 포인터 이동시키기

마우스 포인터를 실제로 움직여야 할 때가 있어요. 예를 들어 사용자에게 프로그램 사용법을 직접 시연해 주는 소프트웨어가 있다면요. 그럴 땐 event generate로 마우스를 "warp"하면 돼요.

for {set xy 0} {$xy < 200} {incr xy} {
    event generate . <Motion> -x $xy -y $xy -warp 1
    update
    after 50
}

보통 사용자의 마우스 포인터를 움직이는 건 통제권을 빼앗기 때문에 좋지 않은 스타일로 여겨져요. 그러니 주의해서 써야 하고, 모든 플랫폼에서 동작한다는 보장도 없어요.

더 알아보기

bind 문서를 함께 보세요. 가상 이벤트 바인딩과 퍼센트 치환의 자세한 동작이 거기 설명돼 있어요.