contextbase
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) |
|
||||||
|
| Klasy parametrów (ContextBase) - filtry, trwałość, InvokeChanged | [references/contextbase.md](references/contextbase.md) |
|
||||||
| Obiekty Worker i Extender - rozszerzenia modelu, akcje w menu Czynności | [references/worker-extender.md](references/worker-extender.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) |
|
||||||
|
|||||||
@@ -86,66 +86,13 @@ public void Action(Context cx)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Klasa parametrów (np filtrów) - dziedziczy z `ContextBase`
|
## Klasy parametrów (ContextBase)
|
||||||
|
|
||||||
Klasy parametrów filtrów dziedziczą z `ContextBase`
|
Klasy parametrów filtrów widoków, parametrów raportów i akcji dziedziczą z `ContextBase` -
|
||||||
|
są mostem między UI a logiką (właściwości czytane/zapisywane przez context, trwałość
|
||||||
|
przez `LoadProperty` / `SaveProperty`, powiadamianie UI przez `InvokeChanged`).
|
||||||
|
|
||||||
```csharp
|
Pełna dokumentacja - patrz [contextbase.md](contextbase.md).
|
||||||
// Definicja klasy parametrów (w ViewInfo)
|
|
||||||
public class TowaryParams(Context context) : ContextBase(context)
|
|
||||||
{
|
|
||||||
[Translate]
|
|
||||||
public Magazyn? Magazyn
|
|
||||||
{
|
|
||||||
get => Context.GetOrDefault<Magazyn>();
|
|
||||||
set => Context.Set(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Translate]
|
|
||||||
public TypTowaru Typ {
|
|
||||||
get => Context.GetOrDefault<TypTowaru>();
|
|
||||||
set => Context.Set(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Accessor(AutoChange = true)]
|
|
||||||
[Caption("Szukaj")]
|
|
||||||
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
|
|
||||||
<!-- Bindowanie w viewform.xml -->
|
|
||||||
<Flow Name="FilterPanel">
|
|
||||||
<Field CaptionHtml="Magazyn" EditValue="{TowaryParams.Magazyn}"/>
|
|
||||||
<Field CaptionHtml="Typ" EditValue="{TowaryParams.Typ}"/>
|
|
||||||
<Field CaptionHtml="Szukaj" EditValue="{TowaryParams.SearchString}"/>
|
|
||||||
</Flow>
|
|
||||||
```
|
|
||||||
|
|
||||||
```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)
|
|
||||||
- Obiektów worker (właściwości wyliczane, akcje)
|
|
||||||
- Obiektów extender (właściwości wyliczane)
|
|
||||||
- Tworzenia obiektów
|
|
||||||
|
|
||||||
## Tworzenie obiektów
|
## Tworzenie obiektów
|
||||||
|
|
||||||
@@ -273,8 +220,6 @@ public void Action(Context cx)
|
|||||||
|
|
||||||
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 context 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. **Klasy parametrów (ContextBase)** - trwałość filtrów, `InvokeChanged`, dziedziczenie `Load`/`Save`,
|
||||||
4. **Stosuj `[Accessor(AutoChange = true)]`** lub `InvokeChanged()` dla powiadamiania UI o zmianach jeżeli wartość
|
patrz [contextbase.md](contextbase.md)
|
||||||
property nie jest przechowywana w context
|
4. **Worker / Extender** - rozszerzanie modelu o logikę UI, patrz [worker-extender.md](worker-extender.md)
|
||||||
5. **Używaj `[Translate]`, `[TranslateIgnore]`, `[Caption("Tytuł")]`** dla property klas parametrów (ContextBase)
|
|
||||||
6. **Worker / Extender** - rozszerzanie modelu o logikę UI, patrz [worker-extender.md](worker-extender.md)
|
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
# Klasa ContextBase - klasy parametrów
|
||||||
|
|
||||||
|
Dokumentacja klas parametrów (filtrów widoków, parametrów raportów, parametrów akcji)
|
||||||
|
dziedziczących z `ContextBase`. Klasy te są **mostem między UI a logiką** - przechowują
|
||||||
|
wartości wybrane przez użytkownika i udostępniają je do widoków, workerów, extenderów
|
||||||
|
oraz przy tworzeniu obiektów.
|
||||||
|
|
||||||
|
Pełna dokumentacja klasy `Context` (kontener kluczy/wartości) - patrz [context.md](context.md).
|
||||||
|
|
||||||
|
## Definicja klasy parametrów
|
||||||
|
|
||||||
|
Klasy parametrów filtrów dziedziczą z `ContextBase` i mają konstruktor przyjmujący `Context`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Definicja klasy parametrów (w ViewInfo)
|
||||||
|
public class TowaryParams(Context context) : ContextBase(context)
|
||||||
|
{
|
||||||
|
[Translate]
|
||||||
|
public Magazyn? Magazyn
|
||||||
|
{
|
||||||
|
get => Context.GetOrDefault<Magazyn>();
|
||||||
|
set => Context.Set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Translate]
|
||||||
|
public TypTowaru Typ {
|
||||||
|
get => Context.GetOrDefault<TypTowaru>();
|
||||||
|
set => Context.Set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Accessor(AutoChange = true)]
|
||||||
|
[Caption("Szukaj")]
|
||||||
|
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
|
||||||
|
<!-- Bindowanie w viewform.xml -->
|
||||||
|
<Flow Name="FilterPanel">
|
||||||
|
<Field CaptionHtml="Magazyn" EditValue="{TowaryParams.Magazyn}"/>
|
||||||
|
<Field CaptionHtml="Typ" EditValue="{TowaryParams.Typ}"/>
|
||||||
|
<Field CaptionHtml="Szukaj" EditValue="{TowaryParams.SearchString}"/>
|
||||||
|
</Flow>
|
||||||
|
```
|
||||||
|
|
||||||
|
```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)
|
||||||
|
- Obiektów worker (właściwości wyliczane, akcje)
|
||||||
|
- Obiektów extender (właściwości wyliczane)
|
||||||
|
- Tworzenia obiektów
|
||||||
|
|
||||||
|
## Trwałość wartości - LoadProperty / SaveProperty
|
||||||
|
|
||||||
|
Klasa `Params` jest tworzona od nowa przy każdym otwarciu widoku (każde wywołanie `InitContext`).
|
||||||
|
Aby wartości filtrów (okres, kategoria, zaznaczone opcje) **przeżyły między otwarciami**, zapisuje się
|
||||||
|
je do persystentnego storage operatora przez metody `LoadProperty` / `SaveProperty` klasy `ContextBase`.
|
||||||
|
|
||||||
|
### Sygnatury
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public void SaveProperty(string propertyName, string category = null);
|
||||||
|
|
||||||
|
public object LoadProperty(string propertyName, string category = null);
|
||||||
|
|
||||||
|
public T LoadProperty<T>(string propertyName, string category = null, T def = default);
|
||||||
|
|
||||||
|
// Wariant z out i wartością domyślną - dla typów wartościowych
|
||||||
|
public bool LoadProperty<T>(string propertyName, out T result,
|
||||||
|
string category = null, T def = default) where T : struct;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `propertyName` - nazwa property w obecnej klasie (najczęściej `nameof(X)`); silnik czyta wartość przez
|
||||||
|
refleksję przy zapisie i ustawia ją przy odczycie.
|
||||||
|
- `category` - klucz przestrzeni nazw w storage, zwykle stała typu `"CRM.Kontrahenci"`,
|
||||||
|
`"Płace.ListyPłac"`. Pozwala oddzielić ustawienia tego samego property w różnych widokach.
|
||||||
|
|
||||||
|
### Wzorzec konstruktora i pól
|
||||||
|
|
||||||
|
Każde "trwałe" property ma trzy elementy: prywatne pole, property z setterem zapisującym
|
||||||
|
oraz wywołanie `LoadProperty` w `Load()`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class Params : ContextBase
|
||||||
|
{
|
||||||
|
static readonly string key = "Modul.Widok";
|
||||||
|
|
||||||
|
public Params(Context context) : base(context)
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
FiltrKontrahentów filtr;
|
||||||
|
[Caption("Kontrahenci")]
|
||||||
|
public FiltrKontrahentów Filtr
|
||||||
|
{
|
||||||
|
get => filtr;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
filtr = value;
|
||||||
|
Context.InvokeChanged(); // powiadom UI
|
||||||
|
SaveProperty(nameof(Filtr), key); // zapisz do storage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Load()
|
||||||
|
{
|
||||||
|
// wariant z rzutowaniem object -> enum
|
||||||
|
var v = LoadProperty(nameof(Filtr), key, FiltrKontrahentów.Aktywni);
|
||||||
|
filtr = v == null ? FiltrKontrahentów.Aktywni : (FiltrKontrahentów)v;
|
||||||
|
|
||||||
|
// wariant generyczny z wartością domyślną - preferowany
|
||||||
|
status = LoadProperty(nameof(Status), key, StatusPodmiotuFiltr.Wszystkie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Setter property powinien:
|
||||||
|
1. zapisać wartość do pola,
|
||||||
|
2. powiadomić UI o zmianie (`Context.InvokeChanged()` lub `Session.InvokeChanged()`),
|
||||||
|
3. zapisać wartość przez `SaveProperty(nameof(X), key)`.
|
||||||
|
|
||||||
|
### Wartości przechowywane w Context (nie w polu)
|
||||||
|
|
||||||
|
Gdy wartość property żyje w `Context` (np. `KontaktOsoba`, `OddzialFirmy`, `Date`,
|
||||||
|
`DefinicjaListyPlac`), używa się helpera `SetContext(Type, object)` do odczytania zapisanej wartości
|
||||||
|
i wstawienia jej z powrotem do context:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public KontaktOsoba Osoba
|
||||||
|
{
|
||||||
|
get => Context.GetOrDefault<KontaktOsoba>();
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Context.Set(value);
|
||||||
|
SaveProperty(nameof(Osoba), key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Load()
|
||||||
|
{
|
||||||
|
SetContext(typeof(KontaktOsoba), LoadProperty(nameof(Osoba), key));
|
||||||
|
SetContext(typeof(OddzialFirmy), LoadProperty(nameof(OddzialFirmy), key));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`SetContext` ustawia wartość do context tylko jeżeli jest różna od `null` lub gdy klucz jeszcze nie
|
||||||
|
istnieje - dzięki czemu nie nadpisuje wartości, którą wcześniej ustawił inny widok.
|
||||||
|
|
||||||
|
### Dziedziczenie - virtualne Load / Save
|
||||||
|
|
||||||
|
W rozbudowanej hierarchii (np. `WyplatyViewInfo.Params` -> `ListyPlacViewInfo.Params` -> `WdzParams`)
|
||||||
|
warto przeciążać `Load()` i `Save()` jako `virtual` / `override` i wywoływać `base`. Każda warstwa
|
||||||
|
zapisuje wyłącznie swoje properties pod własnym kluczem:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class Params : ListyPlacViewInfo.Params
|
||||||
|
{
|
||||||
|
static readonly string key = "Płace.Wypłaty";
|
||||||
|
|
||||||
|
public Params(Context context) : base(context) { }
|
||||||
|
|
||||||
|
protected override void Load()
|
||||||
|
{
|
||||||
|
base.Load();
|
||||||
|
SetContext(typeof(ListaPlac), LoadProperty(nameof(ListaPlac), key));
|
||||||
|
SetContext(typeof(Pracownik), LoadProperty(nameof(Pracownik), key));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Save()
|
||||||
|
{
|
||||||
|
base.Save();
|
||||||
|
SaveProperty(nameof(ListaPlac), key);
|
||||||
|
SaveProperty(nameof(Pracownik), key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
W tym wzorcu settery wywołują `Save()` zamiast pojedynczego `SaveProperty(...)` - dzięki temu
|
||||||
|
jeden zestaw zmian zapisuje całą warstwę naraz, a w klasach pochodnych nie trzeba duplikować
|
||||||
|
zapisów rodzica.
|
||||||
|
|
||||||
|
### Co warto zapisywać
|
||||||
|
|
||||||
|
- filtry tekstowe i enum-y wybierane przez użytkownika,
|
||||||
|
- okresy / daty filtrowania,
|
||||||
|
- aktualnie wybrane słowniki (kategorie, branże, role, listy płac, oddziały),
|
||||||
|
- ustawienia widoczności / trybu (np. `ObowiazywanieZgody`).
|
||||||
|
|
||||||
|
### Czego NIE zapisywać
|
||||||
|
|
||||||
|
- pól wyliczanych (`IsReadOnly...`, `IsVisible...`),
|
||||||
|
- wartości pochodzących z konfiguracji programu (czyta się je przy każdym `Load`),
|
||||||
|
- ulotnych pól typu `string Szukaj` (opcjonalnie - decyzja projektowa; w `KontrahenciViewInfo`
|
||||||
|
jest zapisywane, w `WParams` z `KSeFiewInfo` nie).
|
||||||
|
|
||||||
|
## Powiadamianie UI o zmianach - `InvokeChanged` (nowy wzorzec)
|
||||||
|
|
||||||
|
Wcześniejszy kod używał `OnChanged(EventArgs.Empty)` z poziomu `ContextBase`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// PRZESTARZAŁE
|
||||||
|
set
|
||||||
|
{
|
||||||
|
filtr = value;
|
||||||
|
OnChanged(EventArgs.Empty);
|
||||||
|
SaveProperty(nameof(Filtr), key);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Zalecane** są nowsze formy:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// preferowane - z Context
|
||||||
|
set
|
||||||
|
{
|
||||||
|
filtr = value;
|
||||||
|
Context.InvokeChanged();
|
||||||
|
SaveProperty(nameof(Filtr), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// alternatywnie - z poziomu Session
|
||||||
|
set
|
||||||
|
{
|
||||||
|
filtr = value;
|
||||||
|
Session.InvokeChanged();
|
||||||
|
SaveProperty(nameof(Filtr), key);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Dla properties **bez powiązania z polem trwałym** (czyli pole prywatne wewnątrz klasy, bez wpływu na
|
||||||
|
serializację) wystarczy atrybut `[Accessor(AutoChange = true)]` - silnik sam wywoła powiadomienie po
|
||||||
|
ustawieniu wartości:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Accessor(AutoChange = true)]
|
||||||
|
[Caption("Szukaj")]
|
||||||
|
public string SearchString { get; set; }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dobre praktyki
|
||||||
|
|
||||||
|
1. **Dziedzicz z ContextBase** dla własnych klas parametrów i pamiętaj o konstruktorze `(Context context)`.
|
||||||
|
2. **Stosuj `[Accessor(AutoChange = true)]`** lub `Context.InvokeChanged()` / `Session.InvokeChanged()`
|
||||||
|
do powiadamiania UI. **Nie używaj** już `OnChanged(EventArgs.Empty)` - jest przestarzałe.
|
||||||
|
3. **Używaj `[Translate]`, `[TranslateIgnore]`, `[Caption("Tytuł")]`** dla property klas parametrów (ContextBase).
|
||||||
|
4. **Trwałość filtrów**: każdy zapisywany property w setterze woła `SaveProperty(nameof(X), key)`,
|
||||||
|
a `Load()` (wywołane z konstruktora) odczytuje wartość przez `LoadProperty`. Dla wartości
|
||||||
|
przechowywanych w context używaj `SetContext(typeof(T), LoadProperty(...))`.
|
||||||
|
5. **Stała `key`** - jedna na klasę, najczęściej `static readonly string key = "Modul.Widok"`,
|
||||||
|
identyfikuje przestrzeń w storage operatora.
|
||||||
|
6. **W hierarchii** rób `protected override void Load()` / `Save()` i wołaj `base` - każda warstwa
|
||||||
|
zapisuje własne properties pod własnym `key`.
|
||||||
@@ -273,7 +273,7 @@ public void UsunTowar(Login login, string kod)
|
|||||||
|
|
||||||
## Praca z kontekstem
|
## Praca z kontekstem
|
||||||
|
|
||||||
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).
|
Przykłady klasy parametrów dziedziczącej z `ContextBase` - patrz [contextbase.md](contextbase.md). Współdzielenie 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
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,6 @@ public class StanTowaruWorker
|
|||||||
## Dobre praktyki
|
## Dobre praktyki
|
||||||
|
|
||||||
1. **Używaj [Context]** w obiektach worker i extender dla parametrów inicjowanych z context
|
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))
|
2. **Dziedzicz z ContextBase** dla własnych klas parametrów (patrz [contextbase.md](contextbase.md))
|
||||||
3. **Metody Action zwracają [action result](./action-result.md)** - nie wywołuj UI bezpośrednio
|
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()`
|
4. **`CommitUI()` zamiast `Commit()`** - w workerach/extenderach uruchamianych z UI używaj `CommitUI()`
|
||||||
|
|||||||
Reference in New Issue
Block a user