worker-extender.md
This commit is contained in:
@@ -23,6 +23,7 @@ SKILL.md zawiera "duży obraz" - hierarchię klas, thread-safety, kanoniczne wzo
|
|||||||
| Sesje, transakcje, Login, Database, BusApplication, optimistic locking | [references/session-login.md](references/session-login.md) |
|
| Sesje, transakcje, Login, Database, BusApplication, optimistic locking | [references/session-login.md](references/session-login.md) |
|
||||||
| Paczki danych, Datapack, GuidedRow, ExportedRow, synchronizacja, blokady | [references/datapack-guidedrow.md](references/datapack-guidedrow.md) |
|
| Paczki danych, Datapack, GuidedRow, ExportedRow, synchronizacja, blokady | [references/datapack-guidedrow.md](references/datapack-guidedrow.md) |
|
||||||
| Klasa Context - dane z UI, zaznaczenia, parametry workera | [references/context.md](references/context.md) |
|
| Klasa Context - dane z UI, zaznaczenia, parametry workera | [references/context.md](references/context.md) |
|
||||||
|
| Obiekty Worker i Extender - rozszerzenia modelu, akcje w menu Czynności | [references/worker-extender.md](references/worker-extender.md) |
|
||||||
| Serwisy biznesowe (App / Database / Login / Session scope) | [references/services.md](references/services.md) |
|
| Serwisy biznesowe (App / Database / Login / Session scope) | [references/services.md](references/services.md) |
|
||||||
| Tłumaczenia (Translate, TranslateIgnore), ILogger, ActSource | [references/translations-logging.md](references/translations-logging.md) |
|
| Tłumaczenia (Translate, TranslateIgnore), ILogger, ActSource | [references/translations-logging.md](references/translations-logging.md) |
|
||||||
| Action result zwracany przez worker / extender / Command - raporty, dialogi, nawigacja | [references/action-result.md](references/action-result.md) |
|
| Action result zwracany przez worker / extender / Command - raporty, dialogi, nawigacja | [references/action-result.md](references/action-result.md) |
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Klasa Context
|
# Klasa Context (kontekst)
|
||||||
|
|
||||||
Dokumentacja klasy Context odpowiedzialnej za komunikację między warstwą logiki biznesowej a interfejsem graficznym.
|
Dokumentacja klasy Context odpowiedzialnej za komunikację między warstwą logiki biznesowej a interfejsem graficznym.
|
||||||
|
|
||||||
@@ -8,23 +8,25 @@ Context to **kontener par klucz-wartość**, gdzie:
|
|||||||
- **Klucz** = typ (Type)
|
- **Klucz** = typ (Type)
|
||||||
- **Wartość** = obiekt tego typu (object)
|
- **Wartość** = obiekt tego typu (object)
|
||||||
|
|
||||||
Kontekst jest stale aktualizowany podczas pracy z programem i przechowuje informacje o aktualnym stanie interfejsu.
|
Context jest stale aktualizowany podczas pracy z programem i przechowuje informacje o aktualnym stanie interfejsu.
|
||||||
|
|
||||||
## Zawartość kontekstu
|
## Zawartość context
|
||||||
|
|
||||||
Przykładowa zawartość przy otwartej liście kontrahentów:
|
Przykładowa zawartość przy otwartej liście kontrahentów:
|
||||||
|
|
||||||
| Typ | Opis |
|
| Typ | Opis |
|
||||||
|-----|------|
|
|-----|----------------------------------------|
|
||||||
| `SelectedCounter` | Liczba zaznaczonych wierszy na gridzie |
|
| `SelectedCounter` | Liczba zaznaczonych wierszy na gridzie |
|
||||||
| `Kontrahent[]` | Kolekcja zaznaczonych kontrahentów |
|
| `Kontrahent[]` | Kolekcja zaznaczonych kontrahentów |
|
||||||
| `UILocation` | Aktywny element interfejsu |
|
| `UILocation` | Aktywny element interfejsu |
|
||||||
| `INavigatorContext` | Kontekst grida (zaznaczenia, focus) |
|
| `INavigatorContext` | Context grida (zaznaczenia, focus) |
|
||||||
| `View` | Źródło danych grida |
|
| `View` | Źródło danych grida |
|
||||||
| `Params` | Klasa parametrów filtrów |
|
| `Params` | Klasa parametrów filtrów |
|
||||||
|
| `Session` | Gdy aktywny widok z danymi |
|
||||||
| `Login` | Zalogowany użytkownik |
|
| `Login` | Zalogowany użytkownik |
|
||||||
|
| `[ViewInfo]+Params` | Klasa parametrów widoku |
|
||||||
|
|
||||||
## Odczyt z kontekstu
|
## Odczyt z context
|
||||||
|
|
||||||
### Metody GetOrDefault i GetRequired (zalecane)
|
### Metody GetOrDefault i GetRequired (zalecane)
|
||||||
|
|
||||||
@@ -39,16 +41,16 @@ public void Action(Context context)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Przez indeksator
|
### Przez indeksator - gdy typ określony wartością `Type`
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public void Action(Context cx)
|
public void Action(Context cx)
|
||||||
{
|
{
|
||||||
// Rzuca wyjątek gdy brak obiektu w kontekście
|
// Rzuca wyjątek gdy brak obiektu w context
|
||||||
Kontrahent knt = (Kontrahent)cx[typeof(Kontrahent)];
|
Kontrahent knt = (Kontrahent)cx[typeof(Kontrahent)];
|
||||||
|
|
||||||
// Bez wyjątku - drugi parametr
|
// Bez wyjątku - drugi parametr
|
||||||
Kontrahent knt2 = (Kontrahent)cx[typeof(Kontrahent), false];
|
Kontrahent? knt2 = (Kontrahent?)cx[typeof(Kontrahent), false];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ public void Action(Context cx)
|
|||||||
public void Action(Context cx)
|
public void Action(Context cx)
|
||||||
{
|
{
|
||||||
// Zwraca true jeśli znaleziono, false jeśli nie
|
// Zwraca true jeśli znaleziono, false jeśli nie
|
||||||
if (cx.Get(out DokumentHandlowy dokument))
|
if (cx.Get(out DokumentHandlowy? dokument))
|
||||||
{
|
{
|
||||||
// dokument znaleziony
|
// dokument znaleziony
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ public void Action(Context cx)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Zapis do kontekstu
|
## Ustawienie wartości do context
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public void Action(Context cx)
|
public void Action(Context cx)
|
||||||
@@ -84,135 +86,134 @@ public void Action(Context cx)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Zastosowania
|
## Klasa parametrów (np filtrów) - dziedziczy z `ContextBase`
|
||||||
|
|
||||||
### 1. Filtry na listach głównych
|
Klasy parametrów filtrów dziedziczą z `ContextBase`
|
||||||
|
|
||||||
Klasy parametrów filtrów dziedziczą z `ContextBase` i są automatycznie w kontekście.
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Definicja klasy parametrów (w ViewInfo)
|
// Definicja klasy parametrów (w ViewInfo)
|
||||||
public class TowaryParams(Context context) : ContextBase(context)
|
public class TowaryParams(Context context) : ContextBase(context)
|
||||||
{
|
{
|
||||||
|
[Translate]
|
||||||
public Magazyn? Magazyn
|
public Magazyn? Magazyn
|
||||||
{
|
{
|
||||||
get => Context.GetOrDefault<Magazyn>();
|
get => Context.GetOrDefault<Magazyn>();
|
||||||
set => Context.Set(value);
|
set => Context.Set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Translate]
|
||||||
public TypTowaru Typ {
|
public TypTowaru Typ {
|
||||||
get => Context.GetOrDefault<TypTowaru>();
|
get => Context.GetOrDefault<TypTowaru>();
|
||||||
set => Context.Set(value);
|
set => Context.Set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Accessor(AutoChange = true)]
|
[Accessor(AutoChange = true)]
|
||||||
|
[Caption("Szukaj")]
|
||||||
public string SearchString { get; set; }
|
public string SearchString { get; set; }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* Każde property klasy parametrów dziedziczy z `ContextBase` wymaga kontroli tłumaczenia za pomocą jednego z
|
||||||
|
atrybutu: `[Translate]`, `[TranslateIgnore]`, `[Caption("Tytuł")]`
|
||||||
|
* Bindowanie we viewform.xml wewnątrz `FilterPanel` nie wymaga użycia `Context.` (np `TowaryParams`), ponieważ
|
||||||
|
`Context` jest dostępne bezpośrednio wewnątrz `FilterPanel`
|
||||||
|
* Bindowanie w pageform.xml wymaga użycia `Context.` (np `Context.TowaryParams`)
|
||||||
|
* Gdy property nie używa context -> stosuj `[Accessor(AutoChange = true)]` - zamiennie w kodzie set property można
|
||||||
|
również użyć `Session.InvokeChanged()` lub `Context.InvokeChanged()`
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<!-- Bindowanie w viewform.xml -->
|
<!-- Bindowanie w viewform.xml -->
|
||||||
<Field CaptionHtml="Magazyn" EditValue="{TowaryParams.Magazyn}"/>
|
<Flow Name="FilterPanel">
|
||||||
<Field CaptionHtml="Typ" EditValue="{TowaryParams.Typ}"/>
|
<Field CaptionHtml="Magazyn" EditValue="{TowaryParams.Magazyn}"/>
|
||||||
<Field CaptionHtml="Szukaj" EditValue="{TowaryParams.SearchString}"/>
|
<Field CaptionHtml="Typ" EditValue="{TowaryParams.Typ}"/>
|
||||||
|
<Field CaptionHtml="Szukaj" EditValue="{TowaryParams.SearchString}"/>
|
||||||
|
</Flow>
|
||||||
```
|
```
|
||||||
|
|
||||||
Wartości filtrów są dostępne przez kontekst dla:
|
```xml
|
||||||
|
<!-- Bindowanie w pageform.xml -->
|
||||||
|
<Page>
|
||||||
|
<Group CaptionHtml="Parametry">
|
||||||
|
<Field CaptionHtml="Magazyn" EditValue="{Context.TowaryParams.Magazyn}"/>
|
||||||
|
<Field CaptionHtml="Typ" EditValue="{Context.TowaryParams.Typ}"/>
|
||||||
|
<Field CaptionHtml="Szukaj" EditValue="{Context.TowaryParams.SearchString}"/>
|
||||||
|
</Group>
|
||||||
|
</Page>
|
||||||
|
```
|
||||||
|
|
||||||
|
Wartości filtrów są dostępne przez context do:
|
||||||
- Widoków (filtrowanie danych)
|
- Widoków (filtrowanie danych)
|
||||||
- Workerów (właściwości wyliczane)
|
- Obiektów worker (właściwości wyliczane, akcje)
|
||||||
- Wydruków
|
- Obiektów extender (właściwości wyliczane)
|
||||||
|
- Tworzenia obiektów
|
||||||
|
|
||||||
### 2. Wartości domyślne nowych obiektów
|
## Tworzenie obiektów
|
||||||
|
|
||||||
Kontekst używany do inicjalizacji nowych obiektów wartościami z filtrów. Uzupełniane są właściwości zaznaczone atrybutem `[Context]`, który oznacza próbę odczytania wartości do ustawienia property z kontekstu.
|
Context może tworzyć obiekty worker, extender, i inne obiekty, które mogą być używane w aplikacji.
|
||||||
|
Do utworzenia obiektu użyj metody `Context.CreateObject<ObjectType>()`.
|
||||||
|
|
||||||
```
|
* Parametry konstruktora są wypełniane automatycznie przez context, jeśli są dostępne w kontekście.
|
||||||
Lista faktur:
|
* Property oznaczone przez `[Context]` wypełniane są automatycznie przez context wg typu kontekstu.
|
||||||
Filtr Magazyn: "Sklep"
|
* Atrybut `[Context(Required=false)]` oznacza, że property nie jest wymagana do tworzenia obiektu.
|
||||||
Filtr Kontrahent: "Drynda"
|
* Atrybut `[Context<ParamType>("propertyName")]` lub `[Context(typeof(ParamType), "propertyName")]` pozwala na
|
||||||
↓
|
wypełnienie property z obiektu parametru ze wskazanego property.
|
||||||
Nowy dokument:
|
|
||||||
Magazyn: "Sklep" (z kontekstu - property z [Context])
|
|
||||||
Kontrahent: "Drynda" (z kontekstu - property z [Context])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Wydruki
|
### Przykład tworzenia obiektu `Osoba` z parametrów
|
||||||
|
|
||||||
Wydruki mają dostęp do obiektów z kontekstu jako źródła danych.
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// W kodzie wydruku
|
public class OsobaParams(Context context) : ContextBase(context)
|
||||||
Context cx = ...;
|
|
||||||
if (cx.Get(out Kontrahent[] kontrahenci))
|
|
||||||
{
|
|
||||||
// kontrahenci[] = zaznaczone na liście
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Workery - atrybut [Context]
|
|
||||||
|
|
||||||
Workery mogą pobierać parametry z kontekstu automatycznie.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class MojWorker
|
|
||||||
{
|
|
||||||
[Context] // Pobierane z kontekstu
|
|
||||||
public Magazyn Magazyn { get; set; }
|
|
||||||
|
|
||||||
[Context] // Jeśli brak w kontekście - okno parametrów
|
|
||||||
public Kontrahent Kontrahent { get; set; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Właściwości wyliczane zależne od filtrów
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Worker wyliczający stan magazynowy
|
|
||||||
public class StanMagazynu : IWorker
|
|
||||||
{
|
|
||||||
public object Compute(Context cx, object source)
|
|
||||||
{
|
|
||||||
Towar towar = source as Towar;
|
|
||||||
|
|
||||||
// Pobranie magazynu z kontekstu (z filtra)
|
|
||||||
if (cx.Get(out Magazyn magazyn))
|
|
||||||
{
|
|
||||||
return towar.GetStan(magazyn);
|
|
||||||
}
|
|
||||||
return towar.GetStanCalkowity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Klasa ContextBase
|
|
||||||
|
|
||||||
Bazowa klasa dla obiektów automatycznie umieszczanych w kontekście.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class MojaKlasaParametrow(Context context) : ContextBase(context)
|
|
||||||
{
|
{
|
||||||
|
[Translate]
|
||||||
[Accessor(AutoChange = true)]
|
[Accessor(AutoChange = true)]
|
||||||
public Date DataOd { get; set; }
|
public string Nazwisko { get; set; }
|
||||||
|
|
||||||
|
[Translate]
|
||||||
[Accessor(AutoChange = true)]
|
[Accessor(AutoChange = true)]
|
||||||
public Date DataDo { get; set; }
|
public string Imie { get; set; }
|
||||||
|
|
||||||
public Kontrahent Kontrahent {
|
[Translate]
|
||||||
get => Context.GetOrDefault<Kontrahent>();
|
public Operator Operator {
|
||||||
|
get => Context.GetOrDefault<Operator>();
|
||||||
set => Context.Set(value);
|
set => Context.Set(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Operator na podstawie wartości Operator z context
|
||||||
|
public class Osoba(Operator oper)
|
||||||
|
{
|
||||||
|
// Inicjowane na podstawie Nazwisko z obiektu parametru
|
||||||
|
[Context<OsobaParams>(nameof(OsobaParams.Nazwisko))]
|
||||||
|
public string Nazwisko { get; set; }
|
||||||
|
|
||||||
|
// Inicjowane na podstawie Imie z obiektu parametru
|
||||||
|
[Context<OsobaParams>(nameof(OsobaParams.Imie))]
|
||||||
|
public string Imie { get; set; }
|
||||||
|
|
||||||
|
// Jeżeli brak obiektu Kontrahent w context to property jest ignorowane
|
||||||
|
[Context(Required=false)]
|
||||||
|
public Kontrahent Kontrahent { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OsobaFactory
|
||||||
|
{
|
||||||
|
public static Osoba Create(Context context)
|
||||||
|
{
|
||||||
|
return context.CreateObject<Osoba>();
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Obiekty dziedziczące z `ContextBase` i nie tylko:
|
## Obiekty Worker i Extender
|
||||||
- Są automatycznie dodawane do kontekstu
|
|
||||||
- Wywoływane jest zdarzenie `OnChanged` przy zmianie właściwości
|
Worker i Extender to dwa mechanizmy rozszerzania modelu o logikę UI - dodatkowe properties wyliczane, akcje w menu Czynności,
|
||||||
- Obsługują bindowanie do kontrolek UI
|
pola na formularzu. Oba pobierają parametry z context przez atrybut `[Context]`.
|
||||||
- Właściwości połączone z UI (formularze, parametry) mogą być zadeklarowane z `[Accessor(AutoChange = true)]`, dzięki czemu Accessor automatycznie uruchomi mechanizm powiadamiania o zmianach i nie będzie konieczne wywołanie `Session.InvokeChanged()` lub `Context.InvokeChanged()`
|
|
||||||
|
Pełna dokumentacja (rejestracja, deklaracja, bindowanie w form.xml, akcje, przykłady) -
|
||||||
|
patrz [worker-extender.md](worker-extender.md).
|
||||||
|
|
||||||
## Interfejs INavigatorContext
|
## Interfejs INavigatorContext
|
||||||
|
|
||||||
Dostępny w kontekście gdy aktywna jest lista (grid).
|
Dostępny w context gdy aktywna jest lista (grid).
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public void Action(Context cx)
|
public void Action(Context cx)
|
||||||
@@ -233,7 +234,7 @@ public void Action(Context cx)
|
|||||||
|
|
||||||
## Kolekcje zaznaczonych obiektów
|
## Kolekcje zaznaczonych obiektów
|
||||||
|
|
||||||
W kontekście znajdują się tablice zaznaczonych obiektów.
|
W context znajdują się tablice zaznaczonych obiektów.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public void Action(Context cx)
|
public void Action(Context cx)
|
||||||
@@ -260,7 +261,7 @@ public void Action(Context cx)
|
|||||||
```csharp
|
```csharp
|
||||||
public void Action(Context cx)
|
public void Action(Context cx)
|
||||||
{
|
{
|
||||||
// Dostęp do sesji przez kontekst
|
// Dostęp do sesji przez context
|
||||||
Session session = cx.Session;
|
Session session = cx.Session;
|
||||||
|
|
||||||
// Dostęp do modułu przez sesję
|
// Dostęp do modułu przez sesję
|
||||||
@@ -268,94 +269,12 @@ public void Action(Context cx)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pełny przykład - Worker z kontekstem
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[assembly: Worker<TowarExtender, Towar>]
|
|
||||||
|
|
||||||
namespace Soneta.Towary;
|
|
||||||
|
|
||||||
public class TowarExtenderParams(Context context) : ContextBase(context)
|
|
||||||
{
|
|
||||||
[Accessor(AutoChange = true)]
|
|
||||||
[Caption("Magazyn filtrowania")]
|
|
||||||
public Magazyn MagazynFiltra { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TowarExtender
|
|
||||||
{
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Akcja workera w menu Czynności
|
|
||||||
|
|
||||||
Worker może udostępniać akcję uruchamianą z menu - zaznaczone obiekty trafiają do property opisanej `[Context]`:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[assembly: Worker<WyslijEmailWorker, Kontrahent>]
|
|
||||||
|
|
||||||
public class WyslijEmailWorker
|
|
||||||
{
|
|
||||||
[Context]
|
|
||||||
public Kontrahent[] Kontrahenci { get; set; }
|
|
||||||
|
|
||||||
[Context]
|
|
||||||
public Context Context { get; set; }
|
|
||||||
|
|
||||||
[Action("Wyślij email")]
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
foreach (var k in Kontrahenci)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(k.Email))
|
|
||||||
WyslijEmail(k.Email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WyslijEmail(string email) { /* ... */ }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dobre praktyki
|
## Dobre praktyki
|
||||||
|
|
||||||
1. **Używaj Get<T>, GetOrDefault<T>, GetRequired<T>** zamiast indeksatora - bezpieczniejsze
|
1. **Używaj Get<T>, GetOrDefault<T>, GetRequired<T>** zamiast indeksatora - bezpieczniejsze
|
||||||
2. **Sprawdzaj obecność** obiektów w kontekście przed użyciem
|
2. **Sprawdzaj obecność** obiektów w context przed użyciem
|
||||||
3. **Dziedzicz z ContextBase** dla własnych klas parametrów i pamiętaj o konstruktorze `(Context context)`
|
3. **Dziedzicz z ContextBase** dla własnych klas parametrów i pamiętaj o konstruktorze `(Context context)`
|
||||||
4. **Używaj [Context]** w workerach dla parametrów z kontekstu
|
4. **Stosuj `[Accessor(AutoChange = true)]`** lub `InvokeChanged()` dla powiadamiania UI o zmianach jeżeli wartość
|
||||||
5. **Stosuj `[Accessor(AutoChange = true)]`** lub `InvokeChanged()` dla powiadamiania UI o zmianach
|
property nie jest przechowywana w context
|
||||||
|
5. **Używaj `[Translate]`, `[TranslateIgnore]`, `[Caption("Tytuł")]`** dla property klas parametrów (ContextBase)
|
||||||
## Typowe typy w kontekście
|
6. **Worker / Extender** - rozszerzanie modelu o logikę UI, patrz [worker-extender.md](worker-extender.md)
|
||||||
|
|
||||||
| Typ | Kiedy dostępny |
|
|
||||||
|-----|----------------|
|
|
||||||
| `Login` | Zawsze po zalogowaniu |
|
|
||||||
| `Database` | Zawsze po zalogowaniu |
|
|
||||||
| `LicencjaProgramu` | Zawsze po zalogowaniu |
|
|
||||||
| `Session` | Gdy aktywny widok z danymi |
|
|
||||||
| `View` | Gdy aktywna lista |
|
|
||||||
| `INavigatorContext` | Gdy aktywna lista |
|
|
||||||
| `[Obiekt][]` | Zaznaczenia na liście |
|
|
||||||
| `[ViewInfo]+Params` | Klasa parametrów widoku |
|
|
||||||
| `UILocation` | Lokalizacja w UI |
|
|
||||||
|
|||||||
@@ -227,18 +227,18 @@ public void AktualizujCeny(Login login, string nazwaCeny, decimal procentPodwyzk
|
|||||||
{
|
{
|
||||||
var tm = session.GetTowary();
|
var tm = session.GetTowary();
|
||||||
|
|
||||||
foreach (Towar t in tm.Towary.WgKodu)
|
// Jedna transakcja dla całej pętli - szybciej i atomowo
|
||||||
{
|
|
||||||
// Transakcja biznesowa - WYMAGANA dla każdej zmiany!
|
|
||||||
using (var transaction = session.Logout(editMode: true))
|
using (var transaction = session.Logout(editMode: true))
|
||||||
|
{
|
||||||
|
foreach (Towar t in tm.Towary.WgKodu)
|
||||||
{
|
{
|
||||||
var cena = t.Ceny[nazwaCeny];
|
var cena = t.Ceny[nazwaCeny];
|
||||||
if (cena != null)
|
if (cena != null)
|
||||||
{
|
{
|
||||||
cena.Netto = new DoubleCy(cena.Netto.Value * (1 + procentPodwyzki / 100));
|
cena.Netto = new DoubleCy(cena.Netto.Value * (1 + procentPodwyzki / 100));
|
||||||
}
|
}
|
||||||
transaction.Commit();
|
|
||||||
}
|
}
|
||||||
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
session.Save(); // Zapisuje wszystkie zmiany do bazy
|
session.Save(); // Zapisuje wszystkie zmiany do bazy
|
||||||
@@ -273,7 +273,7 @@ public void UsunTowar(Login login, string kod)
|
|||||||
|
|
||||||
## Praca z kontekstem
|
## Praca z kontekstem
|
||||||
|
|
||||||
Przykłady Workera z `[Context]`, klasy parametrów dziedziczącej z `ContextBase`, akcji w menu Czynności i współdzielenia wartości przez Context - patrz [context.md](context.md).
|
Przykłady klasy parametrów dziedziczącej z `ContextBase` i współdzielenia wartości przez Context - patrz [context.md](context.md). Przykłady Workera z `[Context]` i akcji w menu Czynności - patrz [worker-extender.md](worker-extender.md).
|
||||||
|
|
||||||
## Praca z GuidedRow
|
## Praca z GuidedRow
|
||||||
|
|
||||||
@@ -440,7 +440,8 @@ public void BezpiecznaOperacja(Login login)
|
|||||||
{
|
{
|
||||||
// Logowanie błędu
|
// Logowanie błędu
|
||||||
Console.WriteLine($"Błąd: {ex.Message}");
|
Console.WriteLine($"Błąd: {ex.Message}");
|
||||||
// Zmiany nie zostały zatwierdzone (brak Commit lub Save)
|
// Wyjątek przed Commit() = automatyczny rollback transakcji
|
||||||
|
// Wyjątek z session.Save() = transakcja zatwierdzona w sesji, ale brak zapisu do bazy
|
||||||
// Sesja zostanie automatycznie zwolniona przez using
|
// Sesja zostanie automatycznie zwolniona przez using
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
# 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](context.md) 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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Rejestracja zalecana atrybutem generic
|
||||||
|
[assembly: Worker<NazwaKlasyWorker, DataType>]
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Niezalecana rejestracja atrybutem z parametrami
|
||||||
|
[assembly: Worker(typeof(NazwaKlasyWorker), typeof(DataType))]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deklaracja klasy worker
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
|
||||||
|
[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`)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<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](./action-result.md)
|
||||||
|
* 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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Rejestracja zalecana atrybutem generic
|
||||||
|
[assembly: Worker<NazwaKlasyExtender>]
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Niezalecana rejestracja atrybutem z parametrami
|
||||||
|
[assembly: Worker(typeof(NazwaKlasyExtender))]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deklaracja klasy extender
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[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()}`
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<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.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[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 [context.md](context.md))
|
||||||
|
3. **Metody Action zwracają [action result](./action-result.md)** - nie wywołuj UI bezpośrednio
|
||||||
|
4. **`CommitUI()` zamiast `Commit()`** - w workerach/extenderach uruchamianych z UI używaj `CommitUI()`
|
||||||
Reference in New Issue
Block a user