Wprowadzenie

Wprowadzenie

Aplikacja POS stworzona została z użyciem technologii Windows Presentation Foundation. Szkielet programu podzielić można na kilka sekcji. Podstawową sekcją jest zawartość – jest to centralny obszar aplikacji, w którym prezentowana jest treść w postaci widoków. Drugim elementem jest sekcja nagłówka, nierozerwalnie powiązana z zawartością (widokami), która jest swego rodzaju tytułem dla treści prezentowanych w zawartości. Oprócz tego możemy również wyróżnić sekcję statusu, w której znajdują się przyciski szybkiego dostępu (takie jak zamknij, minimalizuj) oraz przedstawiona jest informacja o aktualnie zalogowanym użytkowniku.

W sekcji zawartości wyświetlane są widoki. Wyróżniamy ich trzy rodzaje:

  • widok podstawowy (primary view)
  • widok modalny (modal view) – widok wyświetlany na widoku podstawowym. Widok przysłania i blokuje możliwość interakcji użytkownika na widoku podstawowym. Nie blokowana jest natomiast logika. Po zamknięciu następuje powrót do widoku podstawowego.
  • widok komunikatu (message view) – widok wyświetlany na widoku podstawowym lub modalnym. Widok przysłania iblokuje możliwość interakcji użytkownika z widokami znajdującymi się pod spodem. Nie blokowana jest natomiast logika. Widok charakteryzuje się rozciągnięciem na całą szerokość ekranu, oraz tłem w kolorze czcionki widoku podstawowego. Typowe użycie: komunikat błędu, pytanie podczas zamykania aplikacji, itp.

Każdy z nich tworzony jest w taki sam sposób (patrz artykuł Tworzenie widoków). To, w jaki sposób widok zostanie zaprezentowany, zależy od sposobu jego otwarcia (więcej w artykule Nawigacja między widokami).

 

 

 




Tworzenie widoków

Nowy moduł

Stworzenie nowego widoku rozpoczniemy od utworzenia nowego modułu POS. W Visual Studio wybieramy nowy projekt WPF Custom Control Library. Ten typ projektu automatycznie wygeneruje nam niezbędną strukturę oraz pozwoli na szybkie dodawanie nowych elementów. Na początku nasz pusty projekt będzie składał się z folderu Themes, w którym będzie plik Generic.xaml oraz pliku CustomControl1.cs, które możemy usunąć.

Kolejnym krokiem będzie utworzenie klasy Module.cs, która będzie odpowiedzialna za rejestrację naszego modułu w aplikacji POS. Klasa ta musi dziedziczyć po klasie bazowej ModuleBase (Comarch.POS.Presentation.Core) oraz odpowiednio implementować metodę Initialize. Wewnątrz tej metody będziemy rejestrować własne serwisy, widoki oraz viewmodele, rejestrować przyciski w menu głównym POS, rejestrować własne kontrolki, zarządzać widocznością właściwości kontrolek w zarządzaniu interfejsem, wpinać się (rozszerzać) istniejące w systemie kontenery oraz datagridy.

Aby nasz przyszły widok był zarządzalny przez użytkownika w trakcie działania aplikacji POS, należy w folderze Themes dodać nowy element typu Resource Dictionary (prawym na folderze Themes, następnie z menu kontekstowego wybieramy kolejno: Add, Resource Dictionary…). Nadajemy mu nazwę ModernUI.xaml i zapisujemy. W pliku tym będziemy definiować domyślne właściwości dla zarządzalnych elementów interfejsu (widoków, kontrolek) – więcej w artykule Zarządzanie widokiem i jego elementami. Na koniec musimy jeszcze zarejestrować ten zasób. Robimy to w konstruktorze klasy Module, dopisując następującą linijkę:

 

LayoutService.RegisterResources(typeof(Module));

 

(LayoutService jest klasą statyczną znajdującą się w przestrzeni Comarch.POS.Presentation.Core.Services).

Utworzenie widoku

Tworzenie nowego widoku rozpoczniemy od utworzenia folderu Views, w którym będziemy przechowywać wszystkie widoki projektu, oraz folder ViewModels na viewmodele. Następnie w folderze Views dodajemy nowy element typu User Control (WPF), przykładowo o nazwie OrdersView.xaml.

Kolejnym krokiem jest zmiana typu UserControl na View z przestrzeni Comarch.POS.Presentation.Core.

 

<core:View x:Class="Comarch.POS.Presentation.Sales.Views.OrdersView"
           xmlns:core="clr-namespace:Comarch.POS.Presentation.Core;assembly=Comarch.POS.Presentation.Core"

 

 W code-behind usuwamy dziedziczenie po UserControl i implementujemy wymagany interfejs View.

Właściwość Header to string wyświetlany w sekcji nagłówka aplikacji – ustawienie jej wymagane jest tylko dla widoków, które będą otwierane jako widoki podstawowe, ponieważ tylko one mają prezentację nagłówka. W pozostałych przypadkach (tj. widok modalny oraz komunikatu) wystarczy ustawić String.Empty. Istnieje również możliwość utworzenia własnego nagłówka sekcji z dowolną zawartością – więcej w Dodanie własnego nagłówka.

Właściwość HeaderLayoutId powinna natomiast zawierać unikalną nazwę dla identyfikatora layout id, niezbędnego do poprawnego działania zarządzania widokiem przez użytkownika POS. Jeżeli widok nie będzie zarządzalny to właściwość tę możemy ustawić jako String.Empty. Dla potrzeby tej dokumentacji ustalamy tutaj wartość „OrdersViewId”.

Konstruktor bazowy wymaga przekazania obiektu typu IViewModel, dlatego teraz przejdziemy do utworzenia viewmodelu. W folderze ViewModels najpierw tworzymy folder o nazwie Orders. Następnie wewnątrz niego dodajemy nową klasę OrdersViewModel oraz interfejs IOrdersViewModel. Interfejs powinien być publiczny i dziedziczyć po IViewModel. Klasa OrdersViewModel także powinna być publiczna oraz powinna implementować klasę bazową ViewModelBase oraz interfejs IOrdersViewModel.

 

Dziedzicząc po ViewModelBase będziemy musieli zaimplementować metodę IsTarget. Wywoływana jest podczas nawigacji i pozwala instancji viewmodelu stwierdzić czy to ona powinna zostać aktywowana czy też nie (więcej na ten temat w artykule Nawigacja między widokami) – na chwilę obecną definiujemy, aby zwracała true.

Dodatkowo, jeżeli widok ma być zarządzalny przez użytkownika POS, musimy dodać jeszcze jedną klasę, viewmodel o nazwie DesignOrdersViewModel, która będzie dziedziczyć po DesignViewModelBase oraz implementować interfejs IOrdersViewModel. Całościową strukturę przedstawia rysunek zamieszczony obok.

Teraz wracamy do code-behind klasy OrdersView (plik OrdersView.xaml.cs) i parametryzujemy konstruktor, tak aby przyjmował parametr typu IOrdersViewModel o nazwie viewModel (uwaga ta nazwa jest istotna i zawsze musi mieć taką właśnie nazwę!). Następnie uzupełniamy konstruktor bazowy przekazując mu naszą zmienną viewModel.

Ostatnim krokiem jest rejestracja naszego nowego widoku, która została opisana w rozdziale Rejestracja widoków do nawigacji oraz zarządzania wyglądem.

Gotowy szablon tworzenia modułu z pustym widokiem dostępny w przykładzie Prosty moduł z nowym pustym widokiem.

Dodanie własnego nagłówka

Dla każdego widoku podstawowego można ustawić tekst nagłówka, który będzie prezentowany w górnej części aplikacji POS. Domyślnie można tam umieścić tylko łańcuch znakowy zdefiniowany we właściwości Header na widoku. W przypadku zaistnienia potrzeby umieszczenia w nagłówku bardziej złożonej struktury, można utworzyć własny widok nagłówka (custom header).

Definiowanie własnego nagłówka rozpoczynamy od dodania do folderu z widokiem nowego elementu User Control (WPF). Dla zwiększeni czytelności element przyjmie nazwę naszego widoku z przyrostkiem Header (w przypadku widoku OrdersView będzie to nazwa OrdersViewHeader).

Ostatnią czynnością będzie powrót do code-behind naszego widoku OrdersView i ustawienie w konstruktorze właściwości CustomHeaderType na typ naszego customowego nagłówka. Widokowi nagłówka automatycznie zostanie przypisany DataContext wskazujący na ViewModel widoku. W tym przypadku będzie to OrdersViewModel. Dzięki temu w naszym nagłówku będziemy mogli używać bindingu bezpośrednio do właściwości na viewmodelu widoku.

 

