PureBasic - GUI Yanıt Vermiyor

 


Resimde, pencerenin başlık bölümünde (Yanıt Vermiyor) yazdığını fark etmemiş olabilirsiniz. Aşağıda yanlış kodlanmış bir program örneği göreceksiniz. Bu programı, çalıştırıp, Check me kutucuğuna tıklarsanız veya pencereyi başlık kısmından tutup sürüklerseniz (taşırsanız) herhangi bir sorun olmayacaktır. Start butonuna tıkladıktan sonra bir şey yapmadan beklerseniz yine bir sorun olmayacaktır. 

Amma velakin, Start butonuna tıkladıktan sonra aynı işlemleri yapmayı denerseniz, yapamadığınızı görürsünüz. Program size tepki veremeyecek ve çakılacaktır. Program, 20 saniye sonra loop (döngü) bittiğinde normale dönecektir. Start butonuna tıklayınca program Test_LongLoop() prosedürüne gider. Orada zaman alan bir For-Next döngüsüne girer. Bu esnada pencerede veya formda oluşan olaylarla ilgilenemez ve çakılır. Formda oluşan, butona tıklama veya pencereyi taşıma olayları'nı ihmal etmemek gerekiyor. Program yazarken sürekli olarak, oluşan olayları tüketmemiz gerekiyor. Tüketmez, ihmal edersek, form donuyor.

Yanlış kodlanmış program örneği

Enumeration Gadget 
  #GADGET_btnStart 
  #GADGET_btnStop 
  #GADGET_chkTest
  #GADGET_txtLoopOutput 
  #GADGET_lbTrace 
EndEnumeration 

#WINDOW_Main = 0
#WINDOW_Main_Flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
#LOOP_MaxLoops        = 200 
#LOOP_SlowDownDelay   =  100 
Global ApplicationQuit  = #False  
Global LoopRunningState = #False   ; init --> not running 

