How to implement Interface in Mainwindow - c#

I'm rewriting my project, so it's easier to edit in future, and want to implement interface.
I've implemented the interface, but it doesn't work in MainWindow, I'm not able to call the method.
So I've tried to use the PalindromeChecker as the default implementation
PalindromeChecker = new PalindromeChecker(); , so I can call the method but it didn't work.
interface ICheckPalindrome
{
bool IsPalindrome(string text);
}
public class PalindromeChecker : ICheckPalindrome
{
/// <summary>
/// Method for checking if the word/text is a palindrome.
/// </summary>
public bool IsPalindrome(string text)
{
......
//Code
}
}
}
namespace TextChecker
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lblInput.Foreground = Brushes.ForestGreen;
lblResult.Foreground = Brushes.ForestGreen;
lblTitel.Foreground = Brushes.ForestGreen;
}
/// <summary>
/// User input and checking the input if the word a palindrome is.
/// </summary>
private void InputText_TextChanged(object sender, TextChangedEventArgs e)
{
string text = InputText.Text;
bool isPalindrome = PalindromeChecker.IsPalindrome(text);
OutputText.Text = text + (isPalindrome ? " is a palindrome" : " is NOT a palindrome");
if (InputText.Text == string.Empty)
{
OutputText.Clear();
}
}
private void ButtonClicked(object sender, RoutedEventArgs e)
{
SubWindow subWindow = new SubWindow();
subWindow.Show();
}
}
}
public class PalindromeChecker : ICheckPalindrome
{
/// <summary>
/// Method for checking if the word/text is a palindrome.
/// </summary>
public bool IsPalindrome(string text)
{
int min = 0;
int max = text.Length - 1;
while (true)
{
if (min > max)
{
return true;
}
char a = text[min];
char b = text[max];
if (a != b)
{
return false;
}
min++;
max--;
}
}
}
I'm really stuck here, I would like to thank you in advance.

As far as can see, you don't want interfaces, class instances to check for a palindrome (do you really want to implement several algorithms to choose from?), but a static method:
// Let's class be partial one: if you want to add a method it it
// you don't have to modify this code but
// add a chunk public partial static class MyStringRoutine {...}
public partial static class MyStringRoutine {
public static bool IsPalindrome(string text) {
//DONE: do not forget about special cases
if (string.IsNullOrEmpty(text))
return true;
for (int i = 0; i < text.Length / 2; ++i)
if (text[i] != text[text.Length - 1 - i])
return false;
return true;
}
}
Then you can use it:
private void InputText_TextChanged(object sender, TextChangedEventArgs e) {
if (string.IsNullOrEmpty(InputText.Text))
OutputText.Clear();
else {
string suffix = MyStringRoutine.IsPalindrome(InputText.Text)
? "is a palindrome"
: "is NOT a palindrome";
OutputText.Text = $"{InputText.Text} {suffix}";
}
}
If you have to implement ICheckPalindrome interface, and thus to work with class instance, you have to create the instance:
private void InputText_TextChanged(object sender, TextChangedEventArgs e) {
if (string.IsNullOrEmpty(InputText.Text))
OutputText.Clear();
else {
// You have to create the instance (checker)
ICheckPalindrome checker = new PalindromeChecker();
// IsPalindrome is the instance method; you can't call is as
// PalindromeChecker.IsPalindrome
string suffix = checker.IsPalindrome(InputText.Text)
? "is a palindrome"
: "is NOT a palindrome";
OutputText.Text = $"{InputText.Text} {suffix}";
}
}

From code you've posted it'd seem that interface ICheckPalindrome is less accessible than PalindromeChecker. Not sure how and why your compiler would let that slip, but you should try changing your code to
public interface ICheckPalindrome
{
bool IsPalindrome(string text);
}
So the interface will reflect its implementation (or rather other way around).

Related

How to implement interface