public partial class OrdersView 
{
        public OrdersView(IOrdersViewModel viewModel) : base(viewModel)
        {
            CustomHeaderType = typeof (OrdersViewHeader);
            InitializeComponent();
        }
…
…

 

Rejestracja widoków do nawigacji oraz zarządzania wyglądem

Po utworzeniu widoku (zaimplementowaniu odpowiednio logiki działania w view-modelu oraz UI w xaml-u), niezbędnym do działania naszego widoku krokiem jest jego rejestracja. Aby tego dokonać otwieramy klasę Module, utworzoną podczas tworzenia projektu (parz Nowy moduł). W metodzie Initialize dodajemy następujące linijki:

 

Register<IOrderViewModel, OrderViewModel>();
RegisterViews(new ViewStructure<OrdersView, DesignOrdersViewModel>("OrdersView", Resources.ResourceManager);

 

Pierwsza z nich rejestruje nasz viewmodel w kontenerze, dzięki czemu w konstruktorze widoku automatycznie zostanie wstrzyknięta instancja interfejsu IOrdersViewModel.

Druga metoda pozwala na zarejestrowanie widoku w trybie zarządzania jego interfejsem przez użytkownika POS w trakcie działania aplikacji. Dzięki temu użytkownik po wejściu w widok zarządzania interfejsem, na liście widoków zobaczy nasz widok. Będzie mógł go otworzyć do edycji i zmienić wygląd wszystkim tym elementom, które zdefiniujemy jako zarządzalne (więcej o tym jak to zrobić w artykule Zarządzanie widokiem i jego elementami). Metoda wymaga przekazania dwóch parametrów. Pierwszy z nich to nazwa klucza w zasobach, natomiast drugi to instancja menadżera zasobów. Parametry te pozwalają na prezentację nazwy widoku w drzewie widoków w zarządzaniu widokami aplikacji POS.

Alternatywnie, jeżeli nie chcemy rejestrować widoku w trybie zarządzania interfejsem, musimy skorzystać z metody RegisterForNavigation<TView >() zamiast RegisterViews.

 

RegisterForNavigation<OrdersView>();

Dodanie widoku do menu głównego

Każdy widok może zostać dodany do menu głównego w postaci kafelka (TileButton), w celu umożliwienie otwarcia widoku w trybie podstawowym. Dodając widok do menu głównego zostanie on również automatycznie dodany do wysuwalnego menu bocznego, które możemy wywołać z poziomu dowolnego widoku podstawowego.

Aby dodać wcześniej utworzony widok w postaci kafelka do menu należy w metodzie Initialize klasy Module zarejestrować widok za pomocą metody RegisterMenuTile<TView>, gdzie TView to nazwa klasy widoku. Parametrami wywołania są natomiast odpowiednio nazwa klucza w zasobach, menager zasobów. Opcjonalnie można również zdefiniować delegat canExecute. W przypadku zwrócenia false, kafel będzie wyszarzony i nieklikalny. Można także podać delegat do akcji, które mają zostać wykonane przed otwarciem widoku lub/oraz zdefiniować uprawnienia, jakie będą wymagane do otwarcia widoku wraz z kluczem w zasobach, gdzie będzie zapisany komunikat, który pojawi się w oknie modalnym podnoszenia uprawnień, w przypadku gdy uprawnienia nie będą spełnione (więcej o uprawnieniach Weryfikacja uprawnień). Dla naszego przykładu zarejestrujemy wcześniej utworzony widok OrdersView.

 

RegisterMenuTile<OrdersView>("OrdersView", Resources.ResourceManager);

 

Po zarejestrowaniu widoku w menu głównym, widok będzie można otworzyć za pomocą odpowiedniego kafelka tam widocznego. Domyślnie kafelek będzie wyglądał tak, jak został zdefiniowany w globalnej konfiguracji interfejsu POS. Aby zmienić jego właściwości wizualne, należy dodać odpowiednie wpisy w pliku ModernUI.xaml, znajdującym się w folderze Themes. Przykładowo, aby ustawić nowy domyślny kolor dla naszego nowego kafelka, otwierającego widok OrdersView, należy dodać następujący wpis:

 

<SolidColorBrush x:Key="OrdersView.Default.Background" Color="Red" />

 

Każdy klucz w MordernUI będzie miał następujący format:

[LayoutId lub Nazwa typu kontrolki].Default.[Nazwa właściwości]

Natomiast cała definicja:

<[Nazwa typu właściwości] x:Key=”[klucz wg. formatu powyżej]” [atrybuty]>[wartość]</[Nazwa typu właściwości>

Atrybuty oraz wartość nie jest obowiązkowa i zależy stricte od typu właściwości, z którą mamy do czynienia.

LayoutId dla kafelka widoku OrdersView jest definiowany w pierwszym parametrze metody RegisterMenuTile. W naszym przypadku LayoutId użyty w kluczu definiującym kolor kafelka to OrdersView, ponieważ taką wartość ustawiliśmy wcześniej wywołując metodę rejestrującą widok w postaci kafla w menu głównym. Właściwości, wspierane przez zarządzanie interfejsem, zależne są od typu kontrolki. W przypadku kafelków jest to kontrolka TileButton. Wykaz właściwości wspieranych dla poszczególnym kontrolek można znaleźć tutaj: Lista wspieranych właściwości

Wpięcie modułu do aplikacji POS

Ostatnim etapem po skompilowaniu tworzonego modułu jest jego uruchomienie w środowisku docelowym, czyli w aplikacji POS. W tym celu zbudowany moduł w postaci biblioteki (lub bibliotek) należy skopiować do katalogu instalacyjnego aplikacji POS. Następnie otworzyć do edycji plik POS.exe.config, odnaleźć w nim sekcję <modules> i dopisać nowy moduł na końcu tej sekcji, wg wzoru:

<module assemblyFile=”[nazwa_modulu].dll” moduleType=”[namespace_klasy_module].Module, [namespace_klasy_module]” moduleName=”[ nazwa_modulu]” />

W przypadku posiadania większej liczby bibliotek, rejestrujemy tylko te, które posiadają zaimplementowaną klasę Module.

 




Nawigacja między widokami

W aplikacji POS możemy otwierać dowolną liczbę widoków w różnych trybach (widoku podstawowego, modalnego lub komunikatu), przy czym w danej chwili aktywny może być tylko jeden z każdej grupy. Pozostałe otwarte określone są mianem nieaktywnych. W każdym momencie będąc na dowolnym widoku podstawowym możemy wywołać okno przełączania pomiędzy widokami podstawowymi (menu nawigacji), w celu aktywowania wybranego innego widoku, który został wcześniej otwarty i nie został zamknięty (stał się nieaktywny). Menu nawigacji nie możemy wywołać na widoku modalnym ani na widoku komunikatu.

Menu nawigacji

Menu nawigacji wywołać możemy za pomocą skrótu klawiszowego CTRL+TAB lub za pomocą przycisku  widocznego w sekcji statusowej obok zegara. W oknie tym otwarte widoki podstawowe reprezentowane są w postaci różnokolorowych kafli z tytułem, ikoną i/lub dodatkowym opisem. Tytuł kafla ustawić możemy za pomocą właściwości Header w klasie View lub za pomocą właściwości SwitchHeader na viewmodelu (jeżeli chcemy żeby różnił się od nagłówka widoku). Ikonę oraz kolor ustawiamy za pomocą styli w pliku ModernUI.xaml. Ustawienie koloru i ikony wykonywane jest w dokładnie taki sam sposób jak podczas definiowania koloru i ikony dla rejestrowanego kafla w menu głównym. Dlatego, jeżeli widok został zarejestrowany już w menu głównym to kafel w oknie menu nawigacji przyjmie kolor i ikonę taką jak kafel w menu głównym pod warunkiem, że wartość klucza resourcesów (pierwszy argument) użyta w metodzie RegisterMenuTile jest taka sama jak wartość właściwości HeaderLayoutId w code-behind widoku. W naszym przypadku nazwy się różnią dlatego wymagane będzie dodanie dodatkowych wpisów w ModernUI.xaml.

Definiowanie w ModerUI.xaml koloru i ikony kafla w oknie menu nawigacji dla przykładowego widoku OrdersView:

 

<SolidColorBrush x:Key="OrdersViewId.Default.Background" Color="Red" />
<models:ImageKey x:Key="OrdersViewId.Default.ImageKey" SvgValue="ListIcon" />

 

Należy pamiętać, że w powyższym przypadku OrdersViewId to wartość przypisana do HeaderLayoutId w klasie widoku.

Dodatkowo, istnieje możliwość dodania w kaflu dodatkowego opisu (tak jak to widać na kaflu Nowe zamówienie na powyższym zrzucie ekranu). Za ten tekst odpowiada właściwość SwitchHeader2 w klasie view-modelu.

Otwieranie widoków

Widoki możemy otwierać w kilku trybach (jako widok podstawowy, widok modalny lub też widok komunikatu). Możemy sterować czy dany widok ma zostać otwarty jako niezależny od innych widoków (oddzielna pozycja w oknie menu nawigacji), czy ma być potomkiem obecnie aktywnego (brak nowej pozycji w menu nawigacji). Otwierając widok możemy ustawić parametr IsPreviewMode (otwiera widok w trybie tylko do odczytu, większość kontrolek jest automatycznie blokowana do edycji) oraz dowolną liczbę własnych parametrów.

Widoki możemy otwierać za pomocą metod dostępnych w klasie ViewModelBase oraz w serwisie IViewManager. Najprostszym sposobem otwarcia widoku podstawowego jest wywołanie metody OpenView<TView>(), gdzie TView to nazwa klasy widoku, który chcemy otworzyć lub aktywować, jeżeli już jest otwarty. Dodatkowe parametry:

  • isChild (bool) – parametr określający czy otwierany widok ma być potomkiem aktualnie aktywnego,
  • parameters (NavigationParameters) – parametr pozwalający na przekazania dowolnej liczby własnych parametrów do viewmodelu otwieranego widoku,
  • isPreviewMode (bool) – parametr, który dostępny jest już w konstruktorze viewmodelu otwieranego widoku (w przeciwieństwie do parameters, który dostępny jest dopiero w metodach inicjalizacyjnych viewmodelu, o których więcej tutaj Kolejność wywołań metod ViewModelu podczas nawigacji). Parametr służy do otwierania widoku w trybie podglądu (z ograniczoną funkcjonalnością). Ustawienie tego parametru na true spowoduje ustawienie właściwości tylko do odczytu IsPreviewMode w klasie ViewModelBase. Dodatkowo, kontrolki TextBox, SwitchBox, RadioButton, NumericTextBox, ComboBox, CheckBox z przestrzeni Comarch.POS.Presentation.Core przejdą w tryb tylko do odczytu, jeżeli tylko któraś z nich znajdzie się w tym widoku.

Aby otworzyć widok w trybie widoku modalnego należy skorzystać z metody:

OpenModalView<TView>(), gdzie TView to nazwa klasy widoku, który chcemy otworzyć lub aktywować, jeżeli jest już otwarty. Dodatkowe parametry:

  • parameters (NavigationParameters) – analogicznie jak dla OpenView
  • isPreviewMode (bool) – analogicznie jak dla OpenView

Metoda nie posiada parametru isChild, ponieważ wszystkie widoki modalne otwierane są w trybie hierarchicznym (są potomkiem widoku, który go otworzył). Wyjątkiem jest brak relacji pomiędzy różnymi typami widoków. Przykładowo otwierając widok modalny z aktywnego widok podstawowego nie zostanie utworzona zależność parent-child.

W celu otwarcia widoku w trybie widoku komunikatu należy wykonać dwa kroki. Pierwszy z nich to ustawienie w xamlu widoku wyrównania poziomego:

<core:View x:Class="Comarch.POS.Presentation.Sales.Views.OrdersView"
                      HorizontalAlignment="Stretch"
…

 

Natomiast drugi to wywołanie metody:

OpenMessageView<TView>(), dostępnej tylko w serwisie ViewManager, gdzie TView to nazwa klasy widoku, który chcemy otworzyć. Analogicznie jak dla widoku modalnego, relacja parent-child występuje zawsze z pominięciem relacji pomiędzy różnymi typami widków. Dodatkowe parametry:

  • parameters (NavigationParameters) – analogicznie jak dla OpenView i OpenModalView,
  • isPreviewMode (bool) – analogicznie jak dla OpenView I OpenModalView

Jeżeli chcemy tylko wyświetlić prosty komunikat lub pytanie z dowolnymi przyciskami, wystarczy, że skorzystamy z serwisu MonitService. Dostępne tam są metody takie jak:

  • ShowInformation – metoda wyświetlająca widok komunikat z dowolną treścią oraz przyciskiem OK,
  • ShowError – metoda wyświetlając widok komunikatu z treścią wyjątku i przyciskiem OK,
  • ShowQuestion – metoda wyświetlająca widok komunikatu dowolną treścią oraz z przyciskami decyzyjnymi TAK i NIE,
  • Show – metoda wyświetlająca widok komunikatu z dowolną treścią oraz dowolnymi przyciskami akcji predefiniowanymi OK, TAK/NIE) lub dowolną liczbą własnych przycisków.

Więcej na temat komunikatów w artykule Notyfikacje i Komunikaty.

Zamykanie widoków

W przypadku zamykania widoków, mamy do dyspozycji metody pozwalające na zamykanie aktualnie aktywnego widoku, metodę do zamykania wybranego widoku (z opcją zamknięcia również wszystkich jego przodków). Zamykając widok możemy, analogicznie jak w przypadku otwierania, dodać dodatkowe własne parametry, które zostaną przekazane do widoku, który zostanie aktywowany po zamknięciu obecnego.

Zamykając aktywny widok, zawsze zostanie aktywowany widok, z którego zamykany widok został wcześniej otwarty. W przypadku, gdy widoki są w relacji rodzic-dziecko (podczas otwieranie widoku IsChild=true), zamykając widok dziecka powrócimy do widoku rodzica.

Aby zamknąć aktywny widok należy użyć z metody:

Będąc na viewmodelu należy wywołać metodę Close(), która bezpośrednio wywołuje metodę CloseView w ViewManagerze przekazując swój widok jako parametr. Metoda Close zamyka zawsze widok powiązany z aktualnym viewmodelem. Dodatkowe parametry:

  • parameters (NavigationParameters) – parametr pozwalający na przekazanie informacji z zamykanego widoku do widoku, który zostanie aktywowany

 Aby zamknąć wskazany widok (aktywny lub nieaktywny) należy użyć metody:

CloseView(), znajdującej się w serwisie IViewManager. Dodatkowe parametry metody:

  • view (IView) – parametr określający jaki widok ma zostać zamknięty,
  • closeParents (bool) – parametr definiujący czy zamknąć również wszystkie widoki zależne w relacji rodzic-dziecko (widoki otwierane z parametrem IsChild=true). Domyślnie wartość ustawiona na true.

Kolejność wywołań metod nawigacyjnych

Podczas nawigacji pomiędzy widokami (otwieranie, zamykanie, przełączania aktywnego widoku) wywoływane są specjalne metody w view-modelach biorących udział w nawigacji. Metody te pozwalają na wykonanie odpowiednich akcji w zależności od tego czy widok jest otwierany po raz pierwszy, aktywowany ponownie, deaktywowany, czy też zamknięty. Dostarczają również parametry, które wysyłamy podczas wywoływania metod otwierających oraz zamykających.

Metody nawigacyjne dostarcza klasa bazowa każdego viewmodelu – ViewModelBase. Są nimi metody OnInitialization, OnActivation, OnActivated, OnDeactivted, Dispose oraz IsTarget. Odpowiednie metody są automatycznie wywoływane podczas procesu otwierania zarówno na widoku, z którego otwieramy nowy, jak i na docelowym. W przypadku procesu zamykania określone metody są wywoływane na zamykanym widoku oraz na widoku, który zostanie aktywowany w wyniku zamknięcia obecnego. Tak samo w przypadku przełączania się pomiędzy otwartymi widokami. Istotną kwestią jest to jakie metody wywoływane są podczas określonej nawigacji oraz ich kolejność.

Proces otwierania, zamykania i przełączania między widokami obrazuje poniższy diagram:

Opis metod:

  • IsTarget (ViewModelBase)

Wywoływana jest podczas otwierania widoku, przed konstruktorem widoku, na wszystkich instancjach viewmodeli widoków zgodnych typem z otwieranym. Jeżeli nie istnieje żadna instancja widoku typu otwieranego to zostanie utworzony nowy widok. Jeżeli istnieją już otwarte widoki tego typu, metoda będzie wywoływać się na każdym viewmodelu widoku w kolejności ich otwarcia. Jeżeli których z nich wróci true to zostanie aktywowany. Jeżeli natomiast żaden istniejący widok tego typu nie wróci true, wtedy zostanie utworzona nowa instancja.

  • OnInitialization (ViewModelBase)

wywoływana jest w ViewModelu tylko podczas otwierania nowego widoku (nowa instancja, nowa zakładka). Brak dostępu do rodzica (wywołanie ViewManager.GetParentViewModel(this) zwróci null).
Uwaga: w tej metodzie nie należy otwierać ani zamykać widoków!

  • OnActivation (ViewModelBase)

wywoływana jest podczas każdego otwarcia widoku czy też samej aktywacji. Brak dostępu do rodzica (wywołanie ViewManager.GetParentViewModel(this) zwróci null).
Uwaga: w tej metodzie nie należy otwierać ani zamykać widoków!

  • OnActivated (ViewModelBase)

wywoływana jest podczas każdego otwarcia widoku czy też samej aktywacji.

  • OnDeactivated (ViewModelBase)

wywoływana jest podczas deaktywacji lub zamknięcia widoku.

  • Dispose (ViewModelBase)

wywoływana jest tylko podczas zamykania widoku. Pozwala na zwolnienie zasobów niezarządzalnych przez Garbage Collector lub zatrzymanie wątków potomnych.

  • OnApplyViewTemplate (View)

Wywoływana w code-behind klasy widoku, jednorazowo po utworzeniu instancji widoku i po załadowaniu wszystkich składowych (kontrolek) widoku.




Notyfikacje i komunikaty

Podstawowym sposobem komunikacji aplikacji POS z użytkownikiem są notyfikacje i komunikaty. Notyfikacje, inaczej powiadomienia, pozwalają na przekazanie krótkiej informacji nieblokującej pracy użytkownika, w postaci prostokątnego bloczka pojawiającego się z prawej strony ekranu pod sekcją statusową. Komunikaty natomiast służą do przekazania dłuższych lub ważnych informacji, nie pozwalając użytkownikowi ich zignorować, ponieważ prezentowane są w postaci widoku komunikatu. Powszechnie stosowane w przypadku wystąpienia błędów.

Notyfikacje (powiadomienia)

Każda notyfikacja składa się z prezentowanej treści (zbyt długi tekst, powyżej 6 wierszy, będzie ucinany) oraz ikony. Wyróżniamy trzy typu powiadomień różniące się ikoną:

  1. Informacja
  2. Ostrzeżenie
  3. Błąd

Po wywołaniu powiadomienia, pojawia się ono z prawej strony ekranu i znika po domyślnie po trzech sekundach. Czas prezentacji notyfikacji można zmienić w pliku konfiguracyjnym aplikacji, poprzez modyfikację wartości klucza NotificationTimeout. W przypadku, gdy użytkownik najedzie myszką na notyfikację, nie zniknie ona i będzie prezentowane tak długo, jak długo kursor myszki będzie znajdował się w jej obszarze. Jeżeli w trakcie prezentacji powiadomienia, pojawi się kolejne, to pierwsze przesunie się poniżej. Na ekranie w jednej chwili możliwe jest wyświetlenie pięciu powiadomień. Gdy będzie ich więcej, kolejne zostaną zbuforowane i zaprezentowane  w momencie znikania aktualnie widocznych.

W celu wyświetlenie notyfikacji należy skorzystać z serwisu INotificationService (każdy viewmodel ma dostęp do tego serwisu poprzez właściwość NotificationService) i wywołać metodę Show(string msg, NotifyIcon icon), gdzie parametr msg to treść komunikatu, a icon to enum określający typ powiadomienia (czyli ikonkę).

Komunikaty

Komunikaty prezentowane są za pomocą widoku komunikatu (charakterystyka tego widoku została opisana w artykule Wprowadzenie). Każdy komunikat składa się z nagłówka (krótkiego tytułu prezentowanego większą czcionką w porównaniu do właściwego komunikatu), tekstu komunikatu (nie ograniczonego znakowo, przy dłuższym tekście pojawi się suwak pozwalający na jego przewijanie), opcjonalnej ikony (wybór z kilku predefiniowanych) oraz co najmniej jednego przycisku. Konfiguracja przycisków jest zależna od typu komunikatu, ale może być także definiowana własna.

W celu wyświetlenie komunikatu należy skorzystać z serwisu IMonitService (każdy viewmodel ma dostęp do tego serwisu poprzez właściwość MonitService) i wywołać jedną z metod, w zależności od potrzeb:

  • ShowInformation

Metoda wyświetla komunikat informacyjny. Ikona informacyjna i przycisk OK.

  • ShowError

Metoda wyświetla komunikat błędu. Ikona błędu i przycisk OK.

  • ShowQuestion

Metoda wyświetla komunikat pytający. Ikona pytania i przyciski: TAK, NIE.

  • Show

Metoda wyświetlająca komunikat dowolnie zdefiniowanego typu. Możemy określić jaka będzie ikona (wybrać z predefiniowanych: informacja, pytanie, ostrzeżenie, błąd lub brak). Wskazać jakie będą dostępne przyciski akcji (OK, TAK/NIE) lub zdefiniować własne.




Serwisy

Aplikacja POS korzysta z mechanizmu kontenerów Unity. Pozwala on na łatwy i szybki dostęp do wszystkich zarejestrowanych wcześniej serwisów w dowolnej klasie ViewModelu oraz View. Rozwiązuje też wszystkie zależności pod warunkiem, że zostały one również wcześniej zarejestrowane. Przykładem może być każdy widok, który pozyskiwany jest z kontenera z jednoczesnym wstrzyknięciem w jego konstruktorze zależnego view-modelu. Jest to możliwe ponieważ każdy view-model jest wcześniej rejestrowany w kontenerze (patrz Rejestracja widoków do nawigacji oraz zarządzania wyglądem). Z kolei temu viewmodelowi wstrzykiwane są inne wymagane serwisy, które są mu niezbędne (pod warunkiem, że zostały wcześniej zarejestrowane).

Pobieranie serwisów z kontenera

Pobieranie instancji wybranego serwisu możemy dokonać na trzy sposoby:

  • Zdefiniowanie publicznej właściwości z dodanym atrybutem Dependency z przestrzeni Practices.Unity.
[Dependency]
public IProductsService ProductsService { get; set; }
  • Wstrzyknięcie instancji w konstruktorze viewmodelu, poprzez prostą deklarację parametru.
public OrdersViewModel(IProductService productService) { … }
  • Wywołanie metody Resolve na obiekcie IUnityContainer dostępnym w klasie ViewModelBase pod właściwością Container.
var productsService = Container.Resolve<IProductsService>();

Utworzenie instancji serwisu wykonywane każdorazowo w chwili pobrania go z kontenera. Wyjątkiem jest przypadek serwisu zarejestrowanego jako singleton, którego instancja tworzona jest tylko raz podczas pierwszej próby pobrania.

Rejestracja własnych serwisów w kontenerze

Rejestracji serwisu możemy dokonać w dowolnym momencie, wszędzie tam gdzie mamy dostęp do obiektu IUnityContainer. Jeżeli chcemy mieć pewność, że serwis będzie dostępny od samego początku, należy go zarejestrować w klasie Module. Taka sytuacja ma miejsce dla wszystkich view-modeli. Serwis możemy zarejestrować na podstawie jego interfejsu. Jeżeli klasa serwisu nie ma interfejsu to nie trzeba jej rejestrować, chyba że chcemy, aby była dostępna jako singleton.

W celu rejestracji serwisu w klasie Module należy skorzystać z metody

Register<TInterface,TClass>(), gdzie TInterface to nazwa interfejsu, a TClass nazwa klasy implementującej ten interfejs. Dodatkowe parametry:

  • singleton (bool) – parametr określający czy pobierana z kontenera instancja ma być każdorazowa tworzona na nowo czy zwracana zawsze ta sama instancja. Domyślnie wartość tego parametru ustawiona jest na false

Poniżej zaprezentowano przykład rejestracji w klasie Module serwisu jako singleton, serwis jest klasy CustomService i implementuje interfejs ICustomService:

Register<ICustomService, CustomService>(true);

Jeżeli zależy nam na tym, aby zarejestrowany serwis od razu został zainstancjonowany, a zawiera zależności które będą wstrzykiwane automatycznie, musimy się upewnić, że będzie to możliwe. W przypadku, gdy serwis jest zależny od innego, który istnieje w innym module, musimy się  upewnić, że został on już zarejestrowany. Aby mieć tego gwarancję instancjonowanie (pobranie z kontenera) należy wykonać wewnątrz metody AfterAllModulesLoaded (znajdującej się w ModuleBase).

Lista dostępnych serwisów w POS

Serwisy możemy podzielić na podstawowe dotyczące rdzenia aplikacji POS oraz biznesowe dotyczące logiki działania biznesowego aplikacji.

Do listy serwisów podstawowych zaliczamy:

  • IMonitService (Comarch.POS.Presentation.Core.Services)

Serwis umożliwiający wyświetlanie komunikatów (informacyjnych oraz pytających) – więcej w artykule Notyfikacje i Komunikaty

  • INotificationService (Comarch.POS.Presentation.Core.Services)

Serwis umożliwiający wyświetlanie notyfikacji – więcej w artykule Notyfikacje i Komunikaty

  • IViewManager (Comarch.POS.Presentation.Core.Services)

Klasa pozwalająca na sterowanie widokami (otwieranie i zamykanie) – więcej w artykule Nawigacja między widokami

  • ILoggingService (Comarch.POS.Library.Logging)

Serwis pozwalający na zapis informacji do pliku loga

  • ISecurityService (Comarch.POS.BusinessLogic.Interfaces.Security)

Serwis odpowiedzialny za autentykację i autoryzację użytkownika POS – więcej w artykule Weryfikacja uprawnień

  • IAutorizationService (Comarch.POS.Presentation.Base.Services)

Serwis umożliwiający walidację uprawnień zalogowanych użytkowników POS – więcej w artykule Weryfikacja uprawnień

Do listy serwisów biznesowych zaliczamy:

  • IConfigurationService (Comarch.POS.Library.Settings)

Serwis pozwalający na uzyskanie dostępu do konfiguracji aplikacji

  • IFiscalizationService (Comarch.POS.Presentation.Fiscalization.Services)

Serwis fiskalizacji

  • ISynchronizationService (Comarch.POS.Synchronization.Interfaces)

Serwis synchronizacji

  • IPrintoutManager (Comarch.POS.Presentation.Core.Services)

Serwis wydruku

  • Oraz wszystkie pozostałe znajdujące się w przestrzeni Comarch.POS.BusinessLogic.Interfaces

 




Omówienie kontrolek

Większość kontrolek używanych w aplikacji POS pochodzi z przestrzeni nazw Comarch.POS.Presentation.Core.Controls. Wyjątkiem jest na przykład TextBlock, który nie posiada POSowego odpowiednika i używamy standardowej kontrolki dostarczanej przez platformę .NET. Pewne specyficzne kontrolki tworzone na potrzeby konkretnych wymagań biznesowych znajdują się w modułach takich jak Comarch.POS.Presentation.Products, Comarch.POS.Presentation.Customers, Comarch.POS.Presentation.Sales. Poniżej opisane zostały wybrane komponenty.

TextBlock

Jedna z podstawowych kontrolek umożliwiająca prezentowanie tekstu. Pochodzi z przestrzeni nazw System.Windows.Controls.

SlideTextBlock

Kontrolka TextBlock wzbogacona o efekt animacji wjeżdżania tekstu na ekran i wyjeżdżania. Każdorazowe wprowadzenie wartości do właściwości Text spowoduje, że tekst ten pojawi się z efektem animacji. Jeżeli wcześniej był wprowadzony inny tekst, to on w tym samym momencie zniknie z efektem animacji. Kontrolka ta jest używana na przykład na ekranie startowym informującym o postępie podczas uruchamiania aplikacji.

Możliwe opcje konfiguracyjne kontrolki:

SlideInDuration : double – czas animacji wjeżdżania tekstu na ekran, wyrażony w milisekundach. Domyślnie wynosi 500.

SlideOutDuration : double – czas animacji wyjeżdzania tekstu z ekranu, wyrażony w milisekundach. Domyślnie 500.

SlideInLength : int – dystans tekstu do pokonania w animacji pojawiania się tekstu. Domyślnie 400.

SlideOutLength : int – dystans tekstu do pokonania w animacji znikania tekstu. Domyślnie 500.

TextBox

Kontrolka umożliwiająca wprowadzania tekstu przez użytkownika.

Wybrane właściwości:

Hint : string – podpowiedź, tekst jaki wyświetli się w kontrolce, jeżeli ta nie będzie wypełniona przez użytkownika. Tekst będzie prezentowany w kolorze określonym przez HintForeground. Podpowiedź może być prezentowana w jednym z dwóch trybów, konfigurowalnych w pliku konfiguracyjnym aplikacji pod kluczem ClearHintOnFocus. Domyślnie false, tekst podpowiedzi znika, gdy użytkownik zacznie wpisywanie znaków, lub true – od razu po zaznaczeniu kursora w kontrolce.

HintForeground : Brush – kolor podpowiedzi.

NumericTextBox

Kontrolka TextBox umożliwiająca wprowadzanie wyłącznie wartości liczbowych.

Wybrane właściwości:

AllowOnlyPositiveValues : bool – flaga pozwalająca na sterowanie czy dopuszczamy możliwość wprowadzania wartości ujemnych

ValueType : ValueType – parametr pozwalająca na określenie czy dopuszczalne będą tylko wartości całkowite

Precision : int – precyzja dla wartości zmiennoprzecinkowych

MinValue i MaxValue : object – możliwość określenia zakresu dopuszczalnych wartości

IsDefaultNullValue : bool – parametr umożliwiający pozostawienie pola pustego

PasswordBox

Kontrolka pozwalająca na wprowadzania tekstu w sposób bezpieczny, bez możliwości podglądu wprowadzanych znaków w interfejsie.

 

Button (CancelButton, AcceptButton, itd.)

Przycisk to jedna z podstawowych kontrolek pozwalających na interakcję z użytkownikiem. Pozwala na wykonanie określonej akcji w momencie, gdy użytkownik kliknie w ten element. Przyciski domyślnie wyglądem przypominają (ponieważ faktycznie mają wymiary 90×80) kwadraty o różnych kolorach. Każdy przycisk może zawierać dowolny tekst opisujący jego działanie, skrót klawiszowy (pozwalający na wywołanie akcji bez potrzeby klikania) oraz ikonkę (w formacie wektorowym lub pliku graficznego np. png).

Wszystkie dostępne właściwości przycisków (takie jak ich kolor, tekst, wymiary, itp.) mogę być określane dowolnie, jednakże w celu ułatwienia pracy przygotowane zostały dedykowane przyciski takie jak np. przycisk zamykania (CancelButton), czy zapisu (SaveButton), które mają już zdefiniowane odgórnie kolory tak, aby nie trzeba było za każdym razem ich określać. Podobnie jak tekst przycisku, skrót klawiaturowy oraz ikona. Wszystkie dostępne warianty przycisku zostały dodane w przestrzeni nazw Comarch.POS.Presentation.Core.Controls.Buttons

Wybrane ważniejsze właściwości:

Content : object – zawartość prezentowana wewnątrz przycisku, na ogół tekst

Orientation : Orientation – ułożenie tekstu i ikony. Domyślnie pionowa (Vertical) – tekst znajduję się pod ikoną. Przy orientacji poziomej ikona znajdzie się po lewej, a tekst po prawej stronie.

Width: double – szerokość przycisku, domyślnie 80

Height : double – wysokość przycisku, domyślnie 90

Margin : Thickness – margines przycisku

InactiveVisibility : Visibility – widoczność przycisku w stanie nieaktywności, gdy IsEnabled=false (np. gdy aktywator akcji zwraca false)

Command : ICommand – akcja w postaci komendy, najczęściej stosowany binding do typu DelegateCommand

DisabledState : Brush – kolor przycisku gdy widoczny w stanie nieaktywności

ImageKey : ImageKey – ustawienie jednej ikon graficznych (PngValue) lub wektorowych (SvgValue) dostępnych w aktualnie wybranym motywie

ImageWidth : double – szerokość ikony

ImageHeight : double – wysokość ikony

IsImageVisible : bool – widoczność ikony

ImageMargin : Thickness – margines ikony

ContentVisibility : Visibility – widoczność tekstu

IsScaleContent : bool – skalowanie tekstu niemieszczącego się w przycisku, domyślnie: false

Shortcut : Shortcut – skrót klawiszowy zastępujący kliknięcie

IsShortcutVisibile : bool – widoczność skrótu klawiszowego

CheckBox

Kontrolka umożliwiająca użytkownikowi wybór jednego z dwóch stanów logiczny. Prezentowana w postaci przycisku, którego kolor zmienia się w zależności od wybranego stanu.

Wybrane właściwości:

IsChecked : bool – aktualny stan logiczny, true – przycisk wciśnięty, false – przycisk niewciśnięty

CheckedStateBackground : Brush – kolor tła przycisku wciśniętego

CheckedStateForeground : Brush – kolor tekstu na przycisku wciśniętym

DisabledStateBackground : Brush – kolor tła przycisku w stanie nieaktywności, gdy IsEnabled=false

DisabledStateForeground : Brush – kolor tekstu przycisku w stanie nieaktywności, gdy IsEnabled=false

Oraz posiada większość właściwości kontrolki Button.

SwitchBox

Kontrolka ta bazuje na kontrolce CheckBox i różni jest od niej przede wszystkim sposobem prezentacji. Podobnie jak CheckBox może przyjąć jeden z dwóch stanów (true – zaznaczony/włączony, false – niezaznaczony/wyłączony). Wyglądem przypomina przełącznik, który może być wzbogacony o opis czynności jaką realizuje zmiana jego stanu.

Wybrane właściwości:

SwitchOffContent : object – zawartość, jaka się pojawi, gdy stan przełącznika będzie na false

SwitchOnContent : object – zawartość, jaka się pojawi, gdy stan przełącznika będzie na true

Content : object – zawartość opisująca do czego służy przełącznik (jakie realizuje zadanie)

IsChecked : bool – stan przełącznika, włączony – true, wyłączony – false

Oraz posiada większość właściwości kontrolki CheckBox.

NullableSwitchBox

Kontrolka ta bazuje na kontrolce SwitchBox. Różni się od niej sposobem prezentacji oraz tym, że jest trzy stanowa (<null> – żaden przycisk nie jest zaznaczony, <true> – zaznaczony prawy przycisk, <false> – zaznaczony lewy przycisk). Stan nieokreślony <null> może być tylko stanem początkowym (jeśli nie ustawiono inaczej), po przejściu w inny stan nie można już wrócić do stanu nieokreślonego. Wyglądem przypomina pigułkę, która może być wzbogacona o opis czynności jaką realizuje zmiana jej stanu.

Wybrane właściwości:

SwitchOffContent : object – zawartość, wyświetlona w lewym przycisku (zaznaczenie tego przycisku odpowiada przejściu w stan <false>) (Domyślna wartość: Nie)

SwitchOnContent : object – zawartość, wyświetlona w prawym przycisku (zaznaczenie tego przycisku odpowiada przejściu w stan <true>) (Domyślna wartość: True)

Content : object – zawartość opisująca do czego służy przełącznik (jakie realizuje zadanie)

IsChecked : bool? – stan przełącznika (Domyśla wartość: <null> czyli żaden przycisk nie jest zaznaczony)

Oraz posiada większość właściwości kontrolki CheckBox.

RadioButton

Kontrolka ta w działaniu i wyglądzie przypomina kontrolkę CheckBox. Różnica polega na tym, że definiując kilka RadioButtonów i nadając im wspólna nazwę dla grupy GroupName, tylko jedna z całej grupy może być w danej chwili zaznaczona.

Wybrane właściwości:

IsChecked : bool – stan zaznaczenia, true-zaznaczona kontrolka, false – odznaczona

CanUndoSelection : bool – możliwość odznaczenia (ustawienia IsChecked=false) poprzez ponowne kliknięcie w kontrolkę

ComboBox

Kontrolka pozwalająca użytkownikowi na wybór jednej z wielu opcji dostępnych w postaci rozwijalnej listy. Wygląda jak kontrolka TextBox z dodatkową ikonką pozwalającą na rozwijanie listy opcji do wyboru.

Wybrane właściwości:

Hint : string – podpowiedź prezentowana gdy nie wybrano żadnej wartości z rozwijalnej listy.

HintForeground : Brush – kolor podpowiedzi.

PopupBackground : Brush – kolor tła listy rozwijalnej.

ItemsSource : IEnumerable – kolekcja opcji do wyboru.

SelectedItem : object – wybrana opcja.

ComboBox2

Rozbudowany odpowiednik kontrolki ComboBox, prezentujący opcję do wyboru nie w postaci prostego popup’a, ale jako okno modalne. W oknie tym domyślnie opcje prezentowane są w postaci listy RadioButtonów, jednakże można również stworzyć zupełnie własny kontent.

Kontrolka składa się z dwóch części:

  • elementu prezentującego wybraną opcję (w postaci TextBlocka z opisem oraz znajdującego się poniżej Buttona o niestandardowym rozmiarze)

  • okna modalnego z listą opcji (domyślnie lista RadioButtonów)

 

Wybrane właściwości:

Source : IComboBoxSource – wymagana właściwość, źródło danych – dla domyślnego wyglądu modalnego należy użyć typu ComboBoxSource<T>, gdzie T to typ danych opcji do wyboru

Label : string – tekst wyświetlany nad przyciskiem kontrolki.

LabelFontSize : double – rozmiar czcionki dla labela.

LabelFontWeight : FontWeight – waga czcionki dla labela.

LabelFontStyle : FontStyle – styl czcionki dla labela.

FontSize : double – rozmiar czcionki tekstu wewnątrz przycisku.

FontWeight : FontWeight – waga czcionki tekstu wewnątrz przycisku.

FontStyle : FontStyle – styl czcionki tekstu wewnątrz przycisku.

Zmiana tła całej kontrolki, koloru tekstu oraz tła przycisku nie jest możliwa bezpośrednio. Zależna jest od wybranych kolorów bazowych w motywie.

Więcej szczegółów Przykłady użycia kontrolki ComboBox2.

TileButton

Kontrolka ta w zasadzie jest Buttonem o określonych innych niż przycisk rozmiarach. Dziedziczy ona po klasie Button, dlatego zawiera wszystkie właściwości jakie posiada przycisk. Używana jest między innymi w menu głównym. Dzięki odpowiedniej metodzie do rejestracji widoków w menu głównym, nie ma potrzeby tworzenia tej kontrolki w tamtym miejscu ręcznie.

ButtonSpinner

Kontrolka ta w połączeniu w kontrolką typu input (np. najczęściej TextBox) umieszczoną wewnątrz, dodaje dodatkowe dwa przyciski sterujące (+ oraz -). Każdorazowe wciśnięcie jednego z nich wywołuje event Spin. Można go wykorzystać do sterowania zawartością wewnętrznej kontrolki.

Wybrane właściwości:

Spin : EventHandler<ButtonSpinnerArgs> – zdarzenie wywołujące się w wyniku kliknięcia w jeden z przycisków ButtonSpinnera (+ lub -).

Więcej szczegółów Przykład użycia kontrolki ButtonSpinner

ComboBoxButton

Kontrolka ta jest rozszerzeniem opisanej wcześniej kontrolki ComboBox. W działaniu natomiast bardziej przypomina Button. Pozwala na prezentację przycisku w postaci ikonki, po kliknięciu w którą rozwija się zdefiniowana lista akcji. Elementy listy określane są za pomocą elementów ComboBoxItem. Przykładem wykorzystania jest menu statusowe:

Wybrane właściwości:

LabelContent : object – dodatkowy opcjonalny klikalny kontent z lewej strony ikony.

ImageSource : Canvas – ikona przycisku w formie wektorowej.

SelectedItem : object – wybrana opcja z listy akcji.

SelectedIndex : int – indeks wybranej opcji z listy akcji.

Więcej szczegółów Przykład użycia kontrolki ComboBoxButton

MultiButton

Kontrolka ta pozwala na agregację wielu przycisków do postaci jednego. Kliknięcie w główny przycisk powoduje pokazanie się popup’a z pełną listą przycisków. W przypadku, gdy na liście będzie tylko jeden przycisk, popup nie pojawi się tylko od razu zostanie wykonana akcja. Agregowane przyciski mogę być również w pełni zarządzalne w aplikacji, wraz z możliwością zmiany ich kolejności oraz ukrywania (osadzone są w zarządzalnym kontenerze).

Wybrane właściwości:

SubButtons : List<Button> – zdefiniowana lista zagregowanych przycisków.

Więcej szczegółów Przykład użycia kontrolki MultiButton

ItemsContainer

Kontrolka ta bazuje na .netowej kontrolce ItemsControl, czyli pozwala na umieszczenie w jej wnętrzu innych kontrolek w sposób zorganizowany, najczęściej w postaci kolejno ułożonych elementów w pionie lub poziomie. Została wzbogacona o mechanizmy pozwalające na dynamiczne zarządzanie zdefiniowaną jej zawartością w trakcie działania aplikacji. W trakcie normalnej pracy kontrolka prezentuje zawartość w zdefiniowanej w xaml-u kolejności. Tę kolejność oraz widoczność określonych wcześniej elementów składowych można zmieniać w trakcie działania aplikacji (może tego dokonać zwykły użytkownik) za pomocą specjalnie przygotowanego panelu do zarządzania widokami aplikacji. Zmiany te zapisywane są w aktualnie wybranym motywie (z wyjątkiem motywu domyślnego). Sterowanie możliwością zarządzania elementami składowymi realizowane jest poprzez zdefiniowanie na użytej w widoku kontrolce właściwości LayoutId oraz na każdym elemencie zdefiniowanym w jej wnętrzu. Innymi słowy, aby kontrolka była zarządzalna, zarówno ona jak i jej składowe muszą mieć zdefiniowane unikalne identyfikatory. Elementami składowymi mogę być w zasadzie dowolne kontrolki, najczęściej jednak jest to tylko jeden typ, np. lista przycisków. Elementy mogę być zdefiniowane w xamlu lub budowane w kodzie, ale bez użycia bindingu, tylko poprzez dodawanie do kolekcji z użyciem metody kontrolki AddItem.

 

Wybrane właściwości:

AutoWrapButtons : bool – sterowanie możliwością automatycznego zwijania przycisków wewnątrz kontenera w przypadku braku miejsca na ich prezentację i zamianę na przycisk „więcej” typu MultiButton (niemieszczące się przyciski ukrywane w popupie). Dodatkowo na każdej kontrolce typu Button można zdefiniować czy dany przycisk może zostać zwinięty (za pomocą właściwości ItemsContainer.NoWrapButton lub bezpośrednio w panelu zarządzania widokami POSa, w zakładce ogólne->nie agreguj).

Orientation : Orientation – ułożenie elementów składowych, poziome lub pionowe.

ItemsContainer, a MoreMultiButton:

W sytuacji kiedy mamy zdefiniowany rozmiar ItemsContainera oraz w środku znajdują się jedynie kontrolki typu Button i włożymy do środka większą ilość kontrolek niż jest w stanie się zmieścić, przy defaultowych ustawieniach (AutoWrapButtons=true) do ItemsContainera zostanie dodana dodatkowa kontrola MoreMultiButton, do której zostaną włożone niemieszczące się przyciski.

W sytuacji kiedy ilość dostępnego miejsca nie pozwala na dodanie nawet jednej kontrolki, nie wyświetli się MoreMultiButton (to znaczy, że nic się nie wyświetli) można wtedy użyć flagi AlwaysShowMultiButton=True, która zawsze doda MoreMultiButton o rozmiarze odpowiadającym całkowitemu dostępnemu miejscu.

Należy pamiętać, że następujące kontrolki: CloseButton (Przycisk zamknij), SearchButton (Przycisk przy wyszukiwaniu) mają domyślnie ustawioną flagę controls:ItemsContainer.NoWrapButton=True, czyli nie będą podlegać agregacji. W sytuacji kiedy będzie się mogła zmieścić tylko jedna kontrolka w ItemsContainer, będzie to właśnie kontrolka nie podlegająca agregacji, co będzie skutkować tym, że przycisk MoreMultiButton się nie pojawi.

Istnieje możliwość ustawienie pewnych właściwości dla automatycznie generowanego przycisku MoreMultIButton, więcej szczegółów w artykule Zarządzanie widokiem i jego elementami.

Więcej szczegółów Przykład użycia kontrolki ItemsContainer oraz  Zarządzanie elementami w kontenerze ItemsContainer.

Grid

Druga bardzo istotna kontrolka w kontekście zarządzania widokami. Pozwala na swobodną budowę widoku za pomocą kolumn i wierszy, w których umieszczamy kolejne elementy. Podobnie jak ItemsContainer pozwala na dynamiczne zarządzanie jego elementami przez użytkownika w trakcie działania aplikacji. Elementy zdefiniowane wewnątrz grida można z niego usuwać, przenosić miedzy komórkami (stworzonymi z kolumn i wierszy) grida, a nawet przenosić do kontenerów ItemsContainer oraz innych Gridów (o ile te też zostały wcześniej zdefiniowane w gridzie). Na bazie tej kontrolki zaleca się budowanie w pełni zarządzalnych widoków. W samym xaml-u widoku dodaje się na początku tę kontrolkę oraz następnie definiuje się w jej wnętrzu wszystkie pozostałe, które będą niezbędne do zbudowania widoku. Sam widok już nie tworzymy w xaml-u, a bezpośrednio w panelu zarządzania widokiem w aplikacji, a cała definicja widoku (układu elementów) zapisywana jest w motywie.

 

Wybrane właściwości:

ColumnDefinition : string – określenie liczby i rozmiaru kolumn, wg wzoru: k1,k2,k3,…kx, gdzie k1, k2, k3, .. kx – wartości określające kolejne kolumny, możliwe wartości:

* – dostosuj szerokość kolumny proporcjonalnie w zależności od ich liczby. Można użyć w połączeniu z liczbą aby określić inne proporcje podziału, np. 2*

Auto – dostosuj szerokość kolumny automatycznie, tyle ile potrzebuje element w środku,

[liczba] – ustaw zadaną szerokość kolumny, np. 100

Przykład definicji grida z trzema kolumnami, pierwsza o szerokości 150, druga tyle ile wymaga element w środku, trzecia bierze całą resztę dostępnego miejsca:

ColumnDefinition=”150,Auto,*”

 

RowDefinition : string – określenie liczby i rozmiaru wierszy, wg wzoru jak dla definicji kolumn,

 

Aby określić w której kolumnie i wierszu ma się znaleźć element zdefiniowany wewnątrz grida należy na nim ustawić właściwość Grid.Position. Przykładowo, jeżeli element grida ma znaleźć się w trzeciej kolumnie i drugim wierszu należy napisać: Grid.Position=”1,2,1,1” (1 – oznacza drugi wiersz, 2 – trzecia kolumna, 1,1 – element będzie zajmował jeden wiersz i jedną kolumnę).

 

Więcej szczegółów: Przykład użycia kontrolki Grid oraz Zarządzanie elementami w kontenerze Grid

Tooltip

Kontrolka pozwala na prezentowanie dodatkowych informacji (na przykład szczegółowych opisów do pól) w formacie popupa po najechaniu myszką na zdefiniowany wcześniej obszar (Caption). Szczegółowe informacje mogą zawierać tytuł (Title) oraz treść (Content).

Wybrane właściwości:

Caption : object – dowolna zawartość (np. tekst lub inne kontrolki) po najechaniu na która prezentuje się tooltip

Title : string – tytuł tooltipa prezentowany w popupie

Content : string – treść prezentowana w tooltipie

TabControl i TabControlItem

Kontrolki te pozwalają na prezentację danych w postaci zakładek. W połączeniu z kontrolką grida można zbudować widok złożony z kilku TabControl (kontenerów na zakładki) oraz wielu zakładek (TabControlItem), które użytkownicy z poziomu panelu zarządzania widokiem będą mogli swobodnie przerzucać pomiędzy znajdującymi się na widoku TabControlami. Doskonałym przykładem wykorzystania zakładek jest widok szczegółów kontrahenta. Składa się on z czterech kontrolek TabControl, które pozwalają prezentować zakładki jakie jak Adresy, Atrybuty, Statystyki, Osoby kontaktowe w dowolnym kontenerze TabControl.

Każdy kontener można podzielić na dwa obszary. Obszar listy RadioButtonów pozwalających na przełączanie się pomiędzy aktywnymi zakładkami. Jeżeli TabControl posiada tyko jeden TabControlItem to ten obszar nie będzie prezentowany (poza trybem zarządzania). Obszar zawartość do prezentacji zawartości aktualnie aktywnej zakładki. Domyślny układ obszarów (zawartość na górze, przyciski na dole) można dowolnie zmieniać.

Wybrane właściwości TabControlItem:

TabContent : object – definicja zawartości zakładki

Source : TabControlItemSource – źródło danych dla definicji zawartości zakładki. Jeżeli nie będzie ustawione domyślnie źródłem danych będzie DataContext kontrolki TabControl, czyli viewmodel.

Klasa TabControlItemSource jest klasa abstrakcyjną dostarczającą tylko dwie właściwości pozwalające na sterowanie działaniem zakładek. Flaga IsLoaded, która trzeba ustawić na true po załadowaniu danych oraz flaga IsDesignMode ustawiania na true w design view modelu, przełączająca zakładkę w tryb zarządzania.

Więcej szczegółów: Przykład użycia kontrolek TabControl i TabControlItem

ScrollViewer

Kontrolka umożliwiająca przewijanie zawartości niemieszczącej się na widoku. Działa tylko w trybie przewijania poziomego, domyślnie jest tak właśnie ustawiony. Aby skorzystać z trybu pionowego należy użyć kontrolki z przestrzeni .NET (System.Windows.Controls.ScrollViewer).

FieldControl i walidacja

Kontrolka pola (FieldControl) jest używana dla pól formularzy. Umożliwia uzyskanie funkcjonalności sterowania walidacją z poziomu zarządzania interfejsem użytkownika (wymagane jest zdefiniowanie LayoutId na kontrolce). Zastosowanie jej zwalnia z potrzeby używania TextBlocka/Label do zdefiniowania opisu pola.

Przykładowo, chcąc dodać w widoku pole tekstowe, które użytkownik będzie musiał wypełnić, należy w pliku widoku xaml dodać:

<controls:FieldControl core:Layout.Id="OrdersViewTextField1"
                                   Source="{Binding Field1}"
                                   LabelText="Opis pola tekstowe:">
 
     <controls:TextBox core:Layout.Id="FieldTestsViewTextBox2" 
            Text="{Binding Field1.Data, 
            UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/> 
</controls:FieldControl>

 

Właściwość Source kontrolki FieldControl wymaga przypisania wartości typu FieldSourceBase. Klasa ta jest abstrakcyjna, dlatego należy użyć jednej z klas potomnych FieldSource<T>, FieldSourceCollection<T> lub w razie potrzebny utworzyć własną implementację bazującą na klasie FieldSourceBase.

Klasa generyczna FieldSource<T> przechowuje pojedynczą wartość typu T we właściwości o nazwie Data. Możemy jej użyć w połączeniu z kontrolkami, które będą wymagały wprowadzenia jednej wartość – na przykład TextBox. Klasa FieldSourceCollection<T> przechowuje kolekcje elementów oraz aktualnie wybrany/zaznaczony element. Kolekcja znajduję się we właściwości Items, natomiast aktualnie wybrany element w SelectedItem. Możemy jej użyć w połączenie z kontrolką ComboBox. Aby walidacja działała na docelowej kontrole (w tym przypadku TextBox) należy w bindingu dodać ValidationOnDataErrors=true.

W view-modelu naszego widoku dodajemy właściwość Field1 typu FieldSource<string> oraz tworzymy instancję. W konstruktorze opcjonalnie możemy zdefiniować domyślną wartość jaka ukaże się w polu TextBox po otwarciu widoku.

public FieldSource<string> Field1 { get; set; }
…
public OrdersViewModel()
{
    Field1 = new FieldSource<string>()
            {
                Error = "prosty komunikat błędu"
            };
    …
}

 

Klasa FieldSource<T> oraz FieldSourceCollection definiuje domyślną regułę walidacji pola. Polega ona na sprawdzeniu czy pole nie jest puste, jeżeli właściwość Wymagalności została ustawiona w zarządzaniu widokiem. Aby zdefiniować własną logikę walidacji należy skorzystać z metody SetValidationRule.

Za pomocą właściwości Error (przykład użycia powyżej) możliwe jest zredefiniowanie domyślnego komunikatu błędu, jaki się wyświetli w postaci ToolTipa po najechaniu myszką na wybrany pole.

Oprócz tego klasa FieldSourceBase dostarcza takie właściwości do odczytu jak:

IsValid – pozwala na sprawdzenie czy pole jest poprawnie walidowane.

IsRequired – pozwala na sprawdzenie czy pole zostało oznaczone jako wymagane przez użytkownika w konfiguracji widoku.

Dodatkowo, aby ręcznie w view-modelu wymusić weryfikację walidacji należy użyć metody RaiseValidation.

Więcej szczegółów: Przykład użycia kontrolki FieldControl

DataGrid

Kontrolka ta pozwala na prezentacji złożonych danych w postaci tabelki z kolumnami, będącymi nagłówkami oraz wierszami, zawierającymi szczegółowe dane. Prezentowane przez kontrolkę informacje mogę być grupowane po dowolnej kolumnie wybranej przez użytkownika w panelu zarządzania widokiem. Dodatkowo, zgrupowane dane mogę być poddawane agregacji (np. sumowania, uśredniania lub własnej implementacji). Dane dla kontrolki pobierane mogę być asynchronicznie. Kontrolka wspiera sterowanie sortowaniem oraz doczytywaniem danych w trakcie przewijania (stronicowanie) poprzez wysyłanie odpowiednich żądań do źródła danych. Sama nie realizuje sortowanie danych, informuje tylko w jaki sposób mają być posortowane, a źródło musi takie posortowane dane już dostarczyć. Więcej informacji o działaniu kontrolki w Prezentacja zbioru danych za pomocą kontrolki DataGrid

 

DatePicker i Calendar

Kontrolka Calendar prezentuje kalendarz i umożliwia wskazanie daty – rok, miesiąc, dzień. Kontrolka DatePicker to ComboBox po kliknięciu w który pojawia się popoup z kalendarzem (Calendar). Po wskazaniu daty jest ona przepisywana do ComboBoxa.

Wybrane właściwości DatePicker:

UndefinedDateText : string – jeżeli wartość tekstowa będzie wprowadzona, pojawi się opcja czyszczenia daty poprzez kliknięcie w RadioButton o treści określonej w tej właściwości.

IsTextHidden : bool – pozwala na ukrycie prezentacji wybranej daty

SelectedDate : DateTime? – wybrana data

DatePicker2

Alternatywnym sposobem wyboru daty jest kontrolka DatePicker2. Prezentuje datę w formie trzech oddzielnych pól. Pole dnia (TextBox), miesiąca (ComboBox) oraz roku (TextBox). Polami kontrolki mogę być niezależnie zarządzalne. Możliwa jest też zmiana kolejności wyświetlania składowych daty w panelu zarządzania widokiem, dzięki temu, że elementy składowe osadzone są w kontenerze.

Wybrane właściwości:

SelectedDate : DateTime? – data prezentowana w kontrolce

IsMonthsListEditable : bool – sterownie edytowalnością comboboxa z listą miesięcy

IsValid : bool – flaga określająca czy wprowadzona data jest poprawna

Więcej informacji w Przykład użycia kontrolki DatePicker2

AssistantControl

Specjalistyczna kontrolka biznesowa używana na każdym dokumencie handlowym, umożliwiająca ustawienie asystenta transakcji zarówno na dokumencie jak i pozycji. W swoim wyglądzie i działaniu bardzo przypomina kontrolkę ComboBox2.

Wybrane właściwości:

Source : AssistantSource – źródło dany dla kontrolki, binding do instancji AssistantSource

Label : string – tekst prezentowany na kontrolce (na przykład na dokumencie handlowym: „Obsługujący”).

AttributeControl

Kolejna specjalistyczna biznesowa kontrolka obsługująca atrybuty obiektów biznesowych. Pozwala na prezentowanie oraz edycję atrybutów w zależności od ich typów, w odpowiednich kontrolkach edycji. Na przykład atrybut typu tekstowego będzie automatycznie zaprezentowany w postaci kontrolki TextBox, a logiczny za pomocą SwitchBoxa. Więcej o obsłudze atrybutów biznesowych w Obsługa atrybutów

BuyerControl i RecipientControl

Biznesowe kontrolki wyboru i prezentacji kontrahenta oraz odbiorcy na dokumencie handlowym. Zbudowane są podobnie jak poprzednie dwie. Składają się w kontrolki oraz źródła danych połączonego za pomocą bindingu.

Wybrane właściwości BuyerControl:

Source : IBuyerSource – źródło danych implementujące interfejs IBuyerSource

 




Prezentacja zbioru danych za pomocą kontrolki DataGrid

Kontrolka Comarch.POS.Presentation.Core.Controls.DataGrid bazuje na tej znanej z .NET i pozwala na prezentację kolekcji danych, podzielonej na kolumny oraz wiersze. Kontrolka DataGrid w POS dodatkowo pozwala na asynchroniczne pozyskiwanie danych, wspiera stronicowanie listy (doczytywanie danych w trakcie przewijania listy), zmienia sposób realizacji sortowania danych oraz modyfikuje mechanizm grupowania wzbogacając go o agregację danych.

Asynchroniczne pobieranie danych

Aby dane zostały pobrane asynchronicznie należy zbindować jego źródło z właściwością AsyncItemsSource znajdującą się na DataGridzie. Źródło musi być obiektem klasy AsyncDataGridCollection<T>, gdzie T to typ danych, jaki będzie prezentowany w pojedynczym wierszu. Logika odpowiedzialna za pozyskiwanie danych powinna zostać zdefiniowana w pierwszym parametrze konstruktora klasy AsyncDataGridCollection. Pobrane dane natomiast powinny zostać przypisane do kolekcji Data.

Przykład.

Documents = new AsyncDataGridCollection<TradeDocumentListRow>(
    (token, o) =>
    {                   
                   //pobieranie danych
        var documents = GetDocumentsAsync(token, o);
 
                  //tylko jeden wątek może w danym momencie modyfikować kolekcję
        lock (Documents.DataLock)
        {
                           //obsługa anulowania pobierania
            if (token.IsCancellationRequested)
            return null;

                            //każde odświeżenie listy wymaga jej wyczyszczenia
            Documents.Data.Clear();
 				
                            //dodanie danych do kolekcji, którą odczyta DataGrid		 
            Documents.Data.AddRange(data);			
        }
 
    return documents;
    }, OnReceiptsOperationCompleted, loggingService);

 

Powyższy przykład przedstawia definicję właściwości Documents zaimplementowaną w konstruktorze view-modelu, która jest zbindowana z właściwością AsyncItemsSource kontrolki DataGrid. Pierwszy argument konstruktora to prosta logika pobierania danych (bez uwzględnienia stronicowania). Metoda zostanie automatycznie wywołana asynchronicznie bezpośrednio przez kontrolkę DataGrida, gdy tylko zostanie ona zainicjowana. Aby ręcznie zdecydować o momencie pobrania danych należy zmienić wartość właściwości LoadDataOnDataGridInitialization=false na obiekcie Documents. W momencie podjęcia decyzji o pobraniu danych wywołać metodę Documents.Refresh(false). Drugi argument konstruktora to metoda, która zostanie wywołana w wątku głównym (UI) tuż po zakończeniu logiki pobierania. Trzeci parametr to instancja serwisu do logowania błędów ILoggingService, dzięki której potencjalne wyjątki jakie zostaną rzucone w wątku pobierania zostaną zapisane w logu aplikacji.

Sortowanie danych

Stronicowanie może być zdefiniowane domyślnie oraz zmieniane przez użytkownika w dowolnym momencie poprzez kliknięcie na wybranej kolumnie. Domyślność z kolei można zmieniać w panelu zarządzania widokiem. Kontrolka sama nie realizuje zadania tyko zleca go źródle danych, inicjując metodę pobierająca dane przesyłając tylko informację o żądanym sposobie sortowania. Informacje w jakiś sposób posortować pobierane dane można odczytać z właściwości Sorting znajdującej się w obiekcie Documents.

Sorting : List<GridSortDescription> – lista zawierająca informację po której kolumnie sortowanie i w jaki sposób.

Składowe klasy GridSortDescription:

PropertyName : string – nazwa kolumny zdefiniowana we właściwości SortMemberPath kolumny. Jeżeli nazwa nie będzie zdefiniowana, domyślnie zostanie ustawiana taka jak nazwa właściwości użytej w bindingu.

Direction : ListSortDirection – kierunek sortowania, do wyboru rosnąco lub malejąco.

Column : DataGridColumn – referencja do kolumny

Stronicowanie listy

Stronicowanie, a w zasadzie doczytywanie dynamiczne danych podczas przewijania listy polega na tym, że kontrolka DataGrid w momencie gdy użytkownik zbliża się do końca listy inicjuje asynchroniczną metodę pobierająca dane informując źródło, że będzie potrzebną kolejną porcja jeżeli istnieje. Mechanizm ten pozwala na sprawną obsługuję dużych zbiorów danych, gdzie pobieranie całego zbioru nie byłoby efektywne i wydajne.

Sama logika stronicowa mysi być zaimplementowana w źródle. Aby wiedzieć, którą porcję danych żąda kontrolka należy skorzystać z właściwości ItemsToTake oraz ItemsToSkip znajdujących się w obiekcie klasy AsyncDataGridCollection<T>.

ItemsToTake : int – liczba wierszy danych, które żąda kontrolka.

ItemsToSkip : int – liczba wierszy danych, które należy pominąć licząc od początku zbioru (ta liczba reprezentuje liczbę wierszy, które zostały już pobrane wcześniej).

Grupowanie i agregacja

Kontrolka DataGrid wspiera mechanizm grupowania i agregacji wartości encji kolekcji prezentowanej przez kontrolkę. Włączenie funkcji grupowania wyłącza działanie mechanizmu stronicowania danych. Aby włączyć grupowanie, na kontrolce należy ustawić właściwość IsGroupingEnabled=”True”. Grupowanie jest w pełni zarządzalne w widoku zarządzaniu UI i pozwala na grupowanie i agregację po wcześniej zdefiniowanych właściwościach. Aby dana właściwość poddawała się grupowaniu należy oznaczyć ją atrybutem [AllowGroupBy] (z przestrzeni nazw: Comarch.POS.Core). Atrybut opcjonalnie pozwala na zdefiniowanie ścieżki do pliku resource oraz postfixu klucza z tłumaczeniem. Domyślnie jest to Properties.Resources, a każdy klucz to nazwa właściwości plus postfix Header. Na przykład dla właściwości o nazwie Name będzie to NameHeader.

Agregacja możliwa jest również tylko po wszystkich właściwościach oznaczonych atrybutem AllowGroupBy. W standardzie możliwe są następujące sposoby agregacji: Suma, Średnia, Maksimum, Minimum. Sposoby te działają tylko na właściwościach typu liczbowego lub string, który konwertuje się do typu liczbowego.

Rozszerzalność agregacji

W celu dodania własnego sposobu agregacji należy stworzyć nową klasę implementującą IAggregationType (przestrzeń nazw: Comarch.WPF.Controls.Aggregation). W klasie tej należy zaimplementować mechanizm agregacji  we właściwości Function.

Jeżeli zakładamy, że nasza nowa agregacja będzie realizowana tylko na liczbach możemy podziedziczyć po klasie AggregationNumberType zamiast implementować interfejs. Następnie przeciążyć metodę Aggregate bez potrzeby przejmowania się weryfikacją typów agregowanych danych.

Na koniec stworzoną klasę należy zarejestrować jako nowy sposób agregacji w systemie. W klasie Module należy wywołać metodę RegisterDataGridGroupAggregation, przekazać nazwę klasy oraz zdefiniować klucz i resource, z którego zostanie pobrane tłumaczenia dla nowego sposobu agregacji.

Więcej szczegółów w Przykład implementacji własnego sposobu agregacji danych w DataGridzie

Grupowanie po atrybutach

Grupowanie i agregacja po atrybutach działa automatycznie i nie wymaga dodatkowej implementacji. Ważne tylko, aby obiekt elementu zawierający atrybuty posiadał właściwości:

  • Attributes typu słownikowego Dictionary<string,AttributeEntity>
  • AttributeRows typu AttributesDictionary



Schematy kolorystyczne oraz czcionka

Interfejs aplikacji POS został stworzony w taki sposób, aby zapewnić spójność wyglądu wszystkich widoków oraz łatwość zarządzania. Dlatego zdefiniowano nazwy kolorów, którymi można się posługiwać tworząc i/lub modyfikując widok.

Zdefiniowane nazwy kolorów można podzielić na dwie grupy. Kolory zarządzalne bezpośrednio przez użytkownika w interfejsie POS oraz kolory zarządzalne pośrednio, niemożliwe do bezpośredniej zmiany przez użytkownika, tylko ustawiane automatycznie na podstawie kolorów zarządzalnych bezpośrednio.

Kolory zarządzalne bezpośrednio

  • ThemeColor – kolor bazowy motywu (kolor podstawowy), używany jako:
    • kolor tekstu w widokach podstawowych i modalnych,
    • kolor tła w widoku komunikatu,
    • kolor tła przycisków w widoku komunikatu,
    • kolor tła przycisku Szukaj/Dodaj np. na liście dokumentów handlowych, paragonie, produktach,
    • kolor tła zaznaczonej pozycji w kontrolce ComboBox,
    • kolor tła notyfikacji,
    • kolor wybranego/zaznaczonego radiobuttona oraz checkboxa
  • ThemeBackground – kolor bazowy motywu, używany jako:
    • kolor tła widoku podstawowego i modalnego,
    • kolor tekstu i ikon wektorowych przycisków (poza menu bocznym),
    • kolor tekstu i ikon wektorowych w widoku komunikatu,
    • kolor tekstu i obramowania przycisków w widoku komunikatu,
    • kolor testu przycisku Szukaj/Dodaj,
    • kolor tekstu i ikony wektorowej notyfikacji,
    • kolor tekstu wybranego/zaznaczonego radiobuttona oraz checkboxa

Kolory zarządzalne pośrednio

  • ThemeColor2 – kolor podstawowy (ThemeColor) z przeźroczystością 85%, używany jako:
    • kolor nagłówka list (DataGrid)
  • ThemeColor3 – kolor podstawowy (ThemeColor) z przeźroczystością 25%, używany jako:
    • kolor zaznaczenia pozycji na liście
  • ThemeColor4 – kolor podstawowy (ThemeColor) z przeźroczystością 65%, używany jako:
    • kolor zaznaczenia wiersza na liście najechanego kursorem myszki,
    • kolor zaznaczenia obrysu przycisku składającego się z samej ikony wektorowej (HeaderButton) po najechaniu na niego kursorem myszki,
    • kolor zaznaczenia w menu bocznym po najechaniu na pozycję kursorem myszki
  • HintColor – kolor podpowiedzi dla kontrolki TextBox
  • Background – kolor stały z określoną przeźroczystością – #1ADFE5E5, używany jako:
    • kolor tła listy

Użycie kolorów w kodzie

W dowolnym fragmencie kodu xaml, kolorów używamy poprzez polecenie DynamicResource, przykładowo:

<TextBox Foreground="{DynamicResource ThemeColor}" />

W większości przypadków powyższy przykład nie ma racji bytu, z uwagi na predefiniowane ustawienia, które zwalniają nas z konieczności takiej dodatkowej konfiguracji dla właściwości Foreground.

Krój czcionki również został zdefiniowany dla całej aplikacji i jest zarządzalny z poziomu interfejsu użytkownika POS. Dla większości kontrolek nie trzeba go dodatkowo ustawiać. Wyjątkiem może być np. definicja kolumn listy, gdzie należy pamiętać o ustawieniu kroju czcionki w następujący sposób:

<DataGridTextColumn FontFamily="{DynamicResource FontFamily}" />

 




Zarządzanie widokiem i jego elementami

Elementy interfejsu (kontrolki) mogę być modyfikowane poprzez zmianę kolorów zarządzalnych bezpośrednio –  Kolor motywu (ThemeColor) i Kolor tła (ThemeBackground), w panelu Konfiguracja interfejsu,  globalnie, wszystkie kontrolki danego typu na raz, w panelu Globalne elementy lub lokalnie, niezależnie per widok, w panelu Zarządzanie widokami. Zmiana dowolnego parametru kontrolki lokalnie ma wyższy priorytet nad zmianą globalną.

Layout.Id

Działanie zarządzania widokiem zależne jest od kilku czynników. Pierwszym jest odpowiednia rejestracja widoku (użycie metody RegisterViews z klasy ModuleBase) oraz utworzenie odpowiedniego view-modelu dla trybu design (DesignViewModel), drugim jest oznaczanie kontrolek odpowiednim atrybutem.

Atrybut ten to Layout.Id, musi on być unikalny w skali całej aplikacji POS. Jest to warunek niezbędny do tego, aby kontrolka była zarządzalna niezależnie. Jeżeli kilka kontrolek będzie miało ten sam Id zmiana właściwości na jednej spowoduje zmianę także dla drugiej kontrolki.

Ustawienie domyślnych wartości zarządzalnych właściwości

Domyślne wartości dla kontrolek można ustawiać na kilka sposobów w zależności od potrzeb.

  1. Bezpośrednio jako atrybuty zdefiniowanej kontrolki w xamlu
<TextBlock Foreground="Red"/>

 

Powyższa kontrolka nie będzie zarządzalna, ponieważ nie został zdefiniowany na niej atrybut Layout.Id

Można również połączyć to ze Schematy kolorystyczne oraz czcionka :

<TextBlock Foreground="{DynamicResource ThemeColor}"/>

 

Kontrolka zarządzalna będzie tylko poprzez zmianę motywu kolorystycznego (zmianie ulegnie kolor czcionki).

Aby kontrolka mogła być zarządzalna globalnie w konfiguracji globalnej interfejsu, należy ją zarejestrować za pomocą metody RegisterControl z klasy ModuleBase.

Aby kontrolka była zarządzalna lokalnie per widok, w którym została użyta (zarządzanie widokami), widok ten musi być odpowiednio zarejestrowany (za pomocą metody RegisterViews) oraz kontrolka musi posiadać atrybut Layout.Id. W przypadku użycie Layout.Id nie należy ustawiać domyślnych właściwości kontrolki bezpośrednio na kontrolce. Zdefiniowanie wartości domyślnej np. Foreground przed określeniem Layout.Id skutkować będzie ignorowaniem tej właściwości. Natomiast ustawienie Foreground po Layout.Id spowoduje, że właściwości nie będzie zarządzalna (zawsze będzie nadpisywana ustawiona bezpośrednio wartością).

Ustawianie atrybutów bezpośrednio powinno być wykonywane tylko dla właściwości niezarządzalnych, np. takich które są niezbędne do poprawnej pracy kontrolki (np. Binding).

 

  1. Styl globalny kontrolki lub lokalny (z użyciem x:Key)
<core:View.Resources>
        <Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
            <Setter Property="Foreground" Value="Red"/>
            <Setter Property="Background" Value="{DynamicResource ThemeBackground}"/>
        </Style>
</core:View.Resources>
..
<TextBlock core:Layout.Id="TextBlockLayoutId" />

 

Powyższy przykład ilustruje globalne ustawienie stylu koloru czcionki oraz tła dla wszystkich kontrolek TextBlock użytych w aktualnym widoku <core:View>.

<core:View.Resources>
        <Style x:Key="TextBlockStyle" 
               TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
            <Setter Property="Foreground" Value="Red"/>
            <Setter Property="Background" Value="{DynamicResource ThemeBackground}"/>
        </Style>
</core:View.Resources>
…
<TextBlock Style="{StaticResource TextBlockStyle}" core:Layout.Id="TextBlockLayoutId" />

 

Powyższa modyfikacja pokazuje jak za pomocą styli ustawić tylko wybraną kontrolkę.

 

  1. Atrybut domyślny zdefiniowany w ModernUI.xaml zgodnie z definicją <typ_atrybutu x:Key=”[LayoutId].Default.[Nazwa_typu]”>[wartość domyslna]</typ_atrubutu>

Plik ModernUI.xml

<SolidColorBrush x:Key="TextBlockLayoutId.Default.Foreground" Color="Red"/>

Plik widoku

<TextBlock core:Layout.Id="TextBlockLayoutId" />

 

Warunkiem wymaganym do poprawnego działania powyższego przykładu jest zarejestrowanie zasobów ModernUI.xaml modułu w klasie rejestrującej moduł (Nowy moduł). W konstruktorze wywołujemy:

LayoutService.RegisterResources(typeof(Module));

 

Ograniczeniami takiego podejścia jest niemożliwość zdefiniowania dynamicznych wartości oraz zgrupowania powtarzających się wartości pod wspólną nazwą. Przykładowo nie jest poprawne użycie następującego kodu:

<SolidColorBrush x:Key="TextBlockLayoutId.Default.Foreground" 
            Color="{DynamicResource ThemeColor}"/>

 

W tym przypadku typ Color (System.Windows.Media.Color) oraz ThemeColor (SolidColorBrush) są niezgodne!

Użycie tego sposobu jest obecnie niezalecane. Wyjątek stanowi definiowane domyślnych parametrów dla kolumn DataGrida.

Lista wspieranych właściwości

Poniższe tabelki zawierają wykaz właściwości, które są zarządzalne z panelu konfiguracji interfejsu dla poszczególnych kontrolek dostępnych w POS.

 

Typ kontrolki

Właściwość Nazwa
Wspierana właściwość [nazwa : typ] Nazwa właściwości w panelu zarządzania

 

FrameworkElement

System.Windows.FrameworkElement

Właściwość Nazwa
Width : double Szerokość
Height : double Wysokość
Margin : Thickness Margines
HorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
VerticalAlignment : VerticalAlignment Wyrównanie w pionie
MaxWidth : double Maks. szerokość
MaxHeight : double Maks. wysokość
Grid.Position : string Położenie

Control

System.Windows.Controls.Control

 

Właściwość Nazwa
Background : Brush Tło
Foreground : Brush Kolor tekstu
FontSize : double Rozmiar czcionki
FontWeight : FontWeight Waga czcionki
FontStyle : FontStyle Styl czcionki
Padding : Thickness Wypełnienie
Grid.Position : string Położenie

Grid

System.Windows.Controls.Grid

 

Właściwość Nazwa
Background : Brush Tło
Margin : Thickness Margines
Visibility: Visibility Widoczność
Width: double Szerokość
Height: double Wysokość
Grid.Position : string Pozycja

 

Comarch.POS.Presentation.Core.Controls.Grid

: System.Windows.Controls.Grid

 

Właściwość Nazwa
ColumnDefinition : string Kolumny
RowDefinition : string Wiersze

Border

System.Windows.Controls.Border

 

Właściwość Nazwa
Visibility : Visibility Widoczność
Background : Brush Tło

ScrollViewer

System.Windows.Controls.ScrollViewer

: System.Windows.Controls.Control

 

Właściwość Nazwa
VerticalScrollBarVisibility : ScrollBarVisibility Pionowy pasek przewijania
HorizontalSchrollBarVisibility : ScrollBarVisibility Poziomy pasek przewijania
Width: double Szerokość
Height : double Wysokość

 

Comarch.POS.Presentation.Core.Controls.ScrollViewer

: System.Windows.Controls.ScrollViewer

 

Właściwość Nazwa

Separator

System.Windows.Controls.Separator

: System.Windows.FrameworkElement

 

Właściwość Nazwa
Background : Brush Tło

TextBlock

System.Windows.Controls.TextBlock

: System.Windows.FrameworkElement

 

Właściwość Nazwa
Background : Brush Tło
Foreground : Brush Kolor tekstu
FontSize : double Rozmiar czcionki
FontWeight : FontWeight Waga czcionki
FontStyle : FontStyle Styl czcionki
Padding : Thickness Wypełnienie
TextAlignment : TextAlignment Wyrównanie tekstu
Visibility : Visibility Widoczność
TextWrapping : TextWrapping Zawijanie tekstu

DoubleColorTextBlock

Comarch.POS.Presentation.Core.Controls.DoubleColorTextBlock

: System.Windows.FrameworkElement

 

Właściwość Nazwa
FirstForeground : Brush Kolor tekstu 1
SecondForeground : Brush Kolor tekstu 2
Background : Brush Tło
FontSize : double Rozmiar czcionki
FontWeight : FontWeight Waga czcionki
FontStyle : FontStyle Styl czcionki
Padding : Thickness Wypełnienie
Visibility : Visibility Widoczność

ErrorTextBlock

Comarch.POS.Presentation.Core.Controls.ErrorTextBlock

: System.Windows.Controls.TextBlock

 

Właściwość Nazwa

TextBox

Comarch.WPF.Controls.TextBox

: System.Windows.FrameworkElement,

System.Windows.Controls.Control

 

Właściwość Nazwa
BorderThickness : Thickness Obramowanie
BorderBrush : Brush Kolor obramowania
FocusedBorderBrush : Brush Kolor obramowania (focus)
ErrorColor : Brush Kolor błędu
HintForeground : Brush Kolor podpowiedzi
Hint: string Kolor podpowiedzi

 

Comarch.POS.Presentation.Core.Controls.TextBox

: Comarch.WPF.Controls.TextBox

 

Właściwość Nazwa

Underline

Comarch.WPF.Controls.Underline

: System.Windows.FrameworkElement

 

Właściwość Nazwa
Stroke : Brush Kolor
StrokeThickness : Thickness Grubość

 

Comarch.POS.Presentation.Core.Controls.Underline

: Comarch.WPF.Controls.Underline

 

Właściwość Nazwa

 

ColumnDefinition

System.Windows.Controls.ColumnDefinition

 

Właściwość Nazwa
Width : double Szerokość

RowDefinition

System.Windows.Controls.RowDefinition

 

Właściwość Nazwa
Height : double Wysokość

DataGridColumn

System.Windows.Controls.DataGridColumn

 

Właściwość Nazwa
MaxWidth : double Maks. szerokość
MinWidth : double Maks. wysokość
Width : DataGridLength Szerokość
SortDirection : ListSortDirection Sortuj
Visibility : Visibility Widoczność
HorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie

DataGridTemplateColumn

System.Windows.Controls.DataGridTemplateColumn

: System.Windows.Controls.DataGridColumn

 

Właściwość Nazwa

 

DataGridTextColumn

System.Windows.Controls.DataGridTextColumn

: System.Windows.Controls.DataGridColumn

 

Właściwość Nazwa
FontSize : double Rozmiar czcionki
Foreground : Brush Kolor tekstu
FontWeight : FontWeight Waga czcionki
FontStyle : FontStyle Styl czcionki

DataGridCell

System.Windows.Controls.DataGridCell

 

Właściwość Nazwa
Margin : Thickness Margines
HorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie

DataGridColumnHeader

System.Windows.Controls.Primitives.DataGridColumnHeader

 

Właściwość Nazwa
Margin : Thickness Margines
HorizontalContentAlignment : HorizontalAlignment Wyrównanie zawartości w poziomie

DataGrid

Comarch.WPF.Controls.DataGrid

: System.Windows.FrameworkElement,

System.Windows.Controls.Control

 

Właściwość Nazwa

 

Comarch.POS.Presentation.Core.Controls.DataGrid

 

Właściwość Nazwa
RowBackground : Brush Tło wiersza
HeaderBackground : Brush Tło nagłówka
ScrollBarWidth : double Szerokość paska przewijania
VerticalScrollBarVisibility : Visibility Pionowy pasek przewijania
HorizontalScrollBarVisibility : Visibility Poziomy pasek przewijania
GroupBy : DataGridGroup Grupuj po
IsVirtualizingWhenGrouping : bool Wirtualizuj wiersze
ScrollUnit : ScrollUnit Jednostka suwaka
Grid.Position : string Pozycja

ItemsContainer

Comarch.POS.Presentation.Core.Controls.ItemsContainer

: System.Windows.FrameworkElement

 

Właściwość Nazwa
Orientation : Orientation Orientacja
Padding : Thickness Wypełnienie
Background : Brush Tło
MoreMultiButtonHeight : double Wysokość
MoreMultiButtonWidth : double Szerokość
MoreMultiButtonImageHeight : double Wysokość ikony
MoreMultiButtonImageWidth : double Szerokość ikony
MoreMultiButtonMargin : Thickness Margines
MoreMultiButtonImageMargin : Thickness Margines ikony
MoreMultiButtonFontSize : double Rozmiar czcionki
MoreMultiButtonIsImageVisible : bool Pokazuj ikonę

Expander

Comarch.POS.Presentation.Core.Controls.Expander

: System.Windows.FrameworkElement,

System.Windows.Controls.Control

 

Właściwość Nazwa
HeaderBackground : Brush Tło nagłówka
HeaderPadding : Thickness Wypełnienie nagłówka

Image

System.Windows.Controls.Image

: System.Windows.FrameworkElement

 

Właściwość Nazwa
Stretch : Stretch Rozciągaj
StretchDirection : StretchDirection Kierunek rozciągania

 

Comarch.WPF.Controls.Image

 

Właściwość Nazwa
HorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
HorizontalContentAlignment : HorizontalAlignment Wyrównanie zawartości w poziomie
VerticalAlignment : VerticalAlignment Wyrównanie w pionie
VerticalContentAlignment : VerticalAlignment Wyrównanie zawartości w pionie

 

Comarch.POS.Presentation.Core.Controls.Image

: System.Windows.FrameworkElement

 

Właściwość Nazwa
DefaultImageKey : ImageKey Domyślna ikona

BundleImage

Comarch.POS.Presentation.Core.Controls.BundleImage

 

Właściwość Nazwa
IconForeground : Brush Kolor
IconMargin : Thickness Margines
IconImageKey : ImageKey Ikona
Width : double Szerokość
Height : double Wysokość
PopupMaxWidth : double Maks. wysokość
PopupMinWidth : double Min. wysokość

Button

Comarch.POS.Presentation.Core.Controls.Button

: System.Windows.FrameworkElement,

System.Windows.Controls.Control

 

Inne bazujące na Button, np.:

Comarch.POS.Presentation.Core.Controls.AcceptButton

Comarch.POS.Presentation.Core.Controls.CancelButton

Comarch.POS.Presentation.Core.Controls.SelectButton

Comarch.POS.Presentation.Core.Controls.CleanButton

Comarch.POS.Presentation.Core.Controls.TileButton

Comarch.POS.Presentation.Core.Controls.PaymentTypeTile

Comarch.POS.Presentation.Core.Controls.PrintLabelButton

Comarch.POS.Presentation.Core.Controls. ShowItemsVariantsButton

 

Właściwość Nazwa
ImageKey : ImageKey Ikona
ImageMargin : Thickness Margines ikony
ImageWidth : double Szerokość ikony
ImageHeight : double Wysokość ikony
ImageHorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
ImageVerticalAlignment : VerticalAlignment Wyrównanie w    pionie
IsImageVisible : bool Pokazuj ikonę
ShortcutMargin : Thickness Margines
ShortcutWidth : double Szerokość
ShortcutHeight : double Wysokość
ShortcutHorizontalAlignment : HorizontalAlignment

 

Wyrównanie w poziomie

 

ShortcutVerticalAlignment : VerticalAlignment Wyrównanie   w pionie
IsShortcutVisible : bool Pokaż skrót
Shortcut : Shortcut Skrót klawiszowy
IsScaledShortcut : bool Skaluj zawartość
ContentMargin : Thickness Margines
ContentWidth : double Szerokość
ContentHeight : double Wysokość
ContentHorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
ContentVerticalAlignment : VerticalAligment Wyrównanie   w pionie
ContentVisibility : Visibility Widoczność
IsScaledContent : bool Skaluj zawartość
Orientation : Orientation Orientacja
ItemsContainer.NoWrapButton : bool Nie agreguj

RadioButton

Comarch.POS.Presentation.Core.Controls.RadioButton

: System.Windows.FrameworkElement,

System.Windows.Controls.Control

 

Właściwość Nazwa
CheckedStateBackground : Brush Tło wybranego element
CheckedStateForeground: Brush Tekst wybranego element
ImageKey : ImageKey Ikona
ImageMargin : Thickness Margines ikony
ImageWidth : double Szerokość ikony
ImageHeight : double Wysokość ikony
ImageHorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
ImageVerticalAlignment : VerticalAlignment Wyrównanie w    pionie
IsImageVisible : Visibility Pokazuj ikonę
ContentMargin : Thickness Margines
ContentWidth : double Szerokość
ContentHeight : double Wysokość
ContentHorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
ContantVerticalAlignment : VerticalAlignment Wyrównanie w pionie
ContentVisibility : Visibility Widoczność
IsScaledContent : bool Skaluj zawartość
Orientation : Orientantion Orientacja

FieldControl

Comarch.POS.Presentation.Core.Controls.FieldControl

: System.Windows.FrameworkElement

 

Właściwość Nazwa
Orientation : Orientation Orientacja
LabelMargin : Thickness Margines
LabelWidth : double Szerokość
LabelHeight : double Wysokość
LabelFontSize : double Rozmiar czcionki
LabelFontStyle : FontStyle Styl czcionki
LabelFontWeight : FontWeight Waga czcionki
LabelForeground : Brush Kolor tekstu
LabelForegroundError : Brush Kolor przy braku walidacji
LabelHorizontalAlignment: HorizontalAlignment Wyrównanie w poziomie
LabelVertivalAlignment: VerticalAlignment Wyrównanie w pionie
ContentIsRequired : bool Wymagana

CheckBox

Comarch.POS.Presentation.Core.Controls.CheckBox

: System.Windows.FrameworkElement,

System.Windows.Controls.Control

 

Właściwość Nazwa
CheckedStateBackground : Brush Tło wybranego element
CheckedStateForeground: Brush Tekst wybranego element
DisabledStateBackground : Brush Tło nieaktywnego
DisabledCheckedStateBackground : Brush Tło nieaktywnego wybranego
ImageKey : ImageKey Ikona
ImageMargin : Thickness Margines ikony
ImageWidth : double Szerokość ikony
ImageHeight : double Wysokość ikony
ImageHorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
ImageVerticalAlignment : VerticalAlignment Wyrównanie w    pionie
IsImageVisible : Visibility Pokazuj ikonę
ContentMargin : Thickness Margines
ContentWidth : double Szerokość
ContentHeight : double Wysokość
ContentHorizontalAlignment : HorizontalAlignment Wyrównanie w poziomie
ContantVerticalAlignment : VerticalAlignment Wyrównanie w pionie
ContentVisibility : Visibility Widoczność
IsScaledContent : bool Skaluj zawartość
Orientation : Orientantion Orientacja

ComboBox

Comarch.WPF.Controls.ComboBox

: System.Windows.FrameworkElement,

System.Windows.Controls.Control

 

Właściwość Nazwa
HorizontalContentAllignment : HorizontalAlignment Wyrównanie zawartości w poziomie
PopupBackground : Brush Tło
FocusedBorderBrush : Brush Kolor obramowania (focus)
ErrorColor : Brush Kolor błędu

 

Comarch.POS.Presentation.Core.Controls.ComboBox

: Comarch.WPF.Controls.TextBox

 

Właściwość Nazwa

 

ComboBox2

Comarch.POS.Presentation.Core.Controls.    ComboBox2

 

Właściwość Nazwa
LabelFontSize : double Rozmiar czcionki
LabelFontStyle : FontStyle Styl czcionki
LabelFontWeight : FontWeight Waga czcionki
Shortcut : Shortcut Skrót klawiszowy
FontSize : double Rozmiar czcionki
FontWeight : FontWeight Waga czcionki
FontStyle : FontStyle Styl czcionki
Visibility : Visibility Widoczność

AutoCompleteComboBox

Comarch.POS.Presentation.Core.Controls.AutoCompleteComboBox

: System.Windows.Controls.Control

 

Właściwość Nazwa
FocusedBorderBrush : Brush Kolor obramowania (focus)
ErrorColor : Brush Kolor błędu
HintForeground : Brush Kolor podpowiedzi
Hint: string Podpowiedź

SwitchBox

Comarch.POS.Presentation.Core.Controls.SwitchBox

: System.Windows.Controls.Control

 

Właściwość Nazwa
VerticalContentAlignment : VerticalAlignment Wyrównanie zawartości w pionie
Margin : Thickness Margines

SearchBox

Comarch.POS.Presentation.Core.Controls.SearchBox

 

Właściwość Nazwa
FontSize : double Rozmiar czcionki
Foreground : Brush Tło
HintForeground : Brush Kolor podpowiedzi
Margin: Thickness Margines
Hint: string Podpowiedź

DatePicker

Comarch.POS.Presentation.Core.Controls.DatePicker

: System.Windows.Controls.Control

 

Właściwość Nazwa
FocusedBorderBrush : Brush Kolor obramowania (focus)
ErrorColor : Brush Kolor błędu
BorderBrush : Brush Kolor obramowania
Visibility : Visibility Widoczność

ButtonSpinner

Comarch.POS.Presentation.Core.Controls.ButtonSpinner

 

Właściwość Nazwa
ButtonImageWidth : double Szerokość
ButtonImageHeight : double Wysokość
ButtonWidth : double Szerokość przycisku
ButtonHeight : double Wysokość przycisku

FilterItemsControl

Comarch.POS.Presentation.Core.Controls.FilterItemsControl

 

Właściwość Nazwa
MaxFilterItemsPerRow : int Maks. liczba filtrów
Visibility : Visibility Widoczność
Grid.Position : string Pozycja

SearchBoxFilter

Comarch.POS.Presentation.Core.Controls.SearchBoxFilter

: Comarch.POS.Presentation.Core.Controls.ComboBox2

 

Właściwość Nazwa
DefaultFilter : string Domyślna wartość filtra

StockTile

Comarch.POS.Presentation.Core.Controls.StockTile

 

Właściwość Nazwa
Background : Brush Tło
IsCodeVisible : bool Pokaż kod magazynu
WarehouseMargin : Thickness Margines
WarehouseCodeFontSize : double Rozmiar czcionki kodu
WarehouseNameFontSize : double Rozmiar czcionki nazwy
StocksFontSize : double Rozmiar czcionki
StocksMargin : Thickness Margines

SetValueNumbersKeyboard

Comarch.WPF.Controls.SetValueNumbersKeyboard

 

Właściwość Nazwa
Visibility : Visibility Widoczność

AttributeControl

Comarch.POS.Presentation.Core.Controls.AttributeControl

: System.Windows.Controls.Control

 

Właściwość Nazwa

AssistantControl

Comarch.POS.Presentation.Core.Controls.AssistantControl

 

Właściwość Nazwa
LabelFontSize : double Rozmiar czcionki
LabelFontStyle : FontStyle Styl czcionki
LabelFontWeight : FontWeight Waga czcionki
FontSize : double Rozmiar czcionki
FontWeight : FontWeight Waga czcionki
FontStyle : FontStyle Styl czcionki
Shortcut : Shorcut Skrót klawiszowy
Visibility : Visibility Widoczność
Grid.Position : string Pozycja

DocumentKeypad

Comarch.POS.Presentation.Core.Controls.DocumentKeypad

 

Właściwość Nazwa
HeaderFontSize : double
KeypadHeaderColor : Brush

 




Tworzenie zarządzalnych widoków

W artykule Tworzenie widoków omówiono podstawowe kroki niezbędne do utworzenia szkieletu nowego widoku. Przedstawiono również jak taki pusty jeszcze widok poprawnie zarejestrować, aby był zarządzalny w trakcie działania aplikacji (Rejestracja widoków do nawigacji oraz zarządzania wyglądem).

Każdy element (kontrolka) oznaczony unikalnym identyfikatorem LayoutId automatycznie staje się zarządzalny. Zarządzanie polega w większości przypadków na możliwości manipulacji niektórymi właściwościami kontrolek, takimi jak na przykład kolor tła, tekstu, formatowanie czcionki, ustawianie marginesu, szerokości, wysokości, itp. Zostało to również opisane w poprzednim rozdziale.

Zarządzalne elementy zyskują dodatkową cechę w momencie, gdy zostaną one zadeklarowane wewnątrz jednego z dostępnych w aplikacji POS kontenerów: Grid oraz ItemsContainer (z przestrzeni Comarch.POS.Presentation.Core.Controls). Cecha ta to możliwość określania czy i w jakim miejscu element ma się znaleźć w kontenerze.

Zarządzanie elementami w kontenerze ItemsContainer

Dodanie dowolnych kontrolek wewnątrz kontenera ItemsContainer skutkuje domyślną prezentacją ich w kolejności w jakiej zostały zadeklarowane. Specyfika kontenera sprawa, że elementy domyślnie ustawiają się obok siebie poziomo lub pionowo w zależności od ustawionej na kontenerze właściwości Orientation (która oczywiście również może być zarządzalna). Jeżeli kontener będzie posiadał unikalny LayoutId stanie się on zarządzalny oraz pozwoli użytkownikowi na manipulację jego elementami (wszystkie elementy również muszą posiadać swoje unikalne identyfikatory LayoutId). Gdy użytkownik przejdzie do trybu zarządzania widokiem zawierającym ten kontener, będzie mógł za pomocą myszki chwycić dowolną kontrolkę znajdującą się wewnątrz i przeciągnąć ją, zmieniając kolejność wewnętrznych elementów kontenera lub całkiem wyrzucić element z kontenera lub też wrzucić ponownie.

Elementy zadeklarowane wewnątrz kontenera ItemsContainer mogą istnieć tylko i wyłącznie w tym kontenerze. To znaczy, że nie jest możliwe, aby użytkownik mógł wrzucił kontrolkę do innego kontenera. Ograniczenie to znosi kontener typu Grid.

Przykład.

<StackPanel>
      <controls:ItemsContainer core:Layout.Id="ExampleView.ItemsContainer1">
          <TextBlock core:Layout.Id="ExampleView.TextBlock1"/>
          <Button core:Layout.Id="ExampleView.Button1"/>
      </controls:ItemsContainer>
      
      <controls:ItemsContainer core:Layout.Id="ExampleView.ItemsContainer2">
          <TextBlock core:Layout.Id="ExampleView.TextBlock2"/>
          <Button core:Layout.Id="ExampleView.Button2"/>
      </controls:ItemsContainer>
  </StackPanel>

 

Na powyższym przykładzie widzimy widok składający się z dwóch zarządzalnych kontenerów ExampleView.ItemsContainer1 oraz ExampleView.ItemsContainer2. Elementy zadeklarowane wewnątrz tych kontenerów domyślnie wyświetlą się w takiej kolejności w jakiej są zapisane.

Po wejściu w tryb zarządzania tym widokiem użytkownik będzie mógł zmienić kolejność elementów. Będzie mógł wyrzucić element/elementy z kontenerów lub wrzucić je tam ponownie (jeżeli wcześniej zostały przez niego wyrzucone). Nie będzie natomiast mógł przerzucić np. elementu ExampleView.TestBlock1 z kontenera ItemsContainer1 do kontenera ItemsContainer2. Przerzucanie elementów pomiędzy kontenerami nie będzie możliwe.

Zarządzanie elementami w kontenerze Grid

Drugim kontenerem pozwalającym na manipulację jego elementami jest Grid. Kontener ten wymaga zdefiniowania siatki kolumn i wierszy, w których będzie można umieszczać elementy. Siatka ta może zostać określona domyślnie w xaml-u oraz może być zarządzalna przez użytkownika (użytkownik może zmieniać liczbę kolumn i wierszy). Domyślnie każdy element dodany do Grida nie będzie wyświetlany dopóki użytkownik w zarządzaniu nie przeciągnie elementu do wybranej komórki kontenera.

Istnieje możliwość zmiany domyślnej prezentacji elementów składowych, aby działało to tak jak dla kontenera ItemsContainer. W przypadku, gdy właściwość DefaultShowChildren=true, wszystkie elementy domyślnie zostaną zaprezentowane na widoku.

Każdy element zdefiniowany w kontenerze Grid może zostać, podobnie jak w przypadku kontenera typu ItemsContainer, wyrzucony/wrzucony do kontenera. Dodatkowo, jeżeli w kontenerze istnieje inny kontener (Grid lub ItemsContainer) elementy mogę być wrzucone bezpośrednio do zagnieżdżonego kontenera. Dzięki temu możliwe staje się np. przenoszenie kontrolek pomiędzy różnymi kontenerami ItemsContainer.

Przykład.

<controls:Grid core:Layout.Id="ExampleView.BaseGrid">
      <TextBlock core:Layout.Id="ExampleView.TextBlock1"/>
      <Button core:Layout.Id="ExampleView.Button1"/>
      <TextBlock core:Layout.Id="ExampleView.TextBlock2"/>
      <Button core:Layout.Id="ExampleView.Button2"/>
 
      <controls:ItemsContainer core:Layout.Id="ExampleView.ItemsContainer1"/>     
      <controls:ItemsContainer core:Layout.Id="ExampleView.ItemsContainer2" />
  </controls:Grid>

 

W powyższym przykładzie wszystkie kontrolki zostały zdefiniowane bezpośrednio w Gridzie. Teraz podczas pierwszego uruchomienia takiego widoku, zobaczymy pusty widok. Dlatego, że domyślnie Grid nie prezentuje elementów. Natomiast po przejściu do trybu zarządzania będziemy mieć możliwość zdefiniowana siatki Grida (jego kolumn i wierszy) oraz możliwość przeciągnięcia każdej ze zdefiniowanych kontrolek na Grid. Będziemy mogli również przerzucić np. kontrolkę ExampleView.TextBlock1 bezpośrednio do kontenera ExampleView.ItemsContainer1 lub ExampleView.ItemsContainer2, po uprzednim wrzuceniu tych kontenerów na Grid.

Domyślna siatkę Grida możemy również zdefiniować już w xamlu za pomocą właściwości ColumnDefinition oraz RowDefinition.  Żądaną liczbę kolumn lub wierszy ustalamy wprowadzając tyle wartości (konkretna liczba, * lub Auto) ile kolumn/wierszy chcemy uzyskać oddzielając je przecinkami.

I tak np. aby zdefiniować pięć kolumn, z których pierwsza będzie miała szerokość 100, druga automatycznie dobierana, a pozostałe proporcjonalnie po równo dostaną pozostałą przestrzeń, właściwość ColumnDefinition musi mieć wartość: „100,Auto,*,*,*”.

Możemy również zdefiniować domyślne położenie elementów w odpowiedniej komórce Grida za pomocą właściwości Grid.Position. Wartość definiowana jest za pomocą czterech liczb oddzielonych przecinkami, które kolejno oznaczają numer wiersza (numeracja od 0), numer kolumny (numeracja od 0), liczba wierszy (minimum 1) oraz liczba kolumn jakie ma zajmować kontrolka.

Przykład.

<controls:Grid core:Layout.Id="ExampleView.BaseGrid" DefaultShowChildren="True">
        <controls:Grid.Style>
            <Style TargetType="controls:Grid" BasedOn="{StaticResource {x:Type controls:Grid}}">
                <Setter Property="ColumnDefinition" Value="*,*,*" />
                <Setter Property="RowDefinition" Value="*,*,*" />
            </Style>
        </controls:Grid.Style>
 
        <TextBlock core:Layout.Id="ExampleView.TextBlock1" Text="Hello">
            <TextBlock.Style>
                <Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
                    <Setter Property="controls:Grid.Position" Value="1,1,1,1" />
                </Style>
            </TextBlock.Style>
        </TextBlock>
 
    </controls:Grid>

 

Teraz widok składa się z Grida, któremu włączyliśmy domyślnie prezentowanie elementów oraz zdefiniowaliśmy trzy równej szerokości kolumny (szerokość ustalana dynamicznie w zależności od wielkości od szerokości widoku) oraz trzy równej wysokości wiersze (wysokość ustalana dynamicznie tak jak w przypadku szerokości). Kontener składa się z jednego elementu jakim jest TextBlock, któremu ustalono domyślną pozycję w kontenerze na środkową komórkę.




Rozszerzalność istniejących widoków

Każdy istniejący w POS widok można rozszerzyć o dodatkowe elementy. Rozszerzenie może polegać na dodaniu nowej kolumny do datagrida, usunięciu istniejącej lub dodanie dowolnej kontrolki w miejscu do tego wcześniej przygotowanym. Możliwe jest również wywoływanie programów firm trzecich, poprzez proste dodanie kontrolki przycisku, pod którym będzie kryła się logika inicjująca inny proces.

Dodanie nowego elementu do istniejącego kontenera

Za pomocą rozszerzeń można dodać praktycznie dowolną kontrolkę do wybranego istniejącego widoku, ale tylko w miejscach uprzednio na to przygotowanych. Nie ma limitu liczby dodawanych elementów, jednakże można  je umieszczać tylko w specjalne przygotowanych na rozszerzalność kontenerach (ItemsContainer oraz Grid).

Aby dodać kontrolkę do istniejącego już widoku, należy zacząć od ustalenia czy widok jest zarządzalny i czy posiada odpowiedni kontener, w którym będzie można umieścić nowy element. W tym celu w aplikacji POS należy otworzyć widok zarządzania interfejsem. Z rozwijalnej drzewiastej listy widoków wybrać ten, dla którego będzie tworzone rozszerzenie. Następnie kliknąć w belkę Elementy, a w niej za pomocą rozwijalnej listy kontenerów wybrać, w którym miejscu tego widoku ma się znaleźć nowy element. Po wybraniu kontenera należy zapisać jego nazwę, ponieważ jest ona globalnym identyfikatorem, który będzie niezbędny na kolejnym etapie implementacji rozszerzenia.

Jeżeli tworząc własny widok chcemy umożliwić jego rozszerzalność, musimy przygotować na tę ewentualność miejsce, poprzez dodanie jednej bądź więcej kontrolek kontenera lub budując widok z użyciem kontrolki Grida (Comarch.POS.Presentation.Core.Controls), pamiętając o nadaniu im unikalnych identyfikatorów LayoutId (więcej o tym w artykule Zarządzanie widokiem i jego elementami).

Dodawanie kontrolki do widoku rozpoczynamy od utworzenia nowego modułu (patrz Nowy moduł) lub jeżeli już został utworzony przechodzimy do ciała metody Initialize() w klasie Module. Aby rozszerzyć widok o nową kontrolkę należy skorzystać z metody

AddButtonToContainer – w przypadku dodawania przycisku lub

AddElementToContainer<TFrameworkElement> – w przypadku dodawania dowolnej kontrolki typu FrameworkElement

Wymagane parametry obu metod to:

  • containerLayoutId (string) – identyfikator kontenera, do którego zostanie dodana kontrolka,
  • buttonLayoutId / elementLayoutId (string) – unikalny identyfikator nowej kontrolki (każda kontrolka musi posiadać niepowtarzalny identyfikator w kontenerze),
  • styleKey (string) – opcjonalna nazwa klucza w pliku ModernUI.xaml, gdzie zdefiniowany zostanie styl dla kontrolki,
  • buttonViewModelFunc / elementViewModelFunc (Func<IViewModel, FrameworkElementViewModel>) – opcjonalny parametr pozwalający na utworzenie lokalnego ViewModelu dla kontrolki. W ViewModel tym będzie można zdefiniować logikę, do której będzie mogła zbindować się kontrolka (za pomocą stylu).

 

Analogicznie, aby dodać element do Grida należy wywołać:

AddElementToGrid<TFrameworkElement> ­– parametry takie same jak dla elementów dodawanych do kontenera

 

Przykład.

Chcemy dodać przycisk do widoku dokumentu paragonu, którego kliknięcie spowoduje pojawienie się notyfikacji z wartością dokumentu.

Nazwa kontenera, do którego zostanie dodany przycisk to DocumentViewRightButtonsContainer. W klasie Module nowego modułu rozszerzającego w metodzie Initialize dodajemy linijkę:

AddButtonToContainer("DocumentViewRightButtonsContainer", "ExtensionButton1", "ButtonStyle", ButtonViewModelFunc);

gdzie ExtentionButton1 to unikalna nazwa (identyfikator LayoutId) dla nowego przycisku, ButtonStyle to nazwa dla klucza ze style dla tego przycisku, a ButtonViewModelFunc to metoda która będzie zwracać lokalny ViewModel w którym będzie zaimplementowana logika wywołująca powiadomienie z odpowiednią treścią.

private FrameworkElementViewModel ButtonViewModelFunc(IViewModel viewModel)
{
    return new ButtonViewModel(viewModel, ViewManager, Container);
}
 
public class ButtonViewModel : FrameworkElementViewModel
{
    public DelegateCommand ExtensionButtonCommand { get; set; }
 
    private readonly IDocumentViewModel _documentViewModel;
    private readonly INotificationService _notifyService;
 
    public ButtonViewModel(IViewModel viewModel, IViewManager viewManager, IUnityContainer container) : base(viewModel, viewManager)
    {
        if (viewModel.IsDesignMode)
            return;

        _notifyService = container.Resolve<INotificationService>();
        _documentViewModel = (DocumentViewModel)viewModel;
        ExtensionButtonCommand=new DelegateCommand(ExtensionButtonAction);
    }
 
    private void ExtensionButtonAction()
    {
        _notifyService.Show($"Wartość dokumentu: {_documentViewModel.Document.Value}", NotifyIcon.Information);                
    }
}

 

W pliku ModernUI.xaml naszego modułu dopisujemy styl dla nowego przycisku, określając w nim treść przycisku oraz bindujemy akcję kliknięcia do komendy powiązanej z metodą ExtensionButtonAction.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:buttons="clr-namespace:Comarch.POS.Presentation.Core.Controls.Buttons;assembly=Comarch.POS.Presentation.Core">

<Style x:Key="ButtonStyle" TargetType="buttons:Button"
       BasedOn="{StaticResource {x:Type buttons:Button}}">
    <Setter Property="Content" Value="Pokaż wartość" />
    <Setter Property="Command" Value="{Binding ExtensionButtonCommand}" />
</Style>

 

Pełny kod przykładu dostępny w Dodawanie kontrolki do kontenera istniejącego widoku

Dodanie kolumny do istniejącego DataGrida

Najprostszym sposobem dodania nowej kolumny do istniejącego datagrida widoku dokumentu (np. paragonu/faktury, zamówienia sprzedaży, itp.) jest przypisanie w systemie ERP atrybutu do elementu dokumentu (więcej w Obsługa atrybutów). Jeżeli natomiast nie chcemy, aby nowa kolumna była atrybutem, należy postąpić podobnie jak w przypadku dodawania kontrolek do kontenerów. W celu rozszerzenia istniejącej listy DataGrid musimy znać jej unikalny identyfikator. Aby go poznać, będąc w zarządzaniu widokami otwieramy widok zawierający datagrid, zaznaczamy go i odczytujemy jego Layout Id z sekcji Właściwości. Następnie za pomocą metody RegisterDataGridExtension znajdującej się w klasie ModuleBase, uzyskujemy dostęp do tej kontrolki i implementujemy dodanie nowej kolumny do kolekcji. Parametrami tej metody są:

  • dataGridLayoutId (string) – identyfikator layoutId rozszerzanego datagrida,
  • action (Action<DataGrid, IViewModel, bool>) – delegat do metody, która zostanie wywołana podczas tworzenia kontrolki datagrida

Przykład 1.

Chcemy dodać kolumnę do listy na widoku nowego paragonu, która będzie informować czy dodana pozycja nie przekracza zdefiniowanej kwoty.

Identyfikator Layout Id listy na paragonie to ReceiptDocumentViewDataGrid. W metodzie Initialize klasy Module dodajemy:

RegisterDataGridExtension("ReceiptDocumentViewDataGrid", DataGridNewColumn);

 

Następnie implementujemy metodę DataGridNewColumn:

private void DataGridNewColumn(DataGrid dataGrid, IViewModel viewModel, bool isDesignMode)
{
    var column = new DataGridTextColumn
    {
        Header = "Przekracza 100?",
        Binding = new Binding {Converter = new ValidateConverter()}
    };
 
    Layout.SetId(column, "DocumentViewDataGridExtendedColumn1"); 
    dataGrid.Columns.Add(column);
}

 

Parametr isDesignMode przyjmie wartość true, gdy widok zawierający ten datagrid zostanie otwarty w trybie zarządzania interfejsem. Następnie dodajemy klasę konwertera ValidateConverter, która będzie zawierała logikę zwracającą odpowiednią wartość dla każdej komórki dodanej kolumny:

internal class ValidateConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var row = value as TradeDocumentItemRow;
 
        if (row!=null)
        {
            return row.Price > 100 ? "TAK" : "NIE";
        }
 
        //w przypadku pozycji na skup
        return "Nie dotyczy";
    }
   … 
}

 