Macro Trace(MessageText) 
  AddGadgetItem(#GADGET_lbTrace, -1, MessageText) 
  SetGadgetState(#GADGET_lbTrace, CountGadgetItems(#GADGET_lbTrace) -1) 
EndMacro 

OpenWindow(#WINDOW_Main, 0, 0, 400, 320, "Event handling example...", #WINDOW_Main_Flags) 
StickyWindow(#WINDOW_Main, 1)  ; show test app always above the PB_IDE 
ButtonGadget(#GADGET_btnStart,  8, 4, 192, 32, "Start Looping") 
ButtonGadget(#GADGET_btnStop, 200, 4, 192, 32, "Stop Looping") 
CheckBoxGadget(#GADGET_chkTest, 8, 40, 384, 24, "Check me -- (see the event handling while the loop is running)") 
TextGadget(#GADGET_txtLoopOutput, 8, 72, 384, 20, "Loop is stopped.") 
SetGadgetColor(#GADGET_txtLoopOutput, #PB_Gadget_BackColor, RGB(0,200,0)) 
ListViewGadget(#GADGET_lbTrace, 8, 104, 384, 208, $4000)  ; #LBS_NOSEL == 0x4000 

Procedure Test_LongLoop() 
  Protected index, msg$  
  
  For index = 0 To #LOOP_MaxLoops 
    Delay(#LOOP_SlowDownDelay)  ; spend some time with nothing 
    
    msg$ = " Item " + Str(index) + "/" + Str(#LOOP_MaxLoops) + " | Checkbox.State == " + GetGadgetState(#GADGET_chkTest) 
    SetGadgetText(#GADGET_txtLoopOutput, msg$) 
    If Not LoopRunningState  
      Trace("LoopRunningState == FALSE ") 
      Break 
    EndIf 
  Next index 
  
  If index >= #LOOP_MaxLoops 
    SetGadgetText(#GADGET_txtLoopOutput, "Loop finished.") 
    Trace("Loop finished (reached MaxLoops == " + index + ")") 
  EndIf 
  LoopRunningState = #False 
EndProcedure 

Repeat
  Event = WaitWindowEvent()
  Select Event
    Case #PB_Event_MoveWindow
      Trace("  New Window Pos == " + WindowX(#WINDOW_Main) + ", " +WindowY(#WINDOW_Main)) 
    Case #PB_Event_CloseWindow    
      ApplicationQuit = #True  
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #GADGET_btnStart                           
          LoopRunningState = #True 
          ClearGadgetItems(#GADGET_lbTrace) 
          SetGadgetText(#GADGET_txtLoopOutput, "Loop is running") 
          Test_LongLoop() 
        Case #GADGET_btnStop                            
          SetGadgetText(#GADGET_txtLoopOutput, "Loop stopped.") 
          LoopRunningState = #False 
        Case #GADGET_chkTest                         
          Trace("Gadget = #GADGET_chkTest == " + GetGadgetState(#GADGET_chkTest)) 
      EndSelect
  EndSelect
Until ApplicationQuit 
End
Burada Macro-EndMacro bloğu kullanılmış. Kullanımı prosedüre benziyor ama prosedür değil. Derleme esnasında Trace(..) yazan yerlerde, macro bloğunun içindeki satırları kopyala yapıştır yapıyor.

Dediğim gibi formun donması veya çakılması sorununu çözmek için olayları (events) fazla bekleme yapmadan tüketmemiz gerekiyor. Çözüm için kullanılabilecek yollardan biri multi-thread programlamadır. Uzun döngüyü yapacak ayrı bir thread (iş parçası) açılabilir. PureBasic de CreateThread komutu ile bu iş yapılabilmektedir. PB’de thread, ayrı bir process (işlem) oluşturmaz. Ana kodla birlikte asenkron olarak çalışır. Sorunu çözmek için farklı yollar deneyeceğiz.

Çözüm yolu

PureBasic komutlarından BindEvent() ve BindGadgetEvent() ile tüm olayları bizim yazdığımız EventHandler() prosedürüne bağlıyoruz.
Enumeration Gadget 
  #GADGET_btnStart 
  #GADGET_btnStop 
  #GADGET_chkTest
  #GADGET_txtLoopOutput 
  #GADGET_lbTrace 
EndEnumeration 

#WINDOW_Main = 0
#WINDOW_Main_Flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
#LOOP_MaxLoops        = 200 
#LOOP_SlowDownDelay   =  100 
Global ApplicationQuit  = #False  
Global LoopRunningState = #False   ; init --> not running 
Declare EventHandler() 
Declare Test_LongLoop()

Macro Trace(MessageText) 
  AddGadgetItem(#GADGET_lbTrace, -1, MessageText) 
  SetGadgetState(#GADGET_lbTrace, CountGadgetItems(#GADGET_lbTrace) -1) 
EndMacro 

OpenWindow(#WINDOW_Main, 0, 0, 400, 320, "Event handling example...", #WINDOW_Main_Flags) 
StickyWindow(#WINDOW_Main, 1)  ; show test app always above the PB_IDE 
ButtonGadget(#GADGET_btnStart,  8, 4, 192, 32, "Start Looping") 
ButtonGadget(#GADGET_btnStop, 200, 4, 192, 32, "Stop Looping") 
CheckBoxGadget(#GADGET_chkTest, 8, 40, 384, 24, "Check me -- (see the event handling while the loop is running)") 
TextGadget(#GADGET_txtLoopOutput, 8, 72, 384, 20, "Loop is stopped.") 
SetGadgetColor(#GADGET_txtLoopOutput, #PB_Gadget_BackColor, RGB(0,200,0)) 
ListViewGadget(#GADGET_lbTrace, 8, 104, 384, 208, $4000)  ; #LBS_NOSEL == 0x4000 

BindEvent(#PB_Event_MoveWindow,  @EventHandler(), #WINDOW_Main) 
BindEvent(#PB_Event_CloseWindow, @EventHandler(),  #WINDOW_Main) 

BindGadgetEvent(#GADGET_btnStart, @EventHandler()) 
BindGadgetEvent(#GADGET_btnStop,  @EventHandler()) 
BindGadgetEvent(#GADGET_chkTest,  @EventHandler())

Procedure EventHandler()
  Select Event()
    Case #PB_Event_MoveWindow
      Trace("  New Window Pos == " + WindowX(#WINDOW_Main) + ", " +WindowY(#WINDOW_Main)) 
    Case #PB_Event_CloseWindow    
      ApplicationQuit = #True 
      End
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #GADGET_btnStart                           
          LoopRunningState = #True 
          ClearGadgetItems(#GADGET_lbTrace) 
          SetGadgetText(#GADGET_txtLoopOutput, "Loop is running")      
        Case #GADGET_btnStop                            
          SetGadgetText(#GADGET_txtLoopOutput, "Loop stopped.") 
          LoopRunningState = #False 
        Case #GADGET_chkTest                         
          Trace("Gadget = #GADGET_chkTest == " + GetGadgetState(#GADGET_chkTest)) 
        EndSelect
  EndSelect 
EndProcedure

Procedure Test_LongLoop() 
  Protected index, msg$  
  
  For index = 0 To #LOOP_MaxLoops 
    Delay(#LOOP_SlowDownDelay)  ; spend some time with nothing 
    
    msg$ = " Item " + Str(index) + "/" + Str(#LOOP_MaxLoops) + " | Checkbox.State == " + GetGadgetState(#GADGET_chkTest) 
    SetGadgetText(#GADGET_txtLoopOutput, msg$)
    
      While WindowEvent(): Wend 
    
    If Not LoopRunningState  
      Trace("LoopRunningState == FALSE ") 
      Break 
    EndIf 
  Next index 
  
  If index >= #LOOP_MaxLoops 
    SetGadgetText(#GADGET_txtLoopOutput, "Loop finished.") 
    Trace("Loop finished (reached MaxLoops == " + index + ")") 
  EndIf 
  LoopRunningState = #False 
EndProcedure 

Repeat
  WaitWindowEvent(10) 
  If LoopRunningState = #True 
    If first = #False 
      Test_LongLoop()   ; <-- cannot called by EventHandler()
      first = #True 
    EndIf 
  Else 
    first = #False 
  EndIf 
Until ApplicationQuit 
End
Burada Test_LongLoop (uzun döngü) prosedürü içinde olayları tüketmek için While WindowEvent(): Wend satırını kullandık. Yine aynı amaçla, Repeat-Until döngüsünde de WaitWindowEvent(10) komutunu kullandık. EventHandler() prosedürünün ilk satırında yazan Event(), PB'nin kendi prosedürü veya fonksiyonudur. 

Bir başka çözüm yolu

BindEvent komutunu kullanmadan, aşağıdaki şekilde de çalışıyor. Bu bir kirli kodlama örneğidir. EventHandler(), iki farklı yerden çağırılıyor. EventHandler() prosedürünün içinden Test_LongLoop() çağırılıyor. Çalışıyor ama kontrolsüz ve şuursuz bir şekilde 😁
Enumeration Gadget 
  #GADGET_btnStart 
  #GADGET_btnStop 
  #GADGET_chkTest
  #GADGET_txtLoopOutput 
  #GADGET_lbTrace 
EndEnumeration 

#WINDOW_Main = 0
#WINDOW_Main_Flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
#LOOP_MaxLoops        = 200 
#LOOP_SlowDownDelay   =  100 
Global ApplicationQuit  = #False  
Global LoopRunningState = #False   ; init --> not running 
Declare EventHandler() 
Declare Test_LongLoop()

Macro Trace(MessageText) 
  AddGadgetItem(#GADGET_lbTrace, -1, MessageText) 
  SetGadgetState(#GADGET_lbTrace, CountGadgetItems(#GADGET_lbTrace) -1) 
EndMacro 

OpenWindow(#WINDOW_Main, 0, 0, 400, 320, "Event handling example...", #WINDOW_Main_Flags) 
StickyWindow(#WINDOW_Main, 1)  ; show test app always above the PB_IDE 
ButtonGadget(#GADGET_btnStart,  8, 4, 192, 32, "Start Looping") 
ButtonGadget(#GADGET_btnStop, 200, 4, 192, 32, "Stop Looping") 
CheckBoxGadget(#GADGET_chkTest, 8, 40, 384, 24, "Check me -- (see the event handling while the loop is running)") 
TextGadget(#GADGET_txtLoopOutput, 8, 72, 384, 20, "Loop is stopped.") 
SetGadgetColor(#GADGET_txtLoopOutput, #PB_Gadget_BackColor, RGB(0,200,0)) 
ListViewGadget(#GADGET_lbTrace, 8, 104, 384, 208, $4000)  ; #LBS_NOSEL == 0x4000 


Procedure EventHandler()
  Select Event()
    Case #PB_Event_MoveWindow
      Trace("  New Window Pos == " + WindowX(#WINDOW_Main) + ", " +WindowY(#WINDOW_Main)) 
    Case #PB_Event_CloseWindow    
      ApplicationQuit = #True 
      End
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #GADGET_btnStart                           
          LoopRunningState = #True 
          ClearGadgetItems(#GADGET_lbTrace) 
          SetGadgetText(#GADGET_txtLoopOutput, "Loop is running")
          Test_LongLoop()
        Case #GADGET_btnStop                            
          SetGadgetText(#GADGET_txtLoopOutput, "Loop stopped.") 
          LoopRunningState = #False 
        Case #GADGET_chkTest                         
          Trace("Gadget = #GADGET_chkTest == " + GetGadgetState(#GADGET_chkTest)) 
        EndSelect
  EndSelect 
EndProcedure

Procedure Test_LongLoop() 
  Protected index, msg$  
  
  For index = 0 To #LOOP_MaxLoops 
    Delay(#LOOP_SlowDownDelay)  ; spend some time with nothing 
    
    msg$ = " Item " + Str(index) + "/" + Str(#LOOP_MaxLoops) + " | Checkbox.State == " + GetGadgetState(#GADGET_chkTest) 
    SetGadgetText(#GADGET_txtLoopOutput, msg$)
    
     While WindowEvent() : EventHandler() : Wend 
   
    If Not LoopRunningState  
      Trace("LoopRunningState == FALSE ") 
      Break 
    EndIf 
  Next index 
  
  If index >= #LOOP_MaxLoops 
    SetGadgetText(#GADGET_txtLoopOutput, "Loop finished.") 
    Trace("Loop finished (reached MaxLoops == " + index + ")") 
  EndIf 
  LoopRunningState = #False 
EndProcedure 

Repeat
  WaitWindowEvent(10) 
  EventHandler()
Until ApplicationQuit 
End



Thread ile çözüm

Bu konuda forumun tecrübeli kullanıcılarından @infratec hemen yardımcı oldu sağolsun.

EnableExplicit

CompilerIf Not #PB_Compiler_Thread
  CompilerError "Enable thread safe in compiler options!"
CompilerEndIf

#WINDOW_Main = 0
#WINDOW_Main_Flags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
#LOOP_MaxLoops        = 200 
#LOOP_SlowDownDelay   =  100

Enumeration Gadget 
  #GADGET_btnStart 
  #GADGET_btnStop 
  #GADGET_chkTest
  #GADGET_txtLoopOutput 
  #GADGET_lbTrace 
EndEnumeration 

Enumeration OwnEvent #PB_Event_FirstCustomValue
  #OwnEvent_ThreadStarted
  #OwnEvent_SetGadgetText
  #OwnEvent_ThreadFinished
EndEnumeration

Structure Parameter_Structure
  Thread.i
  Exit.i
EndStructure

Macro Trace(MessageText) 
  AddGadgetItem(#GADGET_lbTrace, -1, MessageText) 
  SetGadgetState(#GADGET_lbTrace, CountGadgetItems(#GADGET_lbTrace) -1) 
EndMacro 

Procedure Test_LongLoop(*Parameter.Parameter_Structure)
  
  Protected index.i, *msg
  
  PostEvent(#OwnEvent_ThreadStarted)
  
  For index = 0 To #LOOP_MaxLoops
    
    If *Parameter\Exit
      Break
    EndIf
    
    Delay(#LOOP_SlowDownDelay)  ; spend some time with nothing
    
    *msg = UTF8(" Item " + Str(index) + "/" + Str(#LOOP_MaxLoops) + " | Checkbox.State == " + GetGadgetState(#GADGET_chkTest))
    PostEvent(#OwnEvent_SetGadgetText, #WINDOW_Main, #GADGET_txtLoopOutput, 0, *msg)
    
  Next index
  
  *msg = UTF8("Loop finished (reached MaxLoops == " + Str(index) + ")")
  PostEvent(#OwnEvent_ThreadFinished, #WINDOW_Main, #GADGET_lbTrace, 0, *msg)
  
EndProcedure 


Define.i Event, ApplicationQuit, Exit
Define *msg
Define Parameter.Parameter_Structure


OpenWindow(#WINDOW_Main, 0, 0, 400, 320, "Event handling example...", #WINDOW_Main_Flags)
StickyWindow(#WINDOW_Main, 1)  ; show test app always above the PB_IDE
ButtonGadget(#GADGET_btnStart,  8, 4, 192, 32, "Start Looping")
ButtonGadget(#GADGET_btnStop, 200, 4, 192, 32, "Stop Looping")
DisableGadget(#GADGET_btnStop, #True)
CheckBoxGadget(#GADGET_chkTest, 8, 40, 384, 24, "Check me -- (see the event handling while the loop is running)") 
TextGadget(#GADGET_txtLoopOutput, 8, 72, 384, 20, "Loop is stopped.") 
SetGadgetColor(#GADGET_txtLoopOutput, #PB_Gadget_BackColor, RGB(0,200,0)) 
ListViewGadget(#GADGET_lbTrace, 8, 104, 384, 208, $4000)  ; #LBS_NOSEL == 0x4000 


Repeat
  Event = WaitWindowEvent()
  
  Select Event
    Case #OwnEvent_ThreadStarted
      DisableGadget(#GADGET_btnStart, #True)
      DisableGadget(#GADGET_btnStop, #False)
      SetGadgetText(#GADGET_txtLoopOutput, "Loop is running")
      
    Case #OwnEvent_SetGadgetText
      *msg = EventData()
      SetGadgetText(EventGadget(), PeekS(*msg, -1, #PB_UTF8))
      FreeMemory(*msg)
      
    Case #OwnEvent_ThreadFinished
      DisableGadget(#GADGET_btnStart, #False)
      DisableGadget(#GADGET_btnStop, #True)
      *msg = EventData()
      If *msg
        Trace(PeekS(*msg, -1, #PB_UTF8))
        FreeMemory(*msg)
      EndIf
      SetGadgetText(#GADGET_txtLoopOutput, "Loop stopped.")
      If ApplicationQuit
        Exit = #True
      EndIf
      
    Case #PB_Event_MoveWindow
      Trace("  New Window Pos == " + WindowX(#WINDOW_Main) + ", " +WindowY(#WINDOW_Main))
      
    Case #PB_Event_CloseWindow
      ApplicationQuit = #True
      If IsThread(Parameter\Thread)
        Parameter\Exit = #True
      Else
        PostEvent(#OwnEvent_ThreadFinished)
      EndIf
      
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #GADGET_btnStart
          ClearGadgetItems(#GADGET_lbTrace) 
          Parameter\Exit = #False
          Parameter\Thread = CreateThread(@Test_LongLoop(), @Parameter)
          
        Case #GADGET_btnStop
          If IsThread(Parameter\Thread)
            Parameter\Exit = #True
          EndIf
          
        Case #GADGET_chkTest                         
          Trace("Gadget = #GADGET_chkTest == " + GetGadgetState(#GADGET_chkTest)) 
          
      EndSelect
  EndSelect 
  
Until Exit
Hata alırsanız Compiler -> Compiler Options -> Create threadsafe executable kutucuğunu işaretlemeniz gerekiyor. Start butonuna tıklayınca thread (Test_LongLoop) oluşturuluyor. Thread içinden Repeat-Until ana döngüsüne PostEvent() ile suni olaylar gönderiliyor.

Yorumlar

Bu blogdaki popüler yayınlar

VBA - Mscomm (seri port) ile veri loglama

RJ45 2 - Novexx barkod yazıcıya, S7-1200 plc ile etiket yazdırma

Köpüğü alınmış Windows AtlasOS