Files
soneta-erp-skills/soneta-programming/references/worker-extender.md
T
Marcin Wojas 4a69cdb831 contextbase
2026-05-17 15:54:58 +02:00

8.5 KiB

Obiekty Worker i Extender

Obiekty Worker i Extender rozszerzają model danych o dodatkową logikę UI: properties wyliczane, akcje w menu Czynności, dodatkowe pola na formularzu. Oba korzystają z Context do pobierania parametrów.

Obiekty Worker

Dodają dodatkowe properties wyliczane do obiektów, które mogą być stosowane w bindowaniu lub pozwalają na definiowanie pozycji w menu Czynności.

  • Worker jest zawsze przypisany do obiektu danych.
  • W nazwie klasy powinno się stosować sufiks Worker
  • Nazwa klasy worker powinna określać jego działanie.
  • Może być inicjowany z context za pomocą [Context]
  • Rejestracja za pomocą atrybutu assembly z dwoma parametrami [Worker<WorkerType, DataType>] - zalecana wersja generic

Rejestracja worker

// Rejestracja zalecana atrybutem generic
[assembly: Worker<NazwaKlasyWorker, DataType>]
// Niezalecana rejestracja atrybutem z parametrami
[assembly: Worker(typeof(NazwaKlasyWorker), typeof(DataType))]

Deklaracja klasy worker


[assembly: Worker<WyliczenieStanMagazynuWorker, Towar>]

// Worker wyliczający stan magazynowy
public class WyliczenieStanMagazynuWorker
{
    [Context] 
    public Magazyn Magazyn { get; set; }
    
    [Context] 
    public Towar Towar { get; set; }
    
    public decimal StanMagazynu =>
        Magazyn != null
            ? Towar.GetStan(Magazyn)
            : Towar.GetStanCalkowity();
}

Można stosować publiczne metod kontrolujące zachowanie property w edytorze:

  • bool IsVisibleXxx() - widoczność pola
  • bool IsReadOnlyXxx() - disable pola
  • object GetListXxx() - szczegóły edycji

Bindowanie na UI form.xml (liście)

Bindowanie wg schematu: {Workers.<NazwaTypuBezSufiksWorker>.NazwaProperty}

  • Za początku zawsze Workers.
  • Nazwa typu bez sufiksu Worker z nazwy klasy worker (tutaj WyliczenieStanMagazynu)
<Grid Name="List">
    <Field CaptionHtml="Kod" Width="17" EditValue="{Kod}" />
    <Field CaptionHtml="Nazwa" Width="30" EditValue="{Nazwa}" />
    <Field CaptionHtml="Stan magazynu" Width="17" EditValue="{Workers.WyliczenieStanMagazynu.StanMagazynu}" />
</Grid>

Worker dodający pozycje do menu Czynności w UI

Worker udostępnia metodę w menu Czynności za pomocą atrybutu [Action("Tytuł")].

  • Jeden worker może udostępniać wiele pozycji (metod) w menu Czynności.
  • Metoda Action (w przykładzie metoda SendEmails) obiektu worker zwraca action result
  • Metoda bool IsVisibleXxx() (np bool IsVisibleSendEmails()) jest opcjonalna i kontroluje widoczność w menu
  • Metoda bool IsEnabledXxx() (np bool IsEnabledSendEmails()) jest opcjonalna i kontroluje aktywność pozycji w menu
  • Metoda string GetNameXxx() (np string GetNameSendEmails()) jest opcjonalna i kontroluje tytuł pozycji w menu
  • Metoda bool IsCheckedXxx() (np bool IsCheckedSendEmails()) jest opcjonalna i kontroluje zaznaczenie pozycji w menu

Przykład akcji wykonywanej grupowo na liście kontrahentów

[assembly: Worker<SendEmailsForKontrahentWorker, Kontrahent>]

public class SendEmailsForKontrahentWorker
{
    [Context]
    public Kontrahent[] Kontrahenci { get; set; }

    [Action("Wyślij email")]
    public object SendEmails()
    {
        int counter = 0;
        foreach (var k in Kontrahenci)
        {
            if (!string.IsNullOrEmpty(k.Email))
            {
                WyslijEmail(k.Email);
                ++counter;
            }
        }
        
        return "Wysłano {0} emaili.".TranslateFormat(counter);
    }
    
    public bool IsVisibleSendEmails() => Kontrahenci?.Length>0;
    public bool IsEnabledSendEmails() => Kontrahenci.All(k => k.Email!="");

    private void WyslijEmail(string email) { /* ... */ }
}

Przykład akcji wykonywanej pojedynczo na towarze

[assembly: Worker<KodDuzymiLiteramiWorker, Towar>]

public class KodDuzymiLiteramiWorker
{
    [Context]
    public Towar Towar { get; set; }

    [Action("Kod towaru dużymi literami")]
    public void MakeUpperName()
    {
        Towar.Nazwa = Towar.Nazwa.ToUpper();
    }

    public bool IsVisibleMakeUpperName() => Towar != null;
    public bool IsEnabledMakeUpperName() => !Towar.Nazwa.IsNullOrEmpty();
}

