Get a property of a tabitem's viewmodel when i

2019-08-25 05:37发布

问题:

I have a UserControl ParentView containing a DataGrid and a catel:TabControl, in which each TabItem is a tiny UserControl and whose ViewModel is a subclass of a BaseTabViewModel.

This BaseTabViewModel contains a ObservableCollection of a custom class, whose properties will generate the columns of the DataGrid.

Since I have only one DataGrid for all tabs, how can I fill it with the ObservableCollection of selected tab's viewmodel automatically?

Right now, I just unload tabs (LoadTabItems="SingleUnloadOthers") and, since every tab's viewmodel regenerate its collection when instanciated, I use InterestedIn and OnViewModelPropertyChanged to get it; yet this unloading behavior isn't completely reliable, and I often find myself with the DataGrid still filled by the previous tab instead of being cleaned when changing tab.

To note, the fact that it gets cleaned isn't a desired behavior, I just have no other choice right now. Having each tab remaining loaded is my goal.

I know inheritance is pretty frowned upon when it comes to viewmodels, but I honestly can't think of a better way to do it.


ParentView

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <catel:TabControl LoadTabItems="SingleUnloadOthers">
        <TabItem Header="FIRST">
            <local:FirstView/>
        </TabItem>
        <TabItem Header="SECOND">
            <local:SecondView/>
        </TabItem>
        <TabItem Header="THIRD">
            <local:ThirdView/>
        </TabItem>
        <TabItem Header="FOURTH">
            <local:FourthView/>
        </TabItem>
    </catel:TabControl>

    <DataGrid Grid.Row="1" IsReadOnly="True" ItemsSource="{Binding Fields}"/>
</Grid>

ParentViewModel

[InterestedIn(typeof(FirstViewModel))]
[InterestedIn(typeof(SecondViewModel))]
[InterestedIn(typeof(ThirdViewModel))]
[InterestedIn(typeof(FourthViewModel))]
public class ParentViewModel : ViewModelBase
{
    public ObservableCollection<Field> Fields
    {
        get { return GetValue<ObservableCollection<Field>>(FieldsProperty); }
        set { SetValue(FieldsProperty, value); }
    }
    public static readonly PropertyData FieldsProperty = RegisterProperty("Fields", typeof(ObservableCollection<Field>));

    protected override void OnViewModelPropertyChanged(IViewModel viewModel, string propertyName)
    {
        if (propertyName.Equals("Fields"))
        {
            BaseParserViewModel p = viewModel as BaseParserViewModel;
            Fields = p != null ?? p.Fields;
        }
        base.OnViewModelPropertyChanged(viewModel, propertyName);
    }
}

BaseTabViewModel

public abstract class BaseTabViewModel : ViewModelBase
{
    protected BaseParserViewModel()
    {
        Fields = new ObservableCollection<Field>();
    }

    public ObservableCollection<Field> Fields
    {
        get { return GetValue<ObservableCollection<Field>>(FieldsProperty); }
        set { SetValue(FieldsProperty, value); }

    public static readonly PropertyData FieldsProperty = RegisterProperty("Fields", typeof(ObservableCollection<MappedField>));

}

BaseTabViewModel's childs have nothing of interest, as they don't interact with their parent, except for some [ViewModelToModel] properties.

回答1:

If you need to manage state across several view models, I always recommend introducing a service. A view model is something that can live for a short or long amount of time, but the best communication between view models is still services that hold the state (even when the vm's are gone).

Below is my preference of communication:

  1. Services (best way, allows full control, best performance)
  2. Attribute based communication
  3. Messaging (MessageMediator)

I know a lot of people fix their architectural issues with 3, but it's a hack, not a solid solution. Just implement a service (that gets injected into all vm's) like this:

public class FieldCollectionManagementService : IFieldCollectionManagementService
{
    private ObservableCollection<Field> _selectedCollection;

    public ObservableCollection<Field> SelectedCollection
    {
        get { return _selectedCollection; }
        set
        {
            _selectedCollection = value;
            SelectedCollectionChanged.SafeInvoke(this);
        }
    }

    public event EventHandler<EventArgs> SelectedCollectionChanged;
}

Now you can easily handle all the changes without needing the attributes. Then you can even restore state when all view models are gone.