Computer/PowerBuilder

파워빌더 TIP 57

ICARUS㈜ 2009. 3. 19. 17:12
반응형


[TIP1] 데이터윈도우의 컬럼 헤더 텍스트를 클릭하면 해당 컬럼을 정렬하도록 하는 Clicked 이벤트 스크립트
string ls_name

IF dwo.Type = "text" THEN
dwo.Color = RGB(255,0,0)
ls_name = dwo.Name
ls_name = Left(ls_name, Len(ls_name) - 2)
This.SetSort(ls_name + ", A")
This.Sort()
END IF

// code라는 특정한 컬럼만 정렬되도록 지정 할 경우

string ls_name

IF dwo.Type = "text" AND dwo.Name = "code" THEN
dwo.Color = RGB(255,0,0)
ls_name = dwo.Name
ls_name = Left(ls_name, Len(ls_name) - 2)
This.SetSort(ls_name + ", A")
This.Sort()
END IF


[TIP2]
Datawindow에서 다중 테이블의 죠인에 의한 Multi Table Update를 가능하게 하는 방법

몇개의 Table을 Join하여 Datawindow를 만들었을때 Update characteristic에서하나의 Table에 대해서만 선택이 가능하며 다른 Table의 칼럼을 선택하여 OK를 누르면 선택된 Table의 칼럼이 아니라는 Error가 뜬다. 그러나 Update를 원하는 모든칼럼을 선택하고 여러 Table의 Key column을 선택할 수 있는 방법이 있다.

위와같이 필요한 모든 칼럼과 원하는 Option을 선택한뒤 Table to Update 필드에 임의 테이블명을 쓰고 (예:TEMP) OK 버튼을 누르면 Table 'TEMP' not found. Use table name anyway ? 라는 선택 메세지박스가 나타나는데 확인을 누르면 모든게 OK
이 방법은 물론 Update() 함수를 날릴때 Sqlsyntex를 확인해보면 알겠지만 Update나 Insert SQL문이 TEMP라는 Table로 날아감으로 Not Found Table이라는 Error가
발생할것이므로 미리 Modify("Datawindow.Table.UpdateTable = {table명}") 과
Modify("column명..Update = {yes/no}")을 해야하는 수고가 필요한
것은 같으나 선택한 column에 대한 Itemstatus를 내부적으로 Checking 하고있는것이 다르다.
보통 이렇게 쓰지않고 직접 SQL문장을 몇번씩 날리거나 보다 편하게 Stored Procedure를 사용하는게 속편하고 행복한(?) 방법이다.

[TIP3]
Funtion에서..

funtion을 만들때 Argument에 대한 특정한 Type을 지정을 하는데 선택할 수 있는 List에는 없지만 "dragobjct" 와 "blob"를 그냥 typing하여 사용할 수 있다.
특별히 쓰는곳이 없으신 분들이야 필요없겠지만 언젠가 필요할때 기억해두면 유용할것이다.

[TIP4]
Datawindow에서 Enter Key를 DoubleClick 또는 필요한 Event화 하기.

Datawindow에서 User Event를 pbm_dwnprocessenter로 Mapping하여 만든뒤 아래와 같은 script를 쓴다.
This.Postevent(doubleclicked!) This.Setactioncode(1)

다음 script를 쓰면 Enter Key가 Tab Key와 같은 역할을 하게되죠
send(handle(this),256,9,long(0,0)) this.setactioncode(1)

[Tip5]
FreeForm DW를 만들때 Design 높이를 정해놓기.

칼럼의 수가 몇개 되지 않는 데이타윈도우일때는 별문제없겠지만 상당한 수의 칼럼을 FreeForm Presentation Style로 Design할때에 column들이 아래로 계속내려가 있어 다시 정렬시에는 매우 번거러울 것이다.
그래서 새로운 Datawindow를 만드는 화면의 Option을 누르면 Wrap Height가 보이는데 이것을 적당한 크기로 선택하면 DW의 Detail의 높이를 한정할 수 있다.

[TIP6]
DataBase의 행동과 소요시간을 추적해 보기

Profile 을 setup할때 DBMS 부문 앞에 trace를 써주면 (예: trace OR7 ORACLE v7.x) Database의 traffic과 action들을 pbtrace.log 파일로 쌓아놓게 된다.

[TIP7]
DataWindow Error Message Title의 변경

데이타 윈도우에 Error가 발생하면 자체의 Error MessageBox가 뜨는데 Title이 항상 똑같아 보기에 별로다. 그래서 필요한 곳에 한번 아래와 같이 써주면 된다. dw_1.Modify("Datawindow.Message.title ='"+parent.title+"'") 물론 타이틀이야 본인이 원하는대로 하시길...

[TIP8]
가장 간단한 윤년 확인

