SCSF: Použití toolbaru v MDI
Při použití toolbaru v MDI aplikaci je častým cílem, v tomto případě i mým, měnit sestavu jednotlivých položek toolbaru dle zobrazeného (aktivního) okna. Klasická konstrukce použitá v SCSF předpokládá využití v podstatě statického toolbaru, který je nahrán při načtení business modulu. Tj. v třídě ModuleController v metodě. Tento přístup nelze tedy použít v případě MDI.
Jak tedy na to?
Update
Cest je mnoho, já jsem zkoušel zatím dvě. Jedna úspěšně, druhá částečně....
První z nich je využití Service, která vytváří položky toolbaru dle zadaných kriterií s využitím Commandu pro zpracování událostí vyvolané kliknutím na položku v toolbaru. Avšak i toto funkční řešení má jisté omezení. Jelikož Command.AddInvoker nefunguje pro RootWorkItem, nelze vytvořit globální Item toolbar, která by společná pro všechny business moduly. Klasickými příklady jsou funkce jako Tisk nebo Zavřít okno. Všechny commandy se musí invokovat do specifických WorkItem např. takto:
Jak tedy funguje první řešení?
- toolbar je aktualizován pomocí volání metod v Service, v mém případě nazvaném ToolbarProviderService.
- odchycené událostí Enter a Leave definované v samotném View volají metody v Presenteru, které zajistí zobrazení toolbaru pomocí Service. Zkoušel jsem použít události GotFocus a LostFocus, ale tyto události nebyly vyvolány ve všech případech! Příklad z View.
{
InitializeComponent();
this.Enter += new EventHandler(DetailView_Enter);
this.Leave += new EventHandler(DetailView_Leave);
}
private void DetailView_Leave(object sender, EventArgs e){
_presenter.OnDetailLostFocus(sender, e);
}
private void DetailView_Enter(object sender, EventArgs e){
_presenter.OnDetailGotFocus(sender, e);
}
- pro odchycení událostí vyvolané kliknutím na položku toolbaru je nutné definovat odběr těchto událostí. Toto se provede pomocí atributu CommandHandler. V mém případě definovaném v jednotlivých presenterech. Viz příklad:
public void AddNew_ToolItemClicked(object sender, EventArgs e)
{
if (!IsViewActive())
return;
View.SetStatus(ToolItemCommands.STR_CommandAddNew);
}
- tímto definujeme, že budeme odebírat událost kliknutí na ikonu AddNew v toolbaru. Pokud použijete tuto definici na vícech místech se stejným id, tak dojde k odběru na všech definovaných místech.
- je tedy nutné zajistit buď jednoznačný identifikátor pro command, třeba kombinace jména události plus název View a nebo zajištěním, že se akce provede jen pro aktivní okno. V mém případě jsem zvolil zatím druhé řešení.
- zatím nejjednodušším řešením pro zjištění aktivního View, které mi funguje, je toto:
{
IWorkspace wks = this.WorkItem.Workspaces[WrkConstants.MdiWorkspace];
if (wks != null && wks.ActiveSmartPart == View) {
return true;
}
return false;
}
- na SCSF fóru je řešení s využitím Service, které udržuje informace o aktivním View ale to se mi zdá zbytečně složité
Druhé řešení využívá taktéž Service pro vytváření položek toolbaru, ale pro aktivaci a deaktivaci využívá UseCase WorkItem. V mém případě tedy dvě použití, jedno pro správu seznamu a druhé pro správu detailu.
- využití Service je shodné s předchozím případem
- zobrazení a skrytí položek toolbaru je však vyvoláno odběrem událostí z WorkItem a to OnActivated a OnDeactiveted
base.OnActivated();
_toolbarProviderService.DisplayToolbar("info");
}
protected override void OnDeactivated(){
base.OnDeactivated();
_toolbarProviderService.RemoveToolbar("info");
}
//pro zavreni okna, udalost OnDeactivated neni vyvolana pri zavreni, proto se pouzije toto
protected override void OnObjectRemoved(object item)
{
base.OnObjectRemoved(item);
if (item == _view)
_toolbarProviderService.RemoveToolbar("info");
}
- odběr události pomocí Commandu vyvolané kliknutím na položky toolbaru jsou shodné popsané v předchozím případě
- zde jsem narazil na problém s hierarchií jednotlivý UseCase WorkItem v kolekci WorkItems v rodičovském objektu. V tomto případě Business WorkItem. První WorkItem se vytvořila jako child Business WorkItem, ale další se vytvářely jako child v první vytvořené UseCase WorkItem. To vyvolávalo problémy při zavírání jednotlivých oken. Více zde. Zatím jsem toto řešení tedy zavrhl.
Obě řešení mají však trhlinu v použití Service. Jelikož invoke pro jednotlivé položky toolbaru musí být vyvolán jen na aktuálním WorkItem, musí se tato WorkItem specifikovat v Service. Zde jsem zatím nedošel k uspokojivému řešení, jelikož použití DI mi nefunguje i když na scsf fóru tvrdí, že by to mělo. Zatím jsem to vyřešil použitím inicializační metody, která se musí volat před každým zobrazením položek toolbaru. Na lepším řešení se pracuje.
Pokud jste se dostali až sem, tak odměna vás nemine a můžete si nahrát zdroják service :-) [attachment=173:ToolbarP...rService.zip]
- případné komentáře k Service jsou vítány, jelikož jde o dost primitivní, ale funkční, implementaci...
Update:
Tak jsem narazil na problém s ToolbarService. Jde o to,že dochází k nasobnému zobrazování položek toolbaru. Pravděpodobný problém je, že privátní proměnné, hlavně _toolbarNames, která obsahuje názvy zobrazených skupin položek toolbaru, se inicializují pro každou WorkItem znova. A co je vlastně špatně?
- předpokládal jsem, že Service používá něco na způsob Singletonu a při každém použití Service dojde k použití již existující Service, pokud je dostupná.
- řešením by mohlo být použití nějaké persistence pro uložení lokálních proměnných. Nevím jestli půjde použít State v Service, ale moc tomu nevěřím. Možná by zafungovalo použití static proměnných, uvidíme.