VBA에서 사용되는 Userform은 기본적으로 사이즈조절(Resizable)이 되지 않습니다.

이 설정을 변경하는 옵션 또한 지원되지 않습니다.

하지만 기본적으로 Userform은 Windows Form 2.0 을 사용하는 일반 윈도우폼입니다.

 

그동안은 사용할 없었는데, 요번에 사용자에 의해서 크기가 조절되어야 하는 유저폼이 필요한 일이 생겨 찾게 되었습니다.

방법은 그다지 어렵지 않더군요..

  

1. 빈 유저폼을 만듭니다.

 

2. 프로젝트 탐색기에서 우클릭 한 후 [삽입] – [클래스 모듈] 을 선택해 클래스모듈을 하나 만들어 줍니다.


3. 속성에서 방금 생성된 클래스모듈의 이름을 CResizer 로 변경합니다.
    (속성창이 안 보이면 F4)


4. CResizer 클래스모듈에 아래의 코드를 복사해 붙여 넣습니다.

Option Explicit

Private Const MFrameResizer = "FrameResizeGrab"
Private Const MResizer = "ResizeGrab"
Private WithEvents m_objResizer As MSForms.Frame
Private m_sngLeftResizePos As Single
Private m_sngTopResizePos As Single
Private m_blnResizing As Single
Private WithEvents m_frmParent As MSForms.UserForm
Private m_objParent As Object

Private Sub Class_Terminate()

    m_objParent.Controls.Remove MResizer
    
End Sub


Private Sub m_frmParent_Layout()
    
    If Not m_blnResizing Then
        With m_objResizer
            .Top = m_objParent.InsideHeight - .Height
            .Left = m_objParent.InsideWidth - .Width
        End With
    End If

End Sub


Private Sub m_objResizer_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)

    If Button = 1 Then
        m_sngLeftResizePos = x
        m_sngTopResizePos = y
        m_blnResizing = True
    End If
    
End Sub
Private Sub m_objResizer_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)

    If Button = 1 Then
        With m_objResizer
            .Move .Left + x - m_sngLeftResizePos, .Top + y - m_sngTopResizePos
            m_objParent.Width = m_objParent.Width + x - m_sngLeftResizePos
            m_objParent.Height = m_objParent.Height + y - m_sngTopResizePos
            .Left = m_objParent.InsideWidth - .Width
            .Top = m_objParent.InsideHeight - .Height
        End With
    End If
    
End Sub
Private Sub m_objResizer_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
    If Button = 1 Then
        m_blnResizing = False
    End If

End Sub


Public Function Add(Parent As Object) As MSForms.Frame
'
' add resizing control to bottom righthand corner of userform
'
    Dim labTemp As MSForms.Label
    
    Set m_frmParent = Parent
    Set m_objParent = Parent
    
    Set m_objResizer = m_objParent.Controls.Add("Forms.Frame.1", MFrameResizer, True)
    Set labTemp = m_objResizer.Add("Forms.label.1", MResizer, True)
    With labTemp
        With .Font
            .Name = "Marlett"
            .Charset = 2
            .Size = 14
            .Bold = True
        End With
        .BackStyle = fmBackStyleTransparent
        .AutoSize = True
        .BorderStyle = fmBorderStyleNone
        .Caption = "o"
        .MousePointer = fmMousePointerSizeNWSE
        .ForeColor = RGB(100, 100, 100)
        .ZOrder
        .Top = 1
        .Left = 1
        .Enabled = False
    End With
    
    With m_objResizer
        .MousePointer = fmMousePointerSizeNWSE
        .BorderStyle = fmBorderStyleNone
        .SpecialEffect = fmSpecialEffectFlat
        .ZOrder
        .Caption = ""
        .Width = labTemp.Width + 1
        .Height = labTemp.Height + 1
        .Top = m_objParent.InsideHeight - .Height
        .Left = m_objParent.InsideWidth - .Width
    End With
End Function


5. 만들어 두었던 유저폼(Userform1) 에 아래의 코드를 붙여 넣습니다.

Private m_clsResizer As CResizer


Private Sub UserForm_Initialize()
    
    Set m_clsResizer = New CResizer
    m_clsResizer.Add Me

End Sub


Private Sub UserForm_Terminate()
    Set m_clsResizer = Nothing
End Sub


6. Userform1.show 로 유저폼을 띄워보면 우측하단에 Resize 조절이 가능한 표시가 보이게 됩니다.

   

 

이렇게 크기 조절이 가능한 유저폼(Resizable Userform)을 만들었습니다.

물론 이렇게까지만 해놓고 쓸일은 없겠죠. 크기조절을 시킬 때 무언가를 해 주어야 합니다.

그냥 크기만 늘었다 줄었다 하면 아무 소용이 없죠…

폼 안에 있는 컨트롤을 동적으로 움직여줘야 할 수도 있고, 여튼 폼 크기 조절을 사용자에게 시키려던 목적이 있을겁니다.

그리고 그 액션에 대응하는 코드도 작성을 해 주어야 합니다.

