Setting a delay for an action on ListBox _SelectedIndexChanged - c#

I have a listbox with filenames. When the selected index is changed I load the file.
I want something like jQuery's HoverIntent that delays the action of loading the file for a short time so the user can use the down arrow and quickly cycle through the items in the list without the application trying to load each one. Thread.Sleep pauses the whole app so a user can't select another list item until the sleep completes, this is obviously not what I want.

This will work if your using WinForms, make a call to the InitTimer method in the Form constructor.
Load the file in the _timer_Tick event handler. To change the delay set the Interval property in InitTimer to another value.
private System.Windows.Forms.Timer _timer;
private void InitTimer()
{
_timer = new Timer { Interval = 500 };
_timer.Tick += _timer_Tick;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
// TODO: Load file here
}

Use Threading to separate the loading from your GUI.
This should get you started:
public partial class MainWindow : Window
{
CancellationTokenSource cts;
bool loading;
private void SelectedIndexChanged(int index)
{
if (loading)
cts.Cancel();
cts = new CancellationTokenSource();
var loader = new Task.Delay(1000);
loader.ContinueWith(() => LoadFile(index))
.ContinueWith((x) => DisplayResult(x));
loader.Start();
}
private void DisplayResult(Task t)
{
// TODO: Invoke this Method to MainThread
if (!cts.IsCancellationRequested)
{
// Actually display this file
}
}
Could not test, as I'm still on .net 4 whereas Task.Delay() is .net 4.5
You may need to add another field in the form for the file content transfer from the tasks to the GUI.

Winforms:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private CancellationTokenSource _cancel;
private object _loadLock = new object();
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
lock (_loadLock)
{
handleCancellation();
var loader = new Task((chosenFileItemInListbox) =>
{
Thread.Sleep(1000);
LoadFile(chosenFileItemInListbox);
}, listBox1.SelectedItem, _cancel.Token);
}
}
private bool handleCancellation()
{
bool cancelled = false;
lock (_loadLock)
{
if (_cancel != null)
{
if (!_cancel.IsCancellationRequested)
{
_cancel.Cancel();
cancelled = true;
}
_cancel = null;
}
}
return cancelled;
}
private void LoadFile(object chosenFileItemInListbox)
{
if (handleCancellation())
{
return;
}
}
}
The code above could also be applied to WPF, but WPF contains some built in magic for handling delays and cancellation of previous updates.
<ListBox SelectedItem="{Binding Path=SelectedFile, Delay=1000}" />

Related

GUI updates only after the worker thread has ended

I have a Windows Form application and managed DLL in one solution. DLL contains some time consuming functions during which I wish to update the Form contents (callback from the DLL to the Form with progess updates). I have the following code:
Form code, where I initialize the DLL and give it a callback function in the Initialize method. I also start a separate Thread to periodicly check the message_queue for new messages from the DLL. The DLL function is also called in a separate Thread (non blocking for the UI).
private LibraryDLL library_dll;
private ConcurrentQueue<string> message_queue;
public MainForm()
{
InitializeComponent();
library_dll = new LibraryDLL();
message_queue = new ConcurrentQueue<string>();
library_dll.Initialize(ProcessMessage);
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
string message;
if (message_queue.TryDequeue(out message))
{
PrintMessage(message);
}
}).Start();
}
private void ProcessMessage(string message)
{
message_queue.Enqueue(message);
}
private void PrintMessage(string message)
{
this.Invoke((MethodInvoker)delegate
{
listBox_rows.Items.Add(message);
});
}
private void button_send_Click(object sender, EventArgs e)
{
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
library_dll.DoWork();
}).Start();
}
In DLL code, I use the callback method to report progress:
private CallBack callback;
public delegate void CallBack(string message);
public LibraryDLL() { }
public void Initialize(CallBack callback)
{
this.callback = callback;
}
public void DoWork()
{
callback("working...")
Thread.Sleep(500);
callback("working...")
Thread.Sleep(500);
callback("working...")
Thread.Sleep(500);
}
My problem is, that instead of string "working" appearing every 500ms, it appears 3 times after 1500ms (only after the Thread in which the DoWork method is running ends). I also tried the Invalidate()-Update()-Refresh() sequence in the Form's PrintMessage function, but without any effect.
Thanks for the advice!
EDIT1:
I modified the code to use the BackgroundWorker, however, the problem remains (nothing for 1500ms, than all 3 strings at once).
BackgroundWorker bck_worker;
public MainForm()
{
InitializeComponent();
library_dll = new LibraryDLL();
library_dll.Initialize(bck_worker);
bck_worker = new BackgroundWorker();
bck_worker.ProgressChanged += new ProgressChangedEventHandler(bckWorker_ProgressChanged);
bck_worker.WorkerReportsProgress = true;
bck_worker.WorkerSupportsCancellation = true;
}
private void bckWorker_DoWork(object sender, DoWorkEventArgs e)
{
library_dll.DoWork();
}
private void bckWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
PrintMessage((string)e.UserState);
}
private void button_send_Click(object sender, EventArgs e)
{
bck_worker.DoWork += new DoWorkEventHandler(bckWorker_DoWork);
bck_worker.RunWorkerAsync();
}
private void PrintMessage(string message)
{
listBox_rows.Items.Add(message);
}
And the DLL:
private BackgroundWorker bck_worker;
public LibraryDLL() { }
public void Initialize(BackgroundWorker bck_worker)
{
this.bck_worker = bck_worker;
}
public void DoWork()
{
bck_worker.ReportProgress(25, "working...");
Thread.Sleep(500);
bck_worker.ReportProgress(50, "working...");
Thread.Sleep(500);
bck_worker.ReportProgress(75, "working...");
Thread.Sleep(500);
}
EDIT2:
OK, I now tried to add the Invalidate-Update-Refresh sequence at the end of the PrintMessage function and it finaly works (with the BackgroundWorker approach)!
Use background worker and workers's report progress to update your UI: background worker doc

