如何在WPF中编辑不可变对象而无需重复代码?

时间:2022-01-16 11:58:31

We have lots of immutable value objects in our domain model, one example of this is a position, defined by a latitude, longitude & height.

我们的域模型中有很多不可变的值对象,其中一个例子是位置,由纬度、经度和高度定义。

/// <remarks>When I grow up I want to be an F# record.</remarks>
public class Position
{
    public double Latitude
    {
        get;
        private set;
    }

    // snip

    public Position(double latitude, double longitude, double height)
    {
        Latitude = latitude;
        // snip
    }
}

The obvious way to allow editing of a position is to build a ViewModel which has getters and setters, as well as a ToPosition() method to extract the validated immutable position instance. While this solution would be ok, it would result in a lot of duplicated code, especially XAML.

允许编辑位置的明显方法是构建一个具有getter和setter的ViewModel,以及一个ToPosition()方法来提取经过验证的不可变位置实例。虽然这个解决方案还可以,但是会导致大量重复的代码,特别是XAML。

The value objects in question consist of between three and five properties which are usually some variant of X, Y, Z & some auxiliary stuff. Given this, I had considered creating three ViewModels to handle the various possibilities, where each ViewModel would need to expose properties for the value of each property as well as a description to display for each label (eg. "Latitude").

所讨论的值对象由3到5个属性组成,这些属性通常是X、Y、Z的变体和一些辅助属性。考虑到这一点,我考虑过创建三个ViewModel来处理各种可能性,其中每个ViewModel都需要为每个属性的值公开属性,以及为每个标签显示一个描述。“纬度”)。

Going further, it seems like I could simplify it to one general ViewModel that can deal with N properties and hook everything up using reflection. Something like a property grid, but for immutable objects. One issue with a property grid is that I want to be able to change the look so I can have labels and textboxes such as:

更进一步说,我似乎可以将它简化为一个通用的ViewModel,它可以处理N个属性,并使用反射将所有东西连接起来。类似于属性网格,但对于不可变对象。属性网格的一个问题是,我希望能够改变外观,这样我就可以有标签和文本框,比如:

Latitude:   [      32 ]  <- TextBox
Longitude:  [     115 ]
Height:     [      12 ]

Or put it in a DataGrid such as:

或将其放入DataGrid中,例如:

Latitude  |  Longitude  |  Height
      32           115         12

So my question is:

我的问题是:

Can you think of an elegant way to solve this problem? Are there any libraries that do this or articles about something similar?

你能想出一个优雅的方法来解决这个问题吗?有图书馆做这个或文章类似的事情吗?

I'm mainly looking for:

我主要是寻找:

  • Code duplication to be minimized
  • 将代码复制最小化。
  • Easy to add new value object types
  • 易于添加新的值对象类型
  • Possible to extend with some kind of validation
  • 可以通过某种验证来扩展。

3 个解决方案

#1


2  

I found this old question while researching my possible options in the same situation. I figured I should update it in case anyone else stumbles on to it:

我在研究同样情况下的可能选择时发现了这个老问题。我想我应该把它更新一下以防别人发现它:

