Reputation: 11426
I have successfully created a UserControl with a Depedency property allowing me to bind to a single TextBox within my UserControl. However Im unsure how to go about doing this when I have many Controls within my UserControl and only want to bind to single Property (built from the values in the many controls)?
The UserControl has 3 textboxes for year, month and date I want to bind this to a single Date Property, so far I have got this:
<UserControl x:Class="MyApp.DateControl"...>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBox Name="textbox_year" />
<TextBox Name="textbox_month" />
<TextBox Name="textbox_day" />
</StackPanel>
</StackPanel>
</UserControl>
What do I need to add to the code behind to make the Date Property got from the three textboxes so in another container using my control can just bind to Date. I realise since my UserControl is the target I have to make a Dependency Property but it seems so complicated..
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;
}
}
Upvotes: 3
Views: 2534
Reputation: 1401
You can use the TextChanged events on the TextBoxes to set the date:
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>
And the container:
<StackPanel>
<DateControlApp:DateControl x:Name="dateControl" />
<TextBlock Text="{Binding ElementName=dateControl, Path=Date}" />
</StackPanel>
It's very simplistic of course. The rest is left as an exercise for the reader :)
Upvotes: 0
Reputation: 20756
I suggest using a converter to achieve what you want.
Your user control's XAML will look like this:
<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>
In code-behind you will have only you dependency property:
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));
And the converter will look something like this:
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();
}
}
Upvotes: 6
Reputation: 8773
If the case you mentioned is for DateTime, then you can go for Masked TextBox.
WPF Masked Textbox with a value that does not contain mask
Upvotes: 0
Reputation: 3358
This is how I personally would approach something like this. Normally, I would move the members out into a separate logic class and include some other validation in the setters.
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
}
And the 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>
Upvotes: 0