Tworzenie obiektowego UI w OE Development Studio cz. II

Kontynuujemy budowę aplikacji rozpoczętą w pierwszej części.

Mamy formę fCustomer; teraz zbudujemy drugą formę fOrder, która będzie zawierała browser (grid) z rekordami Order dla danego Customera.

Postępujemy analogicznie jak w poprzedniej części: do nowej formy wstawiamy źródło danych binding source, zmieniamy nazwę na np. bsOrder. Definiujemy schemat dla bsOrder.

Wstawiamy DataGridView wybierając źródło danych bsOrder.

Ponieważ browser ma pokazywać tylko rekordy Order dla zadanej wartości pola CustNum (nr klienta) trzeba przekazać z nadrzędnego okna (formy) parametr. Można to zrobić np. definiując w fOrder właściwość iCustNum.

    DEFINE PUBLIC PROPERTY iCustNum AS INTEGER NO-UNDO 
    GET.
    SET.

Metoda refreshData będzie tutaj wyglądała nieco inaczej niż w fCustomer.

    METHOD PUBLIC VOID refreshData(  ):
        
        def var bsOrdQuery as handle no-undo.
        def var bsOrdString as char no-undo.
        create query bsOrdQuery.
        bsOrdQuery:ADD-BUFFER (buffer order:handle).
        bsOrdQuery:QUERY-PREPARE ("for each order where order.custnum = "
                                   + string(iCustNum) + " by custnum").
        bsOrdQuery:QUERY-OPEN ().
        bsOrder:handle = bsOrdQuery.
        RETURN.

    END METHOD.

Wywołanie refreshData wstawiamy do kodu programu zgodnie z sugestiami z poprzedniej części.

