본문 바로가기

컴퓨터/파워빌더

파워빌더 - 실무에 유용한 팁모음2

[Tip21] 

DataWindow Title bar


Window에 Title bar가 있듯이 DataWindow에도 Title bar가 있다.

DataWindow에서 Title bar를 사용하면 마우스로 DataWindow를 이동시킬 수 있게 된다.

하지만 DataWindow control에서 pbm_syscommand user event를 사용하면 DataWindow를 고정시킬 수 있다.


int a, b


a = message.wordparm


CHOOSE CASE a

   CASE 61456, 61458

     message.processed = true

     message.returnvalue = 0

END CHOOSE


return


이렇게 pbm_syscommand에 script를 작성하면 마우스로 DataWindow를 이동시킬 수 없으면서 DataWindow에서 Title bar를 사용할 수 있다.


[Tip22] 

모든 칼럼의 tab order를 0으로


DataWindow에 있는 모든 column의 tab order를 zero로 setting하는 방법.

tab order를 zero로 설정하면 사용자는 Data를 수정하거나 입력할 수 없

게 된다.따라서 들어오는 사용자의 권한에 따라서 수정할 수 있게 또는 수정할 수 없게 프로그래밍하는 경우 유용한 Tip이다.


int i, i_count


i_count = integer(dw_1.Object.DataWindow.Column.Count)


FOR i = 1 TO i_count

 dw_1.SetTabOrder(i, 0)

NEXT


[Tip23] 

DropDown DataWindow의 Display 값


DataWindow에서 Drop-Down DataWindow의 Display되는 값을 얻는 간단한 방법을 소개하겠습니다.

기존에는 DataWindow의 script에서 drop-down DataWindow의 display되는값을 얻기위해 GetChild함수를 사용해서 child datawindow를 구한다음에 getitem 종류의 함수로 display되는 값을 얻을 수 있었습니다. 버젼 4.0에선 그 방법밖에는 없었습니다. 하지만 버젼 5.0에선 child

datawindow를 사용하지 않고 한줄로 해결할 수 있습니다.

새로운 함수인 LookupDisplay함수를 사용하는 겁니다.


string  s

long    l_row


l_row = 3 

//l_row는 DataWindow의 row 번호를 의미합니다.

//즉 우리는 3번 row의 dept_id column의 display되는 값을

//얻고자 합니다.


s = dw_1.Describe("Evaluate('LookupDisplay(dept_id)', " + &

                   String(l_row) + ")")


 이렇게 하면 변수 s에는 dept_id의 코드값에 대응되는 dddw의 display

되는 문자열이 저장됩니다. 위의 Discribe의 인수로 주어지는 수식을 잘 보시면 됩니다.


Evaluate('LookupDisplay(dept_id)', 3)


[Tip24] 

RowCount in Footer


 DataWindow painter에서 하단에 'Page 12 of 15'와 같은 내용의 computed column을 사용할 수 있다.

 이건 간단하게 DataWindow painter에서 제공하는 computed column이다.  하지만 DataWindow preview를 해보면 하단 message bar에서'Rows 12 to 15 of 23'이라는 식의 내용을 볼 수 있다.

이러한 내용을 보여주는 computed column을 만들어 보자.

computed column을 DataWindow의 footer에 놓는다.

우선 간단한 방법으로...

computed column의 expression으로 다음과 같이 기술한다.


'Rows ' +

Describe("datawindow.firstrowonpage") +

'to ' +

Describe("datawindow.lastrowonpage") +

'of ' +

RowCount()


이렇게 하면 'Rows x to y of z'형식으로 항상 보여지게 된다.

하지만 retrieve된 row가 전혀 없을때도 'Rows 0 to 0 of 0'이라는 형식을 고수한다. 이것보다는 'No Rows'가 더 나을것 같다.

그리고 한page에 모든 Row가 보여질 정도로 적은 경우가 있다.

이러한 때는 'Rows x to y' 형식이 더 나을것 같다.

그래서 위의 expression을 수정해보자.


If( RowCount() = 0,

   'No Rows',

   If( Describe("datawindow.firstrowonpage") =

       Describe("datawindow.lastrowonpage"),

       'Row ' +

       Describe("datawindow.firstrowonpage") +

       'of ' + RowCount(),

       'Rows ' +

          Describe("datawindow.firstrowonpage") +

          'to ' +

          Describe("datawindow.lastrowonpage") +

          'of ' +

          RowCount()

     )

)


[Tip25] 

Selected Rows


DataWindow에서 SelectRow함수를 이용하면 DropDownListBox처럼 Row를 마우스로 선택하는 효과를 얻을 수 있다.

