PROGRAMMING WORKSHOP

.Net FrameWork,VB.Net | VBA_XL프로그래밍학습도구만들기

윈도우폼에서 실습용 엑셀문서 띄우기

앞페이지에서 대강의 윈도우폼과 컨트롤들을 적절히 배치하고 만들어 띄웠다
이제 버튼을 크릭하면 엑셀통합문서가 하나 생성되어 나타나게 하고
각각의 버튼에서 하고자 하는 일이 시트에 자동으로 일어나게 하고
그렇게 자동으로 구현된 내용의 VBA코드를 보여주도록 한다면
VBA학습을 위한 시각화도구가 될수 있을 것이다
버튼이 수도 없이 만들어져야 할 것이고
버튼에 대한 정보를 어딘가에 보관하여야 할 것이다
지난화일에는 간단하게 몇개를 만들기 위하여 코드상에 문자열로 표현했지만
버튼이 많이 만들어지게 하려면 버튼에 대한 정보를 어딘가에 보관시키고 사용하면 좋을 것이다
그래서 자원(Resources)폴더에 텍스트화일을 만들어서 보관하면 편리하다



지난 화일에서 버튼을 크릭하면 메시지박스가 뜨는 것으로 하였지만,
이번화일에서는 엑셀문서가 뜨면서..버튼의 캡션에 있는 내용을 실행하여
보여주도록 하게 된다
그리고 TabPage를 선택할때마다 해당 TabPage에 관련된 버튼을 만드는 것을
지난화일에서는 아래와 같이 하였으나

Private Sub myTabIndexChanged(iIndex As Integer)
	myFlowLayoutPanelRightHandSide.Controls.Clear()
	Dim sDatas As String = Nothing
	sCurrentTabPage = TOPIC_LIST.Split(",")(iIndex)

	Select Case sCurrentTabPage
		Case "Application"
			sDatas = "1)교차되는 범위 접근하기|Intersect," &
					  "2)떨어진 범위를 한꺼번에 접근하기|Union"


		Case "Range"
			sDatas = "1)범위의 주변범위를 모두 접근|CurrentRegion," &
					 "2)범위의 마지막셀에 접근|End," &
					 "3)범위에서 이동된 범위에 접근|Offset," &
					 "4)범위에서 확장된 범위에 접근|Resize," &
					 "5)범위에서 특정한 값이 있는 범위에 접근|SpecialCells"

		Case "Shape"
			sDatas = "1)도형만들기|AddShape," &
					 "2)양식콘트롤만들기|AddFormControl"
		Case "PivotTable"
			sDatas = "1)피벗케시만들기|Create," &
				   "2)피벗테이블만들기|CreatePivotTable"
	End Select
	createButton(sDatas)

End Sub

아래와 같이 간략하게 작성할수 있게 되겠지..

Sub myTabIndexChanged(iIndex As Integer)
	myFlowLayoutPanelRightHandSide.Controls.Clear()
	Dim sDatas As String
	sCurrentTabPage = TOPIC_LIST.Split(",")(iIndex)
	Select Case sCurrentTabPage
		Case "Application"
		''Resources에 포함된 자원을 접근하는 것은
		''My.Resources.|저장한 텍스트화일명| 으로 접근한다
			sDatas = My.Resources.Application
			createButton(sDatas)
		Case "Range"
			sDatas = My.Resources.Range
			createButton(sDatas)
		Case "Shape"
		Case "PivotTable"
			'' 엑셀의 개체명이 확장되면서,아래에 추가 되고
			'' Resource 의 텍스트화일도 추가 되면 되겠고
		Case "..."
		Case "..."
	End Select
End Sub

엑셀 통합문서의 창을 활짝열면, 윈도우폼이 가려질 것이고,
그렇다면 엑셀통합문서를 적절한 크기로 줄여서 폼상에 나타나게
하는 것이 바람직 할 것이다
윈도우폼의 통제하에 들어가는 엑셀통합문서를 만들어야 한다

그런데 window폼은 윈도우프로그램이고..
엑셀은 엑셀프로그램이다..
엑셀이 윈도우프로그램에 속해 있는 것이 아닌, 외부라이브러리를 불러서
사용하는 것이다
윈도우프로그램이 닫히여 열려있던 엑셀도 같이 닫혀 버렸으면 좋겠는데...
방법이 없을까??



엑셀통합문서를 띄우려면, 무엇이 당장 생각이 나는지는
다른 코너에서 많이 보고, 내공이 쌓인 분들은..댐박에
엑셀라이브러리를 참조시켜야지...당연히..이렇게 생각이 나야 한다
참조시키시고..
Form1.vb의 전역부분에 Imports시키시고..

Imports XL = Microsoft.Office.Interop.Excel

이때 XL 과 같은 식별자는 왜 사용하냐하면..
다양한 라이브러리를 참조하게 되면, 식별할수 있는 표시가 필요하다
예를 들어서 Window폼 자체에도 Application개체가 있고
Button개체가 있고, 엑셀에도 있을 것이다
그러니 그냥 윈도우폼에서 아무다른 라이브러리를 참조하지 않는다면
아래와 Button이라고 해도 알아 먹는다
다른 것과 헷갈릴것이 없으니...