ls_year = 원하는 년도
IF Isdate(ls_year + "/2/29") THEN
Messagebox("",'윤년이네 !')
END IF

[TIP9]
Window의 Timer Event를 이용하지 않은 시계만들기

1. Datawindow Object의 Attribute 중 Timer interval은 내부적으로
Timer를 발생시킨다. 이 Option을 1000으로하여 1초마다 변화를 발
생하게한뒤 Now() 함수를 쓴 Computed field를 만들어 Header에
위치시키면 시계가 된다. 물론 이 Datawindow는 Dummy column
한개의 External Datawindow로 만들면 될것이다.

2. 이러한 Timer interval을 응용하여 어떤 특정한 칼럼이나 Row를 깜
박이게 하거나 색을 반복적으로 변화시킬 수 있다.
원하는 Column의 Attribute 중 Visible에 다음과 같이 쓰거나
if(mod(integer(Mid(string(Now()),7,2)),2) = 1, 0 , 1)
Attribute 중 color에 아래와 같이 쓰면 된다
if(mod(integer(Mid(string(Now()),7,2)),2) = 1 , 0 , 255)

[TIP10]
특정한 각도의 Text 만들기

Datawindow에서 특정한 StaticText Object를 일정한 각도로 놓아
지게할려면 Attribute 중 Font Escapement에 필요한 수를 쓴다. 수
직으로 할려면 900 이다.

[TIP11]
고정된 Data를 Datawindow내부에 갖고 있기

데이타윈도우 Design 메뉴중 Rows/data 에 필요한 것을 써넣음. Retrieve가 필요없이 화면에 Data가 나타나므로 Design시 유용하다.

[TIP12]
Update된 특정 Row의 칼럼만 다시 Retrieve 하기

간단하다. Reselectrow() 함수를 사용하면 됨.
보통 모든 칼럼대비 10% 미만의 변화가 있을때 사용함이 유용하며 그 이상일때는 완전한 Retrieve() 함수의 사용이 보다 빠르다.

[Tip13]
Path 지정

PowerBuilder로 만든 프로그램을 설치할때 필요한 run-time DLLs를 위해 Path를 설정 해야한다.
autoexec.bat file에서 Path문장으로 설정할 수도 있지만 Win95의 registry를 이용할 수도 있다.

'시작'의 '실행'에서 regedit를 하면 registry를 편집할 수 있다.
'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Apths'

항목을 선택하면 모든 실행화일 이름들이 나열되어있다.
거기에는 'Path'라는 값이 있고 거기에 실행파일에 필요한 path가 기재되어있다. 마우스 오른쪽버튼으로 path를 수정할 수 있다.
예를 들어 'myprog.exe'라는 프로그램을 설치한다면 App Paths밑에 'myprog.exe'라는 key를 만들고 Path값으로 'c\Program Files\Common Files\PowersoftShared;c:\SQLANY50\win32'하면 Windows는 실행파일에 필요한 내용을 찾을 수 있게된다.

[Tip14]
pbm_syscommand Event

PowerBuilder 프로그래밍을 하다보면 response window를 많이 사용하게 된다.
response window에서는 'OK'라던가 'Cancel' button을 사용하게 되는데 사용자가 이 버튼만 사용한다고 말할 순 없다.
윈도우의 오른쪽 상단 구석에 보면 'x'표시가 있다.
마우스로 이걸 누르게 되면 윈도우는 close된다.
우리가 response window를 사용할때 main window와의 message 전달 수단으로 CloseWithReturn 함수를 사용한다.
따라서 main window는 responce window가 close되면 message객체에서 message를 참조하려 할것이고 사용자는 'OK'또는 'Cancel' 버튼으로 종료하지 않았으므로 message는 null값이 전달된다.
즉 실행중에 'null object referenced' error message가 발생한다.
이를 해결하기 위한 script로서는 pbm_syscommand로 User Event를 선언한다. 그런다음에 user event에 다음의 script를 기술한다.
ue_cancel은 cancel 버튼을 눌렀을 때 수행하는 내용과 같다.
그럼 ue_cancel에서 적당한 message를 전달하게 script를 작성하면 끝난다.

IF message.wordparm = 61536 THEN
this.TriggerEvent("ue_cancel")
END IF

[Tip15]
LookUpDisplay(column)

Drop-Down DataWindow 또는 code table인 column을 sort key로 사용하는 경우
LookUpDisplay 함수를 사용한다.
DataWindow Painter에서 메뉴의 Rows - Sort를 선택한다.
'Specify Sort Columns' window에서 column을 drag하여 선택한다.
선택한 drop-down DataWindow 또는 code table edit style인 column을 더블 클릭하면 'Modify Expression' window가 뜬다.
그곳에서 column을 인자로 취하는 LookUpDisplay()함수를 사용한다.

예)
Specify Sort Columns
1) Drag and Drop items
2) Double clik column to edit