또한 동시에 여러개의 Row를 선택할 수 있다.

이건 DataWindow를 DropDownListBox처럼 사용할 수 있다.

이런 SelectRow함수는 DataWindow의 RowFocusChanged 또는 Clicked Event에 주로 기술한다.

또한 SelectRow함수로 선택된 Row의 색깔은 파란색으로 반전 된다.

이렇게 사용자가 선택한 Row가 몇개나 되는지 또한 선택된 Row의 특정

Column의 값을 얻고자 한다면 흔히들 GetSelectedRow함수를 사용해서 loop를 돌리게 된다. 하지만 loop대신 한줄로 해결할 수 있다.


long dept_id[]

dept_id = dw_1.object.dept_id.selected


하면 column dept_id의 값이 dept_id라는 배열에 모두 저장된다.

배열의 DataType은 column의 DataType과 일치하게 선언하면 된다.

즉 선택된 Row의 dept_id라는 column의 값이 dept_id라는 배열에 치환된다.


[Tip26] 

칼럼이름 저장하기


DataWindow에 있는 Column의 이름을 저장하는 방법입니다.

아주 간단한 Tip입니다.


String ls_string[]

int    i

any ll_count


ll_count = dw_1.Object.Datawindow.Column.count


For i = 1 to integer(ll_count)

 ls_string[i] = dw_1.Describe("#"+String(i)+".Name")

Next


이렇게하면 ls_string에 Column 이름이 들어가게 된다.

"#"+String(i)+".Name"을 잘 보세요.

Modify함수나 Describe함수에서 #1,#2 등은 Column을 의미합니다.

즉 DataWindow에서 Table의 Column을 선택할때 상단의 selection list 에 나오는 column의 순서를 #1등으로 표시하는 겁니다.

그럼 #1은 dwobject과 같은 type을 갖게 됩니다.

따라서 dwobject.name처럼 사용하면되죠.

DataWindow의 Itemchanged event에서 사용하는 dwobject처럼 사용하는 겁니다.


[Tip27] 

시리얼 통신


Win3.x에서 16bit PowerBuilder로 communication ports를 사용하는 것은 간단하다. 하지만 Win95/NT에서는 좀 어려운 작업이 필요하다.

왜냐하면 이들 OS는 communication port를 다른 방식으로 사용하기 때문이다. 즉 port를 file처럼 사용한다.  게다가 Win32 API 함수를 사용해야 한다. 또한 communication port file은 특별한 file이기 때문에 매우 특별한 방법으로 사용해야 한다. 먼저 Win32 API 함수가 필요하다.


/* To create a file */


function long CreateFileA(


     ref string lpszName,

     long fdwAccess,

     long fdwShareMode,

     long lpsa,

     long fdwCreate,

     long fdwAttrsAndFlags,

     long hTemplateFile ) library "kernel32.dll"


/* to write to the file */


function boolean WriteFile(


     long hFile,

     ref string lpBuffer,

     long nNumberOfBytesToWrite,

     ref long lpNumberOfBytesWritten,

     st_overlapped lpOverlapped ) library "kernel32.dll"


/* To close the file */


function long GetLastError() library "kernel32.dll"


/* To get error information */


function boolean CloseHandle(long hObject ) library "kernel32.dll"


위 4개의 API함수 ProtoType을 PowerBuilder에서 Global External Fucntions정의하는 곳에 기술하면 된다. 그럼 Script에서 참조할 수 있다. st_overlapped는 communication port로 쓰기를 시도할때 사용하는 structure다. 이것을 PB에서 정의한다.


     $PBExportHeader$st_overlapped.srs

     global type st_overlapped from structure

     long Internal

     long Internalhigh

     long offset

     long offsethigh

     long hevent

     end type


다음 예는 모뎀이 어떻게 전화를 거는지를 보여준다.

그외의 자세한 사항은 Win32 API HelpFile에 설명되어있다.

PowerBuilder에 있는 Watcom C++ compile에 보면 Helpfile이 있다.


/* declare the needed variables */


long ll_comid

long lnull

st_overlapped lst_overlapped

long ll_written

string ls_Port

string ls_Number

string ls_CRLF = "~r~n"


/* some constant, see helpfile for more information */


long GENERAL_WRITE = 1073741824

long SHARE_MODE = 0

long OPEN_EXISTING = 3

long FILE_FLAG_OVERLAPPED = 1073741824


/* port and number to dial */


ls_Port = "COM2"

ls_Number = "ATDT 0306090146" + ls_CRLF

setnull(lnull)


/* open the port by creating the 'file' */


