c# - Consejos sobre navegación de vistas usando Caliburn.Micro MVVM WPF
(1)
Soy nuevo en Caliburn Micro y quiero algunos consejos sobre qué camino tomar para desarrollar la interfaz de mi aplicación y la navegación entre vistas. Mi idea es tener una ventana principal que contenga un menú de botones, cada uno relacionado con una vista específica. Cada vista se almacenará en un WPF UserControl separado. La ventana principal también contendrá un TabControl vinculado a una ObservableCollection de pestañas en el modelo de vista. Cada vez que se hace clic en un botón del menú, quiero agregar una nueva pestaña con un ContentPresenter dentro que cargará dinámicamente una vista y su correspondiente modelo de vista.
Entonces mis preguntas:
1) ¿Debo usar una colección de pantalla aquí?
2) ¿Debería el UserControl implementar la interfaz Screen?
3) ¿Cómo le digo a MainWindow ViewModel qué vista cargar en la nueva pestaña agregada manteniendo viewmodels desacoplados?
Gracias a todos por adelantado.
ACTUALIZAR
Después de mucha lectura y algo de ayuda de la comunidad, logré resolver esto. Este es el AppViewModel resultante:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
Tengo que mantener el ICommand de OpenTabCommand porque el nombre convenido de Caliburn.micro no parece funcionar dentro de DataTemplate. Espero que pueda ayudar a alguien más. Gracias a todos
He hecho algo muy similar usando Caliburn.Micro
, y lo he basado en el ejemplo SimpleMDI
incluido con los ejemplos, con algunos ajustes que se ajustan a mis necesidades.
Al igual que en el ejemplo, tuve un ShellViewModel
principal:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
con un ShellView
correspondiente que contiene un TabControl
- <TabControl x:Name="Items">
, vinculándolo a la propiedad Items
del Conductor
.
En este caso particular, también tuve un ContextMenu
en mi ShellView
, atado (utilizando las convenciones de Caliburn.Micro
), a una serie de comandos que ViewModels
una instancia y ViewModels
varios otros ViewModels
(generalmente con un UserControl
correspondiente, utilizando el método ActivateItem
en el Conductor
.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
En ese caso, no ViewModels
que ViewModels
se creara con ninguna dependencia particular ni desde ninguna otra ubicación en el programa.
En otras ocasiones, cuando necesité desencadenar ViewModel
desde otra parte de la aplicación, utilicé el Caliburn.Micro
EventAggregator
para publicar eventos personalizados (por ejemplo, OpenNewBrowser
), que pueden ser manejados por las clases que implementan la interfaz correspondiente (por ejemplo, IHandle<OpenNewBrowser>
), por lo que su ViewModel
principal podría tener un método simple de Handle
responsable de abrir la View
requerida:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
Esta sección de la documentación probablemente sea útil, especialmente la sección Simple MDI.
Código adicional que mencioné en los comentarios:
A veces utilizo un método genérico en esta línea. Asegúrese de que si tengo una instancia existente de una pantalla de un tipo en particular, cambie a ella, o cree una nueva instancia si no es así.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Usado como:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}