I want to change my code, also I want to use Interfaces so if someone wants a future so it could be easily implemented, without changing too much code.
So I successfully implemented my interface IPalindromeChecker. But the problem is in MainWindow now. So I'm not sure but I would make another Interface and with public void Output(string text) method. I've tried adding a method in IPalindromeChecker public void Output(string text) but it didn't work.
interface ICheckPalindrome
{
bool IsPalindrome(string text);
}
public class PalindromeChecker : ICheckPalindrome
{
/// <summary>
/// Method for checking if the word/text is a palindrome.
/// </summary>
public bool IsPalindrome(string text)
{
int min = 0;
int max = text.Length - 1;
while (true)
{
if (min > max)
{
return true;
}
char a = text[min];
char b = text[max];
if (a != b)
{
return false;
}
min++;
max--;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lblInput.Foreground = Brushes.ForestGreen;
lblResult.Foreground = Brushes.ForestGreen;
lblTitel.Foreground = Brushes.ForestGreen;
}
/// <summary>
/// User input and checking the input if the word a palindrome is.
/// </summary>
private void InputText_TextChanged(object sender, TextChangedEventArgs e)
{
string text = InputText.Text;
bool isPalindrome = TextChecker.PalindromeChecker(text); // HERE IS THE PROBLEM
OutputText.Text = text + (isPalindrome ? " is a palindrome" : " is NOT a palindrome");
if (InputText.Text == string.Empty)
{
OutputText.Clear();
}
}
public class PalindromeChecker : ICheckPalindrome
{
/// <summary>
/// Method for checking if the word/text is a palindrome.
/// </summary>
public bool IsPalindrome(string text)
{
int min = 0;
int max = text.Length - 1;
while (true)
{
if (min > max)
{
return true;
}
char a = text[min];
char b = text[max];
if (a != b)
{
return false;
}
min++;
max--;
}
}
}
It's unclear what TextChecker.PalindromeChecker is but if you want to be able to switch implementations of the ICheckPalindrome interface without having to modify your MainWindow, you should inject the window with an implementation of ICheckPalindrome at runtime and write your code against the interface.
You could for example use a property to do this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lblInput.Foreground = Brushes.ForestGreen;
lblResult.Foreground = Brushes.ForestGreen;
lblTitel.Foreground = Brushes.ForestGreen;
//use the PalindromeChecker as the default implementation
PalindromeChecker = new PalindromeChecker();
}
public ICheckPalindrome PalindromeChecker { get; set; } //<--
private void InputText_TextChanged(object sender, TextChangedEventArgs e)
{
string text = InputText.Text;
bool isPalindrome = PalindromeChecker.IsPalindrome(text);
OutputText.Text = text + (isPalindrome ? " is a palindrome" : " is NOT a palindrome");
if (InputText.Text == string.Empty)
{
OutputText.Clear();
}
}
}
Switching to another implementation is then as simple as just setting the PalindromeChecker property of the MainWindow to an instance of another class that implements the same ICheckPalindrome interface.

Visual C# returning value from an event handler

I have a form that has a button to get a method executed in another class.
Code on the form:
public delegate void CustomPreviewCreate();
public static event CustomPreviewCreate CustomPreviewCreate_Do;
private void CreatePreview()
{
if (CustomPreviewCreate_Do !=null)
{
CustomPreviewCreate_Do();
}
}
This event then gets handled in another class. What I would like to achieve is that I can feed back to the form some form of return value if the method correctly executed.
What I tried so far does not get me the result.
Here is the code:
public void Initialize()
{
SubAsstViewPartControl.CustomPreviewCreate_Do += SubAsstViewPartControl_CustomPreviewCreate_Do;
// this gives me a the compiler error that the return type is wrong
}
private bool SubAsstViewPartControl_CustomPreviewCreate_Do()
{
// do stuff
return false;
}
Is there any direct way to return value from an event handler or I need to use a separate static field to store the event result in?
Update:
Per #Jon's comment, which seemed the simplest to me, I added an answer below demonstrating the simplest approach.
The common approach is to encapsulate your value in the type of EventArgs your event expects. For example, the Framework's CancelEventArgs contains a settable bool Cancel property, allowing each CancelEventHandler to assign a value. The sender can then read the property after the event has been invoked. You could also use a container-like EventArgs class if you want to collect separate values from individual event handlers. For example:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
public class SingleValueEventArgs : EventArgs
{
public int Value { get; set; }
}
public class MultiValueEventArgs : EventArgs
{
private List<int> _values = new List<int>(); // Private to prevent handlers from messing with each others' values
public IEnumerable<int> Values
{
get { return _values; }
}
public void AddValue(int value) { _values.Add(value); }
}
public class Exposer
{
public event EventHandler<SingleValueEventArgs> WantSingleValue;
public event EventHandler<MultiValueEventArgs> WantMultipleValues;
public void Run()
{
if (WantSingleValue != null)
{
var args = new SingleValueEventArgs();
WantSingleValue(this, args);
Console.WriteLine("Last handler produced " + args.Value.ToString());
}
if (WantMultipleValues != null)
{
var args = new MultiValueEventArgs();
WantMultipleValues(this, args);
foreach (var value in args.Values)
{
Console.WriteLine("A handler produced " + value.ToString());
}
}
}
}
public class Handler
{
private int _value;
public Handler(Exposer exposer, int value)
{
_value = value;
exposer.WantSingleValue += exposer_WantSingleValue;
exposer.WantMultipleValues += exposer_WantMultipleValues;
}
void exposer_WantSingleValue(object sender, SingleValueEventArgs e)
{
Console.WriteLine("Handler assigning " + _value.ToString());
e.Value = _value;
}
void exposer_WantMultipleValues(object sender, MultiValueEventArgs e)
{
Console.WriteLine("Handler adding " + _value.ToString());
e.AddValue(_value);
}
}
class Program
{
static void Main(string[] args)
{
var exposer = new Exposer();
for (var i = 0; i < 5; i++)
{
new Handler(exposer, i);
}
exposer.Run();
}
}
}
Per Jon Skeet's comment, which seemed the simplest to me, the simplest approach seems to be as follows:
public delegate bool CustomPreviewCreate(); // here we declare a return type
public static event CustomPreviewCreate CustomPreviewCreate_Do;
private void CreatePreview()
{
if (CustomPreviewCreate_Do !=null)
{
bool returnval = CustomPreviewCreate_Do();
}
}
And then:
// the method is declared to return the same type
bool SubAsstViewPartControl_CustomPreviewCreate_Do()
{
// do stuff
return true; // return the value of the type declared
}

