2017-09-14 2 views
0

Juste au moment où je pensais que j'allais mieux, TabControl me pose maintenant des problèmes. J'ai lu des articles pertinents ici sur StackOverflow, mais je n'ai pas réussi à faire fonctionner ma simple application de démonstration comme je le voulais.Liaison de données correcte dans WPF à l'aide de TabControl et de MVVM

Pour garder les choses au point, je vais commencer par une seule question à propos de quelque chose que je ne comprends pas.

J'ai un TabControl dont les TabItems hébergent chacun le même UserControl. Lorsque je définis le DataTemplate de TabControl.ContentTemplate sur mon UserControl, un rendu de ce contrôle apparaît, mais il semble que ce soit le même contrôle pour chaque onglet. Ou peut-être que ce n'est lié à aucun des onglets.

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 met juste le ViewModel comme DataContext:

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

Contenu du TabItem devrait être un autre 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(); 
     } 
    } 
} 

Et le 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 accueille un autre TabControl, où le contenu de l'onglet 1 est 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(); 
     } 
    } 
} 

qui ressemble comme ceci à l'exécution:

enter image description here

questions que j'ai jusqu'à présent:

  1. Quand je rentre Nom de la personne 1 puis cliquez sur la personne 2 onglet, Nom de la personne 1 est encore visible, d'où mon hypothèse que les contrôles ne sont pas DataBound correctement . Je comprends que ItemsControls ne transmet pas leur DataContext à leurs enfants, mais je ne suis pas sûr de savoir comment résoudre ce problème sans associer la vue dans le code-behind.
  2. Je m'attendais à obtenir des erreurs de liaison de données dans la fenêtre de sortie en raison du DataContext manquant, mais je ne reçois aucune erreur. Je suppose que DataContext est null, mais cela ne se traduirait-il pas toujours par une erreur de liaison?
  3. Comment puis-je utiliser Snoop efficacement pour déboguer des problèmes comme celui-ci?

est ici la solution échantillon: http://www.filedropper.com/tabcontrolmvvm

+0

Peut-être utile: https://stackoverflow.com/a/1870658/1136211 – Clemens

+0

Comment votre MainViewModel regarder? – sTrenat

+0

Oups, j'ai oublié l'évidence! Édité. En outre, @Clemens un emporter de votre poste lié est que la collection de sauvegarde ne devrait pas de TabItems, il devrait probablement être un ViewModel pour MyTabItem. Et le XAML qui spécifie le DataTemplate devrait être bon tel quel. Je vais essayer ça au cas où c'était mon problème tout le temps! – Dave

Répondre

0

Voici la solution:

Dans MainWindow modifier votre modèle TabControl, pour lier en-tête de votre modèle:

<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> 

En MyTabItem.XAML, mis UpdateTrigger, car par défaut « OnLostFocus » peut parfois pas enregistrer vos données:

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

En MainViewModel modifier la création de vos onglets, il aura la propriété de nom aussi:

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 }); 

En outre, le plus important, de créer votre propre classe TabItem pour contenir certaines données limitée:

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

Je vais marquer cela comme la bonne solution, car elle est assez proche. Mon implémentation possède une collection de TabItemViewModel, et mon XAML est légèrement différent. Je posterai ma réponse réelle plus tard. Mais c'est fondamentalement la même chose, et le plus important est que ItemsSource pour le TabControl ne doit pas être de TabItem, mais du ViewModel. – Dave

+0

Eh bien, j'ai écrit ceci sur le code que vous avez posté :) – sTrenat