DotNetZip Cancel extraction in Task

I have a method that executes a zip-extraction in a Task. Now I want the ability to Cancel the operation. But when I call the Cancel() method everything seems to stop immediately and the while runs forever:
public class OsiSourceZip
{
private const string ZipPassword = "******";
private bool _extractionDone;
private bool _cancelExtraction;
public async Task Extract(string sourceFile, string extractionDir)
{
Task extraction = Task.Run(() =>
{
using (ZipFile zipf = ZipFile.Read(sourceFile))
{
zipf.ExtractProgress += delegate(object sender, ExtractProgressEventArgs args)
{
args.Cancel = _cancelExtraction;
RaiseExtractionProgressUpdate(args);
};
zipf.Password = ZipPassword;
zipf.Encryption = EncryptionAlgorithm.WinZipAes256;
zipf.ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently;
zipf.ExtractAll(extractionDir);
}
});
await extraction;
_extractionDone = true;
RaiseSourceInstallationCompleted();
}
public void Cancel()
{
_cancelExtraction = true;
while (!_extractionDone)
{
Thread.Sleep(500);
}
}
}
I've set a break point on args.Cancel = _cancelExtraction; but the event is not fired anymore as soon as the Cancel() method is called.
I've found a solution to this. I basically got rid of my own Cancel Method and do it as the dotnetzip Framework wants it. In the progress event.
Let's say you want to cancel the operation when the Form gets closed. I capture the FormClosing Event, cancel the close procedure and remember the close request. The next time the progress event fires, I set the the cancel property in the event args and close the Form myself in the completed Event:
public partial class MainForm : Form
{
private bool _closeRequested;
private void OnSourceInstallationCompleted(object sender, EventArgs e)
{
if (_closeRequested) { this.Close(); }
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (!_closeRequested)
{
_closeRequested = true;
e.Cancel = true;
}
}
private void OnExtractionProgressUpdate(object sender, ExtractProgressEventArgs e)
{
e.Cancel = _closeRequested;
}
}
I think it's rather ugly but it works....

Creating a timer winform application using only delegates and events [WITHOUT TIMER OBJECT]

