使用MVVM在WPF中使用ItemSsource进行Binding / DataContext问题

时间:2015-03-31 21:29:12

标签: wpf xaml mvvm binding datacontext

我有一个Window的ViewModel,在这个窗口中我有很多UserControl个。这些工作正常,每个的绑定和DataContext设置得恰当;除了一个......

在我的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检查绑定时

  

无法设置Expression。它被标记为“不可共享”&#39;并且已经被使用

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

非常感谢你的时间。


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

6 个答案:

答案 0 :(得分:2)

我调查了您的代码,我没有看到为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中编写了该代码。

希望这有帮助。

答案 1 :(得分:1)

  

基于声明:编辑。这是使用相同日志的答案   控制但绑定到后面的代码,我想绑定到一个单独的   档案:https://stackoverflow.com/a/16745054/626442

请参阅以下代码以获取代码隐藏并创建视图模型。

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

答案 2 :(得分:1)

MainWindowView你可能有:

public LogViewerViewModel LogViewerViewModel { get; set; }

但那是关于它的。您没有声明要将其初始化为任何内容。话虽如此,我认为你需要有类似的东西:

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

    //Doing everything else here
}

其他一切看起来都不错,所以我唯一想到的是你没有初始化你的视图模型。检查输出窗口是否有任何其他绑定错误。确保您的收藏品中也包含物品。

答案 3 :(得分:1)

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

public LogViewerViewModel LogViewerViewModel { get; set; }

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

我几乎肯定你有一个null而不是值。

如果没有检查以下内容:

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

你可以测试以下内容,而不是放在itemscontrol的ItemTemplate中的样式位置,因为我直接看到DataTemplate而不是

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

答案 4 :(得分:1)

您是否尝试明确约束:

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

答案 5 :(得分:1)

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

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

请注意,DataTemplate有点特殊:https://stackoverflow.com/a/4480488