ll_Comid = CreateFileA(ls_port,GENERAL_WRITE,SHARE_MODE,lnull, OPEN_EXISTING,FILE_FLAG_OVERLAPPED,lnull)


IF ll_ComId >= 0 THEN


/* write the number to the port */


   writefile(ll_ComId, ls_Number,len( ls_Number),ll_written, lst_overlapped)

   messagebox("Yo!","Press Enter To Disconnect")

   writefile(ll_ComId, ls_CRLF,len(ls_CRLF),ll_written, lst_overlapped)


ELSE


/* display error */


   messagebox(string(ll_Comid),getlasterror())


END IF


/* close always */

closehandle(ll_ComId)

[Tip28] 

Window를 항상 Top위치에


Window가 다른 Window들 보다 항상 Top에 있어야할 필요가 있는 경우가 있다. Win3.1에서의 시계가 Option으로 항상 위에 있게 할 수 있게 되어있었다. PowerBuilder에서는 이것을 아주 간단히 구현할 수 있도록 SetPosition() method로써 제공하고있다.


SetPosition()에 사용되는 argument로서 TopMost!와 NoTopMost!가 있다. 이밖에 Behind!, ToTop!, ToBottom!이 있다.

winobj.SetPosition(TopMost!)로 하면 Window는 항상 Top위치에 있게 된다. 이를 해제할려면 winobj.SetPosition(NoTopMost!)로 하면 된다. 이밖에 ToTop!은 Window들 중에서 가장 높은 Top위치로 올리는 것이고, ToBottom!은 Window들 중에서 가장 낮은 Bottom위치로 내리는 것이다. Behind!를 사용할때는 Argument가 더 추가되어서 여러 Window들 중에서 몇번째 위치로 올려놓을 것인지를 지정해주어야 된다.

SetPosition() Method는 Window Object뿐만 아니라 Window에 있는 다양한Object에도 적용가능하다.

예를 들어서 겹쳐있는 DataWindow의 위치를 변화시켜줄 필요가 있는 경우에도 사용할 수 있다.


[Tip29] 

OpenWithParm


OpenWithParm을 사용하면 Open되는 Window로 필요한 parameter를 넘겨줄 수 있다.

하지만 PB에선 하나의 변수만 넘길 수 있다.(여러개를 넘길때는 structure를 사용한다.)

예를 들어서 OpenWithParm(w_abc,"hello"))은 PB의 global 변수인

Message.StringParm에 저장되어 전달된다.

OpenWithParm(w_abc, 34)하면 Global 변수인 Message.DoubleParm에 저장되어 전달된다.

그밖에 다른 Type의 값들인 경우는 MessagePowerObjectParm에 저장되어 전달된다. 그럼 하나이상의 값을 어떻게 전달할 수 있는가...

그건 structure object을 사용하면 된다.

structure object에 unbounded array를 정의하면 필요한 갯수만큼을 전달할 수 있다.

unbounded array는 실행시에 배열의 크기를 결정하는 형식이므로 얼마든지 많은 값을 전달할 수 있다.

structure painter에서 'Variable Name'을 s[]로하고 'Type'을 string으로 한다. 그럼 배열 s[]는 string을 저장할 수 있다.

계속해서 필요한 여러 Type의 배열을 정의해준다.

예를 들어서 double type의 d[], boolean type의 b[], datetime type의 dt[] 등으로 같은 structure안에 각각을 정의한다.

structure 이름을 str_parms로 한다면 아래와 같이 활용할 수 있다.


//Script에서 정의한 str_parms type의 structure 변수를 선언한다.


     str_parms l_str_parms

//전달하고자 하는 값을 Type에 맞는 structure의 member에 치환시킨

//다.


     l_str_parms.s[1] = "Smith"

     l_str_parms.s[2] = s_company_name


     OpenWithParm(w_abc, l_str_parms)

     //w_abc의 Open event

     //앞전의 script에서 보낸 structure가 전역변수인

     //Message.PowerObjectParm에 저장되어 있다.


     str_parms l_str_parms


     l_str_parms = Message.PowerObjectParm


     //여기서 전달받은 structure의 각각의 값을 사용한다.

     //(ex. l_str_parms.s[1], etc.)


     위와같은 방식은 CloseWithReturn 함수에서도 똑같이 적용된다.



[Tip30] 

MultiLineEdit에서의 TabKey사용


MultiLineEdit에서 입력중에 Tab key를 누르면 tab order 순서에 따라 focus가 이동된다.

이때 발생하는 event는 pbm_keydown 또는 pbm_dnwkey인데 이들 event를 직접 가로채서 MultiLineEdit에 tab문자나 Spaces문자를 입력하게 하는것은 힘들다.

