# Action result zwracany przez akcje (worker, extender, Command) Akcje (metody worker, akcje extender, handlery Command, callbacki) zwracają **action result** — obiekt sterujący tym, co stanie się w UI po wykonaniu logiki biznesowej. Typ zwróconego obiektu decyduje o sposobie obsługi. ## Spis treści - [Najważniejsza zasada](#najważniejsza-zasada) - [Wartości specjalne](#wartości-specjalne) - `null`, `string`, `bool` - [Sterowanie aktualnym formularzem](#sterowanie-aktualnym-formularzem) - close, refresh - [Okna informacyjne i pytania do użytkownika](#okna-informacyjne-i-pytania-do-użytkownika) - MessageBox, potwierdzenia - [Otwieranie obiektów i okien](#otwieranie-obiektów-i-okien) - edycja, podgląd - [Nawigacja po programie](#nawigacja-po-programie) - listy, foldery, URL - [Pliki i strumienie](#pliki-i-strumienie) - pobieranie plików, podgląd - [Raporty](#raporty) - generowanie i parametry raportów - [Dialogi parametrów](#dialogi-parametrów) - kreatory, dialog z ContextBase - [Operacje klienta i bezpieczeństwo](#operacje-klienta-i-bezpieczeństwo) - [Tabela szybkiego wyboru](#tabela-szybkiego-wyboru) - mapa typ → efekt UI ## Najważniejsza zasada **Nie wywołuj sam UI z poziomu worker/extender.** Zamiast pokazywać MessageBox, otwierać formularz czy generować plik — **zwróć odpowiedni action result**. Framework zrobi resztę, panując nad sesją, transakcją i wątkiem UI. ```csharp // ŹLE - kod biznesowy nie powinien znać UI [Action("Sprawdź")] public void Sprawdz() { MessageBoxWindow.Show("Niepoprawne dane".Translate()); // <- nie tak } // DOBRZE - zwracamy action result [Action("Sprawdź")] public object Sprawdz() { return new MessageBoxInformation("Walidacja".Translate(), "Niepoprawne dane".Translate()); } ``` ## Wartości specjalne | Zwrócona wartość | Działanie | |---|---| | `null` | Brak akcji. Transakcja jest commitowana. | | `void` (metoda bez return) | Brak akcji. | | `Task` / `Task` | Framework czeka na zakończenie i obsługuje zwróconą wartość. | | `Exception` | Pokazuje okno błędu (`MessageBoxWindow.ShowException`). Transakcja jest commitowana (rollback w wyjątku robi się wcześniej). | ## Sterowanie aktualnym formularzem ### `FormAction` (enum) Najprostsza forma — sama wartość enum jako action result. Handler konwertuje to do `FormActionResult` z ustawioną akcją. | Wartość | Działanie | |---|---| | `None` | Brak akcji. | | `Save` | Zapis bez zamykania, bez potwierdzeń warningów. | | `SaveWithConfirmation` | Zapis bez zamykania, z potwierdzeniem warningów. | | `SaveAndClose` | Zapis i zamknięcie, bez potwierdzeń. | | `SaveAndCloseWithConfirmation` | Zapis i zamknięcie, z potwierdzeniem warningów. | | `Refresh` | Odczyt danych formularza z bazy. | | `RefreshOwner` | Odświeżenie formularza-rodzica. | | `Close` | Zamknięcie bez zapisu, bez ostrzeżeń. | ```csharp [Action("Zatwierdź")] public FormAction Zatwierdz() { Status = Status.Zatwierdzony; return FormAction.SaveAndClose; } ``` ### `FormActionResult` Rozszerzona wersja `FormAction` — pozwala dodać `EditValue` (kontynuacja po zapisie), `Context`, `CommittedHandler` (kod wywoływany po commit transakcji). ```csharp return new FormActionResult { Action = FormAction.SaveAndClose, EditValue = nowyDokument, // otwarcie kolejnego obiektu po zapisie CommittedHandler = context => null, }; ``` ## Okna informacyjne i pytania do użytkownika ### `string` Krótki komunikat — handler konwertuje do `MessageBoxInformation` z domyślnym tytułem "Informacja". ```csharp return "Operacja zakończona pomyślnie".Translate(); ``` ### `MessageBoxInformation` Pełna kontrola nad okienkiem dialogowym. Przyciski (OK / Anuluj / Tak / Nie) pojawiają się automatycznie na podstawie ustawionych handlerów (`OKHandler`, `CancelHandler`, `YesHandler`, `NoHandler`). Handler każdego przycisku jest typu `Func` — **może zwrócić kolejny action result**, który zostanie obsłużony rekurencyjnie (mechanizm `DelayedHandler`). | Property | Znaczenie | |---|---| | `Caption` | Tytuł okna (NULL = standardowy zależny od `Type`). | | `Text` / `TextMarkdown` | Treść (czysty tekst lub markdown). | | `Type` | `Information` / `Warning` / `Error` — kolorystyka. | | `OKHandler` / `CancelHandler` / `YesHandler` / `NoHandler` | Akcje po wybraniu przycisku. | | `IsSecondDefault` | `Enter` wywołuje drugą akcję (No/Cancel zamiast Yes/OK). | ```csharp return new MessageBoxInformation("Potwierdzenie".Translate(), "Czy na pewno usunąć?".Translate()) { Type = MessageBoxInformationType.Warning, YesHandler = () => { Usun(); return FormAction.RefreshOwner; }, NoHandler = () => null, }; ``` ## Otwieranie obiektów i okien ### Zwrócenie `Row` lub dowolnego `object` Domyślny handler otwiera obiekt w nowym formularzu (`ObjectWindow` w HTML). Działa to dla każdego typu nieobsługiwanego przez specyficzne handlery — w tym `GuidedRow`, kreatorów, dialogów. ```csharp [Action("Otwórz kontrahenta")] public Kontrahent OtworzKontrahenta() => Wystawca; ``` ## Nawigacja po programie ### `NavigationResult` Przejście do innego folderu danych lub bazy. Działa jak kliknięcie w drzewie folderów. | Property | Znaczenie | |---|---| | `Address` | Ścieżka folderu (np. `"Handel/Sprzedaż/Faktury sprzedaży"`) lub `//<ścieżka>` dla innej bazy. | | `Context` | Kontekst (filtry) folderu. | | `Target` | `Self` / `NewWindow` / `NewTab`. | | `KeepCurrentCredentials` | Logowanie do innej bazy aktualnymi danymi (JWT). | | `KeepSessionLiving` | Przeniesienie żywej sesji do folderu docelowego (`Self` only). | | `KeepCurrentViewInHistory` | Aktualny widok zostaje w historii nawigacji. | Najważniejsze ścieżki do folderów aplikacji znajdują się w skill `/soneta-mcp-ui/common-folders.md`. ```csharp return new NavigationResult("Handel/Sprzedaż/Faktury sprzedaży") { Target = NavigationTarget.NewTab }; ``` ### `HyperlinkResult` Otwarcie URL w przeglądarce. ```csharp return new HyperlinkResult { Address = "https://soneta.pl", Target = NavigationTarget.NewWindow, }; ``` ### `CommandMenu` Menu poleceń wyświetlone użytkownikowi (drop-down z opcjami). Po wybraniu wykonuje się akcja przypisana do polecenia. ### `LoginParameters` Wymusza przelogowanie (np. po zmianie operatora). Framework wywołuje `LoginService.Relogin`. ## Pliki i strumienie ### `NamedStream` Pojedynczy plik do pobrania przez przeglądarkę lub ramkę SonetaFrame. ```csharp return new NamedStream("raport.pdf", () => GenerujPdfStream()); ``` ### `NamedStream[]` Tablica plików → spakowane jako ZIP (`Pliki_yyyyMMddHHmmssfff.zip`) i pobrane jako jeden plik lub zapisywane pojedynczo przez ramkę SonetaFrame. ### `StorageFileEditor` Edycja pliku przechowywanego w storage bazy danych (XML, DOCX, REPX, itp.) ## Raporty ### `ReportResult` Główny action result raportu — uruchamia mechanizm wydruków. Działa w **trzech trybach**: 1. **Tryb interaktywny (menu)** — bez `TemplateFileName`, framework otwiera okno raportu i ewentualne okno parametrów (`UseReportMenu == true`). Stosować, gdy chcemy pokazać użytkownikowi listę dostępnych wzorców dla danego typu/ViewInfo. 2. **Tryb automatyczny (z kodu)** — z `TemplateFileName`, bez UI raportu. Wzorzec wskazany jawnie, wydruk od razu generowany. Można dodać `OutputHandler` do przejęcia strumienia wynikowego. 3. **Tryb serwisowy (bez rezultatu)** — przez `IReportService.GenerateReport / GenerateReportStr / PrintReport`. Stosować w testach, taskach tła i kodzie nie-UI. #### Najważniejsze property | Property | Znaczenie | |---|---| | `TemplateFileName` | Nazwa lub ścieżka wzorca (`.repx`, `.repx.cs`, `.aspx`, `.dotx`) lub XML z ustawieniami wydruku. Ustawienie wyklucza `ReportName`. | | `TemplateFileSource` | `AspxSource.Local` (plik na dysku) / `Storage` (konfiguracja w bazie — pliki w `XtraReports/...`). | | `ReportName` | Nazwa raportu z menu (tryb interaktywny). Wyklucza `TemplateFileName`. | | `DataType` | Typ danych — `typeof(Towar)` (jeden obiekt), `typeof(Towary)` (cały View), `typeof(Towar[])` (zaznaczone wiersze). Steruje też ładowaniem listy dostępnych wydruków. | | `ViewInfo` | ViewInfo źródłowego widoku (np. dla raportów listy z customowym ViewInfo). | | `ViewNames` | Nazwy widoków, w których wyszukać raport po nazwie. | | `Rows` | `IEnumerable` z wierszami źródłowymi (zamiast pobierania z `Context`). **Nie działa** w trybie menu. | | `Context` | Kontekst raportu — źródło parametrów (`Context.Set(params)`), zaznaczeń, kultury. | | `OutputFormat` | `HTML` (domyślnie) / `PDF` / `TXT`. | | `Target` | `File` / `Preview` / `Printer`. Z `OutputHandler` traktowany jak `File`. | | `PrinterName` | Drukarka dla `Target = Printer`. | | `CultureInfo` | Język wydruku (nadpisuje kulturę sesji — patrz `DxReport_Generator_Dx_Zakup_English_ReportResult`). | | `AskForParameters` | `false` = wydruk bez pytania o parametry (wymaga ustawionych parametrów w `Context`). | | `Sign` / `VisibleSignature` / `Encrypt` | Podpis certyfikatem i hasło PDF (tylko tryb interaktywny w wersji desktop). | | `OutputHandler` | `Func` — przejmuje wygenerowany strumień, jego wynik staje się rezultatem akcji. Tylko z `TemplateFileName`. | | `ParametersHandler` | `Action` wywoływany przed pytaniem o parametry — sposób na zainicjowanie obiektów parametrów (oddzielnie dla każdego typu). | | `Caption` | Tytuł okna. | #### Wzorzec: wydruk z akcji (worker / extender) ```csharp [Action("Drukuj fakturę")] public object DrukujFV() { Context.Set(new SprzedazSnippet.MyParametryWydruku(Context)); return new ReportResult { TemplateFileName = "Sprzedaz.repx", DataType = typeof(DokumentHandlowy), Context = Context, AskForParameters = false, }; } ``` #### Wzorzec: wydruk z testu / taska (bez UI) ```csharp var rs = Session.GetRequiredService(); Context.Set(new SprzedazSnippet.MyParametryWydruku(Context)); var rr = new ReportResult { TemplateFileName = "Sprzedaz.repx", DataType = typeof(DokumentHandlowy), Context = Context, AskForParameters = false, }; string html = rs.GenerateReportStr(rr); // HTML jako string // lub using var stream = rs.GenerateReport(rr); // strumień (PDF / inny) // lub rs.PrintReport(rr, archive: true, archivePath: "C:\\Archive"); ``` #### Wzorzec: wzorzec ze storage (XtraReports/Wzorce użytkownika) ```csharp var rr = new ReportResult { TemplateFileName = "XtraReports/Wzorce użytkownika/EmptyReport.repx.cs", TemplateFileSource = AspxSource.Storage, Context = Context, AskForParameters = false, }; ``` #### Wzorzec: zaznaczone wiersze + custom params ```csharp Context.Set(towary); // tablica zaznaczonych Row Context.Set(new CennikViewInfo.CennikParams(Context)); var rr = new ReportResult { DataType = typeof(Towar[]), // l.mn. = zaznaczone TemplateFileName = "...", TemplateFileSource = AspxSource.Storage, Context = Context, OutputFormat = ReportFormats.TXT, AskForParameters = false, }; ``` #### Wzorzec: jeden obiekt + powiązane konteksty ```csharp var fvNewSession = Session.Get(fv); Context.Set(fvNewSession); // single Context.Set(new[] { fvNewSession }); // jako tablica (dla DataType = Towar[]) Context.Set(new SprzedazSnippet.MyParametryWydruku(Context)); var rr = new ReportResult { TemplateFileName = "Sprzedaz.repx", DataType = typeof(DokumentHandlowy), Context = Context, AskForParameters = false, }; ``` #### Wzorzec: wymuszenie języka wydruku ```csharp var rr = new ReportResult { TemplateFileName = "Zakup.repx", DataType = typeof(DokumentHandlowy), Context = Context, OutputFormat = ReportFormats.TXT, CultureInfo = CoreTools.CultureEN, AskForParameters = false, }; ``` #### Sprawdzenie wymaganych typów parametrów Przykład uzupełnia context standardowymi wartościami parametrów potrzebnymi dla raportu. ```csharp Type[] paramTypes = rs.GetParameterTypes("Sprzedaz.repx", Context); foreach (Type t in paramTypes) { Context.Set(Activator.CreateInstance(t, Context)); } ``` #### Deprecated property - `TemplateType` → używać `TemplateFileName`. - `Format` (`ReportResultFormat`) → używać `OutputFormat` (`ReportFormats`). - `Preview` → używać `Target = ReportTargets.Preview`. - `SilentProgress` → używać `!AskForParameters`. ### `ReportOrganizerResult` Menedżer raportów (lista raportów dla tabeli/widoku) — okno zarządzania zestawem raportów dla podanego typu danych. ### `ReportEditorResult` Otwarcie edytora raportu (DOTX, DX) — w zależności od `Format` wybierany jest odpowiedni handler. ### `PdfResult` Pojedynczy PDF (do podglądu, druku lub pobrania) sterowany przez `Action`: `Preview` / `Print` / `Save` / `OpenInBrowser`. ### `PdfResult[]` Tablica PDF — handler (`PdfsResultHandler`) wysyła wszystkie na drukarkę przez `IPrintingService`. ### `DotxReportPrintResult`, `TextReportPrintResult`, `DxReportPrintResult` Specjalizowane action result wydruku / podglądu konkretnych formatów (DOTX edytor, raport tekstowy, DevExpress). ## Dialogi parametrów ### `QueryContextInformation` Otwiera okno z parametrami uzupełniającymi `Context` przed wywołaniem właściwej operacji. To samo okno pojawia się przed niektórymi czynnościami z menu lub przed raportami. Po `OK` wywoływany jest handler, który dostaje uzupełnione obiekty parametrów jako argumenty. Po `Cancel` — `CancelHandler`. #### Mechanizm 1. Akcja zwraca `QueryContextInformation` z handlerem typu `Func`. 2. Framework patrzy na typy parametrów handlera (`T1`, `T2`, ...). 3. Dla każdego typu sprawdza, czy `Context` zawiera już instancję — jeśli **tak**, pomija pytanie i od razu wywołuje handler. 4. Jeśli czegoś brakuje, otwiera okno parametrów (renderowane przez `QueryContextRenderer`). 5. Po `OK` wywołuje handler z parametrami pobranymi z `Context`. 6. Wartość zwrócona z handlera staje się **kolejnym action result** (rekurencyjnie obsługiwana). #### Tworzenie — fabryki `Create` ```csharp // Jeden typ parametrów QueryContextInformation.Create(p => DoWork(p)); // Kilka typów (do 6) QueryContextInformation.Create((p1, p2) => DoWork(p1, p2)); // Context + parametry (Context można dostać jako pierwszy parametr) QueryContextInformation.Create((cx, p) => DoWork(cx, p)); // Bez handlera — sama informacja "wypełnij te typy w kontekście" QueryContextInformation.CreateForTypes(typeof(Params1), typeof(Params2)); // Z dowolnego Delegate QueryContextInformation.CreateFromHandler(myDelegate); ``` #### Klasa parametrów Typowo dziedziczy z `ContextBase` (przyjmuje `Context` w konstruktorze, ma `OnChanged()` do odświeżania), używa atrybutów Soneta i `System.ComponentModel`: ```csharp [Caption("Parametry wydruku")] class MyParams(Context context) : ContextBase(context) { [Category("Daty")] // grupowanie wizualne [Caption("Data od")] [Priority(1)] // kolejność w grupie [Accessor(AutoChange=true)] public Date DataOd { get; set; } [Category("Daty")] [Caption("Zakres")] [Priority(3)] [Accessor(AutoChange=true)] public FromTo Okres { get; set; } [Category("Informacje")] [Caption("Opis")] [Required] [Accessor(AutoChange=true)] public string Info { get; set; } [Hidden] // ukrycie pola public int Internal { get; set; } } ``` Renderer: - grupuje pola po `[Category]` (`GroupContainer` z `CaptionHtml = nazwa kategorii`), - sortuje wewnątrz grupy po `[Priority]` (rosnąco), - pomija pola z `[Hidden]` / `[Browsable(false)]`, - `[Accessor(AutoChange=true)]` zadba o automatycznie wywołanie `Session.InvokeChanged()` na `set`. - może renderować tę samą kategorię wielokrotnie, jeśli pola są przeplatane innymi. #### Wzorzec: zapytanie o parametr w workerze ```csharp [Action("Zmień podstawę zwolnienia")] public object Zmien() { return QueryContextInformation.Create(p => { using var t = Towar.Session.Logout(true); Towar.PodstawaZwolnienia = p.Podstawa; t.Commit(); return null; }); } ``` #### Wzorzec: łańcuch dialogów (wynik handlera = kolejny `QueryContextInformation`) ```csharp [Action("Dwuetapowy dialog")] public object Wieloetapowo() { return QueryContextInformation.Create(p1 => QueryContextInformation.Create(p2 => { using var t = Session.Logout(true); // wykorzystaj p1 i p2 t.Commit(); return null; }) ); } ``` #### Wzorzec: dialog wewnątrz `FormActionResult.CommittedHandler` ```csharp return new FormActionResult { EditValue = new SecondTestObject { Context = Context }, UseDialog = true, CommittingHandler = cx => FormAction.Close, CommittedHandler = cx => QueryContextInformation.Create(p => { using var t = Towar.Session.Logout(true); Towar.PodstawaZwolnienia = p.Podstawa; t.Commit(); return Towar; // otworzy formularz dla Towar po pytaniu }) }; ``` #### Wzorzec: dialog z `MessageBoxInformation` jako wynikiem ```csharp public object Pokaz() { return QueryContextInformation.Create((MessageParams p) => new MessageBoxInformation { Caption = "Test".Translate(), Text = p.Message, Type = p.IsWarning ? MessageBoxInformationType.Warning : MessageBoxInformationType.Information }); } ``` #### Wzorzec: dialog z plikami (`NamedStream[]`) `QueryContextInformation` obsługuje też typy z plikami — w klasie parametrów `NamedStream` lub `NamedStream[]` z `GetListXxx()` zwracającym `FileDialogInfo`: ```csharp class NsArgs : ContextBase { public NsArgs(Context cx) : base(cx) {} public NamedStream[] Streams { get; set; } public FileDialogInfo GetListStreams() => new FileDialogInfo { InitialDirectory = "x:\\" } .AddTxtFilter() .AddXmlFilter(); } // użycie: return QueryContextInformation.Create((NsArgs ns) => ns.Streams[0].FileName); ``` #### Property obiektu | Property | Znaczenie | |---|---| | `Context` | Kontekst, na którym pracuje okno. Standardowo `null` → użyty zostanie kontekst wywołania. | | `Caption` | Tytuł okna. | | `Data` | Dane przekazane do konstruktora (rzadko używane bezpośrednio). | | `Types` | Tablica typów do uzupełnienia w kontekście — gdy używamy `CreateForTypes`. | | `AcceptHandler` | `Func` bez parametrów. Alias na handler ustawiony przez `Create<>`. | | `CancelHandler` | `Func` — wynik traktowany jak kolejny action result. | | `ResultHandler` | `Func` — postprocessing wartości zwróconej z handlera. | | `ObjectConstructor` | `ConstructorInfo` do tworzenia obiektu wynikowego (zaawansowane). | | `GetHandler()` | Zwraca `Delegate` przekazany do `Create<>` (dla refleksji w testach). | | `SetCaption(c)` / `SetCancelHandler(h)` | Fluent setter. | #### `TryInvoke(Context)` Pozwala uniknąć okna, gdy parametry **są już** w kontekście: ```csharp // 1) gdy w kontekście brakuje Params - zwraca self (otwarcie okna) object r = QueryContextInformation .Create(MyAction) .TryInvoke(cx); // r is QueryContextInformation == true // 2) gdy Params jest w kontekście - wywołuje handler natychmiast cx.Set(new Params(cx) { ... }); object r = QueryContextInformation .Create(MyAction) .TryInvoke(cx); // r == wynik MyAction ``` #### Walidacja typu propertysów Pola z atrybutem `[Context(nameof(T.Prop))]` muszą mieć właściwy typ wartości docelowej (typ property `T.Prop`). Niezgodność → `Context.InvalidValueException` przy wywołaniu `ContextInvoker.CreateObject<>()`. ### `StartActionInformation` Wewnętrzny action result — uruchomienie akcji w nowym kontekście (używany przez framework do delegowania wykonania workera). Rzadko zwracany ręcznie. ## Operacje klienta i bezpieczeństwo ### `ClientActionResult` Wywołanie operacji w aplikacji klienckiej **SonetaFrame** (desktop wrapper). Działa tylko gdy `BusTools.DeviceType.IsBrowserApp()`. Pozwala wykonać akcję po stronie klienta (otwarcie pliku lokalnego, integracja z systemem operacyjnym) i opcjonalnie odebrać odpowiedź przez `ResponseHandler`. ### `MfaRequest` / `MfaSourceBase` 2FA (Multi-Factor Authentication) — uruchamia okno weryfikacji tożsamości użytkownika. Handler deleguje do serwisu `IMfaHandleResolver`. Stosować dla akcji wymagających dodatkowego potwierdzenia. ### `ClipboardStream` Operacja na schowku przeglądarki (kopiowanie do / odczyt z). ## Tabela szybkiego wyboru | Co chcę zrobić | Co zwrócić | |---|---| | Pokazać komunikat | `string` lub `MessageBoxInformation` | | Pokazać błąd | `Exception` (throw) lub `MessageBoxInformation` (Type = Error) | | Zapisać/zamknąć formularz | `FormAction.Save*` / `FormAction.Close` | | Zadać pytanie tak/nie | `MessageBoxInformation` z `YesHandler`/`NoHandler` | | Otworzyć obiekt do edycji | sam obiekt (`Row` lub inny) | | Otworzyć inny folder | `NavigationResult` | | Otworzyć stronę WWW | `HyperlinkResult` | | Pobrać plik | `NamedStream` | | Pobrać archiwum plików | `NamedStream[]` | | Wydruk PDF | `PdfResult` lub `PdfResult[]` | | Raport | `ReportResult` | | Zapytać o parametry | `QueryContextInformation` | | Nic nie robić | `null` / `void` |