Przykład akcji otwierającej formularz kontrahenta dla dokumentu

[assembly: Worker<PokazKontrahentaDokumentuWorker, DokumentHandlowy>]

public class PokazKontrahentaDokumentuWorker
{
    [Context]
    public DokumentHandlowy Dokument { get; set; }

    [Action("Kontrahent dokumentu")]
    public Kontrahent Pokaz()
    {
        return Dokument.Kontrahent;
    }
    
    public bool IsEnabledPokaz() => Dokument.Kontrahent != null;
    
    public string GetNamePokaz() => "Pokaż kontrahenta: {0}".TranslateFormat(Dokument.Kontrahent?.Nazwa);
}

Obiekty Extender

Pozwalają na bindowanie logiki interface-owej do formularzy. Można bindować methods i properties z obiektu extender.

  • Extender nie jest przypisany do danych
  • W nazwie klasy powinno się stosować sufiks Extender
  • Może być inicjowany z context za pomocą [Context]
  • Rejestracja za pomocą atrybutu assembly z jednym parametrem [Worker<ExtenderType>] - zalecana wersja generic

Można stosować publiczne metod kontrolujące zachowanie property w edytorze:

  • bool IsVisibleXxx() - widoczność pola
  • bool IsReadOnlyXxx() - disable pola
  • object GetListXxx() - szczegóły edycji

Rejestracja extender

// Rejestracja zalecana atrybutem generic
[assembly: Worker<NazwaKlasyExtender>]
// Niezalecana rejestracja atrybutem z parametrami
[assembly: Worker(typeof(NazwaKlasyExtender))]

Deklaracja klasy extender

[assembly: Worker<UpperNazwaExtender>]

// Extender pokazujący nazwę dużymi literami
public class UpperNazwaExtender
{
    [Context] 
    public Towar Towar { get; set; }
    
    public string UpperNazwa 
    {
        get => Towar.Nazwa.ToUpper();
        set => Towar.Nazwa = value.ToUpper();
    }
    
    public bool IsReadOnlyUpperNazwa() => string.IsNullOrEmpty(Towar.Nazwa);
    
    public string PokazNazwe() => "Oryginalna nazwa towaru: {0}".TranslateFormat(Towar.Nazwa); 
}

Bindowanie na UI pageform.xml (formularz)

Bindowanie wg schematu: {new <NazwaTypuZSufixExtender>.NazwaProperty}

  • Za początku zawsze new
  • Nazwa typu z sufiksem Extender z nazwy klasy extender (tutaj UpperNazwaExtender)
  • Podobnie do property, możemy bindować metody: {new <NazwaTypuZSufixExtender>.NazwaMetody()}
<Page CaptionHtml="Nazwa zakładki">
    <Group CaptionHtml="Identyfikacja towaru">
        <Field CaptionHtml="Kod" Width="17" EditValue="{Kod}" />
        <Field CaptionHtml="Nazwa" Width="30" EditValue="{new UpperNazwaExtender.UpperNazwa}" />
        <Command CaptionHtml="Oryginalna nazwa" DataContext="{new UpperNazwaExtender}" MethodName="PokazNazwe" />
    </Group>
</Page>

Pobieranie parametrów z context - atrybut [Context]

Worker (i extender) może pobierać parametry z context automatycznie.

public class MojWorker
{
    [Context]  // Pobierane z context
    public Magazyn Magazyn { get; set; }
    
    [Context]  // Jeśli brak w context - okno parametrów
    public Kontrahent Kontrahent { get; set; }
}

Pełny przykład - Worker z context

[assembly: Worker<Soneta.Towary.StanTowaruWorker, Towar>]

namespace Soneta.Towary;

public class TowarExtenderParams(Context context) : ContextBase(context)
{
    [Accessor(AutoChange = true)]
    [Caption("Magazyn filtrowania")]
    public Magazyn MagazynFiltra { get; set; }
}

public class StanTowaruWorker
{
    [Context]
    public TowarExtenderParams Params { get; set; }

    [Context]
    public Towar Towar { get; set; }

    public decimal StanWMagazynie => 
        Params.MagazynFiltra != null
            ? PoliczStanMagazynu(Towar, Params.MagazynFiltra) 
            : PoliczStanMagazynu(Towar);

    private decimal PoliczStanMagazynu(Towar towar, Magazyn magazyn)
    {
        // Wyliczyć stan we wskazanym magazynie
        return 0;
    }

    private decimal PoliczStanMagazynu(Towar towar)
    {
        // Wyliczyć stan w całej firmie
        return 0;
    }
}

Dobre praktyki

  1. Używaj [Context] w obiektach worker i extender dla parametrów inicjowanych z context
  2. Dziedzicz z ContextBase dla własnych klas parametrów (patrz contextbase.md)
  3. Metody Action zwracają action result - nie wywołuj UI bezpośrednio
  4. CommitUI() zamiast Commit() - w workerach/extenderach uruchamianych z UI używaj CommitUI()