简体   繁体   中英

Updating relative time with WPF DataTemplate

I'm using a DataTemplate to show items (from a class via data binding) in a listbox. This class also contains date and time, and I'm using a converter to convert this date/time to relative time (xx minutes ago), which is then displayed in a TextBlock. So far everything is great.

The problem is that I don't know how to keep this relative time updated (they're all stuck at the generated value, eg "1 second ago"). I could use ListBox.Items.Refresh(), but that also re-runs the animations I've set up for the items.

Any ideas?

Thanks in advance!

Have your model implement INotifyPropertyChanged . If your two way binding is set correctly call OnPropertyChanged passing it the property name whenever a property you want updated changes. This will alert whomever is looking at changes to that property (ie your view hence the two way binding requirement) that the value has changed and it needs to update.

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

Update:

Use a timer. I'm not going to steal the source from Dave so here is a link to his answer to a very similar question. In your timer_Tick method do your relative time calculation. This will update your GUI every second.

You need to implement INotifyPropertyChanged or DependencyProperty for the property you're binding to in order to see updates.

DependencyProperty is the preferred method in WPF, since it will result in better performance at runtime. Here is the documentation , including an example of how to create one.

Ok this might not be the most elegant solution (I'm pretty sure of it), but for now I've done this with acceptable results using the ListBox's ScrollChanged event to simply add 1 millisecond to each visible item's time, which causes the relative time to update ;) The code is called every time I add something to the listbox, and only affects the currently visible items (kind of like the 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.
    }
}

I decided to post my own solution as I feel it is the simplest and requires much less code than others I have seen.

//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");
    }
}

You would then perform relative time operation inside converter:

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

And add converter to binding:

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

And to toggle whether or not the object should tick, you could define an additional constructor or flag in object class indicating if such behavior is enabled. That way if your application supports changing the date format, you could simply iterate your objects, check each's flag, and remove (or swap) the binding's existing converter.

I had the same problem just now and I solved it by creating a ViewModel for those items.

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

And then updating them using a timer.

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

Using two extension methods (IEnumerable.ForEach and DateTime.ToRelativeTime)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM