在用户控件中保留数据

时间:2012-01-13 20:10:38

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

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

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>

所以这是用例,你把这只小狗放在一个标签上,选择一个时间&amp;日期,导航离开选项卡并返回。如果您只对“日期”属性进行数据绑定而不是小时和分钟,而不是您发现两者都设置为0.

这样做的原因是,一旦您从选项卡中导航,就会丢弃用户控件,当您向后导航时,您需要创建一个新的用户控件(.Net 4)。

绑定到小时和分钟是非常困难的,并且对消费者没有意义,因为它需要DateTime对象。

我想知道在重新创建用户控件时重新加载小时和分钟的核心模式是什么。

这就是在应用程序中使用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);
        }
    }
}

我想要摆脱的有点无用的东西

    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}" />
我喜欢它吗?不,不是真的我希望我可以抛弃价值转换器和显式绑定。但那将是另一天的研究项目。

3 个答案:

答案 0 :(得分:2)

解决方案是MVVM模式。您的绑定数据不得与控件具有任何关系。即使您离开选项卡并放置内容控件,数据仍然存在。

修改

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

答案 1 :(得分:0)

你通过以下方式绑定int小时和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);

        }
    }

同样适用于min ....至少这是我之前使用MVVM所做的事情,但所有这些都包含在我的ViewModel中的数据对象中。

答案 2 :(得分:0)

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

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

如果您不想这样做,那么如果用户想要保持重置值,则由用户自行决定{Data = {1}} / Hours。这有点像使用带有TabControl的任何其他UserControl - MinutesCheckBox.IsCheckedListBox.SelectedItem等等,除非它们被绑定,否则它们都会丢失。

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

我过去使用的另一个替代方法是覆盖TabControl以在删除选项卡时缓存Expander.IsExpanded,并使用缓存的ContentPresenter而不是重新加载选项卡项当它被切换回来时。如果您想要代码,那么它位于另一个问题的this answer