이 코드는 Userform1_Resize() 이벤트 프로시저 안에 작성해 주면 됩니다.

 

1. Userform1에 빈 레이블(Label)을 아무곳에나 하나 추가합니다. 
   그리고 유저폼 초기화 프로시저를 아래처럼 수정합니다.

Private Sub UserForm_Initialize()
    
    Set m_clsResizer = New CResizer
    m_clsResizer.Add Me
    
    With Label1
        .Visible = False
        .Top = Me.Height - .Height - 30
        .Left = Me.Width - .Width - 20
    End With
End Sub


2. Userform 코드에 아래와 같이 Resize 이벤트 프로시저를 추가해 줍니다.

Private Sub UserForm_Resize()
    With Label1
        .Caption = "W : " & Me.Width & "  H : " & Me.Height
        .Top = Me.Height - .Height - 30
        .Left = Me.Width - .Width - 20
    End With
End Sub


3. CResizer 클래스 모듈에서 MouseDown과 MouseUp 프로시저를 찾아 아래처럼 수정해 줍니다. 이건 기능보다는 약간의 재미를 위해서입니다.

Private Sub m_objResizer_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)

    If Button = 1 Then
        m_sngLeftResizePos = x
        m_sngTopResizePos = y
        m_blnResizing = True

        '***** Label Visible
        m_objParent.Label1.Visible = True
    End If
    
End Sub


Private Sub m_objResizer_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
    If Button = 1 Then
        m_blnResizing = False
        
        '***** Label Invisible
        m_objParent.Label1.Visible = False
    End If

End Sub


4. 유저폼을 실행한 후 크기조절을 해 보면 아래처럼 현재 창의 크기가 레이블에 표시되고, 창의 크기와 관계없이 우측 하단 고정된 위치에 따라붙게 됩니다.

 

그다지 많이 쓰일일은 없지만, 활용 여하에 따라 보다 나은 Activity한 UX를 설계할 수 있지 않을까 생각됩니다.



별 것 아닌 상황이지만 의외로 번거롭다..

 

 

 

엑셀에 Add-in하는 방식으로 VBA개발을 자주 하게 되다보니 오피스 버전별 호환성을 테스트하기 위해 기본적으로 오피스 2007, 2010을 동시에 설치해두고 사용한다.

 

오피스 2010을 설치한 후에는 2007을 추가로 설치할 방법이 없으므로,

Windows설치 후 오피스2007을 先설치, 그리고 오피스2010을 後설치 (업그레이드 아닌 독립실행형으로) 하게 된다.

 

이런 환경에서는 기본적으로 오피스 문서(pptx나 xlsx, docx)를 더블클릭 했을 떄 오피스2010으로 실행이 된다.

 

헌데 더블클릭시 기본으로 열리는 프로그램을 바꾸고 싶으면 어떻게 해야 할까?

 

윈7의 기본프로그램 설정에서는 office14폴더의 엑셀(2010버전) / office12폴더의 엑셀(2007버전) 아이콘이 각기 다르게 취급되지만, 둘 중 뭘 선택해도 기본으로 열리는 프로그램은 변경되지 않는다.

 

 

물론 엑셀2007 프로그램 아이콘을 직접 실행해서 2007버전의 엑셀을 띄워둔 채로 파일을 더블클릭 하면 2007에서 열린다.

 

하지만 매 번 실행할 때마다 빈 문서(Book1) 이 실행된 채로 열리게 되서 작업표시줄 한켠을 차지하게 되고.. 무엇보다도… 매우 귀찮다.

 

 

이번에 비교적 간단한 방법으로 기본으로 열리는 프로그램을 변경하는 방법을 알게 되어 기록해 둔다.

 

 

[제어판]-[프로그램 추가/제거] – 기본으로 실행할 Office 버전 선택 – [변경] – [복구]

 

 

 

 

 

 

이와 같이 해 주면 약 3~4분정도 오피스 구성을 다시 하게 되고 기본 프로그램이 변경되어 더블클릭시 원하는 버전으로 실행되게 된다.

 

레지스트리를 변경해서 간단히 처리 하는 방법도 있을것으로 예상되지만… 그것까지 뒤져보는 시간이면 이걸 한번 해주는게 간단하므로.. Pass.

   

Microsoft SQL Server 2012 Express 에는 새롭게 LocalDB 제품이 포함되었다. 

 

 Express 제품군의 새로운 버전인 LocalDB는 모든 프로그래밍 기능을 포함하지만 사용자 모드에서 실행되며 구성이 필요 없는 빠른 설치가 가능하고 필수 구성 요소가 적은 새로운 경량 버전의 Express입니다. 이 제품을 사용하면 코드를 통해 쉽게 데이터베이스를 만들고 데이터베이스에 대한 작업을 수행할 수 있습니다. 이 제품은 Visual Studio와 같은 데이터베이스 개발 도구 및 응용 프로그램과 함께 제공되거나 로컬 데이터베이스가 필요한 응용 프로그램에 포함될 수 있습니다.

 

로컬에서만 사용할 응용 프로그램에 DB가 필요할 때,
Access나 Excel 파일을 DB 대용으로 사용하기엔 좀 버거울 경우 사용하면 좋을 것 같다.

 

LocalDB는 무료이면서도 복잡한 설치과정이 없고 기존 MS-SQL Server의 MDF와의 호환성도 완벽하게 보장되는 로컬 데이터베이스이다.

 

가장 좋은 점은 뭐니뭐니 해도 기존의 MS-SQL 파일 포맷인 MDF와 LDF를 100% 그대로 가져다 쓸수 있다는 점일 것이다.

 

   

또한 Express Edition보다 설치과정이 쉽고 간단하다.

약 33MB(64bit 기준) 정도의 SqlLocalDB.msi 파일과 5MB(64bit) 정도의 SQL Native Client 만 설치하면 일반 PC에서도 강력한 MS-SQL Server의 기능을 마음껏 활용할 수 있게 된다.

단, LocalDB 자체가 윈도우XP는 지원하지 않기 때문에... Vista 이상의 운영체제를 사용해야 한다.

 

설치파일은 MS 공식 다운로드 사이트에서 받을 수 있다.
http://www.microsoft.com/ko-kr/download/details.aspx?id=29062

   

 

설치 자체는 매우 간단하다. 다운로드 받은 파일을 실행하면 별다른 과정 없이 설치가 완료된다.

 

기존의 서버류 제품들과는 다르게, LocalDB는 작업 인스턴스가 실행되지도 않고, 서비스 등록을 하지도 않는다.

응용 프로그램에서 LocalDB 커넥션 스트링을 호출하는 순간에 sqlservr.exe 프로세스가 자동으로 실행되면서 DB와 연결이 된다.

이로 인해 PC를 켜고나서 최초로 LocalDB 응용 프로그램을 실행할 때에는 약간의 지연시간이 발생하게 된다.

 

MSDN에도 이 부분에 대해 노트를 해 두었다.

 

참고

컴퓨터에서 사용자가 처음으로 LocalDB에 연결하려고 시도하는 경우 자동 인스턴스가 생성되고 시작되어야 합니다. 인스턴스를 만드는 추가 시간으로 인해 시간 초과 메시지와 함께 연결 시도가 실패할 수 있습니다. 이 경우 만들기 프로세스가 완료되도록 몇 초 정도 기다린 후에 다시 연결하십시오.

 

 

뭐 실행해본 결과 체감시간에 그다지 큰 변화는 없었다. Sqlservr.exe 파일은 C:\Program Files\Microsoft SQL Server\110\LocalDB\Binn 에 설치가 되어 있으며, 크기가 187KB로 상당히 컴팩트하다.

 

최초 실행시 인스턴트 생성으로 인한 로스나 패널티는… 무시해도 되는 수준이라고 생각된다.

 

 

 

이제 이 상태에서는 Visual Stutio를 통한 .NET 개발을 진행할 수 있다.

(다만, .net Framework가 SqlClient 제공자를 알고 있어야 커넥션이 가능하기 때문에 닷넷프레임워크 4.0을 업데이트 해줘야 한다.)

Microsoft .NET Framework 4용 업데이트 4.0.2 - 런타임 업데이트(KB2544514)

 

 

VS에서 localdb를 커넥션하고 이용하는 방법은 마이크로소프트 2012년 1월호에 자세히 나와 있어서 생략한다.(이곳에서 확인 가능)

 

 

여기서는 일반적인 ADO를 이용해 접속하는 방법에 대해 알아보겠다.

 

VS에서 닷넷 프레임워크를 4.0.2로 업데이트 한 것과 마찬가지로, 일반 ADO에서 커넥션을 하려면 공급자를 설치해 줘야 한다.

localDB는 [sqlncli11] 공급자가 필요한데, SQL Server Native Client 패키지 안에 포함된 sqlncli.msi 를 설치하면 된다.

아래 링크에서 아래쪽으로 쭉 내려가다 보면 Native Client가 보인다.

 

http://www.microsoft.com/ko-kr/download/details.aspx?id=29065

 

Microsoft® SQL Server® 2012 Native Client

Microsoft SQL Server Native Client(SQL Server Native Client)는 SQL OLE DB Provider와 SQL ODBC 드라이버를 포함하는 단일 DLL(동적 연결 라이브러리)입니다. 여기에는 네이티브 코드 API(ODBC, OLE DB 및 ADO)를 사용하여 Microsoft SQL Server 2005, 2008, 2008 R2 및 SQL Server 2012에 연결하는 응용 프로그램에 대한 런타임 지원이 포함되어 있습니다. SQL Server Native Client는 새로운 SQL Server 2012 기능 활용에 필요한 새로운 응용 프로그램을 작성하거나 기존 응용 프로그램을 향상시키는 데 사용됩니다. SQL Server Native Client에 대한 이러한 재배포 가능 설치 관리자는 새로운 SQL Server 코드 이름 'Denali' 기능을 활용하기 위해 런타임에 필요한 클라이언트 구성 요소를 설치하고, SQL Server Native Client API를 사용하는 응용 프로그램을 개발하는 데 필요한 헤더 파일을 설치하기도 합니다.

X86 패키지(sqlncli.msi)
X64 패키지(sqlncli.msi)

 

X86 패키지는 약 3.2MB, x64 패키지는 5.1MB이다. SqlLocalDB.msi와 마찬가지로 설치는… 별다른 설정 없이 그냥 하면 된다. SSMS만 깔려고 해도 이것저것 서비스팩이며 뭐며 귀찮은데.. Express버전도 설치할라치면 온갖 설정과 계정 등 복잡한 요소가 많은데.. 정말 단순해서 좋다.

 

고객들에게 프로그램을 제공해 줄 때 이것저것 잔뜩 설치하라고 하지 않아도 되니까..

 

여기까지 설치가 되었으면 이제 ADO 커넥션을 위한 모든 준비가 완료되었다.

 

 

 

VB등의 프로그램에서 기존에 OLEDB로 SQL Server에 연결하던 대로 참조를 걸어준다. – (Microsoft ActiveX Data Object 2.6 Library – 2.8도 있고 6.0도 있지만 난 그냥 그나마 오류가 덜 나고 하위호환이 잘 되는 것 같은 2.6을 사용한다.. 사실 별 차이를 모르겠음 -..-)

 

 

 

커넥션 스트링을 만들고 접속해본다.

Dim mydb As ADODB.Connection

Dim myrs As ADODB.Recordset

 

mydsn = ""

mydsn = mydsn & "Provider=SQLNCLI11;Data Source=(localdb)\v11.0;Integrated Security=SSPI;"

 

Set mydb = New ADODB.Connection

mydb.Open (mydsn)

 

mydb.state 를 찍어봐서 1이 나오면 정상적으로 연결된 것이다.

 

나의 경우, Sql Server에서 돌리고 있던 MDF와 LDF를 그대로 내 하드디스크로 복사해 왔다.

기존에 SQL SERVER를 커넥트 하던 솔루션에서 커넥션 스트링만 아래처럼 변경해 준 후 돌려보니 에러메시지 한줄 없이 그대로 LocalDB를 이용해 정상 실행 되었다.

 

mydsn = ""

mydsn = mydsn & "Provider=SQLNCLI11;Data Source=(localdb)\v11.0;Integrated Security=SSPI;"

mydsn = mydsn & "AttachDbFileName=C:\LocalDBFile\ LEEJUNGNAM_ACA.mdf"

 

정말 완벽하게 100% 호환이 되는것일까? 해서 SSMS로 접속을 해 보았다.

MSDN에서 본 대루.. 서버이름은 (localdb)\v11.0

저 서버이름의 v11.0은 명명된 네임스페이스에 속하는 특수한 인스턴스 이름 패턴으로, 꼭 저대로 사용해야만 한다고 한다.

어쨌든… 접속.

 

안된다. SSMS 2008에서는 (localdb)\v11.0 이라는 서버의 존재를 인식하지 못한다.

 

해서 SSMS 2012버전을 설치했다. LocalDB 를 다운로드 했던 페이지에 같이 있다.

헌데.. SSMS 2008은 200메가 남짓이더니 2012는 600메가가 넘는 파일을 다운로드 하네.. 괜히 싫다..

 

아무튼 설치를 마치고 다시 접속을 시도.

 

연결이 잘 된다. 개체탐색기를 보니 아래와 같이 한번 커넥션 했던 개체들에 대해서 자동으로 데이터베이스 등록이 되어있다.

저장 프로시저와 함수, 테이블구조, 데이터, id 시드값, 심지어 다이어그램까지 모든 환경이 100% 그대로 호환된다.

 

헌데, SQL Server를 이용해서 잘 만들어져 있는 프로그램을 왜 굳이 로컬DB로 바꾸는 것일까?

고객은 자신의 데이터가 자기의 PC가 아닌 다른 곳에 존재하고, 누군가가 그걸 들여다볼 수 있다는 사실에 꺼림칙해 한다.

그렇다고, 매 고객마다 비싼 MS-SQL Server를 호스팅받아서 마련하라고 할 수도 없는 노릇이다.

고객이 MS-SQL Server를 마련한다고 해도, 어차피 프로그램이 구동되려면 커넥션스트링에 DB 아이디와 비번이 들어가야 하므로 개발/공급한 사람은 언제든지 DB에 접근할 수 있다.

또 개발해준 입장에서는 DB 스키마나 프로시저 등 고유한 솔루션의 소스코드까지 몽땅 권리를 넘겨준 것이 아니므로,, 고객의 SQL Server에 이 프로그램을 구축해주기가 찜찜한건 마찬가지다.

또 이상하게도, 자기PC에 파일을 보관하는 것보다 IDC에 존재하는 SQL Server의 데이터 안정성이 훨씬더 높은데도 불구하고, 그 호스팅하는 업체가 망하거나(-_-), DB가 따운되면 프로그램을 이용하지 못하는게 아니냐는 걱정을 한다.. 거 참..-_-;;

