【问题标题】:Persisting data in a user control在用户控件中持久化数据
【发布时间】:2012-02-10 00:21:17
【问题描述】:

这是一个简单的日期时间控件,具有分钟和小时的附加功能。

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。

这样做的原因是,一旦您离开选项卡,您就会丢弃用户控件,而当您返回时,您会增加一个新的用户控件 (.Net 4)。

绑定到小时和分钟是丑陋的,并且对于消费者来说没有意义,因为它需要一个 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);
        }
    }
}

我想摆脱的有点无用的thurd

    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}" />

我喜欢吗?不,我真的不希望我可以放弃值转换器和显式绑定。但这将是改天的研究项目。

【问题讨论】:

    标签: c# .net wpf data-binding user-controls


    【解决方案1】:

    解决方案是 MVVM 模式。您的绑定数据必须与控件没有任何关系。即使您离开 Tab 并释放内容控件,数据 仍然存在。

    编辑

    我看到在 XAML 中您绑定到用户控件的属性。这根本是错误的。您需要使用来自 DataLayer 而不是来自控件的数据。创建一个类,用作这些属性的值持有者。

    【讨论】:

    • 属性是可绑定的,这就是它们的使用方式。所以有一个对象有一个 datetime 属性,该属性设置为 w/2 方式绑定。小时和分钟导致了问题。
    • @Bas Harmer:据我所知,它们是UserControl 的属性,所以如果控件出于任何原因处置,这些属性就会消失。
    • 是的,我没有说明如何使用控制;但是,如果您将属性设置为可绑定,则数据将保留在值中。我希望保持与日期选择器控件相同的模式,但有小时和分钟。
    • @Bas Hamer:但如果你有一个绑定到UserControl 的类,它应该保存有效数据。在这一点上,您的问题可能是您需要重新绑定数据,因此它将从 UI 上的数据层推送。
    • 数据在绑定的 DateTime 对象中,该对象在控制器上,没有问题。唯一的问题是小时和分钟的显示在导航之间重置为 00。我知道有一些解决方案,比如编写自己的 datetime 类,它具有小时和分钟的读/写属性,但如果可以的话,我希望它能够与 vanilla Datetime 一起工作,并将其作为最后的手段。跨度>
    【解决方案2】:

    您通过以下方式绑定 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);
    
            }
        }
    

    same for min.... 至少这是我之前使用 MVVM 所做的,但所有这些都包含在我的 ViewModel 中的数据对象中。

    【讨论】:

    • 是的,您有效地创建了一个自定义日期时间对象,而不是开箱即用的对象,将 Hour 和 Minute 的只读属性替换为读写属性。这会起作用,但我希望找到一种方法来使用香草日期时间对象。
    【解决方案3】:

    这取决于这是一个旨在供许多不同应用程序使用的通用 UserControl,还是一个旨在用于一种特定情况的一次性 UserControl。

    如果它是一个通用控件,为什么不将 Hours/Minutes 绑定到绑定的 Date 属性?为此,只需绑定到DateTimeRangeElement.Date.Hours(或Minutes)而不是DateTimeRangeElement.Hours。如果用户正在绑定单个日期数据对象,他们会期望当他们更改控件中的值时该对象的小时/分钟会得到更新。

    如果您不想这样做,则由用户决定是否要保持该值不重置,DataBind Hours/Minutes。这有点像将任何其他 UserControl 与 TabControl 一起使用 - CheckBox.IsCheckedListBox.SelectedItemExpander.IsExpanded 等都会丢失,除非它们绑定到某些东西。

    如果这是一次性用户控件,意在与您可以控制的特定视图和 TabControl 一起使用,那么请务必绑定小时/分钟。

    我过去使用的另一种替代方法是在选项卡卸载时覆盖 TabControl 以缓存 ContentPresenter,并在切换选项卡时使用缓存的 ContentPresenter 而不是重新加载选项卡项背部。如果你想要那个代码,它位于this answer另一个问题

    【讨论】:

    • 这会导致问题,因为 DateTime 没有实现 INotifyPropertyChanged,因此对日期的更改不会传播到小时。
    • @BasHamer DependencyProperty.Register 提供了一个重载,可以让您注册一个 PropertyChanged 方法,尽管您不需要它,因为您的 UI 将绑定到 Date 属性并且会在何时更新它会改变(您可以完全摆脱 Hours/Minutes 属性。如果人们关心它们,他们可以使用 Date.HoursDate.Minutes
    • 我只想编写一次;它已经被使用了几次。将绑定更新为 {Binding ElementName=myDateTimeControl, Path=Date.Hour, Mode=OneWay, StringFormat=00} 并取出小时和分钟属性,读取更改事件中提交的文本。该值在选项卡导航之间保持不变,但显示仍变为 00
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-21
    相关资源
    最近更新 更多