关于您的最后一个问题:很遗憾,您不能轻松在 WPF 中实例化字典。我相信this answer 很好地解释了这部分。 WPF 4.5 Unleashed 这本书很好地总结了链接答案的内容:
此限制的常见解决方法(无法实例化
WPF 版本的 XAML 中的字典)是派生一个非泛型的
简单地从通用类中提取类,以便可以从 XAML 中引用它...
但即便如此,在我看来,在 xaml 中实例化该字典仍然是一个痛苦的过程。此外,Blend 不知道如何创建该类型的样本数据。
关于如何获得设计时支持的隐含问题: 在 WPF 中有几种方法可以实现设计时数据,但我目前对于复杂场景的首选方法是创建一个自定义的 DataSourceProvider。在应得的地方给予赞扬:我从this article 得到了这个想法(比这个问题还要老)。
DataSourceProvider 解决方案
创建一个实现DataSourceProvider 并返回数据上下文样本的类。将实例化的 MainWindowViewModel 传递给 OnQueryFinished 方法是使魔术发生的原因(我建议阅读它以了解它的工作原理)。
internal class SampleMainWindowViewModelDataProvider : DataSourceProvider
{
private MainWindowViewModel GenerateSampleData()
{
var myViewModel1 = new MyViewModel { EventName = "SampleName1" };
var myViewModel2 = new MyViewModel { EventName = "SampleName2" };
var myViewModelCollection1 = new ObservableCollection<MyViewModel> { myViewModel1, myViewModel2 };
var timeToMyViewModelDictionary = new Dictionary<DateTime, ObservableCollection<MyViewModel>>
{
{ DateTime.Now, myViewModelCollection1 }
};
var viewModel = new MainWindowViewModel()
{
TimesAndEvents = timeToMyViewModelDictionary
};
return viewModel;
}
protected sealed override void BeginQuery()
{
OnQueryFinished(GenerateSampleData());
}
}
您现在要做的就是将您的数据提供者作为示例数据上下文添加到您的视图中:
<Window x:Class="SampleDataInBlend.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SampleDataInBlend"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<d:Window.DataContext>
<local:SampleMainWindowViewModelDataProvider/>
</d:Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding TimesAndEvents}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}"/>
<ListBox ItemsSource="{Binding Value}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<TextBlock Text="{Binding EventName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
注意:<d:Window.DataContext> 中的“d”很重要,因为它告诉 Blend 和编译器该特定元素用于设计时,在编译文件时应该忽略它。
完成之后,我的设计视图现在如下所示:
设置问题
我从 5 个类开始(其中 2 个是从 WPF 项目模板生成的,我建议使用它):
- MyViewModel.cs
- MainWindowViewModel.cs
- MainWindow.xaml
- App.xaml
MyViewModel.cs
public class MyViewModel
{
public string EventName { get; set; }
}
MainWindowViewModel.cs
public class MainWindowViewModel
{
public IDictionary<DateTime, ObservableCollection<MyViewModel>> TimesAndEvents { get; set; } = new Dictionary<DateTime, ObservableCollection<MyViewModel>>();
public void Initialize()
{
//Does some service call to set the TimesAndEvents property
}
}
MainWindow.cs
我采用了生成的 MainWindow 类并对其进行了更改。基本上,现在它要求一个 MainWindowViewModel 并将其设置为它的 DataContext。
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
MainWindow.xaml
请注意解决方案中缺少设计数据上下文。
<Window x:Class="SampleDataInBlend.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SampleDataInBlend"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<Grid>
<ListBox ItemsSource="{Binding TimesAndEvents}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}"/>
<ListBox ItemsSource="{Binding Value}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<TextBlock Text="{Binding EventName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
App.cs
首先,从 xaml 端删除 StartupUri="MainWindow.xaml",因为我们将从后面的代码中启动 MainWindow。
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var viewModel = new MainWindowViewModel();
// MainWindowViewModel needs to have its dictionary filled before its
// bound to as the IDictionary implementation we are using does not do
// change notification. That is why were are calling Initialize before
// passing in the ViewModel.
viewModel.Initialize();
var view = new MainWindow(viewModel);
view.Show();
}
}
构建并运行
现在,如果一切都正确完成并且您充实了 MainWindowViewModel 的 Initialize 方法(我将在底部包含我的实现),当您构建和运行您的WPF 应用程序:
又是什么问题?
问题是设计视图中没有显示任何内容。
我的 Initialize() 方法
public void Initialize()
{
TimesAndEvents = PretendImAServiceThatGetsDataForMainWindowViewModel();
}
private IDictionary<DateTime, ObservableCollection<MyViewModel>> PretendImAServiceThatGetsDataForMainWindowViewModel()
{
var myViewModel1 = new MyViewModel { EventName = "I'm real" };
var myViewModel2 = new MyViewModel { EventName = "I'm real" };
var myViewModelCollection1 = new ObservableCollection<MyViewModel> { myViewModel1, myViewModel2 };
var timeToMyViewModelDictionary = new Dictionary<DateTime, ObservableCollection<MyViewModel>>
{
{ DateTime.Now, myViewModelCollection1 }
};
return timeToMyViewModelDictionary;
}