Powyższy przykład zakłada, że wszystkie informacje niezbędne do zaprezentowania wartości są dostępne w encji wiersza. W przypadku, gdy logika biznesowa dla nowej kolumny też musi zostać rozszerzona należy najpierw pobrać niezbędne dodatkowe dane dla nowej kolumny. Pobrane dane możemy przechować w specjalnie przygotowanej publicznej właściwości dostępnej na każdym viewmodelu – CustomDataDictionary. Jest to właściwość typu słownikowego (string, object), do której możemy się odwołać w zdefiniowanej kolumnie za pomocą bindingu.

 

Przykład 2.

Dodajemy nową kolumnę na widoku paragonu, która będzie prezentować nazwę cennika produktu dodanego na listę. Encja produktu (IDocumentItemRow) zawiera tylko identyfikator cennika (PriceListId), ale nie posiada jej nazwy.

 

Zaczynamy od pobrania pełnej listy cenników i zapisania jej w słowniku CustomDataDictionary. Cenniki wystarczy, że pobierzemy tylko raz na początku, np. podczas otwierania widoku. W tym celu możemy skorzystać z extension points i wpiąć się w AfterOnInitializationEvent, który jest wywoływany z metody AfterOnInitialization po inicjalizacji, lub poprzez podziedziczenie po klasie DocumentViewModel i przeciążenie metody OnInitialization. Możemy to również zrobić podczas wpinania się z nową kolumną, czyli w akcji metody RegisterDataGridExtension. Na potrzeby tego przykładu wybierzmy właśnie tę ostatnią metodę.

