Possible to register the same ViewModel with Different names?

Jul 16, 2013 at 12:13 AM
Edited Jul 16, 2013 at 1:44 AM
Hi,

Can you create multiple NonShared ViewModels with a different name? My problems is I have a composite view:
<UserControl x:Class="Natsar.ODS.Documents.Spoken.SpokenView"
.
..
...
<Grid x:Name="LayoutContent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <Border Padding="5,5,5,5" BorderBrush="#77000000" BorderThickness="1,1,1,1" Margin="0,0,0,5" CornerRadius="12,12,12,12" Width="Auto">
        <ContentControl x:Name="FlowDocumentDetails" Content="{Binding FlowDocumentDetails}">
            <ContentControl.ContentTemplate>
                <DataTemplate>
                    <flowdoc:FlowDocumentView/>
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>
    </Border>
</Grid>
</UserControl>
..the SpokenView gets created and added manually when the user clicks a Button on the Custom TabControl. The Button click event is added to the TabControl during initialization of the template:
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    .
    ..
    ...
    
    // set up the event handler for the 'New Tab' Button Click event
    _addNewButton = this.Template.FindName("PART_NewTabButton", this) as ButtonBase;
    if (_addNewButton != null)
        _addNewButton.Command = this.VerseTabCommand;
}
..and here is when the SpokenView is manually created. During this initialization the flowdoc:FlowDocumentView created automatically:
public void AddTabItem(SelectedReferenceState selectedReferenceState, /*SpokenTabType spokenTabType, */string parameter)
{
    .
    ..
    ...
    
        SpokenTabItem tabItem;

        // Using Items Property
        ISpokenViewModel spokenViewModel = ServiceLocator.Current.GetInstance<ISpokenViewModel>();
        SpokenView view = new SpokenView(selectedReferenceState);
        view.ViewModel = spokenViewModel;
        spokenViewModel.SelectedReferenceState = selectedReferenceState;
        tabItem = new SpokenTabItem { Header = parameter, Content = view, SpokenTabKey = parameter };

        int v = 0;
        if (selectedReferenceState.spokenTabType == SpokenTabType.Verse)
            v = this.Items.Add(tabItem);
        else
            if (selectedReferenceState.spokenTabType == SpokenTabType.Chapter)
                this.Items.Insert(0, tabItem);
            else
                if (i == -1 || i == this.Items.Count - 1 || AddNewTabToEnd)
                    v = this.Items.Add(tabItem);
                else
                {
                    v = i;
                    this.Items.Insert(++i, tabItem);
                }
    ...
    ..
    .
}
..but, I'm unable to get the FlowDocumentViewModel of the FlowDocumentView from the SpokenViewModel when its being automatically created when the SpokenView has completed its initialization.

I hope I can get you to understand this. Each TabItem needs its own NonShared objects of SpokenView and FlowDocumentViewModel, but at the same time SpokenView needs access to the FlowDocumentViewModel.

So is there a way I can create an object instance with a unique name like Unity. Is this available in MEF OR I how can accomplish this? This looked interesting C# MEF: Exporting multiple objects of one type, and Importing specific ones, but still trying to visually construct this to work for my situation.

Each TabItem has to be unique because each TabItem will have different content which could go well over 1,000 Tabs.
Jul 16, 2013 at 4:19 PM
Edited Jul 16, 2013 at 9:45 PM
I figured out a solution to get the current FlowDocumentViewModel for the corresponding SpokenViewModel for a NonShared object. After the TabItem is added to the Collection it causes a OnItemsChanged(NotifyCollectionChangedEventArgs e) event. And here is where I search for the Composite View to obtain the the Model:
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
    base.OnItemsChanged(e);

    .
    ..
    ...
    
        if (e.Action == NotifyCollectionChangedAction.Add && SelectNewTabOnCreate)
        {
            SpokenTabItem tabItem = (SpokenTabItem)this.ItemContainerGenerator.ContainerFromItem(e.NewItems[e.NewItems.Count - 1]);
            SelectedItem = tabItem;

            SpokenTabPanel itemsHost = Helper.FindVirtualizingTabPanel(this);
            if (itemsHost != null)
            {
                itemsHost.MakeVisible(tabItem, Rect.Empty);
                SelectedReferenceState selectedReferenceState = tabItem.SelectedReferenceState;
                selectedReferenceState.contentControl = LogicalTreeHelper.FindLogicalNode(tabItem, "FlowDocumentDetails") as ContentControl;
                if (selectedReferenceState.contentControl != null)
                {
                    selectedReferenceState.flowDocumentReader = FindVisualChild<FlowDocumentReader>(selectedReferenceState.contentControl);
                    selectedReferenceState.flowDocumentFilter = Parameters.FlowDocumentChangedEvent;

                    IEventAggregator eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
                    eventAggregator.GetEvent<FlowDocumentChangedEvent>().Publish(selectedReferenceState);
                }
            }

            tabItem.Focus();
        }
    ...
    ..
    .
}
The FindVisualChild code:
private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is childItem)
            return (childItem)child;
        else
        {
            childItem childOfChild = FindVisualChild<childItem>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}
..then I execute an Event for the SpokenViewModel and update the reference to the FlowDocumentViewModel:
private void OnFlowDocumentChangedEvent(SelectedReferenceState parameter)
{
    .
    ..
    ...
    
        FlowDocumentView myFlowDocumentView = TreeHelper.FindVisualChild<FlowDocumentView>(parameter.contentControl);
        this.flowDocumentViewModel = myFlowDocumentView.ViewModel;
        this.SelectedReferenceState = parameter;
        this.flowDocumentViewModel.ReadReference(selectedReferenceState);

    ...
    ..
    .
}
:) Any suggestion?
Developer
Jul 16, 2013 at 9:01 PM
Hi,

In my opinion the approach you describe in the second post is better than registering each FlowDocumentViewModel with different names. In the first approach you will still have the problem of passing the name of the corresponding FlowDocumentViewModel to obtain it from the container, plus a possible memory leak as the container will kept alive all the FlowDocumentViewModel you created in your application, due to they lifetime being managed by the container (non-shared).

Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Jul 16, 2013 at 9:37 PM
Edited Jul 16, 2013 at 9:43 PM
Thanks Damian for the reply! :)

Yes, I agree, because this was the objective and now I don't need multiple unique objects.