2011-01-31 3 views
3

Я успешно создал UserControl с свойством Depedency, позволяющим мне привязываться к одному TextBox внутри моего UserControl. Однако я не знаю, как это сделать, когда у меня есть много элементов управления в моем UserControl и вы хотите привязываться только к одному свойству (построенному из значений во многих элементах управления)?WPF UserControl со многими элементами управления - как создать свойство зависимостей, которое сопоставляется ко многим элементам управления?

UserControl имеет 3 текстовые поля для года, месяца и даты, я хочу, чтобы связать это с одной дате собственности, до сих пор я получил это:

<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 является целью я должен сделать Dependency Property, но это кажется настолько сложным ..

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; 
     } 
    } 

ответ

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(); 
    } 
} 
+0

Хмм, я так не думаю, что использование конвертера выглядит намного больше - я не думаю, что необходимо создать 3 дополнительных привязки, чтобы получить одну дату esp. при привязках 90% времени не смогут сделать дату - см. мой ответ .. – markmnl

+0

Эти привязки не являются «лишними». В вашем коде вы выполняете эти привязки вручную в коде ... Хотя в моем случае вся логика преобразования инкапсулирована в конвертер, что упрощает логику управления основным пользователем. И эти привязки не сбой, если вы поместите правильную схему преобразования в конвертер. –

+0

Основная логика управления пользователем проще без привязок, я думаю. Эти привязки будут без необходимости запускать ваш конвертер каждый раз при нажатии клавиши. Неважно, это просто не то, что им нужно. – markmnl

0

Это, как я лично подходил что-то вроде этого. Обычно я бы переместил участников в отдельный логический класс и включил некоторые другие проверки в сеттеры.

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> 
+0

Извините, но то, что вы показали, не является UserControl, которое я хочу повторно использовать в другом месте и привязать свойство Date элемента управления к моей ViewModel. Как и привязка TextBox Text propperty к их ViewModel .. – markmnl

+0

ahhh, следовало бы прочитать более внимательно. Должно быть довольно простое преобразование, к сожалению, я не могу смотреть на него снова и снова. – Thomas

+0

не беспокоится, это не конверсия. Я хочу либо – markmnl

0

Если дело вы упомянули для DateTime, то вы можете пойти на маскарад TextBox.

WPF Masked Textbox with a value that does not contain mask

+0

, это не то, что я хочу, так как имеет только один элемент управления, такой как один TextBox, в то время как у меня есть много forwhich, который нельзя использовать IValueConverter, который я уже очень хорошо знаком с .. – markmnl

0

Вы можете использовать события TextChanged в TextBoxes для установки даты:

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> 

Это очень упрощенно, конечно. Остальное оставлено как упражнение для читателя :)

Смежные вопросы