그래서.. LocalDB를 이용하면 위와 같은 문제에 대해서 고객과 개발사 양쪽이 어느정도 합의가능한 도출점을 찾기가 쉬워진다.

고객은 자신이 원하는 바 대로 로컬PC에 자신들의 데이터를 보존할 수 있고,(물론 안전성 측면에서는 몇배 취약하지만..)

외부의 다른 접근이 불가능 하므로 보안을 유지할 수도 있다.

개발사의 입장에서는 개발이 용이한 SQL 솔루션을 그대로 유지하고 별다른 마이그레이션이나 작업을 할 필요가 없다.

물론 고객이 SSMS를 설치해서 DB구조나 소스를 볼 수는 있다. localDB는 기본적으로 윈도우 사용자인증이기 때문에..

하지만 권리를 넘겨 준 만큼 책임에서도 자유로워 질 수 있다.

고객이 DB를 접근하고, 제어하는 순간 그 뒤 발생하는 모든 오류나 문제에 대해서 책임지지 않아도 된다.

데이터 보관 및 백업에 대한 책임도 없다.

 

매 번 localDB를 사용할 일은 없겠지만,

필요에 따라 사용하기엔 더할나위없이 좋은 기능이라 생각된다.

 

VBA에서 스캐너 Twain 드라이버와 이미지프로세싱툴인 LEAD Tools를 이용해 스캐너를 제어하고 이미지를 만들어 저장한다.



* ltocx13n.ocx 파일을 System32 디렉토리에 카피.
* [도구] -> [참조] 에서 LEAT Main Control 등록.


Sett 이름의 시트의 A와 B열에 이런모양으로다가 환경설정값을 저장해놓고 불러다 쓰도록 한다. 환경설정폼은 알아서..

DeviceName KODAK Scanner: i1120
Resolution 200 Dpi
Color 흑백
Page 단면
Paper A4 크기


VBE에서 간단하게 유저폼 생성해서 버튼2개와 LEAD Main Control 추가.
[도구] -> [추가 컨트롤] 에서 LEAD Main Control (13.0) 체크한다음 폼에다 올려준다.



Dim TwainF_W, TwainF_H
Dim TwainPixT, TwainBit

'******************************************************************************
' Direct Scan Button Click
'******************************************************************************
Sub cmdScanST_Click()


Hwnd = FindWindow(vbNullString, Me.Caption)
LEAD1.AutoSetRects = True
LEAD1.AutoRepaint = False

LEAD1.EnableTwainEvent = True

'//////// 설정 저장값에 따라 상수값 변환
Select Case Replace(Sett.Range("B5"), " 크기", "")
    Case "기본값": TwainF_W = 11952: TwainF_H = 16848
    Case "A3": TwainF_W = 16838: TwainF_H = 23811   '11.7 x 16.5 inches
    Case "A4": TwainF_W = 11952: TwainF_H = 16848   '8.3 x 11.7 inches
    Case "A5": TwainF_W = 8352: TwainF_H = 11952    '5.83 x 8.3 inches
    Case "B4": TwainF_W = 14570: TwainF_H = 20636   '10.1 x 14.3 inches
    Case "B5": TwainF_W = 10368: TwainF_H = 14544   '7.2 x 10.1 inches
End Select

Select Case Sett.Range("B3")
    Case "흑백": TwainPixT = TWAIN_PIX_HALF: TwainBit = 1
    Case "8 bit 회색조": TwainPixT = TWAIN_PIX_GRAY: TwainBit = 8
    Case "24 bit 컬러": TwainPixT = TWAIN_PIX_RGB: TwainBit = 24
End Select

With LEAD1
    .TwainSourceName = Sett.Range("B1").Value
    .TwainMaxPages = -1               'Default
    .TwainAppAuthor = ""              'Default
    .TwainAppFamily = ""              'Default
    .TwainFrameLeft = -1              'Default
    .TwainFrameTop = -1               'Default
    .TwainFrameWidth = TwainF_W
    .TwainFrameHeight = TwainF_H
    .TwainBits = TwainBit
    .TwainPixelType = TwainPixT
    .TwainRes = Val(Sett.Range("B2").Value)
    .TwainContrast = TWAIN_DEFAULT_CONTRAST          'Default contrast
    .TwainIntensity = TWAIN_DEFAULT_INTENSITY        'Default intensity
    .EnableTwainFeeder = True
    .EnableTwainAutoFeed = True
    .EnableTwainDuplex = 0
End With

    SavedSetting = LEAD1.EnableMethodErrors
   
    Me.MousePointer = 11 'Set the pointer to an hourglass
    LEAD1.TwainRealize (Hwnd)
    Me.MousePointer = 0 'Set the mouse pointer back to the default
       
   
    LEAD1.TwainFlags = 0
    LEAD1.EnableMethodErrors = False
    nRet = LEAD1.TwainAcquire(Hwnd)
    If nRet <> SUCCESS Then
        MsgBox "TWAIN 장치가 준비되지 않았습니다."
        LEAD1.EnableMethodErrors = SavedSetting
        GoTo FINISHED
    End If