하지만 Tab key에의한 Focus이동을 허용하지 않고 Tab문자나 Spaces문자를 MultiLineEdit에 입력하게 하고자 한다면 MultiLineEdit의 LoseFocus Event에서 Tab Key가 눌려졌는지를 KeyPress()함수를 이용해서 처리할 수 있다.

Tab Key가 눌려졌다면 원하는 문자를 입력하고 나서 User Event를 Post시켜서 focus를 SetFocus()함수를 사용해서 다시 MultiLineEdit로 돌려놓는다.


LostFocus Event


IF KeyDown(KeyTab!) THEN

   this.ReplaceText("   ")

   this.PostEvent("ue_setfocus")

END IF


[Tip31] 

WAV 화일 불러오기


waveform audio file을 연주하기 위해선 API함수를 사용해야 합니다.

이 함수는 SndPlaySoundA()함수입니다.

두개의 Parameter를 취하는데 하나는 .WAV file name 또하나는 연주하는 방법.

먼저 API함수를 사용하기 위해서는 Declare Menu에서 Local External Function을 선언해야 합니다.

Function Boolean sndPlaySoundA(String s_file, UINT u_flags) Library "WINMM.dll"

Function UINT LoadLibraryA(String as_library) Library "kernel32"

Subroutine FreeLibrary(UINT HInstance) Library "kernel32"


위에서 LoadLibraryA()함수는 멀티미디어 Library가 있는지 확인하는 함수.

그런다음 sndPlaySoundA()함수로 .WAV file을 전달한다.

연주가 끝난후에 메모리를 해제하기 위해서 FreeLibrary()함수가 호출된다. 다음의 예제 소스를 f_play라는 함수로 만들어 필요시에 사용하면 편리합니다.


return value는 None이고 Access 속성은 Public입니다.


UINT lu_instance


lu_instance = LoadLibraryA("WINMM.dll")


IF lu_instance = 0 THEN

   sndPlaySoundA(as_wave, 0)

   FreeLibrry(lu_instance)

END IF


return


위의 함수사용은 아주 간단합니다.

as_wave는 Argument로 받는겁니다.

즉 f_play("sound.wav")하면 됩니다.

sndPlaySoundA()함수에서 .WAV file을 지정할때 Path에 들어있는 file이어야 합니다. 그렇지 않으면 Directory도 지정해주어야 합니다.

두번째 Argument는 여러가지가 있습니다.


SND_SYNC       0     sound를 연주하고 return되기 전에 종료한다.

SND_ASYNC      1     sound를 연주하고 연주하는중에 return한다.

SND_NODEFAULT  2     WAV fiel이 발견되지 않았다면 default sound가 연주되지 않는다.

SND_MEMORY     4     file name이 memory안에 있는 image를 가리키고 있다.

SND_LOOP       8     SndPlaySound가 file name에 대해 NULL값을 가지고 호출 될 때까지 연주한다.

SND_NOSTOP    16     현재 sound가 연주중이면 FALSE를 return한다. 지정한 sound file이 발견되지 않으면 함수는 소리를 연주하지 않고 FALSE를 return합니다.

SND_NOSTOP Argument가 지정되어 있고 또 다른 Sound가 현재 연주중인 경우에도 FALSE가 return됩니다.


[Tip32] 

AVI 화일 보기


두개의 API함수를 사용해야 합니다.

micSendStringA()함수, 연주장치에 명령을 내보내는 역할을 합니다.

mdiGetErrorStringA()함수, 처리과정중에 관계된 에러를 보고합니다.

역시 API함수를 사용하기 위해서는 Declare Menu에서 Local External Function을 선언해야 합니다.


Function UINT LoadLibraryA(String as_library) Library "kernel32"

Subroutine FreeLibrary(UINT HInstance) Library "kernel32"

Function Boolean mciGetErrorStringA(long errorStr, ref string buffer,int wlength) Library "WINMM.dll"

Function Long mciSendStringA(ref string command, ref string returnstring,int wlength, UINT wcallback) Library "WINMM.dll"


Script는 좀 깁니다.


string ls_command, ls_buffer, ls_file, ls_path, ls_driver

long ll_error

UINT lu_lib


//멀티미디어 Library가 install되어 있는지를 loadlibraryA함수로 확인  //합니다.

IF lu_lib <> 0 Then

  MessageBox("오류",'멀티미디어 디바이스 라이브러리가 없습니다.', &

              StopSign!)

