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
Worker dorzuca do obiektu danych dodatkowe properties wyliczane (do użycia w bindowaniu) oraz pozycje w menu Czynności.
- Przypisuj worker do konkretnego obiektu danych — worker zawsze działa w kontekście jednego typu.
- Dodawaj do nazwy klasy sufiks
Worker(np.WyliczenieStanMagazynuWorker). - Wybieraj nazwę klasy opisującą działanie, nie technikę.
- Inicjuj parametry z kontekstu przez
[Context]. - Rejestruj przez generyczny atrybut
[assembly: Worker<WorkerType, DataType>]— to wersja zalecana.
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ść polabool IsReadOnlyXxx()- disable polaobject 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
Workerz nazwy klasy worker (tutajWyliczenieStanMagazynu)
<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()(npbool IsVisibleSendEmails()) jest opcjonalna i kontroluje widoczność w menu - Metoda
bool IsEnabledXxx()(npbool IsEnabledSendEmails()) jest opcjonalna i kontroluje aktywność pozycji w menu - Metoda
string GetNameXxx()(npstring GetNameSendEmails()) jest opcjonalna i kontroluje tytuł pozycji w menu - Metoda
bool IsCheckedXxx()(npbool 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 (!k.Email.IsNullOrEmpty())
{
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ść polabool IsReadOnlyXxx()- disable polaobject 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
Extenderz nazwy klasy extender (tutajUpperNazwaExtender) - 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
- Używaj [Context] w obiektach worker i extender dla parametrów inicjowanych z context
- Dziedzicz z ContextBase dla własnych klas parametrów (patrz contextbase.md)
- Metody Action zwracają action result - nie wywołuj UI bezpośrednio
CommitUI()zamiastCommit()- w workerach/extenderach uruchamianych z UI używajCommitUI()