0
votes

I'm doing a WPF application with the M-V-VM patern (i'm using galasoft if it's relevant), but I have issues when I navigate through a tabcontrol.
I'm adding tabs on the run. All the binding seems to goes well : inside the tab or in the header of the tab. I've bind my tabcontrol to a observable list. Through an interface I'm adding several types of viewmodel to this list and the binding seems correct.
My XAML code looks like this :

<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="itemTemplate">
            <TechnicalControls:ItemTab />
        </DataTemplate>
    </Grid.Resources>

    <TabControl Grid.Row="1" x:Name="MainTab" Grid.Column="1" 
            ItemsSource="{Binding TabViewModels}"
            SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"

            ItemContainerStyleSelector="{StaticResource LastItemStyleSelector}"
            ItemTemplate="{StaticResource itemTemplate}"
            >

        <TabControl.Resources>
            <DataTemplate DataType="{x:Type VM:JobViewModel}" x:Shared="False" >
                <FunctionnalControls:Job />
            </DataTemplate>
            <DataTemplate DataType="{x:Type VM:ExcelJobViewModel}" x:Shared="False">
                <FunctionnalControls:ExcelJob />
            </DataTemplate>
            <DataTemplate DataType="{x:Type VM:MonitoringViewModel}" x:Shared="False">
                <FunctionnalControls:Monitoring />
            </DataTemplate>
            <DataTemplate DataType="{x:Type VM:ErrorViewModel}" x:Shared="False">
                <FunctionnalControls:Error />
            </DataTemplate>                

        </TabControl.Resources>
    </TabControl>
</Grid>

For example if I go from a ExcelJob to another ExcelJob usercontrol, the new usercontrol is not load properly but it changes then it works, for exemple, I can go to a ExcelJob to another ExcelJob if only I go through the monitoring.

I've already look to this this but it didn't work for me.
I've also looked at this : it says that we should not used inputs because you can focus them. I've tried to set the IsEnabled property on the users controls to false. I did it when tabs were changing. It didn't work...

The only solution that I can see is to go through another a new usercontrol with no other purpose to be used every time a tab is changed but this is ugly, and I'm pretty sure, Microsoft thought about this and came up with a better solution.

If necessary I can put the code of the view model.

EDIT : Just to clarify, when I click on other tab with the same control, instead of showing me the new usercontrol, it shows me the previous one. In order to see the new one, I have to change to another tab with another usercontrol then come back on the one I want to see.
I've look through debug and the when I click on the other tab It doesn't call the viewmodel

<UserControl x:Class="App.ExcelJob"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}">
    <Grid >
        <Label>Futur Excel Job</Label>
        <TextBox Width="200" Height="60" Text="{Binding Header}"/>
    </Grid>
</UserControl>

So Main returns the Mainviewmodel and Main.ExcelJobVM returns the good viewmodel of the usercontrol. the returned isntance is based on selected Index.
The only thing I need is to force the redrawing of the usercontrol or recall the method to update the datacontext, by loading the good viewmodel. I tried, I've failed so far. I'm not sure of what I'm doing because I want to use the event SelectionChanged of the tabcontrol but it would be in the code behind, and I don't know if it would still respect the MVVM pattern.

1
The first thing I notice is that you have both the SelectedItem and SelectedIndex properties bound. Those both actually accomplish the same thing (set the selected item), and I've seen it cause problems when you set both properties at once. Just set one or the other.Rachel
Also when you say "the new usercontrol is not load properly", what do you mean by that? If you change from one ExcelJob to another ExcelJob, WPF might not bother re-drawing the template, however it should change the DataContext behind the template, so anything bound should change.Rachel
@rachel Thanks I will change remove one of the selected property. And to explain it better, If I change from one ExcelJob to another, the new one is not loaded properly, the previous one is still displayed instead of the new one.Thibaut
That is how WPF templates work. If two items are getting drawn using the exact same template, WPF doesn't bother to generate a new template and only changes the DataContext behind it. You could try this code to overwrite a tab control so instead of unloading/reloading each tab when you change, it uses an existing ContentPresenter for the tab, but I'm not positive if that would work.Rachel
The data context is not updated since the new context is from another ExcelJob viewmodel that is never load. Thanks I will try that.Thibaut

1 Answers

4
votes

The problem is that you have the DataContext hardcoded in your UserControl, and your UserControl is a Template.

When you switch to a tab that uses the same Template, WPF doesn't bother to redraw the template and only changes the DataContext behind the template. But in your case, you have the DataContext hardcoded in the UserControl, so it's not using the existing data context from the TabItem.

The best solution would be to remove the DataContext binding from your UserControl, and let it be inherited from the TabItem when the selected item changes.

For example:

WPF says

User has selected ExcelJobA for display. Because of the DataTemplate, let me draw it using an ExcelJob UserControl

<TabItem>
    <ContentPresenter DataContext="ExcelJobA">
        <local:ExcelJob DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}" />
    </ContentPresenter>
</TabItem>

So an ExcelJob UserControl gets created, and by default the DataContext of ExcelJobA would be inherited by the UserControl.

When the user changes the selected tab to ExcelJobB, WPF goes

Hey, the user has changed to ExcelJobB. Because of the DataTemplate, let me draw it using an ExcelJob UserControl, BUT WAIT! I'm already displaying an ExcelJob UserControl, so let me just change the DataContext behind that to ExcelJobB

<TabItem>
    <ContentPresenter DataContext="ExcelJobB">
        <local:ExcelJob DataContext="{Binding Main.ExcelJobVM, Source={StaticResource Locator }}" />
    </ContentPresenter>
</TabItem>

So the actual displayed ExcelJob UserControl does not get recreated or redrawn, but only the DataContext behind it changes.

HOWEVER, because you have hard-coded the DataContext inside your UserControl, the actual data context obtained from the selected item doesn't ever get used, because a DataContext specified from inside a <Tag> always takes precedence over a DataContext that would be inherited from further up the visual tree.

You need to remove the DataContext binding from inside your UserControl, and let it get passed in normally from your TabControl's SelectedItem, and it will work fine.

<TabItem>
    <ContentPresenter DataContext="ExcelJobA">
        <local:ExcelJob /> <!-- DataContext inherited from the ContentPresenter -->
    </ContentPresenter> 
</TabItem>