Как связать DateTime как DepedencyProperty объекта DependencyObject, чтобы он отображался в текстовом поле?

Я хочу научиться использовать объекты и свойства зависимостей. Я создал этот класс,

    public class TestDependency : DependencyObject
    {
        public static readonly DependencyProperty TestDateTimeProperty =
            DependencyProperty.Register("TestDateTime", 
            typeof(DateTime), 
            typeof(TestDependency), 
            new PropertyMetadata(DateTime.Now));

        public DateTime TestDateTime
        {
            get { return (DateTime) GetValue(TestDateTimeProperty); }
            set { SetValue(TestDateTimeProperty, value); }
        }
    }

Класс окна такой

public partial class MainWindow : Window
{
    private TestDependency td;
    public MainWindow()
    {
        InitializeComponent();
        td = new TestDependency();
        td.TestDateTime = DateTime.Now;
    }
}

Теперь я хочу использовать его для отображения текущего DateTime в TextBlock, который обновляется каждую секунду, добавляя это в сетку

<Grid>
    <TextBlock Text="{Binding TestDateTime,ElementName=td}" Width="200" Height="200"/>
</Grid>

Я вижу TextBlock, но в нем вообще нет значения Date Time. Что я делаю неправильно?


person black eyed pea    schedule 02.02.2013    source источник


Ответы (2)


Прежде всего, если вы хотите обновлять время отображения раз в секунду, вам понадобится таймер для запуска обновления. Для этого хорошо работает DispatchTimer.

public class TestDependency : DependencyObject
{
    public static readonly DependencyProperty TestDateTimeProperty =
        DependencyProperty.Register("TestDateTime", typeof(DateTime), typeof(TestDependency),
        new PropertyMetadata(DateTime.Now));

    DispatcherTimer timer;

    public TestDependency()
    {
        timer = new DispatcherTimer(new TimeSpan(0,0,1), DispatcherPriority.DataBind, new EventHandler(Callback), Application.Current.Dispatcher);
        timer.Start();

    }

    public DateTime TestDateTime
    {
        get { return (DateTime)GetValue(TestDateTimeProperty); }
        set { SetValue(TestDateTimeProperty, value); }
    }

    private void Callback(object ignore, EventArgs ex)
    {
        TestDateTime = DateTime.Now;
    }

}

Далее нам нужно изменить XAML, чтобы он правильно привязывался к обновленному объекту зависимостей.

<Window.DataContext>
    <local:TestDependency/>
</Window.DataContext>
<Grid>
    <TextBlock Text="{Binding TestDateTime}" />
</Grid>

Поскольку мы устанавливаем DataContext в XAML, вы фактически можете удалить весь код позади кода в конструкторе MainWindow.

person blaise    schedule 02.02.2013
comment
Большое спасибо, это работает очень хорошо. В качестве отступления могу ли я задать вам еще один вопрос, почему нам не нужно передавать имя переменной td? Я имею в виду, что объект зависимости объявлен в объекте Window как private TestDependency td; , не добавляя специально td в привязку и просто добавляя тип TestDependency, привязывается ли он к частной переменной или создает свой собственный экземпляр? - person black eyed pea; 02.02.2013
comment
@blackeyedpea WPF создаст экземпляр TestDependency и установит его как значение Window.DataContext. Таким образом, TextBlock будет ссылаться на него и использовать его TestDateTime для установки текста TextBlock. - person Colin; 02.02.2013
comment
Это потому, что все привязки данных обрабатываются в XAML. Он фактически игнорировал все, что вы делали в конструкторе оконных объектов. Волшебный код привязки данных в XAML — это часть ‹Window.DataContext›. Если вы хотите сделать это в коде, вы можете сделать что-то вроде этого. DataContext = td; часть {Binding TestDateTime} пытается получить свойство с именем TestDateTime любого объекта, установленного в данный момент как DataContext. - person blaise; 02.02.2013
comment
@Colin Спасибо за объяснение, теперь я понимаю, как это работает - person black eyed pea; 02.02.2013
comment
@blaise Спасибо, DataContext для меня новый, очень интересный. - person black eyed pea; 02.02.2013

Если вы просто хотите показать некоторые значения в своем TextBlock, вам здесь не нужен объект зависимости. Попробуйте что-то вроде этого:

public partial class MainWindow : Window
{
    public DateTime Test
    { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        this.Test = DateTime.Now;
    }
}

<Grid>
    <TextBlock Text="{Binding Path=Test,RelativeSource={RelativeSource AncestorType=Window,Mode=FindAncestor}}"></TextBlock>
</Grid>

Здесь я не показываю код, который может обновлять значение каждую секунду. Я просто хочу уточнить, что это неправильная ситуация для использования свойства зависимости. Конечно, для этого вы можете использовать Dependency Property. Но объект зависимости и свойство зависимости могут предложить вам некоторые дополнительные функции, такие как привязка данных. Но это не означает, что вам нужно использовать объект зависимости или свойство зависимости в качестве источника привязки данных.

person Colin    schedule 02.02.2013
comment
Хорошо, на самом деле я делаю это специально, чтобы опробовать DependencyObject и DepdencyProperty. Ваш ответ очень правильный и красивый - person black eyed pea; 02.02.2013
comment
Я пытаюсь преобразовать код blaise для привязки к частной переменной td, используя ‹TextBlock Text={Binding Path=Test,RelativeSource={RelativeSource AncestorType=Window,Mode=FindAncestor}}›‹/TextBlock›, изменив путь привязки, как мне установить его в частную переменную td? - person black eyed pea; 02.02.2013
comment
@blackeyedpea Насколько мне известно, я не думаю, что вы можете установить частную переменную в качестве пути привязки. Или, может быть, вы можете обратиться к этому для получения дополнительной информации. . - person Colin; 02.02.2013
comment
что, если это общедоступная переменная, может ли это работать? ‹TextBlock Text={Binding ElementName = td, Path=TestDateTime}/› - person black eyed pea; 02.02.2013
comment
ElementName предназначен для привязки к другим элементам, объявленным в XAML, по их x:Name, а не к вещам, объявленным в коде. Когда вы назначаете x:Name в XAML, вы также можете получить доступ к этому объекту по имени в своем коде программной части. - person John Bowen; 02.02.2013
comment
@JohnBowen О, круто. Какую привязку я использую для привязки к объекту Source, объявленному в процедурном коде? Мне удалось выполнить привязку в коде, используя Binding binding = new Binding(); привязка.Источник = тд; binding.Path = новый путь к свойству (TestDateTime); tb.SetBinding(TextBlock.TextProperty, привязка); но мне интересно, можно ли это сделать и в Xaml? - person black eyed pea; 02.02.2013
comment
Обычно вы хотите полагаться на DataContext, как в ответе @blaise, но вы также можете установить для этого свойства DataContext все, что хотите, из кода. - person John Bowen; 02.02.2013
comment
@JohnBowen Я задал новый вопрос для [этого] (stackoverflow.com/questions/14664269/), не могли бы вы опубликовать там? Спасибо - person black eyed pea; 02.02.2013