Tworzenie obiektowego UI w OE Development Studio cz. IV

W czwartej części naszego cyklu, zajmiemy się obiecanym tematem edycji rekordów w bazie danych. Przypomnijmy: dane wyświetlane w obiektach graficznych są zasilane ze źródła BindingSource.
To źródło pobiera dane z tabeli np. Customer czy Order, ale działa niejako w jednym kierunku, tzn. nie nadaje się do zapisu danych. W naszych przykładach zapis będzie realizowany do tabel w bazie poprzez zdefiniowane statyczne bufory rekordów.

W przypadku aplikacji działającej np. na serwerze aplikacji, gdzie nie ma bezpośredniego dostępu do bazy, źródło BindingSource konfiguruje się w oparciu o schemat ProDataSet, z tabelami tymczasowymi, a edycja jest realizowana poprzez zapis do tych właśnie obiektów.

OK, zaczynamy.

W formie fCustomer dodajemy przycisk z etykietą Delete, nazwa np. Del.
Klikamy dwa razy w zdarzenie Click aby utworzyć metodę do obsługi tego zdarzenia (Del_Click). W metodzie tej wstawiamy poniższy kod:

DEFINE BUFFER bCust FOR Customer.
DEFINE BUFFER bOrder FOR Order.
DEFINE VARIABLE lDel AS LOGICAL NO-UNDO.
DEFINE VARIABLE iActCustNum AS INTEGER NO-UNDO.
		
MESSAGE "Do you want to delete this record?" SKIP 
            bsCust:InputValue["Name"]:ToString() SKIP 
	    bsCust:InputValue["City"]:ToString() SKIP
VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO UPDATE lDel.
IF NOT lDel THEN 
   RETURN.

Na razie mamy sukces tylko połowiczny – pojawia się komunikat gdzie możemy podjąć decyzję o kasowaniu rekordu.

Teraz dodamy kod kasujący rekord Customer i połączone z nim rekordy Order.
Dodajemy kod poniżej instrukcji RETURN:

IF NOT lDel THEN 
   RETURN.

iActCustNum = INTEGER(bsCust:InputValue["CustNum"]:ToString()).
DO TRANSACTION ON ERROR UNDO, RETURN:
   FOR FIRST bCust WHERE bCust.CustNum = iActCustNum:
      FOR EACH bOrder WHERE bOrder.CustNum = bCust.CustNum.
          DELETE bOrder.
      END.
      DELETE bCust.
   END. 
END. 

Żeby program zadziałał trzeba pamiętać, że w bazie są jeszcze trygery, zabezpieczające przed utratą danych. Trzeba albo te trygery zmodyfikować albo wyłączyć. Decyzja należy do Was.

Drugim problemem jest to, że po skasowaniu rekordu DataGrid nie odświeża automatycznie danych; musimy to zrobić sami.
Dodajemy w metodzie kod:

bsCust:MovePrevious().
iPos = bsCust:POSITION.
refreshData().
bsCust:POSITION = iPos.

Zmienną iPos deklarujemy jako integer. Po skasowaniu kursor ustawia się na poprzednim rekordzie.

Teraz dodajemy przyciski Edit i Create i generujemy metody dla zdarzenia Click.

Przeważnie te same funkcjonalności można stworzyć na różne sposoby. Można np. wykorzystać możliwości jakie daje DataGrid, ale wg Wiesława, ta metoda może generować wiele problemów i nie jest zbyt czytelna. Wygodniej jest stworzyć oddzielne okno dla edycji. Tworzymy więc nową formę fCustEdit.

Dodajemy 4 pola TextBox: CustNum, Name, City, Country, oraz etykiety dla tych pól.

Wstawiamy przyciski btnOK (etykieta OK), btnCancel (etykieta Cancel) i ustawiamy ich predefiniowaną funkcjonalność.

We właściwościach fCustEdit ustawiamy parametr CancelButton -> btnCancel.
We właściwościach btnOK ustawiamy DialogResult -> OK.

Wracamy do okna fCustomer, do metody dla kliknięcia przycisku Edit. Wstawiamy tu kod:

