피로곰's 모두의 프린터

모두의 프린터는 개인, 기업 상관 없이 누구나 무료로 사용가능한 프로그램입니다. 회원가입,카드결제등을 요구하지 않습니다! 광고를 통해 연결되는 사이트에서의 회원가입, 카드결제 피해를 보지 않도록 주의하세요!
반응형

Go언어로 윈도GUI .. 3번째 글입니다.
우선 관련 글들을 작성하면서 계속 사용하게될 소스파일 하나 먼저 던져 놓고 시작하겠습니다.

walk_wrap.go
0.01MB

walk_wrap.go 라고 매번 치기 귀찮으니 이놈, 저놈, 랩퍼 등등으로 쓸수 있으니 알아서 잘 보시기 바랍니다. 이놈에 대해 이래저래 쓰기 앞서서..
웹 프로그래밍을 할때도 그렇고 GUI 프로그래밍을 할때도 그렇고 콘솔 프로그램과 달리 프로그램을 만들다 보면 메시지 박스를 띄워서 뭔가를 하거나 확인 창을 띄워야 하거나 하는 경우가 있습니다.

이런 놈들이죠.. 앞에 놈은 자바스크립트의 alert 같은 놈이고 뒤엣놈은 confirm 같은 놈입니다. 이렇게 메시지 박스나 확인창을 생성해주는 두 함수에 대해서 먼저 설명을 하고 넘어 가겠습니다.

func MsgBox(msg string, window ...*walk.MainWindow) 
func Confirm(msg string, window ...*walk.MainWindow) bool

이렇게 2개 함수가 존재하는데 둘다 window ...*walk.MainWindow 라는 파라메터가 존재합니다만.. 이놈은 흔히 윈도 API에서 생성시키는 대화상자의 부모(Parent)를 지정해서 뜨는 창이 부모창에 종속되어 동작하게 할지 아니면 별개로 따로 떨어진 형태로 동작하게 할지를 원하는 바에 따라 쓰도록 한 부분입니다만.. 만약 대화상자를 띄우는 윈도 창이 존재하고 그 창의 Child로 대화상자를 붙이려면 두번째 인자에 walk 윈도 객체를 넘기면 되는 건데 .. 이게 개념이 좀 설명히 복잡하긴 합니다만.. 이게 뭔 소리인지는 나중에 심심하면 한번 따로 해보시구요.
... 로 가변인자로 지정된 놈이라 실 사용시엔 이 두 함수다 msg string 값만 전달해도 오류 없이 동작 합니다. 저도 실제로 그리 쓰고 있구요. 일단 지금은 msg string 인자만 입력해 쓴다 생각 합시다.

MsgBox("메시지 박스임 ㅋㅋㅋ") 
if Confirm("그래서 할껴 말껴? 할껴?") { 
	MsgBox("할껴") 
} else {
	MsgBox("말껴") 
}

이런식의 사용이 되겠네요. Confirm 함수는 리턴값이 true 이면 "예" 버튼을 false 이면 "아니오" 버튼을 클릭한 경우입니다.
자 그럼 이제 기본적으로 제가 만든 랩퍼를 어케 쓰는지 기본 창을 띄우는 방법을 설명하면서 진행해보도록 하겠습니다.

// 리사이즈 가능, 최대/최소 가능 
func NewWindowMgr(title string, width int, height int, icon *walk.Icon) (*WinResMgr, *walk.MainWindow) 
// 리사이즈 불가, 최대/최소 가능 
func NewWindowMgrNoResize(title string, width int, height int, icon *walk.Icon) (*WinResMgr, *walk.MainWindow) 
// 리사이즈 불가, 최대/최소 불가 
func NewWindowMgrNoResizeNoMinMax(title string, width int, height int, icon *walk.Icon) (*WinResMgr, *walk.MainWindow) 
// 광고 팝업용 
func NewWindowMgrAds(title string, width int, height int) (*WinResMgr, *walk.MainWindow)

창을 생성하는 함수는 총 4개가 존재합니다. NewWindowMgrAds 를 제외한 나머지 함수들은 형태는 기본적으로 비슷(?)합니다만. 리사이즈가 가능하다는 것은 전체 창의 크기를 자유롭게 변경이 가능하다는 것이구요.
최대/최소 가능은