creating a template method in a static method

I created a class InputBox that only has one method, which is static:
Public static Show(string i_Prompt)
this method creates an InputBoxForm and gives the form a method for this property: public Predicate<char> isKeyValid { get; set; }
which runs in here:
private void textBoxInput_KeyPress(object sender, KeyPressEventArgs e)
{
if (isKeyValid != null)
{
e.Handled = !isKeyValid(e.KeyChar);
}
}
the idea is that a developer will derive my class and create he's own logic on how to deal with getting characters from the user.
this implementation of the static Show method is:
public static string Show(string i_Prompt)
{
string input = string.Empty;
using (InputBoxForm form = new InputBoxForm(i_Prompt))
{
form.isKeyValid = s_Instance.keyValidationLogic;
if (form.ShowDialog() == DialogResult.OK)
{
input = form.Input;
}
}
return input;
}
the template is keyValidationLogic. (which return true for all the keys in the base InputBox)
the problem, as you can see that i cannot overwrite a static method.
how would I implement a template method in a static method?
I have to create an instance of the Input Box, but I want to use the derived class instance
the class InputBox is not static. i want it to be derived, so developers will be able to customize the logic of the input box.
thanks
I'm not sure your approach is the best way to to about this, but you should be able to solve your issue in the following way.
You can do this with ordinary inheritance:
class InputBox
{
protected virtual bool ValidateKey(char key)
{
// Allow anything
return true;
}
public string Show(string i_Prompt)
{
using (InputBoxForm form = new InputBoxForm(i_Prompt))
{
form.isKeyValid = this.ValidateKey;
if (form.ShowDialog() == DialogResult.OK)
{
return form.Input;
}
}
return string.Empty;
}
}
class DigitInputBox : InputBox
{
protected override bool ValidateKey(char key)
{
return key >= '0' && key <= '9';
}
}
To use:
(new MyCustomizedInputBox()).Show("Numbers only, please!");

Delegate SystemAction as constructor parameter

