Modifying Quartz.NET job details after they've been scheduled - c#

I have a Quartz.NET application where I need the administrators to be able to modify the job details - mostly information in each jobs datamap, but also things like the triggers - here is my code I'm using
protected void ButtonSubmit_Click(object sender, EventArgs e)
{
JobDetail jobDetail = sched.GetJobDetail(hdnID.Value, hdnGroupID.Value);
jobDetail.JobDataMap["idname"] = txtName.Text;
jobDetail.JobDataMap["initialPath"] = TextBox1.Text;
jobDetail.JobDataMap["targetPath"] = TextBox2.Text;
jobDetail.JobDataMap["regex"] = TextBox3.Text;
jobDetail.JobDataMap["overrideemails"] = txtEmails.Text;
jobDetail.JobDataMap["flush"] = chkflush.Checked;
jobDetail.JobDataMap["impUsername"] = txtImpUsername.Text;
jobDetail.JobDataMap["impDomain"] = txtImpDomain.Text;
jobDetail.JobDataMap["impPassword"] = txtImpPassword.Text;
Trigger[] triggers = sched.GetTriggersOfJob(hdnID.Value, hdnGroupID.Value);
if (ddlScheduleType.SelectedIndex == 0)
{
foreach (SimpleTrigger trigger in triggers.OfType<SimpleTrigger>())
{
if (ddlInterval.SelectedIndex == 0)
{
trigger.RepeatInterval = TimeSpan.Parse("00:00:01");
}
else if (ddlInterval.SelectedIndex == 1)
{
trigger.RepeatInterval = TimeSpan.Parse("00:01:00");
}
else if (ddlInterval.SelectedIndex == 2)
{
trigger.RepeatInterval = TimeSpan.Parse("00:00:01");
}
}
}
else
{
foreach (CronTrigger trigger in triggers.OfType<CronTrigger>())
{
trigger.CronExpressionString = txtCron.Text;
}
}
}
(I know what I'm doing with the foreach loops is stupid, but there is only ever one trigger with a job and it's a snippet of code I recieved here).
Problem is, the page posts back fine and the new values still stay in the textboxes. But when I go view the job again, nothing changes at all. What am I doing wrong? It's confusing as there are no errors at all.
Note the hiddenfields are also correctly set.
Thanks
The ButtonSubmit_Click event is certainly working as I've debugged the program and the program goes through that.

The instance you get by calling sched.GetTriggersOfJob and sched.GetJobDetail are clones of the real triggers / jobs.
Your changes to those objects are not used by the scheduler until you reschedule the changed trigger or add a the changed job with the changed trigger.
I think you should be able to use RescheduleJob if you only change the triggers and you could remove the original trigger and add a new one.

Related

Popup status window during a datagrid iteration update