Dim oBtn As Button=New Button()

이라고 할때, 엑셀을 참조시켰을때, Button이 도대체
윈도우의 버튼이냐...아니면 엑셀의 버튼이냐 편집기가 헷갈린다
윈도우에서 버튼개체를 얻으려면 이렇게 길게

System.Windows.Forms.Button

작성해야 한다
하지만
Imports WF=System.Windows.Forms 라고 임포트시켜놓으면
WF.Button 이라고 하면 간단히 어디에 속한 버튼인지 알수 있다
그래서 엑셀의 경우도 여러분이 좋아하는 식별자로 표시해두는 것이 좋다

Imports XX(여러분이 좋아하는 임의의 식별자)=Microsoft.Office.Interop.Excel

이라고 해주는 것이 편리하다

이렇게 하여...생성된 버튼을 크릭하면 우선 뭔가 보여주기 위하여
엑셀통합문서가 열려야 한다
윈도우폼 위에..
그런데 뭔가 윈도우폼과 연결고리를 만들어 주어야 윈도우폼이 닫히면
엑셀문서도 같이 닫히는 완전히 엑셀통합문서가 윈도우의 똘마니게
되게 연결을 시켜주는 것이 좋을 것이다
그러기 위하여 API함수를 하나 사용하자..
윈도우 모듈시트의 아무곳에나..아래와 같이 작성한다


''소위 말하는 윈도우핸들이라는 것이 있다
''윈도우시스템의 모든 창(window)는 이것에 접근하는 핸들이 있는 것..
''전역변수로 아래와 같이 통합문서의 윈도우핸들을 보관하는 변수를 선언하고
Public hwnd1 As IntPtr
''아래의 두개의 API함수를 내용없이 써주면 된다
''그런데 이것을 사용하려면 
''Imports System.Runtime.InteropServices 
''를 선언부에 임포트시켜주어야 한다, 그렇지 않으면 API함수를 이해못한다
''API함수는 COM 시스템에 있는 것이지, Net시스템에 있는 것이 아니기때문에
''COM시스템의 API함수를 사용하기 위하여 InteropServices를 임포트시키는 것..
<DllImport("user32.dll")>
''말그대로 SetParent 라는 함수는 통합문서의 부모를 윈도우로 해준다는 것이고..
Public Shared Function SetParent(hWndChild As IntPtr, hWndNewParent As IntPtr) As IntPtr
End Function
''통합문서의 위치와 크기를 잡아주는 것도 아래의 함수가 역할을 한다
<DllImport("user32.dll")>
Public Shared Function MoveWindow(hwnd As IntPtr, x As Integer, y As Integer, nWidth As Integer, nheight As Integer, repaint As Boolean) As Boolean
End Function

복잡해 보이시는지??
곰곰히 생각하면 아무것도 아니다..
그냥 외울것도 없고..위와 같이 작성해주는 약속을 지켜주면 된다

이제 맨위의 TabPage 선택에 따라서 버튼을 만들어주게 된다
버튼을 만드는 프로시져를..

Private Sub createButton(sDatas As String)
	''전달받은 버튼을 만들기 위한 문자열정보(자원정보에 보관된 Text화일을 읽어낸것)를
	''String개체의 Split 메소드로 배열화시켜서
	For Each sX As String In sDatas.Split(vbCrLf)
		'' 버튼을 하나씩 만든다
		'' 버튼의 갯수는 Text화일에 편하게 기록한 내용의 갯수만큼..
		Dim oBtn As System.Windows.Forms.Button = New System.Windows.Forms.Button
		oBtn.AutoSize = True
		oBtn.Text = sX.Split(")")(1).Trim
		'' 버튼에 마우스를 가져가면 손가락모양의 아이콘이 나오게 하고
		oBtn.Cursor = Cursors.Hand
		'' 버튼의 Tag 속성에 문자열 정보에서 일부를 떼어서 나중에 사용하기 위하여 보관하고
		oBtn.Tag = Val(sX.Split(")")(0))
		'' 버튼을 크릭하면 excelWork 라는 프로시져를 호출한다
		AddHandler oBtn.Click, AddressOf excelWork
		'' 콘테이너 콘트롤의 Controls집합체에 추가하고
		myFlowLayoutPanelRightHandSide.Controls.Add(oBtn)
	Next