W metodzie Initialize klasy Module wywołujemy:

RegisterDataGridExtension("ReceiptDocumentViewDataGrid", DataGridNewColumnWithCustomBL);

 

Następnie implementujemy metodę DataGridNewColumnWithCustomBL

private void DataGridNewColumnWithCustomBL(DataGrid dataGrid, IViewModel viewModel, bool isDesignMode)
{
       if (viewModel is CustomDocumentViewModel vm)
       {
                //fill custom dictionary with dictionary of data for custom column (priceListId => name)
                vm.CustomDataDictionary.Add(CustomColumnTest, new Dictionary<int, string>
                {
                    {1, "pierwszy" },
                    {2, "drugi" }
                });
 
                //after initial price changed refresh custom column binding
                vm.AfterSetInitialPrice += () => { 
                       vm.OnPropertyChanged(nameof(vm.CustomDataDictionary)); 
                };
       }
 
       var column = new DataGridTextColumn
       {
          Header = "Price list name",
          Binding = new MultiBinding
          {
            Converter = new CustomMultiConverter(),
            Bindings =
            {
              new Binding(),
              new Binding
              {
                RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(DocumentView), 1),
 Path = new PropertyPath($"DocumentViewModel.CustomDataDictionary[{CustomColumnTest}]")
              }
            }
          }
       };
 
    dataGrid.Columns.Add(column);
}

 

W pierwszej części metody symulujemy pobranie cenników poprzez wypełnienie słownika CustomDataDictionary słownikiem (id cennika, nazwa cennika) z dwiema wartościami. Celowo tworzymy słownik w słowniku, ponieważ CustomDataDictionary może potencjalnie przydać się to przechowywania również innych informacji (np. dla innej logiki biznesowej). Klucz CustomColumnTest to zdefiniowane w klasie Module pole typu const string zawierające unikalną nazwę, po której będziemy identyfikować naszą kolekcję danych (cenniki).

public const string CustomColumnTest = "CustomColumnTest";

W drugiej części metody tworzona jest kolumna z multibindingiem wraz z konwerterem. Multibinding definiuje binding do aktualnej encji wiersza oraz do słownika z cennikami zawartego w słowniku CustomDataDictionary pod kluczem CustomColumnTest. W konwerterze natomiast uzyskujemy oba obiekty, dzięki czemu możemy zwrócić nazwę cennika na podstawie id zawartego w encji oraz nazwy zawartej w słowniku.

internal class CustomMultiConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var documentItemRow = values[0] as IDocumentItemRow;
            var dictionary = values[1] as Dictionary<int, string>;
 
            //if item has price list id then show price list name (when initial price changes, price list id nulls)
            if (documentItemRow?.PriceListId.HasValue ?? false)
            {
                string name = null;
                if (dictionary?.TryGetValue(documentItemRow.PriceListId.Value, out name) ?? false)
                {
                    return name;
                }
            }
 
            return null;
        }