I've spent 4 hours on this and totally failed.
I know that i need to use BackgroundWorker but all the tutorials refer to running a progress script on the actual form you are running the worker on.
I have a large datagrid, which the user can use a check box to "select all" and then press "UPDATE ALL"
This updates every grid with a bunch of options they choose.
For some users this may be 5 records which is nothing, but some might update 200 records with 5 options which takes about... 10-15 secs to iterate through them.
I have tried so many variations of running BGworker which loads a FrmLoading.Showdialog
Or trying to have BGworker "do work" running the code and then the main thread having the FrmLoading.Show()
However nothing is working.
If i have the update code in the background worker, it fails because the datagrid and everything is in a different thread.
The other way round, and it just hangs on FrmLoading.Show()
Any advice would be great.
I just can't seem to get my head around how to get this working for what seems to be an easy idea!
Current Update Code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//MessageBox.Show(Convert.ToBoolean(rowx.Cells["clnselected"].Value).ToString());
if (Convert.ToBoolean(rowx.Cells["clnselected"].Value) == true)
{
//if cycle has a value.
if (cmbcycle.SelectedIndex != -1)
{
rowx.Cells["clncycletype"].Value = cycle;
rowx.Cells["clnpackscollect"].Value = packs;
}
//if location has a value
if (cmblocation.SelectedIndex != -1)
{
location = Convert.ToInt32(cmblocation.SelectedValue);
rowx.Cells["clnlocation1"].Value = location;
}
if (cmbsize.SelectedIndex != -1)
{
size = Convert.ToInt32(cmbsize.SelectedValue);
rowx.Cells["clnpacksize"].Value = size;
}
if (chkDelivery.Checked == true)
{
rowx.Cells["clnDelivery"].Value = true;
}
if (chkSignSheet.Checked == true)
{
rowx.Cells["clnSigningSheet"].Value = true;
}
}
countupdated++;
}
foreach (DataGridViewRow row in dataGridpatients.Rows)
{
row.Cells["clnselected"].Value = false;
row.DefaultCellStyle.BackColor = Color.White;
}
cmbsize.SelectedIndex = -1;
cmblocation.SelectedIndex = -1;
cmbcycle.SelectedIndex = -1;
chkDelivery.Checked = false;
chkSignSheet.Checked = false;
#countupdated++;
I also have #CountSelected.
What i want to do is run this code above but have a popup overlay (dialog) with my logo + "Updating X%"
Where X = countupdated/countselected * 100
I now know i need to use the background worker and invoke for the above, but literally have no idea regarding how to invoke the grid and go from there.
I understand i need to invoke the variables I'm using
(eg. cmbcycle.SelectedIndex)
I know iterating through 150 records and updating individual cells is probably wrong,
My other option is creating a datatable from "selected" cells on that datatable
then Running the update via SQL instead of iterating through a bound table.
Then after the SQL i can re-create the table which will now have the new cell values updated in it?
Would that be a more appropriate way to do it?
Max rows on this table would be 200. Average ~70 so we are never talking 500 or 1000
EDIT:
So the checked answer works to run the background worker and refer to the controls on the form.
The issue is that if i do this:
backgroundWorker1.RunWorkerAsync();
splashy.ShowDialog();
Then the splash screen pops up after the background worker ends
If i do this:
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Then the popup semi-forms and hangs until the end of the background worker, at which time it closes
because of the RunWorkerCompleted event.
EDIT:
I have no updated the code in DoWork and used Invokes to refer to the controls.
This works and the code runs fine.
I now need a popup ot appear showing the progress through the updates.
splashy.InvokeBy(() =>
{
splashy.Show();
});
backgroundWorker1.RunWorkerAsync();
Does not work. It causes the popup but freeze
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Allows the Dialog to show (not 'frozen' and distorted) However the Lab (lblprogress) does not update.
This is because the form never get to the RunWorker method, it is stuck at ShowDialog.
It would be a good idea to make modifications on your DataSource itself and then bind it with the DataGridView.
But as from your existing code if you want to access your controls/UI to update or change values from BackgroundWorker.RunWorkerAsync method or any other Thread call for that matter, you can create an extension method to .Invoke() the controls like:
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
Keep this static class under the same Namespace as your main class for convenience.
Thus this code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
Will become:
dataGridpatients.InvokeBy(() =>
{
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
});
Similarly,
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
Will become:
cmbcycle.InvokeBy(() =>
{
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
});
This way you van safely access your controls, while keeping your UI responsive at the same time. Update your Popup Status UI the same way!
This answer is based around o_O's answer.
The main issue is that i wanted the UI to actually update and the background worker to supply the splash.
Instead of running all the 'hard code' in the BGW, i left it in the original thread, but called a BGW to display a popup Dialog form.
so at the start of the "hard code" I used:
backgroundWorker1.RunWorkerAsync();
This called:
FrmSplash splashy;
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
splashy = new FrmSplash();
splashy.ShowDialog();
}
In order to remove the dialog box, at the end of the code in the GUI thread, i used:
splashy.InvokeBy(() =>
{
splashy.Close();
}
);
backgroundWorker1.CancelAsync();
Which uses the extension supplied by O_o
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
I have also built a label update into splashy
So i could call
splashy.InvokeBy(() =>
{
splashy.SetStatus(countupdated.ToString());
}
);
As i iterated through the datagridview rows. This updated the label on the splash screen :)