Else

   FreeLibrary(lu_lib)


 //AVIVideo device에 대한 registry를 찾는다 RegistryGet()함수가 0   // 을 return하면 video playback device를 갖고 있는것이고 -1을 ret   // urn하면 컴퓨터에 비디오 실행을 위한 장치가 구성되어 있지 않다는   // 의미.


   ls_buffer = "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control" + &

               "\MediaResources\MCI\AVIVideo"

   ll_error = RegistryGet(ls_buffer,"Driver",ls_driver

   IF ll_error = -1 Then

      MessageBox('오류','설치된 비디오 드라이브가 없습니다.', StopSign!)

   Else


      //device를 open하기 위해 command string을 작성한다.

      //open하기 위한 file과 AVIVedeo Type, alias를 지정한다.

      //alias는 후에 멀티미디어 함수 호출때 사용하기 위한것이므로

      //아무렇게나 주어도 상관없다.


     ls_buffer = Fill(char(0),255)

     ls_command = "open music.avi type avivideo alias cartoon"

     ll_error = mciSendStringA(ls_command, ls_buffer, 255, &

                                Handle(Parent))

     //device를 open하는 동안 Error가 발생하였으면 ErrorMessage를

     //얻어서 보여준다.

    //정상적으로 open되면 mcisendStringA()함수에게 alias를 사용해      //서 file을 실행하라고 알려준다.


      If ll_error <> 0 Then

         mciGetErrorStringA(ll_error, ls_buffer, 255)

         MessageBox('오류', ls_buffer)

      Else

       //연주가 끝났는지를 확인하기 위해서 Yield()함수를 사용한다.

         Yield()

         ls_command = 'play cartoon notify'

         mciSendStringA(ls_command, ls_buffer,255, &

                        Handle(Parent))

         Yield()

      End If

   End If

End If



window에서 pbm_mmmcinotify event ID를 이용 User Event를 만듭니다.

비디오 파일의 연주가 끝나면 이 이벤트가 구동하게 됩니다.

stript는...


String ls_command, ls_buffer


//device를 닫는다.


ls_command = 'close cartoon'

mciSendStringA(ls_command, ls_buffer, 255, Handle(this))

Yield()


[Tip33] 

MDI frame 작업공간


하나 이상의 컨트롤을 갖는 mdi frame을 custom mdi라고 한다.

MDI frame에 컨트롤을 추가하면 작업 공간을 바꾸어야 한다.

프레임에서 모든 컨트롤들은 일관된 이미지를 주기 위하여 위치와 크기가 정해져야 한다. 따라서 custom MDI frame을만들때는 frame의 resize event에서 mdi_1의 추가된 컨트롤의 위치와 크기를 바꾸어 준다.

처음 custom MDI frame에서 MDI_1의 높이와 넓이는 0이다.

만약 작업 공간이 없으면 Sheets는 열리지만 보이지 않으며, 작업 공간보다 Sheet가 더 크면 잘라져 버린다.

다음의 예처럼 마이크로 헬프의 기능을 대체하기 위해 userobject를 만든다고 하면,mdi의 크기가 조정이 되어야 할 것이다.


1) 먼저 작업공간(mdi_1)의 넓이와 높이를 얻는다.


int li_width, li_height


li_width  = w_genapp_frame.WorkSpaceWidth()

li_height = w_genapp_frame.WorkSpaceHeight()


2) 새로 추가한 control을 제외한 새로운 작업 공간(mdi_1)의 크기를 계산한다.


li_height = li_height - (cb_print.y + cb_print.height)

li_height = li_height - MDI_1.MicroHelpHeight

li_height = li_height + WorkSpaceY()


3) 추가한 Control의 위치를 이동시킨다.


mdi_1.Move (WorkSpaceX(), cb_print.y + cb_print.height)


4) 작업 공간(mdi_1)을 변경한다.


mdi_1.Resixe(li_width, li_height)


[Tip34] 

Application Open Script


//application open event script


environment   env            //환경 설정에 관련된 변수를 정의한다.

string        startupfile     //ini파일을 정의 한다.


/* 시스템 환경 정보를 가져온다. */

IF (GetEnvironment(env) <> 1 ) THEN

    MessageBox("Application Open ", "시스템의 환경을 알수가 없습니다!")

    HALT

END IF


/* 각 시스템의 환경에 따라 적절한 INI파일을 설정한다. */

CHOOSE CASE env.OSType

       CASE Windows!, WindowsNT!

            startupfile = "pb.ini"

       CASE Sol2!, AIX!, OSF1!, HPUX!

            startupfile = ".pb.ini"

       CASE Macintosh!

            startupfile = "PowerBuilder Preferences"

       CASE ELSE

            MessageBox("Error","시스템의 환경을 알 수가 없습니다!")

            HALT

END CHOOSE


/* PB.INI파일로부터 필요한 트랜잭션의 정보를 가져온다. */