... 
    }

 

Pełny przykład zawiera jeszcze wpięcie na metodę SetInitialPrice, w którym wywoływane jest żądanie odświeżenia bindingu z CustomDataDictionary, ponieważ po zmianie ceny początkowej prezentowana cena nie jest już ceną z cennika (właściwość PriceListId  będzie teraz nullem) i nowa kolumna z nazwą nie powinna jej już prezentować.

 

Pełny kod przykładów dostępny w Dodawanie kolumny do DataGrida na istniejącym widoku

Dostęp do istniejącego elementu

Możliwe jest również uzyskanie dostępu do właściwości każdej istniejącej kontrolki, która posiada ustawiony layoutId. W celu w klasie Module należy skorzystać z metody AttachToFrameworkElement. Metoda posiada analogiczne parametry jak metoda RegisterDataGridExtension.

Dodawanie elementów do obszaru statusowego

Obszar statusowy charakteryzuje się tym, że elementy tam umieszczone dostępne są przez cały czas działania aplikacji, niezależnie od otwartych widoków. Dostęp do tego obszaru możliwy jest z poziomu dowolnego widoku podstawowego. W celu dodania kontrolki z własną logiką do obszaru statusowego należy w klasie Module, w metodzie Initialize wywołać metodę AddElementToStatusBar<TFrameworkElement>. Argumentami metody są:

  • elementLayoutId (string) – unikalny identyfikator dodawanej kontrolki,
  • styleKey (string) – nazwa klucza w pliku ModernUI.xaml, gdzie zdefiniowany zostanie styl dla kontrolki,
  • elementViewModelFunc (Func<IStatusBar,StatusBarEementBase>) – delegat do metody, która zostanie wywołana podczas tworzenia kontrolki

Przykład implementacji w Przykład rozszerzenia obszaru statusowego




Rozszerzalność zarządzania interfejsem

Omówiona do tej pory rozszerzalność interfejsu dotyczy tylko wewnętrznych kontrolek POS. W przypadku zaistnienia potrzeby dodania nowej kontrolki, którą będziemy chcieli zarządzać możemy skorzystać z mechanizmu rozszerzalności zarządzania interfejsem. Mechanizm ten pozwala na rejestrowanie nowych typów kontrolek i określanie jakie jej właściwości będą zarządzalne. Oprócz tego możliwe jest również dodawanie zarządzania do właściwości już istniejących kontrolek oraz ukrywania zarządzania wskazanych właściwości.

Ukrywanie właściwości do edycji