I have a winform I'm working on that needs change a label when it hits a specific phase and the same label when it's complete. I have an interface setup and a class that implements this interface. In the class constructor, I'm trying to add the Action<> as a parameter but I am having trouble processing what I need to do in order to get it to flow correctly.
Never could get it to work :/
While your example i cannot give specific example because it confuses me. I can still answer some questions.
Does this help?
private Action _methodOnProgress;
public ChipLoadFirmware(Action method)
{
_methodOnProgress = method;
}
public bool LoadFirmWare()
{
(p) = >
{
_methodOnProgress();
}
}
Maybe an event pattern here will be more reliable.
First add a
public event EventHandler Progress
to your ChipLoadFirmware class, and make it public in the manager class (lets assume it's called Manager and the ChiLoad instance is called Loader)
So, when you instance the manager in the form you add
Manager.Loader.Progress += (o, e) => { /* Change the label */ };
And finally in the (p) => { } do
Progress(this, EventArgs.Empty);
EDIT Here is your code with the changes, supose the class Form is your form and labelToChange the label which must be changed;
/// <summary>
/// Represents an object that can load the firmware
/// </summary>
public interface ILoadFirmware
{
/// <summary>
/// Loads firmware on a device
/// </summary>
bool LoadFirmware();
event EventHandler Progress;
event EventHandler Status;
}
public class ChipLoadFirmware : ILoadFirmware
{
private readonly log4net.ILog logger = Logging.Log4NetManager.GetLogger();
private readonly ImageLoader imageLoader = new ImageLoader ();
private bool abort = false;
private string cmdText = string.Empty;
private string errortext = string.Empty;
private string isaIp;
public event EventHandler Progress;
public event EventHandler Status;
/// <summary>
/// Sets up an ImageLoader
/// </summary>
/// <param name="isaTargetIpAddress">The IP address of the ISA to load the firmware on</param>
public ChipLoadFirmware (string isaTargetIpAddress)
{
this.isaIp = isaTargetIpAddress;
imageLoader.EventHandler += (s, e) => { };
logger.DebugFormat("Using IP Address {0}", isaTargetIpAddress);
}
/// <summary>
/// Loads the firmware onto the device
/// </summary>
/// <returns>Returns true if successful</returns>
public bool LoadFirmware()
{
bool result = imageLoader.Run(this.isaIp,
false,
Command.Command,
string.Empty,
CommandFlag.CommandFlag,
2000,
(p) => { Progress(this, EventArgs.Empty); },
(s) => { Status(this, EventArgs.Empty); },
ref this.abort,
out this.cmdText,
out this.errortext);
if (!result)
{
result = false;
throw new InvalidOperationException(
string.Format(
"ImageLoader failed with message: {0}",
errortext));
}
return result;
}
}
public class CalibrationManager : ICalibrationManager
{
const uint startAddress = 0x00000000;
const uint startAddress = 0x00000000;
private readonly log4net.ILog logger;
private readonly ISignalGenerator sigGenerator;
public readonly ILoadFirmware loadFirmware;
private readonly IReader reader;
private readonly IValueParser valueParser;
private readonly IRepository<CalibrationValue> calibrationRepository;
private readonly IRepository<UIDList> uidRepository;
private string uid;
public CalibrationManager(ISignalGenerator sigGen, ILoadFirmware loadFirmware, IReader chipRreader, IValueParser chipValueParser, IRepository<UIDList> uidRepo, IRepository<CalibrationValue> calRepo)
{
if (sigGen == null ||
loadFirmware == null ||
chipReader == null ||
chipValueParser == null ||
calRepo == null ||
uidRepo == null) throw new ArgumentNullException();
logger = Logging.Log4NetManager.GetLogger();
this.sigGenerator = sigGen;
this.loadFirmware = loadFirmware;
this.reader = chipReader;
this.valueParser = chipValueParser;
this.uidRepository = uidRepo;
this.calibrationRepository = calRepo;
}
public void Calibrate(bool reuse)
{
int voltageG;
int currentG;
// Read uid from device
this.uid = uidReader.ReadUid();
logger.DebugFormat("Read UID {0}", this.uid);
var possibleCalibrations = getCalibrationDataFromRepo();
if (possibleCalibrations.Any() && reuse)
{
logger.Debug("Reusing existing values");
var existingCalibration = possibleCalibrations.OrderByDescending(c => c.DateCalibrated).First();
currentG = existingCalibration.CurrentGain;
voltageG = existingCalibration.VoltageGain;
}
else
{
logger.Debug("Reading new values");
// Set voltage and current for signal generator
sigGenerator.SetOutput(187, 60);
// Load firmware onto chip for CLI commands
loadFirmware.LoadFirmware();
UpdateStateChange(StateOfDevice.LoadFirmware);
// Read voltage from device and calibrate
voltageG = valueParser.ReadVoltageValue();
UpdateStateChange(StateOfDevice.CalibrateVoltage);
// Read current from device and calibrate
sigGenerator.SetOutput(38, 60);
currentG = valueParser.ReadCurrentValue();
UpdateStateChange(StateOfDevice.CalibrateCurrent);
// Insert values into table
CalibrationValue cv = new CalibrationValue();
cv.UidId = uidRepository.GetBy(e => e.UID.Equals(this.uid)).Id;
cv.CurrentGain = currentG;
cv.VoltageGain = voltageG;
calibrationRepository.Insert(cv);
calibrationRepository.Submit();
UpdateStateChange(StateOfDevice.DatabaseInsert);
}
logger.DebugFormat("Updated database for current gain to {0} and voltage gain to {1}", currentG, voltageG);
// Once here, device has passed all phases and device is calibrated
UpdateStateChange(StateOfDevice.CalibrationComplete);
}
private IQueryable<CalibrationValue> getCalibrationDataFromRepo()
{
int uidId = uidRepository.GetBy(e => e.UID.Equals(this.uid)).Id;
return calibrationRepository.SearchFor(c => c.UidId == uidId);
}
public bool CalibrationDataExists()
{
this.uid = uidReader.ReadUid();
logger.DebugFormat("Read UID {0}", this.uid);
return getCalibrationDataFromRepo().Any();
}
public event Action<StateOfDevice, string> StateChange;
private void UpdateStateChange(StateOfDevice state, string message = "")
{
var sc = StateChange;
if (sc != null)
{
StateChange(state, message);
}
}
}
public class Form
{
Label labelToModify;
void MagickButtonClick(object Source, EventArgs e)
{
CalibrationManager manager = new CalibrationManager(.......);
manager.loadFirmware.Progress += (o, e) => { labelToModify.Text = "Some progress achieved!!"; };
}
}

Passing variables to timer event in a class

I have a method in a class that receives and returns multiple parameters from/to Form1.
I need to use a timed event to execute some code using those parameters.
I have arranged this simplified code to show the dynamic:
class Motor
{
public static System.Timers.Timer _timer;
int valReg = 30;
public void PID(decimal _actualSpeed, Decimal _speedRequest, out Decimal _pwmAuto, out decimal _preValReg)
{
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timerAutoset);
_timer.Enabled = true;
// {....}
_pwmAuto = valReg;
_preValReg = valReg - 1;
}
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e)
{
/* here I need to work with:
_actualSpeed
_speedRequest
_pwmAuto
_preValReg
and send back the last two variables
*/
}
}
This is how I pass and receive the variables from Form1 button :
private void button4_Click(object sender, EventArgs e)
{
// some code ................
Motor mtr = new Motor();
mtr.PID(speedRequest, actualSpeed, out pwmAuto, out xxx);
//..more code
How can I pass/get back those parameters to/from _timerAutoset event?
I tend to solve this problem using anonymous delegates.
public void PID(decimal _actualSpeed, Decimal _speedRequest, out Decimal _pwmAuto, out decimal _preValReg)
{
_pwmAuto = valReg;
_preValReg = valReg - 1;
// Because we cannot use [out] variables inside the anonymous degegates,
// we make a value copy
Decimal pwmAutoLocal = _pwmAuto;
Decimal preValRegLocal = _preValReg;
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += (sender, e) => { HandleTimerElapsed(_actualSpeed, _speedRequst, pwmAutoLocal, preValRegLocal); };
_timer.Enabled = true;
// {....}
}
static void HandleTimerElapsed(Decimal actualSpeed, Decimal speedRequst, Decimal pwmAuto, Decimal preValReg)
{
// (...)
}
(You have to be mindful when the delegate accesses local variables from the enclosing block. Double-check the code to ensure the values stored in those variables will not change between the assignment of the event handler and the invocation of this handler).
It seems these parameters are coming from somewhere else. One approach could be to pass a callback via delegate and use it to get the updated values from.
Another approach will be to make a class and pass it to Motor's constructor and use its reference in the _timerAutoset to get the updated values.
Using Delegates:
class Motor
{
public static System.Timers.Timer _timer;
int valReg = 30;
public delegate TimerParam ParameterizedTimerDelegate();
public static ParameterizedTimerDelegate TimerCallback { get; set; }
public void PID()
{
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timerAutoset);
_timer.Enabled = true;
// {....}
//Param.PwmAuto = valReg;
//Param.PreValReg = valReg - 1;
}
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e)
{
TimerParam param = TimerCallback();
/* here you can use:
Param.ActualSpeed
Param.SpeedRequest
Param.PwmAuto
Param.PreValReg
*/
}
}
Using a shared instance:
class TimerParam
{
public decimal ActualSpeed { get; set; }
public decimal SpeedRequest { get; set; }
public Decimal PwmAuto { get; set; }
public decimal PreValReg { get; set; }
}
class Motor
{
public static System.Timers.Timer _timer;
int valReg = 30;
public TimerParam Param { get; set; }
public void PID(TimerParam param)
{
Param = param;
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timerAutoset);
_timer.Enabled = true;
// {....}
Param.PwmAuto = valReg;
Param.PreValReg = valReg - 1;
}
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e)
{
/* here you can use:
Param.ActualSpeed
Param.SpeedRequest
Param.PwmAuto
Param.PreValReg
*/
}
}
You can then update the instance of TimerParam that you passed to the Motor class and timer will always get the updated values.
you could try using lambda expression for inserting additional arguement..
_timer.Elapsed += (sender, e) => _timerAutoset(sender, e, _actualSpeed,_speedRequest);
your method be like
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e,decimal speed,decimal speedRequest)
You just could initialize them in your class, so all methods could access them...
private void StartTimerForDeleteMessage(UC_ChatReceiveMessageControl ucChatReceiveMessageControl)
{
try
{
System.Timers.Timer aTimer = new System.Timers.Timer();
aTimer.Elapsed += (sender, e) => MyElapsedMethod(sender, e, ucChatReceiveMessageControl);
aTimer.Interval = 1000;
aTimer.Enabled = true;
}
catch (Exception ex)
{
Helper.WriteToLogFile("SetMessageBodyContentAfterAcknoledged ex::" + ex.Message, LoggingLevel.Errors);
}
}
static void MyElapsedMethod(object sender, ElapsedEventArgs e, UC_ChatReceiveMessageControl ucChatReceiveMessageControl)
{
try
{
}
catch (Exception ex)
{
Helper.WriteToLogFile("SetMessageBodyContentAfterAcknoledged ex::" + ex.Message, LoggingLevel.Errors);
}
}
I'm using a Backgroundworker styled class called "ScheduledWorker" which executes a recurring operation on a separate thread and returns to the main thread after each execution of this background operation.
For data exchange an object variable can be passed to the background operation when starting the ScheduledWorker and can also be changed while the ScheduledWorker is running. Inside the background procedure this object can be called via DoScheduledWorkEventArgs.Argument. The time when the DoWork event was raised can be called via DoScheduledWorkEventArgs.SignalTime property. The way ScheduledWorker reports result and progress of the background operation to the main thread is the same as the BackgroundWorker class.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
namespace ScheduledWorker
{
/// <summary>
/// Executes a recurring operation on a separate thread.
/// </summary>
[DefaultEvent("DoWork")]
[HostProtection(SharedState = true)]
public partial class ScheduledWorker : Component, ISupportInitialize
{
private bool enabled;
private bool delayedEnable;
private bool initializing;
private bool disposed;
private readonly ManualResetEvent doNotDisposeWaitHandle = new ManualResetEvent(false);
private int disposeWaitMSec;
private bool cancellationPending;
private bool isRunning;
private bool isOccupied;
private bool isWorking;
private object argument;
private readonly object statusChangeLockObject = new object();
private readonly object doWorkKey = new object();
private readonly object runWorkerCompletedKey = new object();
private readonly object progressChangedKey = new object();
private readonly EventHandler<DoScheduledWorkEventArgs> workHandler;
private readonly SendOrPostCallback completedCallback;
private readonly SendOrPostCallback progressCallback;
private AsyncOperation mainThreadOperation;
private Timer timer;
private double interval;
/// <summary>
/// Initializes a new instance of the ScheduledWorker class and sets the <see cref="ScheduledWorker.Interval"/> property to 100 milliseconds.
/// </summary>
public ScheduledWorker() : this(100, -1) { }
/// <summary>
/// Initializes a new instance of the ScheduledWorker class, and sets the <see cref="ScheduledWorker.Interval"/> property to the specified number of milliseconds.
/// </summary>
/// <param name="interval">The time, in milliseconds, between events. The value must be greater than zero and less than or equal to <see cref="int.MaxValue"/>."/></param>
public ScheduledWorker(double interval, int disposeWaitMSec) : base()
{
this.interval = interval;
this.disposeWaitMSec = disposeWaitMSec;
completedCallback = new SendOrPostCallback(AsynOperationCompleted);
progressCallback = new SendOrPostCallback(ProgressReporter);
initializing = false;
delayedEnable = false;
workHandler = new EventHandler<DoScheduledWorkEventArgs>(WorkerThreadStart);
}
/// <summary>
/// Occurs when <see cref="ScheduledWorker.RunWorkerAsync"/> or <see cref="ScheduledWorker.RunWorkerAsync(object)"/> are called.
/// </summary>
public event EventHandler<DoScheduledWorkEventArgs> DoWork
{
add
{
Events.AddHandler(doWorkKey, value);
}
remove
{
Events.RemoveHandler(doWorkKey, value);
}
}
/// <summary>
/// Occurs when the background operation has completed, has been canceled, or has raised an exception.
/// </summary>
public event EventHandler<RunWorkerCompletedEventArgs> RunWorkerCompleted
{
add
{
Events.AddHandler(runWorkerCompletedKey, value);
}
remove
{
Events.RemoveHandler(runWorkerCompletedKey, value);
}
}
/// <summary>
/// Occurs when <see cref="ScheduledWorker.ReportProgress(int)"/> or <see cref="ScheduledWorker.ReportProgress(int, object)"/> are called.
/// </summary>
public event EventHandler<ProgressChangedEventArgs> ProgressChanged
{
add
{
Events.AddHandler(progressChangedKey, value);
}
remove
{
Events.RemoveHandler(progressChangedKey, value);
}
}
/// <summary>
/// Starts raising the <see cref="ScheduledWorker.DoWork"/> event by setting Enabled to true.
/// </summary>
public void RunWorkerAsync()
{
RunWorkerAsync(null);
}
/// <summary>
/// Starts raising the <see cref="ScheduledWorker.DoWork"/> event by setting Enabled to true.
/// </summary>
/// <param name="argument">A parameter for use by the background operation to be executed in the <see cref="ScheduledWorker.DoWork"/> event handler.</param>
public void RunWorkerAsync(object argument)
{
Argument = argument;
Enabled = true;
}
/// <summary>
/// Stops raising the <see cref="ScheduledWorker.DoWork"/> event by setting Enabled to false.
/// </summary>
public void Stop()
{
Enabled = false;
}
/// <summary>
/// Gets or sets a value indicating whether the <see cref="ScheduledWorker.DoWork"/> event should be raised.
/// </summary>
[Category("Behavior")]
public bool Enabled
{
get
{
lock (statusChangeLockObject)
{
return enabled;
}
}
set
{
if (DesignMode)
{
delayedEnable = value;
enabled = value;
}
else if (initializing)
{
delayedEnable = value;
}
else if (enabled != value)
{
lock (statusChangeLockObject)
{
if (!value)
{
if (timer != null)
{
timer.Dispose();
timer = null;
}
enabled = false;
if (!isWorking)
{
if (!isOccupied)
{
isRunning = false;
}
SetMainThreadOperationCompleted();
}
}
else
{
enabled = true;
if (timer == null && !isRunning)
{
if (disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
else
{
int roundedInterval = Convert.ToInt32(Math.Ceiling(interval));
isRunning = true;
isOccupied = false;
isWorking = false;
cancellationPending = false;
SetMainThreadOperationCompleted();
mainThreadOperation = AsyncOperationManager.CreateOperation(null);
timer = new Timer(MyTimerCallback, null, roundedInterval, roundedInterval);
}
}
else if (isRunning)
{
throw new InvalidOperationException("ScheduledWorker is busy.");
}
else
{
UpdateTimer();
}
}
}
}
}
}
/// <summary>
/// Gets or sets the interval, expressed in milliseconds, at which to raise the <see cref="ScheduledWorker.DoWork"/> event.
/// It can be changed while the ScheduledWorker is running.
/// </summary>
[Category("Behavior"), DefaultValue(100d), SettingsBindable(true)]
public double Interval
{
get
{
return interval;
}
set
{
if (value <= 0)
{
throw new ArgumentException("Minimum interval is 1.");
}
else
{
interval = value;
if (timer != null)
{
UpdateTimer();
}
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the ScheuledWorker can report progress updates.
/// </summary>
[DefaultValue(false)]
public bool WorkerReportsProgress { get; set; }
/// <summary>
/// Raises the ProgressChanged event.
/// </summary>
/// <param name="percentProgress">The percentage, from 0 to 100, of the background operation that is complete.</param>
public void ReportProgress(int percentProgress)
{
ReportProgress(percentProgress, null);
}
/// <summary>
/// Raises the ProgressChanged event.
/// </summary>
/// <param name="percentProgress">The percentage, from 0 to 100, of the background operation that is complete.</param>
/// <param name="userState">The state object passed to <see cref="ScheduledWorker.RunWorkerAsync(object)"/>.</param>
public void ReportProgress(int percentProgress, object userState)
{
if (!WorkerReportsProgress)
{
throw new InvalidOperationException("This ScheduledWorker does not support reporting progress.");
}
else
{
mainThreadOperation.Post(progressCallback, new ProgressChangedEventArgs(percentProgress, userState));
}
}
/// <summary>
/// Gets or sets a value indicating whether the ScheduledWorker supports asynchronous cancellation.
/// </summary>
[DefaultValue(false)]
public bool WorkerSupportsCancellation { get; set; }
/// <summary>
/// Gets a value indicating whether the application has requested cancellation of a background operation.
/// </summary>
[Browsable(false)]
public bool CancellationPending
{
get
{
lock (statusChangeLockObject)
{
return cancellationPending;
}
}
}
/// <summary>
/// Requests cancellation of a pending background operation.
/// </summary>
public void CancelAsync()
{
if (!WorkerSupportsCancellation)
{
throw new InvalidOperationException("This ScheduledWorker does not support cancellation.");
}
else
{
lock (statusChangeLockObject)
{
cancellationPending = true;
Stop();
}
}
}
/// <summary>
/// Gets a value indicating whether the ScheduledWorker is running an asynchronous operation. This is the case until the SchedeuledWorker has been stopped (<see cref="ScheduledWorker.Enabled"/> = false)
/// and the last <see cref="ScheduledWorker.DoWork"/> event has completed.
/// </summary>
[Browsable(false)]
public bool IsBusy
{
get
{
lock (statusChangeLockObject)
{
return isRunning;
}
}
}
/// <summary>
/// A parameter for use by the background operation to be executed in the <see cref="ScheduledWorker.DoWork"/> event handler.
/// It can be changed while the ScheduledWorker is running.
/// </summary>
[Browsable(false)]
public object Argument
{
get
{
return Interlocked.Exchange(ref argument, argument);
}
set
{
Interlocked.Exchange(ref argument, value);
}
}
/// <summary>
/// Begins the run-time initialization of a ScheduledWorker that is used on a form or by another component.
/// </summary>
public void BeginInit()
{
Close();
initializing = true;
}
/// <summary>
/// Ends the run-time initialization of a ScheduledWorker that is used on a form or by another component.
/// </summary>
public void EndInit()
{
initializing = false;
enabled = delayedEnable;
}
private void MyTimerCallback(object state)
{
lock (statusChangeLockObject)
{
try
{
if (enabled && !isOccupied)
{
doNotDisposeWaitHandle.Reset();
isOccupied = true;
isWorking = true;
FILE_TIME fileTime = new FILE_TIME();
SafeNativeMethods.GetSystemTimeAsFileTime(ref fileTime);
workHandler.BeginInvoke(this,
new DoScheduledWorkEventArgs(Argument,
DateTime.FromFileTime((long)((((ulong)fileTime.ftTimeHigh) << 32) | (((ulong)fileTime.ftTimeLow) & 0xffffffff)))),
null,
null);
}
}
catch { }
}
}
private void WorkerThreadStart(object sender, DoScheduledWorkEventArgs args)
{
Exception Error = null;
try
{
if (CancellationPending)
{
args.Cancel = true;
}
else
{
OnDoWork(args);
}
if (args.Cancel)
{
args.Result = null;
cancellationPending = true;
}
}
catch (Exception ex)
{
Error = ex;
args.Result = null;
}
finally
{
mainThreadOperation.Post(completedCallback, new RunWorkerCompletedEventArgs(args.Result, Error, args.Cancel));
doNotDisposeWaitHandle.Set();
}
}
protected void OnDoWork(DoScheduledWorkEventArgs args)
{
((EventHandler<DoScheduledWorkEventArgs>)Events[doWorkKey])?.Invoke(this, args);
}
private void AsynOperationCompleted(object args)
{
lock (statusChangeLockObject)
{
isWorking = false;
if (!enabled)
{
isRunning = false;
SetMainThreadOperationCompleted();
}
}
OnRunWorkerCompleted((RunWorkerCompletedEventArgs)args);
lock (statusChangeLockObject)
{
isOccupied = false;
if (!enabled)
{
isRunning = false;
SetMainThreadOperationCompleted();
}
}
}
protected void OnRunWorkerCompleted(RunWorkerCompletedEventArgs args)
{
((EventHandler<RunWorkerCompletedEventArgs>)Events[runWorkerCompletedKey])?.Invoke(this, args);
}
private void SetMainThreadOperationCompleted()
{
if (mainThreadOperation != null)
{
mainThreadOperation.OperationCompleted();
mainThreadOperation = null;
}
}
private void ProgressReporter(object arg)
{
OnProgressChanged((ProgressChangedEventArgs)arg);
}
protected void OnProgressChanged(ProgressChangedEventArgs args)
{
((EventHandler<ProgressChangedEventArgs>)Events[progressChangedKey])?.Invoke(this, args);
}
private void UpdateTimer()
{
int roundedInterval = Convert.ToInt32(Math.Ceiling(interval));
timer.Change(roundedInterval, roundedInterval);
}
protected override void Dispose(bool disposing)
{
disposed = true;
Close();
base.Dispose(disposing);
}
public void Close()
{
if (timer != null)
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
using (ManualResetEvent disposeWaitHandle = new ManualResetEvent(false))
{
if (timer.Dispose(disposeWaitHandle))
{
disposeWaitHandle.WaitOne(disposeWaitMSec, false);
}
timer = null;
}
}
initializing = false;
delayedEnable = false;
enabled = false;
doNotDisposeWaitHandle.WaitOne(disposeWaitMSec, false);
doNotDisposeWaitHandle.Close();
SetMainThreadOperationCompleted();
}
[StructLayout(LayoutKind.Sequential)]
internal struct FILE_TIME
{
internal int ftTimeLow;
internal int ftTimeHigh;
}
private sealed class SafeNativeMethods
{
[ResourceExposure(ResourceScope.None)]
[DllImport("Kernel32"), SuppressUnmanagedCodeSecurityAttribute()]
internal static extern void GetSystemTimeAsFileTime(ref FILE_TIME lpSystemTimeAsFileTime);
}
}
/// <summary>
/// Provides data for the <see cref="ScheduledWorker.DoWork"/> event.
/// </summary>
public sealed class DoScheduledWorkEventArgs : DoWorkEventArgs
{
internal DoScheduledWorkEventArgs(object arg, DateTime signalTime) : base(arg)
{
SignalTime = signalTime;
}
/// <summary>
/// Gets the date/time when the <see cref="ScheduledWorker.DoWork"/> event was raised.
/// </summary>
public DateTime SignalTime { get; }
}
}
I just coded this class. I wish it helpful to others.
private class CustomTimer : IDisposable
{
private int duration = 1000;
private Action<object> tick;
private object obj;
private Thread thread;
private bool start = false;
public CustomTimer(int duration, Action<object> tick)
{
this.duration = duration;
this.tick = tick;
}
public void Start(object obj)
{
this.obj = obj;
start = true;
if (thread == null)
{
keepRunning = true;
thread = new Thread(ThreadMethod);
thread.Start();
}
else
{
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
}
}
public void Stop()
{
if (!start)
return;
start = false;
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
}
public bool IsStopped
{
get { return !start; }
}
private bool keepRunning = false;
private void ThreadMethod()
{
while (keepRunning)
{
if (start)
{
try { Thread.Sleep(duration); } catch { }
if (start && keepRunning)
tick(this.obj);
}
else if(keepRunning)
{
try { Thread.Sleep(int.MaxValue); } catch { }
}
}
}
public void Dispose()
{
this.keepRunning = false;
this.start = false;
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
}
}

Categories