C# String comparison not working

I'm having this wierd problem within the application I'm currently working on.
string searchText = "onMouseOver=\"CallList_onMouseOver(this);\" id=\"";
List<int> searchOrders = AllIndexesOf(scraper.clientBrowser.DocumentText, searchText);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < searchOrders.Count; i++)
{
string order = scraper.clientBrowser.DocumentText.Substring(searchOrders[i] + searchText.Length, 6);
scraper.clientBrowser.Document.GetElementById(order).InvokeMember("Click");
for (int j = 0; j < scraper.clientBrowser.Document.Window.Frames.Count; j++)
{
if (scraper.clientBrowser.Document.Window.Frames[j].Document != null && scraper.clientBrowser.Document.Window.Frames[j].Document.Body != null)
{
string orderText = scraper.clientBrowser.Document.Window.Frames[j].Document.Body.InnerText ?? "Nope";
//MessageBox.Show(j + Environment.NewLine + orderText);
if (!orderText.Contains("Nope"))
{
sb.AppendLine(orderText + Environment.NewLine);
}
}
}
}
Clipboard.SetText(sb.ToString());
The thing is, whenever I uncomment the MessageBox.Show, I can clearly see orderText is filled with another value than "Nope", the Stringbuilder gets filled, and the correct text is copied.
However if I comment the Messagebox.Show, the outcome of this loop is always "Nope". I'm stuck here, I have no idea what could cause something like this.
The scraper.clientBrowser is a System.Windows.Forms.WebBrowser.
Update:
Solved the issue by waiting for the document to be loaded, created this mechanism:
public bool DocumentLoaded
{
get { return documentLoaded; }
set { documentLoaded = value; }
}
private void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
this.DocumentLoaded = true;
this.clientBrowser = sender as WebBrowser;
}
void clientBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
this.DocumentLoaded = false;
}
Then in the class I'm using:
while(!scraper.DocumentLoaded)
{
System.Threading.Thread.Sleep(100);
}
It sounds like you need to ensure that the page is fully loaded, like there might be a race condition. I would suggest wiring up the WebBrowser.DocumentCompleted event, and then attempting your scrapping logic.
Update
I overlooked this initially, this certainly has something to do with your issue. The line where you are invoking a click, like so scraper.clientBrowser.Document.GetElementById(order).InvokeMember("Click");. This is done in the iteration, which will more than likely manipulate the DOM -- will it not? I suggest going about this problem entirely different. What are you trying to achieve exactly, (not how you're trying to do it)?
With this alone, I would suggest that you refer to this SO Q/A and look at how they're waiting for the click to finish.
Only one thing I can guest here:
When you uncomment MessageBox.Show, at the time the message box show the info, the clientBrowser use this time to finish loading page. Then when you press OK on message box, the page is load completed, so you get the result. When you comment it, you dont wai for page loaded, so the result is diffent.

WPF only binds data to datagrid between user actions. How to force update to datagrid?

User can perform an action and I add data to a collection that is bound to a DataGrid and that works fine.
But when there is a long running process that has multiple records added to the collection I never see the DataGrid updated until the process is over. What am I doing wrong? I am really new to this so I am sure the issue is something simple.
private ObservableCollection<StatusEntry> _collSe = new ObservableCollection<StatusEntry>();
public ObservableCollection<StatusEntry> CollSe
{
get { return _collSe; }
set
{
_collSe = value;
// NotifyPropertyChanged("CollSe");
}
}
CollSe.Add(new StatusEntry() { TimeStamp = DateTime.Now, Comment = "STARTED: Translating file to DataTable" });
DataTable dt = ExcelHelper.ReadAsDataTable(tbFileName.Text);
CollSe.Add(new StatusEntry() { TimeStamp = DateTime.Now, Comment = "COMPLETE: Translating file to DataTable" });
EDIT:
More clearly here is what I tried.. Still does not update the UI until the end though
private void btnProcessFile_Click(object sender, RoutedEventArgs e)
{
//THis should happen as soon as the button is pressed
ThreadStart job = new ThreadStart(() =>
{
for (int i = 0; i < 20; i++)
{
// The new thread puts UI operations in the dispatching queue
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
CollSe.Add(new StatusEntry() { TimeStamp = DateTime.Now, Comment = "Happy Tools STARTED" });
}));
}
});
Thread thread = new Thread(job);
thread.Start();
//The another minute of processing here.....
I made the mistake of asking a question without understanding the basics and therefore coding all over the place not sure what I was doing. So I think I got it now. The article that helped my simple mind understand it is here..
The main point is to understand WPF has a main thread and a UI thread and how to go from one to the other to allow access to the UI.
http://elegantcode.com/2009/07/03/wpf-multithreading-using-the-backgroundworker-and-reporting-the-progress-to-the-ui/