이렇게 최소, 최대 버튼이 타이틀바에 존재 하느냐 안하느냐의 차이를 가져옵니다.
NewWindowMgrAds 함수는 모두의 프린터에서 팝업 광고를 위해서 만들어둔 함수인데요.

이렇게 좌측 하단 시계 위에 뜨는 타이틀바 없는 창을 띄워줍니다. 이미지나 기타 요소를 배치하면 위와 같은 팝업 윈도를 얻을수 있겠지...만;;; 보통 많이 쓰는 기능은 아니니까요.
이 함수들의 리턴값은 동일하게 *WinResMgr, *walk.MainWindow 로 같습니다. 실제로 WinResMgr 구조체 내부의 각종 메소드들이 *walk.MainWindow 구조체에 이런저런 짓을 해주는 랩퍼이긴 한데 랩퍼 외부에서도 walk.MainWindow를 다뤄야 하는 경우도 많이 있기 때문에 두개를 전부 리턴합니다.

/** * WinResMgr **/ 
type WinResMgr struct {
	window *walk.MainWindow 
    parentList *list.List
}

물론 WinResMgr 구조체 내의 window *walk.MainWindow 이놈과 윈도 생성 함수들에서 리턴되는 *walk.MainWindow 객체는 같은 놈입니다.
창 생성은 보통

// 창 생성 
mgr, window := NewWindowMgr("테스트 윈도/리사이즈/최대최소 가능", 1024, 768, GetIcon())

// mgr, window 를 통해 gui 관련 객체 생성 
// 관런처리 

// 창 표시 ( 이 함수에서 Block 됩니다. ) 
mgr.StartForeground()

위와 같이 NewWindowMgr..... 류 함수들로 랩퍼와 walk 의 MainWindow 객체를 만든후에 실체 창이 표시되고 윈도 관련 프로시저들이 동작하는 mgr.StartForeground() 같은 함수 사이에 UI에 필요한 요소들의 배치코드들을 작성합니다. 윈도 창과 관련된 프로시저가 시작되는 함수들의 종류는 다음과 같습니다.

func (m *WinResMgr) StartWindow() 
func (m *WinResMgr) StartForeground()
func (m *WinResMgr) HideStart()

보통 제가 가장 많이 쓰는건 StartForeground()입니다. 창을 띄우면서 최 당단으로 보내라는거구요 StartWindow()의 경우 창의 위치는 조정하지 않기 때문에 경우에 따라선 실행되고 창이 뜨는 사이에 다른 프로그램이 상단으로 포커싱 될 경우 그 프로그램의 뒤에 창이 뜰수 있습니다. 어찌됫건 창이 표시되는 순간엔 최상단에서 보여지길 바라는 경우가 많기 때문에 저는 StartForground() 함수를 보통 사용합니다.
HideStart()의 경우 윈도 프로시저를 시작은 하는데 창은 일단 감춘 상태로 실행되는 겁니다. 필요에 따라 ShowForeground() 같은 함수를 써서 창을 보여주도록 해야지 창을 볼수 있습니다. 결론은 .. 그냥 닥치고 StartForeground() ㅋㅋ 이 StartWindow 나 StartForeground 함수는 창이 닫혀서 프로시저가 종료되기 전까진 리턴되지 않고 Block됩니다. 한마디로 이놈들 호출 전에 창과 관련된 초기 설정등은 모두 끝내야 한다는 말입니다.
NewWindowMgr.. 시리즈 함수들의 파라메터에 대해서 설명을 잠시 하자면 NewWindowMgrAds 함수를 제외하고는 전부

title string, width int, height int, icon *walk.Icon 
// 타이틀, 창 넓이, 창 높이, 아이콘


이렇게 4개의 파라메터를 공통적으로 사용합니다. 말그대로 윈도 타이틀은

이런 창의 타이틀 메시지를 뜻하구요. width, height 는 창의 크기를 정하는 겁니다. 마지막으로 icon의 경우에


/**
*	LoadIcon
**/
func LoadIcon(icoBuf []byte, icoName string) {
	icoFile := filepath.Join(os.TempDir(), icoName)

	var err error

	if _, err = os.Stat(icoName); os.IsNotExist(err) {
		if err = ioutil.WriteFile(icoFile, icoBuf, 0644); err != nil {
			return
		}
	}

	iconMutex.Lock()
	gIcon, _ = walk.NewIconFromFile(icoFile)
	iconMutex.Unlock()
}

