如何在 UserControl 中扩展模型?
Posted
技术标签:
【中文标题】如何在 UserControl 中扩展模型?【英文标题】:How do I extend a model in a UserControl? 【发布时间】:2021-01-16 02:52:36 【问题描述】:我想了解如何在 WPF 中编写适当的用户控件/视图模型。为了简单起见,我发明了以下示例:
说,我们有一个DateRange
类,定义为:
using System;
namespace MyDateApp
public class DateRange
public DateTime Start
get;
set;
= new DateTime();
public int Length // in days
get;
set;
= 0;
(为了论证,我们假设这个类不能以任何方式修改。)
类的一个实例被用作我们窗口的数据上下文:
using System;
using System.Windows;
namespace MyDateApp
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
DateRange range = new DateRange();
range.Start = new DateTime(2020, 1, 1);
range.Length = 5;
DataContext = range;
我想实现一个自定义视图/控件,它允许应用程序的用户选择开始和结束日期,而不是开始日期和范围。它将被包裹在一个名为DateControl
的UserControl
中:
<Window x:Class="MyDateApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyDateApp">
<StackPanel>
<local:DateControl Range="Binding" />
</StackPanel>
</Window>
我能够得到这个DateControl
工作的基本实现:
using System;
using System.Windows;
using System.Windows.Controls;
namespace MyDateApp
public partial class DateControl : UserControl
public DateControl()
InitializeComponent();
public DateRange Range
get return (DateRange)GetValue(RangeProperty);
set SetValue(RangeProperty, value);
public static readonly DependencyProperty RangeProperty =
DependencyProperty.Register("Range", typeof(DateRange), typeof(DateControl), new PropertyMetadata(new DateRange()));
public DateTime End
get => Range.Start + new TimeSpan(Range.Length, 0, 0, 0);
set => Range.Length = (value - Range.Start).Days;
使用以下 XAML:
<UserControl x:Class="MyDateApp.DateControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyDateApp"
Name="UserControl">
<StackPanel Orientation="Horizontal">
<TextBlock Text="From:" />
<DatePicker SelectedDate="Binding Range.Start, ElementName=UserControl" />
<TextBlock Text="To:" />
<DatePicker SelectedDate="Binding End, ElementName=UserControl" />
</StackPanel>
</UserControl>
它非常适合开始日期,但是结束日期的值是错误的。因此,我的问题:
我怎样才能正确地实现这个?
我的成功条件是:
原始模型类必须保持不变, 并且用户控件必须更新主窗口数据上下文。奖励: 我必须实现什么才能将此控件用作ListBoxItem
?
【问题讨论】:
DataContext 会自动传递给您的 UserControl。不需要依赖属性。因此,您可以简单地绑定到 ViewModel 中的属性 Start 和 End 回答您的条件:“原始模型类必须保持不变”,然后可能从模型继承。保持 UI 尽可能的愚蠢。否则你总是依赖你的 UI 来处理业务逻辑。第二:这不是你的模型,而是你的视图模型。 @Klamsi “DataContext 会自动传递给您的 UserControl。”是什么意思?我可以在不绑定主窗口的情况下执行<DatePicker SelectedDate="Binding Range.Start, ElementName=UserControl" />
吗?
@Klamsi 我想分离会很好,但我的业务逻辑必须存在于某个地方......我不确定如何执行你的想法。
【参考方案1】:
创建一个位于视图和模型之间的视图模型,并将视图中的控件绑定到视图模型,而不是直接绑定到视图,例如:
public class ViewModel
//error handling and validation omitted for brevity...
private readonly DateRange _model = new DateRange();
private DateTime _startDate;
public DateTime StartDate
get
return _model.Start;
set
_model.Start = value;
private DateTime _endDate;
public DateTime EndDate
get return _endDate;
set
_endDate = value;
_model.Length = (int)_endDate.Date.Subtract(_startDate.Date).TotalDays;
【讨论】:
到目前为止这是有道理的,但是我如何将这个视图模型与其余代码一起插入以使其工作?特别是,XAML 应该是什么样子? XAML 中的控件只能绑定到视图模型属性。或者将控件属性绑定到视图模型属性,将控件绑定到控件属性:<local:DateControl Start="Binding StartDate" ... />
是的,但我必须在某个地方从我的模型转换为我的视图模型。我在哪里做呢?
视图模型具有对模型实例的引用并处理“转换”。视图不知道模型。
啊。是的。你说的对!但是,这不适用于 List<DateRange>
列表,因为数据是“绑定”在视图模型中的?【参考方案2】:
将模型直接暴露给视图通常是个坏主意。
这包括“包装”模型属性的方法。
这适用于琐碎的应用程序,但您会发现许多边缘情况使其没有吸引力。 例如,您的用户编辑模型类。 这验证失败。 现在,您的模型中有错误数据。 它也相当笨重。
我建议将您的模型类视为 DTO。
构建具有相应属性的视图模型以及您将不可避免地需要的额外属性。
实例化该视图模型并从模型中复制数据以将其呈现给视图。
数据库>模型>视图模型>视图
在编辑/插入时执行相反的操作并实例化模型以传回您的存储库或实体框架。
视图 > 视图模型 > 模型 > 数据库
这样,您的数据注释可以愉快地存在于视图模型中,而不会污染模型。如果您使用的是实体框架等 ORM,这将特别方便。
对于特别复杂的场景,您甚至可能需要另一层用于特定领域逻辑的类。
要直接复制属性,请考虑使用 automapper。
https://automapper.org/
编辑:
使用这种方法,视图模型与模型完全断开,因此视图不会改变它。从而满足要求1。
此视图模型应由主窗口视图模型实例化为私有成员。主窗口的公共属性将被设置为一个实例。对于列表,属性将是这些视图模型的列表或 observablecollection。
我会考虑删除用户控件中的依赖属性,而是将逻辑放入新的视图模型中。
我不遵循这个逻辑到底是什么,但我希望你不会对你设计的每个视图模型提出问题,这里的任何答案都应该表明这个原则。
因此视图模型可能看起来像:
public class DateRangeViewModel : BindableBase
public DateRangeViewModel(DateTime _datefrom, int _length)
length = _length;
fromDate = _datefrom;
setUpDate();
private void setUpDate()
ToDate = ((DateTime)fromDate).AddDays(length);
private int length = 0;
public int Length
get => length;
set setUpDate(); SetProperty(ref length, value, nameof(Length));
private DateTime? fromDate;
public DateTime? FromDate
get => fromDate;
set => SetProperty(ref fromDate, value, nameof(FromDate));
private DateTime? toDate;
public DateTime? ToDate
get => toDate;
set setUpDate(); SetProperty(ref fromDate, value, nameof(ToDate));
您的 windowviewmodel 会读取您的数据,这些数据来自您将其作为模型的任何地方。
为您新建一个 DateRangeViewModel,将 fromdate 和 length 属性传递给 ctor。
【讨论】:
我遇到了你之前所说的基本要点。但是,我的问题是,虽然我可能在概念层面上理解它,但我无法将其放入代码中。视频创作者和博客作者都忽略了所有这些小的实现细节。这些实际上可能是微不足道的,但之前没有看到它们实现,我无法真正理解这些概念。因此,请把你的文章写成能解决我的特定例子的代码。 迄今为止我发现的最好的教程来自“Angelsix”(youtube)。他制作了一系列关于 WPF UI 编程的文章。以上是关于如何在 UserControl 中扩展模型?的主要内容,如果未能解决你的问题,请参考以下文章
使用 MVVM 在 MainWindow 上绑定 UserControl 视图模型
如何从作为wpf mvvm模式中的窗口打开的视图模型中关闭用户控件?
我可以编写一个 .NETCF 部分类来扩展 System.Windows.Forms.UserControl 吗?