我有一个复杂模型的集合,每个模型都包含其他复杂模型的接口实例集合,我需要显示这些父子复杂模型,允许编辑父子复杂模型的所有属性。
如何最好地显示此数据并允许单独编辑父对象和子对象的属性,以及通过组合选择多个单元格和上下文菜单单击(即跨多个父对象更改子模型上的相同属性值) ? 我还需要能够通过从编辑机制(当前为 DataGrid 单元格)中的搜索来执行诸如将模型属性值设置为其他复杂模型实例的操作?
下面是一个类的通用示例,它近似于我在应用程序中使用的内容。
enum ChildType
{
One,
Two,
Three
}
class ComplexType
{
public long ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
class IChildModel
{
ChildType Type { get; set; }
string Name { get; set; }
}
class ChildModel1 : IChildModel
{
public ChildType Type { get; set; }
public string Name { get; set; }
public string Property1 { get; set; }
public decimal Property2 { get; set; }
public ComplexType Property3 { get; set; }
}
class ChildModel2 : IChildModel
{
public ChildType Type { get; set; }
public long Property1 { get; set; }
public string Property2 { get; set; }
}
class Parent
{
public long ID { get; set; }
public string Name { get; set; }
public CustomObservableCollection<IChildModel> Children { get; set; }
}
class ViewModel
{
public CustomObservableCollection<Parent> Parents { get; set; }
}
到目前为止,我已经使用 DataGrid 实现了应用程序,并使用反射在 View 代码隐藏中动态生成了列。子复杂对象实例的列绑定使用 CustomObservableCollection<> 上的下标(在这种情况下,自定义集合允许按通用值 [enum ChildType] 进行索引)。特别是绑定使得跨多个父级的子实例正确设置同一属性的值变得困难(通过列上的多选和上下文菜单单击设置值)。同样,我正在处理 View 的代码隐藏中的此类大规模更改,使用反射绑定路径解析来设置属性值(感觉不对;讨厌那样做)。我希望能够在 ViewModel 上设置选定的子项,并将属性名称和新值传递给 ViewModel 中的命令以进行更改。即使能够将命令传递给子类型、属性和新值也会很好(我认为)。
我通过 Google、stackoverflow、Code Project 等进行的研究已将我指向我当前的解决方案,但我觉得我对问题的思考不正确,应该有更好的 MVVM 方法来解决这个问题。
编辑
此应用程序的主要重点是允许在视图中编辑多个父模型实例和子模型实例,用户可以在该视图中比较多个实例的值,并允许跨多个相同类型的对象设置父属性或子属性的值为相同的值(即 Parent1 和 Parent2 都有一个 ChildModel1 并且用户希望将两个父对象的 ChildModel1 的 Property3 上的 Name 设置为“X”)。虽然,应用程序仍然必须允许对父对象和子对象的属性进行单独编辑(DataGrid 似乎确实很好地满足了要求)。为了满足这些要求,我在视图中实现了动态列创建。下面是这个逻辑的一个通用示例。
private void DataGrid_TargetUpdated(object sender, DataTransferEventArgs e)
{
var vm = DataContext as ViewModel;
if (vm != null && vm.Parents != null) {
List<ChildType> processedChildTypes = new List<ChildType>();
foreach (var parent in vm.Parents) {
for (int childIndex = 0; childIndex < parent.Children.Count; ++childIndex) {
var child = vm.Children[childIndex];
if (!processedChildTypes.Contains(child.Type)) { // Ensure each child type is only processed once
processedChildTypes.Add(child.Type);
CreateChildPropertyColumns(processedChildTypes, child);
}
}
}
}
private void CreateChildPropertyColumns(List<ChildType> processedChildTypes, IChildModel child)
{
PropertyInfo[] childProperties = child.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); // Only use properties declared on the child type
Type childInterfaceType = typeof(IChildModel);
foreach (PropertyInfo childProperty in childProperties) {
// Only create a column if the property is editable
if (childProperty.CanWrite) {
if (childInterfaceType.IsAssignableFrom(childProperty.PropertyType)) {
var subChild = childProperty.GetValue(child, null) as IChildModel;
if (subChild != null && !processedChildTypes.Contains(subChild.Type)) {
processedChildTypes.Add(subChild.Type);
CreateChildPropertyColumns(processedChildTypes, subChild);
}
}
else
dataGrid.Columns.Add(CreateChildPropertyColumn(child.Type, childProperty));
}
}
}
private DataGridColumn CreateChildPropertyColumn(ChildType childType, PropertyInfo propertyInfo)
{
DataGridColumn column = null;
var binding = new Binding(string.Format("Children[{0}].{1}", childType, propertyInfo.Name));
/* Create column based on PropertyInfo here */
/* Default case is a text column */
column = new DataGridTextColumn() { Binding = binding };
column.Header = propertyInfo.Name;
return column;
}