Na koniec dodajemy do formy przycisk btnClose z etykietą Close. Definiujemy metodę dla zdarzenia Click wstawiając jedną instrukcję:

    METHOD PRIVATE VOID btnClose_Click( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
        THIS-OBJECT:Close( ).
	RETURN.

    END METHOD.

Teraz przechodzimy do formy fCustomer. Wstawiamy deklarację zmiennej oOrd.

    DEF VAR oOrd AS fOrder. 

Definiujemy metodę dla zdarzenia Click przycisku btnOrders.

    
    METHOD PRIVATE VOID btnOrders_Click( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
        IF (NOT VALID-OBJECT(oOrd) OR oOrd:IsDisposed) THEN DO:
            oOrd = NEW fOrder().
            oOrd:iCustNum = bscust:InputValue["CustNum"].
            oOrd:RefreshData().
            oOrd:Show().	//okno niemodalne

        END.
	RETURN.

    END METHOD.

Metoda tworzy instancję obiektu fOrder (najpierw sprawdza czy obiekt nie jest już zainicjowany), następie ustawia właściwość iCustNum (czyli przekazuje parametr), otwiera zapytanie i wyświetla formę w postaci niemodalnej. Oznacza to, że użytkownik ma dostęp zarówno do formy fOrder jak i fCustomer.

Gdybyśmy chcieli uruchomić fOrder w postaci modalnej (dostęp do fCustomer będzie dopiero po zamknięciu fOrder) to zamiast instrukcji oOrd:Show() trzeba napisać: WAIT-FOR oOrd:ShowDialog().

Okno z zamówieniami otwiera się przykrywając częściowo okno z klientami. Można zdefiniować parametry okna tak aby otwierało się np. tuż obok. Aby to osiągnąć trzeba skorzystać z poniższej sztuczki Wiesława:

    oOrd:Location = new System.Drawing.Point(int(this-object:Location:X) + 
        this-object:Width,int(this-object:Location:Y)).
    oOrd:Height = this-object:Height.

Cały tryger ma teraz następującą postać:

METHOD PRIVATE VOID btnOrders_Click( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
    IF (NOT VALID-OBJECT(oOrd) OR oOrd:IsDisposed) THEN DO:
        oOrd = NEW fOrder().
        oOrd:iCustNum = bscust:InputValue["CustNum"].
        oOrd:RefreshData().
        oOrd:Show().	//okno niemodalne
        //WAIT-FOR oOrd:ShowDialog().	//okno modalne
        oOrd:Location = new System.Drawing.Point(int(this-object:Location:X) + 
           this-object:Width,int(this-object:Location:Y)).
        oOrd:Height = this-object:Height.
    END.
	RETURN.

END METHOD.

Efekt widać poniżej:

Naszą aplikację trzeba jeszcze trochę usprawnić. Ponieważ zdecydowaliśmy się na okno niemodalne, trzeba obsłużyć zmianę wyświetlanego rekordu w oknie fCustomer – powinna temu towarzyszyć zmiana wyświetlanych danych w oknie fOrder.
W widoku zdarzeń bsCust klikamy na zdarzenie PositionChanged (innym zdarzeniem, które warto wziąć pod uwagę jest CurrentChanged).

Do kodu programu zostanie wstawiona metoda obsługująca to zdarzenie. Musimy dodać do niej poniższy kod:

METHOD PRIVATE VOID bsCust_PositionChanged( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
    IF VALID-OBJECT(oOrd) THEN DO:
        IF oOrd:IsDisposed THEN  
           RETURN.                                                              
        oOrd:iCustNum = bscust:InputValue["CustNum"].
        oOrd:RefreshData().
    END.	
    RETURN.

END METHOD.

Możecie teraz sprawdzić, że jeśli okno fOrder jest otwarte, zmiana podświetlonego rekordu w oknie fCustomer powoduje wyświetlenie zamówień dla tego rekordu.

To oczywiście nie koniec. Wkrótce pojawi się następna część.

Wiesław Kurzątkowski
Piotr Tucholski

Tworzenie obiektowego UI w OE Development Studio cz. I

Nieco ponad 2 lata temu pokazałem jak zrobić pierwsze kroki na drodze tworzenia interfejsu graficznego z wykorzystaniem obiektów .Net w narzędziu OpenEdge Developer Studio.
Temat ten w rozmowach z Wami co jakiś czas powraca. Powodem jest najczęściej chęć wykonania interfejsu o lepszym wyglądzie i bardziej skalowalnego niż tradycyjny, niemłody już interfejs progressowy.

Do powrotu skłoniło mnie ostatecznie spotkanie z Wiesławem Kurzątkowskim – specjalistą od programowania z wykorzystaniem technik obiektowych firmy Novum. Wiesław obiecał pokazać niektóre stosowane przez siebie rozwiązania oraz służyć swoją wiedzą fachową w razie pytań i wątpliwości.

W dokumentacji OpenEdge można znaleźć ciekawe przykłady całych aplikacji, ale zwracacie uwagę na to, że po pierwsze: nie tworzy się ich od podstaw, a z półgotowych projektów, po drugie: opierają się na kontrolkach Infragistics, na które trzeba mieć dodatkową licencję. W naszych przykładach wykorzystamy ogólnodostępne kontrolki Microsoft oraz OpenEdge. Jeśli będzie zainteresowanie z Waszej strony to także pokażemy coś z kontrolkami Telerik.

OK, rozpoczynamy od powtórzenia stworzenia prostej aplikacji, opisanej we wspomnianym artykule, przedstawiającej browser (grid) z rekordami z tabeli Customer, a następnie dodamy drugie okno zawierające grid z rekordami Orders dla danego Customera.

W narzędziu OpenEdge Developer Studio 11.7 zakładamy nowy projekt o nazwie net1 typu klienckiego GUI for .NET.

Do projektu przyłączamy połączenie z serwerem bazy sports2000 (opisywane już wcześniej). Teraz tworzymy nową formę New -> ABL Form o nazwie fCustomer, naciskamy Finish.

Klasę formy można podglądać w postaci kodu ABL lub wizualnej (OE Visual Designer). Między trybami możemy się przełączać np. poprzez F9 / Shift + F9.

Do pustej formy przeciągamy z zestawu kontrolek OpenEdge: ProBindingSource. W widoku Properties zmieniamy nazwę domyślną na bsCust.

Następnie definiujemy schemat dla bsCust wybierając połączenie z bazą danych. Wybieramy tabelę Customer i kilka pól, jak na rysunku.

Teraz dodajemy kontrolkę DataGridView, wybierając źródło danych binding source bsCust i ew. ustawiając kolumny w żądanej kolejności.

W oknie wstawiamy dwa przyciski (Microsoft Button): btnOrders z etykietą Orders i btnClose z etykietą Close, jak na rysunku. Uruchomienie naszej aplikacji nie spowoduje jeszcze wyświetlenia danych Customer. Trzeba w tym celu napisać krótki fragment kodu.

Dodajemy metodę refreshData, która definiuje i otwiera zapytanie dla tabeli Customer, a następnie podpina je do źródła danych bsCust.

    METHOD PUBLIC VOID refreshData(  ):
        
        DEF VAR bsCustQuery AS HANDLE NO-UNDO.
        DEF VAR bsCustString AS CHAR NO-UNDO.
        CREATE QUERY bsCustQuery.
        bsCustQuery:ADD-BUFFER (BUFFER customer:HANDLE).
        bsCustQuery:QUERY-PREPARE ("FOR EACH customer").
        bsCustQuery:QUERY-OPEN ().
        bsCust:HANDLE = bsCustQuery.
        RETURN.

    END METHOD.

Wywołanie tej metody dodajemy do konstruktora.

    CONSTRUCTOR PUBLIC fCustomer (  ):
             
        SUPER().
        InitializeComponent().
        THIS-OBJECT:ComponentsCollection:ADD(THIS-OBJECT:components).
        refreshData().
        
        CATCH e AS Progress.Lang.Error:
            UNDO, THROW e.
        END CATCH.

    END CONSTRUCTOR.

Uruchamiamy aplikację – dane z tabeli Customer są poprawnie wyświetlane w browserze.

Wywołanie refreshData w konstruktorze może w niektórych przypadkach bardziej złożonych aplikacji być niekorzystne i generować błędy. To samo osiągniemy wstawiając wywołanie refreshData w metodzie formy Load lub Shown. Klikamy więc dwukrotnie w widoku Events na Shown; automatycznie jest wstawiona do naszego kodu metoda obsługi dla tego zdarzenia. Przenosimy tutaj wywołanie refreshData z konstruktora.

    METHOD PRIVATE VOID fCustomer_Shown( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
        refreshData().	
	RETURN.

    END METHOD.

W obiektach .NET jest wiele predefiniowanych ustawień i zdarzeń, które możemy wykorzystywać dla poprawienia funkcjonalności naszych aplikacji. Jednym z nich jest automatyczne sortowanie AutoSort dla binding source. Zmieniamy wartość tego parametru na True.


Jeśli klikniemy teraz na nazwę kolumny, uzyskamy sortowanie danych w browserze według jej wartości. Poniżej widać sortowanie po polu Name.

Na koniec tej części pokażemy ustawienia parametru Anchor (zakotwiczenie) i jak on wpływa na skalowalność obiektów.

Domyślna wartość Anchor to Top, Left. Wartości te powodują, że obiekt nie jest dobrze skalowalny gdy zmieniamy rozmiar okna pociągając za prawą lub dolną krawędź. Na obrazku poniżej, browser nie zwiększył automatycznie wysokości, a prawy przycisk jest niewidoczny.

Na tym rysunku poszerzenie okna nie powoduje zwiększenie szerokości browsera.

Zmieńmy ustawienia Anchor dla browsera na: Top, Bottom, Left, Right

… a dla przycisków na: Bottom, Right. Uruchamiamy znów aplikację…

Zmiana rozmiarów okna powoduje automatyczne dostosowanie rozmiaru browsera, a przyciski pozostają widoczne pod browserem.

Rozszerzenie okna powoduje wprawdzie odsłonięcie szarego obszaru, ale tylko dlatego, że nie ma więcej kolumn do wyświetlenia. Chyba fajnie, co?

Na razie to tyle. Następnym razem dodamy okno dla zamówień aktualnego klienta.

Wiesław Kurzątkowski
Piotr Tucholski

Jak rozpocząć tworzenie interfejsu .NET w OE Dev. Studio

Tworzenie interfejsu .NET to dość szerokie pojęcie. Ja chciałbym tutaj napisać o początkach wykorzystania form .NET w narzędziu OpenEdge Developer Studio przy zastosowaniu języka ABL.
Technologia łączenia języka progressowego i technologii obiektowych pojawiła się w OE10.1, czyli ładnych kilka lat temu.
W dalszym ciągu jednakże wiele osób nie wie jak rozpocząć pracę z tworzeniem takiego interfejsu.

Bardzo pomocne będą tutaj filmy i towarzyszące im dokumenty pdf, stworzone przez Johna Sadda. Opracowania Johna są jasne i czytelne. Ze względu na to, że były stworzone kilka lat temu, mogą zawierać pewne drobne nieaktualności.

John jest twórcą materiałów dot. wielu zagadnień związanych z OpenEdge, do których adres znajdziecie w sekcji Adresy w sieci -> OpenEdge Educational Videos.
Te, które nas najbardziej teraz interesują, znajdziecie tutaj.

Rozpocznijmy od utworzenia nowego projektu typu OpenEdge w konfiguracji GUI for .NET.

Od razu utworzona jest klasa – plik Form1.cls oraz procedura, która może uruchamiać tę klasę – plik RunForm1.p.

Jest to o tyle fajne, że wiele osób nie wiedziało dotąd jak uruchomić klasę z poziomu procedury. Teraz dostaje od razu gotowe rozwiązanie. Jeśli chcemy utworzyć nową klasę formy wybieramy File -> New -> ABL Form.

Plik można otworzyć jako kod ABL lub jego wizualna postać (wybieramy View Design, lub OpenEdge Visual Designer).

Na razie forma to puste okno. W widoku Toolbox widzimy dwa zestawy kontrolek: Microsoft Controls i OpenEdge Controls. Z tego drugiego wybieramy źródło danych: bindingSource. Przy założeniu, że jesteśmy podłączeni do bazy danych, definiujemy źródło danych jako kilka pól z tablicy Customer.

Teraz do okna wstawiamy kontrolkę DataGridView i zaznaczamy zdefiniowane przed chwilą źródło danych.

Aby dane były wyświetlane trzeba dodać fragment kodu ABL w konstruktorze formy (patrz poniżej).

OK, dane są już prawidłowo wyświetlane.

Gorąco polecam obejrzeć prezentacje Johna Sadda, gdyż dokładnie objaśnia on poszczególne kroki, z zagłębieniem się w szczegóły dotyczące kodu ABL.

Do tego tematu powrócimy na blogu.