将数据持久化到用户控件中
本文关键字:控件 用户 数据 持久化 | 更新日期: 2023-09-27 18:20:26
这是一个简单的日期时间控件,增加了分钟和小时的功能。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace foo.WizardElements
{
/// <summary>
/// Interaction logic for DateTimeRangeElement.xaml
/// </summary>
public partial class DateTimeRangeElement : UserControl
{
public DateTimeRangeElement()
{
InitializeComponent();
dp.DataContext = this;
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
Date = null;
Hours = 0;
Minutes = 0;
}
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date",
typeof(DateTime?),
typeof(DateTimeRangeElement));
public DateTime? Date
{
get { return (DateTime?)GetValue(DateProperty); }
set
{
SetValue(DateProperty, value);
}
}
public static readonly DependencyProperty HoursProperty = DependencyProperty.Register("Hours",
typeof(int),
typeof(DateTimeRangeElement));
public int Hours
{
get { return (int)GetValue(HoursProperty); }
set
{
SetValue(HoursProperty, value);
}
}
public static readonly DependencyProperty MinutesProperty = DependencyProperty.Register("Minutes",
typeof(int),
typeof(DateTimeRangeElement));
public int Minutes
{
get { return (int)GetValue(MinutesProperty); }
set
{
SetValue(MinutesProperty, value);
}
}
private void TimeUpdated()
{
if(Hours > 23)
Hours = 23;
if(Minutes > 59)
Minutes = 59;
if (Date.HasValue && (Date.Value.Hour != Hours || Date.Value.Minute != Minutes))
{
Date = new DateTime(Date.Value.Year, Date.Value.Month, Date.Value.Day, Hours, Minutes, 0);
}
if ((!Date.HasValue) && (Hours > 0 || Minutes > 0))
{
var now = DateTime.Now;
Date = new DateTime(now.Year, now.Month, now.Day, Hours, Minutes, 0);
}
}
private void Changed(object sender, object e)
{
TimeUpdated();
}
}
}
和xaml
<UserControl x:Class="foo.WizardElements.DateTimeRangeElement"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behavior="clr-namespace:foo.Behaviours"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="myDateTimeControl">
<Grid>
<StackPanel Orientation="Horizontal" Margin="2" Height="24">
<DatePicker x:Name="dp"
VerticalAlignment="top"
Foreground="LightGray"
SelectedDate="{Binding ElementName=myDateTimeControl,Path=Date, Mode=TwoWay}"
BorderBrush="{x:Null}" SelectedDateChanged="Changed"
>
<DatePicker.Background>
<SolidColorBrush Color="white" Opacity="0.2"/>
</DatePicker.Background>
</DatePicker>
<TextBox Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Hours, Mode=TwoWay, StringFormat=00}" TextChanged="Changed">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Label Content=":"/>
<TextBox Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Minutes, Mode=TwoWay, StringFormat=00}" TextChanged="Changed">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Grid HorizontalAlignment="Left" VerticalAlignment="top">
<Button
Background="Transparent"
Click="Clear_Click"
BorderThickness="0"
>
<Grid>
<Image Source="/foo;component/Images/Clear-left.png" Stretch="Uniform"/>
</Grid>
</Button>
</Grid>
</StackPanel>
</Grid>
</UserControl>
所以这里是一个用例,你把这个小狗放在一个标签上,选择一个时间&日期,导航离开选项卡并返回。如果你只数据绑定"日期"项目,而不是小时和分钟,你会发现这两个项目都设置为0。
这样做的原因是,一旦你离开一个选项卡,你就会处理掉用户控件l,当你导航回来时,你会创建一个新的用户控件l(.Net 4)。
绑定到Hours和minutes是很模糊的,对于消费者来说没有意义,因为它期望一个DateTime对象。
我正试图弄清楚当重新创建用户控件时,重新加载小时和分钟的核心模式是什么。
这就是用户控件在应用程序中的使用方式
<we:DateTimeRangeElement Date="{Binding Path=Filter.StartTime, Mode=TwoWay}" />
编辑:我有一个解决方案,我不喜欢,但它会一直做下去,直到我能把胶水弄掉。我所做的是创建自己的datetime对象,并利用它可以获得一个更可绑定的对象。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using ToolSuite.Contract.BaseClasses;
using System.Globalization;
namespace foo.WizardElements
{
/// <summary>
/// Interaction logic for DateTimeRangeElement.xaml
/// </summary>
public partial class DateTimeRangeElement : UserControl
{
public DateTimeRangeElement()
{
InitializeComponent();
this.GotFocus += DateTimeRangeElement_GotFocus;
}
void DateTimeRangeElement_GotFocus(object sender, RoutedEventArgs e)
{
if(Date!=null)
Date.PropertyChanged += Date_PropertyChanged;
}
void Date_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.GetBindingExpression(DateProperty).UpdateSource();
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
Date = null;
}
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date",
typeof(FriendlyDateTime),
typeof(DateTimeRangeElement));
public FriendlyDateTime Date
{
get { return (FriendlyDateTime)GetValue(DateProperty); }
set
{
SetValue(DateProperty, value);
}
}
}
}
xaml
<UserControl x:Class="foo.WizardElements.DateTimeRangeElement"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behavior="clr-namespace:foo.Behaviours"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="myDateTimeControl">
<Grid>
<StackPanel Orientation="Horizontal" Margin="2" Height="24">
<DatePicker x:Name="dp"
VerticalAlignment="top"
Foreground="LightGray"
SelectedDate="{Binding ElementName=myDateTimeControl,Path=Date.Date, Mode=TwoWay}"
BorderBrush="{x:Null}"
>
<DatePicker.Background>
<SolidColorBrush Color="white" Opacity="0.2"/>
</DatePicker.Background>
</DatePicker>
<TextBox x:Name="Hours" Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Date.Hour, Mode=TwoWay, StringFormat=00}">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Label Content=":"/>
<TextBox x:Name="Minutes" Width="30" Text="{Binding ElementName=myDateTimeControl, Path=Date.Minute, Mode=TwoWay, StringFormat=00}">
<i:Interaction.Behaviors>
<behavior:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]*$" MaxLength="3" />
</i:Interaction.Behaviors>
</TextBox>
<Grid HorizontalAlignment="Left" VerticalAlignment="top">
<Button
Background="Transparent"
Click="Clear_Click"
BorderThickness="0"
>
<Grid>
<Image Source="/foo;component/Images/Clear-left.png" Stretch="Uniform"/>
</Grid>
</Button>
</Grid>
</StackPanel>
</Grid>
</UserControl>
辅助
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ToolSuite.Contract.BaseClasses;
namespace foo.WizardElements
{
public class FriendlyDateTime : NotifyPropertyChangedBase
{
public FriendlyDateTime()
{
}
public FriendlyDateTime(DateTime? value)
{
Date = value;
}
public DateTime? Date
{
get
{
if (base._values.ContainsKey("Date"))
return Get<DateTime>("Date");
else
return null;
}
set
{
if (value == null)
{
if (base._values.ContainsKey("Date"))
base._values.Remove("Date");
}
else
Set<DateTime>("Date", value.Value);
base.NotifyPropertyChanged("Date");
base.NotifyPropertyChanged("Hour");
base.NotifyPropertyChanged("Minute");
}
}
public int Hour
{
get { return Date.HasValue ? Date.Value.Hour : 0; }
set
{
if (Hour > 23)
Hour = 23;
var d = Date.HasValue ? Date.Value : DateTime.Now;
Date = new DateTime(d.Year, d.Month, d.Day, value, Minute, 0);
}
}
public int Minute
{
get { return Date.HasValue ? Date.Value.Minute : 0; }
set
{
if (Minute > 59)
Minute = 59;
var d = Date.HasValue ? Date.Value : DateTime.Now;
Date = new DateTime(d.Year, d.Month, d.Day, Hour, value, 0);
}
}
static public implicit operator DateTime?(FriendlyDateTime value)
{
return value.Date;
}
static public implicit operator FriendlyDateTime(DateTime? value)
{
// Note that because RomanNumeral is declared as a struct,
// calling new on the struct merely calls the constructor
// rather than allocating an object on the heap:
return new FriendlyDateTime(value);
}
}
}
我想摆脱的有点无用的想法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Globalization;
using ToolSuite.Contract.BaseClasses;
namespace foo.WizardElements
{
public class FriendlyDateTimeValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(DateTime?))
{
return ((FriendlyDateTime)value).Date;
}
else if (targetType == typeof(FriendlyDateTime))
{
return new FriendlyDateTime(value as DateTime?);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(DateTime?))
{
return ((FriendlyDateTime)value).Date;
}
else if (targetType == typeof(FriendlyDateTime))
{
return new FriendlyDateTime(value as DateTime?);
}
return null;
}
}
}
最后是的使用方式
<we:DateTimeRangeElement Date="{Binding Path=Filter.EndTime, Mode=TwoWay, Converter={StaticResource DateTimeConverter}, UpdateSourceTrigger=Explicit}" />
我喜欢它吗?不,我真的不希望放弃值转换器和显式绑定。但这将是另一天的研究项目。
解决方案是MVVM模式。绑定的数据不必与控件有任何关系。即使您导航离开选项卡并释放内容控件,数据也会保留。
编辑
我看到在XAML中,您绑定到用户控件的属性。这根本是错误的。您需要使用DataLayer中的数据,而不是控件中的数据。创建一个类,该类用作这些属性的值持有者。
您通过以下方式绑定int hour和int min:
public DateTime StartTime
{
get { return startdate; }
set
{
startdate = new DateTime(value.Year, value.Month, value.Day, startdate.Hour, startdate.Minute, 0);
RaisePropertyChanged("StartTime");
RaisePropertyChanged("StartHour");
RaisePropertyChanged("StartMinute");
}
}
public int StartHour
{
get { return StartTime.Hour; }
set
{
startdate = new DateTime(startdate.Year, startdate.Month, startdate.Day, value, startdate.Minute, 0);
}
}
至少这是我以前使用MVVM所做的,但所有这些都包含在我的ViewModel中的数据对象中。
这取决于这是用于许多不同应用程序的通用UserControl,还是用于一个特定情况的一次性UserControl。
如果是通用控件,为什么不将Hours/Minutes绑定到绑定的Date
属性?要做到这一点,只需绑定到DateTimeRangeElement.Date.Hours
(或Minutes
),而不是DateTimeRangeElement.Hours
。如果用户正在绑定一个日期数据对象,那么他们希望在更改控件中的值时,该对象的小时/分钟会得到更新。
如果您不想这样做,那么如果用户希望防止值重置,则由他们决定DataBind Hours
/Minutes
。这有点像将任何其他UserControl与TabControl一起使用——CheckBox.IsChecked
、ListBox.SelectedItem
、Expander.IsExpanded
等都会丢失,除非它们与某个东西绑定。
如果这是一次性的UserControl,旨在与您可以控制的特定视图和TabControl一起使用,那么请确保绑定小时/分钟。
我过去使用的另一种选择是在卸载选项卡时覆盖TabControl以缓存ContentPresenter
,并在切换回选项卡时使用缓存的ContentPresenter
而不是重新加载选项卡项。如果你想要代码,它位于另一个问题的答案