• 2005-12-11

    Eclipse 팁

    키 바인딩

    가능하면 Visual Studio 랑 비슷하게 맞추려면 다음과 같이 하라.

    Category Command Key
    Window Next Editor Ctrl + Tab
    Text Editing Delete Line Ctrl + L
    PyDev - Editor Python Comment Ctrl + K, Ctrl + C
    PyDev - Editor Python Uncomment Ctrl + K, Ctrl + U
    Run/Debug Run Last Launched Ctrl + F5
    Run/Debug Debug Last Launched F5

    SubVersion 연동하기

    eclipse 용 subversion 플러그인인 subclipse 를 이용하면 손쉽게 버전 관리가 가능하다. 다음 글을 참고할 것.

  • 2005-12-11

    ADO.NET 팁

    ADO.NET + VB.NET 으로 게임 상점 관리툴을 개발하면서 얻은 팁들을 정리해보았다.

    현재 선택된 셀 알아내기

    그리드에서 현재 선택된 셀을 알아내려면 DataGrid.CurrentCell 또는 DataGrid.CurrentRowIndex 를 사용한다. 그런데, 마우스가 그리드 프레임 또는 외부에 존재할 경우 값이 부정확하게 나온다. 따라서, DataGrid.HitTest(mousepos) 를 사용해서 마우스 좌표를 그리드 좌표로 변환한 다음 바운드 체크를 해야 한다.

    스키마 변경시 해야 할 일들

    DB 관련 작업을 하다 보면 불가피하게 스키마를 변경해야 할 때가 있다. 그때마다, 기존의 VB.NET 프로젝트에 만들어져 있는 형식화된 DataSet과 DataAdapter를 재설정해야만 낭패를 면할 수 있다.

    • 방법은 간단한데, 디자이너 모드에서 각 DataAdapter를 선택하고 오른쪽 클릭해서, "데이터 어댑터 구성"을 선택해서 새로 SQLCommand 들을 생성해주면 된다.
    • 그다음 "데이터 집합 생성"을 선택하면 된다.
    • 끝나고 나서 DataSet 객체를 오른쪽 클릭해서, "데이터 집합 보기" 를 선택한 후 잘 바뀌었는지 체크해본다. (때로는, FK 문제로 에러가 나기도 하지만 무방하다)
    • 틈틈히 스키마가 담겨있는 xsd 파일을 체크해서 최신 스키마로 되어 있는지를 확인해주면 더욱 좋다.

    관계 지정

    데이터 그리드에서 관련된 row 를 보여주고 싶다면 DataRelation 을 설정하면 된다.

    • 프로그래밍 방식 : DataRelation 객체를 생성해서 부모 테이블의 컬럼과 자식 테이블의 컬럼을 지정할 수 있다. (비츄)

    ' ProductInfo.ProductID = ProductItems.ProductID
    Dim FK_ProductItems_ProductInfo As DataRelation = New DataRelation( "FK_ProductItems_ProductInfo", _
    dsBoom.ProductInfo.ProductIDColumn, dsBoom.ProductItems.ProductIDColumn)
    FK_ProductItems_ProductInfo.Nested = True
    dsBoom.Relations.Add(FK_ProductItems_ProductInfo)

    • 디자이너 방식 : 스키마 에디터에서 컬럼 끼리 연결짓고 이름을 명시하면 된다. (추천)

    • DataRelation.Nested 속성은 디자이너 모드에서 속성 화면에서 수정하면 된다.

    • 만약, 상위 혹은 하위 테이블의 내용을 보여주고 싶지 않다면 DataGrid.AllowNavigation = False 로 지정하라.
    • 네비게이션 상태에서의 테이블 스타일은 명시적으로 설정하려면, 해당 DataGrid 에 테이블 스타일을 하나 더 추가하면 된다.
    • DataRelation의 이름이 한글이어도 무방하다. 단 row 마다 다르게 줄 수는 없는 듯하다. (가령 하위 테이블 row 의 개수를 이름 뒤에 붙이고 싶어도..포기하라..)

    DataSet.Merge

    데이터 집합의 개념상 메모리상의 캐시를 갖고 이리 저리 갖고 놀다가 최종적인 변화를 DB로 업데이트하게 된다. 보통 이런 작업은 그리드의 특정 row 를 클릭하면 해당 정보를 보여주는 새로운 윈도우 폼이 뜨고, 여기에서 수정을 하고 저장하면 원래 데이터 집합에 반영시키면 된다는 거다.

    이때 주의해야 할 점은.. DataSet.AcceptChanges() 와 DataSet.RejectChanges() 는 DB에서 읽어온 후로의 변화를 커밋하거나 롤백시킨다는 점이다. 즉 이 함수 중 하나를 일단 부르게 되면, 그동안 있었던 모든 변화가 날아가거나 더이상 롤백시킬 수 없는 상황에 이르게 된다.

    만약, 에디트 폼의 수많은 컨트롤들과 원본 데이터집합을 DataView 를 통해서 바인딩시킬 경우, 에디트 폼에서 수정을 하다가 캔슬시켜도 원본 데이터집합이 변형되게 된다. 그렇다고 Reject 를 호출하게 되면 그전의 수정 사항이 모두 날아가게 되니... 대략 난감한 상황이 된다.

    레이옷이 추천하는 방식은, 새 에디트 폼에 원본 데이터집합의 복사본(DataSet.Copy())을 넘겨주고 에디트 폼에서 수정한 후 OK 하면 Merge 하고 Cancel 하면 그냥 무시하는 방식이다.

    관련 테이블의 내용도 함께 보여주는 방법

    특정 상품에 포함된 아이템 정보를 보여준다고 하자. DB 상에서는 이를 상품-아이템 관계 테이블로 구현하게 된다. 이때, DB 에서 읽어오는 값은 보통 정수형 상품키/아이템키 값일 뿐이며, 이를 그냥 바인딩시키면 그리드에는 숫자만 나오게 되므로 대략 알아보기가 곤란하다. ( 3번 아이템 2개와 5번 아이템 1개로 구성된 상품.. 직관적이지 않다)

    이를 해결하는 방법은 여러 가지가 있는 듯한데, 본좌 1개의 방법만 발견했다. 그것은, 아예 데이터 아답타 레벨에서 join 을 걸든 join 된 view 를 이용하든, 키값과 이름을 함께 읽어온다는 거다.

    이 방식의 단점은, 데이터 아답타 마법사에서 UpdateCommand와 DeleteCommand 를 자동으로 생성하지 못한다는데 있다. 결국 이 부분은 수동으로 해줘야 하는데... 난감하다.

    일단 다른 아답타의 Update/Delete Command 에서 샘플을 보고 참고해서 만들어나가면 되긴 된다. 이때, 파라미터가 잘 만들어졌는지, Current/Original 인지 확인해줄 것

    주의사항 : 뷰를 통해서 데이터를 갖고 올 경우, 데이터 집합 생성시 xxxView 로 만들어진다. 이를 원래의 관계 테이블로 매핑시키려면, DataAdapter.TableMapping 속성을 설정해주면된다.

    입력 검증(verification) 과 이벤트 핸들링

    만약 그리드에서 on the fly 수정을 하려면, 데이터 테이블에서 발생시키는 이벤트를 핸들링시켜야 한다. 총 6가지의 이벤트가 존재한다.

    • ColumnChanging -> ColumnChanged
    • RowChanging -> RowChanged
    • RowDeleting -> RowDeleted

    이런 이벤트들은 대략 다음과 같은 조건에서 발생한다.

    • 특정 셀을 수정한 다음 column 이 바뀌면(==focus changed) ColumnChanging -> ColumnChanged 순서로 호출된다.
    • row 가 바뀌면 ColumnChanging -> ColumnChanged -> RowChanging -> RowChanged 순서로 호출된다.

    상점 관리툴에서는, 그리드에서 수정을 하게 되면 저장 버튼을 활성화 시켜서 뭔가가 바뀌었다고 사용자에게 알려주도록 하기 위해, xxxChanged 이벤트 핸들러에서 대략 다음과 같은 코드를 사용했다.

    Private Sub OnCategoryRowChanged(ByVal sender As Object, ByVal args As DataRowChangeEventArgs)
    SaveButton.Enabled = dsBoom.HasChanges
    End Sub

    실제로 이벤트 핸들러에서는 현재 유저가 수정한 내용을 보고, 범위에 맞는지 혹은 적절한 값인지를 검사해서 틀릴 경우 예외를 던지는 방식의 입력 검증을 해줘야 한다.

    데이터 그리드 간 연동

    데이터베이스 상에 MasterTable, SlaveTable 이라는 2개의 테이블이 dsXXX 라는 데이터셋에 dlXXX 라는 relation 으로 연관되어 있다고 하자. 이 테이블들을 각각 MasterGrid, SlaveGrid 에서 보여주려고 할 때, 아래와 같이 설정하면 두 그리드가 연동된다.

    MasterGrid.DataSource = dsXXX
    MasterGrid.DataMember = "MasterTable"

    SlaveGrid.DataSource = dsXXX
    SalveGrid.DataMember = "MasterTable.dlXXX"

    이때 주의할 점은, SlaveGrid 와 연관된 SlaveTable 의 실제 크기와 보여지는 크기가 다르다는 것이다. (FK로 필터링되었으니 출력되는 크기는 실제 크기보다 작거나 같게 된다) 따라서, 마우스로 특정 row 를 선택했을 때, MasterGrid 의 경우 데이터테이블을 통해서 직접 접근하면 되지만, SlaveGrid 의 경우에는 이를 알 수 없다는 것이 문제가 된다. (특히 VisibleRowCount 는 단지 현재 화면에 보이는 크기라서 신용할 수 없다)

    이 경우에는 현재 SlaveGrid 의 CurrentRowIndex 라든지 HitTestInfo 를 써서 현재 row 를 알아낸 다음, MasterTable(MasterGrid.CurrentRowIndex).GetChildRows("dlXXX") 를 통해서 실제 값에 접근하면 된다. 이 GetChildRows(relation_name) 은 DataGrid.AllowNavigation = True 를 설정했을 때 나오는 놈들과 같다.

    다이얼로그 with 데이터집합

    단지 책을 통해서 얻은 지식만으로 한 거라서 더 좋은 방법이 있을지도 모르겠지만, 폼에서 작업을 하다가 어떤 상황이 되어 다이얼로그를 띄워야 할 때, 그 다이얼로그에서도 현재 데이터셋과 동일한 넘을 Clone() 이든 Copy() 든 복제해서 써야 할 경우가 있다.

    예를 들면, 특정 상품 카테고리에 새 상품을 추가할 때, 전체 상품 목록 중에서 이미 해당 카테고리에 들어가 있는 상품을 제외한 나머지 목록이 데이터 그리드에 담긴 새 다이얼로그를 띄워야 하는 경우가 있을 수 있다. (지금 하고 있는 작업이다) 이를 위해서는

    i. 새 다이얼로그에 데이터셋을 추가
    i. 데이터 그리드 하나를 추가
    i. 디자이너에서 데이터그리드의 데이터소스 및 데이터멤버를 지정
    i. 코드에서 다이얼로그 객체를 생성하고 부모 폼에서 데이터셋을 복사한 다음 다이얼로그를 띄우기

    Dim dlg As ProductSelectDialog = new ProductSelectDialog
    dlg.dsXXX = Me.dsXXX.Copy
    dlg.ShowDialog()

    이렇게 해도, 새 다이얼로그는 빈 그리드만을 담게 된다. 이유는 단 하나. 디자이너 모드에서 지정한 데이터소스 및 데이터멤버가 Copy() 에 의해서 초기화되었기 때문이다. (너무나도 사소한 버그) 이를 해결하려면 프로그래밍방식으로 지정하는 수 밖에 없다.

    Dim dlg As ProductSelectDialog = new ProductSelectDialog
    dlg.dsXXX = Me.dsXXX.Copy
    dlg.DataGrid1.DataSource = dlg.dsXXX
    dlg.DataGrid1.DataMember = "what"
    dlg.ShowDialog()

    마우스로 그리드 위치 알아내기

    물론 DataGrid.CurrentRowIndex 라든지 CurrentCell 이 좋을 것 같지만, MouseUp 이벤트 핸들러에서 얘들은 그리드 바깥쪽을 클릭했을 때 이전에 선택된 값을 리턴하므로 부정확한 정보를 리턴하게 된다. 따라서 HItTestInfo 를 사용하는 것을 추천한다.

    Dim pos As DataGrid.HitTestInfo = MyGrid.HitTest(e.X, e.Y)

    그런데, 이렇게 값을 얻어와서 선택된 row 의 실제 값을 알아오려면 바운드 체크가 필수적이다. 만약 그리드에서 row header, column header 를 출력하고 있다면 유저는 헤더를 선택하기가 쉬운데, 이때 각 값은 -1 을 리턴하게 되고 이 값으로 데이터테이블에 접근하면 낭패가 된다. 즉, pos.Row > 0 And pos.Column > 0 체크는 필수적이며, 가능하면 바인딩된 데이터 테이블의 최대 크기와도 비교해주면 좋을 것이다. 덤으로, DataGrid.Select(row) 를 해주면 현재 row 전체의 배경색을 반전시킬 수 있다.

    새 row 의 특정 셀에 값 넣기

    그리드의 마지막 row 를 클릭하면 임시 row 가 새로 만들어지고, 여기에 값을 적당히 넣고 포커스를 다른 곳으로 옮기면 이넘이 그리드에 바인딩된 데이터 테이블로 커밋된다.

    문제는, 이 넘은 커밋되기 전까지는 데이터 테이블에서 검색할 수 없다는 점이다. 웬지 이 row 의 상태는 Detached 인 거 같지만, DataTable.Select() 에서는 이 상태로 검색을 지원하지 않는다. 당연히 아직 커밋되지 않았으므로 CurrentRows 로도 찾을 수는 없다.

    해결책은, DataGrid.Item(row,col) 을 이용해서 직접 수정하면 된다는 것. 지금까지는 무조건 데이터테이블을 통해서 실제 데이터에 접근했는데, 앞으로는 그리드를 통해서 데이터에 접근하는 것도 좋은 방법이 될 듯하다.

    특정 셀에 DBNull 넣기

    원하는 셀에 포커스를 맞춘 다음, Ctrl + 0 를 입력하면 DBNULL 로 초기화된다.

    output parameter 가 있는 프로시저 실행

    코드에서 SQLCommand 를 직접 사용하면 된다. 코드는 매우 직관적이며 간단하지만, 한가지 주의할 점이 있다.

    실제 프로시저에서 명시한 것과 동일한 이름의 파라미터를 사용하라!!

    Dim cmd As SqlCommand = New SqlCommand
    cmd.CommandText = "proc_name"
    cmd.Connection = conn
    cmd.CommandType = CommandType.StoredProcedure

    Dim param As SqlParameter = cmd.Parameters.Add("@in_param", SqlDbType.VarChar, 11)
    param.Value = "something"

    param = cmd.Parameters.Add("@out_param", SqlDbType.Int)
    param.Direction = ParameterDirection.Output

    conn.Open()

    Dim reader As SqlDataReader
    reader = cmd.ExecuteReader()
    reader.Read()
    reader.Close()
    conn.Close()

    TextBox1.Text = cmd.Parameters("@out_param").Value

  • 2005-12-11

    ADO.NET 이란?

    ADO.NET은 OLE DB 및 XML을 통해 제공되는 데이터 소스뿐 아니라 Microsoft SQL Server와 같은 '''데이터 소스에 대한 일관성 있는 액세스'''를 제공합니다. 데이터 공유 소비자 응용 프로그램은 ADO.NET을 사용해서 이러한 데이터 소스에 연결하여 '''데이터를 검색, 조작 및 업데이트'''할 수 있습니다.
    -- from MSDN

    • 모든 것은 데이터 소스 - DB, 엑셀, XML, 텍스트파일, ...
    • 연결되지 않은 데이터 집합 - 캐싱 방식
    • XML 과의 강력한 통합
    • N 레이어 어플리케이션에 적합 - 특히 웹 어플리케이션...

    ADO.NET의 구성 요소

    ADO

    DataSet

    메모리에 존재하는 데이터들 (쉽게 말하자면 in 메모리 DB, but...)

    • 데이터 테이블(들) + 데이터 관계(들)
    • 형식화된 데이터 집합 vs. 형식화되지 않은 데이터 집합

    ADO
    ADO

    DataTable

    동일한 데이터(DataRow)의 집합.

    • PK. Unique, Auto Increment, Not Null 등의 Constraint 를 지정할 수 있다.
    • DB 테이블과 유사하다. 그러나, 완벽하게 같지 않다 -> DB View 또는 조인을 통해 데이터 테이블로 갖고 올 수도 있고, 로컬에서 수동 생성도 가능하다.

    ADO

    DataRelation

    데이터 테이블간의 관계.

    • PK. FK. Unique, Auto Increment
    • 데이터 그리드의 navigation 에서 사용됨.

    ADO

    DataAdapter

    데이터 소스에서 데이터를 읽어 데이터집합을 채운다. 그리고, 데이터집합의 변경 사항을 데이터 소스로 반영한다.

    • SelectCommand, UpdateCommand, DeleteCommand, InsertCommand...

    ADO

    DataView

    특정 데이터 테이블을 row 필터링한 부분 집합.

    • 컬럼 필터링은 안됨
    • 데이터 테이블의 조인도 안됨
    • SQL 처럼 WHERE 필터를 걸 수 있다.

    ADO

    DataGrid

    테이블 형태의 데이터를 보여주는 강력한 .NET 컨트롤

    • 정렬, 추가, 삭제, 수정, 네비게이션, 등등 웬만한 기본 기능들은 모두 들어 있다.
    • 텍스트박스, 체크박스만이 포함될 수 있다. 그러나, 커스텀 컨트롤을 만들면 딴 넘들도 가능.

    ADO

    DataBinding

    데이터와 컨트롤의 자동 매핑

    • 텍스트 박스, 체크 박스 등 단순 컨트롤과의 바인딩
    • 콤보박스, 그리드 등 복잡한 컨트롤과의 바인딩

    ADO

    VB.NET 으로 데이터베이스 어플리케이션 만들기

    가장 빨리 DB 어플리케이션을 만드는 방법!

    1. 2개의 테이블을 드래그 앤 드랍해와서, 형식화된 데이터 집합을 만든다
    2. XSD를 수정해서 관계를 만든다.
    3. 데이터 그리드를 추가, 바인딩한다.
    4. 수정후 업데이트하기
    5. 끝.
  • 2005-12-07

    나쁜 아이디어를 위한 좋은 방법

    지금 하고 있는 이런 하찮은 일은 대충 빨리 끝낼 예정이다. 난 멋진 프로젝트를 언젠가 해야할 몸이기 때문이다.

    당연히 웹 디자인에만 적용되는 이야기는 아니며, 특히 legacy code 를 다루는 프로그래머들이 쉽게 빠지는 길이기도 하다. 사실 나를 포함한 우리 팀원들 대다수가 이런 마음가짐으로 일하고 있기에 더욱 가슴이 시리다. 물론 지금으로서는 빨리 끝내는 것만이 유일한 해결책이지만...

  • 2005-11-23

    VB를 이용한 선분과 원의 충돌체크

    collide

    고등학교때까지만 해도 기하쪽은 상당히 자신있었는데 나이가 들어갈수록 3D 쪽에는 상당히 취약해서 가고 있다. 몇 달 전 관련 서적을 쭈욱 질러주고 조금 보다가 때려쳤었다. 그러다 이번에 유지 보수 중인 legacy code 에서 brute force 충돌 체크 코드를 좀 더 깔끔하게 해보고자 선분과 원의 충돌 체크를 구현해보게 되었다. 역시나 이럴 때에는 MFC 보다는 .NET winform 이 제일 손쉽기에 재빨리 VB.NET 을 띄워 줬다.

    우선 Matrix 나 Vector 류의 함수는 .NET framework 에서 지원하지 않기 때문에 DirectX 쪽의 참조를 갖고 와야 한다. 그다음 리얼타임렌더링 625 페이지를 펼쳐서 그대로 코딩하면 된다. 실제로 타이핑은 금방 끝났는데 웬걸 제대로 동작하지 않아서 이 사람 저 사람 붙잡고 물어보다가 잠이 들고, 오늘 회사에 와서 차분히 책을 읽어보니 그 중 한 넘이 normalized vector 여야 한다는 걸 찾아냈다. 뿌듯.. -_-+

    책은 반직선과 구의 충돌이라서 선분 충돌을 구하려면 교차 좌표 중 가까운 넘을 찾아내서 그 길이가 원래 반직선 길이보다 짧아야 진짜 충돌이 된다. 사실 3D 를 쭉 하던 사람들이야 너무 쉬운 것일테지만 수학책을 한동안 놓고 살았던 본인에게는 꽤나 막중한 과제였다. 역시나 해내고 나니 성취감 보다는 허탈감이 든다. 흑.

    
    Imports Microsoft.DirectX
    
    Public Class Form1
    Inherits System.Windows.Forms.Form
    
    Dim bCollided As Boolean = False
    
    Dim circleRect As Rectangle
    Dim PenWidth As Integer = 1
    
    Dim o As Vector2 = New Vector2
    Dim o2 As Vector2 = New Vector2
    Dim c As Vector2 = New Vector2
    Dim r As Single = New Single
    Dim d As Vector2 = New Vector2
    Dim l As Vector2 = New Vector2
    Dim s As Single = New Single
    
    Dim l2 As Single = New Single
    Dim m2 As Single = New Single
    Dim world As Matrix = New Matrix
    Dim t As Single = New Single
    
    Private Function CheckCollide() As Boolean
    
    r = circleRect.Width / 2
    c.X = circleRect.X + r
    c.Y = circleRect.Y + r
    
    Dim P As Vector2 = Vector2.Subtract(o2, o)
    d = Vector2.Normalize(P)
    
    l = Vector2.Subtract(c, o)  ' l = c - o
    s = Vector2.Dot(l, d)  ' s = l dot d
    l2 = Vector2.Dot(l, l)  ' l^2 = l dot l
    Dim r2 As Single = r * r
    
    If (s  r2) Then  ' if ( s  r^2 ) return reject
    Return False
    End If
    
    m2 = l2 - s * s  ' m^2 = l^2 - s^2
    
    If (m2 > r2) Then  ' if ( m^2 > r^2 ) return reject
    Return False
    End If
    
    ' 이제 선분이 교차하고 있다. 교차하는 두 점을 구한 후 가장 가까운 넘과의 거리가 선분의 길이보다 작아야 한다.
    
    Dim q As Single = Math.Sqrt(r2 - m2)
    If (l2 > r2) Then
    t = s - q
    Else
    t = s + q
    End If
    
    Return P.Length >= t
    
    End Function
    
    Private Sub setupCircle(ByVal e As Point)
    If e.X