/**
*	LoadIconFromFile
**/
func LoadIconFromFile(icoPath string) {
	iconMutex.Lock()
	defer iconMutex.Unlock()
	gIcon, _ = walk.NewIconFromFile(icoPath)
}

walk_wrap.go 파일 내의 이 두함수 참고해서 직접 매번 ico 파일을 읽어다가 *walk.Icon 객체를 전달 하셔도 됩니다만 제 경우에는 전역으로 하나 읽어둔 아이콘을 이곳저곳 사용하도록 GetIcon과 gIcon 이라는 전역변수를 지정해서 쓰고 있습니다.
그리하야 go embed를 이용해서 바이너리에 아이콘 파일 자체의 바이너리를 저장해두신 경우에는 LoadIcon 함수로 윈도임시폴더에 아이콘을 저장해놓고 그놈을 읽어다 쓰실수도 있고 아에 특정 위치의 ico 파일을 읽어 들이고 싶으신거면 LoadIconFromFile 함수에 ico 파일 경로를 지정하셔도 되겠습니다.
main 함수의 시작부에 LoadIcon 이나 LoadIconFromFile로 아이콘을 로딩해 두었으면 창 생성을 할때는 GetIcon함수를 사용하면 아이콘 적용이 되어 창이 뜨게 됩니다.
그래서 이전글에서 잠깐 나온 코드긴 한데 ..

	mgr, _ := NewWindowMgr("기본 레이아웃", 1025, 768, GetIcon())

	// HSplitter
	mgr.HSplit()
	mgr.Label("HSplitter")
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.PushButton("버튼4", func() {})
	mgr.EndSplit() // HSplit, VSplit 사용후엔 EndSplit!!

	// VSplitter
	mgr.VSplit()
	mgr.Label("VSplitter")
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.PushButton("버튼4", func() {})
	mgr.EndSplit()

	// HSplitter + VSplitter
	mgr.HSplit()

	mgr.VSplit()
	mgr.Label("HSplit 안에 VSplit")
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.EndSplit() // End of VSplit

	mgr.HSplit()
	mgr.Label("HSplit 안에 HSplit")
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.EndSplit()

	mgr.VSplit()
	mgr.Label("HSplit 안에 VSplit2")
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.CheckBox("체크1", false, func() {})
	mgr.EndSplit()

	mgr.EndSplit() // End of HSplit

	// 그룹박스 Vertical
	mgr.GroupBox("그룹박스(V)", true)
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.CheckBox("체크1", false, func() {})
	mgr.EndGroupBox()

	// 그룹박스 Horizen..
	mgr.GroupBox("그룹박스(H)", false)
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.CheckBox("체크1", false, func() {})
	mgr.EndGroupBox()

	mgr.HSplit()

	mgr.GroupBox("HSplit으로 분리한 그룹박스1", true)
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.CheckBox("체크1", false, func() {})
	mgr.EndGroupBox()

	mgr.GroupBox("HSplit으로 분리한 그룹박스2", true)
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.CheckBox("체크1", false, func() {})
	mgr.EndGroupBox()

	mgr.EndSplit()

	mgr.StartForeground()

이런 경우에

 mgr, _ := NewWindowMgr("기본 레이아웃", 1025, 768, GetIcon())

 mgr.StartForeground()

사이에

	// HSplitter
	mgr.HSplit()
	mgr.Label("HSplitter")
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.PushButton("버튼4", func() {})
	mgr.EndSplit() // HSplit, VSplit 사용후엔 EndSplit!!
    
    .....
    .....
    .....
    
    mgr.GroupBox("HSplit으로 분리한 그룹박스2", true)
	mgr.PushButton("버튼1", func() {})
	mgr.PushButton("버튼2", func() {})
	mgr.PushButton("버튼3", func() {})
	mgr.CheckBox("체크1", false, func() {})
	mgr.EndGroupBox()

	mgr.EndSplit()

이렇게 버튼이니 라벨이니 이러한 것들이 주루륵 들어가게 된것입니다.
일단 창을 띄우는 것은 여기까지 하구요. 다음 글에서 하나씩 더 진행해 보도록 하겠습니다.

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band