While using timers, stopwatches and threads is the standard way, I was wondering if there was a way to create a Winform Application in c# which had a label with initial value as 0 and which automatically kept on incrementing once a button is clicked and when the same button is clicked again it should pause. Personally, I feel that the trick is to use multicast delegates. But I am stuck as to how to proceed.
NOTE: Possible use of method callback and InvokeRequired().
this code dose not use timer or stopwatch.
i have wrote a simple class for you, forgive me if its not so standard because im so lazy for now :)
public partial class Form1 : Form
{
CancellationTokenSource src;
CancellationToken t;
public Form1()
{
InitializeComponent();
}
//start incrementing
private async void button1_Click(object sender, EventArgs e)
{
this.Start.Enabled = false;
this.Cancel.Enabled = true;
this.src = new CancellationTokenSource();
this.t = this.src.Token;
try
{
while (true)
{
var tsk = Task.Factory.StartNew<int>(() =>
{
Task.Delay(500);
var txt = int.Parse(this.Display.Text) + 1;
return (txt);
}, this.t);
var result = await tsk;
this.Display.Text = result.ToString();
}
}
catch (Exception ex)
{
return;
}
}
// Stop incrementing
private void button1_Click_1(object sender, EventArgs e)
{
this.src.Cancel();
this.Cancel.Enabled = true;
this.Start.Enabled = true;
}
}
Really not sure why you think this can be done with your restrictions in place. If you want a delay in-between your "events", then you need to use some kind of Timer, or some kind of thread (classic Thread or some kind of Task) that has a delay within it...no way around that.
Here's another approach that'll probably violate your restrictions:
public partial class Form1 : Form
{
private Int64 value = -1;
private bool Paused = true;
private int IntervalInMilliseconds = 100;
private System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);
public Form1()
{
InitializeComponent();
this.Shown += Form1_Shown;
}
private async void Form1_Shown(object sender, EventArgs e)
{
await Task.Run(delegate ()
{
while (true)
{
value++;
label1.Invoke((MethodInvoker)delegate ()
{
label1.Text = value.ToString();
});
System.Threading.Thread.Sleep(IntervalInMilliseconds);
mre.WaitOne();
}
});
}
private void button1_Click(object sender, EventArgs e)
{
if (Paused)
{
mre.Set();
}
else
{
mre.Reset();
}
Paused = !Paused;
}
}
USE an EVENT.
If you can not use timers or threads, then how about creating a do while loop that executes an event.
Some PSEUDO code is below - it should give you the idea..
bool IWantEvents = false;
public event EventHandler<myHandler> myNonTimerEvent ;
FormStart()
{
this.myNonTimerEvent += new MyNonTimerEventHandler();
IWantEvents = true;
Do
{
.. do some weird stuff - set IWantEvents False on condition ..
}
while(IWantEvents)
}
MyNonTimerEventHandler()
{
.. Do what I would do if I was using a timer event.
}

Changing Timer Interval in Backgroundworker DoWork Disables the Timer [C#]

I have the problem with changing the timer Interval in backgroundworker's DoWork event. While changing the Interval by clicking the Button, Timer stops and doesn't start again.
Does anyone know how to solve this problem?
Simple code:
public Form1()
{
InitializeComponent();
timerTest.Tick += new EventHandler(timerTest_Tick);
timerTest.Interval = 1000;
timerTest.Start();
}
private void buttonTest_Click(object sender, EventArgs e)
{
push = true;
}
private void timerTest_Tick(object sender, EventArgs e)
{
ticks++;
labelTest.Text = ticks.ToString();
if(running == false)
{
running = true;
backgroundWorkerTest.RunWorkerAsync();
}
}
public void activate()
{
timerTest.Stop();
timerTest.Interval = 4000;
timerTest.Start();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
while(running)
{
if(push == true)
{
activate();
}
}
}
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
running = false;
}
}
}
You never set push to false.
Therefore, the following code:
while(running)
{
if(push == true)
{
activate();
}
}
will continuously call activate() in a tight loop. activate() stops the timer and then restarts it, and the time between calls to it will be far less than the timer interval. Therefore, the timer will never be left long enough to fire.
In any case, why don't you call activate() directly from buttonTest_Click()?
I can see this was asked a long time ago, but for the reference:
When it comes to timers or threadings in general (remember timer is system.threading) in combination with background workers (tasks) Never ever try to change a thread proerties randomly without knowing what the worker is doing.
It is always a good practice when assigning the DoWork handler to prepare the background worker Progress and Complete handlers as well.
At each cycle, report the progress or the completion, this would give you the chance to do your checks and modify another thread properties if needed.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
// do task 1 as per the timer1 interval
// do task 2 as per such and such .....
// if I call ChangeInterval here I'll be fiddling with another thread when
// this is still in progress
// that a full loop, Progress will be reported now
}
}
private void backgroundWorker1_ProgressChanged(object sender,ProgressChangedEventArgs e)
{
// Now as the Do work is not in progress
// do something
// check if the user wanted to change the interval ?
// if yes then
ChangeInterval(6000);
// here the progress reporting is done so it will go back to DoWork with the
// NewInterval Value in place and the timer enabled
}
private void ChangeInterval(int NewInterval)
{
timer1.Enabled =false;
timer1.Interval = NewInterval;
timer1.Enabled = true;
}
Try invoke your activate method with the UI Thread's Dispatcher. (Assuming Win Forms?)
this.Invoke(new Action(activate));
Reason is that your timer is a UI control and you're updating the Interval on a separate thread. This will throw a Cross-Thread exception.
Why don't you see the exception? When the DoWork method in your BackgroundWorker throws an exception, it will be propogated to the Completed method. So you should always look at e.Error to see if an exception occurred.
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
{
// Oh no something went wrong...
}
running = false;
}
It took me a while, but I found out what was wrong. I'll post you a working code, just in case someone will have the same problem.
public partial class Form1 : Form
{
public int ticks = 0;
public bool running = false;
public bool push = false;
public Form1()
{
InitializeComponent();
timerTest.Tick += new EventHandler(timerTest_Tick);
timerTest.Interval = 1000;
timerTest.Start();
}
private void buttonTest_Click(object sender, EventArgs e)
{
push = true;
}
private void timerTest_Tick(object sender, EventArgs e)
{
ticks++;
labelTest.Text = ticks.ToString();
if(running == false)
{
running = true;
backgroundWorkerTest.RunWorkerAsync();
}
}
public void activate()
{
ZmienIntervalNaAwaryjny = true;
}
public bool ZmienIntervalNaAwaryjny = false;
private void DoWork(object sender, DoWorkEventArgs e)
{
if(push == true)
{
activate();
}
}
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
if(ZmienIntervalNaAwaryjny == true)
{
timerTest.Stop();
timerTest.Interval = 12000;
timerTest.Start();
}
ZmienIntervalNaAwaryjny = false;
running = false;
}
}

