WPF MVVM Light maestro-detalle en TabControl, pase el elemento seleccionado usando mensajes

Soy muy nuevo en WPF MVVM (Light) y necesito ayuda.

Lo que tengo es un escenario maestro-detalle, TabControl en la vista principal, vista maestra (ProductsView) en la primera pestaña y múltiples vistas de detalles diferentes (por ejemplo, DetailsView) en las siguientes pestañas.

Dependiendo del elemento seleccionado (Producto seleccionado) en ProductsView, quiero obtener elementos en DetailsView, pero no inmediatamente, solo cuando el usuario hace clic en el elemento de pestaña que contiene esta vista de detalles.

Por lo tanto, la obtención de datos detallados de la base de datos debe diferirse de alguna manera hasta el punto en que el usuario haga clic en la pestaña de detalles adecuada (es posible que no haga clic en ella en absoluto).

Aquí está mi xaml:

<!-- Main View -->
<UserControl x:Class="MyApp.Views.MainView">
  <TabControl>
    <TabControl.Items>
      <TabItem>
        <views:ProductsView />
      </TabItem>
      <TabItem>
        <views:DetailsView />
      </TabItem>
      <!-- More TabItems with details views -->
    </TabControl.Items>
  </TabControl>
</UserControl>

<!-- Products View -->
<UserControl x:Class="MyApp.Views.ProductsView">
  <Grid DataContext="{Binding Source={StaticResource Locator}, Path=ProductsVM}">
    <DataGrid ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct}">
      <DataGridTextColumn Binding="{Binding Path=Model.ProductID}" Header="Product ID" />
    </DataGrid>
  </Grid>
</UserControl>

<!-- Details View -->
<UserControl x:Class="MyApp.Views.DetailsView">
  <Grid DataContext="{Binding Source={StaticResource Locator}, Path=DetailsVM.Details}">
    <StackPanel>
      <TextBox Text="{Binding Path=Model.Field1}" />
      <TextBox Text="{Binding Path=Model.Field2}" />
    </StackPanel>
  </Grid>
</UserControl>

Y codigo:

public class ProductsViewModel : ViewModelBase
{
    private ObservableCollection<Product> products;
    public ObservableCollection<Product> Products
    {
        get
        {
            return products;
        }
        set
        {   // RaisePropertyChanged
            Set("Products", ref products, value);
        }
    }

    private Product selectedProduct;
    public Product SelectedProduct
    {
        get
        {
            return selectedProduct;
        }
        set
        {   // RaisePropertyChanged and broadcast message of type PropertyChangedMessage<Product>
            Set("SelectedProduct", ref selectedProduct, value, true);
        }
    }

    public ProductsViewModel(IDataService dataService)
    {
        this.dataService = dataService;
        Products = dataService.GetAllProducts();
    }
}

public class DetailsViewModel : ViewModelBase
{
    private Details details;
    public Details Details
    {
        get
        {
            return details;
        }
        set
        {   // RaisePropertyChanged
            Set("Details", ref details, value);
        }
    }

    public DetailsViewModel(IDataService dataService)
    {
        this.dataService = dataService;
        Messenger.Default.Register<PropertyChangedMessage<Product>>(this, m => Details = dataService.GetDetails(m.NewValue.Model.ProductID));
    }
}

Ahora esto funciona, pero después de la selección del producto, todos los detalles se recuperan inmediatamente en todas las pestañas de detalles. Estuve pensando, tal vez cuando el usuario hace clic en una pestaña en MainView, MainViewModel debería enviar un mensaje con el índice de pestaña a ProductviewModel y luego ProductviewModel debería enviar otro mensaje pasando SelectedProduct al DetailsViewModel del tabitem solicitado actualmente según el índice de pestaña, lo que haría actualizar sus datos de detalle.

Pero, ¿cómo enviaría un mensaje solo al tabitem/DetailsView actualmente solicitado en función del índice de tabulación, no a todos?

Y también este viaje de ida y vuelta suena demasiado complicado. ¿Puedes darme algunas sugerencias? ¿O es esto totalmente incorrecto? ¿Quizás haya otra solución más simple y elegante para esto?

preguntado el 12 de junio de 12 a las 19:06

3 Respuestas

Por lo general, tendría mi ViewModel rastrear el Tabs y SelectedTab también, y simplemente cargaría la pestaña actual en el SelectedTab PropertyChanged evento

Algo como esto:

// Not expanding these to full properties with property change 
// notifications for sake of simplicity here
ObservableCollection<ViewModelBase> Tabs;
ViewModelBase SelectedTab;

void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SelectedTab")
    {
        if (SelectedTab is IDetailsTab)
            ((IDetailsTab)SelectedTab).LoadProduct(SelectedProduct);

            // Or depending on your structure:
            var productsTab = Tabs[0] as ProductsViewModel;
            ((IDetailsTab)SelectedTab).LoadProduct(productsTab.SelectedProduct);
    }
}

y su XAML se vería así:

<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}">
    <TabControl.Resources>
        <DataTemplate DataType="{x:Type local:ProductsViewModel}">
            <local:ProductsView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:DetailsViewModel}">
            <local:DetailsView />
        </DataTemplate>
    </TabControl.Resources>
</TabControl>

Respondido el 13 de junio de 12 a las 19:06

Creo que lo que buscas es el modelo clásico de pub/sub. Algo con un evento llamado RequestViewOfProductDetails que se generará cuando un usuario haga clic en un elemento de control de pestaña. Configuraría la publicación en el modelo de vista principal con algo como esto:

public MasterViewModel()
{
    Messenger.Default.Send<RequestViewOfProductDetails>(_selectedProduct); 
}

Luego, en su modelo de vista de detalles, configuraría el modelo de vista para responder a ese evento:

Messenger.Default.Register<RequestViewOfProductDetails>(this, delegate(Product prod) 
{ 
  // fetch details
});  

Y configuraría un mensaje para cada tipo de detalle, publicando ese evento de detalles específico en cada selección de un elemento de pestaña. Tedioso, lo sé, pero eso separa todas las pestañas. Ahora, si fuera yo, haría algo diferente. Haría las solicitudes en subprocesos de trabajo en segundo plano, para que pueda evitar este dolor de cabeza de apuntar a pestañas específicas. Una vez que se selecciona un producto, simplemente envíe captadores para llenar todas sus pestañas de forma asincrónica y las cosas suceden de forma transparente.

Respondido el 12 de junio de 12 a las 19:06

Cuando se seleccione su pestaña de detalles, actualice los datos en la pestaña por este evento. Pase con evento param producto seleccionado.

Respondido 28 Jul 15, 14:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.