FINISHED:
    LEAD1.EnableMethodErrors = SavedSetting
    LEAD1.EnableTwainEvent = False
   
    'LEAD1.BitonalScaling = BITONALSCALING_SCALETOGRAY
    'LEAD1.AutoScroll = True
    'LEAD1.DstWidth = LEAD1.ScaleWidth 'Use the full control width.
    'LEAD1.DstClipWidth = LEAD1.ScaleWidth
    'CalcHeight = (LEAD1.BitmapHeight * LEAD1.ScaleWidth) / LEAD1.BitmapWidth
    'LEAD1.DstHeight = CalcHeight 'Use the proportional height.
    'LEAD1.DstClipHeight = CalcHeight
    'LEAD1.ForceRepaint

End Sub




'******************************************************************************
' Device Select Button Click
'******************************************************************************
Sub cmdSet_Click()
   
    On Error GoTo SELECT_CANCEL
   
    Hwnd = FindWindow(vbNullString, Me.Caption)
    LEAD1.TwainSelect Hwnd
   
    Sett.Range("B1") = LEAD1.TwainSourceName

    Exit Sub
   
SELECT_CANCEL:

End Sub







Private Sub LEAD1_TwainPage()

Dim PutRng As Range
Dim myfile As String

DoEvents

    '///// 오늘날짜 폴더 생성.
    Dim Path_add As String
    Dim file_name As String
        Path_add = ThisWorkbook.Path & "\ScanImages"
        file_name = ""
   
    If Dir(ThisWorkbook.Path & "\ScanImages", vbDirectory) = "" Then
        MkDir Path_add
    End If
   
    If Dir(ThisWorkbook.Path & "\ScanImages\" & Left(Now(), 10), vbDirectory) = "" Then
        MkDir Path_add & "\" & Left(Now(), 10)
    End If
   
    Path_add = Path_add & "\" & Left(Now(), 10)
   
   
    '///// 스캔 이미지 저장.
    Static PageCount As Integer
    PageCount = PageCount + 1
    myfile = Path_add & "\" & Left(Now(), 10) & "_" & Right("000" & PageCount, 4) & ".TIF"
    LEAD1.Save myfile, FILE_CCITT_GROUP4, TwainBit, 0, SAVE_OVERWRITE
    'LEAD1.Save myfile, FILE_TIF, TwainBit, 0, SAVE_OVERWRITE
   

    'Call OCRReader(myfile)
 
End Sub



당연히 이것만 가지고는 실행이 안된다.
리드툴은 창의 핸들을 필요로 한다 그렇기때문에 VBA의 Userform에 윈도우핸들값을 찾아다줘야 한다. (Hwnd) 

유저폼 코드에는 api를 public수준으로 갖다넣을수 없으므로 빈모듈을 하나 추가한다음

Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Public Declare Function EnumChildWindows Lib "user32" _
(ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByRef lparam As Long) As Long

Public Hwnd As Long
Public lparam As Long


이렇게 넣어준다.
창핸들을 잡는건 cmdScanST_Click() 프로시저에
Hwnd = FindWindow(vbNullString, Me.Caption)
이걸로 잡아주면된다.

LEAD1.EnableTwainEvent = True  해주면  스캔작업이 일어난 후에 자동으로 LEAD1_TwainPage()를 호출한다.

.EnableTwainFeeder = True
.EnableTwainAutoFeed = True
이 두개 프로퍼티를 True로 해주면 여러장 연속급지 처리가 된다.

.EnableTwainDuplex = 0
이 프로퍼티는 0일때 단면스캔, 1이면 양면스캔이다.
정확히는
Value                                    Meaning
TWAIN_DUPLEX_NONE        [0] No duplex scanning
TWAIN_DUPLEX_1PASS       [1] 1-Pass duplex scanning
TWAIN_DUPLEX_2PASS       [2] 2-Pass duplex scanning
TWAIN_UNSUPPORTED       [2001] The current Twain device does not support duplex scanning.


nRet = LEAD1.TwainAcquire(Hwnd)
twainActuire가 실제 스캔명령을 내리는 메서드이며 결과값을 nRet로 리턴한다. 성공하면 SUCCESS가 뜨고 LEAD1_TwainPage()로 넘어가서  지정된 동작을 수행하게 된다.

LEAD1.Save 가 파일저장 시키는 명령.



리드툴의 도움말 파일.

윈7에서 구 hlp파일을 열기 위해 필요한 업데이트 링크.
http://www.askvg.com/how-to-open-help-files-hlp-in-windows-7-that-require-windows-help-program-winhlp32-exe/

이것때문에 대체 몇시간을 끙끙대고 있었던가 -_-

임의의 도메인을 생성해서 hosts 파일에 추가해놓고  개발테스트를 주로 한다.
나역시 내마음대로 abcd.co.kr 을 만들어 호스트 등록 해놓고 사이트를 올리는데...

잘 돌아가는 사이트를 통째로 폴더복사 해서  떠놓고 iis 간단히 셋팅해줬을 뿐인데
무슨이유에선지  로그인이 안된다.
계속 디버그 찍어봐도 DB커넥이고 글로벌변수고 잘 먹히는데
도대체가 request.cookie 만 먹통....

이유는? IE에서 hosts 사용시 session/cookie 정상동작을 안한다는 문제..
http://eclipse4j.tistory.com/44  님 감사합니다 ㅜ

이거 해결하고 자시고 할 필요도 없다
그냥 AUTH체크 include에  아이디와 권한변수를 상수값으로 박아넣어버리고 개발하면 된다
어차피 크로스브라우징 체크해야하니  크롬등 다른브라우저에서 로그인 테스트 해보면 된다.
실서버 포팅할때만 빼주면 된다

아 열받어

'Web Program' 카테고리의 다른 글

2011.07.22 CSS+JQuery를 이용한 드롭다운메뉴  (0) 2011.07.22

오류 :

Microsoft OLE DB Provider for SQL Server 오류 '80004005'
사용자 'sa'의 계정이 현재 잠겨 있으므로 로그인하지 못했습니다. 시스템 관리자가 잠금을 해제할 수 있습니다.

해결 :

1) 쿼리 실행
ALTER LOGIN sa WITH PASSWORD = '1234' UNLOCK
go
2) 윈도우 인증으로 SQL 접속