METHOD PRIVATE VOID Edit_Click( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
   DEFINE VARIABLE oCustEdit AS fCustEdit NO-UNDO.
   oCustEdit = NEW fCustEdit().
   WAIT-FOR oCustEdit:ShowDialog().
   DELETE OBJECT oCustEdit.

   RETURN.

Przycisk Edit już działa choć na razie nie widzimy danych.

Aby przekazać dane do edycji można skorzystać z różnych metod. My zdefiniujemy w fCustEdit właściwości dla każdego edytowanego pola. Np. pName dla pola Name; i tak dalej dla pozostałych pól.

    DEFINE PUBLIC PROPERTY pName AS CHARACTER NO-UNDO 
    GET.
    SET. 

Generujemy obsługę zdarzenia Shown dla fCustEdit i wpisujemu kod:

METHOD PRIVATE VOID fCustEdit_Shown( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
   ASSIGN
      textBoxName:TEXT = pName
      textBoxCity:TEXT = pCity
      textBoxCountry:TEXT = pCountry
      textBoxCustNum:TEXT = string(pCustNum).
   RETURN.

END METHOD.

Nasze okno edycyjne będzie pokazywać dane ustawione w fCustomer. Ponieważ pole CustNum nie może być przez nas edytowane ustawiamy w jego właściwościach Enabled = False, a żeby wyłączyć ramkę wokół pola ustawiamy BorderStyle = None.

Aby zapisać dane po edycji dodajemy obsługę kliknięcia na przycisk OK.

METHOD PRIVATE VOID btnOK_Click( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
   ASSIGN 
      pName    = textBoxName:Text
      pCity    = textBoxCity:Text
      pCountry = textBoxCountry:TEXT.
   RETURN.

END METHOD.

Wracamy do fCustomer i ustawiamy właściwości do wyświetlenia w oknie fCustEdit, czyli dodajemy kod w Edit_Click:

oCustEdit = NEW fCustEdit().
		   	
oCustEdit:pCustNum = int(bsCust:InputValue["CustNum"]:ToString()).
oCustEdit:pName = bsCust:InputValue["Name"]:ToString().
oCustEdit:pCity = bsCust:InputValue["City"]:ToString().
oCustEdit:pCountry = bsCust:InputValue["Country"]:ToString().
iActCustNum = INT(bsCust:InputValue["CustNum"]:ToString()).
		
WAIT-FOR oCustEdit:ShowDialog().

Okno fCustEdit wygląda teraz tak:

Następnym krokiem jest zapis wprowadzonych danych w fCustomer na dole obsługi Edit_Click. Dodajemy transakcję:

WAIT-FOR oCustEdit:ShowDialog().

DO TRANSACTION ON ERROR UNDO, RETURN:
   FOR FIRST bCust WHERE bCust.CustNum = iActCustNum SHARE-LOCK :
      ASSIGN
         bCust.Name    = oCustEdit:pName
         bCust.City    = oCustEdit:pCity
         bCust.Country = oCustEdit:pCountry.
   END.
END.

bsCust:Refresh().

OK, edycja jest już obsłużona. Pozostało jeszcze dodanie obsługi dla utworzenia nowego rekordu.

W tym celu wykorzystamy okno fCustEdit i fragment kodu dla edycji. Nie musimy przekazywać wartości pól, ale tworzymy nowy rekord komendą CREATE. Kod dla przycisku Create wygląda tak:

DEFINE BUFFER bCust FOR Customer.
DEFINE VARIABLE oCustEdit AS fCustEdit NO-UNDO.
DEFINE VARIABLE rRowid AS ROWID NO-UNDO.
        
oCustEdit = NEW fCustEdit().
                   
WAIT-FOR oCustEdit:ShowDialog().		
        
DO TRANSACTION ON ERROR UNDO, RETURN:
   CREATE bCust.
   ASSIGN
      bCust.Name    = oCustEdit:pName
      bCust.City    = oCustEdit:pCity
      bCust.Country = oCustEdit:pCountry.
   rRowid = ROWID(bCust).
END.

refreshData().
bsCust:HANDLE:reposition-to-rowid(rRowid).
RETURN.


Zmienna ROWID jest potrzebna żeby po dodaniu ustawić się na nowym rekordzie.
Małą niedogodnością wykorzystania formy fCustEdit jest to, że numer rekordu wynosi 0, ale z tym powinniście sobie poradzić bez problemu np. przenosząc instrukcję CREATE przed wywołanie formy i przekazanie parametru CustNum.


W zasadzie program jest gotowy, ale Wiesław chciał na koniec uatrakcyjnić go nieco dla Was.
Po pierwsze dodajmy klawisze skrótów dla operacji na rekordach.

We właściwościach formy fCustomer ustawiamy KeyPreview = True.

Następnie dla gridu włączamy zdarzenie KeyDown.

W metodzie obsługującej to zdarzenie definiujemy:

METHOD PRIVATE VOID dataGridView1_KeyDown( INPUT sender AS System.Object, INPUT e AS...
   CASE e:KEYCODE:ToString():
      WHEN "Insert" THEN Cre:PerformClick().
      WHEN "Return" THEN Edit:PerformClick().
      WHEN "Delete" THEN Del:PerformClick().
   END CASE.
   RETURN.

END METHOD.

Cre, Edit, Del to nazwy przycisków odpowiednio dla: Create, Edit i Delete. Całość działa poprawnie.

I już naprawdę na koniec, dodamy licznik rekordów. Przyda się gdy będziemy dodawać czy usuwać rekordy.
Dodajemy obiekt typu label, nadajemy nazwę np. labInfo. W metodzie refreshData dodajemy na końcu linię kodu:

labInfo:TEXT = "Liczba rekordów: " + STRING(bsCust:Count).

Po każdym wywołaniu tej metody liczba rekordów jest wyświetlana w obiekcie.

Niniejszym kończymy na razie cykl tworzenia aplikacji przy wykorzystaniu obiektów .Net w ABL, ale być może do niego wrócimy. Ponieważ pojawiła się już wersja OpenEdge 12, teraz trzeba napisać coś na ten temat.

Wiesław Kurzątkowski
Piotr Tucholski