将数据持久化到用户控件中

本文关键字:控件 用户 数据 持久化 | 更新日期: 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.IsCheckedListBox.SelectedItemExpander.IsExpanded等都会丢失,除非它们与某个东西绑定。

如果这是一次性的UserControl,旨在与您可以控制的特定视图和TabControl一起使用,那么请确保绑定小时/分钟。

我过去使用的另一种选择是在卸载选项卡时覆盖TabControl以缓存ContentPresenter,并在切换回选项卡时使用缓存的ContentPresenter而不是重新加载选项卡项。如果你想要代码,它位于另一个问题的答案