Do WPF have Touch-and-Hold gesture? I cannot find event for that, so I tried to implement one for myself. I know that there is Stylus class but in WPF it does not help me. If there aren't one there is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace WebControlTouch
{
/// <summary>
/// Due to lack of Touch-and-Hold gesture, here is implementation of it. Stupid M$.
/// </summary>
public static class Touch_and_Hold
{
#region Constructor + methods
/// <summary>
/// Static constructor which creates timer object with 1000ms interval, also sets parameters of Timer.
/// </summary>
static Touch_and_Hold()
{
gestureTimer = new Timer(1000);
gestureTimer.AutoReset = false;
gestureTimer.Elapsed += gestureTimer_Elapsed;
}
/// <summary>
/// On elasped (time ofc)
/// </summary>
/// <seealso cref="gestureTimer"/>
static void gestureTimer_Elapsed(object sender, ElapsedEventArgs e)
{
occured = true;
}
/// <summary>
/// Call it on OnTouchDown event.
/// It will start timer and will count time of touch
/// </summary>
/// <returns>Returns that gesture occured</returns>
public static void onTouch()
{
gestureTimer.Start();
}
/// <summary>
/// Call it on touch up mainwindow event (or somewhere else)
/// It stops gesture timer
/// </summary>
public static void onTouchUp()
{
occured = false;
}
#endregion
#region Members + properties
/// <summary>
/// Timer for measuring touchTime
/// </summary>
private static Timer gestureTimer;
/// <summary>
/// Do tap-and-hold occured
/// </summary>
private static bool occured = false;
/// <summary>
/// Property for getting occured flag
/// </summary>
public static bool occuredGesture
{
get { return occured; }
}
#endregion
}
}
If yes, please tell me name of the event. If not - try to steer me to solution.
Any help will be very appreciated.
It is possible to do that in an awaitable fashion. Create a timer with specific interval. Start it when user tapped and return the method when timer elapsed. If user release the hand, return the method with false flag.
public static Task<bool> TouchHold(this FrameworkElement element, TimeSpan duration)
{
DispatcherTimer timer = new DispatcherTimer();
TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();
timer.Interval = duration;
MouseButtonEventHandler touchUpHandler = delegate
{
timer.Stop();
if (task.Task.Status == TaskStatus.Running)
{
task.SetResult(false);
}
};
element.PreviewMouseUp += touchUpHandler;
timer.Tick += delegate
{
element.PreviewMouseUp -= touchUpHandler;
timer.Stop();
task.SetResult(true);
};
timer.Start();
return task.Task;
}
For more information, read this post.
I've previously achieved this by create a custom control that extends button to delay the trigger of a button command after a delay on press-and-hold.
public class DelayedActionCommandButton : Button
First dependency properties:
public static readonly DependencyProperty DelayElapsedProperty =
DependencyProperty.Register("DelayElapsed", typeof(double), typeof(DelayedActionCommandButton), new PropertyMetadata(0d));
public static readonly DependencyProperty DelayMillisecondsProperty =
DependencyProperty.Register("DelayMilliseconds", typeof(int), typeof(DelayedActionCommandButton), new PropertyMetadata(1000));
public double DelayElapsed
{
get { return (double)this.GetValue(DelayElapsedProperty); }
set { this.SetValue(DelayElapsedProperty, value); }
}
public int DelayMilliseconds
{
get { return (int)this.GetValue(DelayMillisecondsProperty); }
set { this.SetValue(DelayMillisecondsProperty, value); }
}
These give us a control on how the delay should be and an output of how long is left.
Next I create an animation, to control the elapsed amount which when complete fires the command. There is also a cancel delay method:
private void BeginDelay()
{
this._animation = new DoubleAnimationUsingKeyFrames() { FillBehavior = FillBehavior.Stop };
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.DelayMilliseconds)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.Completed += (o, e) =>
{
this.DelayElapsed = 0d;
this.Command.Execute(this.CommandParameter); // Replace with whatever action you want to perform
};
this.BeginAnimation(DelayElapsedProperty, this._animation);
}
private void CancelDelay()
{
// Cancel animation
this.BeginAnimation(DelayElapsedProperty, null);
}
Finally, we wire up the event handlers:
private void DelayedActionCommandButton_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
{
this.BeginDelay();
}
private void DelayedActionCommandButton_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
{
this.CancelDelay();
}
When used in XAML, you can optionally create a template that can animate based on the value of DelayElapsed to provide a countdown, or visual cue such as an expanding border, whatever takes your fancy.
Related
i'm new with xamarin forms. I'm writing an app and i need to create a function that allow call api continuously to check the change of data, if have any change, i will handle something.
I'm looking for the solution but nothing, please help me :(
Thread is an idea?
Okay so first of all you need to poll the API in order to receive the data that you need to check. To do this you can implement my PollingTimer.cs class:
using System;
using System.Threading;
using Xamarin.Forms;
namespace CryptoTracker.Helpers
{
/// <summary>
/// This timer is used to poll the middleware for new information.
/// </summary>
public class PollingTimer
{
private readonly TimeSpan timespan;
private readonly Action callback;
private CancellationTokenSource cancellation;
/// <summary>
/// Initializes a new instance of the <see cref="T:CryptoTracker.Helpers.PollingTimer"/> class.
/// </summary>
/// <param name="timespan">The amount of time between each call</param>
/// <param name="callback">The callback procedure.</param>
public PollingTimer(TimeSpan timespan, Action callback)
{
this.timespan = timespan;
this.callback = callback;
this.cancellation = new CancellationTokenSource();
}
/// <summary>
/// Starts the timer.
/// </summary>
public void Start()
{
CancellationTokenSource cts = this.cancellation; // safe copy
Device.StartTimer(this.timespan,
() => {
if (cts.IsCancellationRequested) return false;
this.callback.Invoke();
return true; // or true for periodic behavior
});
}
/// <summary>
/// Stops the timer.
/// </summary>
public void Stop()
{
Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel();
}
}
}
Now that you have added a polling timer to your project, you must now go to the content page that you wish to poll from. Here is the pseudo code for what your content page should look like:
namespace YourApp.Views
{
public class MainPage : ContentPage
{
PollingTimer timer;
public MainPage ()
{
//PUT UI CODE HERE
Content = layout;
//Instantiate Polling timer to call handleaction every 5 seconds
timer = new PollingTimer(TimeSpan.FromSeconds(5), HandleAction);
}
/// <summary>
/// When the page enters the users view, this procedure is called.
/// </summary>
protected override void OnAppearing()
{
base.OnAppearing();
//Handle action and start your timer
HandleAction();
timer.Start();
}
/// <summary>
/// When the page disappears from the users view this procedure is called.
/// </summary>
protected override void OnDisappearing()
{
base.OnDisappearing();
//Stop your timer
timer.Stop(); //Stop the timer
}
/// <summary>
/// Callback for the timer.
/// </summary>
void HandleAction()
{
//Make call to your api to get data
//Compare data with data you currently have
// Do whatever you want.
}
I hope this helps you. Let me know if you need any more help :)
You can use Timer Class for you issue.
I'm using this code that works perfectly except I want to add the ability to know when a startSpeaking call is done speaking.
static class VoiceEffect
{
SpeechSynthesizer reader = new SpeechSynthesizer();
private volatile bool _isCurrentlySpeaking = false;
/// <summary>Event handler. Fired when the SpeechSynthesizer object starts speaking asynchronously.</summary>
private void StartedSpeaking(object sender, SpeakStartedEventArgs e)
{ _isCurrentlySpeaking = true; }
/// <summary>Event handler. Fired when the SpeechSynthesizer object finishes speaking asynchronously.</summary>
private void FinishedSpeaking(object sender, SpeakCompletedEventArgs e)
{ _isCurrentlySpeaking = false; }
private VoiceEffect _instance;
/// <summary>Gets the singleton instance of the VoiceEffect class.</summary>
/// <returns>A unique shared instance of the VoiceEffect class.</returns>
public VoiceEffect GetInstance()
{
if(_instance == null)
{ _instance = new VoiceEffect(); }
return _instance;
}
/// <summary>
/// Constructor. Initializes the class assigning event handlers for the
/// SpeechSynthesizer object.
/// </summary>
private VoiceEffect()
{
reader.SpeakStarted += new EventHandler<SpeakStartedEventArgs>(StartedSpeaking);
reader.SpeakCompleted += new EventHandler<SpeakCompletedEventArgs>(FinishedSpeaking);
}
/// <summary>Speaks stuff.</summary>
/// <param name="str">The stuff to speak.</param>
public void startSpeaking(string str)
{
reader.Rate = -2; // Voice effects.
reader.Volume = 100;
// if the reader's currently speaking anything,
// don't let any incoming prompts overlap
while(_isCurrentlySpeaking)
{ continue; }
reader.SpeakAsync(str);
}
/// <summary>Creates a new thread to speak stuff into.</summary>
/// <param name="str">The stuff to read.</param>
public void createVoiceThread(string str)
{
Thread voicethread = new Thread(() => startSpeaking(str)); // Lambda Process
voicethread.IsBackground = true;
voicethread.Start();
}
}
from this question https://stackoverflow.com/a/17153718/1137006
I call that by writing this in another class:
TextToSpeech.startSpeaking(text);
I want to know when that call is done and it has finished speaking.
Possibly as an event?
I can see the event inside the VoiceEffect class but I don't know how to get it to fire in the class that do the startSpeaking() call.
The reason I need to know is to change a WPF-control after something has been spoken.
Is that possible to add to this code?
Edit: To clarify
In my MainWindow class I can call multiple TextToSpeech from the file TextToSpeech.cs that has the VoiceEffect class and startSpeaking method.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TextToSpeech.startSpeaking("Test1");
TextToSpeech.startSpeaking("Test2");
TextToSpeech.startSpeaking("Test3");
TextToSpeech.startSpeaking("Test4");
}
}
They will all wait for the one previous to be done before speaking and does also let the program continue forward during the time it's speaking.
I want to know if I can get an event or something when for example "Test2" has been spoken and then change a WPF-control in the MainWindow? For example hiding a Text label.
Is this what you are looking for?
public MainWindow() {
InitializeComponent();
SpeechSynthesizer reader = new SpeechSynthesizer();
reader.SpeakCompleted += Reader_SpeakCompleted;
}
void Reader_SpeakCompleted(object sender, SpeakCompletedEventArgs e) {
}
How can I create a loop that will be continuously executed whenever the message loop is idle in WPF?
The goal here is to perform some long running graphical update, such as refreshing a PicktureBox, that is capable of consuming whatever free resources are available but shouldn't freeze the UI or otherwise take priority over any other operations in the message queue.
I noticed this blog post which provides the code to do this in a winforms application, but I don't know how to translate it to a WPF application. Below is the code of a WinForms render loop class that I made based on the other article:
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace Utilities.UI
{
/// <summary>
/// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible).
/// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
/// </summary>
public sealed class WinFormsAppIdleHandler
{
private readonly object _completedEventLock = new object();
private event EventHandler _applicationLoopDoWork;
//PRIVATE Constructor
private WinFormsAppIdleHandler()
{
Enabled = false;
SleepTime = 10;
Application.Idle += Application_Idle;
}
/// <summary>
/// Singleton from:
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </summary>
private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler());
public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } }
/// <summary>
/// Gets or sets if must fire ApplicationLoopDoWork event.
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the minimum time betwen ApplicationLoopDoWork fires.
/// </summary>
public int SleepTime { get; set; }
/// <summary>
/// Fires while the UI is free to work. Sleeps for "SleepTime" ms.
/// </summary>
public event EventHandler ApplicationLoopDoWork
{
//Reason of using locks:
//http://stackoverflow.com/questions/1037811/c-thread-safe-events
add
{
lock (_completedEventLock)
_applicationLoopDoWork += value;
}
remove
{
lock (_completedEventLock)
_applicationLoopDoWork -= value;
}
}
/// <summary>
/// FINALMENTE! Imagem ao vivo sem travar! Muito bom!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Application_Idle(object sender, EventArgs e)
{
//Try to update interface
while (Enabled && IsAppStillIdle())
{
OnApplicationIdleDoWork(EventArgs.Empty);
//Give a break to the processor... :)
//8 ms -> 125 Hz
//10 ms -> 100 Hz
Thread.Sleep(SleepTime);
}
}
private void OnApplicationIdleDoWork(EventArgs e)
{
var handler = _applicationLoopDoWork;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Gets if the app still idle.
/// </summary>
/// <returns></returns>
private static bool IsAppStillIdle()
{
bool stillIdle = false;
try
{
Message msg;
stillIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
catch (Exception e)
{
//Should never get here... I hope...
MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message);
}
return stillIdle;
}
#region Unmanaged Get PeekMessage
// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
}
}
The best way to do this is to use the per-frame callbacks provided by the static CompositionTarget.Rendering event.
To elaborate a bit on the answer of Oren, you can attach a method to the Rendering event like this:
CompositionTarget.Rendering += Loop;
The Loop function can then update the properties of an element, in this example an element positioned on a Canvas:
private void Loop(object sender, EventArgs e)
{
LeftPos++;
Canvas.SetLeft(ball, LeftPos);
}
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.compositiontarget.rendering?view=net-5.0
I'm trying to build a display for a die roll. What I want to do is flicker images of random faces on the die, then end with the face that shows the number rolled. After this happens, I want the function to continue and return the number of the die roll. Here's what I have
public int RollDie()
{
RollNum = dieRoll.Next(1, 7);
DispCount = 0;
Timer Time = new Timer();
Time.Interval = TimerInterval;
Time.Tick += DisplayRollHandler;
Time.Start();
System.Threading.Thread DispThread = new System.Threading.Thread(Time.Start);
DispThread.Start();
DispThread.Join();
return RollNum;
}
private void DisplayRollHandler(object sender, EventArgs evt)
{
if (DispCount < TargetDispCount)
{
Random Nums = new Random();
Display.BackgroundImage = Faces[Nums.Next(0, 6)];
DispCount++;
}
else
{
((Timer)sender).Stop();
Display.BackgroundImage = Faces[RollNum - 1];
}
}
where dieRoll is a random object and Display is a Panel. The image flicker works, and it does return the number of the roll consistently. Unfortunately, it doesn't wait for the display flicker to finish before continuing, which is a problem when I have automatic messages that pop up after the die is rolled.
I'm a fairly inexperienced programmer, so I'm probably missing a really basic concept. I know that if I could abstract this into a method call, I can wait for a method call to finish, but I can't figure out how to do that without using a Thread.Sleep call and freezing the program.
Any suggestions?
There is a fundamental error with this solution in that the randomizer should never be instantiated within the dice object itself. A simple test will show why. Simply add another dice object to the form and roll both at the same time. Notice something funny? They are always the same!
This is because, as a default, the randomizer used the current time to seed the generator. Creating two (or more) objects in the same part of code will result in all of the dice objects having the same seed, and thus the same result when rolled every time.
A better solution would involve creating a static singleton class that would handle all the rolling (randomizing) all the dice for you.
Here is a quick example (using a Dice class that is a bit more generic):
public static class DiceRoller
{
private static Random _roller;
public static void RollDice(Dice dice)
{
if (dice.Faces.Count < 1)
throw new InvalidOperationException("A dice must contain at least 1 side to be rolled.");
if (_roller == null)
_roller = new Random();
int index = _roller.Next(dice.Faces.Count);
dice.SetFacingIndex(index);
}
}
Just wrote a little Dice class which will provide the desired values to you:
public class Dice
{
private Random _Random;
private BackgroundWorker _Worker;
/// <summary>
/// Initializes a new instance of the <see cref="Dice"/> class.
/// </summary>
public Dice()
{
_Random = new Random();
InitializeDefaultValues();
InitializeBackgroundWorker();
}
/// <summary>
/// Occurs when the dice finished rolling.
/// </summary>
public event EventHandler Rolled;
/// <summary>
/// Occurs while the dice is rolling and the value has changed.
/// </summary>
public event EventHandler RollingChanged;
/// <summary>
/// Gets or sets the including maximum value that the dice can return.
/// </summary>
/// <value>
/// The maximum value.
/// </value>
[DefaultValue(6)]
public int Maximum { get; set; }
/// <summary>
/// Gets or sets the including minimum value that the dice can return.
/// </summary>
/// <value>
/// The minimum.
/// </value>
[DefaultValue(1)]
public int Minimum { get; set; }
/// <summary>
/// Gets the result that this dice currently has.
/// </summary>
public int Result { get; private set; }
/// <summary>
/// Gets or sets the duration of the rolling.
/// </summary>
/// <value>
/// The duration of the rolling.
/// </value>
[DefaultValue(typeof(TimeSpan), "00:00:03")]
public TimeSpan RollingDuration { get; set; }
/// <summary>
/// Starts rolling the dice.
/// </summary>
public void Roll()
{
if (!_Worker.IsBusy)
{
CheckParameters();
_Worker.RunWorkerAsync();
}
}
private void CheckParameters()
{
if (Minimum >= Maximum)
{
throw new InvalidOperationException("Minimum value must be less than the Maximum value.");
}
if (RollingDuration <= TimeSpan.Zero)
{
throw new InvalidOperationException("The RollingDuration must be greater zero.");
}
}
private void InitializeBackgroundWorker()
{
_Worker = new BackgroundWorker();
_Worker.WorkerReportsProgress = true;
_Worker.DoWork += OnWorkerDoWork;
_Worker.ProgressChanged += OnWorkerProgressChanged;
_Worker.RunWorkerCompleted += OnWorkerRunWorkerCompleted;
}
private void InitializeDefaultValues()
{
Minimum = 1;
Maximum = 6;
Result = Minimum;
RollingDuration = TimeSpan.FromSeconds(3);
}
private void OnWorkerDoWork(object sender, DoWorkEventArgs e)
{
var finishTime = DateTime.UtcNow + RollingDuration;
while (finishTime > DateTime.UtcNow)
{
Result = _Random.Next(Minimum, Maximum + 1);
_Worker.ReportProgress(0);
// ToDo: Improve sleep times for more realistic rolling.
Thread.Sleep(50);
}
}
private void OnWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
RaiseEvent(RollingChanged);
}
private void OnWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
RaiseEvent(Rolled);
}
private void RaiseEvent(EventHandler handler)
{
var temp = handler;
if (temp != null)
{
temp(this, EventArgs.Empty);
}
}
}
In a first and simple example i simply added a button to a form (buttonRoll) and a label (labelDiceResult) and added the follwing code (don't forget to add the initialize method into the form constructor):
private void InitializeDice()
{
_Dice = new Dice();
_Dice.RollingChanged += OnDiceRollingChanged;
_Dice.Rolled += OnDiceRolled;
}
void OnDiceRolled(object sender, EventArgs e)
{
buttonRoll.Enabled = true;
}
void OnDiceRollingChanged(object sender, EventArgs e)
{
// ToDo: Select desired picture from image list depending on _Dice.Result
labelDiceResult.Text = _Dice.Result.ToString();
}
private void OnButtonRollClick(object sender, EventArgs e)
{
buttonRoll.Enabled = false;
_Dice.Roll();
}
As a last step i would maybe tweak the Thread.Sleep(50) call to use different values over time by using a calculated list depending on the rising part of a sinus and the desired duration to let the dice slow down over time. But i let this part open for the reader (or a next question).
I am relatively new to C#/.Net. I'm developing a desktop application that requires multi threading. I came up with the following pattern below as a base. I was wondering if anyone could point out how to make it better in terms of coding, being thread safe, and being efficient.
Hopefully this makes some sense.
public abstract class ThreadManagerBase
{
// static class variables
private static ThreadManagerBase instance = null;
private static BackgroundWorker thread = null;
private static ProgressBarUIForm progress = null;
/// <summary>
/// Create a new instance of this class. The internals are left to the derived class to figure out.
/// Only one instance of this can run at any time. There should only be the main thread and this thread.
/// </summary>
public abstract static ThreadManagerBase NewInstance();
/// <summary>
/// Clears the instance.
/// </summary>
public static void ClearInstance()
{
instance = null;
}
/// <summary>
/// Initializes the background worker with some presets.
/// Displays progress bar.
/// </summary>
private abstract static void InitializeThread()
{
thread = new BackgroundWorker();
thread.WorkerReportsProgress = true;
thread.WorkerSupportsCancellation = true;
thread.DoWork += new DoWorkEventHandler(thread_DoWork);
thread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(thread_RunWorkerCompleted);
thread.ProgressChanged += new ProgressChangedEventHandler(thread_ProgressChanged);
thread.RunWorkerAsync();
progress = new ProgressBarUIForm();
progress.EnableCancelButton = true;
progress.UserCanceled += new EventHandlerCancelClicked(progress_UserCanceled);
progress.ShowDialog();
thread.Dispose();
thread = null;
}
private static void progress_UserCanceled(bool userCanceled)
{
thread.CancelAsync();
}
private static void thread_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progress.SetProgressLevel = e.ProgressPercentage;
progress.SetProgressMessage = e.UserState.ToString();
}
private static void thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progress.Close();
progress = null;
}
private static void thread_DoWork(object sender, DoWorkEventArgs e)
{
ProcessWork();
}
private abstract static void ProcessWork()
{
// do actuall stuff here.
// the derived classes will take care of the plumbing.
}
}
Have you looked into the Microsoft Parallel Extensions to .NET Framework 3.5? It's a pretty good library that takes a lot of the work out of threading.
There are also a lot of articles on MSDN about threading patterns that you should research too. Threading can get really complicated really fast. It's nice to have someone else to have though of all the important stuff that can go wrong, and simplify it down to a library or a pattern. Of course, there's danger in that too if you don't understand the gotchas of any particular solution. So, make sure you research well whatever solution you choose.
I don't see a good reason to create this abstraction over BackgroundWorker.
If you insist, just a warning: I'm not sure if it changed in later releases, but in NET 2.0, it wasn't possible to really cancel the DoWork handler (unless it checked once in a while if it was asked to stop). Read here for a solution.
I have done something similar to this. There is a good reason if you do have multiple tasks that you want to perform, but you dont want to have BackgroundWorker code replicated through the entire project. I dont have the progressbar tied to the actual base class, I just have that in the main form. Here is the solution I came up with:
The following is the base class:
public abstract class Operation
{
#region public Event Handlers
///
/// The event that updates the progress of the operation
///
public event OperationProgressChangedEventHandler OperationProgressChanged;
///
/// The event that notifies that the operation is complete (and results)
///
public event OperationCompletedEventHandler OperationCompleted;
#endregion
#region Members
// Whether or not we can cancel the operation
private bool mWorkerSupportsCancellation = false;
// The task worker that handles running the operation
private BackgroundWorker mOperationWorker;
// The operation parameters
private object[] mOperationParameters;
#endregion
///
/// Base class for all operations
///
public Operation(params object[] workerParameters)
{
mOperationParameters = workerParameters;
// Setup the worker
SetupOperationWorker();
}
#region Setup Functions
///
/// Setup the background worker to run our Operations
///
private void SetupOperationWorker()
{
mOperationWorker = new BackgroundWorker();
mOperationWorker.WorkerSupportsCancellation = mWorkerSupportsCancellation;
mOperationWorker.WorkerReportsProgress = true;
mOperationWorker.WorkerSupportsCancellation = true;
mOperationWorker.DoWork += new DoWorkEventHandler(OperationWorkerDoWork);
mOperationWorker.ProgressChanged += new ProgressChangedEventHandler(OperationWorkerProgressChanged);
mOperationWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OperationWorkerRunWorkerCompleted);
}
#endregion
#region Properties
///
/// Whether or not to allow the user to cancel the operation
///
public bool CanCancel
{
set
{
mWorkerSupportsCancellation = value;
}
}
#endregion
#region Operation Start/Stop Details
///
/// Start the operation with the given parameters
///
/// The parameters for the worker
public void StartOperation()
{
// Run the worker
mOperationWorker.RunWorkerAsync(mOperationParameters);
}
///
/// Stop the operation
///
public void StopOperation()
{
// Signal the cancel first, then call cancel to stop the test
if (IsRunning())
{
// Sets the backgroundworker CancelPending to true, so we can break
// in the sub classes operation
mOperationWorker.CancelAsync();
// This allows us to trigger an event or "Set" if WaitOne'ing
Cancel();
// Wait for it to actually stop before returning
while (IsRunning())
{
Application.DoEvents();
}
}
}
///
/// Whether or not the operation is currently running
///
///
public bool IsRunning()
{
return mOperationWorker.IsBusy;
}
#endregion
#region BackgroundWorker Events
///
/// Fires when the operation has completed
///
///
///
private void OperationWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Allow the sub class to clean up anything that might need to be updated
Clean();
// Notify whoever is register that the operation is complete
if (OperationCompleted != null)
{
OperationCompleted(e);
}
}
///
/// Fires when the progress needs to be updated for a given test (we might not care)
///
///
///
private void OperationWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Notify whoever is register of what the current percentage is
if (OperationProgressChanged != null)
{
OperationProgressChanged(e);
}
}
///
/// Fires when we start the operation (this does the work)
///
///
///
private void OperationWorkerDoWork(object sender, DoWorkEventArgs e)
{
// Run the operation
Run(sender, e);
}
#endregion
#region Abstract methods
///
/// Abstract, implemented in the sub class to do the work
///
///
///
protected abstract void Run(object sender, DoWorkEventArgs e);
///
/// Called at the end of the test to clean up anything (ex: Disconnected events, etc)
///
protected abstract void Clean();
///
/// If we are waiting on something in the operation, this will allow us to
/// stop waiting (ex: WaitOne).
///
protected abstract void Cancel();
#endregion
}
The following is an example test class for the example I posted:
class TestOperation : Operation
{
AutoResetEvent mMsgRec;
public TestOperation(params object[] workerParameters)
: base(workerParameters)
{
CanCancel = true;
mMsgRec = new AutoResetEvent(false);
//mSomeEvent += DoSomething();
}
protected override void Cancel()
{
mMsgRec.Set();
}
protected override void Clean()
{
//mSomeEvent -= DoSomething();
}
protected override void Run(object sender, DoWorkEventArgs e)
{
BackgroundWorker bg = (BackgroundWorker)sender;
for (int i = 0; !bg.CancellationPending && (i < 90); i++)
{
bg.ReportProgress(i);
Thread.Sleep(100);
}
for (int i = 90; !bg.CancellationPending && (i < 100); i++)
{
mMsgRec.WaitOne(2000, false);
bg.ReportProgress(i);
}
if (bg.CancellationPending)
{
e.Cancel = true;
}
else
{
e.Result = "Complete"; // Or desired result
}
}
}
And here is what the main form would look like (very basic example):
public partial class Form1 : Form
{
TestOperation t;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
t = new TestOperation();
t.CanCancel = true;
t.OperationProgressChanged += new OperationProgressChangedEventHandler(t_OperationProgressChanged);
t.OperationCompleted += new OperationCompletedEventHandler(t_OperationCompleted);
t.StartOperation();
}
void t_OperationCompleted(RunWorkerCompletedEventArgs e)
{
progressBar1.Value = 0;
}
void t_OperationProgressChanged(ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void button2_Click(object sender, EventArgs e)
{
t.StopOperation();
}
}
I'm currently investigation Threadmare over at http://sklobovsky.nstemp.com/community/threadmare/threadmare.htm for a C# project. It looks very, very useful. It's in Delphi, but the principles apply to any language that can handle events.
You don't need a BackgroundWorker unless you want to be spoonfed, normal threads are perfectly acceptable, as long as you follow the rules.