Source Data Columns Ascendin
emp_id LookUpDisplay(dept_id) *
dept_id
emp_fnam
emp_lname

그러면 dept_id code로 sort되지않고 dept_id의 display되는 값이 sort 기준이 된다.

[Tip16]
SetMicroHelp

마우스를 어떤 control위로 이동시키면 microhelp message가 뿌려지는
프로그램을 본 일이 있을것이다.
이걸 PowerBuilder에서 구현하기 위한 Tip이다.
표준 Window event로써 pbm_nchittest가 있다.
이건 마우스가 어떤 control로 이동하면 자동으로 발생하는 event다.
예를 들어서 command button을 만들었다면 사용자가 마우스를 command button위로 이동시켰을 때 pbm_nchittest event가 발생한다.
그럼 우리는 pbm_nchittest event를 commnand button의 user event로 만들고 microhelp message를 뿌리는 script를 작성하면 끝이다.
message는 control의 properties... 의 Tag에 기술하고 pbm_nchittest
event의 script로서 다음과 같이 기술하면 된다.

g_w_frame.SetMicroHelp(this.tag)

g_w_frame은 MDI frame window를 가리키고 있다.
Tag가 싫다면 직접 string을 줘도 된다.
script는 여러분의 application에 맞게 기술하면된다.
user event만드는 방법은 해당 control의 script에 들어가서
메뉴의 Declare항목 User Events...를 선택하면 된다.
그럼 Events - controlname 이라는 window가 뜨고 거기에서 Event Name은 적당한 이름을 준다.
가능하면 we_nchittest라고 주는 것을 권장한다.
그리고 Event ID는 pbm_nchittest라고 주면 된다.
또는 Paste Event ID:라는 ListBox에서 찾아보면 있다.
더블클릭하면 해당 event id가 Event ID에 등록된다.

[Tip17]
자동 Row 삽입

DataWindow에서 사용자가 맨 마지막 row 마지막 column에서 Tab Key를 누르면 자동으로 새로운 row를 insert해주는 Tip을 소개하겠다.

먼저 DataWindow에서 User Event를 선언하자.
선언할 User Event는 ue_tabout이라고 하고 사용할 User Event는 pbm_dwntabout이다.
이 Event는 사용자가 tab key를 입력해서 DataWindow를 벗어날때 발생하는 event다.
따라서 focus는 DataWindow가 아닌 다른곳으로 이동하게 되는 순간에 발생한다.
ue_tabout script는 다음처럼 기술하면 된다.

long l_row

l_row = this.InsertRow(0)
this.SetRow(l_row)

//DataWindow가 horizontal scroll bar를 갖도록 property를 설정한 경우.
this.Modify("datawindow.HorizontalScrollPosition = 1")

this.SetColumn("first_column")
this.SetFocus()

[Tip18]
문자를 반짝이게(blink 기능)

DataWindow에서 display되는 문자를 반짝이게 하는 Tip이다.
문자를 반짝이게 하기 위해서는 DataWindow의 property중에서 timer inteval을 적당한 값으로 setting시켜야 한다.
10정도로 잡으면 된다. 그런다음 column이나 기타 control을 만들고 control의 property에서 Expressions의 visible에 다음 코드를 작성한다.
if(mod(Integer(Mid(String(Now(),),7,2)),2) = 1,0,1)

색변화를 주기위해서는 color에 다음 코드를 작성한다.
if(mod(Integer(Mid(String(Now(),),7,2)),2) = 1,0,255)

이렇게 하면 1초를 주기로 반짝이게 된다.
Now()는 현재 시간이고 Mid()로 초를 잘라내어 2로 나눈 나머지 값의 변화로 반짝이게 하는 것이다.
timer interval이 0으로 되어있으면 Now()는 처음 retrieve된 시간을 유지하게 되므로 반드시 timer interval을 줘야 한다.

[Tip19]
ScrollToFirstRowOnPage

User가 DataWindow의 scrollbar를 눌러 scroll시킬때 row에 대한 highlight도 이동되도록 하는 Tip이다.
highlight되는 row는 현재 보여지는 page의 첫번째 row가 되도록 한다.
vertical scrollbar를 누르면 ScrollVertical event가 발생한다.
따라서 ScrollVertical Event에서 Describe()함수로 현재 보여지는 page의 첫번째 row의 값을 구해서 highlight를 설정하면 된다.

integer li_RC
long ll_row
string ls_result

// 현재 Page의 첫번째 row를 구한다.
ls_result = this.describe ( "DataWindow.FirstRowOnPage" )
if ls_result = "!" then
MessageBox ( "Error", "Describe failed (1)" )
halt close
end if

ll_row = long ( ls_result )

if ll_row < 1 then
MessageBox ( "Error", "Describe failed (1)" )
halt close
end if

// ScrollToRow함수로 상단의 row를 current row로 설정한다.
li_RC = this.ScrollToRow ( ll_row )
if li_RC <> 1 then
MessageBox ( "Error", "ScrollToRow failed" )
halt close
end if

[Tip20]
Text at Any Angle

이번은 아주 간단한 Tip이다.
DataWindow에서 static text또는 column등의 property에서 expressions list를 보면 font.escapement가 있다.
font.excapement property는 font가 display되는 각도를 지정하는 것이다. 즉 글자를 90도로 기울이고자 할때는 값을 900으로 맞춰주면 된다.
지정해주는 값은 각도*10을 하면된다. 즉 90도*10 = 900이다.
font.excapement를 지정함으로써 어떤 각도로든지 글자의 Angle을 조절할 수 있다.
font.excapement를 지정한 결과의 display형태는 DataWindow Painter에서 preview해보면 알 수 있다.

[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함수에 의해 발생되지 않
는다.

[Tip41]
Row와 Column의 Status

◈ DataWindow의 Row나 Column의 상태
New! (row만)
NewModified! (row만)
NewModified! (row와 column)
DataModified! (row와 column)

◈ row 검색
row가 datawindow에 retrieve되면 NotModified!라는 row의 상태 가 되며 모든 column은 NotModified! 라는 column상태가 된다.

◈ retrieve row의 수정
column이 사용자 엔트리에 의하던지 SetItem함수에 의해 변경되면 그 column의 상태가 DataModified!로 변경되고 그 column이 존재하는 Row상태는 DataModified!로 변경된다.

◈ Row삽입
datawindow에 row를 삽입할때 row는 New!라는 상태를 가지며 row의 모든 column은 NotModified!라는 Column상태를 가진다. 삽입된 row안의 칼럼이 갱신된 후 사용자가 SetItem함수 또는 기본값으로 입력하던지 칼럼상태가 DataModified!로 변경될 때 그 Row의 상태는 NewModified!로 변경된다.

◈ sql문 생성
row의 상태
발생 명령문
NewModified!
Insert
DataModified!
Update


◈ DwSetItemStatus
datawindow의 row나 column의 상태를 변경하기 위해서 사용하는 함수

◈ DwGetItemStatus
datawindow의 row나 column의 현재상태를 알아보기 위해서 사용하 는 함수

[Tip42]
Return Value들..

◈ sqlcode에서
0 ok
-1 error
100 no data

◈ update에서
1 update함수 사용
-1 update함수 실패
dberror event발생

◈ retrieve에서
1 retrieve함수 성공
-1 retrieve함수 실패
dberror event발생
0 no data
dberror event발생하지 않음

◈ 4.0의 SetActionCode-->5.0의 return value
SetActionCode(0) ----> return 0 ----> 계속 실행
SetActionCode(1) ----> return 1 ----> 진행 멈춤
SetActionCode(2) ----> return 2 ----> 현재의 요청을 무시하고
다음의 요청을 실행

[Tip43]
파워빌더에서 전화걸기

Declare 밑의 Local External Function에 다음과 같이 Script한다.

Function int OpenComm (string lpComName, uint wInQueue, uint wOutQueue) Librar
y "user.exe"

Function int CloseComm (int nCid) Library "user.exe"
Function int WriteComm (int nCid, string lpBuf, int nsize) Library "user.exe"

Function int FlushComm (int nCid, int nQueue) Library "user.exe"

Button Script 에 아래와 같이 Scriptd한다.

int li_comid
int li_rc

String ls_port // Poer No.
String ls_buffer /* 전화 번호(에를 들어 Pre Dial 이 있다면
9,01410 이런식으로*/

li_comid = OpenComm(ls_port,128,128)
If li_comid < 0 Then
If li_comid = -2 Then
Messagebox("Port: "+ ls_port + " In - Use","Error")
Else
Messagebox("Port: " + ls_port, "Error")
End If
Return -1
End If

Flushcomm(li_comid,0)

Flushcomm(li_comid,1)
li_rc = WriteComm(li_comid, ls_buffer, len(ls_buffer))

MessageBox("Dialing " + Left(ls_buffer,len(ls_buffer) - 2),"Hit Ok to hang up modem")
CloseComm(li_comid)
return 1

[Tip44]
Null 값 핸들링시에

이번 Tip은 DataWindow Painter에서 NULL값을 처리하는 작은 트릭입니다. 일반적으로 '급여:' + String(salary) 라는 computed field를 사용할 수 있겠는데 만약 salary column의 값이 NULL인 경우 field또한 NULL값을 취하게 되어 아무런 값도 나타나지 않게 됩니다.
때문에 대개의 경우 IF문을 사용하지만 여기서는 String함수를 그대로 이용하는 방법을 소개합니다.
String함수의 mask는 number를 보기좋게 변환시켜주는 역할을 합니다.
mask는 number의 4가지 양태를 각각 지정할 수 있습니다.
즉 음수,양수,0,NULL 각각에 해당하는 mask를 semicolon으로 구분하여 별도로 지정합니다.
생략하는 경우 맨앞에 나타나는 mask로 처리됩니다.
'급여:' + string(salary, "#,##0; ; ; '(unknown)'")
위와같이 computed field를 사용하면 4번째 mask가 NULL인 경우에 적용됩니다. 즉 NULL인경우 '급여:(unknown)'이라고 나타나게 됩니다.
2번째,3번째는 생략되었기 때문에 1번째 mask 적용을 받게 됩니다.
이상의 설명은 String함수의 첫번째 Parameter data type이 number인 경우에 적용되는 팁이었습니다.

[Tip45]
시스템환경을 체크해보자(cpu)

/* Declare */
Environment env
INTEGER resp

/* Variable Setting */
resp = GetEnvironment(env) //시스템 환경을 읽어오는 함수

CHOOSE CASE env.cputype //CPU의 TYPE(파워빌더는 14 //개 인식)
CASE alpha!
sle_1.text = 'Alpha'
CASE hppa!
sle_1.text = 'HPPA'
CASE i286!
sle_1.text = '286'
CASE i386!
sle_1.text = '386'
CASE i486!
sle_1.text = '486'
CASE m68000!
sle_1.text = '68000'
CASE m68020!
sle_1.text = '68020'
CASE m68030!
sle_1.text = '68030'
CASE m68040!
sle_1.text = '68040'
CASE mips!
sle_1.text = 'MIPS'
CASE pentium!
sle_1.text = 'Pentium'
CASE powerpc!
sle_1.text = 'PowerPC'
CASE rs6000!
sle_1.text = 'RS6000'
CASE sparc!
sle_1.text = 'Sparc'
END CHOOSE

 

[Tip46]
시스템환경을 체크해보자(os)

/* Declare */
Environment env
INTEGER resp

/* Variable Setting */
resp = GetEnvironment(env) //시스템 환경을 읽어오는 함수

CHOOSE CASE env.OSType //OS TYPE(파워빌더는 7개 //인식)
CASE aix!
sle_1.Text = 'AIX'
CASE hpux!
sle_1.Text = 'HPUX'
CASE macintosh!
sle_1.Text = 'MacIntosh'
CASE osf1!
sle_1.Text = 'OSF1'
CASE sol2!
sle_1.Text = 'Solaris 2'
CASE Windows!
sle_1.Text = 'Windows'
CASE Windowsnt!
sle_1.Text = 'Windows NT'
END CHOOSE

sle_2.Text = Trim(Sle_1.Text) + ' ' + STRING(env.osmajorrevision) + &
'.' + STRING(env.osminorrevision) + &
'.' + STRING(env.osfixesrevision)

[Tip47]
PowerBuilder의 버전은?

/* Declare */
Environment env
INTEGER resp

/* Variable Setting */
resp = GetEnvironment(env) //시스템 환경을 읽어오는 함수

CHOOSE CASE env.pbtype //현재 인스톨된 파워빌더의 type
CASE enterprise!
sle_1.Text = 'Enterprise'
CASE desktop!
sle_1.Text = 'Desktop'
END CHOOSE

IF env.win16 THEN
sle_2.Text = Trim(sle_1.Text) + "/16" + ' ' + &
STRING(env.pbmajorrevision) + '.' + &
STRING(env.pbminorrevision) + '.' + &
STRING(env.pbfixesrevision)
ELSE
sle_2.Text = Trim(sle_1.Text) + "/32" + ' ' + &
STRING(env.pbmajorrevision) + '.' + &
STRING(env.pbminorrevision) + '.' + &
STRING(env.pbfixesrevision)
END IF


[Tip48]
Application의 환경

IF env.machinecode THEN //어플리케이션의 실행코드
sle_1.Text = "Machine Code"
ELSE
sle_1.Text = "P-Code"
END IF

st_colors.text =string(env.numberofcolors) //화면의 색상
st_height.text = string(env.screenheight) //화면의 높이
st_width.text = string(env.screenwidth) //화면의 너비

[Tip49]
DataWindow에러 메시지의 변경

DataWindow에서 값을 입력하다보면 'DataWinodw Error'라는 Title의 Message Box가 나타납니다.
영어로 쓰여져있고 일반 End User들에게는 생소한 영문이라서 눈에 거슬렸는데 이 title을 원하시는 내용으로 변경하는 방법이 있습니다.
DataWindow attribute에는 Message.Title이란 것이 있습니다.
이부분을 수정하면 됩니다.
dw_1.Modify("DataWindow.Mesage.Title = '입력오류'")

[Tip50]
Dynamic DW Object and Compile...

DataWindow control 이나 DataStore의 Object을 실행시에 Dynamic하게 변경시킬 수 있다.
이런 프로그램은 실행파일을 만들때 PBR(PowerBuilder Resource) file에 DataWindow Object name을 기술해줘야 한다.
DW Object을 dynamic하게 변동시키면 PowerBuilder에서는 문제없이 잘 실행되지만 실행파일을 만들어 보면 에러가 발생한다.
다음과 같이 dynamic하게 하는 경우는 반드시 PBR file을 작성해야한다.

dw_1.dataobject = 'd_employee_maint'

PBR file이 필요한 이유는 DataWindow object 이름이 인용부호로 둘러싸여 있기 때문에 compiler가 실행파일 만들때 DataWindow object이라고 인식하지 못한다. 따라서 DataWindow Object으로 포함시키지 않게된다. 그렇기 때문에 반드시 PBR file에 다음과 같은 내용을 기술해주어야한다. employee.pbl(d_employee_maint) PBR file은 매모장같은 곳에서 작성하여 .pbr 확장자로 저장시키면된다.
그런다음에 실행파일을 만드는 Project painter에서 PBR file 이름을
포함시켜 주면 된다.

[Tip51]
Powersoft사의 21세기 date

PowerBuilder 5.0과 InfoMaker 5.0이외에도 다음과 같이 Powersoft사가 이전에 발표한 버전의 제품에서도 21세기를 대비한 Date가 지원된다. PowerBuilder 3.0 이상의 PowerBuilder 제품 InfoMaker 4.0 이상의 InfoMaker 제품이 PowerBuilder과 동일하게 지원한다.
PowerMaker 3.0과 PowerViewer 3.0에서는 PowerBuilder 3.0과 동일하게 Date를 지원한다.
Date형과 DateTime형의 데이터는 입력과 디스플레이 용도의 모든 형식으로 지원되며 Date의 진행도 적확히 파악되어 2000년 이상의 데이터까지도 표현 가능하다.
Date는 PowerBuilder내에서 Format과 Edit Mask 등 , 여러 형식으로 표현할 수 있다.
특히 Date는 MM , DD , YY를 1에서 4까지의 자릿수를 이용하여 4자리 , 6자리 , 8자리등의 어떠한 조합으로도 구성할 수 있다.
이것은 개발자가 어플리케이션에 대해서 사전에 결정해야 할 사항이다.

* 2자리의 년도
년도가 2자리로 표시되면 PowerBuilder와 InfoMaker는 다음과 같이
세기를 선택한다

구간
PowerBuilder와 InfoMaker의 가정
00 과 49사이
첫 두자리를 20으로 가정
50 과 99사이
첫 두자리를 19으로 가정

만일 출생일 등의 1950년 이전의 데이터가 있다면 항상 그것을 4자리의 년도로 정의하여 PowerBuilder와 InfoMaker가 올바르게 인식할 수 있도록 유의해야 한다.
* Date의 유효 범위
PowerBuilder와 InfoMaker는 1000 ~ 3000년의 년도를 지원한다.

* 윤년의 처리
PowerBuilder와 InfoMaker가 윤년에 대해서 사용하는 알고리즘은 다음과 같다. 년도는 4로 나뉘어져야 하며 만약 100으로도 나뉘어진다면 400으로도 나뉘어져야 한다.
그러므로 1900년은 윤년이 아니고 2000년은 윤년이다.

* S-Designor의 경우
S-Designor의 경우 Date의 처리는 상관없다.
Date의 형식은 개발자가 Domain과 Data Element를 모두 정의하므 로 원하는 형태로 표시된다. 툴에 의해 취급되는 데이터는 없다.

[Tip52]
DropDownListBox의 Reset

DropDownListBox 가 처음 선택하지 않은 상태로 하려면?
Allow Editing 이 가능할 경우에는 ddlb_1.text = "" 로 하면 초기화가 되지만 Allow Editing 이 불가능할 경우에는 ddlb_1.text = "" 로 해도 초기화가 되지 않는다. 이럴 경우에는 SelectItem() 함수를 사용한다.
ddlb_1.SelectItem(0) 하면 초기에 선택되지 않은 상태로 초기화 할 수 있다.

[Tip53]
Column의 중복제거

다음과 같이 자료가 display되는 경우 대분류의 중복 자료를 없애고자 한다.

대분류 중분류 소분류
1 12 23
1 22 33
1 23 43
2 24 53
2 25 63
2 26 73

이런 경우 대개 Rows의 Supress Repeating Values를 사용하지만
다음과 같은 경우는 사용할수가 없다.

대분류 중분류 소분류
12 23
22 33
1 23 43
24 53
25 63
2 26 73

이때는 Column Object의 Expressions의 Visible 속성에
다음과 같이 서술하면 된다.

if (a[0] = a[1] ,0,1)

물론 위의 경우도 Supress Repeating Values를 사용하지 않고
if (a[0] = a[-1] ,0,1)이라 서술하면 된다.

[Tip54]
DW list에서 현재 row의 표시

datawindow로 만든 리스트에서 현재 row를 표시하는 방법은 여러가지가 있지요. 가장 많이 쓰는 방법은 RowFocusChanged 이벤트에서

Selectrow(0,false)
SelectRow(GetRow(),true)

라고 적는 방법이 있죠.
위와 같이 하면 키보드의 위화살표,아래화살표로 리스트를 이동할 때
현재 row가 반전되서 나옵니다.
그런데 dw로 만든 list에서 중복선택할 경우는 현재 row를 표현한다는게
어렵지요. 선택한 row를 반전시켜야 하는데, 현재 row를 반전시킬 수도 없고 이럴때
dw_employee.SetRowFocusIndicator()
를 사용하는 것입니다.
dw_employee.SetRowFocusIndicator(FocusRect!)
라고 미리 정해 놓으면 현재 row는 점선사각형으로 표현되죠.
그리고 선택한 row는 반전시키면 되고.

[Tip55]
DW에서 like사용시 null컬럼 나오게

table test 의 컬럼이
empno,name,deptname,address,phone
(사번,이름,부서명,주소,전화번호)
라고 할때 datawindow를 디자인할때 주소로 검색할 경우
retrieve argument를 r_address 라고 할때

select empno,name,deptname,address,phone
from test
where address = :r_address;

로 정의한다.
그런데, 전체를 검색하기 위해 r_address 의 값을 '%'로 주었을 경우
address 컬럼이 null인 경우는 가져오지 못한다.
이럴 경우 다음과 같이 한다.

select empno,name,deptname,address,phone
from test
where ((address = :r_address) or
(address is null and :r_address = '%'))

[Tip56]
2000년 문제

2000년 문제를 다루는 Tip으로서 그리 대단한건 아니지만 알면 도움될거 같아서 언급한다.
PowerBuilder의 DataWindow에서 Data를 Retrieve한다음에 메뉴의 SaveAs에서 일반 Text file로 저장하면 PowerBuilder은 Date또는 DateTime의 날짜부분을 yy/mm/dd 형태로 저장한다.
이렇게 저장된 파일을 import받으면 년수가 50보다 작을때 20xx가된다.
즉 2000년을 기본으로 계산한다. 50보다 크면 19xx가된다.
하지만 이런식의 저장과 import를 막기위한 방법이 있다.
PowerBuilder이 사용하는 yy/mm/dd의 기본형은 Windows95에서 참조하여 사용한다. 따라서 Windows95의 날짜 기본형을 정정하면 해결된다. 정정하는 방법은 Windows95의 제어판에서 국가별 설정을 선택한다.
그러면 국가별 설정 등록 정보의 날짜를 선택하여 간단하게 표기의 형식을
yyyy/mm/dd로 수정해주면 된다. 그러면 항상 PowerBuilder에서 그 형식을 기본형으로 사용하게되어 2000년 형식에서 오는 문제를 해결할 수 있다.

[Tip57]
DDDW(DropDownDataWindow) Trick

Parent DataWindow가 retrieve되는 시점에 DropDown DataWindow(dddw)의 모든 내용이 retrieve된다.
어떤 사람이 parent datawindow의 clicked event에서 dddw.retrieve()를 하면된다는 그러한 솔루션을 제공한적이 있었다. 그것이 틀리다고 볼수는 없다.하지만 문제가 있다.
clicked event는 user가 dropdown 화살표를 click하던 일반 text column을 click하던간에 event가 발생된다는 것이다.
만일 click하는 지점이 일반문자(dddw의 화살표 옆에 있는 문자말고...)라면 program은 dddw.retrieve()하지말아야 한다. 따라서 그 방법은 이들간의 click 지점이 어딘지를 구분해야하는 script가 필요하다.
GetObjectAtPointer()와 GetClickedColumn()함수는 dddw가 click되어졌는지 아닌지를 알려주진 못한다.
따라서 몇가지 내부적인 Hacking에 의해 올바른 event는 pbm_dwndropdown이라는 결론에 이르게 되었다. 그래서 solution은 다음과 같다.
user event ue_dddw_dropdown을 만들자(pbm_dwndropdown을 mapping) 그리고 다음과 같은 script를 작성한다.

1) DataWindowChild dwc
dwGetChild(GetColumnName(), dwc)
dwc.SetTransObject(SQLCA)
dwc.retrieve()

위의 것은 기본적인 기능을 구현한 것이고 다음은 여러 개선사항을 첨가한 예다.

2) dwGetChild(GetColumnName(), dwc)
// 만약 dwc.rowcount()>1 이라면 그러면 dddw는 이미 한번
// drop down했다는 것이다. 따라서 귀찮게 다시 retrieve() 할 // 필요없다.
// 또하나 주의할것이 있다. dddw는 하나의 단일 row 즉 blank // 인 단일 row를 기본적으로 갖는다.

if dwc.rowcount() > 1 then return

dwc.SetTransObject(SQLCA)
dwc.retrieve()
3)위의 (1)과 동일한 방법
dwc.retrieve(GetText())

dddw의 SQL문장:
select country_name from contry_lookup
where country_name like :retrieve_argument + "%"

위의 것은 만약 user가 'ca'를 입력해서 dropdown 화살표를 click하면 국가이름의 목록이 아주 보기좋게 drop down될것이다.

cambodia
cameroon
canada
.
.
하지만 france나 sussia같은 국가명은 retrieve되지 않는다. 왜냐하면 SQL문장의 like구문에 의해 ca로 시작하는 것만 retrieve하기 때문이다.
이건 retrieve량을 줄이게되어 메모리 절약등의 효과도 있게된다.
그런데 만일 dddw가 employee_name이고 department field가 있다고 생각한다면...
회사의 사원은 상당히 많다 따라서 department에 'Information Service'라고 입력한다. 그리고 employee_name field로 이동하여 dddw의 화살표를 click하면 dddw는 오직 Information Service에서 일하는 사원만을 retrieve하게된다.