Another option (not available when Paul offered his solution since .Net 4 wasn't out yet) is to use the same strategy, but instead of implementing it using CustomTypeDescriptors, use a combination of generics, dynamic objects and reflection to achieve the same effect.

另一种选择(因为。net 4还没有发布,所以保罗提供了他的解决方案)是使用相同的策略,而不是使用CustomTypeDescriptors实现它,而是使用泛型、动态对象和反射的组合来实现相同的效果。

In this case, you define a class

在这种情况下,定义一个类

class Mutable<ImmutableType> : DynamicObject
{
   //...
}

It's constructor takes an instance of the immutable type and a delegate that constructs a new instance of it out of a dictionary, just like in Paul's answer. The difference here, however, is that you override the TryGetMember and TrySetMember to populate an internal dictionary that you're eventually going to use as the argument for the constructor-delegate. You use reflection in order to verify that the only properties that you're accepting are those that are actually implemented in ImmutableType.

它的构造函数接受一个不可变类型的实例,一个委托从字典中构造一个新实例,就像Paul的答案一样。但是,这里的不同之处在于,您可以覆盖TryGetMember和TrySetMember来填充一个内部字典,最终您将使用它作为构造-委托的参数。您使用反射来验证您所接受的唯一属性是那些实际上在ImmutableType中实现的属性。

Performance wise, I wager that Paul's answer is faster, and doesn't involve dynamic objects, which are known to put C# developers into fits. But the implementation for this solution is also a little simpler, because Type Descriptors are a bit arcane.

就性能而言,我敢打赌保罗的答案是更快的,并且不涉及动态对象,众所周知,动态对象会使c#开发人员适应。但是这个解决方案的实现也比较简单,因为类型描述符有点晦涩。


Here's the requested proof-of-concept / example implementation:

以下是所要求的概念验证/示例实现:

https://bitbucket.org/jwrush/mutable-generic-example

https://bitbucket.org/jwrush/mutable-generic-example

#2


5  

Custom Type Descriptors could be used to solve this problem. Before you bind to a Position, your type descriptor could kick in, and provide get and set methods to temporarily build the values. When the changes are committed, it could build the immutable object.

可以使用自定义类型描述符来解决这个问题。在绑定到一个位置之前,类型描述符可以启动,并提供get和set方法来临时构建值。当提交更改时,它可以构建不可变对象。

It might look something like this:

它可能是这样的:

DataContext = new Mutable(position, 
    dictionary => new Position(dictionary["lattitude"], ...)
);

Your bindings can still look like this:

您的绑定仍然可以如下所示:

<TextBox Text="{Binding Path=Lattitude}" />

Because the Mutable object will 'pretend' to have properties like Lattitude thanks to its TypeDescriptor.

因为由于它的类型描述符,可变对象将“假装”具有像lat韧这样的属性。

Alternatively you might use a converter in your bindings and come up with some kind of convention.

或者,您可以在绑定中使用转换器,并提出某种约定。

Your Mutable class would take the current immutable object, and a Func<IDictionary, object> that allows you to create the new immutable object once editing completes. Your Mutable class would make use of the type descriptor, which would create PropertyDescriptors that create the new immutable object upon being set.

您的可变类将使用当前不可变对象,而Func 允许您在编辑完成后创建新的不可变对象。您的可变类将使用类型描述符,它将创建PropertyDescriptors,在被设置时创建新的不可变对象。 ,对象>

For an example of how to use type descriptors, see here:

有关如何使用类型描述符的示例,请参见这里:

http://www.paulstovell.com/editable-object-adapter

http://www.paulstovell.com/editable-object-adapter

Edit: if you want to limit how often your immutable objects are created, you might also look at BindingGroups and IEditableObject, which your Mutable can also implement.

编辑:如果您想限制创建不可变对象的频率,您还可以查看BindingGroups和IEditableObject,您的可变对象也可以实现它们。

#3


0  

Can you think of an elegant way to solve this problem?

你能想出一个优雅的方法来解决这个问题吗?

Honestly, you just dance around the problem, but don't mention the problem itself ;).

老实说,你只是绕着问题跳来跳去,但不要提及问题本身;

If I correctly guess your problem, then the combination of MultiBinding and IMultiValueConverter should do the trick.

如果我正确地猜测了您的问题,那么MultiBinding和IMultiValueConverter的组合应该可以解决这个问题。

HTH.

HTH。

P.S. BTW, you have immutable class instances, not value objects. With value objects (which are described by struct keyword) you would dance much more no matter if there were setters or not :).

顺便说一句,你有不可变的类实例,而不是值对象。使用value对象(由struct关键字描述),无论是否有setter,您都会跳得更多:)。

#1


2  

I found this old question while researching my possible options in the same situation. I figured I should update it in case anyone else stumbles on to it:

我在研究同样情况下的可能选择时发现了这个老问题。我想我应该把它更新一下以防别人发现它:

