(我对WPF很新,所以这个问题可能看起来很明显或不一致。)
需要从子模式窗口编辑应用程序的某些基础业务数据,并且只有在用户按下此窗口中的“确定”按钮时才更新数据。我们将此窗口称为SettingsDialog。
在这种情况下,使用WPF数据绑定将SettingsDialog的控件绑定到业务数据是否仍然合理? (如果是这样,仅当用户按下SettingsDialog的OK按钮时如何更新业务数据?)
或者在显示SettingsDialog时从业务数据手动分配SettingsDialog的控件值是否更好,然后仅在用户按下OK按钮时将其分配回来?
正确选择的参数是什么(更小或更清晰的代码,性能,可扩展性)?
类似案例是否有一些公认的设计模式?
编辑:我将Bubblewrap的答案标记为已被接受,因为它最适合我自己的具体案例。虽然,卫兵和约翰的答案似乎也可以接受。
总结一下:使用数据绑定有一些优点。 它允许SettingsDialog对业务对象内部连接和依赖关系(如果有的话)一无所知,允许以后轻松地从模态切换到非模态模式,减少GUI和业务数据之间的依赖关系。
要在OK按钮单击时实现对象的更改,可以使用对象克隆/分配,或者对象可以实现IEditableObject接口。
在某些微不足道的情况下,使用数据绑定可能会产生一些不必要的开销。
答案 0 :(得分:4)
之前我遇到过类似的要求,并且更喜欢使用数据绑定的两种变体。
在第一个变体中,我们克隆对象并绑定到克隆。当用户按下OK时,克隆的对象将替换为真实对象。这在一次编辑一个对象时非常有用,并且该对象不会被其他对象直接引用。如果它被引用,那么您可以将克隆中的值复制到原始值,以便引用保持不变。这样可以节省工作量,因为编辑器不需要额外的工作,最多必须在对象上定义2个方法,克隆和可能的Apply方法来复制克隆中的值。
我们绑定到原始对象的第二个变体,但是我们将原始值存储在编辑对话框中,可以是本地字段,也可以是临时数据对象。当用户按下OK时,不会发生任何特殊情况,但是当用户按下取消时,我们还原值。当您在对话框中仅编辑几个简单属性时,这非常有用。
我更喜欢数据绑定解决方案,因为它不会使用应用/取消逻辑污染编辑对话框,如果在编辑器中实现,则非常依赖于您的XAML:控件可以重命名,组合框可以替换为文本框等,所有这将影响手动从控件中分配数据的代码。
克隆/应用方法只需要在您的业务对象上,理论上它们比您的UI编辑器更稳定。即使XAML发生变化,在大多数情况下,您的绑定也可以保持不变。例如,从组合框到文本框的更改仅表示您绑定到Text而不是SelectedValue,但实际绑定是相同的。
答案 1 :(得分:4)
一种选择是让您的业务对象实现IEditableObject。
IEditableObject通常用于数据网格场景,但它适用于您所描述的情况。此外,如果您的UI发生变化以允许在DataGrid中进行内联编辑,则无需更改业务对象。
答案 2 :(得分:1)
您可以使用数据绑定对GUI进行单向更新,但如果您希望仅在按下“确定”后推迟更新业务模型,则最好在代码中执行此操作。在这种情况下,数据绑定可能是不必要的。
以FolderBrowserDialog
为例。在调用SelectedPath
之前,您可以将初始值设置为ShowDialog()
,但在处理数据之前等待对话框返回DialogResult.OK
。类似的方法应该适用于您的情况。
答案 3 :(得分:0)
在表单上加载处理程序转到Bindings集合并将每个DataSourceUpdateMode设置为Never。 在OK处理程序上设置为反向(DataSourceUpdateMode.OnValidation)并调用表单ValidateChildren
如果通过GUI完成绑定,则会有类BindingSource的新表单成员 它简化了CurrencyManager.Bindings可以访问的位绑定,并且可以使用BindingSource.EndEdit代替ValidateChildren。
答案 4 :(得分:0)
我最近遇到了同样的问题 - 我有弹出对话框表单来编辑对象属性。经过一些调整后,我最终得到了下面的代码,这似乎运作良好。希望它可以帮到某人。
我用它来显示弹出窗体
public static bool EditTask(MyTask task)
{
//make a backup object in case user clicks Cancel
MyTask backupTask = new MyTask();
CloneObject(task, backupTask);
//dialog form uses data binding to the backupTask object
MyTaskWindow f = new MyTaskWindow(backupTask);
f.ShowDialog();
if (f.DialogResult.HasValue && f.DialogResult.Value)
{
//user clicked "Ok" - clone everything back
CloneObject(backupTask, task);
return true;
}
else
{
return false;
}
}
//Reflection is used to clone object
//copied from http://www.c-sharpcorner.com/UploadFile/ff2f08/deep-copy-of-object-in-C-Sharp/
private static void CloneObject(object objSource, object objTarget)
{
//step : 1 Get the type of source object and create a new instance of that type
Type typeSource = objSource.GetType();
//object objTarget = Activator.CreateInstance(typeSource);
//Step2 : Get all the properties of source object type
PropertyInfo[] propertyInfo = typeSource.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
//Step : 3 Assign all source property to taget object 's properties
foreach (PropertyInfo property in propertyInfo)
{
//Check whether property can be written to
if (property.CanWrite)
{
//Step : 4 check whether property type is value type, enum or string type
if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(System.String)))
{
property.SetValue(objTarget, property.GetValue(objSource, null), null);
}
//else property type is object/complex types, so need to recursively call this method until the end of the tree is reached
else
{
object objPropertyValue = property.GetValue(objSource, null);
if (objPropertyValue == null)
{
property.SetValue(objTarget, null, null);
}
else
{
Type newTypeSource = objPropertyValue.GetType();
object newObjTarget = Activator.CreateInstance(newTypeSource);
CloneObject(objPropertyValue, newObjTarget);
property.SetValue(objTarget, newObjTarget, null);
}
}
}
}
}