2012-01-13 87 views
0

这是一个简单的日期时间控件,它具有分钟和小时的附加功能。在用户控件中保留数据

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对象。

我想弄清楚什么corect模式将用于重新创建usercontrol时重新加载小时和分钟。

这是用户控件是如何在应用程序中使用

<we:DateTimeRangeElement Date="{Binding Path=Filter.StartTime, Mode=TwoWay}" /> 

编辑: 我有一个解决方案,我不喜欢,但它会做,直到我能得到胶的出路。我所做的是创建我自己的日期时间对象,并使用我能够获得更多可绑定对象。

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

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

回答

2

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

编辑

我看到,在XAML绑定到用户控件的属性。这基本上是错误的。您需要从DataLayer而不是控件中获取数据。创建一个类似于这些属性的值持有者的类。

+0

属性是可绑定的,这就是他们如何使用。所以有一个对象具有设置为2路绑定的日期时间属性。小时和分钟导致问题。 – 2012-01-13 20:47:55

+0

@Bas Harmer:尽我所见,他们是'UserControl'的属性,所以如果控制因任何原因而处置,属性就消失了。 – Tigran 2012-01-13 20:49:48

+0

是的我没有显示如何使用conrol;但是如果你使用可绑定的属性而不是数据中存在的数据值。我希望保持与日期选择器控件相同的模式,但需要数小时和分钟。 – 2012-01-13 20:54:45

0

您可以通过绑定INT小时和INT分:

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的数据对象中。

+0

是的,你有效地创建了一个自定义的日期时间对象rahter比开箱即用,交换读取属性的小时和分钟的只读属性。这将工作,但我希望能找到一种方法来做到这一点与香草日期时间对象。 – 2012-01-13 21:24:38

0

这取决于这是一个通用的UserControl,是否被许多不同的应用程序使用,或者一次性的UserControl意味着在一个特定的情况下使用。

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

如果你不想这样做,那么如果他们想要保持重置值不变,那么它可以由用户提供给DataBind Hours/Minutes。这就像使用任何其他用户控件与TabControl - CheckBox.IsChecked,ListBox.SelectedItem,Expander.IsExpanded等都会迷路,除非它们被绑定到某个东西。

如果这是一次性用户控件,意味着要与您控制的特定视图和TabControl一起使用,那么只需确保绑定小时/分钟。

我已经在过去使用的另一种方法是覆盖的TabControl缓存ContentPresenter当标签被卸载,并且当它被切换回使用缓存ContentPresenter不是重新加载标签项目。如果你想要的代码,它位于this answer到另一个问题

+0

这会导致问题,因为DateTime没有实现INotifyPropertyChanged,所以对日期的更改不会传播到小时。 – 2012-01-13 20:56:02

+0

@BasHamer'DependencyProperty.Register'提供了一个重载,它允许你注册一个'PropertyChanged'方法,尽管你不需要它,因为你的UI将被绑定到'Date'属性,并且会在更改时更新(你可以完全摆脱小时/分钟属性,如果人们关心他们,他们可以使用Date.Hours或Date.Minutes) – Rachel 2012-01-13 21:05:56

+0

我想只编码一次;它已经被使用了几次。 更新了绑定到{Binding ElementName = myDateTimeControl,Path = Date.Hour,Mode = OneWay,StringFormat = 00}的绑定,并提取了小时和分钟proeprty,读取已更改事件中提交的文本。该值在标签导航之间保持不变,但显示仍然变为00 – 2012-01-13 21:19:06