[보안] - [로그인] - [sa : 속성] 에서 "암호 정책 강제 적용", "암호 만료 강제 적용" 체크 해제

김정선님의 쿼리 튜닝 글을 보고  몇가지 실제사례에 넣어본 결과.

자료 : 5천ROW정도의 문제테이블 (MQ_Q_INFO)
         300ROW정도의 유형테이블_카테고리  (MQ_Q_TYPE)
         600ROW정도의  기존출제문제 히스토리테이블 (MQ_Q_HST)
         200ROW정도의 회원테이블(MQ_MEM)
        ※ FN_SPLIT은 문자열을 분리해서 POS값과 VALUE값을 테이블로 반환해주는 사용자함수.

액션 : 문제테이블에서 이번에 출제할 대상문항을 여러 조건에 의해 추려내야한다.
조건1. 정해진 유형에 소속된 문항만 추출. (20010^20210^ .... 문자열을 SPLIT 처리)
          유형은 10개를 선택할수도 있고 100개를 선택할수도 있다..
조건2. 과거 출제경력을 조회해서 최근 5회차에 출제된 문제를 배제시킨다.(TIMES로 관리됨)
조건3. 조건2와 더불어서  기존에 3회이상 출제됐었던 문제를 배제시킨다. (SETTIMES로 누적관리됨)

조건1(선택유형 내에서만 뽑는부분)은 걍 SPLIT해서 해당키값을 유형테이블에서 Q_TYPE_CD로 바꿔온다음 문제테이블에서 쪼인해버리는걸로 처리했다.
이렇게 해서 뽑았을때 5천ROW중 708문제가 대상문제로 추출됨.

조건2와 3은 UNION으로 묶고 DISTINCT해준다음 (90개문항이 배제대상문항으로 나옴)
그걸  김정선님의 차집합 스킬로 처리했다.. 최종 산출 row는 618개.

한번에 만들어진게 아니고 계속 조립식으로 만들다보니 너저분해지긴 했지만..
요점은  김정선님의 차집합 추천스킬의 결과를 보는게 목적이므로......


참고블로그 : http://blog.naver.com/visualdb/50028645471
여기서 3,4,5번만 가지고 테스트.


3. NOT IN + 상관서브쿼리+ TOP 1  (평가 : (Very) Good)

 SELECT TPB1.* FROM MQ_Q_INFO TPB1
 , ( SELECT A.Q_TYPE_CD
  FROM MQ_Q_TYPE A,
  (SELECT * FROM  dbo.[FN_SPLIT]('20010^20210^20700^20710^20720^20220^20230^20240^20250^20260^20270^20280^20290^20300','^')) B
  WHERE
  A.CTG2_CD = B.VAL1
 ) TPB2
 WHERE
 TPB1.Q_TYPE_CD = TPB2.Q_TYPE_CD
 
 AND TPB1.Q_NUM NOT IN (SELECT TOP 1 TPC1.Q_NUM FROM
       (
        SELECT DISTINCT(YY.Q_NUM) FROM 
         (
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND SETTIMES > 3
          UNION
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND TIMES > (SELECT MAX(TIMES)-5 FROM MQ_Q_HST WHERE STU_ID = '8701290008')
         ) YY
         ) TPC1
         WHERE TPC1.Q_NUM = TPB1.Q_NUM
         )
총실행시간
시도횟수 1 : 2220 ms
시도횟수 2~10 :  1087 ~ 1165  (평균 1097)