SQLCA.DBMS       =ProfileString("PB.INI","Database","DBMS",             " ")

SQLCA.Database   =ProfileString("PB.INI","Database","DataBase",         " ")

SQLCA.LogID      =ProfileString("PB.INI","Database","LogID",            " ")

SQLCA.LogPass    =ProfileString("PB.INI","Database","LogPassword",      " ")

SQLCA.ServerName =ProfileString("PB.INI","Database","ServerName",       " ")

SQLCA.UserID     =ProfileString("PB.INI","Database","UserID",           " ")

SQLCA.DBPass     =ProfileString("PB.INI","Database","DatabasePassword", " ")

SQLCA.Lock       =ProfileString("PB.INI","Database","Lock",             " ")

SQLCA.DbParm     =ProfileString("PB.INI","Database","DbParm",           " ")


/* 데이터베이스에 연결한다. */

connect;


IF SQLCA.SQLCODE <> 0 THEN

   MessageBox("데이타베이스에 연결할 수 없습니다!",sqlca.sqlerrtext)

   RETURN

END IF


/* MDI frame 윈도우를 연다. */

Open(w_genapp_frame) 


[Tip35]

내방식대로의 MicroHelp #1


1) userobject내의 instance 변수를 정의한다.

   Window    iw_parent_window

   Integer   ii_menu_ht = 0

   Boolean   ib_show_clock

   Integer   ii_resizeable_offset


2) userobject용 함수만들고 저장하기

   uf_init(window,boolean): 오브젝트가 놓여 있는 윈도우의 open ev

            ent에서 호출되어야 한다. 첫번째 변수는 윈도우를 등록하고,              두번째는 object에 시각을 나타낼 것인지 아닌지를 결정한다.

            이 함수는 오직 한번만 호출된다.

   uf_resize(): 모 윈도우의 Resize Event에서 호출된다. 이것은 모                    윈도우의 크기나, 바뀔때 나타낼 크기와 위치를 변경한

                다.

   uf_set_clock()        : 모 윈도우의 Timer Event 에서 호출된다.

                           이것은 현재의 시각을 표시한다.

   uf_set_msg(string)    : Object Bar의 왼쪽부터 해당 메시지를 표

                            시한다.

                           Null string은 메시지를 지운다.  


/******************************************************************/

/* uf_init                                                        */

/* scope : public, parameters : aw_win(window), ab_clock_on(boolean)    */

/* return값 : 없음                                              */

/*****************************************************************/


iw_parent_window = aw_win  


//만약 메뉴가 있다면 메뉴의 크기만큼 보장하기 위해서

IF len(iw_parent_window.menuname) > 0 THEN

   ii_menu_ht = 175

ELSE

   ii_menu_ht = 98

END IF


//해당 윈도우가 Resize속성일때..

IF aw_win.resizable THEN

   ii_resizeable_offset = 0

ELSE

   ii_resizeable_offset = 16

END IF


//시각을 표시하기 원하면 초기 시간을 SET한다.

ib_show_clock = ab_clock_on


IF not ib_show_clock THEN

   HIDE(st_clock)

ELSE

   uf_set_clock()

ENDIF


//윈도우의 속성에 맞게 크기를 조정한다. 그리고 사용자를 표시한다.

uf_resized()


st_user.text = '' + gs_userid


/****************************************************************/

/* uf_resized                                                  */

/* scope : public, parameters :없음    return 값 : 없음     */

/***************************************************************/

//uf_init()함수가 실행되지 않았을때이다.

IF ii_memi_ht = 0 THEN RETURN


//user object 의 크기나 위치를 변경할 때 성능을 위하여 hide시킨다.

Hide(This)


//모 윈도우의 크기대로 user object의 크기를 변경한다.

This.Width  = iw_parent_window.Width


//각 Component의 크기를 변경한다.

st_clock.x =  iw_parent_window.Width - (st_clock.Width + 38)

st_login_time.x  =  st_clock.x - (st_login_time.Width + 12)

st_msg.Width     =  st_login_time.x - (st_msg.x + 12)


//모 윈도우의 맨 하단으로 오브젝트를 이동한다.

Move{(This, 1, iw_parent_window.height - (this.height + ii_menu_ht) +   &

     ii_resizeable_offset)}

Show(This)


[Tip36] 

내방식대로의 MicroHelp #2


/******************************************************************/

/* uf_set_clock                                                  */

/* scope : public, parameters :없음    return 값 : 없음       */

/*****************************************************************/


st_login_time.text = string(gd_logindt, "mm/dd") + &

                     " " + string(gd_logindt, "h:mm:ss")

st_clock.text      = string(today(), "mm/dd") + &

                     " " + string(now(), "h:mm:ss")


/*****************************************************************/

/* uf_set_msg                                                   */

/* scope:public,parameters:as_msg(string) return 값 : 없음  */

/*****************************************************************/

st_msg.text =  '' + as_msg

/*****************************************************************/

/* window의 Open Event                                      */

/*****************************************************************/


//Window의 Open Script


int x,y

uo_msg.uf_init(w_main.true)      //user object를 초기화한다.

timer(60)                        //1분마다 timer를 발생시킨다.


x = this.workspacewidth()

y = this.workspaceheight() - 1


mdi_1.move(1,1)            //work space를 초기 위치로 설정한다.


// window의 Timer Event


/*1분마다 user object에 시각을 나타낸다. */

uo_msg.uf_set_clock()

/*****************************************************************/

/* HelpMsg()                                                   */

/* scope :public, parameters : s_color('R':Red(에러,데이타가 없음,경고메시지)         */

/*               'N':Black(성공,ok,information..)             */

/* return 값 : 1:ok, -1 :error or bad arg                    */

/****************************************************************/


IF len(s_msg) < 1 THEN RETURN -1


CHOOSE CASE s_color

       CASE  'R'

             w_main.uo_msg.st_msg.textcolor = 255

             w_main.uo_msg.uf_set_msg(s_msg)

       CASE  'B'

             w_main.uo_msg.st_msg.textcolor = 167

             w_main.uo_msg.uf_set_msg(s_msg)

       CASE  'B'

             w_main.uo_msg.st_msg.textcolor = RGB(0,0,0)

             w_main.uo_msg.uf_set_msg(s_msg)

       CASE ELSE

             RETURN -1

END CHOOSE


RETURN 1



[Tip37] 이용시간 체크하기


//global variable

Time gt_s_time


/* 1login을 하는 해당 event에서 gt_s_time에 접속시간을 넣어준다. */

gt_s_time = Now()



/******************************************************************/

/*f_login_time()만들기, 종료하는시점에서 불러오면 현재까지의 접속한 시간을        */

/*            check해 준다.menu의 exit메뉴에서 호출하면 된다.   */

/******************************************************************/


time  lt_start,  lt_end

long  ll_s_time, ll_e_time, ll_temp

string  ls_hour,   ls_minute, ls_second


lt_start =  gt_s_time     //처음에 login하던 시점의 시간.

lt_end   =  Now()         //현재 접속종료하려하는 시점의 시간.


/* 시작과 종료시간의 차를 계산하기 위해서 모두 초단위로 변환한다. */

ll_s_time = Long(String(lt_end,         "hh"))*3600     +      &

            Long(Right(String(lt_end,   "hh:mm"),2))*60 +      &

            Long(String(lt_end,         "ss"))

ll_e_time = Long(String(lt_start,       "hh"))*3600     +      &

            Long(Right(String(lt_start, "hh:mm"),2))*60 +      &

            Long(String(lt_start,       "ss"))


ll_temp = ll_s_time - ll_e_time


//초단위를 다시 시간, 분, 초단위의 time형식으로 변환한다.

ls_hour   = String(Long(ll_temp/3600))    // 시간

ll_temp   = Mod(ll_temp, 3600)            // 시간단위변환후의 나

                                               //머지값

ls_minute = String(Long(ll_temp/60))      // 분                 

ll_temp   = Mod(ll_temp, 60)              // 분단위변환후의

                                              //나머지값  

ls_second = String(ll_temp)               // 초                   

// 종료시에 사용시간을 뿌려준다.

MessageBox("알림", "이용 하신 시간은 " + ls_hour   +  " 시간 " +    &

                    ls_minute + " 분 " + ls_second +  " 초 입니다!  &

                    이용해 주셔서 감사합니다!")



[Tip38] 

라이브러리 최적화


Library는 2GB까지 확장할 수 있다. 그러나 Library가 너무 크면, 시스템의 디스크 접근 시간이 그 라이브러리를 읽는 데 많은 영향을 주게 된다. Library가 더 크게 되면, 더욱이 라이브러리 처리 엔진이 특정 OBJECTS에 대한 처리 요구를 수행하기 위해 큰 Library전체를 검색해야 할 필요가 생긴다.


Library의 엔트리수는 50~60이 적당하다.

라이브러리가 너무 많은 엔트리, 즉 오브젝트들을 갖고 있으면 라이브러리 페인터에서 엔트리가 열릴때의 작업이 불편하고 개발자 역시 라이브러리를 운영 유지하거나 관리하기가 더 어려워진다.


Library의 수와 양의 딜레마를 적절하게 조절한다.

라이브러리가 너무 크던가 너무 난잡하게 되는 것을 원하지 않는 곳과 마찬가지로, 너무 적은 OBJECT를 갖는 라이브러리도 좋지 않다.

경우에 따라서, 어떤 라이브러리는 1~2M의 크기가 될 수도 있을 것이다.

앞에서 말한 라이브러리의 크기와 그 오브젝트의 양에 대해서는 상호배타적인 관계를 가지고 있으므로 이러한 두 가지의 관계 사이에서 융통성을 가지고 운영하면 될 것이다.


어플리케이션을 개발하고 있는 프로젝트중, 많은 경우에 라이브러리의 수가 19개를 넘어갔을때에 문제가 종종 발생하는 경험을 한다.

그러나 라이브러리의 크기가 커서 문제가 되는 경우는 거의 없다.

이는 단지 속도 등의 성능에 관련된 문제이기 때문이다.


수시로 Library를 최적화(Optimize)한다.

개발을 진행하는 동안 라이브러리는 내부에 비활용 공간을 가지게 된다.

개발을 하는 중간중간에 라이브러리를 optimize해 줄 필요가 있다.

파워빌더가 관리하는 라이브러리의 내부 구조는 HDD와 비슷해서 어느 오브젝트의 크기가 커지면, 다른 위치에 연결된 구조를 가지게 되고 때론 사용하지 않는 조각들이 많이 존재하게 된다.

라이브러리 페인터에는 Optimize라는 유틸리티가 있다.

Optimize를 이용하여 라이브러리의 구조에 대한 최적화를 한다.

파워소프트사는 일주일에 한번 정도의 최적화를 권장하고 있다.

이런 최적화 작업은 단지 라이브러리의 내부 구조에 대한 작업일 뿐, 그 내용에 대해서는 아무런 영향을 미치지 않는다.

또한 개발이 종료되거나 테스트를 하기 전에는 반드시 PBD 파일이나 DLL, EXE파일을 만들기 전에 라이브러리를 최적화 해 주는 것이 좋다.


[Tip39] 

SQLDBCode의 사용


데이타 베이스를 CONNECT하고자 할때 비록 CONNECT에 실패해도 SQLCODE는 0을 리턴할때가 있다.

에러 메세지는 첫번째의 Data Retrieve 또는 Update를 수행할때 반드시 SetTrans 또는 SetTransObject를 기술하여야 한다는 것을 나타낸다.

그러므로 Connect가 발생하면 SQLCode 보다는 SQLDBCode를 체크해야 한다.

만일 SQLDBCode 가 0 이 아니면 connect되지 않았다는 것이다.


Connect Using SQLCA;


IF SQLCA.SQLDBCode <> 0 OR SQLCA.SQLCode <> 0 THEN....


FIELD NAME

RETURN VALUE

SQLCode

0    : 성공

100  : 발견되지 않음

-1   : sql error

SQLNRows

영향 미치는 row의 수

SQLDBcode

데이타베이스가 부여하는 에러코드

SQLErrText

데이타베이스가 부여하는 정보

단, DBMS가 SQLCA 트랜젝션 오브젝트가 생략되었다면 SQLDBCode나 SQLCode가 리턴되지 않는다.

DBMS가 확인되어지지 않았기 때문에, SQLDBCode 나 SQLErrText 에 대한

데이타베이스가 그것을 만들수 없게 된다.



[Tip40] 

DataWindow와 연결되는 4개의 버퍼


◈ Primary Buffer :

   삭제나 여과가 안된 데이타를 포함한다.

   데이타는 Retrieve와 InsertRow함수를 통하여 이 버퍼에 배치된다.

   일단 Primary 버퍼에 포함된 데이타는 사용자에게 가시적이다.


◈ Filter Buffer  :

   Filter 함수를 통하여 실행시에 여과되는 데이타를 포함한다.


◈ Deleted Buffer :

  데이타베이스에서는 아직 삭제되지 않았지만 DataWindow에서 삭제된   데이타를 포함한다. 데이타는 DeleteRow함수를 통하여 이 버퍼에 배치

 된다.


◈ Original Buffer:

  Update나 Delete문의 WHERE 절을 발생시키기 위하여 사용될 데이

 타를 포함한다.

  SQL UPDATE와 INSERT는 Primary and/or filtered 버퍼에 있는   데이타를 근거로하여 생성된다.

  데이타윈도우가 갱신능력을 갖지 않으면, Deleted 나 Original 버퍼가    유지되지 않고, 그리고 어떤 SQL문도 Update함수에 의해 발생되지 않

 는다.


출처 : 주호의파워빌더