I've worked myself into a bit of a problem -- I'm not sure there's a graceful way out without restructuring my code. I have a server-side timer which is running and it needs to simulate clicking a tab of a RadTabStrip.
Client-Side I have the following method:
function OnClientTabSelected(sender, eventArgs)
{
FixSplitter($find(rightPaneID));
}
FixSplitter is dependent on an additional control, though:
function FixSplitter(sender, eventArgs)
{
var multiPage = $find(multiPageID);
...
}
now, server-side, I have the following:
public void DoTimerCycleTick(object sender, TimerEventArgs eventArgs)
{
GlobalSettings globalSettings = StateManager.GetStates<GlobalSettings>();
if( globalSettings.CycleEnabled )
{
if (!Equals(DateTime.Now.CompareTo(globalSettings.TimeLastCycled.AddMinutes(globalSettings.CycleInterval)), -1)) //CompareTo returns -1 when time is earlier than.
{
int nextIndex = SelectedIndex + 1;
if( nextIndex == Tabs.Count)
{
nextIndex = 0;
}
SelectedIndex = nextIndex;
LayoutManager.Instance.MultiPage.SelectedIndex = nextIndex;
LayoutManager.Instance.MultiPageUpdatePanel.Update();
//ScriptManager.RegisterClientScriptBlock(Page, Page.GetType(), "KEY", "OnClientTabSelected();", true);
globalSettings.TimeLastCycled = DateTime.Now;
}
}
StateManager.SaveGlobalSettings(globalSettings);
}
The relevant bit of this code is where I'm setting indices. Clearly, this does not trigger the OnClientTabSelected method. Yet, I need to run the FixSplitter method.
So, I thought a quick fix for this surprise would be to register a client script. Indeed, this would probably be sufficient if not for the dependence on the multiPage. Because my MultiPage is wrapped in an UpdatePanel which is currently updating -- it cannot be found using $find(multiPageID). $find(multiPageID) returns null where as $find(rightPaneID) returns the expected object -- both declarations are identical and the code works smoothly in all other scenarios. I am confident in saying that the culprit is the UpdatePanel.
Do I have options other than creating a new method which does the same things as OnClientTabSelected except forces the MultiPageUpdatePanel to postback after executing -- instead of calling Update on the UpdatePanel server-side?
I recognize that there are deeper issues with this, but it's unfinished code that can't be broken apart for a few more weeks.
EDIT: After some work, here's my proposed solution. Open to critique, I'm no expert in this. :)
/// <summary>
/// Performs one tick of a timer on the chart.
/// Ticks based on time for testability and to prevent
/// weird cases when skipping the clock time forward.
/// </summary>
public void DoTimerCycleTick(object sender, TimerEventArgs eventArgs)
{
GlobalSettings globalSettings = StateManager.GetStates<GlobalSettings>();
if( globalSettings.CycleEnabled )
{
if (!Equals(DateTime.Now.CompareTo(globalSettings.TimeLastCycled.AddMinutes(globalSettings.CycleInterval)), -1)) //CompareTo returns -1 when time is earlier than.
{
int oldIndex = SelectedIndex;
int newIndex = (oldIndex + 1) != Tabs.Count ? (oldIndex + 1) : 0;
SelectedIndex = newIndex;
LayoutManager.Instance.MultiPage.SelectedIndex = newIndex;
//LayoutManager.Instance.MultiPageUpdatePanel.Update();
ScriptManager.RegisterStartupScript(Page, Page.GetType(), "KEY", string.Format("OnServerTabSelected({0});", newIndex), true);
globalSettings.TimeLastCycled = DateTime.Now;
}
}
StateManager.SaveGlobalSettings(globalSettings);
}
var showLoadingPanel = true;
function OnServerTabSelected(newIndex) {
var oldID = $find(multiPageID).get_selectedPageView().get_id();
var newID = $find(multiPageID).get_pageViews().getPageView(newIndex).get_id();
if ($telerik.$("#" + oldID).is(":visible")) {
$telerik.$("#" + oldID).fadeOut(1000, function () {
$telerik.$("#" + newID).fadeIn(1000, function () {
showLoadingPanel = true;
$find(multiPageID).set_selectedIndex(newIndex);
});
});
showLoadingPanel = false;
FixSplitter($find(rightPaneID));
return;
}
}
Can't you just overload the FixSplitter method, one that takes those two paramters, does the find and then calls the other method with all 3 parameters? That way you can always override the multiPage value when you might need it;
function FixSplitter(sender, eventArgs)
{
var multiPage = $find(multiPageID);
FixSplitter(sender, multiPage, eventArgs);
}
function FixSplitter(sender, multiPage, eventArgs)
{
...
}
If you can't overload for some reason you could use an alternative notation like DoFixSplitter.
Related
I am trying to disable some menu strip items while a textbox is displaying "Calculating...". Once that value goes away, I wish to re-enable the menu items. Its purpose is not to interrupt MD5/CRC32 calculations. So far, I've tried various method of code, and have had no luck so far. What's listed below should work, but for some reason it does not. Any help would be appreciated.
// THIS PART WORKS
if (boxMD5.Text.Contains("Calculating") == true)
{
openROMToolStripMenuItem.Enabled = false;
saveROMDataToolStripMenuItem.Enabled = false;
asTXTToolStripMenuItem.Enabled = false;
asHTMLToolStripMenuItem.Enabled = false;
}
// THIS PART DOES NOT WORK
else if (boxMD5.Text.Contains("Calculating") == false)
{
openROMToolStripMenuItem.Enabled = true;
saveROMDataToolStripMenuItem.Enabled = true;
asTXTToolStripMenuItem.Enabled = true;
asHTMLToolStripMenuItem.Enabled = true;
}
I can't quite tell you why the code isn't doing what you expect, but I can make a suggestion that will change your approach and may help achieve your goal at the same time. What you are trying to do shouldn't be to disable the menu when the textbox contains "Calculating" but instead you should disable the menu while the calculations are being performed. From a user/UI perspective, these are the same thing, but the inner-workings of your program know better.
Based on you PasteBin code, try this:
private void openROMToolStripMenuItem_Click(object sender, EventArgs e)
{
//Other code omitted for brevity
if (File.Exists(OpenFileDialog1.FileName))
{
UpdateUI("Calculating...");
backgroundWorker1.RunWorkerAsync(OpenFileDialog1.FileName);
}
//Other code omitted for brevity
}
and
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UpdateUI(e.Result.ToString());
}
where the new method UpdateUI() looks like this:
void UpdateUI(string hash)
{
var calculating = hash == "Calculating...";
if (!calculating)
{
progressBar1.Value = 0;
}
openROMToolStripMenuItem.Enabled = !calculating;
saveROMDataToolStripMenuItem.Enabled = !calculating;
asTXTToolStripMenuItem.Enabled = !calculating;
asHTMLToolStripMenuItem.Enabled = !calculating;
boxMD5.Text = hash;
}
Also, notice how you are able to just put !calculating in the if statement rather than calculating == false. This is because the value is already true or false so you don't have to compare it to anything to figure that out. The same thing applies to your original code but you don't need it anymore with this approach.
This question already has answers here:
Is async always asynchronous in C#? [duplicate]
(1 answer)
Do you have to put Task.Run in a method to make it async?
(3 answers)
async method in C# not asynchronous?
(3 answers)
Closed 5 years ago.
I have a TextBox with a TextChanged event that calls a custom event if the text of the textbox represents an existing file. In this event, there is a call to an outside dll that does some processing on the File, which can take upwards of a minute to finish. There is also some post-processing I do, dependent on what result this method returns to me. Currently, this is blocking my UI, which is highly undesirable.
There are essentially 2 "options"/scenarios I see.
Within the custom event, somehow wait for the dll call to finish, before continuing the event, while also keeping the UI free. This seems like the simplest idea from my multithreading-untrained self, but it also conceptually throws red flags at me: Is this even possible given that the custom event itself (called from TextChanged) is on the UI thread?
Throw the entire custom event into it's own thread using Task.Run(). Downside here is that apart from the dll method call, there is quite a good amount of UI elements that are affected by getters/setters after the long method. I could write alternated getters/setters based on the appropriate InvokeRequired, but if there is a more correct way to do this, I'd rather take that approach.
I made a much shorter (although contrived) example project, which shows essentially what I'm after, using option 2 from above:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
comboBox1.Items.Add("Select One...");
comboBox1.Items.Add("Item 1");
comboBox1.Items.Add("Item 2");
Value = 0;
}
public string SetMessage
{
set
{
if (lblInfo.InvokeRequired)
lblInfo.BeginInvoke((MethodInvoker)delegate () { lblInfo.Text = Important ? value + "!" : value; });
else
lblInfo.Text = Important ? value + "!" : value;
}
}
public bool Important
{
get
{
return chkImportant.Checked;
}
set
{
if (chkImportant.InvokeRequired)
chkImportant.BeginInvoke((MethodInvoker) delegate() { chkImportant.Checked = value; });
else
chkImportant.Checked = value;
}
}
public SomeValue Value
{
get
{
if (comboBox1.InvokeRequired)
{
SomeValue v = (SomeValue)comboBox1.Invoke(new Func<SomeValue>(() => SomeValue.Bar));
return v;
}
else
{
switch (comboBox1.SelectedIndex)
{
case 1:
return SomeValue.Foo;
case 2:
return SomeValue.Bar;
default:
return SomeValue.Nothing;
}
}
}
set
{
if (comboBox1.InvokeRequired)
{
comboBox1.BeginInvoke((MethodInvoker)delegate ()
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
});
}
else
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
}
}
}
private void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public bool ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and return
//an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
//Do I *need* to multithread the whole thing?
Task.Run(() => CustomEvent(this, new EventArgs()));
}
}
public enum SomeValue
{
Nothing = 0,
Foo = 100,
Bar = 200
}
Note: I'm not asking for code review on my option 2 code. Rather, I'm asking if option 2 is necessary to accomplish, since that option causes me to change a considerably larger portion of code, given that it's only 1 method within it holding up the entire process.
I also realize I can simplify some of the code in these properties to prevent replication. For the sake of demonstrating to myself and debugging, I am holding off on that at this time.
Here is what I had related to option 1 (left out duplicate code and the getters/setters without their invokes):
private async void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (await ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public async Task<bool> ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and
//return an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}
This is basically what you want. I'm violating a couple best-practices here, but just showing it's not that complicated. One thing to keep in mind is that the user can now click this button multiple times in a row. You might consider disabling it before processing. Or you can do a Monitor.TryEnter() to make sure it's not already running.
private async void buttonProcess_Click(object sender, RoutedEventArgs e)
{
textBlockStatus.Text = "Processing...";
bool processed = await Task.Run(() => SlowRunningTask());
}
private bool SlowRunningTask()
{
Thread.Sleep(5000);
return true;
}
I have an automatic function, the idea is to execute in an specific time some actions.
It receives a ListView with the actions and specific times.
It runs almost pefect. I say almost because sometimes it doesn't executes one action (lets say action #10, and it has 30 actions to do) and when this happen the rest of the actions neither get executed.
I have some validations where I check if the previous actions is executed then the current one is excuted, but it doesn't make any difference, it continues stopping at some point.
Here the code for this automatic function:
Constructor
public RunAutomatic()
{
InitializeComponent();
stopwatch = new System.Windows.Forms.Timer();
stopwatch.Interval = 5;
stopwatch.Tick += new EventHandler(stopwatch_Tick);
repSeconds = 0;
for (int x = 0; x < repeatActions.Capacity; x++)
{
repeatActions.Add(x);
finished.Add(x);
}
}
Run Function
private void RunFaster()
{
if (contPos > 1)
{
ArrayList tmp = (ArrayList)repeatActions[contPos - 1];
if ((bool)tmp[tmp.Count - 1] == true) //This is where it is supposed to validate that the previous action is already executed
Execute(2);
else
{
contPos = contPos - 1;
Execute(2);
}
}
else
Execute(2);
}
I have tried to solve this but can't get over it.
Thanks to all replies.
This is a bit hard to explain, but I'm hoping this example will clear it up.
Say I have some function call Visible:
public bool Visible(/* Some page element */)
{
// Checks if something on a webpage is visible. Returns a "true" is yes, and "false" if not
}
Is it possible to some how wait for this function to return true? What I've written out so far looks like this:
public void WaitUntil(/*function returning bool*/ isTrue)
{
for (int second = 0; ; second++)
{
if (second >= 12)
{
/* Thow exception */
}
else
{
if (isTrue /*calls the isTrue function with given parameters*/)
{
return;
}
}
}
}
Such that these two method could be used together like:
WaitUntil(Visible(/* Some page element */));
to wait until a page element is visible... Is this possible?
Here is how to do it (although you should consider using events as this kind of "waiting" is strongly discouraged)
/*Important Note: This is ugly, error prone
and causes eye itchiness to veteran programmers*/
public void WaitUntil(Func<bool> func)
{
DateTime start = DateTime.Now;
while(DateTime.Now - start < TimeSpan.FromSeconds(12))
{
if (func())
{
return;
}
Thread.Sleep(100);
}
/* Thow exception */
}
//Call
WaitUntil(() => Visible(/* Some page element*/));
I am working on a class project and I've run into a problem I can't figure out. I have a feeling it's actually pretty easy, but I've been working on stuff so long I can't think straight anymore.
I have a login page that allows a user to login and pass 2 data items to the next page using Context.Items and Server.Transfer. Here is the code snippet:
Context.Items["preferred"] = true;
Context.Items["pageNum"] = 1;
Server.Transfer("ProductsShelf.aspx");
On the "ProductsShelf" page I can access those two items and use the data like so:
pageNumber = (int)Context.Items["pageNum"];
I am then using a switch-statement with pageNumber to display certain information:
switch (pageNumber)
{
case 1:
imgProd.ImageUrl = "assets/laptop.bmp";
lbl_Name.Text = "Laptop";
lbl_desc.Text = "This is a cheap laptop!";
lbl_price.Text = "199.99";
break;
}
Obviously there's other entries I'm omitting. What I want to do is click a next or previous button and use the event to change the Context.Items["pageNum"] data so the Page_Load() event uses different data in the switch-statement. Hope that makes sense. Here is one of the button click events:
protected void btn_Prev_Click(object sender, EventArgs e)
{
if (pageNumber == 1 || pageNumber == 2)
{
Context.Items["pageNum"] = 1;
}
else if (pageNumber == 3)
{
Context.Items["pageNum"] = 2;
}
Context.Items["preferred"] = preferredCustomer;
Server.Transfer("ProductsShelf.aspx");
}
The problem is that before the button click event fires, the form posts and clears the Context.Items and pageNumber values. This means that the button event if-statements never fire and it results in:
pageNumber = (int)Context.Items["pageNum"];
Being null, throwing an exception and making me very sad. So my question is, how can I go about retaining the values? Should I switch to Response.Redirect and have something like ?page=1 in the URL? Or will that clear too when the form posts? Hopefully I'm not doing this completely wrong.
If TL;DR, here's a quick summary:
Context.Items has 2 values passed with Server.Transfer
These values determine what's shown on the next page
The form clears Context.Items and variables before button click event fires
The values are null, the if-statement doesn't run, and the app throws an exception
Question: how should I go about retaining those values?
Thanks a lot. :)
HttpContext items can be used within one request only - it will be recreated for next request so your values are bound to lose. You should use view-state to preserve data across post-backs. In page load, you should check if data exists in context and then copy it to view-state. Then in button click events, you can read the data from view-state, put into the context items and do server.transfer.
Here's simple sample code:
private int PageNumber
{
get
{
var value = ViewState["pageNum"];
return null == value? 1: (int)value;
}
set
{
ViewState["pageNum"] = value;
}
}
private bool IsPreferredCustomer
{
get
{
var value = ViewState["preferred"];
return null == value? false: (bool)value;
}
set
{
ViewState["preferred"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
var preferred = Context.Items["preferred"];
if (null != preferred)
{
IsPreferredCustomer = (bool)preferred;
}
var pageNum = Context.Items["pageNum"];
if (null != pageNum )
{
PageNumber = (int)Context.Items["pageNum "];
}
}
Use the same PageNumber property in event code.