绑定 ViewModel 属性 的值更新失败
Value Update of Bound ViewModel Property Fails
我正在尝试将自定义控件 属性 绑定到其视图模型的 属性 并失败。
我已经为设置 StartDate 定义了依赖项 属性 并更新了 PropertyChangeCallback 方法
public static readonly DependencyProperty StartDateProperty =
DependencyProperty.Register(StartDatePropertyName,
typeof(DateTime),
typeof(CustomDateTimeControl),
new PropertyMetadata(DateTime.Now.AddYears(-7),
OnStartDatePropertyChanged));
private static void OnStartDatePropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
DateTime dtNewValue = (DateTime)e.NewValue;
if (dtNewValue != DateTime.MinValue)
{
DateTimeControl dtCtrl = d as DateTimeControl;
dtCtrl.StartDate = (DateTime)e.NewValue;
dtCtrl.CoerceValue(StartDateProperty);
}
}
StartDate
属性 绑定到其 ViewModel 的开始日期,因为 VM 需要执行一些操作,然后用于定义自定义控件的下一个可用视图。
<Setter Property="StartDate"
Value="{Binding StartDate,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged}" />
其中定义的 DependencyProperty 也是从主窗口视图设置的
<CustomDateTimeLib:CustomDateTimeControl StartDate="01/01/2000 00:00:00" />
绑定仅使用依赖项的默认值 属性 更新视图模型中的 属性,但不使用上面在 MainView 中设置的值,即使依赖项 属性 正在使用 MainView 中的值进行更新。
ViewModelLocatorclass
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<CalendarViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public CustomDateTimeLib:CustomDateTimeControl CalendarVM
{
get
{
return ServiceLocator.Current.GetInstance<CustomDateTimeControl>();
}
}
}
App.Xaml
<Application x:Class="MvvmCustomTestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CustomDateTimeLib.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.galasoft.ch/ignore"
StartupUri="MainWindow.xaml"
mc:Ignorable="d ignore">
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>
CustomDateTimeControl 中的 ApplyTemplate 覆盖方法class
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CalendarViewModel vm = (CalendarViewModel)this.DataContext;
vm.StartDate = this.StartDate;
}
还为 StartDate
定义了一个 属性 更改回调方法
如果我理解正确的话,您有一个自定义 DP,您想要绑定到 ViewModel 属性,但是您还想从视图中为此 属性 设置默认值。
这不是 MVVM 或您使用绑定时的理想设置。 MVVM应该有所有的逻辑,包括像"default value for X"这样的东西在ViewModel层,而View层只是用来为用户提供与ViewModel(数据)层交互的可视化方式。
所以你的解决方案是:
- 在您的 ViewModel 中设置默认值
- 在您的依赖项中提供处理 属性 如果值 == DateTime.Min,请使用不同的默认值
- 如果您真的想让视图提供默认值,请使用转换器(
DateTime
)或 FallbackValue(DateTime?
)
- 使用第二个 DP 来定义应该使用的默认值
- 向控件添加 Loaded 事件处理程序以读取 DataContext 并设置默认值
如果您使用 MVVM,选项 1 是最佳解决方案,因为自定义默认值之类的东西应该在 ViewModel 而不是 View 中设置。
如果此默认值特定于此 UserControl,则选项 2 是最佳选择,并且在任何时候使用此控件时都是相同的。
选项 3、4 和 5 适用于无论出于何种原因您确实坚持从 View 层设置默认值的情况。具体用哪一个要看你的情况。
假设您使用 #1,我希望您的最终 XAML 看起来像这样:
<!-- assumes DataContext is of type DateTimeCtrlVM via inheritance or direct binding -->
<CustomDateTimeLib:CustomDateTimeControl StartDate="{Binding StartDate}" />
就是这样。
上面 XAML 代码中的 <Setter>
实际上导致了以下情况的发生:
- 创建的控件默认值为 1/1/2000
- 属性绑定
OneWayToSource
到VM属性,也就是说数据只会从Target(Control)流向Source(ViewModel),所以1/1/的值2000 正在持久化到您的 VM
因此摆脱将 属性 绑定为 OneWayToSource 的 Setter,使用上面显示的 XAML 绑定,并在 ViewModel 中设置默认值,它应该可以工作。
我正在尝试将自定义控件 属性 绑定到其视图模型的 属性 并失败。
我已经为设置 StartDate 定义了依赖项 属性 并更新了 PropertyChangeCallback 方法
public static readonly DependencyProperty StartDateProperty =
DependencyProperty.Register(StartDatePropertyName,
typeof(DateTime),
typeof(CustomDateTimeControl),
new PropertyMetadata(DateTime.Now.AddYears(-7),
OnStartDatePropertyChanged));
private static void OnStartDatePropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
DateTime dtNewValue = (DateTime)e.NewValue;
if (dtNewValue != DateTime.MinValue)
{
DateTimeControl dtCtrl = d as DateTimeControl;
dtCtrl.StartDate = (DateTime)e.NewValue;
dtCtrl.CoerceValue(StartDateProperty);
}
}
StartDate
属性 绑定到其 ViewModel 的开始日期,因为 VM 需要执行一些操作,然后用于定义自定义控件的下一个可用视图。
<Setter Property="StartDate"
Value="{Binding StartDate,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged}" />
其中定义的 DependencyProperty 也是从主窗口视图设置的
<CustomDateTimeLib:CustomDateTimeControl StartDate="01/01/2000 00:00:00" />
绑定仅使用依赖项的默认值 属性 更新视图模型中的 属性,但不使用上面在 MainView 中设置的值,即使依赖项 属性 正在使用 MainView 中的值进行更新。
ViewModelLocatorclass
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<CalendarViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public CustomDateTimeLib:CustomDateTimeControl CalendarVM
{
get
{
return ServiceLocator.Current.GetInstance<CustomDateTimeControl>();
}
}
}
App.Xaml
<Application x:Class="MvvmCustomTestApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CustomDateTimeLib.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.galasoft.ch/ignore"
StartupUri="MainWindow.xaml"
mc:Ignorable="d ignore">
<Application.Resources>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>
CustomDateTimeControl 中的 ApplyTemplate 覆盖方法class
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CalendarViewModel vm = (CalendarViewModel)this.DataContext;
vm.StartDate = this.StartDate;
}
还为 StartDate
定义了一个 属性 更改回调方法如果我理解正确的话,您有一个自定义 DP,您想要绑定到 ViewModel 属性,但是您还想从视图中为此 属性 设置默认值。
这不是 MVVM 或您使用绑定时的理想设置。 MVVM应该有所有的逻辑,包括像"default value for X"这样的东西在ViewModel层,而View层只是用来为用户提供与ViewModel(数据)层交互的可视化方式。
所以你的解决方案是:
- 在您的 ViewModel 中设置默认值
- 在您的依赖项中提供处理 属性 如果值 == DateTime.Min,请使用不同的默认值
- 如果您真的想让视图提供默认值,请使用转换器(
DateTime
)或 FallbackValue(DateTime?
) - 使用第二个 DP 来定义应该使用的默认值
- 向控件添加 Loaded 事件处理程序以读取 DataContext 并设置默认值
如果您使用 MVVM,选项 1 是最佳解决方案,因为自定义默认值之类的东西应该在 ViewModel 而不是 View 中设置。
如果此默认值特定于此 UserControl,则选项 2 是最佳选择,并且在任何时候使用此控件时都是相同的。
选项 3、4 和 5 适用于无论出于何种原因您确实坚持从 View 层设置默认值的情况。具体用哪一个要看你的情况。
假设您使用 #1,我希望您的最终 XAML 看起来像这样:
<!-- assumes DataContext is of type DateTimeCtrlVM via inheritance or direct binding -->
<CustomDateTimeLib:CustomDateTimeControl StartDate="{Binding StartDate}" />
就是这样。
上面 XAML 代码中的 <Setter>
实际上导致了以下情况的发生:
- 创建的控件默认值为 1/1/2000
- 属性绑定
OneWayToSource
到VM属性,也就是说数据只会从Target(Control)流向Source(ViewModel),所以1/1/的值2000 正在持久化到您的 VM
因此摆脱将 属性 绑定为 OneWayToSource 的 Setter,使用上面显示的 XAML 绑定,并在 ViewModel 中设置默认值,它应该可以工作。