4. NOT EXISTS + 상관서브쿼리+ TOP 1 (평가 : Not bad )

 SELECT TPB1.* FROM MQ_Q_INFO TPB1
 , ( SELECT A.Q_TYPE_CD
  FROM MQ_Q_TYPE A,
  (SELECT * FROM  dbo.[FN_SPLIT]('20010^20210^20700^20710^20720^20220^20230^20240^20250^20260^20270^20280^20290^20300','^')) B
  WHERE
  A.CTG2_CD = B.VAL1
 ) TPB2
 WHERE
 TPB1.Q_TYPE_CD = TPB2.Q_TYPE_CD
 
 AND NOT EXISTS (SELECT TOP 1 TPC1.Q_NUM FROM
       (
        SELECT DISTINCT(YY.Q_NUM) FROM 
         (
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND SETTIMES > 3
          UNION
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND TIMES > (SELECT MAX(TIMES)-5 FROM MQ_Q_HST WHERE STU_ID = '8701290008')
         ) YY
         ) TPC1
         WHERE TPC1.Q_NUM = TPB1.Q_NUM
         )


총실행시간
시도횟수 1 : 145 ms
시도횟수 2~10 : 17ms ~ 60ms (평균 57.7ms)

 엄청빠르다 +_+

 

 

5. 상관 서브쿼리+ TOP 1 + IS NULL (평가 : 김정선의 서브쿼리 튜닝 필살기 (Very) Good)

 SELECT TPB1.* FROM MQ_Q_INFO TPB1
 , ( SELECT A.Q_TYPE_CD
  FROM MQ_Q_TYPE A,
  (SELECT * FROM  dbo.[FN_SPLIT]('20010^20210^20700^20710^20720^20220^20230^20240^20250^20260^20270^20280^20290^20300','^')) B
  WHERE
  A.CTG2_CD = B.VAL1
 ) TPB2
 WHERE
 TPB1.Q_TYPE_CD = TPB2.Q_TYPE_CD
 
 AND (SELECT TOP 1 TPC1.Q_NUM FROM
  (
   SELECT DISTINCT(YY.Q_NUM) FROM 
    (
     SELECT Q_NUM FROM MQ_Q_HST
     WHERE STU_ID = '8701290008'
     AND SETTIMES > 3
     UNION
     SELECT Q_NUM FROM MQ_Q_HST
     WHERE STU_ID = '8701290008'
     AND TIMES > (SELECT MAX(TIMES)-5 FROM MQ_Q_HST WHERE STU_ID = '8701290008')
    ) YY
    ) TPC1
    WHERE TPC1.Q_NUM = TPB1.Q_NUM
    ) IS NULL

총실행시간
시도횟수 1 : 202 ms
시도횟수 2~10 : 175ms ~ 217ms (평균 186.5ms)









결론 : 다른부분은 전혀 튜닝도 안되고 구조도 제멋대로인 부분이지만   차집합구하기 부분만 놓고 봤을때는 어떤 형태의 쿼리에 얹히느냐에 따라  성능의 우위가 바뀔수도 있음을 확인할수 있다.

똑같은작업을 할때 NOT IN이냐 NOT EXISTS냐에 따라 20배에 가까운 속도를 단축할수 있다는데에 새삼 놀랍다....



쓸데없는 DISTINCT를 뺏더니 57.7ms에서 36ms로  또 급 단축되네..ㅎㅎㅎ


디자이너 요청에 의해 CSS를 이용한 드롭다운 메뉴를 만들어야 했다.
그것도 스르륵~ 내려오는 이펙트를 겸해서..

CSS만으로도 가능은 한것 같은데 ie가 안된다. 크로스브라우징이...
어찌 검색하다가 Jquery 라는걸 난생처음 알게되었다.
위대한 구글이 hoverIntent 플러그인까지 사용할 수 있게 도와주었다.
첨부파일은 hoverintent 플러그인 파일.



function overfunc() { $('div.the_menu').slideDown(500); }
function outfunc() { if(!$.hovering) { $('div.the_menu').slideUp(500); } }
hiConfig = {
        sensitivity: 3, // number = sensitivity threshold (must be 1 or higher)
        interval: 200, // number = milliseconds for onMouseOver polling interval
        timeout: 200, // number = milliseconds delay before onMouseOut
        over: overfunc, // function = onMouseOver callback (REQUIRED)
        out: outfunc   // function = onMouseOver callback (REQUIRED)
}

$(document).ready(function () {
 $.hovering = false;
 $('img.menu_class').hoverIntent (hiConfig);
 
 $('div.the_menu').hover(
   function() {
    $.hovering = true;
   },
   function() {
    $.hovering = false;
    $('div.the_menu').slideUp(500);
   }
   );
   
});

 

이미지 객체에 마우스를 올리고 잠깐 딜레이 하면  div메뉴객체가 스르륵 드롭다운
div메뉴객체가 펼쳐지고 그 위에 마우스가 올려진 상황에서는 드롭다운 메뉴가 접히지 않음.

이미지 객체와 div메뉴객체에서 동시에 마우스가 out되면  드롭다운 메뉴가 슬라이드업

휴.... Jquery 첫입문작 치고는 뭐 나쁘지 않은것 같다.

'Web Program' 카테고리의 다른 글

hosts 설정해서 개발테스트 할때 Session 문제..  (0) 2012.01.06

+ Recent posts