End Sub
'' 버튼을 크릭하면 사용될 이벤트프로시져
Sub excelWork(sender As Button, e As EventArgs)
	'' xml화일의 존재여부에 따라서 에러가 날수도 있으니
	'' Try~Catch 문으로 에러브록을 구성하고
	Try
		''XML문에는 버튼을 크릭할때 작업내용을 VBA로 할때
		''사용될 VBA코드를 XML문서내에 작성한 것이다
		''아래에서 추가 설명한다

		''XElement를 집합체로 보관할 집합체 변수를 만들고
		Dim oXML As IEnumerable(Of XElement)
		''작업시간이 길수 있으므로 이 코드가 실행되는 동안은
		''커서가 웨이팅아이콘으로 유지하게 하는 것이 사용자가 덜 혼돈 스러울 것이다
		Cursor = Cursors.WaitCursor
		''////////////////////////////////////////////////////
		''통합문서를 생성하여 위치하기 위한 프로시져/////////////
		checkBookExist()
		''//////////////////////////////////////////////////
		''TabPage의 인덱스번호에 따라서 조건문 분기한다
		''조건에 따라서 XML화일이 다르고(xml화일이 아주 많이 사용되게 된다)
		Dim iIndex As Integer = CInt(sender.Tag)
		Select Case sCurrentTabPage
			Case "Range"
				Select Case iIndex
					Case 1
						''해당되는 XML 개체를 저장된 xml화일로 만들고
						''xml 화일이 저장된 위치는  bin/debug 폴더에 저장한다
						''이것이 세팅이 되면, 프로그램의 폴더가 되는 것이고..
						''이것을 프로그래밍적으로 Application.StartupPath 라고 경로표기 한다
						''만약 Imports 부분에서
						''Imports XL = Microsoft.Office.Interop.Excel
						''엑셀 식별자를 XL로 하여 놓지 않으면 
						''이 부분에서 에러가 난다..왜냐면 Application 개체가 Excel에 있는 것과
						'' Window에 있는 것이 어느것인지 헷갈리게 되는 것이고..
						'' 엑셀 Application 개체에는 StartupPath 라는 속성은 없고..
						oXML = XElement.Load(Application.StartupPath & "\useCurrentRegion.xml").Descendants("data")
					Case 2
				End Select
			Case "Application"
				Select Case iIndex
					Case 1
						oXML = XElement.Load(Application.StartupPath & "\useIntersect.xml").Descendants("data")
					Case 2
						oXML = XElement.Load(Application.StartupPath & "\useUnion.xml").Descendants("data")
				End Select
		End Select
	Catch ex As Exception
	Finally
		Cursor = Cursors.Arrow
	End Try
End Sub

'' 버튼이 크릭될때 최초 호출되는 프로시져 checkBookExist()는
'' 엑셀통합문서가 열려있는지, 아닌지 확인하고
'' 있으면 위치와 크기를 다시 확인 정리하고
'' 없으면 새로 만드는 프로시져
Sub checkBookExist()
	Try
		'' 엑셀이 없다면..
		If myXLApp Is Nothing Then
			'' 다를 프로시져를 호출하여 엑셀 통합문서를 생성하고 위치 잡는다
			setExcelBook(myXLLeft, myXLTop, myXLWidth, myXLHeight)
		Else
			''혹시 엑셀개체는 살아 있을지라도
			''사용자가 엑셀창을 작게 하였거나, 위치를 바꾸었거나 하였을때를
			''위하여 위치와 윈도우와 엑셀과의 부모관계를 안전하게 재설정하게 한다
			hwnd1 = CType(myXLApp.Hwnd, IntPtr)
			SetParent(hwnd1, Me.Handle)
			MoveWindow(hwnd1, myXLLeft, myXLTop, myXLWidth, myXLHeight, True)
		End If
	Catch ex As Exception
	End Try

End Sub
'' 엑셀이 최초 생성되도록 하는 프로시져
Sub setExcelBook(lLeft As Long, lTop As Long, lWidth As Long, lHeight As Long)
	'' 엑셀 Application을 생성하여 전역변수에 주고
	myXLApp = New XL.Application()
	'' Visible 속성을 True로 하지 않으면 눈에 보이지 않는다
	myXLApp.Visible = True
	'' 통합문서 하나 생성하여 붙이고
	myXLBook = myXLApp.Workbooks.Add
	''엑셀 Appliction의 속성 몇개 주고, 이것은 VBA에서 이미 알고 있는 내용들
	myXLApp.DisplayFullScreen = True
	myXLApp.ActiveWindow.DisplayHeadings = True
	'' 중요한 것은 윈도우핸들러를 엑셀 Application속성의 Hwnd 속성에서 얻어서 
	'' 전역변수에 주고
	hwnd1 = CType(myXLApp.Hwnd, IntPtr)
	'' API 함수인 SetParent를 엑셀핸들러와 윈도우핸들러를 부모자식관계를 유지하고
	SetParent(hwnd1, Me.Handle)
	'' 또 다른 API함수를 불러서 , 엑셀 핸들러와 위치와 크기를 준다
	MoveWindow(hwnd1, lLeft, lTop, lWidth, lHeight, True)
End Sub

그리고 엑셀문서를 닫았을때
엑셀이 닫히면서 전역변수를 죽여 주어야 다른 버튼의 작업이 계속 될수 있을 것이다
아래의 화일을 열어보시면 , 이해가 가실 것이다
그래서 엑셀 Application의 이벤트목록을 찾아서 아래와 같이 작성해주자



아래의 화일에서는 엑셀화일을 띄우는 것 까지만 했다
다음 페이지에서는 엑셀화일을 이용하여 VBA 학습을 위한 쌤플을 구현해보자


***[LOG-IN]***