Comparing to previous string gets me stuck in a loop

Ok, so I have a program that checks a twitch url for whenever someone new follows the channel by comparing a certain string is different from a "temp" string that I use for reference. But instead of only outputting a message every time the string is different it gets stuck in a loop of outputting the latest follower and then second latest follower then latest follower again etc.
What am I missing? Also, is there a better way of checking if a certain string is updated?
private void DonationListen()
{
try
{
followers = this.donationClient.DownloadString("https://api.twitch.tv/kraken/channels/" + channel.Trim() + "/follows");
donationTimer.Interval = 10000;
donationTimer.Elapsed += new ElapsedEventHandler(CheckUpdates);
donationTimer.Start();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private void CheckUpdates(object source, ElapsedEventArgs e)
{
donationTimer.Stop();
int startIndex = followers.IndexOf("display_name\":") + 15;
int endIndex = followers.IndexOf(",\"logo", startIndex);
prevFollower = followers.Substring(startIndex, (endIndex - 1) - startIndex);
if (firstRun == true)
{
temp = prevFollower;
}
else if (prevFollower != temp)
{
//New follower detected
temp = prevFollower;
if (updateFollower != null)
{
updateFollower(prevFollower);
}
}
else
{
//Follower is the same as before
}
firstRun = false;
DonationListen();
}
I'm thinking it might have something to do with the downloadstring trying to get a new string from the url but failing since it's currently being updated and therefore the CheckUpdates doesn't have correct information or something?
Without a good code example, it is difficult to know for sure what the problem is. So we are left inspecting the code you did show us.
Based on that, it appears to me as though your "loop" is being caused by repeatedly subscribing to the same event.
In your DonationListen() method, you have this statement:
donationTimer.Elapsed += new ElapsedEventHandler(CheckUpdates);
In the CheckUpdates() method (i.e. the handler you are subscribing), you have this statement (as the very last statement):
DonationListen();
In other words, every time the timer's Elapsed event is raised, you add another event handler instance to the event. For every handler you add, the CheckUpdates() method will be called.
Again, without a good code example, it is difficult to know for sure what the best fix would be. But given the code that is here, it appears to me that you could just remove that last statement from the CheckUpdates() method, as the DonationListen() method does not appear to do anything that needs doing again.

Have MessageBox appear once (code inside timer)

UPDATE: I've managed to fix my problem. Using the code below, I moved my MessageBox AFTER my XML saving and changed the Timer from 100ms to 400ms. I now have 1 box appear, thank god. Although If anyone has a short cut to updating a single value (ActReminded) in the List array(ActListTask), that'd be great to know.
I'm having a little issue with displaying the MessageBox. Show inside a timer without it spamming me. Here's the part of the code I've been working with:
public class ActiveTasks
{
//Properties here
}
public List<ActiveTasks> ActTaskList = new List<ActiveTasks>();
for (int i = 0; i < ListActive.Items.Count; i++)
{
if (DTime.Date == newDateTime.Date)
{
if (newDateTimeLeft.CompareTo(TimeSpan.Zero) <= 0 && ActTaskList[i].ActReminded != "true")
{
MessageBox.Show("!!!!");
ActTaskList.Add(new ActiveTasks()
{
ActTitle = ActTaskList[i].ActTitle,
ActDesc = ActTaskList[i].ActDesc,
ActDate = ActTaskList[i].ActDate,
ActTime = ActTaskList[i].ActTime,
ActStatus = ActTaskList[i].ActStatus,
ActReminded = "true",
ActRepeat = ActTaskList[i].ActRepeat
});
ListActive.Items.RemoveAt(i);
ActTaskList.RemoveAt(i);
XDocument XmlActTasks = GenerateActiveListToXML(ActTaskList);
}
}
}
I actually decided I may want to hold onto the reminder status, whether it has been shown or not as I wouldn't want a repeated reminder every time the program is opened. Since I don't know of a way to update an individual part of ActTaskList I just re-added it, and then deleted the original. This code manages to recognise that if it happens, it will change the reminder status from false, to true; after I've Ok'ed all the spam. So it will stop the MessageBox once I've managed to closed all the Messageboxes. However, it doesn't stop the spam. Would it be anything to do with the fact I've set the timer to 100ms? Or could their be an alternative way to make the messagebox appear without it being inside the timer?
The odds of the current time lining up exactly to the second what is happening in your loop is small. Why not treat newDateTime as a cut off point and just set a flag?
//Declare this outside of the loop
bool hasDisplayed = false;
//Inside the timer event handler
if (!hasDisplayed && DateTime.Now >= newDateTime)
{
hasDisplayed = true;
MessageBox.Show("!!!!!!!!!!!!!");
}
Can you do something like this?
Action message = () => MessageBox.Show("!!!!!!!!!!!!!"));
object lockOb = new object();
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
lock(lockOb)
if(null != message)
{
message();
message = null;
}
}
You say you've already tried a boolean indicating the message has already been shown, I'm assuming because the code probably looked like it did below.
void TimerLoop()
{
bool msgAlreadyShown;
if(!msgAlreadyShown)
{
MessageBox.Show("!!!!!!!");
}
// Other work in your timer function
}
The problem with that code is that the bool will be set to false each time the function is called by the timer. You haven't posted much code, but you've at least stated what you're trying to accomplish, a timer that checks if a reminder should be presented to the user.
I'm about to make some wild guesses about how you've put together your software, there's a good chance it's way off, but I hope it might point you in the right direction. You could have some sort of reminder class like this:
public class Reminder
{
string Message { get; set;}
DateTime Alarm { get; set; }
bool IsDismissed { get; set; }
}
I'm assuming you might want to have multiple reminders that can be checked for in the timer loop, so your timer loop could look something like:
private List<Reminder> _activeReminders; // A list of reminders
void TimerLoop(object s, ElapsedEventArgs e)
{
lock(_activeReminders)
{
var now = DateTime.Now;
foreach(var reminder in _activeReminders)
{
// only run this code if the time has passed and it hasn't already
// been shown
if(now.CompareTo(reminder.Alarm) >= 0 && !reminder.IsDismissed)
{
MessageBox.Show(reminder.Message);
reminder.IsDismissed = true;
}
}
}
}
This is a pretty naive implementation, since you probably don't want to hold onto the reminders for forever and the reminders are never removed from the _activeReminders list, but you essentially just need to add some sort of state to determine if the reminder has already been shown.
Of course, this isn't a complete example either, since I never new up the _activeReminders field or add anything to it, but I think this might help get the idea of what you need to do across. Also, you might not care about multiple reminders, and your timer code could look nothing like this. The main idea was to show you how you can keep track of the state of a reminder, and tailor it to your own code. The above was just an example.
Also, I haven't actually tested it, so treat it more like pseudocode than anything else. However, the logic is sound, and should it should only cause the message box to appear once.

Categories