【问题标题】:WPF, covariance, contravariance, and binding in .Net.Net 中的 WPF、协变、逆变和绑定
【发布时间】:2015-10-22 14:46:37
【问题描述】:

我需要创建一个设置管理器,但我不能使用 appSettings 或 applicationSettings,因为我需要特定的行为(在运行时添加/删除设置,从多个文件中加载/保存...)。

我遇到的问题是找到我想要的最终结果的解决方案。以下是它在用例中应该是什么样子的示例:

public partial class MyWindow : Window
{
    private SettingsManager _settingsManager;

    public MyClass()
    {
        _settingsManager.Add(new Option(){Name = "Option1", DisplayName = "Option 1", DefaultValue = "SomeText", Value = "SomeText"});
        _settingsManager.Add(new Option(){Name = "Option2", DisplayName = "Option 2", DefaultValue = 5});
        _settingsManager.Add(new Option(){Name = "Option3", DisplayName = "Option 3"});
        _settingsManager["Option2"] = 10;
        _settingsManager["Option3"].DefaultValue = Brushes.Black;
        _settingsManager["Option3"] = Brushes.White;

        DataContext = _settingsManager;
    }
}

与其对应的 XAML 文件:

<Window x:Class="AnotherProject.MyWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
    <ItemsControl ItemsSource="{Binding}">
        <ItemsControl.Resources>
            <DataTemplate DataType="{x:Type String}">
                <Label Content="{Binding}"/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type int}">
                <Label Content="{Binding}" Foreground="Red"/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type Brush}">
                <Label Content="{Binding ToString}" Foreground="{Binding}"/> 
            </DataTemplate>
        </ItemsControl.Resources>
    </ItemsControl>
</Grid>

我知道 WPF 不支持 int 和 string 类型并且会产生错误,但这仅用作演示示例,实际类型不会产生这些错误。

我尝试从 appSettings 使用的 SettingsProperty 继承,但对于我想要获得的东西来说它似乎太复杂了,而且我无法在不强制转换的情况下分配值。

我尝试使用泛型,但类型识别出现问题。除非将对象转换为适当的类型,否则以下行不起作用:

_settingsManager["Option2"] = 10;

我知道单独实现一些用例部分的方法,但不是一种能完成上述所有部分的方法。

有没有可能做到这一点?

更新: 这是 SettingManager 的样子

    public class SettingsManager : ObservableCollection<Option>
{
    public SettingsManager()
    {
    }

    public SettingsManager(IEnumerable<Option> options)
    {
        if (options != null)
        {
            foreach(var option in options)
            {
                this.Add(option);
            }
        }
    }

    public Option this[String key]
    {
        get
        {
            return this.Where(x => x.Name == key).FirstOrDefault();
        }
        set
        {
            var res = this.First(x => x.Name == key);
            if (res != null)
            {
                this.SetItem(this.IndexOf(res), value);
            }
            else
                this.Add(value);
        }
    }

    public SettingsManager AddRange(IEnumerable<Option> options)
    {
        if (options != null)
        {
            foreach(var option in options)
            {
                this.Add(option);
            }
        }
        return this;
    }

    public void ResetAll()
    {
        this.ToList().ForEach(x => x.Reset());
    }
}

【问题讨论】:

  • 您将Stringint 类型设置错误,您需要在XAML 中声明System 命名空间,然后才能访问这些类型。即使您这样做,我也不认为您的DataTemplates 工作。如果您显示SettingsManager 类会很有帮助,该类是否实现了诸如ICollection&lt;Option&gt; 之类的接口?或者它只是一个暴露Add 方法和一些索引器的包装类?
  • 我已经用 SettingsManager 类更新了这个问题。另外,我知道它不会按原样工作。这是一个非常简化的代码,用于演示我希望如何使用这些设置。

标签: c# .net wpf generics covariance


【解决方案1】:

ItemsControl 将其ItemsSource 设置为Option 的集合,这意味着每个数据项的类型为Option。所以DataTemplate with DataType 不能过滤不同类型的ValueOption 的属性)来应用不同的模板。在这种情况下,您应该使用DataTemplateSelector,为方便起见,将您的DataTemplates 定义为XAML 中的资源(然后我们可以在SelectTemplate 方法内的代码中找到它们):

public class OptionTemplateSelector : DataTemplateSelector {
   public override DataTemplate SelectTemplate(object item, DependencyObject container){
      var option = item as Option;
      if(option != null){
          var itemContainer = container as FrameworkElement;
          if(option.Value is int){
             return itemContainer.FindResource("intOption") as DataTemplate;
          }
          else if(option.Value is string){
             return itemContainer.FindResource("stringOption") as DataTemplate;
          }
          else if(option.Value is Brush){
             return itemContainer.FindResource("brushOption") as DataTemplate;
          }
      }
      return null;
   }
}

将模板选择器定义为 XAML 中的某些资源:

<local:OptionTemplateSelector x:Key="ot">

剩余的 XAML:

<ItemsControl ItemsSource="{Binding}"
              ItemTemplateSelector="{StaticResource ot}">         
    <ItemsControl.Resources>  
        <DataTemplate x:Key="stringOption">
            <Label Content="{Binding Value}"/>
        </DataTemplate>
        <DataTemplate x:Key="intOption">
            <Label Content="{Binding Value}" Foreground="Red"/>
        </DataTemplate>
        <DataTemplate x:Key="brushOption">
            <Label Content="{Binding Value}" Foreground="{Binding}"/> 
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>

【讨论】:

  • 嗯,上次我尝试使用 DataTemplateSelectors 时并不顺利。但这似乎是最明显和最简单的解决方案。
猜你喜欢
  • 1970-01-01
  • 2015-02-09
  • 2012-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-14
  • 1970-01-01
  • 2016-11-03
相关资源
最近更新 更多