//(1)과 같은 방법
dwc.retrieve(GetItemString(GetRow(), "department"))

dddw의 SQL문장:
select employee_name from employee_table
where department = :retrieve_argument

여기까지의 설명에서 주 요점 사항으로 기억할것은 pbm_dwndropdown event를 이해하고 사용하는 것이다. 이건 undocumented event다.

[Tip58] DataWindow, refer to data

DataWindow, refer to data in a different row.
DataWindow에 product_id라는 column이 있다.
product_id값을 검사하여 product_id값이 변경되면 글자색도 변경하고자 한다. 이런 경우 현재 row의 product_id값과 다음 row의 product_id값을 비교할 수 있다면 간단해진다.
DataWindow에서 각각의 column은 마치 배열처럼 취급할 수 있다.
즉 row는 column의 첨자라고 볼 수 있다.
이러한 특징은 script 작성시에 많이 느낄 수 있을 것이다.
그러나 DataWindow field에선 좀 다르다.
현재 row는 0이고 그 다음 row는 -1, -2, -3으로 나간다.
그 전 row는 1,2,3,4.. 가 된다.
따라서 위의 문제가 아주 간단히 해결된다.

if ( product_id = product_id[-1], rgb(0, 0, 0), rgb(255, 0, 0) )
이러한 식을 product_id 의 color property에 기술하면 간단히 해결된다.

