我正在使用需要大量值转换的视图的WPF应用程序。最初,我的哲学(部分是受到有关XAML信徒的激烈辩论的启发)的,我应该严格按照支持视图数据要求的观点来构建视图模型。这意味着将值转换为可见性,画笔,大小等所需的任何值转换都将由值转换器和多值转换器处理。从概念上讲,这似乎很优雅。视图模型和视图都将具有独特的目的并且可以很好地分离。在“数据”和“外观”之间将划清界限。
好吧,在将这种策略赋予“旧的大学尝试”之后,我有些怀疑是否要继续以这种方式发展。我实际上正在强烈考虑转储值转换器,并将(几乎)所有值转换的责任直接交给视图模型。
使用价值转换器的现实似乎并没有达到完全分开的关注点的表观价值。对于价值转换器,我最大的问题是使用它们很乏味。您必须创建一个新类,实现IValueConverter
或IMultiValueConverter
,将一个或多个值转换为object
正确的类型,进行测试DependencyProperty.Unset
(至少对于多值转换器而言),编写转换逻辑,在资源字典中注册转换器 [请参见下面的更新],最后,使用相当冗长的XAML(这要求转换器的绑定和名称都使用魔术字符串)来连接转换器[请参见下面的更新]。调试过程也不是一件容易的事,因为错误消息通常是不明确的,尤其是在Visual Studio的设计模式/ Expression Blend中。
这并不是说替代方案(使视图模型负责所有值转换)是一种改进。这很可能是另一面草更绿的问题。除了失去优雅的关注点分离之外,您还必须编写一堆派生属性,并确保RaisePropertyChanged(() => DerivedProperty)
在设置基本属性时认真调用,这可能会带来令人不愉快的维护问题。
以下是我汇总的初始清单,这些清单允许视图模型处理转换逻辑并取消使用值转换器的优缺点:
- 优点:
- 由于取消了多转换器,因此总绑定数更少
- 较少的魔术字符串(绑定路径
+转换器资源名称) 无需再注册每个转换器(还要维护此列表)- 减少编写每个转换器的工作(无需实现接口或强制转换)
- 可以轻松注入依赖项以帮助进行转换(例如颜色表)
- XAML标记不那么冗长,更易于阅读
- 转换器重用仍然可能(尽管需要一些计划)
- DependencyProperty.Unset没有神秘问题(我在多值转换器中注意到的一个问题)
*删除线表示如果您使用标记扩展,这些好处将消失(请参阅下面的更新)
- 缺点:
- 视图模型和视图之间的耦合更强(例如,属性必须处理可见性和画笔之类的概念)
- 更多的总体属性可允许直接映射视图中的每个绑定
(请参阅下面的更新2)RaisePropertyChanged
必须为每个派生属性调用- 如果转换基于UI元素的属性,则仍必须依赖转换器
因此,正如您可能会说的那样,我对此问题感到非常沮丧。我非常不愿走重构的道路,只是意识到无论我在视图模型中使用值转换器还是暴露大量的值转换属性,编码过程都是同样低效而乏味的。
我是否缺少任何利弊?对于那些尝试了两种价值转换方式的人,您觉得哪种对您更好,为什么?还有其他选择吗?(门徒提到了有关类型描述符提供程序的一些内容,但是我无法理解他们在说什么。对此的任何见解都将受到赞赏。)
更新资料
我今天发现,可以使用一种称为“标记扩展”的东西来消除注册值转换器的需要。实际上,它不仅消除了注册它们的需要,而且还提供了在您键入时选择转换器的智能感知Converter=
。这是让我入门的文章:http : //www.wpftutorial.net/ValueConverters.html。
使用标记扩展的能力在上面我的优缺点列表和讨论中(见删除线)在某种程度上改变了平衡。
作为这个启示的结果,我正在尝试一个混合系统,在该系统中,我将转换器用于BoolToVisibility
我所说的内容MatchToVisibility
,并将视图模型用于所有其他转换。MatchToVisibility基本上是一个转换器,可以让我检查绑定值(通常是枚举)是否与XAML中指定的一个或多个值匹配。
例:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
基本上,这是检查状态为已完成还是已取消。如果是,那么可见性将设置为“可见”。否则,它将设置为“隐藏”。事实证明,这是非常普遍的情况,使用此转换器可以为我节省约15个属性(以及关联的RaisePropertyChanged语句)。请注意,当您键入时Converter={vc:
,“ MatchToVisibility”将显示在智能菜单中。这显着减少了出错的机会,并减少了使用值转换器的麻烦(您不必记住或查找所需值转换器的名称)。
如果您感到好奇,我将在下面粘贴代码。这种实现的一个重要特点MatchToVisibility
是,它检查是否绑定的值是一个enum
,如果是,它检查,以确保Value1
,Value2
等也都是相同类型的枚举。这样可以在设计时和运行时检查是否有任何枚举值输入错误。为了将其改进为编译时检查,您可以改用以下代码(我手动输入了此代码,因此,如果我有任何错误,请原谅我):
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue={x:Type {win:Visibility.Visible}},
IfFalse={x:Type {win:Visibility.Hidden}},
Value1={x:Type {enum:Status.Finished}},
Value2={x:Type {enum:Status.Canceled}}"
尽管这样做比较安全,但对于我来说值得它太冗长了。如果要执行此操作,则最好在视图模型上使用属性。无论如何,我发现设计时检查对于我迄今为止尝试过的场景来说是完全足够的。
这是代码 MatchToVisibility
[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
[ConstructorArgument("ifTrue")]
public object IfTrue { get; set; }
[ConstructorArgument("ifFalse")]
public object IfFalse { get; set; }
[ConstructorArgument("value1")]
public object Value1 { get; set; }
[ConstructorArgument("value2")]
public object Value2 { get; set; }
[ConstructorArgument("value3")]
public object Value3 { get; set; }
[ConstructorArgument("value4")]
public object Value4 { get; set; }
[ConstructorArgument("value5")]
public object Value5 { get; set; }
public MatchToVisibility() { }
public MatchToVisibility(
object ifTrue, object ifFalse,
object value1, object value2 = null, object value3 = null,
object value4 = null, object value5 = null)
{
IfTrue = ifTrue;
IfFalse = ifFalse;
Value1 = value1;
Value2 = value2;
Value3 = value3;
Value4 = value4;
Value5 = value5;
}
public override object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
var values = new[] { Value1, Value2, Value3, Value4, Value5 };
var valueStrings = values.Cast<string>();
bool isMatch;
if (Enum.IsDefined(value.GetType(), value))
{
var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
isMatch = valueEnums.ToList().Contains(value);
}
else
isMatch = valueStrings.Contains(value.ToString());
return isMatch ? ifTrue : ifFalse;
}
}
这是代码 BaseValueConverter
// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public abstract object Convert(
object value, Type targetType, object parameter, CultureInfo culture);
public virtual object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
这是ToEnum扩展方法
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
更新2
自发布此问题以来,我遇到了一个使用“ IL编织”为属性和从属属性注入NotifyPropertyChanged代码的开源项目。这使Josh Smith将视图模型的愿景实现为“类固醇的价值转换器”变得轻而易举。您可以只使用“自动实现的属性”,编织器将完成其余工作。
例:
如果输入此代码:
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
...这是编译的内容:
string givenNames;
public string GivenNames
{
get { return givenName; }
set
{
if (value != givenName)
{
givenNames = value;
OnPropertyChanged("GivenName");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
这可以节省您键入,阅读,滚动过去等等的代码量。但是,更重要的是,它使您不必弄清楚什么是依赖项。您可以像添加新的“属性获取”一样,FullName
而不必费心地在依赖关系链中添加RaisePropertyChanged()
呼叫。
这个开源项目叫什么?原始版本称为“ NotifyPropertyWeaver”,但所有者(Simon Potter)此后创建了一个名为“ Fody”的平台,用于托管整个IL编织器系列。在此新平台下,NotifyPropertyWeaver的等效项称为PropertyChanged.Fody。
- Fody设置说明: http : //code.google.com/p/fody/wiki/SampleUsage(将“ Virtuosity”替换为“ PropertyChanged”)
- PropertyChanged.Fody项目站点: http : //code.google.com/p/propertychanged/
如果您希望使用NotifyPropertyWeaver(安装起来比较简单,但将来不一定会在错误修复后进行更新),请访问项目站点:http : //code.google.com/p/ notifypropertyweaver /
无论哪种方式,这些IL编织器解决方案都完全改变了类固醇视图模型与值转换器之间的争论。
MatchToVisibility
似乎是启用某些简单模式切换的一种便捷方法(我有一个视图,特别是带有大量可以打开和关闭的零件。在大多数情况下,视图的各个部分甚至都标有x:Name
)以匹配模式它们对应。)我并没有真正想到这是“业务逻辑”,但我会给您一些评论。
BooleanToVisibility
采用与可见性相关的一个值(是/否),然后将其转换为另一个值。这似乎是的理想用法ValueConverter
。另一方面,MatchToVisibility
是在中编码业务逻辑View
(应该显示什么类型的项目)。在我看来,这种逻辑应该被推到ViewModel
甚至是我所说的逻辑上EditModel
。用户看到的应该是被测试的东西。