Binding/DataContext 使用 MVVM 的 WPF 中的 ItemSource 问题

Binding/DataContext Issue with ItemSource in WPF using MVVM

我有一个 Window 的 ViewModel,在这个 Window 里面有很多我制作的 UserControl。这些工作正常,每个绑定和 DataContexts 都设置得当;除了一个...

在我的 MainWindowView XAML 我有

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

在我的 MainWindowViewModel 中我有

public LogViewerViewModel LogViewerViewModel { get; set; }

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>

其中 LogViewerViewModel

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

在 Styles.xaml 中我们有

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

LogEntry 的模型是

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

但是当我使用 Snoop 检查绑定时我的项目没有显示

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

这清楚地表明 DataContext 设置不正确。我在这里做错了什么,为什么我的 DataContext 没有设置为我的控件?

非常感谢您的宝贵时间。


编辑。这是一个使用相同日志控件但绑定到后面代码的答案,我想绑定到一个单独的文件:

Based on the statement: Edit. Here is an answer using the same log control but binding to the code behind, I want to bind to a separate file:

参考下面的代码来了解代码隐藏并创建视图模型。

 public partial class MainWindow : Window
{       
    public MainWindow()
    {
        InitializeComponent();
        MainViewModel vm = new MainViewModel();
        this.DataContext = vm.LogEntries;           
    }        
}


class MainViewModel
{
    private string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
    private List<string> words;
    private int maxword;
    private int index;

    public ObservableCollection<LogEntry> LogEntries { get; set; }

    public MainViewModel()
    {
        random = new Random();
        words = TestData.Split(' ').ToList();
        maxword = words.Count - 1;

         LogEntries = new ObservableCollection<LogEntry>();
        Enumerable.Range(0, 200000)
                  .ToList()
                  .ForEach(x => LogEntries.Add(GetRandomEntry()));

        Timer = new Timer(x => AddRandomEntry(), null, 1000, 10);
    }

    private System.Threading.Timer Timer;
    private System.Random random;
    private void AddRandomEntry()
    {
        System.Windows.Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() =>
        {
           LogEntries.Add(GetRandomEntry());
        }));
    }

    private LogEntry GetRandomEntry()
    {
        if (random.Next(1, 10) > 1)
        {
            return new LogEntry()
            {
                Index = index++,
                DateTime = DateTime.Now,
                Message = string.Join(" ", Enumerable.Range(5, random.Next(10, 50))
                                                     .Select(x => words[random.Next(0, maxword)])),
            };
        }

        return new CollapsibleLogEntry()
        {
            Index = index++,
            DateTime = DateTime.Now,
            Message = string.Join(" ", Enumerable.Range(5, random.Next(10, 50))
                                         .Select(x => words[random.Next(0, maxword)])),
            Contents = Enumerable.Range(5, random.Next(5, 10))
                                 .Select(i => GetRandomEntry())
                                 .ToList()
        };

    }
}

public class LogEntry : PropertyChangedBase
{
    public DateTime DateTime { get; set; }

    public int Index { get; set; }

    public string Message { get; set; }
}

public class CollapsibleLogEntry : LogEntry
{
    public List<LogEntry> Contents { get; set; }
}

public class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        Application.Current.Dispatcher.BeginInvoke((Action)(() =>
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }));
    }
}

MainWindowView你可能有:

public LogViewerViewModel LogViewerViewModel { get; set; }

但仅此而已。你没有声明你正在将它初始化为任何东西。话虽这么说,我认为你需要有类似的东西:

public MainWindowView()
{
    LogViewerViewModel = new LogViewerViewModel();

    //Doing everything else here
}

其他一切看起来都不错,所以我唯一的想法是您没有初始化您的视图模型。检查您的输出 window 是否有任何其他绑定错误以及其他错误。确保您的 collection 中也有物品。

在我看来,有几件事可以提供帮助,首先

public LogViewerViewModel LogViewerViewModel { get; set; }

我没看到你在哪里实例化了它,但如果它定义得太晚,它就不会被视图读取,所以你也可以在视图模型中实现 PropertyChangedBase (INotifiedPropertyChanged)。

我几乎可以确定您使用的是空值而不是值。

如果不是,请检查以下内容:

另一件事是确保海关控件的所有属性都是定义明确的依赖属性(控件的名称)。

你可以测试下面的,而不是放在itemscontrol的ItemTemplate里面的style地方,因为我直接看到DataTemplate而不是

<Setter Property="ItemTemplate"><Setter.Value>

您是否尝试过明确绑定:

<Controls:LogViewerView HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        DataContext="{Binding MainWindowViewModel.LogViewerViewModel, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:MainWindowView}}}"/>

主视图需要一个命名空间:

xmlns:Views="clr-namespace:GambitFramework.Utilities.Controls.Views"

在没有看到更多 MainWindowView 的情况下,我猜你在劫持你的意图之间有一个 DataContext。

我调查了您的代码,但没有看到为 MainWindowView 设置 DataContext。这样做有很多选择。例如2种方式:

首先 - 在您的 MainWindowView.xaml.cs 集中创建并设置您的视图模型:

public MainWindow()
{
    InitializeComponent();
    DataContext = new MainWindowViewModel();
}

第二个 - 在 MainWindowView.xaml:

中创建并设置视频模型
<Window x:Class="Test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:test="clr-namespace:Test"
    Title="MainWindow"
    Width="525"
    Height="350">
<Window.DataContext>
    <test:MainWindowViewModel/>
</Window.DataContext>

当您执行上述其中一项操作时,它应该会起作用。

我还注意到,您注意到 LogViewerView 中的冗余代码,它可能只是:

<DockPanel>
    <ItemsControl ItemsSource="{Binding LogEntries}" Style="{StaticResource LogViewerStyle}" />
</DockPanel>

因为您已经在 ResourceDictionary 中的 LogViewerStyle 中编写了该代码。

希望对您有所帮助。

您正在将 LogViewerViewModel 绑定到 MainWindowView 的 DataContext 而不是 LogViewerView 的 DataContext

如果您想从父级的 DataContext 派生,请查看类似的问题,例如:How to access parent's DataContext from a UserControl

注意 DataTemplate 有点特殊: