Search code examples
wpfxamlmvvmbindingdatacontext

Binding/DataContext Issue with ItemSource in WPF using MVVM


I have a ViewModel that is a Window, inside this Window there are many UserControls that I have made. These work fine and the bindings and DataContexts for each is set appropriately; all apart from one...

In my MainWindowView XAML I have

<Controls:LogViewerView HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        DataContext="{Binding LogViewerViewModel}"/>

and in my MainWindowViewModel I have

public LogViewerViewModel LogViewerViewModel { get; set; }

The LogViewerView

<UserControl x:Class="GambitFramework.Utilities.Controls.Views.LogViewerView"
             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:Caliburn="http://www.caliburnproject.org"
             xmlns:Models="clr-namespace:GambitFramework.Utilities.Models"
             mc:Ignorable="d" 
             d:DesignHeight="300" 
             d:DesignWidth="200">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../../Resources/Styles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>   
    </UserControl.Resources>
    <DockPanel>
        <ItemsControl ItemsSource="{Binding LogEntries}" 
                          Style="{StaticResource LogViewerStyle}">
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer CanContentScroll="True">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DockPanel>
</UserControl>

where LogViewerViewModel is

public class LogViewerViewModel : PropertyChangedBase
{
    private BindableCollection<LogEntry> logEntries;

    public LogViewerViewModel() { }
    public LogViewerViewModel(IEnumerable<LogEntry> logEntries)
    {
        LogEntries = new BindableCollection<LogEntry>(logEntries);
    }

    public BindableCollection<LogEntry> LogEntries
    {
        get { return logEntries; }
        set
        {
            logEntries = value;
            NotifyOfPropertyChange(() => LogEntries);
        }
    }
}

and where in Styles.xaml we have

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:Caliburn="http://www.caliburnproject.org" 
                    xmlns:Models="clr-namespace:GambitFramework.Utilities.Models">
    <Style x:Key="LogViewerStyle" TargetType="ItemsControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <ScrollViewer CanContentScroll="True">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <DataTemplate DataType="{x:Type Models:LogEntry}">
        <Grid IsSharedSizeScope="True">
            <Grid.ColumnDefinitions>
                <ColumnDefinition SharedSizeGroup="Timestamp" Width="Auto"/>
                <ColumnDefinition SharedSizeGroup="Index" Width="Auto"/>
                <ColumnDefinition SharedSizeGroup="IconSource" Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="{Binding Timestamp}" 
                       Grid.Column="0"
                       FontWeight="Bold" 
                       Margin="5,0,5,0"/>
            <TextBlock Text="{Binding Index}" 
                       Grid.Column="1"
                       FontWeight="Bold" 
                       Margin="0,0,2,0" />
            <TextBlock Text="{Binding Message}" 
                       Grid.Column="3"
                       TextWrapping="Wrap"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

Where the model for LogEntry is

public class LogEntry : PropertyChangedBase
{
    private uint index;
    private DateTime timestamp;
    private IconPresentor iconSource;
    private string message;

    public uint Index
    {
        get { return index; }
        set
        {
            index = value;
            NotifyOfPropertyChange(() => Index);
        }
    }

    public DateTime Timestamp
    {
        get { return timestamp; }
        set
        {
            timestamp = value;
            NotifyOfPropertyChange(() => Timestamp);
        }
    }

    public string Message
    {
        get { return message; }
        set
        {
            message = value;
            NotifyOfPropertyChange(() => Message);
        }
    }
}

But my items are not being displayed and when I use Snoop to check the bindings

Cannot set Expression. It is marked as 'NonShareable' and has already been used

which clearly suggests the DataContext is not set correctly. What am I doing wrong here and why is my DataContext not set for my control?

Thanks very much for your time.


Edit. Here is an answer using the same log control but binding to the code behind, I want to bind to a separate file: https://stackoverflow.com/a/16745054/626442


Solution

  • You are binding the LogViewerViewModel to the DataContext of the MainWindowView instead of the DataContext of the LogViewerView

    If you want to derive from parent's DataContext, have a look at similar questions like: How to access parent's DataContext from a UserControl

    Notice that the DataTemplate is a bit special: https://stackoverflow.com/a/4480488