'How to add a countdown timer to a child element in Wpf
Im quite new to coding. So far I have a WPF application that when I press submit it creates the treeview but I wanted to add a countdown timer for each child item and have it display the time remaining next to the child item. The problem is the treeview doesn't update and I dont know how to assign a timer for each child item
using Microsoft.Azure.Cosmos.Core.Collections;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace Test_v2
{
public partial class MainWindow : Window
{
public int secondsCount = 100;
public MainWindow()
{
InitializeComponent();
DispatcherTimer disTmr = new DispatcherTimer();
disTmr.Tick += new EventHandler(disTmr_Tick);
disTmr.Interval = new TimeSpan(0, 0, 1);
disTmr.Start();
}
public void disTmr_Tick(object sender, EventArgs e)
{
secondsCount--;
}
List<TreeViewItem> folderList = new List<TreeViewItem>();
public void SubmitButton_Click(object sender, RoutedEventArgs e)
{
if (Folder.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter Folder Name";
return;
}
if (Name.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter a Name";
return;
}
TreeViewItem parent = new TreeViewItem();
for (int i = 0; i < folderList.Count; i++)
{
if (folderList[i].Header.ToString() == Folder.Text)
{
parent = folderList[i];
break;
}
}
if (folderList.Contains(parent))
{
FolderInArrayBlock.Text = "True";
TreeViewItem newChild = new TreeViewItem();
newChild.Header = Name.Text + secondsCount.ToString();
parent.Items.Add(newChild);
}
else
{
FolderInArrayBlock.Text = "false";
TreeViewItem? treeItem = null;
treeItem = new TreeViewItem();
treeItem.Header = Folder.Text;
folderList.Add(treeItem);
treeItem.Items.Add(new TreeViewItem() { Header = Name.Text + secondsCount.ToString()});
LearningItems.Items.Add(treeItem);
}
}
}
}
Solution 1:[1]
First of all, if you are using Wpf, you need to use MVVM approch if you want to make a sustainable and maintainable code. This means you need to seperate View funcionalities from Model funcionalities and use a ViewModel as a bridge to be able to communicate with those two things. In Wpf we should try to use Bindings and notifypropertychange to build the brige between View and ViewModel and not use control naming for later use in code behind .cs.(Code behind is the .cs file which belongs to .xaml file ex.: MainWindow.xaml.cs)
I recommend you to take a look at this page, which explains why its so important to use MVVM in your Wpf applications: MVVM pattern
I have created a sample project which demonstrate a proper approch for your task, in my opinion.
MainWindow.xaml
<Window x:Class="TreeViewWithCountDown.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TreeViewWithCountDown"
xmlns:localviewmodels="clr-namespace:TreeViewWithCountDown.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TreeView ItemsSource="{Binding Path=Items, Mode=OneWay}">
<!--We use TreeView Resources because we bind Items as ItemSource and Items is a List of StorageItems, which can be either FolderItem or FileItem.
TreeView can display the two types differently if we specify in the Resources-->
<TreeView.Resources>
<!--Here we specify how to display a FolderItem-->
<HierarchicalDataTemplate DataType="{x:Type localviewmodels:FolderItem}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"/>
</HierarchicalDataTemplate>
<!--Here we specify how to display a FileItem-->
<DataTemplate DataType="{x:Type localviewmodels:FileItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FileNames"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"
Grid.Column="0"/>
<TextBlock Text="{Binding Path=CountdownTime}"
Margin="0 0 15 0"
Grid.Column="1">
</TextBlock>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace TreeViewWithCountDown
{
public partial class MainWindow : Window
{
private ViewModel _viewModel= new ViewModel();
public MainWindow()
{
InitializeComponent();
//Really important to define where to look for the binding properties
DataContext = _viewModel;
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Timers;
using TreeViewWithCountDown.ViewModels;
namespace TreeViewWithCountDown
{
public class ViewModel : INotifyPropertyChanged
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public ViewModel()
{
//Filling up our Items property which will be given to the View for display
Random random = new Random();
FileItem item0 = new FileItem("file0", random.Next(0,100));
FolderItem item1 = new FolderItem("folder1");
item1.Items.Add(item0);
FileItem item2 = new FileItem("file2", random.Next(0, 100));
FileItem item3 = new FileItem("file3", random.Next(0, 100));
Timer timer = new Timer(3000);
timer.Elapsed += Time_Elapsed;
timer.Start();
Items.Add(item1);
Items.Add(item2);
Items.Add(item3);
}
private void Time_Elapsed(object sender, ElapsedEventArgs e)
{
foreach (StorageItem item in Items)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else
{
//Reducing counters of Files in Folders
ReduceFileCountInFolders(item);
}
}
}
//A file can be nested in multiple folders so we can solve this with a recursive method
private void ReduceFileCountInFolders(StorageItem item)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else if (item is FolderItem folderItem)
{
if (folderItem.Items != null && folderItem.Items.Count > 0)
{
foreach (StorageItem storageItem in folderItem.Items)
{
ReduceFileCountInFolders(storageItem);
}
}
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
StorageItem.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TreeViewWithCountDown.ViewModels
{
public class StorageItem : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public StorageItem(string name)
{
Name = name;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
FileItem.cs
namespace TreeViewWithCountDown.ViewModels
{
public class FileItem : StorageItem
{
private int _countdownTime;
public int CountdownTime
{
get => _countdownTime;
set
{
if (_countdownTime != value && value > 0)
{
_countdownTime = value;
OnPropertyChanged();
}
}
}
public FileItem(string name, int num) : base(name)
{
CountdownTime = num;
}
}
}
FolderItem.cs
using System.Collections.Generic;
namespace TreeViewWithCountDown.ViewModels
{
public class FolderItem : StorageItem
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public FolderItem(string name) : base(name)
{
}
}
}
The final look: View
Hope this will help, if anything seems complicated, feel free to ask!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | bazsisz |
