带有许多控件的WPF UserControl - 如何创建映射到许多控件的依赖项属性?

时间:2011-01-31 04:02:10

标签: wpf xaml wpf-controls binding

我已经成功创建了一个带有Depedency属性的UserControl,允许我绑定到UserControl中的单个TextBox。但是,当我在UserControl中有许多控件并且只想绑定到单个属性(根据许多控件中的值构建)时,我不确定如何执行此操作?

UserControl有3个用于年,月和日期的文本框我想将它绑定到单个Date属性,到目前为止我已经得到了这个:

<UserControl x:Class="MyApp.DateControl"...>
<StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBox Name="textbox_year" />
        <TextBox Name="textbox_month" />
        <TextBox Name="textbox_day" />
    </StackPanel>
</StackPanel>
</UserControl>

我需要在后面的代码中添加什么才能使Date属性从三个文本框中获取,因此在使用我的控件的另一个容器中可以绑定到Date。我意识到,因为我的UserControl是目标,我必须创建一个依赖属性,但它看起来很复杂..

public partial class DateControl : UserControl
{
    public DateControl()
    {
        InitializeComponent();
    }

    public DateTime Date
    {
        get
        {
            DateTime dt;
            if (DateTime.TryParseExact(String.Format("{0}-{1}-{2}", this.textbox_year.Text, this.textbox_month.Text, this.textbox_day.Text), "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out dt))
                return dt;
            else
                return DateTime.MinValue;
        }
    }

4 个答案:

答案 0 :(得分:6)

我建议使用转换器来实现您想要的效果。

您的用户控件的XAML将如下所示:

<UserControl x:Class="MyDateControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:my="clr-namespace:MyDateControl"
             x:Name="root">
    <UserControl.Resources>
        <my:DatePartConverter x:Key="DatePartConverter"
                              Date="{Binding ElementName=root, Path=Date}"/>
    </UserControl.Resources>

    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBox Name="textbox_year" Text="{Binding ElementName=root, Path=Date, Converter={StaticResource DatePartConverter}, ConverterParameter=year, Mode=TwoWay}"/>
            <TextBox Name="textbox_month" Text="{Binding ElementName=root, Path=Date, Converter={StaticResource DatePartConverter}, ConverterParameter=month, Mode=TwoWay}" />
            <TextBox Name="textbox_day" Text="{Binding ElementName=root, Path=Date, Converter={StaticResource DatePartConverter}, ConverterParameter=day, Mode=TwoWay}" />
        </StackPanel>
    </StackPanel>
</UserControl>

在代码隐藏中,您将只拥有依赖属性:

public DateTime Date {
   get { return (DateTime)GetValue(DateProperty); }
   set { SetValue(DateProperty, value); }
}

public static readonly DependencyProperty DateProperty = 
   DependencyProperty.Register("Date", typeof(DateTime), typeof(MyDateControl), 
      new FrameworkPropertyMetadata(DateTime.Now, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

转换器看起来像这样:

public class DatePartConverter : Freezable, IValueConverter
{
    public DateTime Date {
        get { return (DateTime)GetValue(DateProperty); }
        set { SetValue(DateProperty, value); }
    }

    public static readonly DependencyProperty DateProperty =
        DependencyProperty.Register("Date", typeof(DateTime), typeof(DatePartConverter), new UIPropertyMetadata(DateTime.Now));


    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        DateTime date = (DateTime)value;
        string datePartType = (string)parameter;

        string result;

        switch (datePartType) {
            case "year":
                result = date.Year.ToString().PadLeft(4, '0');
                break;
            case "month":
                result = date.Month.ToString().PadLeft(2, '0');
                break;
            case "day":
                result = date.Day.ToString().PadLeft(2, '0');
                break;
            default:
                throw new InvalidOperationException("Unknown date part type (ConverterParameter)");
        }

        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        string datePartValue = (string)value;
        string datePartType = (string)parameter;

        DateTime result;

        switch (datePartType) {
            case "year":
                result = new DateTime(int.Parse(datePartValue), Date.Month, Date.Day);
                break;
            case "month":
                result = new DateTime(Date.Year, int.Parse(datePartValue), Date.Day);
                break;
            case "day":
                result = new DateTime(Date.Year, Date.Month, int.Parse(datePartValue));
                break;
            default:
                throw new InvalidOperationException("Unknown date part type (ConverterParameter)");
        }

        return result;
    }

    protected override Freezable CreateInstanceCore() {
        return new DatePartConverter();
    }
}

答案 1 :(得分:0)

这就是我个人会这样做的方式。通常,我会将成员移到一个单独的逻辑类中,并在setter中包含一些其他验证。

public partial class MainWindow : Window, INotifyPropertyChanged
{
  public MainWindow()
  {
     InitializeComponent();
     DataContext = this;
  }

  public DateTime Date
  {
     get
     {
        DateTime dt;
        if (DateTime.TryParseExact(String.Format("{0}-{1}-{2}", DateYear, DateMonth, DateDay), "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out dt))
           return dt;
        return DateTime.MinValue;
     }
  }

  private string year = "2011";
  public String DateYear
  {
     get { return year; }
     set { if (year == value) return; year = value; NotifyPropertyChanged("DateYear"); NotifyPropertyChanged("Date"); }
  }

  private string month = "11";
  public String DateMonth
  {
     get { return month; }
     set { if (month == value) return; month = value; NotifyPropertyChanged("DateMonth"); NotifyPropertyChanged("Date"); }
  }

  private string day = "11";
  public String DateDay
  {
     get { return day; }
     set { if (day == value) return; day = value; NotifyPropertyChanged("DateDay"); NotifyPropertyChanged("Date"); }
  }

  #region INotifyPropertyChanged
  public event PropertyChangedEventHandler PropertyChanged;
  private void NotifyPropertyChanged(string info)
  {
     if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(info));
  }
  #endregion
}

和xaml

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
  <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Label>Date:</Label>
    <Label Grid.Column="1" Content="{Binding Path=Date}" />
    <Label Grid.Row="1">Year</Label>
    <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=DateYear}" />
    <Label Grid.Row="2">Month</Label>
    <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=DateMonth}" />
    <Label Grid.Row="3">Day</Label>
    <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=DateDay}" />
  </Grid>
</Window>

答案 2 :(得分:0)

如果您提到的案例是针对DateTime的,那么您可以使用Masked TextBox。

WPF Masked Textbox with a value that does not contain mask

答案 3 :(得分:0)

您可以使用TextBoxes上的TextChanged事件来设置日期:

public partial class DateControl : UserControl
{
    public DateControl()
    {
        InitializeComponent();

        textbox_year.TextChanged += RecalculateDate;
        textbox_month.TextChanged += RecalculateDate;
        textbox_day.TextChanged += RecalculateDate;
    }

    private void RecalculateDate( object sender, TextChangedEventArgs e )
    {
        DateTime dt;
        if ( DateTime.TryParseExact( String.Format( "{0}-{1}-{2}", textbox_year.Text, textbox_month.Text, textbox_day.Text ), "yyyy-MM-dd", null, DateTimeStyles.None, out dt ) )
            SetValue( DateProperty, dt );
    }

    public static readonly DependencyProperty DateProperty =
        DependencyProperty.Register( "Date", typeof( DateTime ), typeof( DateControl ), new PropertyMetadata( DateTime.MinValue ) );

    public DateTime Date
    {
        get { return (DateTime)GetValue( DateProperty ); }
    }
}

XAML:

<UserControl x:Class="DateControlApp.DateControl"
         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" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBox Name="textbox_year" />
        <TextBox Name="textbox_month" />
        <TextBox Name="textbox_day" />
    </StackPanel>
</StackPanel>
</UserControl>

容器:

<StackPanel>
    <DateControlApp:DateControl x:Name="dateControl" />
    <TextBlock Text="{Binding ElementName=dateControl, Path=Date}" />
</StackPanel>

当然,这非常简单。其余部分留给读者练习:)