MoonKnight
MoonKnight

Reputation: 23831

Binding to DataGrid of TabItem not Working using MVVM

I have a small application to help myself learn WPF and MVVM etc. I have been using the example by Josh Smith found here to construct my own application. I have got the application adding TabItems, but the embedded DataGrid which is bound to an ObservableCollection<ResourceViewModel> is not showing any data, see the image below:

Example

The DataGrid is the section surrounded in blue. The UserControl also seems to be showing the in the tab itself for some reason, but that is not the problem I am asking about here. The UserControl contains a DataGrid which is bound as follows

<DataGrid ItemsSource="{Binding Resources}" 
          dataAccess:DataGridTextSearch.SearchValue="{Binding ElementName=searchBox, 
              Path=Text, UpdateSourceTrigger=PropertyChanged}" 
          AlternatingRowBackground="Gainsboro" 
          AlternationCount="2" 
          HorizontalAlignment="Stretch"
          VerticalAlignment="Stretch">
...</DataGrid>

The Resources property is defined in the ViewModels namespace as

internal class ResourceDataViewModel : WorkspaceViewModel
{
    readonly ResourceDataRepository resourceRepository;
    public ObservableCollection<ResourceViewModel> Resources { get; private set; }
    ...
}

Where the ResourceViewmodel holds the information for each row of the DataGrid. I can confirm that the Resource property is populated. When I use the same model outside of MVVM and populate Resource in the same way it works. Can someone provide me with and idea of why this could be happening?

I have attempted to set the explicit path for the binding

ItemsSource="{Binding Path=(viewModels:Resources)}" 

but this also does not work. Thanks for your time.


Edit. To address comments. I set the DataContext in the App.xaml.cs file by

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    MainWindow window = new MainWindow();

    // Create the ViewModel to which 
    // the main window binds.
    MainWindowViewModel mainWindowViewModel = new MainWindowViewModel();

    // When the ViewModel asks to be closed, 
    // close the window.
    EventHandler handler = null;
    handler = delegate
    {
        mainWindowViewModel.RequestClose -= handler;
        window.Close();
    };
    mainWindowViewModel.RequestClose += handler;

    // Allow all controls in the window to 
    // bind to the ViewModel by setting the 
    // DataContext, which propagates down 
    // the element tree.
    window.DataContext = mainWindowViewModel;
    window.Show();
}

The XAML of the MainWindow:

<Window x:Class="ResourceStudio.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModels="clr-namespace:ResourceStudio.ViewModels"
        xmlns:views="clr-namespace:ResourceStudio.Views"
        Title="MainWindow" Height="629.4" Width="814.4">
   <Window.Resources>
      <ResourceDictionary Source="MainWindowResources.xaml" />
   </Window.Resources>
   <Grid>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="284*"/>
         <ColumnDefinition Width="567*"/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
         <RowDefinition Height="48"/>
         <RowDefinition Height="*"/>
         <RowDefinition Height="24"/>
      </Grid.RowDefinitions>
      <DockPanel KeyboardNavigation.TabNavigation="None" 
                     Background="#FFBEC8D8" 
                 Grid.ColumnSpan="2" 
                 Margin="0,0,0.4,0">
         <Menu DockPanel.Dock="Top" 
                   Background="#FFF9F9F9" 
                   BorderBrush="Black" 
                   KeyboardNavigation.TabNavigation="Cycle">
            <MenuItem Header="_File">
               <MenuItem Header="Load _Resource..." 
                         Height="Auto" 
                         Command="{Binding LoadResourceCommand}"/>
               <MenuItem Header="_Add Language..." 
                         Height="Auto"/>
               <Separator/>
               <MenuItem Header="Close _Workspace" 
                         Height="Auto"
                         Command="{Binding CloseCommand}"/>
               <MenuItem Header="E_xit" 
                         Height="Auto" Command="{Binding CloseCommand}" />
            </MenuItem>
            <MenuItem Header="_Edit">
            </MenuItem>
         </Menu>
         <ToolBarTray DockPanel.Dock="Top" MaxHeight="24" Background="#FFF9F9F9">
            <ToolBar Background="#FFF9F9F9">
               <Button ToolBar.OverflowMode="Never">One</Button>
               <Button>Two</Button>
               <Button>Three</Button>
            </ToolBar>
         </ToolBarTray>
      </DockPanel>
      <Grid Grid.Row="1" Grid.ColumnSpan="2" Margin="0,0,0.4,23.6" Grid.RowSpan="2">
         <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
         </Grid.ColumnDefinitions>
         <TabControl ItemsSource="{Binding Path=Workspaces}" 
                     Grid.Column="2" 
                     HorizontalAlignment="Stretch" 
                     VerticalAlignment="Stretch" 
                     TabStripPlacement="Top" 
                     Height="Auto" 
                     Width="Auto">
         </TabControl>
      </Grid>
      <StatusBar Grid.Row="2" Grid.ColumnSpan="2" Margin="0,0.4,0.4,-0.4">
         <StatusBarItem DockPanel.Dock="Left" Background="#FF007ACC" Margin="0,2,0,0">
            <TextBlock Text="Ready" Margin="5,0,0,0"/>
         </StatusBarItem>
      </StatusBar>
   </Grid>
</Window>

Where the MainWindowResources.xaml is:

<ResourceDictionary 
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:viewModels="clr-namespace:ResourceStudio.ViewModels"
   xmlns:views="clr-namespace:ResourceStudio.Views"
   >
   <!--This template applies a ResourceControl view to an instance of the 
   ResourceDataViewModel class shown in the main window.-->
   <DataTemplate DataType="{x:Type viewModels:ResourceDataViewModel}">
      <views:ResourceControl/>
   </DataTemplate>

   <!--This template explains how to render the 'Workspace' 
   content area in the main window.-->
   <DataTemplate x:Key="WorkspacesTemplate">
      <TabControl 
      IsSynchronizedWithCurrentItem="True" 
      ItemsSource="{Binding}" 
      Margin="4"/>
   </DataTemplate>
</ResourceDictionary>

The full code for the ResourceControl.xaml is:

<UserControl x:Class="ResourceStudio.Views.ResourceControl"
             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:viewModels="clr-namespace:ResourceStudio.ViewModels" 
             xmlns:dataAccess="clr-namespace:ResourceStudio.DataAccess" 
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Name="control">
   <DockPanel DataContext="{Binding ElementName=control}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
      <TextBox Text="M"  DockPanel.Dock="Top" Name="searchBox" />
      <Grid DockPanel.Dock="Top">
         <Border BorderBrush="#FF007ACC" BorderThickness="2" HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch">
            <DataGrid ItemsSource="{Binding Path=(viewModels:Resources)}" 
                      dataAccess:DataGridTextSearch.SearchValue="{Binding ElementName=searchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}" 
                      AlternatingRowBackground="Gainsboro" AlternationCount="2" HorizontalAlignment="Stretch"
                      VerticalAlignment="Stretch">
               <DataGrid.Resources>
                  <dataAccess:SearchValueConverter x:Key="searchValueConverter"/>
                  <Style TargetType="{x:Type DataGridCell}">
                     <Setter Property="dataAccess:DataGridTextSearch.IsTextMatch">
                        <Setter.Value>
                           <MultiBinding Converter="{StaticResource searchValueConverter}">
                              <Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
                              <Binding RelativeSource="{RelativeSource Self}" Path="(dataAccess:DataGridTextSearch.SearchValue)" />
                           </MultiBinding>
                        </Setter.Value>
                     </Setter>
                     <Style.Triggers>
                        <Trigger Property="dataAccess:DataGridTextSearch.IsTextMatch" Value="True">
                           <Setter Property="Background" Value="Orange" />
                        </Trigger>
                     </Style.Triggers>
                  </Style>
               </DataGrid.Resources>
               <DataGrid.CellStyle>
                  <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
                     <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                           <Setter Property="Background" Value="#FF007ACC"/>
                           <Setter Property="Foreground" Value="White"/>
                        </Trigger>
                     </Style.Triggers>
                  </Style>
               </DataGrid.CellStyle>
            </DataGrid>
         </Border>
      </Grid>
   </DockPanel>
</UserControl>

The TextBox is bound to the DataGrid. When the user types into that TextBox the DataGrid filters and highlights the cells which contains the required text. This however, is not the problem and this code works, it is just the binding to the DataGrid I am interested in. Thanks again for tour time.

Edit2: According to @dkozl's comments I have removed the DataContext="{Binding ElementName=control}" from the DockPanel declaration, so we now have

<DockPanel HorizontalAlignment="Stretch" 
           VerticalAlignment="Stretch">
...

and in the MainWindowResource.xaml I now have

<ResourceDictionary 
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:viewModels="clr-namespace:ResourceStudio.ViewModels"
   xmlns:views="clr-namespace:ResourceStudio.Views"
   >
   <!--This template applies a ResourceControl view to an instance of the 
   ResourceDataViewModel class shown in the main window.-->
   <DataTemplate DataType="{x:Type viewModels:ResourceDataViewModel}">
      <views:ResourceControl DataContext="{Binding}"/>
   </DataTemplate>

   <!--This template explains how to render the 'Workspace' 
   content area in the main window.-->
   <DataTemplate x:Key="WorkspacesTemplate">
      <TabControl 
      IsSynchronizedWithCurrentItem="True" 
      ItemsSource="{Binding}" 
      Margin="4"/>
   </DataTemplate>
</ResourceDictionary>

This has not worked. That is my DataGrid in the ResourceControl is not being populated. Thanks again for your time it is most appreciated...

Upvotes: 1

Views: 1218

Answers (1)

dkozl
dkozl

Reputation: 33394

Your UserControl DockPanel.DataContext is bound to ResourceControl control and not to ResourceDataViewModel class. What you need to do instead is to bind DataContext of ResourceControl in your DataTemplate. To achive that first remove DataContext="{Binding ElementName=control}" from ResourceControl.DockPanel and then bind ResourceControl.DataContext to your object by <views:ResourceControl DataContext={Binding}"/>. Also you need to change DataGrid items binding from ItemsSource="{Binding Path=(viewModels:Resources)}" to ItemsSource="{Binding Path=Resources}".

Not part of the original question but same template applies to tab header and tab content because DataTemplate is type specific and in this case tab header content and tab content is the same thing. To solve the issue remove DataTemplate for viewModels:ResourceDataViewModel type and put this directly into your main TabControl:

<TabControl.ContentTemplate>
   <DataTemplate>
      <views:ResourceControl DataContext={Binding}"/>
   </DataTemplat‌​e>
</TabControl.ContentTemplate>

Upvotes: 2

Related Questions