Ukrycie właściwości skutkuje usunięciem jej pozycji z zarządzania interfejsem. Użytkownik mający dostęp do zarządzania interfejsem nie będzie mógł zmienić jej wartości. Kontrolka, której właściwość zostanie ukryta będzie miała ustawioną wartość na domyślną zdefiniowana w stylach. Jednakże, jeżeli domyślna wartość została zdefiniowana w pliku ModernUI.xaml, zostanie ona zignorowana (różnice pomiędzy definiowaniem wartości domyślnych opisane zostały w artykule Zarządzanie widokiem i jego elementami

Ukrywać możemy określone właściwości na konkretnej kontrolce unikalnie zidentyfikowanej za pomocą Layout Id. Metoda ukrywająca właściwości to DisablePropertiesForLayoutElement znajdująca się w klasie PropertiesManager. Parametry tej metody:

  • layoutId (string) – parametr będący identyfikatorem kontrolki, której właściwość chcemy ukryć.
  • properties (params DependencyProperty[]) – jeden lub więcej właściwości, które mają zostać ukryte dla danej kontrolki.

 

Przykład ukrycia właściwości Ikona (ImageKey) z kafla Nowy dokument (layout id NewSalesDocument):

PropertiesManager.Instance.DisablePropertiesForLayoutElement("NewSalesDocument", TileButton.ImageKeyProperty);

 

Uwaga. W powyższym przypadku zniknie ikona na kaflu Nowy document ponieważ domyślna wartość dla właściwości ImageKey została zdefiniowana w pliku ModernUI.xaml.

Przykład ukrycia właściwości Wysokość (Height) z przycisku Zatwierdź (AcceptButton) konfiguracji globalnej:

PropertiesManager.Instance.DisablePropertiesForLayoutElement("AcceptButton", FrameworkElement.HeightProperty);

 

Dodawanie nowych właściwości do edycji

W przypadku, gdy istniejąca kontrolka albo zupełnie nowa ma zdefiniowane własne właściwości, które będziemy chcieli, żeby były modyfikowalne w zarządzaniu interfejsem, należy je zarejestrować. Można to zrobić na dwa sposoby, w zależności od tego czy właściwość ma być widoczna tylko dla kontrolki w konkretnym miejscu lub też globalnie dla wszystkich wystąpień tej kontrolki.

W pierwszym przypadku (rejestracja widoczna tylko dla kontrolki we wskazanym miejscu) należy skorzystać z metody AddPropertiesForLayoutElement z klasy PropertiesManager. Parametry metody:

  • layoutId (string) – unikalny identyfikator kontrolki (Layout Id),
  • properties (params DependencyProperty[]) – właściwości kontrolki, które mają być zarządzalne

W drugim przypadku (rejestracja widoczna globalnie we wszystkich miejscach użycia kontrolki) należy skorzystać z metody RegisterControlProperties z klasy PropertiesManager. Parametry metody:

  • controlType (Type) – typ kontrolki,
  • properties (params DependencyProperty[]) – właściwości, które mają być widoczne dla kontrolki wskazanego typu
  • baseTypeProperties (Type) – typ kontrolki bazowej, z której zostaną pobrane zarejestrowane już wcześniej właściwości i użyte do rejestracji właściwości tej kontrolki

 

Obsługa atrybutów

Aplikacji POS obsługuje atrybuty jednowartościowe synchronizowane z systemu ERP. Typy wspieranych atrybutów to: tekst, liczba, słownik, wartość logiczna, lista, data. Aby dana klasa atrybutu została przesynchronizowana do stanowiska POS, w systemie ERP (Altum) należy oznaczyć tę klasę do podglądu i/lub edycji w sekcji Comarch POS (dostać się tam można poprzez zaznaczenie zakładki „Konfiguracja”, następnie „Atrybuty” i na końcu poprzez wybranie odpowiedniego atrybutu). Atrybuty mogę być prezentowane w postaci kolumn na liście (kontrolka datagrid) lub też w postaci kontrolek (generowanych w zależności od typu klasy atrybutu, np. dla klasy typu tekst będzie to TextBox, podczas gdy dla typu słownik będzie to ComboBox).

Dodanie nowej klasy atrybutu do istniejącego widoku w POS

Każdy widok w aplikacji POS, który wspiera atrybuty jest powiązany bezpośrednio z pewnym obiektem systemu ERP. Przykładowo, widok Lista kontrahentów prezentuje atrybuty na liście w postaci kolumn. Może pokazać atrybuty wraz z ich wartościami przypisanymi tylko do obiektu Kontrahent w systemie ERP. W przypadku widoku paragonu, lista (datagrid) prezentuje klasy atrybutów powiązane tylko z obiektem Element paragonu (PAR), natomiast atrybuty prezentowane w prawym dolnym rogu ekranu w postaci kontrolek powiązane są z obiektem Paragon (PAR).

Lista widoków wspierających atrybutu wraz z powiązanymi obiektami systemu ERP

Widok POS Element widoku Obiekt biznesowy systemu ERP
Dokument paragonu

(DocumentView)

Lista pozycji Element paragonu (PAR)
Dokument Paragon (PAR)
Podgląd dokumentu paragonu

(DocumentPreviewView)

Lista pozycji Element paragonu (PAR)
Dokument Paragon (PAR)
Atrybuty dokumentu paragonu (tryb szybkiej  sprzedaży)

(DocumentAttributesView)

Dokument Paragon (PAR)
Szczegóły pozycji dokumentu paragonu (tryb szybkiej  sprzedaży)

(DocumentItemPropertiesView)

Dokument Element paragonu (PAR)
Dokument faktury

(DocumentView)

Lista pozycji Element faktury sprzedaży (FS)
Dokument Faktura sprzedaży (FS)
Podgląd dokumentu faktury

(DocumentPreviewView)

Lista pozycji Element faktury sprzedaży (FS)
Dokument Faktura sprzedaży (FS)
Atrybuty dokumentu faktury (tryb szybkiej  sprzedaży)

(DocumentAttributesView)

Dokument Faktura sprzedaży (FS)
Szczegóły pozycji dokumentu faktury (tryb szybkiej  sprzedaży)

(DocumentItemPropertiesView)

Dokument Element faktury sprzedaży (FS)
Korekta ręczna paragonu

Korekta ręczna paragonu – tryb wymiany

(ManualExchangeView)

Lista pozycji Element ręcznej korekty ilościowej paragonu (KIPAR)
Dokument Ręczna korekta ilościowa paragonu (KIPAR)
Podgląd korekty ręcznej paragonu

(ManualCorrectionPreviewView)

Lista pozycji Element ręcznej korekty ilościowej paragonu (KIPAR)
Dokument Ręczna korekta ilościowa paragonu (KIPAR)
Korekta ręczna faktury

Korekta ręczna faktury – tryb wymiany

(ManualExchangeView)

Lista pozycji Element ręcznej korekty ilościowej faktury sprzedaży  (KIFS)
Dokument Ręczna korekta ilościowa faktury sprzedaży (KIFS)
Podgląd korekty ręcznej faktury

(ManualCorrectionPreviewView)

Lista pozycji Element ręcznej korekty ilościowej faktury sprzedaży (KIFS)
Dokument Ręczna korekta ilościowa faktury sprzedaży  (KIFS)
Korekta paragonu

(ExchangeView)

Lista pozycji Element korekty ilościowej paragonu
Dokument Korekta ilościowa paragonu
Podgląd korekty paragonu

(CorrectionPreviewView)

Lista pozycji Element korekty ilościowej paragonu
Dokument Korekta ilościowa paragonu
Korekta faktury

(ExchangeView)

Lista pozycji Element korekty ilościowej faktury sprzedaży (KIFS)
Dokument Korekta ilościowa faktury sprzedaży (KIFS)
Podgląd korekty faktury

(CorrectionPreviewView)

Lista pozycji Element korekty ilościowej faktury sprzedaży (KIFS)
Dokument Korekta ilościowa faktury sprzedaży (KIFS)
Dokument faktury zaliczkowej

(AdvanceInvoiceView)

Dokument Faktura zaliczkowa
Podgląd faktury zaliczkowej

(AdvanceInvoicePreviewView)

Dokument Faktura zaliczkowa
Korekta faktury zaliczkowej

(AdvanceInvoiceCorrectionView)

Dokument Korekta faktury zaliczkowej
Podgląd korekty faktury zaliczkowej

(AdvanceInvoiceCorrectionPreviewView)

Dokument Korekta faktury zaliczkowej
Dokument TAX FREE

(TaxFreeView)

Lista pozycji Element TAX FREE (TF)
Dokument TAX FREE (TF)
Podgląd TAX FREE

(TaxFreePreviewView)

Lista pozycji Element TAX FREE (TF)
Dokument TAX FREE (TF)
Dokumenty handlowe

(DocumentsListView)

Lista pozycji Paragon (PAR), Faktura sprzedaży (FS), Faktura zaliczkowa, Korekta ilościowa paragonu (KIPAR), Korekta ilościowa faktury sprzedaży (KIFS), Ręczna korekta ilościowa paragonu (KIPAR), Ręczna korekta ilościowa faktury sprzedaży (KIFS), Korekta faktury zaliczkowej, TAX FREE (TF)
Kontrahenci

(CustomesListView)

Lista pozycji Kontrahent
Dodaj/Edytuj kontrahenta

(CustomerView)

Dokument Kontrahent

 

Dodaj/Edytuj kontrahenta biznesowego

(BusinessCustomerView)

Dokument Kontrahent

 

Szczegóły kontrahenta

(CustomerDetailsView)

Dokument Kontrahent

 

Szczegóły kontrahenta biznesowego

(BusinessCustomerDetailsView)

Dokument Kontrahent

 

Nowe zamówienie

(SalesOrderView)

Lista pozycji Element zamówienia sprzedaży (ZS)
Dokument Zamówienie sprzedaży (ZS)
Podgląd zamówienia sprzedaży

(SalesOrderPreviewView)

Lista pozycji Element zamówienia sprzedaży (ZS)
Dokument Zamówienie sprzedaży (ZS)
Kompletacja

(SalesOrderPreparationView)

Lista pozycji Element zamówienia sprzedaży (ZS)
Nowa oferta

(SalesQuoteView)

Lista pozycji Element oferty sprzedaży (OS)
Dokument Oferta sprzedaży (OS)
Podgląd oferty sprzedaży

(SalesQuotePreviewView)

Lista pozycji Element oferty sprzedaży (OS)
Dokument Oferta sprzedaży (OS)
Zamówienia i oferty sprzedaży

(SalesOrdersListView)

Lista pozycji Zamówienie sprzedaży (ZS), Oferta sprzedaży (OS)
Reklamacja

(ComplaintView)

Lista pozycji Element reklamacji sprzedaży (RLS)
Dokument Reklamacja sprzedaży (RLS)
Reklamacje

(ComplaintsListView)

Lista pozycji Reklamacja sprzedaży (RLS)
Dokument kasowy (KP/KW)

(CashDocumentView)

Dokument Operacje kasowo/bankowe
Wpłata/wypłata z sejfu

(VaultInflowOutflowView)

Dokument Operacje kasowo/bankowe
Dokumenty kasowe

(CashDocumentsListView)

Lista pozycji Operacje kasowo/bankowe
Podgląd dokumentu wydania MM-

(WarehouseDocumentPreviewView)

Lista pozycji Element przesunięcia międzymagazynowego (MM-)
Dokument Przesunięcie międzymagazynowe (MM-)
Nowy dokument wydania

(NewWarehouseDocumentView)

Lista pozycji Element przesunięcia międzymagazynowego (MM-)
Dokument Przesunięcie międzymagazynowe (MM-)
Dokument wydania

(WarehouseDocumentView)

Lista pozycji Element przesunięcia międzymagazynowego (MM-)
Dokument Przesunięcie międzymagazynowe (MM-)
Dokument przyjęcia PRP

(ReceivingAndDeliveryReportView)

Dokument Protokół przyjęcia (PRP)
Dokumenty magazynowe

(WarehouseDocumentsListView)

Lista pozycji Przesunięcie międzymagazynowe (MM-), Protokół przyjęcia (PRP), Zamówienie zakupu (ZZ), Przyjęcie zewnętrzne (PZ)
Przyjęcie listu przewozowego

(DeliveryNoteView)

Dokument Przyjęcie listu przewozowego
Podgląd listu przewozowego

(DeliveryNotePreviewView)

Dokument Przyjęcie listu przewozowego
Przyjęcie dostawy MM-

(WarehouseDocumentsToReceiptListView)

Lista pozycji Przesunięcie międzymagazynowe – (MM-), Protokół przyjęcia (PRP), Zamówienie zakupu (ZZ), Przyjęcie zewnętrzne (PZ)
Przyjęcie zewnętrzne

(PurchaseOrderReceptionView)

Lista pozycji Element przyjęcia zewnętrznego (PZ)
Dokument Przyjęcie zewnętrzne (PZ)
Zamówienie zakupu

(PurchaseOrderView)

Lista pozycji Element zamówienia zakupu (ZZ)
Dokument Zamówienie zakupu (ZZ)
Korekty zasobów

(StockCorrectionsListView)

Lista pozycji Przychód wewnętrzny (PW), Rozchód wewnętrzny (RW)
Przychód/Rozchód wewnętrzny

(NewInternalReceiptOrReleaseView)

Lista pozycji Element przychodu wewnętrznego (PW), Element rozchodu wewnętrznego (RW)
Dokument Przychód wewnętrzny (PW), Rozchód wewnętrzny (RW)
Podgląd Przychodu/Rozchodu wewnętrznego

(InternalReceiptOrReleasePreviewView)

Lista pozycji Element przychodu wewnętrznego (PW), Element rozchodu wewnętrznego (RW)
Dokument Przychód wewnętrzny (PW), Rozchód wewnętrzny (RW)
Zamówienie wewnętrzne

(InternalOrdersListView)

Lista pozycji Zamówienie wewnętrzne (ZWE)
Nowa paczka

(GeneratedWarehouseDocumentView)

Lista pozycji Element przesunięcia międzymagazynowego (MM-)
Dokument Przesunięcie międzymagazynowe (MM-)
Przesunięcia ręczne

(ManualMovementsListView)

Lista pozycji Przesunięcie międzymagazynowe (MM-)
Przesunięcie ręczne

(NewManualMovementWarehouseDocumentView)

Lista pozycji Element przesunięcia międzymagazynowego (MM-)
Dokument Przesunięcie międzymagazynowe (MM-)
Przesunięcia wewnętrzne

(InternalMovementsListView)

Lista pozycji Element przesunięcia międzymagazynowego (MM-)
Przesunięcie wewnętrzne

(NewInternalWarehouseDocumentView)

Lista pozycji Przesunięcie międzymagazynowe (MM-)
Inwentaryzacja

(InventoryCountView)

Dokument Inwentaryzacja
Arkusz inwentaryzacji

(InventoryCountListView)

Dokument Arkusz inwentaryzacji
Utworzone zamówienie wewnętrzne

(CreatedInternalOrdersListView)

Lista pozycji Zamówienie wewnętrzne (ZWE)
Nowe zamówienie wewnętrzne

(NewInternalOrderView)

Dokument Zamówienie wewnętrzne (ZWE)
Podgląd zamówienia wewnętrznego

(CreatedInternalOrderPreviewView)

Dokument Zamówienie wewnętrzne (ZWE)
Lista artykułów

(ProductsListView)

Lista pozycji Artykuł

 

Aby wskazaną klasę atrybutu dodać do POS, należy ją oznaczyć jako Retail POS (pogląd lub/i edycja). Opcja pogląd umożliwi działanie atrybutu w trybie tylko do odczytu w aplikacji POS. Natomiast wybranie opcji edycja będzie skutkowało możliwością zmiany wartości atrybutu, a po zapisie zmian przeniesienie tej informacji do systemu ERP. Następnie klasę atrybutu wiążemy z odpowiednim typem obiektu, który po stronie POS jest obsługiwany (parz tabelka powyżej). Po dodaniu i przesynchronizowaniu ustawień nowe atrybuty pojawią się na odpowiednim widoku w aplikacji POS i będą mogły być zarządzalne z poziomu konfiguracji interfejsu przez użytkownika POS.

Przykładowo dodajemy nową klasę atrybutu o nazwie X typu tekst, oznaczamy do edycji w sekcji Retail POS. Następnie wiążemy ją z obiektem Kontrahenta w systemie ERP. Po przesynchronizowaniu danych uruchamiamy aplikację POS i wchodzimy do konfiguracji widoku Dodaj kontrahenta. Tam zaznaczmy odpowiedni kontener (CustomerItemsContainer) pozwalający na prezentację atrybutów, a następnie z listy dostępnej po prawej stronie przeciągamy nasz nowy atrybut X na widok i upuszczamy go w odpowiednim miejscu.

Dodanie nowej klasy atrybutu do nowego widoku nieposiadających jej obsługi

Wsparcie dla atrybutów możemy również dodawać we własnych widokach tworzonych w ramach rozszerzenia. Atrybuty mogę być prezentowane w postaci dynamicznie generowanych kolumn na liście (kontrolka DataGrid) lub też w postaci niezależnych kontrolek (rodzaj kontrolki będzie zależny od typu klasy atrybutu).

Atrybuty w postaci kolumn na liście DataGrid

W celu implementacji atrybutów na listach w postaci dynamicznie generowanych kolumn, należy w pierwszej kolejności dla encji danych zaimplementować interfejs IAttributable. Dostarcza on trzy właściwości, niezbędne do poprawnej obsługi atrybutów na listach. Dwie pierwsze właściwości Id oraz ObjectType należy ustawić zgodnie z encją. Id to identyfikator encji, natomiast ObjectType to typ encji. Trzecia właściwość Attributes to słownik, który będzie trzeba wypełnić podczas asynchronicznego pobierania danych dla listy. Do wypełniania tego słownika należy skorzystać z metody FillAttributesForList znajdującej się w serwisie IAttributesService. Zanim dane ze słownika będą mogły zostać wyświetlone najpierw lista musi wygenerować dodatkowe kolumny. Aby mogła to zrobić, podczas pierwszego pobrania danych (IsInitialization=true) należy ustawić właściwość AttributeClasses znajdującą się w klasie AsyncDataGridCollection. Do ustawiana tej właściwości należy skorzystać z metody GetAttributesClasses z serwisu IAttributesService. Metoda przyjmuje dwa parametry, pierwszy pozwala na ustalenie jakie klasy atrybutów powinny być prezentowane na danej liście, drugi natomiast filtruje, które z klas dla danych grup mają być widoczne. W przypadku, gdy drugi parametr będzie null, pobierane będą wszystkie klasy, z wybranych w pierwszym parametrze grup. Implementacja powinna być zrealizowana tak, aby filtrowanie klas atrybutów było zarządzalne w trybie design mode widoku. Dlatego w DesignViewModelu danego widoku należy wywołać metodę z drugim parametrem ustawionym na null. Pozwoli to na pobranie wszystkich klas i wygenerowanie wszystkich możliwych kolumn (domyślnie ukrytych). Natomiast w ViewModelu widoku  należy do drugiego parametru przekazać właściwość VisibileAttributesClassesList, który będzie zawierała listę klas atrybutów ustawioną w trybie design przez użytkownika. Metoda FillAttributesForList wypełnienia słownik Attributes. Wymaga przekazania dwóch parametrów. Pierwszy to lista encji (encja musi implementować IAttributable), drugi to lista identyfikatorów klas atrybutów, dla który należy uzupełnić każdą z encji wartościami tych klas atrybutów. W celu optymalizacji, nie pobierania zbyt wielu informacji drugi parametr ustawiamy przekazując właściwość VisibleAttributesClassesList.

Sortowanie dla kolumn, które zostały wygenerowane dla atrybutów, jest wyłączone.

Domyślnie wygenerowane kolumny dla atrybutów są ukryte. Aby je wyświetlić należy dla trybu design mode zaimplementować pobieranie wszystkich możliwych atrybutów dla danej listy, tak aby użytkownik miał możliwość wyboru kolumny i zdefiniowania widoczność a także innych właściwości.

Atrybutu prezentowane są w postaci tekstu w przypadku, gdy lista jest w trybie tylko do odczytu. Natomiast dla trybu edycji, generowane są w postaci odpowiednich kontrolek w zależności o typu danych. Dla typu logicznego będzie to CheckBox, dla listy oraz słownika ComboBox, a dla pozostałych typów TextBox.

Kompletny przykład implementacji Widok dokumentu handlowego z obsługują atrybutów

Atrybuty jako niezależne kontrolki

Alternatywnym sposobem prezentacji atrybutów w tworzonych widokach jest implementacja ich w postaci dynamicznie generowanych kontrolek zależnych typu klasy atrybutu. Kontrolki pozwolą nam również na wprowadzanie zmian w wartościach atrybutów. Implementację możemy podzielić na trzy etapy. Pierwszy to implementacja pobierania atrybutów i prezentowania ich w postaci kontrolek widocznych w zdefiniowanym kontenerze. Drugi etap to implementacja walidacji wartości w atrybutach. Natomiast trzeci etap to zapis wprowadzonych zmian w wartościach atrybutów. Przykład implementacji Widok dokumentu handlowego z obsługują atrybutów




Weryfikacja uprawnień

Autentykacja użytkownika

Do uwierzytelniania użytkowników POS służy serwis ISecurityService. Zawiera on metody pozwalające zarówno zalogować użytkownika do systemu, wylogować go, jak również zablokować ekran oraz zweryfikować czy użytkownik posiada wymagane uprawnienia.

Metody serwisu ISecurityService:

  • SignIn(string login, SecureString password)
    Metoda pozwala zalogować użytkownika do systemu.
  • SignOut()
    Metoda wylogowująca użytkownika. W efekcie czego zostaną zamknięte wszystkie otwarte wcześniej widoki i zostaniemy przeniesieni do widoku logowania.
  • Lock()
    Metoda blokująca ekran. W wyniku jej wywołania zostanie otwarty widok logowania i nie będzie możliwe przejście do innych otwartych widoków do czasu potwierdzenia swojej tożsamości przez użytkownika poprzez wprowadzenie hasła.

Autoryzacja użytkownika

W POS każdy zalogowany użytkownik może lub nie posiadać uprawniania do zdefiniowanych odgórnie miejsc w aplikacji. Definiowanie uprawnień odbywa się w systemie ERP, poprzez odpowiednie przypisanie grupie użytkowników uprawnień do akcji i obiektów biznesowych. W celu weryfikacji czy zalogowany użytkownik posiada odpowiednie prawa należy skorzystać z serwisu IAuthorizationService i znajdującej się tam metody ValidatePermissions lub będąc w klasie viewmodelu użycie bezpośrednio wywołania metody rozszerzającej o tej samej nazwie. Wywołanie metody spowoduje sprawdzenia uprawnień, jeżeli weryfikacja nie powiedzie się, zostanie otwarty widok modalny z możliwością wskazania użytkownika, dla którego ponownie przeprowadzimy proces weryfikacji (pod warunkiem wprowadzenie jego loginu i zgodnego hasła). Taki zabieg nie spowoduje przelogowania na wskazanego użytkownika, lecz tylko przejście przez dany etap weryfikacji uprawnień.
Parametry metody:

  • accessDeniedMessage (string) – tekst jaki ma się pojawić, jeżeli użytkownik nie posiada weryfikowanych uprawnień,
  • autorization (IAuthorization) – uprawnienie jakie chcemy sprawdzić,
  • successAction (Action) – akcja jaka ma nastąpić jeżeli weryfikacja uprawnień przebiegnie pomyślnie,
  • cancelAction (Action) – opcjonalna akcja jaka ma nastąpić, gdy użytkownik anuluje możliwość podniesienia uprawnień poprzez zalogowanie się na inne konto na czas weryfikacji uprawnień,
  • login (string) (domyślnie: null) – opcjonalny login użytkownika dla jakiego będą weryfikowane uprawnienia,
  • password (SecureString) (domyślnie: null) – hasło użytkownika dla jakiego będą weryfikowane uprawnienia (wymagane jeżeli wprowadzimy login)
  • logByCard (bool) (domyślnie: false) określa czy użytkownik zalogował się poprzez kartę magnetyczną

Przykład.

Sprawdzamy czy zalogowany użytkownik posiada uprawnienie do dodawania paragonu:

this.ValidatePermissions("Brak uprawnień do tworzenia paragonu", Authorization.Check.To(PermissionName.Receipt).WithLevels(PermissionLevel.Add),
                () =>
                {
                    NotificationService.Show("Uprawnienie pomyślnie zweryfikowane", NotifyIcon.Information);
                });



Obsługa wyjątków

Każdy niezłapany wyjątek w POS zostanie zaprezentowany w postaci okna komunikatu oraz zapisany w pliku loga. Okno składa się z tytułu błędu oraz jego treści. Jeżeli wyjątek jest typu System.Exception lub dziedziczy po nim, tytułem błędu będzie typ rzucanego wyjątku, a treścią jego Message. W logu zapisana zostanie informacja o typu wyjątku, treść Message oraz stos wywołań. Nie jest to zbyt eleganckie rozwiązanie, ponieważ użytkownik nie powinien być straszony dziwnymi tytułami typu: NullReferenceException.

Rozwiązaniem jest skorzystanie z klasy Comarch.POS.Library.Erros.RetailException. Klasa ta dziedziczy po System.Exception i wprowadza dwie dodatkowe właściwości, które domyślnie są predefiniowane. Są nimi UITitle, używany do prezentacji tytułu błędu, oraz UIMessage do prezentacji treści bardziej przyjaznej użytkownikowi. Treści te są oczywiście lokalizowane w wspieranych przez POS językach. W logu natomiast niezmiennie zapisuje się typ wyjątku, jego oryginalna treść oraz stos wywołań. Aby ustawić własny tytuł i/lub treść należy stworzyć nowy typ wyjątku dziedziczący po klasie RetailException i przeciążyć odpowiednie właściwości.

W POS zdefiniowana kilkanaście typów pochodnych RetailException. Przykładową niepełną listę wraz z treściami UITitle oraz UIMessage dla języka polskiego prezentuje poniższa tabela.

Typ wyjątku

UITitle

UIMessage

RetailException Błąd systemu Wystąpił nieznany błąd
RetailSecurityException Odmowa dostępu Wystąpił nieznany błąd
RetailVoucherException Obsługa bonów Wystąpił nieznany błąd
RetailVoucherBlockedException Obsługa bonów Bon o numerze {0} jest zablokowany



Rozszerzalność synchronizacji danych z DataService

Dodawanie i aktualizacja danych

Czynność po stronie Altum

W bazie Altum istnieje procedura POS.ExportCustomObjects. Należy ją nadpisać, tak by pobierała z bazy dane, które mają trafić do danego POS-a, i zwracała je w formacie XML.

Do procedury są przekazywane parametry pozwalające ograniczyć i dostosować zestaw danych wysyłanych do konkretnego POS-a:

  • @rowVersion (bigint) – Wartość pozwalająca przeprowadzać synchronizację różnicową. Jest to wartość znajdująca się w XML wygenerowanym przez tę procedurę podczas ostatniej pomyślnej synchronizacji (w atrybucie RowVersion).
  • @companyUnitId (int) – Id centrum, w którym zdefiniowano POS-a (CompanyStructure.CompanyUnits)
  • @pointOfSaleId (int) – Id POS-a (Synchronization.PointsOfSales)
  • @languageId (int) – Id języka danych (Dictionaries.Languages)

Przy eksporcie danych z kolumn typu bit i datetime należy korzystać z funkcji POS.GetBitStringPOS.GetDatetimeString.

Czynność po stronie POS

W bazie POS-a istnieje procedura Synchronization.ImportCustomObjects. Należy ją nadpisać, tak by aktualizowała w bazie POS-a tabele na podstawie otrzymanych od Altum danych w formacie XML – tych wygenerowanych przez procedurę POS.ExportCustomObjects.

Przykład

Przykład pokazuje synchronizację różnicową danych z dwóch tabel – eksport z bazy Altum i import do bazy POS-a:

  • SecDictionaries.Dic_PaymentForms -> Configuration.CustomPaymentForms
  • dbo.Dic_Country -> Configuration
a)    Procedura eksportująca
ALTER PROCEDURE [POS].[ExportCustomObjects]
    @rowVersion bigint,
    @companyUnitId int,
    @pointOfSaleId int,
    @languageId int
AS
BEG+IN
                                    SET NOCOUNT ON;
    declare @dbts bigint = cast(@@DBTS as bigint)
        
    select
            @dbts as [@RowVersion],

        (select
            pf.Id			as [@Id],
            pf.Name		as [@Name],
            pf.CategoryId		as [@Type],
            POS.GetBitString(pf.Active)
                        as [@IsActive]
        from SecDictionaries.Dic_PaymentForms pf
        where pf.Timestamp > @rowVersion		
        for xml path('PaymentForm'), type),
        (select
                        c.Id		as [@Id],
            c.Code		as [@Code],
            c.Name		as [@Name],
            POS.GetBitString(c.Active)
                    as [@IsActive]
        from dbo.Dic_Country c
        where c.Timestamp > @rowVersion
        
        for xml path('Country'), type)
    for xml path('CustomObjects')
END
b)    Procedura importująca
ALTER PROCEDURE [Synchronization].[ImportCustomObjects]
    @XML xml
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Countries --
    select
        doc.col.value('@Id', 'int') Id,
        doc.col.value('@Code', 'nvarchar(50)') Code,
        doc.col.value('@Name', 'nvarchar(100)') Name,
        doc.col.value('@IsActive', 'bit') IsActive
    into #Countries
    from @XML.nodes('/DataFromERP/CustomObjects/Country') doc(col)

update pos
    set
        Code = erp.Code,
        Name = erp.Name,
        IsActive = erp.IsActive
    from Configuration.CustomCountries pos
        join #Countries erp on erp.Id = pos.Id

    insert into Configuration.CustomCountries
    (
        Id,
        Code,
        Name,
        IsActive
    )
    select
        Id,
        Code,
        Name,
        IsActive
    from #Countries erp
    where not exists (select 1 from Configuration.CustomCountries where Id = erp.Id)

    -- Payment forms --
    select
        doc.col.value('@Id', 'int') Id,
        doc.col.value('@Name', 'nvarchar(50)') Name,
        doc.col.value('@Type', 'tinyint') [Type],
        doc.col.value('@IsActive', 'bit') IsActive
    into #PaymentForms
    from @XML.nodes('/DataFromERP/CustomObjects/PaymentForm')  doc(col)

    update pos
    set
        Name = erp.Name,
        Type = erp.Type,
        IsActive = erp.IsActive
    from Configuration.CustomPaymentForms pos
        join #PaymentForms erp on erp.Id = pos.Id

    insert into Configuration.CustomPaymentForms
    (
        Id,
        Name,
        Type,
        IsActive
    )
    select
        Id,
        Name,
        Type,
        IsActive
    from #PaymentForms erp
    where not exists (select 1 from Configuration.CustomPaymentForms where Id = erp.Id)
    
END

Obsługa słowników uniwersalnych

Aktualizacja procedury eksportującej

W procedurze POS.ExportGenericDirectories, w klauzuli WHERE należy uwzględnić InternalName słownika, który tez ma być synchronizowany.

W procedurze POS.ExportGenericDirectoryValues, w klauzuli WHERE należy uwzględnić InternalName słownika, który też ma być synchronizowany.

Klucze obce do schematu Altum

Dla tabel synchronizowanych

Dostępność obiektów, uprawnienia itp. mogą, na poziomie wiersza w danej tabeli, wpływać na to, które dane są synchronizowane. Przykładem mogą być Kontrahenci, grupy kontrahentów, magazyny, rejestry, formy płatności itd.

Aby uniknąć wielokrotnej ewaluacji, które dane należy synchronizować w tabelach pochodnych (na podstawie FK) należy posiłkować się tabelą POS.SentObjects.

Przykład: Eksport definicji podatków powiązanych z kontrahentem

(select
        ven.Id as [@Id],
        ven.ActivityId as [@ActivityId],
        ven.VendorId as [@VendorId]
        from Implementations.VendorActivityConnectionsEcoTax ven
        inner join Implementations.ActivityEcoTax ac on ven.ActivityId = ac.Id
        inner join Implementations.SettingsEcoTax sett on ac.Id = sett.CompanyActivityId and sett.CompanyId = @companyUnitId 
        inner join POS.SentObjects so on so.ObjectId = ven.VendorId and so.SyncTypeId = 14 and so.POSId = @pointOfSaleId
        where ven.Timestamp > @rowVersion 
                        for xml path('VendorActivityConnectionsEcoTax'), type)

wartość typu synchronizowanego obiektu można znaleźć w tabeli POS.SyncTypes.

Dla tabel niesynchronizowanych

Należy samodzielnie zaimplementować proces synchronizacji, tak jakby tabela pochodziła z doróbki.

Usuwanie danych

Czynności po stronie Altum

a) Do tabeli DeletionTypes dodać wpis z Id >= 1000 oraz unikatową nazwą typu usuwanych obiektów.
b) W triggerze AFTER DELETE tabeli, z której usuwanie danych ma być synchronizowane do POS, dodawać do tabeli POS.DeletedObjects wpisy o usuniętych obiektach. Kolumny do wypełnienia:

  • DeletionTypeId – identyfikator typu zdefiniowanego w pkcie a)
  • Ident – identyfikator usuniętego obiektu. Może to być liczba (int), GUID (uniqueidentifier), nvarchar lub zbiór wartości rozdzielonych znakiem „|”, np. „3428|654”. Patrz też punkt dot. czynności po stronie POS (kolumny IdentColumnName, IdentColumnType).
  • POSId – identyfikator POS-a (Synchronization.PointsOfSales) – należy go wypełnić, jeśli obiekt powinien zostać usunięty tylko z konkretnego stanowiska POS, albo pozostawić NULL, jeśli powinien zostać usunięty na wszystkich stanowiskach.

Czynności po stronie POS

Do tabeli Synchronization.DeletionTypes dodać wiersz definiujący sposób obsługi przez mechanizm synchronizacji informacji o usunięciu obiektów przychodzący z ERP-a.

Dostępne są dwa tryby usuwania: automatyczny i niestandardowy.

  • W trybie automatycznym mechanizm synchronizacji będzie automatycznie usuwał dane z podanej tabeli, identyfikując wiersze na podstawie podanych nazw kolumn (muszą być uzupełnione wartości w kolumnach TableName, IdentColumnName, IdentColumnType, a także NULL w kolumnie CustomProcName). Stosowany do usuwania na POS-ie niemal wszystkich typów obiektów usuwalnych w ERP-ie (patrz standardowe wpisy w tabeli Synchronization.DeletionTypes).
  • Tryb niestandardowy natomiast wymaga podania procedury obsługującej usuwanie obiektów danego typu (musi być wypełniona kolumna CustomProcName). Używany jest, gdy warunek usunięcia jest bardziej skomplikowany i nie da się zastosować mechanizmu automatycznego lub gdy przy usuwaniu należy wykonać dodatkowe operacje.

Tabela Synchronization.DeletionTypes zawiera następujące kolumny:

  • DelType – nazwa taka, jak w bazie Altum w tabeli DeletionTypes.
  • Order – liczba wyznaczająca kolejność usuwania typów obiektów.
  • TableName [tylko tryb automatyczny] – nazwa tabeli, z której obiekty mają być automatycznie usuwane w ramach danego typu DelType.
  • IdentColumnName [tylko tryb automatyczny] – nazwy kolumn (rozdzielonych znakiem „|”), wg których ma się odbywać automatyczna identyfikacja usuwanych wierszy, np. „Id”, „GUID”, „PriceTypeId|CustomerGroupId”. Ich liczba i kolejność musi się zgadzać z wartością wpisywaną w bazie Altum do kolumny POS.DeletedObjects.Ident.
  • IdentColumnType [tylko tryb automatyczny] – typy kolumn zdefiniowanych jako IdentColumnName, w tej samej liczbie i kolejności, np. „int”, „uniqueidentifier”, „int|int”. W przypadku typu nvarchar(x) wpisujemy „nvarchar”.
  • CustomProcName [tylko tryb niestandardowy] – nazwa procedury odpowiadającej za usunięcie obiektów danego typu. Powinna korzystać z danych z tabeli tymczasowej #DeletedObjects. Patrz istniejące procedury Synchronization.DeleteCustomerPriceTypes, Synchronization.DeleteWarehouseDocuments.

 




Rozszerzalność synchronizacji danych z POS – opcja 1

Przykład procedury eksportującej

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Synchronization].[GetCustomData]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [Synchronization].[GetCustomData]
GO

CREATE FUNCTION [Synchronization].[GetCustomData]
(
	@syncType int,
	@documentId int
)
RETURNS XML
AS
BEGIN

	declare @data XML;
	
	set @data = (select [Implementations].[GetSpecificData](@syncType, @documentId)					
					for xml path('SpecificElements'), root('CustomData'), type)
	return @data

END
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Implementations].[GetSpecificData]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [Implementations].[GetSpecificData]
GO

CREATE FUNCTION [Implementations].[GetSpecificData]
(
	@syncType int,
	@documentId int
)
RETURNS XML
AS
BEGIN

	declare @specifics XML;

	
	set @specifics = (select 					
					el.OrdinalNumber as [@OrdinalNumber],
el.SpecificCode		 as [@SpecificCode],
					el. SpecificTypeId	 as [@SpecificTypeId]
					from ExtensionSchema.SpecificDataTable el
					inner join Documents.TradeDocuments doc on el.DocumentId = doc.Id
					where el.DocumentId = @documentId and @syncType = 45 
					for xml path('row'))
	return @specifics

END
GO

Eksport danych z POS

Istnieje możliwość aby do obiektów tworzonych na POS i synchronizowanych do systemu ERP dołączyć własne dane. W tym celu należy nadpisać na bazie POS funkcję Synchronization.GetCustomData. Funkcja zwraca XMLa, a jako argumenty przyjmuje typ synchronizowanego obiektu (int) oraz jego identyfikator (int). Funkcja uruchamiana jest osobno dla każdego obiektu jaki ma zostać przesłany do systemu ERP.

Import po stronie DataService

Import danych odbywa się w kodzie C#. Każdy przetwarzany obiekt posiada property CustomData, typu XElement. Dane należy zdeserializować i samemu przetworzyć.
Przydatne informacje można pobrać ze statycznej klasy WebServiceHelper. Dla każdego wywołania metody kontraktu DataService-u można uzyskać informację o instancji POSa:

– kod POSa

– GUID POSa

– kod profilu

– wersja

Przykład importu na DataService-ie

[DataServiceBusinessModule]
public static class Module    
{
   [MethodInitializer]
   public static void Initialize()        
   {
      var customerService = IoC.Container.Resolve<IDataCustomerExtensionPointService>();
      customerService.AfterSaveCustomerEvent += CustomerServiceEx_AfterSaveCustomerEvent;
   }
   private static void CustomerServiceEx_AfterSaveCustomerEvent(object sender, DTOResultEventArgs<Queue.DTO.CustomerDTO, string, int> e)         
   {
      Console.WriteLine("{0}: {1}", e.Argument, e.EntityRow.CustomData.Name);
      var xe = e.EntityRow.CustomData; // XElement
   }
}

 




Rozszerzalność synchronizacji danych z POS – opcja 2

Eksport na POS

Istnieje możliwość synchronizowania danych niepowiązanych z obiektami powstającymi na POS i synchronizowanymi do systemu ERP. W tym celu należy wykorzystać procedurę Synchronization.ExportCustoms. Należy pamiętać o zachowaniu struktury w ciele procedury <Customs><Custom>, natomiast struktura pod węzłem <Custom> jest w pełni dowolna.

Warto aby struktura tabel, z której wybieramy dane do przesłania, miała postać drzewiastą, a główna tabela posiadała kolumny:

  • GUID – zapewni identyfikacje obiektu miedzy POS i ERP
  • Id – do relacji w strukturze
  • Type – jeśli chcielibyśmy rozróżniać dane na DSie, tym bardziej gdy istniałyby osobne doróbki
  • WasSentToERP – aby odfiltrowywać dane które zostały już wysłane

Oznaczanie wysłanych danych

Istnieje możliwość wykorzystania standardowej metody do oznaczania wysłanych obiektów, aby oznaczać obiekty niestandardowe.
W tym celu przeciążamy metodę serwisu ISynchronizationRepository (ten obszar nie posiada punktów rozszerzeń)

public class SynchronizationRepositoryExt : SynchronizationRepository
{
   public override void MarkIfWasSentToERP(string xml)
   {
      base.MarkIfWasSentToERP(xml);

      using (var context = new POSDBContext())
      {
         context.ExecuteProcedure("Synchronization.MarkSentCustomData", xml);
      }
   }
}

Wewnątrz możemy np. wywołać procedurę, która oznaczy wysłane obiekty.

CREATE PROCEDURE [Synchronization].[MarkSentCustomData]
	@p0 xml
AS
BEGIN
	UPDATE CustomSchema.CustomRootTable
	SET WasSentToERP = 1
	Where GUID in (SELECT xmlData.Col.value('@GUID','varchar(max)') FROM @p0.nodes('/Customs/Custom') xmlData(Col))
END
GO

Przykład eksportu

CREATE PROCEDURE [Synchronization].[ExportCustoms] 
AS
BEGIN
	SET NOCOUNT ON;

	select
		pad.GUID 			as [@GUID],
		pad.Type as [@Type],
		pad.Data1 		as [@Data1],
		Synchronization.GetDatetimeString(pad.ChangeDate) 							as [@ChangeDate],
		(
			select			
td.Number 			as [@NumberString], 
td.Status	 			as [@Status], 
td.Value 				as [@Value]
			from 				CustomSchema.Table1 td 			where pad.Id = td.RootId
			for xml path('Table1'), root('Tables1'), type
		),		
		(
			select				
ti.ToPayNet 				as [@ToPayNet], 
				ti.Points				 as [@Points], 
ti.ToPay				as [@ToPay]
			from 			CustomSchema.Table2 ti 			where 								pad.Id = ti.RootId
			for xml path('Table2'), root('Tables2'), type
		)

	from CustomSchema.RootData pad
	where pad.WasSentToERP = 0

	for xml path('Custom'), root('Customs')
	
END
GO

Import po stronie DataService

Import danych odbywa się z wykorzystaniem serwisu IDataCustomService, i przeciążeniu metody SaveCustom. Metoda jako argument dostaje każdy wiersz Custom w postaci obiektu XElement.

Aby obsłużyć wiele dorobek należy wykorzystać punkty rozszerzeń dla DataService.

Snippet do importu

[DataServiceBusinessModule]
public static class Module    
{
   [MethodInitializer]
   public static void Initialize()        
   {
       var dataCustomService = IoC.Container.Resolve<IDataCustomExtensionPointService>();
       dataCustomService.OnSaveCustomEvent += DataCustomService_OnSaveCustomEvent;
   }
   private static void DataCustomService_OnSaveCustomEvent (object sender, XEEventArgs e)
   {
   //deserializacja + zapis danych
   }
}

 




Niestandardowe akcje DataService

Kontrakt DataService’u zawiera dwie metody umożliwiające wywołanie uniwersalnej akcji:

byte[] CustomGet(string operationCode, byte[] args)
void CustomExecute(string operationCode, byte[] args)

Zarówno argument jak i typ zwracany (tylko CustomGet) jest tablicą bajtów, aby można było przesłać oraz odebrać dowolna strukturę.

Wywołanie na POS

Metody do wywołania akcji uniwersalnych znajdują się w serwisie ISynchronizationService. Wystarczy we własnym module wstrzyknąć instancje w/w serwisu i wywołać żądaną operację.

Obsługa na DataService

Podpięcie obsługi akcji uniwersalnej polega na zarejestrowaniu jej obsługi w module rozszerzającym.

!!Uwaga!! klasa opatrzona atrybutem musi być statyczna

[DataServiceBusinessModule]
public static class Module    
{
   [MethodInitializer]
   public static void Initialize()        
   {
      var customOpsService = IoC.Container.Resolve<ICustomOperationsService>();
      customOpsService.RegisterCustomGet("my_op", MyCustomGet);
   }
   private static byte[] MyCustomGet(byte[] data)        
   {
     //kod   
   }
}

 




Punkty rozszerzeń (POS extension points)

Moduł Comarch.POS.ExtensionPoints umożliwia wielokrotne rozszerzenie tej samej metody viewmodelu. W ten sposób można tworzyć wiele niezależnych od siebie rozszerzeń, które modyfikują działanie tej samej funkcji natywnej.

Architektura punktów rozszerzeń

Dla rozszerzanego viewmodelu został stworzony dodatkowy viewmodel dziedziczący, a dla niego dedykowany serwis biznesowy. Przykładowo:

public class DocumentViewModelExtensionPoint : DocumentViewModel

public class DocumentViewModelExtensionPointService :

  PrintingViewModelNavigationExtensionPointService<IDocumentViewModel>,

  IDocumentViewModelExtensionPointService

  IDocumentViewModelExtensionPointInternalService

Każda rozszerzana metoda viewmodelu posiada w serwisie dwa eventy „do wpięcia”: Before oraz After. Wyjątek stanowią aktywatory, czyli metody które zwracają flagę boolean. Dla nich istnieje tylko jeden event.

Rozszerzając konkretną metodę viewmodelu uzyskujemy dostęp zarówno do parametrów danego wywołania jak również do instancji danego viewmodelu. Celem jest udostepnienie pełnego kontekstu działania.

!!Uwaga!!

Event Before<nazwa_metody> umożliwia sygnalizowanie przerwania wywołania standardowego wywołania. Należy pamiętać ze w środowisku wielodoróbkowym pod ten sam event może być zapiętych wiele rozszerzeń. Każde kolejne wywołanie będzie bazować na rezultacie poprzedniego. Nie można zakładać ze na początku wywołania wartość Cancel będzie ustawiona na false. Dodatkowo należy założyć ze kolejność wywołania rozszerzeń jest niedeterministyczna.

Przykład:

[Dependency]
public IPaymentViewModelExtensionPointService PaymentViewModelExtensionPointService
{ get; set; }

public override void Initialize()
{
   PaymentViewModelExtensionPointService.BeforeAddToPaymentFormEvent +=
_paymentViewModelExtensionPointService_BeforeAddToPaymentFormEvent;
}

private void _paymentViewModelExtensionPointService_BeforeAddToPaymentFormEvent(object sender, EntityViewModelCancelEventArgs<IPaymentViewModel, PaymentFormRow> e)
{
            if (e.Cancel) // obsluga stanu wejsciowego
                return;

            // kod
        }

Wywołanie natywnej metody ViewModelu

Może się zdarzyć sytuacja, gdy w ramach rozszerzenia chcemy uzależnić wywołanie natywnej operacji od decyzji operatora. Komunikaty na POS nie są blokujące, zatem obsługę można kontynuować tylko w handlerze zwrotnym z okna komunikatu.

private void _documentViewModelExtensionPointService_BeforeProductAddEvent(object sender, EntityViewModelCancelEventArgs<IDocumentViewModel, LotRow> e) {




   e.Cancel = true;




   MonitService.ShowQuestion("Czy dodać towar?", (s, m) => {

       if (m.MonitResult == MonitResult.Yes) {

           var serv = DocumentViewModelExtensionPointService.

GetViewModelActions(e.ViewModel);

           serv.ProductAdd(e.EntityRow);

       }

   });

}

Metoda GetViewModelActions zwraca nam instancje, która umożliwia wywoływanie natywnych metod bazowego viewModelu z pominięciem rozszerzalności.

Dostępne punkty rozszerzeń

Standardowo każde rozszerzenie viewmodelu daje możliwość modyfikacji metod nawigujących:

  • OnActivated() – BeforeOnActivatedEvent, AfterOnActivatedEvent
  • OnInitialization() – Before OnInitializationEvent, AfterOnInitializationEvent
  • OnDeactivated() – BeforeOnDeactivatedEvent, AfterOnDeactivatedEvent

Viewmodele, które obsługują proces wydruków posiadają dodatkowo rozszerzenia do metod:

  • Print() – BeforePrintEvent, AfterPrintEvent
  • CanPrint() – CanPrintEvent

Przykład implementacji

Poniżej znajduje się implementacja gotowego rozszerzenia, które pokaże dwa komunikaty w momencie zapisu dokumentu handlowego, jeden przed zapisem, drugi zaraz po.

using Microsoft.Practices.Unity;

using Comarch.POS.ExtensionPoints.Presentation.Sales.ViewModels;

using Comarch.POS.Presentation.Core.Services;

using Comarch.POS.Presentation.Core;




public class Module : ModuleBase

{

   private readonly IUnityContainer _container;

       

   [Dependency]

   public IMonitService MonitService { get; set; }




   [Dependency]

   public IDocumentViewModelExtensionPointService DocumentViewModelExtensionPointService { get; set; }

   public Module(IUnityContainer container) : base(container)

   {

      _container = container;

   }




   public override void Initialize()

   {

      DocumentViewModelExtensionPointService.BeforeSaveEvent += _documentViewModelExtensionPointService_BeforeSaveEvent;

      DocumentViewModelExtensionPointService.AfterSaveEvent += _documentViewModelExtensionPointService_AfterSaveEvent;

   }




   private void _documentViewModelExtensionPointService_AfterSaveEvent(object sender, GenerateViewModelEventArgs<IDocumentViewModel> e) {

      MonitService.ShowInformation("Extension: after save");

   }




   private void _documentViewModelExtensionPointService_BeforeSaveEvent(object sender, GenerateEntityViewModelCancelEventArgs<IDocumentViewModel> e) {

      if (e.Cancel)

         return;




      MonitService.ShowInformation("Extension: before save");

   } 

}



Moduły dodatkowe i ich rozszerzalność

Moduły dodatkowe są zintegrowane z logiką POSa za pomocą eventów. W tym celu w POS zostały stworzone specjalne interfejsy, które umożliwiają w/w komunikację. Każdy interfejs posiada swój „wewnętrzny” odpowiednik. Interfejs podstawowy definiuje metody serwisu do komunikacji z serwisami zewnętrznymi. Serwis „wewnętrzny” definiuje eventy, poprzez które komunikacja jest realizowana.

Interfejs podstawowy używany jest na viewModelach w POS.

Interfejs wewnętrzny używany jest w modułach zewnętrznych.

Obecnie istnieją następujące interfejsy:

IDeviceEventService oraz IDeviceEventInternalService (obsługa szuflady)

IDocumentEventService oraz IDocumentEventInternalService (fiskalizacja dokumentów)

IFiscalPrinterEventService oraz IFiscalPrinterEventInternalService (wydruki do płatności elektr. na druk. fisk.)

IPaymentEventService oraz IPaymentEventInternalService (płatności elektr.)

ISessionEventService oraz ISessionEventInternalService (raporty fiskalne oraz raporty płatności elektr.)




Moduł fiskalny

Istnieją dwa podstawowe interfejsy, dzięki którym można dostosować działanie modułu fiskalnego do własnych potrzeb.

IFiscalizationService – zawiera wszystkie metody uczestniczące w komunikacji z drukarka fiskalną. Dodatkowo można sterować metodami, które przygotowują dane do fiskalizacji (np., elementy  dokumentu, płatności, adres do faktury)

ItemCustomizationService – umożliwia modyfikację dowolnego pola, które jest wysyłane na drukarkę fiskalna.

Istotnym aspektem rozszerzania modułu fiskalnego jest to, ze bazowa klasa Module musi dziedziczyć po klasie Module z modułu fiskalnego, a nie z Comarch.POS.Presentation.Core. Możemy wówczas przeciążyć dodatkowe metody (RegisterServices, TriggerEventBinding, RegisterViewModels, RegisterViews, AddContainerElements)

Modyfikacja sterownika Comarch.B2.Printer2

Dodatkowo, jeśli istnieje taka potrzeba to można, dziedzicząc po klasie PrinterManager, przeciążyć każdą metodę.

Należy pamiętać, aby klasa dziedzicząca również dziedziczyła po interfejsie IPrinterService. Wynika to z faktu ze sterowniki ładowane są dynamicznie, a instancjonowanie następuje na podstawie odpowiedniego interfejsu.

public class MyPrinterManager : PrinterManager, IPrinterService { … }

Wydruk własnego dokumentu

Istnieje możliwość wydruku własnego dokumentu. Na odpowiednim viewModelu należy wykorzystać metodę PrintCustomDocument z serwisu IDocumentEventService.

Następnie można albo

– dziedzicząc po klasie FiscalizationService wywoływać na wewnętrznej instancji IPrinterService metody do drukowania linii w trybie niefiskalnym (NonFiscalOpen, NonFiscalLinePrint, NonFiscalClose)

Albo

– napisać własny driver (np. na podstawie Comarch.B2.Printer2), przeciążyć metodę PrintCustomDocument oraz, dziedzicząc po klasie FiscalizationService, przeciążyć metodę PrintCustomDocument

Dodatkowe flagi

W klasie TradeDocument zostało dodane property FiscalParams, które przechowuje dodatkowe informacje dla modułu fiskalnego.

 

 




Inne – Bony

Bony

Protokół komunikacyjny dla bonów.
Używane metody Rest, kontrakt: Comarch.B2.DataService.Contracts.dll

VoucherEntity[] GetInternalVouchers(string numer)
Metoda powinna zwracać aktualną listę bonów zgodnych ze wskazanym numerem (zwykle będzie to jeden bon).

VoucherResult UpdateVouchers(VoucherEntity[] vouchers);
Metoda powinna wykonać akcję dodania/aktywacji/deaktywacji/aktualizacji poszczególnych bonów przekazanych jako parametr.

To jaka czynność ma być zrealizowana dla konkretnych bonów będzie zależało od danych zawartych w encji VoucherEntity. Istotne dane przesyłane przez POS:

VoucherEntity

Id : int Id bonu (0 jeżeli ma być utworzony)
TypeId : VoucherKindEnum Typ bonu

(Unknown, InternalSold, InternalReleased, External, GiftCard)

SortId : int Rodzaj bonu
CurrencyId : int Waluta bonu
IsActive : bool Stan bonu

True – bon wewnętrzny (sprzedany, wydany, karta) do wykorzystania (deaktywacji) lub w przypadku karty do aktualizacji stanu Amount

Number : string Numer bonu
Amount : decimal Wartość bonu

Spodziewana akcja w zależności od konfiguracji parametrów VoucherEntity przesłanych do usługi bonów (DataService) za pomocą metody UpdateVouchers:

Id SortId IsActive Akcja
External Wykorzystanie bonu zewnętrznego

Zapis na bazę jako użyty i nieaktywny na wartość określoną w Amount z walutą CurrencyId oraz numerem Number

­0 InternalReleased Wygenerowanie bonu wewnętrznego wydanego

Zapis na bazę jako aktywny i nieużyty bon na wartość Amount o numerze Number, walucie CurrencyId

>0 InternalReleased

InternalSold

GiftCard

true Wykorzystanie bonu lub aktualizacja wartości karty podarunkowej

 

>0 InternalReleased false Aktywacja istniejącego bonu wewnętrznie wydanego

Bool IsExternalVoucherExists(string numer, int sortId)
Metoda powinna sprawdzić czy istnieje już w bazie bon zewnętrzny o wskazanym numerze i rodzaju.




Przykłady

Uzupełnieniem niniejszego dokumentu jest solucja Visual Studio z pełną listą przykładów podzielonych na oddzielnego projekty. Aby uruchomić przykłady można zbudować całą solucję, następnie skopiować pliki wynikowe do folderu instalacyjnego POS-a i zarejestrować tylko jeden moduł inicjujący POSUsageExample.dll (instrukcja rejestracji znajduje się w pliku README.txt solucji). Alternatywą jest budowanie każdego projektu z osobną (w zależności który przykład chcemy przeanalizować) i tylko tę wybraną bibliotekę zarejestrować w aplikacji POS. Projekty podzielone są na trzy kategorie: przykłady użycia kontrolek, przykłady całych widoków oraz przykłady rozszerzalności istniejących widoków.

Przykłady użycia kontrolek POS-a

Przykłady użycia kontrolki ComboBox2

Przykłady użycia kontrolki ze standardowym widokiem modalnym listy do wyboru oraz z niestandardową prezentacją. Dostępne w projekcie ComboBox2Example.

Przykład użycia kontrolki ButtonSpinner

Przykład użycia kontrolki wraz z TextBoxem do sterowania wartościami liczbowymi wprowadzonymi przez użytkownika. Dostępny w projekcie ButtonSpinnerExample.

Przykład użycia kontrolki ComboBoxButton

Dostępny w projekcie ComboBoxButtonExample.

Przykład użycia kontrolki MultiButton

Przykład użycia kontrolki wraz z pełną zarządzalnością w aplikacji. Dostępny w projekcie MultiButtonExample.

Przykład użycia kontrolki ItemsContainer

Przykłady użycia kontrolki wraz z pełną zarządzalnością w aplikacji. Pierwszy z definicją elementów w xaml-u, drugi z dynamicznie asynchroniczną budowaną zawartością w kodzie. Dostępne w projekcie ItemsContainerExample.

Przykład użycia kontrolki Grid

Przykład budowy w pełni zarządzalnego widoku w oparciu o Grid. Dostępny w projekcie GridExample.

Przykład użycia kontrolki FieldControl

Przykłady użycia kontrolki wraz z pełną zarządzalnością i obsługą walidacji. Dostępne w projekcie FieldControlExample.

Przykład użycia kontrolek TabControl i TabControlItem

Przykłady użycia kontrolki TabControl oraz TabControlItem do tworzenie zakładek na widoku. Dostępny w projekcie TabControlExample.

Przykład użycia kontrolki DatePicker2

W projekcie DatePicker2Example zamieszczono kod przykładowego użycia kontrolki wraz z walidacją.

Przykłady tworzenie widoków

Prosty moduł z nowym pustym widokiem

Przykład przedstawia w jaki sposób należy tworzyć moduły rozszerzające dla POS-a. Dostępny w projekcie EmptyViewExample. Składa się z klasy Module pozwalającej na rejestrację modułu oraz pustego widoku (SimpleView, SimpleViewModel), wraz z trybem zarządzania interfejsem (DesignSimpleViewModel). Widok został zarejestrowany w postaci kafla w menu głównym aplikacji POS.

Typowy widok listy dokumentów

Przykład przedstawia budowę typowego widoku z listą pobierającą dane asynchronicznie, wspierającą sortowanie oraz stronicowanie, wyszukiwarką oraz filtrami. Składa się z klasy Module (która odpowiada za rejestrację modułu rozszerzającego oraz widoku w postaci kafla w menu głównym aplikacji POS) oraz klas widoku i view-modelu listy – SimpleListView, SimpleListViewModel i DesignSimpleListViewModel. Przykład dostępny w projekcie DataGridCompleteExample.

Typowy widok dokumentu handlowego

Przykład przedstawia budowę typowego widoku dokumentu handlowego, zawierającego DataGrid oraz wyszukiwarkę SearchBox. Dostępny w projekcie DocumentExample.

Widok dokumentu handlowego z obsługują atrybutów

Przykład budowy widoku dokumentu handlowego wzbogacony o obsługuję atrybutów dla listy oraz w postaci dynamicznie generowanych kontrolek w kontenerze widoku. Dostępny w projekcie DocumentAttributesExample.

Przykłady rozszerzalności istniejących widoków POS-a

Dodawanie kontrolki do kontenera istniejącego widoku

Projekt ControlExtensionsExamples zawiera przykład opisany w tym dokumencie oraz drugi pokazujący dodawanie przycisków zarówno do kontenera ItemsContainer jak i na Grid do istniejącego widoku stworzonego w tym samym projekcie.

Dodawanie kolumny do DataGrida na istniejącym widoku

Pełny przykład opisanej w tym dokumencie rozszerzalności kolumn DataGrida w projekcie DataGridColumnExtensionExample.

Przykład implementacji własnego sposobu agregacji danych w DataGridzie

W projekcie DataGridAggregationExample przedstawiono przykładową implementację agregacji w postaci mediany.

Przykład rozszerzenia obszaru statusowego

Projekt StatusBarExtensionExample dodaje dwa przyciski do obszaru statusowego. Jeden będący skrótem do otwierania nowego dokumentu handlowego, drugi wykorzystujący kontrolkę ComboBoxButton.