【问题标题】:Expression Blend and Sample data for Dictionary in WPF applicationWPF 应用程序中字典的 Expression Blend 和示例数据
【发布时间】:2011-06-23 06:39:32
【问题描述】:

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

我的一个视图模型属于以下类型:

public Dictionary<DateTime, ObservableCollection<MyViewModel>> TimesAndEvents

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

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

【问题讨论】:

  • 仍在寻找这个问题的答案
  • @Sentry 在这里可以问很多问题。您是否提供了对模拟视图模型的参考以及要使用的实际设计时数据?如果是这样,您能否不将设计时数据上下文指定为类似于(在父级上)d:DataContext="{StaticResource PathToDesignTimeDataVMStuffInResources" 的资源,所以它只是在设计时将数据上下文交换为假的东西?
  • @ChrisW。我在不到一年前开始 WPF,所以我不太习惯所有的可能性。请原谅我在这里缺乏知识,但我看不出您的问题(模拟视图模型?)与 XAML 形式的示例数据有什么关系。
  • @Sentry 不用担心。是否意味着您有实际的假数据可用于提供我假设您在运行时在 xaml 中拥有的绑定?这些虚假数据必须存在于某个地方。
  • @ChrisW。我的假数据就是:假的。随机字符串,随机数。 Blend 可以为我的大多数视图模型创建它们,但是当有字典时,Blend 不会生成它。甚至Dictionary&lt;string, string&gt;

标签: wpf xaml expression-blend


【解决方案1】:

关于您的最后一个问题:很遗憾,您不能轻松在 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>

注意:&lt;d:Window.DataContext&gt; 中的“d”很重要,因为它告诉 Blend 和编译器该特定元素用于设计时,在编译文件时应该忽略它。

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


设置问题

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

  1. MyViewModel.cs
  2. MainWindowViewModel.cs
  3. MainWindow.xaml
  4. 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;
}

【讨论】:

    【解决方案2】:

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

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

    所以我可以有一些硬编码的值来填充我的列表、组合框等。使所有内容的样式变得更加容易。

    我使用 MVVM Light,因此在我的 ViewModel 的构造函数中我使用了这样的模式:

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

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

    【讨论】:

      【解决方案3】:

      由于 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 放在应用程序文件夹中,因此“Source=/Data.xaml”就可以了。每次重新加载设计器(重建将确保它)时,将应用松散 xaml 中的内容。结果应该是这样的

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多