9.3 KiB
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.
Definicja klasy parametrów
Klasy parametrów filtrów dziedziczą z ContextBase i mają konstruktor przyjmujący Context:
// 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
ContextBasewymaga kontroli tłumaczenia za pomocą jednego z atrybutu:[Translate],[TranslateIgnore],[Caption("Tytuł")] - Bindowanie we viewform.xml wewnątrz
FilterPanelnie wymaga użyciaContext.(npTowaryParams), ponieważContextjest dostępne bezpośrednio wewnątrzFilterPanel - Bindowanie w pageform.xml wymaga użycia
Context.(npContext.TowaryParams) - Gdy property nie używa context -> stosuj
[Accessor(AutoChange = true)]- zamiennie w kodzie set property można również użyćSession.InvokeChanged()lubContext.InvokeChanged()
<!-- 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>
<!-- 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
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ęściejnameof(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():
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:
- zapisać wartość do pola,
- powiadomić UI o zmianie (
Context.InvokeChanged()lubSession.InvokeChanged()), - 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:
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:
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; wKontrahenciViewInfojest zapisywane, wWParamszKSeFiewInfonie).
Powiadamianie UI o zmianach - InvokeChanged (nowy wzorzec)
Wcześniejszy kod używał OnChanged(EventArgs.Empty) z poziomu ContextBase:
// PRZESTARZAŁE
set
{
filtr = value;
OnChanged(EventArgs.Empty);
SaveProperty(nameof(Filtr), key);
}
Zalecane są nowsze formy:
// 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:
[Accessor(AutoChange = true)]
[Caption("Szukaj")]
public string SearchString { get; set; }
Dobre praktyki
- Dziedzicz z ContextBase dla własnych klas parametrów i pamiętaj o konstruktorze
(Context context). - Stosuj
[Accessor(AutoChange = true)]lubContext.InvokeChanged()/Session.InvokeChanged()do powiadamiania UI. Nie używaj jużOnChanged(EventArgs.Empty)- jest przestarzałe. - Używaj
[Translate],[TranslateIgnore],[Caption("Tytuł")]dla property klas parametrów (ContextBase). - Trwałość filtrów: każdy zapisywany property w setterze woła
SaveProperty(nameof(X), key), aLoad()(wywołane z konstruktora) odczytuje wartość przezLoadProperty. Dla wartości przechowywanych w context używajSetContext(typeof(T), LoadProperty(...)). - Stała
key- jedna na klasę, najczęściejstatic readonly string key = "Modul.Widok", identyfikuje przestrzeń w storage operatora. - W hierarchii rób
protected override void Load()/Save()i wołajbase- każda warstwa zapisuje własne properties pod własnymkey.