简体   繁体   中英

Proper databinding in WPF using TabControl and MVVM

Just when I thought I was getting better at this, TabControl is now giving me problems. I have read relevant posts here on StackOverflow, but have been unable to get my simple demo application to work the way I want it to.

To keep things focused, I'll start with a single question about something I don't understand.

I have a TabControl whose TabItems each host the same UserControl. When I set the TabControl.ContentTemplate's DataTemplate to my UserControl, a rendering of that control appears, but it looks like it's the same control for each tab. Or perhaps it's not tied to any of the tabs at all.

MainWindow.xaml

<Window x:Class="TabControlMvvm.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:localviews="clr-namespace:TabControlMvvm.Views"
    Title="MainWindow" Height="350" Width="525">
    <TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
        <TabControl.ContentTemplate>
            <DataTemplate>
               <localviews:PersonMainPanel />
            </DataTemplate>
        </TabControl.ContentTemplate>        
    </TabControl>
</Window>

Code-behind just sets the ViewModel as its DataContext:

namespace TabControlMvvm {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new TabControlMvvm.ViewModels.MainViewModel();
        }
    }
}

The TabItem's Content should be another UserControl, PersonMainPanel.xaml:

<UserControl x:Class="TabControlMvvm.Views.PersonMainPanel"
             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" 
             xmlns:localviews="clr-namespace:TabControlMvvm.Views"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Border BorderBrush="Red" BorderThickness="2">
        <TabControl TabStripPlacement="Bottom">
            <TabItem Header="Tab 1">
                <localviews:MyTabItem />
            </TabItem>
            <TabItem Header="Tab 2">
                <TextBlock Text="This was left blank intentionally" />
            </TabItem>
            <TabItem Header="Tab 3">
                <TextBlock Text="This was also left blank intentionally" />
            </TabItem>
        </TabControl>
    </Border>
</UserControl>    

Code-behind:

namespace TabControlMvvm.Views {
    /// <summary>
    /// Interaction logic for PersonMainPanel.xaml
    /// </summary>
    public partial class PersonMainPanel : UserControl {
        public PersonMainPanel()
        {
            InitializeComponent();
        }
    }
}

And the MainViewModel:

namespace TabControlMvvm.ViewModels {
    public class MainViewModel : ViewModelBase {
        public ICollectionView Tabs { get; set; }
        public int Selected { get; set; }

        public class Person
        {
            public string Name { get; set; }
        }

        public class DummyController {
            public List<Person> Persons { get; private set; }

            public DummyController()
            {
                Persons = new List<Person> {
                    new Person { Name = "Larry" },
                    new Person { Name = "Darryl" },
                    new Person { Name = "Other brother Darryl" }
                };
            }
        }

        public DummyController Controller { get; private set; }

        public RelayCommand HelloCommand { get; set; }

        public MainViewModel()
        {
            Controller = new DummyController();

            /*
            IEnumerable<TabItem> tabs = Enumerable.Range( 1, _controller.Persons.Count())
                                                  .Select( x => new TabItem { Header = String.Format( "Person {0}", x),
                                                                              Content = new PersonMainPanel() });
             */
            IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
                                                  .Select( x => new TabItem { Header = String.Format( "Person {0}", x)});
            Tabs = CollectionViewSource.GetDefaultView( tabs.ToList());
            Tabs.MoveCurrentToFirst();

            InitializeCommands();
        }

        private void InitializeCommands()
        {
            HelloCommand = new RelayCommand( () => { MessageBox.Show( String.Format( "Hello, Person {0} named {1}!", 
                                                                      Selected, Controller.Persons[Selected].Name)); });
        }
    }
}

PersonMainPanel hosts another TabControl, where Tab 1's Content is MyTabItem.xaml:

<UserControl x:Class="TabControlMvvm.Views.MyTabItem"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Name:" />
            <TextBox Text="{Binding Name}" Width="100" />            
        </StackPanel>
        <Button Command="{Binding HelloCommand}" Content="Say Hello" />
    </StackPanel>
</UserControl>

Code-behind:

namespace TabControlMvvm.Views {
    /// <summary>
    /// Interaction logic for MyTabItem.xaml
    /// </summary>
    public partial class MyTabItem : UserControl {
        public MyTabItem()
        {
            InitializeComponent();
        }
    }
}

Which looks like this at runtime:

在此处输入图片说明

Issues I have so far:

  1. When I enter Person 1's Name and then click the Person 2 tab, Person 1's Name is still visible, hence my assumption that the controls are not databound properly. I understand that ItemsControls do not pass their DataContext down to their children, but I am not sure how to fix this without associating the View in code-behind.
  2. I would have expected to get databinding errors in the Output window because of the missing DataContext, but I don't get any errors. I assume the DataContext is null, but wouldn't this still result in a binding error?
  3. How can I use Snoop effectively to debug problems like this?

Here's the sample solution: http://www.filedropper.com/tabcontrolmvvm

Here is solution:

In MainWindow modify your TabControl template, to bind Header from your Model:

<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
    <TabControl.ContentTemplate>
        <DataTemplate>
            <localviews:PersonMainPanel />
        </DataTemplate>
    </TabControl.ContentTemplate>
    <TabControl.ItemContainerStyle>
        <Style TargetType="TabItem">
            <Setter Property="Header" Value="{Binding Header}"/>
        </Style>
    </TabControl.ItemContainerStyle>
</TabControl>

In MyTabItem.xaml, set UpdateTrigger, because default one 'OnLostFocus' can sometimes not save your data:

 <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="100" />       

In MainViewModel modify creating of your tabs, so it will have Name property too:

IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
                                        .Select( x => new TabItem { Header = String.Format("Person {0}", x), Name = Controller.Persons[x-1].Name });

Also, the most important, create own TabItem class to contain some bounded data:

public class TabItem
{
    public string Name { set; get; }
    public string Header { set; get; }    
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM