WPF应用程序中Dictionary的Expression Blend和Sample数据

时间:2011-06-23 06:39:32

标签: wpf xaml expression-blend

我有一个WPF应用程序,我正在使用Blend来设置样式。

我的一个视图模型是:

public Dictionary<DateTime, ObservableCollection<MyViewModel>> TimesAndEvents

但是当我尝试在Expression Blend中创建一些示例数据时,它根本不会为此属性创建XAML。

你能在XAML中创建这样的数据类型吗?非设计时间支持正在削弱我的工作效率。

3 个答案:

答案 0 :(得分:0)

我已经在我的定位器中创建了我的Viewmodel的设计时实例的路线,我在上面提到了@ChrisW:

d:DataContext="{Binding Source={StaticResource Locator}, Path=DesignTimeVM}"

所以我可以使用一些硬编码的值来填充我的列表,组合框等。使样式更容易。

我使用MVVM Light,所以在我的ViewModel的构造函数中,我使用这样的模式:

if(IsInDesignMode)
{
  ListUsers = new List<User>();
.
.
.
}

代码只会在设计时执行,您将Xaml UI绑定到实际数据。

答案 1 :(得分:0)

关于您的上一个问题:,遗憾的是,您无法轻松在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>

注意:&#39; d&#39;在<d:Window.DataContext>中很重要,因为它告诉Blend和编译器该特定元素是设计时间的,并且在编译文件时应该忽略它。

完成后,我的设计视图现在如下所示:

An image of Blend's design view with sample data in it.

设置问题

我从5个类开始(2个是从WPF项目模板生成的,我建议使用它):

  1. MyViewModel.cs
  2. MainWindowViewModel.cs
  3. MainWindow.xaml
  4. 的App.xaml
  5. 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的初始化方法(我将在底部包含我的实现),你应该会看到如下所示的屏幕构建并运行您的WPF应用程序:

    An image of what your screen should look like.

    又出现了什么问题?

    问题是设计视图中没有显示任何内容。

    An image depicting a blank screen in Blend's design view.

    我的 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;
    }
    

答案 2 :(得分:0)

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

在这种情况下,我将Data.xaml放在应用程序文件夹中,因此&#39; Source = / Data.xaml&#39;会好的。每次设计器重新加载(重建将确保它)时,将应用松散xaml中的内容。结果应该看起来像

松散的xaml几乎可以包含所有内容,例如ResourceDictionary或UiElements。但Blend或Visual Studio都不会为您正确检查它。最后,希望这足以得到答案。