[TIP 59]
DataWindow 리스트 멀티 Select 기법

윈도우즈의 화일관리자등을 보면 Ctrl+Click 의 경우 계속해서 선택하고
Shift+Click 의 경우 처음 클릭과 나중클릭의 자료를 전부 선택하는 기능을 DataWindow에 적용해 보았습니다. 먼저 Global 함수를 다음과 같이 만듭니다.
----------------------------------------------
함수명 : gf_multi_select
전달값 : a_dw (DataWindow type)
----------------------------------------------
long w_rownum,w_selrow,i
w_rownum = a_dw.GetClickedRow()
if w_rownum <= 0 then return
if keydown(keyshift!) then
w_selrow = a_dw.GetSelectedRow(0)
if w_selrow > 0 then
for i = min(w_selrow,w_rownum) to max(w_selrow,w_rownum)
a_dw.SelectRow(i,true)
next
else
a_dw.SelectRow(w_rownum,true)
end if
elseif keydown(KeyControl!) then
if a_dw.isSelected(w_rownum) then
a_dw.SelectRow(w_rownum,false)
else
a_dw.SelectRow(w_rownum,true)
end if
else
if a_dw.isSelected(w_rownum) then
a_dw.SelectRow(0,false)
else
a_dw.SelectRow(0,false)
a_dw.SelectRow(w_rownum,true)
end if
end if

이 함수를 해당 DataWindow의 Click Event에서 부르면 됩니다.

dw_1의 click event

gf_multi_select(this)


출처 : http://203.232.148.164/zb/zboard.php?id=tip&page=1&sn1=&divpage=1&category=5&sn=off&ss=on&sc=on&select_arrange=reg_date&desc=desc&no=103

 

출처의 리스트 : http://203.232.148.164/zb/zboard.php?id=tip&select_arrange=reg_date&desc=desc&page_num=16&selected=&exec=&sn=off&ss=on&sc=on&su=&category=5&keyword=


원본출처 : http://www.pisibook.co.kr/down/Tip.hwp
[출처] TIP 59 개 정리 [펌]|작성자 맨발이