Обновление относительного времени с помощью WPF DataTemplate

Я использую DataTemplate для отображения элементов (из класса через привязку данных) в списке. Этот класс также содержит дату и время, и я использую конвертер для преобразования этой даты / времени в относительное время (xx минут назад), которое затем отображается в TextBlock. Пока все отлично.

Проблема в том, что я не знаю, как обновлять это относительное время (все они застряли на сгенерированном значении, например, «1 секунду назад»). Я мог бы использовать ListBox.Items.Refresh (), но это также повторно запускает анимацию, которую я настроил для элементов.

Любые идеи?

Заранее спасибо!


person Kristaps A.    schedule 15.12.2009    source источник
comment
Хорошо, этот материал с INotifyPropertyChanged и DependencyProperty мне не по зубам, поэтому я немного изменил пользовательский интерфейс, чтобы исправить это на данный момент. Наверное, вернусь к этому когда-нибудь позже. Спасибо :)   -  person Kristaps A.    schedule 16.12.2009


Ответы (5)


Вам необходимо реализовать INotifyPropertyChanged или DependencyProperty для свойства, к которому вы привязаны, чтобы видеть обновления.

DependencyProperty - предпочтительный метод в WPF, поскольку он приведет к повышению производительности во время выполнения. Вот документация, включая пример того, как создать его.

person Will Eddins    schedule 15.12.2009

Попросите вашу модель реализовать INotifyPropertyChanged. Если ваша двусторонняя привязка установлена ​​правильно, вызывайте OnPropertyChanged, передавая ему имя свойства всякий раз, когда свойство, которое вы хотите обновить, изменится. Это предупредит всех, кто смотрит на изменения в этом свойстве (т. Е. Ваше мнение, следовательно, требование двусторонней привязки), что значение изменилось и его необходимо обновить.

public string Name
{
    get { return m_Name; }
    set 
    {
        OnPropertyChanged(Name);
        m_Name = value; 
    }
}

Обновление:

Используйте таймер. Я не собираюсь красть исходник у Дэйва, поэтому вот ссылка на его ответьте на очень похожий вопрос. В вашем timer_Tick методе рассчитайте относительное время. Это будет обновлять ваш графический интерфейс каждую секунду.

person Ragepotato    schedule 15.12.2009
comment
Но будет ли OnPropertyChanged работать, если время для каждого элемента не меняется? (Это время создания предмета на сервере) - person Kristaps A.; 16.12.2009
comment
Таким образом, свойство времени модели не меняется. Я предполагаю, что вы используете DateTime.Now для относительного расчета и просто хотите, чтобы расчет активно обновлялся, верно? - person Ragepotato; 16.12.2009
comment
О таймере: не будет ли такой быстрый таймер сильно сказываться на использовании ресурсов приложением? Во всяком случае, метод с использованием виртуализированного ScrollChanged (который вызывается каждый раз, когда вы прокручиваете или добавляете видимый элемент) работает для меня достаточно хорошо. Спасибо за вашу помощь :) - person Kristaps A.; 17.12.2009
comment
Это будет не так уж и много стока, он вызывается только раз в секунду. И, поскольку кажется, что вы здесь новичок, не могли бы вы отметить один из предложенных ответов как принятый. Это помогает другим узнать, когда вопросы закрыты, и дает хорошее руководство тем, кто смотрит на вопросы в будущем, относительно того, что работает, а что нет. Спасибо и добро пожаловать = D - person Ragepotato; 17.12.2009

Хорошо, это может быть не самое элегантное решение (я в этом почти уверен), но пока я сделал это с приемлемыми результатами, используя событие ScrollChanged ListBox, чтобы просто добавить 1 миллисекунду ко времени каждого видимого элемента, что вызывает относительное время обновлять;) Код вызывается каждый раз, когда я что-то добавляю в список, и влияет только на видимые в данный момент элементы (вроде как VirtualizingStackPanel) :)

int VO = 0; // I think that this was protection for when a load of items are added at the beginning. Maybe you can do fine without it.
private void HomeList_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    int v = (int)e.VerticalOffset;
    if (HomeList.Items.Count > 0 && v != VO) // Maybe you can do fine without VO.
    {
        for (int i = 0; i < e.ViewportHeight; i++)
        {
            // Add 1 millisecond to the item's time here
        }
        VO = v; // Maybe you can do fine without VO.
    }
}
person Kristaps A.    schedule 17.12.2009
comment
Вам вообще не следует изменять значение DateTime. Вам нужен конвертер. - person ; 20.04.2016

Только что у меня была такая же проблема, и я решил ее, создав ViewModel для этих элементов.

public class MyItem
{
    public DateTime { get; set; }
}

public class MyItemViewModel : INotifyPropertyChanged
{
    private string relativeTime;
    public string RelativeTime
    {
        get { return relativeTime; }
        set
        {
            relativeTime = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("RelativeTime"));
        }
    }

    public DateTime Date { get; set; }

    public static implicit operator MyItemViewModel(MyItem item)
    {
        return new MyItemViewModel { Date = item.Date }
    }
}

А затем обновлять их с помощью таймера.

updateRelativeTimeString = new Timer(s =>
    Items.ForEach(
         item => item.RelativeTime = item.Date.ToRelativeTime()),
    null,
    0,
    5000);

Использование двух методов расширения (IEnumerable.ForEach и DateTime.ToRelativeTime)

person loraderon    schedule 15.10.2010
comment
Разве не имеет смысла использовать таймер ПРОСТО для вызова события изменения свойства и использования преобразователя, привязанного к значению? Пока таймер вызывает событие PropertyChanged, преобразователь автоматически выбирает момент преобразования и выводит новое значение. - person ; 20.04.2016

Я решил опубликовать свое собственное решение, так как считаю его самым простым и требует гораздо меньше кода, чем другие, которые я видел.

//AbstractViewModel implements INotifyPropertyChanged
public class MyObject : AbstractViewModel
{
    private DateTime date = DateTime.UtcNow;
    public DateTime Date
    {
        get
        {
            return date;
        }
        set
        {
            date = value;
            OnPropertyChanged("Date");
        }
    }

    public MyObject()
    {
        Timer t = new Timer();
        t.Interval = 1000; //Update every second
        t.Elapsed += T_Elapsed;
        t.Enabled = true;
    }

    private void T_Elapsed(object sender, ElapsedEventArgs e)
    {
        OnPropertyChanged("Date");
    }
}

Затем вы должны выполнить операцию относительного времени внутри конвертера:

using System;
using System.Globalization;
using System.Windows.Data;

namespace MyConverters
{
    [ValueConversion(typeof(DateTime), typeof(string))]
    public class RelativeTimeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime Date = (DateTime)value;
            if (Date == null) return "never";
            return Utility.RelativeTime(Date);
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }
}

И добавляем конвертер в привязку:

<Run Text="{Binding Date, Converter={StaticResource RelativeTimeConverter}}"/>

И для переключения, должен ли объект отмечаться, вы можете определить дополнительный конструктор или флаг в классе объекта, указывающий, включено ли такое поведение. Таким образом, если ваше приложение поддерживает изменение формата даты, вы можете просто перебирать свои объекты, проверять каждый флаг и удалять (или менять местами) существующий конвертер привязки.

person Community    schedule 20.04.2016