C# Singleton form not opening correctly from Timer, opening correctly from button press

I have a singletone form that can be opened from a ribbon button or that will check every minute whether it should be open after passing a few conditional checks.
When opening the form from the ribbon button, it works correctly every time.
When opening on the timer, the form does not get rendered correctly, any place a control should be is just displayed as a white rectangle. Screenshots below.
ThisAddIn.cs
using Timer = System.Timers.Timer;
public partial class ThisAddIn
{
private Timer ticker;
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer(5 * 60 * 1000);
ticker.AutoReset = true;
ticker.Elapsed += new System.Timers.ElapsedEventHandler(checkForOverdue);
ticker.Start();
}
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
bool overdue = false;
foreach (Reminder reminder in reminders)
{
DateTime now = DateTime.Now;
if (reminder.time <= now)
{
overdue = true;
break;
}
}
if (overdue)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
}
Ribbon.cs
public partial class Ribbon
{
private void reminderListButton_Click(object sender, RibbonControlEventArgs e)
{
RemindersList form = RemindersList.CreateInstance();
if (form != null)
{
form.Show();
}
}
}
RemindersList.cs
public partial class RemindersList : Form
{
private static RemindersList _singleton;
private RemindersList()
{
InitializeComponent();
this.FormClosed += new FormClosedEventHandler(f_formClosed);
}
private static void f_formClosed(object sender, FormClosedEventArgs e)
{
_singleton = null;
}
public static RemindersList CreateInstance(List<Reminder> rs)
{
if (_singleton == null)
{
_singleton = new RemindersList(rs);
_singleton.Activate();
// Flash in taskbar if not active window
FlashWindow.Flash(_singleton);
return _singleton;
}
else
{
return null;
}
}
}
EDIT - SOLUTION
Per sa_ddam213's answer, I changed out the System.Timers.Timer for a Windows.Forms.Timer and it's now working just how I wanted.
Code changes:
ThisAddIn.cs
using Timer = System.Windows.Forms.Timer;
public partial class ThisAddIn {
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
ticker = new Timer();
ticker.Interval = 5 * 60 * 1000;
ticker.Tick += new EventHandler(checkForOverdue);
ticker.Start();
}
// Also needed to change the checkForOverdue prototype as follows:
private void checkForOverdue(object sender, EventArgs e)
}
You can't touch UI controls/elements with any other thread than the UI thread, in your case the System.Timer is running on another thread and the window will never open
Try switching to a Windows.Forms.Timer
Or invoke the call back to the UI thread.
private void checkForOverdue(object sender, System.Timers.ElapsedEventArgs e)
{
base.Invoke(new Action(() =>
{
/// all your code here
}));
}
I suspect that the timer event handler is not launched on the UI thread, which could cause all sorts of problems. I would check that first and ensure that the UI stuff is actually done on the UI thread.

Categories

Resources