我的其中一个视图模型的类型是:
public Dictionary<DateTime, ObservableCollection<MyViewModel>> TimesAndEvents
但是当我尝试在Expression Blend中创建一些样本数据时,它简单地不会为此属性创建XAML。
你能否在XAML中创建这样的数据类型?缺乏设计时支持正在损害我的生产力。
public Dictionary<DateTime, ObservableCollection<MyViewModel>> TimesAndEvents
但是当我尝试在Expression Blend中创建一些样本数据时,它简单地不会为此属性创建XAML。
你能否在XAML中创建这样的数据类型?缺乏设计时支持正在损害我的生产力。
关于如何获得设计时支持的隐含问题:在WPF中有几种实现设计时数据的方法,但对于复杂情况,我目前首选的方法是创建自定义的DataSourceProvider。值得一提的是:我从this article中得到了这个想法(该文章甚至比这个问题还要早)。
创建一个实现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项目模板生成的,我建议在此使用):
public class MyViewModel
{
public string EventName { get; set; }
}
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类并进行了修改。基本上,现在它会要求一个MainWindowViewModel,并将其设置为其DataContext。
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
}
<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>
首先,从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应用程序时,您应该看到像下面这样的屏幕:
问题在于设计视图中没有显示任何内容。
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;
}
自从Xaml 2009支持泛型类型以来,就可以编写一个松散的Xaml(无法在WPF项目中编译)来表示一个字典。
Data.xaml
<gnrc:Dictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:gnrc="clr-namespace:System.Collections.Generic;assembly=mscorlib"
xmlns:om="clr-namespace:System.Collections.ObjectModel;assembly=System"
x:TypeArguments="sys:DateTime,om:ObservableCollection(x:String)">
<om:ObservableCollection x:TypeArguments="x:String">
<x:Key>
<sys:DateTime>2017/12/31</sys:DateTime>
</x:Key>
<x:String>The last day of the year.</x:String>
<x:String>Party with friends.</x:String>
</om:ObservableCollection>
<om:ObservableCollection x:TypeArguments="x:String">
<x:Key>
<sys:DateTime>2018/1/1</sys:DateTime>
</x:Key>
<x:String>Happy new year.</x:String>
<x:String>Too much booze.</x:String>
</om:ObservableCollection>
<om:ObservableCollection x:TypeArguments="x:String">
<x:Key>
<sys:DateTime>2018/1/10</sys:DateTime>
</x:Key>
<x:String>Just another year.</x:String>
<x:String>Not much difference.</x:String>
</om:ObservableCollection>
</gnrc:Dictionary>
但它不受 Blend 或 Visual Studio 等设计工具的支持。如果你把它放到与设计器关联的 xaml 中,你会得到数十个错误。为了解决这个问题,我们需要使用 XamlReader.Load 方法通过标记扩展从 Data.xaml 提供值。
InstanceFromLooseXamlExtension.cs
public class InstanceFromLooseXamlExtension : MarkupExtension
{
public Uri Source { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
{
throw new ArgumentNullException(nameof(Source));
}
Uri source;
if (Source.IsAbsoluteUri)
{
source = Source;
}
else
{
var iuc = serviceProvider?.GetService(typeof(IUriContext)) as IUriContext;
if (iuc == null)
{
throw new ArgumentException("Bad service contexts.", nameof(serviceProvider));
}
source = new Uri(iuc.BaseUri, Source);
}
WebResponse response;
if (source.IsFile)
{
response = WebRequest.Create(source.GetLeftPart(UriPartial.Path)).GetResponse();
}
else if(string.Compare(source.Scheme, PackUriHelper.UriSchemePack, StringComparison.Ordinal) == 0)
{
var iwrc = new PackWebRequestFactory() as IWebRequestCreate;
response = iwrc.Create(source).GetResponse();
}
else
{
throw new ArgumentException("Unsupported Source.", nameof(Source));
}
object result;
try
{
result = XamlReader.Load(response.GetResponseStream());
}
finally
{
response.Close();
}
return result;
}
}
这个标记扩展有一个 Uri 类型的 Source 属性,让用户指定要加载哪个 XAML 文件。最后,像这样使用标记扩展。
MainWindow.xaml
<Window x:Class="WpfApp.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:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{local:InstanceFromLooseXaml Source=/Data.xaml}">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Key}">
<ListBox ItemsSource="{Binding Value}"/>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
松散的XAML可以包含几乎所有东西,例如ResourceDictionary或带有UiElements的内容。但是无论是Blend还是Visual Studio都无法正确地为您检查它。最终,希望这足以作为答案。
最近我已经采用了创建视图模型的设计时实例的方式,将其放在我的定位器中,并像@ChrisW上面建议的那样进行引用:
d:DataContext="{Binding Source={StaticResource Locator}, Path=DesignTimeVM}"
这样我就可以有一些硬编码的值来填充我的列表、组合框等,这样整个样式就更容易了。
我使用MVVM Light,在我的ViewModel构造函数中,我使用以下模式:
if(IsInDesignMode)
{
ListUsers = new List<User>();
.
.
.
}
该代码仅在设计时执行,您的Xaml UI将绑定到实际数据。
d:DataContext="{StaticResource PathToDesignTimeDataVMStuffInResources"
,使其在设计时间中仅仅替换数据上下文为虚假的部分? - Chris W.Dictionary<string, string>
也不行。 - Sentry