I think i went somewhat over my head with a little tool i wanted to make.

The idea was to bind certain keys(users choice) to countdown timers and progressbars.
But since the keys are never the same i have to save the chosen key for the selected progressbar.

So when the assigned key is pressed the timer & progressbar linked to that key will start a countdown timer.
But from what i can tell there is no simple way to do this.
Found some examples with keyboard hooks, but none of them where in WPF.

Hope i am making sense about what i want.
And any advice or examples would be much appreciated.

Dani AI

Generated

Original goal: let users pick keys that start independent countdowns and progress bars. steered this toward a key→progress mapping, and demonstrated a BackgroundWorker-based solution. Another practical pattern—especially if the app uses MVVM—is to attach dynamic KeyBindings that invoke ICommand instances which drive a DispatcherTimer per timer item. That keeps UI updates on the UI thread, makes cancellation and binding to ProgressBar.Value trivial, and avoids explicit thread marshaling.

Example (ViewModel-driven timer using DispatcherTimer and ICommand):

public class TimerItemViewModel : INotifyPropertyChanged
{
    public Key HotKey { get; set; }
    public int DurationSeconds { get; set; } = 10;
    public double Progress { get; private set; }
    public ICommand StartCommand { get; }

    private readonly DispatcherTimer timer;
    private int elapsed;

    public TimerItemViewModel()
    {
        StartCommand = new RelayCommand(_ => Start(), _ => !timer.IsEnabled);
        timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
        timer.Tick += (s,e) => {
            elapsed++;
            Progress = Math.Min(100.0 * elapsed / DurationSeconds, 100.0);
            OnPropertyChanged(nameof(Progress));
            if (elapsed >= DurationSeconds) timer.Stop();
        };
    }

    public void Start()
    {
        elapsed = 0;
        timer.Start();
        OnPropertyChanged(nameof(Progress));
    }

    // INotifyPropertyChanged omitted for brevity
}

Register the hotkey at runtime by adding an InputBinding for each view model:

var kb = new KeyBinding(viewModel.StartCommand, new KeyGesture(viewModel.HotKey, ModifierKeys.None));
this.InputBindings.Add(kb);

Notes and troubleshooting

  • InputBindings are active while the Window (or focused element) is active; controls like TextBox may swallow keys—use PreviewKeyDown or AddHandler(KeyDownEvent, handler, true) to catch handled events.
  • For global hotkeys (start when the app is not focused), use RegisterHotKey via P/Invoke or a small library (be mindful of key conflicts).
  • Persist mappings by storing HotKey.ToString() (or use KeyConverter) and restore them at startup, recreating the corresponding InputBindings.
  • Give each timer its own DispatcherTimer to allow concurrent timers; ensure InputBindings are removed on dispose and timers stopped on shutdown.

Recommended Answers

All 4 Replies

You should be able to add the key with the corresponding progressbar to something like a dictionary Dictionary<Char,ProgressBar> and then on keypress check your dictionary and retrieve the relevant entry

Ok, that not know about that.
I am going to look further into that, thanks for the tip.

Use in your main window a Dictionary with a Key and a specific class to store your progress information. Also, have a StackPanel in your main window, to display the ProgressBars.

private Dictionary<Key, Progress> progressBars = 
    new Dictionary<Key, Progress> { 
        { Key.O, new Progress() } , 
        { Key.L, new Progress() } 
    };

Bind your windows KeyDown event, and check for required keys.

private void Window_KeyDown(object sender, KeyEventArgs e)
{
    if ((e.Key == Key.O  || e.Key == Key.L) && !progressBars[e.Key].isActive)
    {
        progressBars[e.Key].isActive = true;
        progressBars[e.Key].StartProgress(main_stack); //main_stack is a StackPanel
    }
}

And for your Progress class, you can use something like this:

//pseudocode
class Progress
    ProgressBar pb
    bool isActive       // check if the current thread is active
    int time            // a certain amount of seconds for the timer
    BackgroundWorker bw // a thread to run your timer

Also, you'll need to bind the BackgroundWorker's events, such as DoWork, ProgressChanged and RunWorkerCompleted.
Here's my quick take on the Progress class:

class Progress
{
    public ProgressBar pb = new ProgressBar();
    public Boolean isActive = false;
    public BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
    public int time = 10;

    public Progress()
    {
        bw.WorkerReportsProgress = true;
        bw.DoWork += bw_DoWork;
        bw.ProgressChanged += bw_ProgressChanged;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        pb.Height = 20;
        pb.MaxWidth = 100;
    }

    void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        (pb.Parent as StackPanel).Children.Remove(pb); //remove progressBar
        pb.Value = 0;                                  //reset the value
        isActive = false;                              //show it's done
    }

    void bw_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        this.pb.Value = Math.Min(e.ProgressPercentage, 100);
    }

    void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        for (int i = 0; i < time; i++)
        {
            bw.ReportProgress((100 * i) / time);
            System.Threading.Thread.Sleep(1000);
        }
    }

    public void StartProgress(StackPanel grid)
    {
        grid.Children.Add(pb);
        bw.RunWorkerAsync();
    }
}

Alright thanks, managed to get it to work with Dictionary.
Also then example helped a lot, thank you very much both of you.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.