Another option (not available when Paul offered his solution since .Net 4 wasn't out yet) is to use the same strategy, but instead of implementing it using CustomTypeDescriptors, use a combination of generics, dynamic objects and reflection to achieve the same effect.

另一种选择(因为。net 4还没有发布,所以保罗提供了他的解决方案)是使用相同的策略,而不是使用CustomTypeDescriptors实现它,而是使用泛型、动态对象和反射的组合来实现相同的效果。

In this case, you define a class

在这种情况下,定义一个类

class Mutable<ImmutableType> : DynamicObject
{
   //...
}

It's constructor takes an instance of the immutable type and a delegate that constructs a new instance of it out of a dictionary, just like in Paul's answer. The difference here, however, is that you override the TryGetMember and TrySetMember to populate an internal dictionary that you're eventually going to use as the argument for the constructor-delegate. You use reflection in order to verify that the only properties that you're accepting are those that are actually implemented in ImmutableType.

它的构造函数接受一个不可变类型的实例,一个委托从字典中构造一个新实例,就像Paul的答案一样。但是,这里的不同之处在于,您可以覆盖TryGetMember和TrySetMember来填充一个内部字典,最终您将使用它作为构造-委托的参数。您使用反射来验证您所接受的唯一属性是那些实际上在ImmutableType中实现的属性。

Performance wise, I wager that Paul's answer is faster, and doesn't involve dynamic objects, which are known to put C# developers into fits. But the implementation for this solution is also a little simpler, because Type Descriptors are a bit arcane.

就性能而言,我敢打赌保罗的答案是更快的,并且不涉及动态对象,众所周知,动态对象会使c#开发人员适应。但是这个解决方案的实现也比较简单,因为类型描述符有点晦涩。


Here's the requested proof-of-concept / example implementation:

以下是所要求的概念验证/示例实现:

https://bitbucket.org/jwrush/mutable-generic-example

https://bitbucket.org/jwrush/mutable-generic-example

#2


5  

Custom Type Descriptors could be used to solve this problem. Before you bind to a Position, your type descriptor could kick in, and provide get and set methods to temporarily build the values. When the changes are committed, it could build the immutable object.

可以使用自定义类型描述符来解决这个问题。在绑定到一个位置之前,类型描述符可以启动,并提供get和set方法来临时构建值。当提交更改时,它可以构建不可变对象。

It might look something like this:

它可能是这样的:

DataContext = new Mutable(position, 
    dictionary => new Position(dictionary["lattitude"], ...)
);

Your bindings can still look like this:

您的绑定仍然可以如下所示:

<TextBox Text="{Binding Path=Lattitude}" />

Because the Mutable object will 'pretend' to have properties like Lattitude thanks to its TypeDescriptor.

因为由于它的类型描述符,可变对象将“假装”具有像lat韧这样的属性。

Alternatively you might use a converter in your bindings and come up with some kind of convention.

或者,您可以在绑定中使用转换器,并提出某种约定。

Your Mutable class would take the current immutable object, and a Func<IDictionary, object> that allows you to create the new immutable object once editing completes. Your Mutable class would make use of the type descriptor, which would create PropertyDescriptors that create the new immutable object upon being set.

您的可变类将使用当前不可变对象,而Func 允许您在编辑完成后创建新的不可变对象。您的可变类将使用类型描述符,它将创建PropertyDescriptors,在被设置时创建新的不可变对象。 ,对象>

For an example of how to use type descriptors, see here:

有关如何使用类型描述符的示例,请参见这里:

http://www.paulstovell.com/editable-object-adapter

http://www.paulstovell.com/editable-object-adapter

Edit: if you want to limit how often your immutable objects are created, you might also look at BindingGroups and IEditableObject, which your Mutable can also implement.

编辑:如果您想限制创建不可变对象的频率,您还可以查看BindingGroups和IEditableObject,您的可变对象也可以实现它们。

#3


0  

Can you think of an elegant way to solve this problem?

你能想出一个优雅的方法来解决这个问题吗?

Honestly, you just dance around the problem, but don't mention the problem itself ;).

老实说,你只是绕着问题跳来跳去,但不要提及问题本身;

If I correctly guess your problem, then the combination of MultiBinding and IMultiValueConverter should do the trick.

如果我正确地猜测了您的问题,那么MultiBinding和IMultiValueConverter的组合应该可以解决这个问题。

HTH.

HTH。

P.S. BTW, you have immutable class instances, not value objects. With value objects (which are described by struct keyword) you would dance much more no matter if there were setters or not :).

顺便说一句,你有不可变的类实例,而不是值对象。使用value对象(由struct关键字描述),无论是否有setter,您都会跳得更多:)。