C
C#4mo ago
Qwerz

✅ How do you handle countdowns of objects?

I have a List of Objects, each has an expiry date. I would like to display a countdown (e.g. 1 hour and 1 minute until expiry) in one of my views (WPF). I'm unsure however, how to construct this in regards to timers. I calculate the time left by subtracting the current date from the date of expiration. If I do this in the object class and implement OnPropertyChanged I get a stackoverflow error, which is understandable because the subtraction would be calculated infinitely often. I also don't want to create a timer for every object because there could be quite a lot of those. What is a better approach for this? I thought of creating a second collection that stores all the calculated values and is updated by a single timer, but I'm unsure how to implement this. I'd be grateful for your assistance!
24 Replies
Cracker
Cracker4mo ago
how many items you can show on screen ? Expiration - CurrentDate should be correct approach lets say 1 million items are in the list but only 100 of them shown in screen, so you get expiration date of those 100 items and start 100 instance of timer
canton7
canton74mo ago
Create a single timer which ticks one a second?
Cracker
Cracker4mo ago
but you also need to extract elapsed time from the expiration of each item
Shinyshark
Shinyshark4mo ago
DispatcherTimer Class (System.Windows.Threading)
A timer that is integrated into the Dispatcher queue which is processed at a specified interval of time and at a specified priority.
Qwerz
QwerzOP4mo ago
My problem with the approach of having a separate collection that is refreshed via a timer is that I’m not sure how to reference it in my View and bind to it, because it’s a different datacontext than the viewmodel I’m using. I can’t wrap my head around how to structure this in a meaningful way, how to keep track of each objects expiration. I’m a bit surprised there’s no easy example because I thought this was something relatively commonly implemented.
canton7
canton74mo ago
I'd do: * ExpirableObjectVM has a property which returns time to go * Parent VM has a collection of ExpirableObjectVM * ParentView shows that collection * ParentVM has a timer which fires once a whatever. When it fires, it goes through all the ExpirableObjectVMs in the collection and tells each to raise a PropertyChanged event for its "time to go" property
Qwerz
QwerzOP4mo ago
So you’d have two views (one used as a component) and two corresponding nested view models? Why not just use one view model? I’m, sorry but I’m a bit confused by having a collection of viewmodels. Could you explain this please? I’m always eager to learn if there’s a better way!
canton7
canton74mo ago
I'd probably only have 1 view. Yes it's common for a parent VM to have a collection of child VMs - that's how things like ItemsControl / ListView / ListBox work I'm assuming you want to show the countdown for each of the objects in your list? In which case an ItemsControl is the normal way of displaying a list of children
Qwerz
QwerzOP4mo ago
I guess I deviated from the norm a little then. I’m using a single viewmodel and view, where I have a listbox with data template that binds to a collection in the viewmodel. And yes, I would like to show the countdown in the list. Do you have an idea where I could get an example for the timer per object implementation? It would really help to see how others have gone about doing this.
canton7
canton74mo ago
If you're binding to it, it's a ViewModel? That sounds like the structure I'm proposing
Qwerz
QwerzOP4mo ago
Well yes, but I only have one viewmodel that is in my views datacontext to which I can bind. You're suggesting to use two view models, making me unsure if I can get away with using one?
canton7
canton74mo ago
I'm not sure where this confusion is coming from I'm afraid. If you have: ParentVM.cs
public class ParentVM : INotifyPropertyChanged
{
public ObservableCollection<ChildVM> Children { get; }
}
public class ParentVM : INotifyPropertyChanged
{
public ObservableCollection<ChildVM> Children { get; }
}
ChildVM.cs
public class ChildVM : INotifyPropertyChanged
{
public TimeSpan TimeRemaining { get; }
}
public class ChildVM : INotifyPropertyChanged
{
public TimeSpan TimeRemaining { get; }
}
ParentView.xaml
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TimeRemaining}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TimeRemaining}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then you have one view (ParentView) and two ViewModels (ParentVM and ChildVM), and you bind to both (ParentView.xaml binds directly to ParentVM, and there's a DataTemplate in ParentView whose DataContext is set to each ChildVM and which binds to properties on ChildVM)
Qwerz
QwerzOP4mo ago
Thanks for expanding on this. I'm confused because I can't quite identify why using two ViewModels is necessary or solved the problem at hand (periodically updating collection of objects in an items control). I have a very similar setup, the difference being that I don't have a child VM but only a ViewModel for my View. Thus I'm still stuck on implementing a solution using a single timer instead of one for each object in the list. I'm also curious to understand whether using two VMs is beneficial / the "correct" way? Thank you! In the meantime, I thought I could maybe use OnPropertyChange in the get method of a property of minutes, but that seems to be impractical and not work at all.
canton7
canton74mo ago
Literally, how else would you structure this?
Qwerz
QwerzOP4mo ago
I have: TestView.xaml
c#
<ItemsControl ItemsSource="{Binding Cars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TimeRemaining}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
c#
<ItemsControl ItemsSource="{Binding Cars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TimeRemaining}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
TestViewViewModel.cs
c#
private ObservableCollection<CarModel> cars;
public ObservableCollection<CarModel> Cars{
get => cars;
set{cars = value;
OnPropertyChanged();}
}

public TestViewViewModel(){
cars = new ObservableCollection<CarModel>();
}
c#
private ObservableCollection<CarModel> cars;
public ObservableCollection<CarModel> Cars{
get => cars;
set{cars = value;
OnPropertyChanged();}
}

public TestViewViewModel(){
cars = new ObservableCollection<CarModel>();
}
(simplified) I have seen multiple VMs (your example) used in ItemControls before, but I didn't see any advantage in it and just continued doing it like I was used to.
canton7
canton74mo ago
So CarModel is a ViewModel. You're binding to its properties from a view. Why all the confusion?
Qwerz
QwerzOP4mo ago
You're right, my CarModel is your ChildViewModel. I was thinking there was a difference with some deeper meaning between Model and ViewModel?
canton7
canton74mo ago
A ViewModel is something you bind to, which implements INotifyPropertyChanged So, next step. I'd do some variant on:
public class CarViewModel : INotifyPropertyChanged
{
private readonly DateTime expiryTime;
public TimeSpan TimeRemaining
{
get => ...
private set ... // Normal INPC boilerplace
}


public void UpdateTimeRemaining() => TimeRemaining = expiryTime - DateTime.Now;
}

public class TestViewViewModel : INotifyPropertyChanged
{
private readonly DispatcherTimer timer;

public ObservableCollection<Car> Cars { get; } = [];

public TestViewModel()
{
timer = new DispatcherTimer() { ... };
timer.Elapsed += (o, e) =>
{
foreach (var car in Cars)
{
car.UpdateTimeRemaining();
}
};
}
}
public class CarViewModel : INotifyPropertyChanged
{
private readonly DateTime expiryTime;
public TimeSpan TimeRemaining
{
get => ...
private set ... // Normal INPC boilerplace
}


public void UpdateTimeRemaining() => TimeRemaining = expiryTime - DateTime.Now;
}

public class TestViewViewModel : INotifyPropertyChanged
{
private readonly DispatcherTimer timer;

public ObservableCollection<Car> Cars { get; } = [];

public TestViewModel()
{
timer = new DispatcherTimer() { ... };
timer.Elapsed += (o, e) =>
{
foreach (var car in Cars)
{
car.UpdateTimeRemaining();
}
};
}
}
Qwerz
QwerzOP4mo ago
Thank you very much, this is the smartest solution i did not think of! I will get this working.
canton7
canton74mo ago
I mean, you can give each CarViewModel a timer if you want, but then you need to manage starting/stopping them, they might end up ticking at different times so your UI looks weird, etc But, MVVM is straightforward. You want to show the time remaining? Put a property on the VM for "time remaining". You want that to update? Add a method to update that property. You want all children of a parent to update at the same time? Make the parent update all of its children at the same time
Qwerz
QwerzOP4mo ago
This basically decribes your example from above, right? I just finished implementing it this way in my project and it works like a charm! The best thing is, it still feels MVVM conform and I can definitely see myself using something similar in many future situations. MVVM is straightforward...to those who already excel at it it seems 😉 I've been learning the pattern for this past year and I still find myself trapped in simple problems like these. Thanks again for taking the time to help me out, very much appreciated!
canton7
canton74mo ago
No worries, glad you got it working! One last thing: if your CarModel is used outside of the UI layer, then don't go modifying it to add a TimeRemaining property or add INPC etc. It's fine to create a CarViewModel which wraps your CarModel, then all your MVVM stuff can go on CarViewModel, and CarModel can stay nice and pristine $close
MODiX
MODiX4mo ago
If you have no further questions, please use /close to mark the forum thread as answered
Qwerz
QwerzOP4mo ago
Thanks for the tip, I will implement it this way once I progress further!
Want results from more Discord servers?
Add your server