I have here a long method that takes a little while to execute. I would like to keep the user entertained so I created a progress bar and a label. What I would like is for that label to change while the system executes the progress. Ive been looking at Application.DoEvents(), but it seems like thats the wrong way to go. This application is pretty simple and its just a project and nothing professional. All this app does is send a file to a client and insert the data into a database.
I have one label (besides a success and error label), that I would like to constantly update along side the progress bar. Any ideas or tips on how to do this? Would Application.DoEvents() be acceptable in this situation? Or is there a simple way to update the text. I am using C#, asp.net, and a System.Web.UI.Page. Any help or pointing me to the right direction would be greatly appreciated.
protected void Button_Click(object sender, EventArgs e)
{
PutFTPButton.Enabled = false;
Thread.Sleep(3000);
Button btn = (Button)sender;
KaplanFTP.BatchFiles bf = new KaplanFTP.BatchFiles();
KaplanFTP.Transmit transmit = new KaplanFTP.Transmit();
//label text change here
if (btn.ID == PutFTPButton.ID)
{
//bf.ReadyFilesForTransmission();
DirectoryInfo dir = new DirectoryInfo(#"C:\Kaplan");
FileInfo[] BatchFiles = bf.GetBatchFiles(dir);
bool result = transmit.UploadBatchFilesToFTP(BatchFiles);
//label text change here
if (!result)
{
ErrorLabel.Text += KaplanFTP.errorMsg;
return;
}
bf.InsertBatchDataIntoDatabase("CTL");
bf.InsertBatchDataIntoDatabase("HDR");
bf.InsertBatchDataIntoDatabase("DET");
bf.InsertBatchDataIntoDatabase("NTS");
List<FileInfo> allfiles = BatchFiles.ToList<FileInfo>();
allfiles.AddRange(dir.GetFiles("*.txt"));
bf.MoveFiles(allfiles);
//label text change here
foreach (string order in bf.OrdersSent)
{
OrdersSentDiv.Controls.Add(new LiteralControl(order + "<br />"));
}
//lblWait.Visible = false;
OrdersSentDiv.Visible = true;
OrdersInfoDiv.Visible = false;
SuccessLabel.Visible = true;
NoBatchesToProcessLbl.Visible = true;
BatchesToProcessLbl.Visible = false;
PutFTPButton.Enabled = false;
BatchesCreatedLbl.Text = int.Parse(NextBatchNum).ToString();
Thread.Sleep(20000);
if (KaplanFTP.errorMsg.Length != 0)
{
ErrorLabel.Visible = true;
SuccessLabel.Visible = false;
ErrorLabel.Text = KaplanFTP.errorMsg;
}
}
}
I think you can use an Ajax UpdateProgress control, check Progress Bar on File Upload ASP.NET.
EDIT: Another one Displaying Progress Bar For Long Running Processes using ASP.NET AJAX.
Application.DoEvents() is not available in an ASP.NET application, nor is it's use acceptable in a standard WinForms application with the advent of multicore processors and the .NET threading library.
A web application requires communication to/from a server. Therefore simply updating the text of a label does nothing unless you are sending that back to the client. In your case you would need an event which was signaled by this line (because it is a batch upload):
transmit.UploadBatchFilesToFTP(BatchFiles);
The event would update the value you want to display. You would then need some AJAX code (or an ASP.NET update panel around a ASP.NET label) on the web page in question to get and display the new value.
HTH
delegate void SetTextCallback(string text);
private void SetText(string text)
{
if (this.label1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else this.label1.Text = text;
}
void SomeMethod()
{
SetText(yourVariable.ToString());
}
if i understand you correctly this should work.
Related
I'm starting with SAP B1 UI API (9.0) and I'm trying to handle a button click without any luck so far. This is how I'm doing it (removing unnecessary to make it shorter):
static void Main(string[] args)
{
SetApplication(args);
var cParams = (FormCreationParams)App.CreateObject(BoCreatableObjectType.cot_FormCreationParams);
cParams.UniqueID = "MainForm_";
cParams.BorderStyle = BoFormBorderStyle.fbs_Sizable;
_form = App.Forms.AddEx(cParams);
/*Setting form's title, left, top, width and height*/
// Button
var item = _form.Items.Add("BtnClickMe", BoFormItemTypes.it_BUTTON);
/*Setting button's left, top, width and height*/
var btn = (Button)item.Specific;
btn.Caption = "Click Me";
_form.VisibleEx = true;
App.ItemEvent += new _IApplicationEvents_ItemEventEventHandler(App_ItemEvent);
}
private static void SetApplication(string[] args)
{
string connectionString = args[0];
int appId = -1;
try
{
var guiApi = new SboGuiApi();
guiApi.Connect(connectionString);
App = guiApi.GetApplication(appId);
}
catch (Exception e)
{ /*Notify error and exit*/ }
}
private static void App_ItemEvent(string FormUID, ref ItemEvent pVal, out bool BubbleEvent)
{
BubbleEvent = true;
if (FormUID == "MainForm_" && pVal.EventType == BoEventTypes.et_CLICK &&
pVal.BeforeAction && pVal.ItemUID == "BtnClickMe")
{
App.MessageBox("You just click on me!");
}
}
When I click the button nothing happens, is this the way to go? I've made so many variations in the handler method but nothing yet. Another detail is that the visual studio's debugger terminates as soon as the addon is started (maybe this has something to do with my problem).
I hope you can help me. Thanks in advance.
David.
Since the application stops running there are two possible answers to this question depending on what you prefer to use.
If you are using the SAPbouiCOM library you need a way to keep the application running, the way I use is the System.Windows.Forms.Application.Run(); from the windows forms assembly.
If you are using the SAPBusinessOneSDK and SAPbouiCOM.Framework as a reference you can use the App.Run();.
Both of these need to be invoked as soon as your setup code has run.
I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.
Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file.
This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).
Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.
private void bRecord_Click(object sender, EventArgs e)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);
data_feed = client.GetStream();
data_write = new StreamWriter(data_feed);
data_write.Write("<SEND_DATA/>\r\n");
data_write.Flush();
exit_state = false;
string behavior = null;
//code to launch form2 with the checkboxes
//...
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
int var = data_feed.ReadByte();
if (var != -1)
{
data_in += (char)var;
if (data_in.IndexOf("\r\n") != -1)
{
//code to check the checkboxes state in form2
//if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
//if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;
file.WriteLine(data_in + behavior);
data_in = "";
}
}
}
while (exit_state == false);
});
worker.RunWorkerAsync();
}
private void bStop_Click(object sender, EventArgs e)
{
exit_state = true;
worker.CancelAsync();
}
I hope I've been clearer now.
I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.
At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.
Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).
Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.
backgroundWorker.WorkerSupportsCancellation = true;
private void CancelBW()
{
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork += ((sender, args)
{
//Handle the cancellation (in your case do this in your loop for sure)
if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
return;
//Do your stuff.
});
There is no common way to directly cancel the backgroundWorker
operation. You always need to handle this.
Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.
private void MyForm : Form
{
private CancellationTokenSource ct;
public MyForm()
{
InitializeComponent();
checkbox1.Enable = false;
//Disable all checkboxes here.
ct = new CancellationTokenSource();
}
//Event if someone click your start button
private void buttonStart_Click(object sender, EventArgs e)
{
//Enable all checkboxes here
//This will be called if we get some progress from tcp
var progress = new Progress<string>(value =>
{
//check the behaviour of the checkboxes and write to file
file.WriteLine(value + behavior);
});
Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
}
//Event if someone click your stop button
private void buttonStop_Click(object sender, EventArgs e)
{
ct.Cancel();
//Disable all checkboxes (better make a method for this :D)
}
private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
{
do
{
if (ct.IsCancellationRequested)
return;
int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
if (temp != -1)
{
data_in += (char)temp;
if (data_in.IndexOf("\r\n") != -1)
{
if (progress != null)
progress.Report(data_in); //Report the tcp-data to form thread
data_in = string.empty;
}
}
while (exit_state == false);
}
}
This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.
The most important part is that you are not allowed to access gui
components in another thread then gui thread. You tried to access the
checkboxes within your BackgroundWorker DoWork which is no possible
and throw an exception.
So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.
Let me know if you have any further questions.
I am trying to update a textbox using the code behind in an ASP application like this:
protected void click_handler(object sender, EventArgs e)
{
Thread worker = new Thread(new ThreadStart(thread_function));
worker.Start();
}
protected void thread_function()
{
int i = 0;
while(true)
{
Textbox.Text = i.ToString();
i++;
}
}
The textbox shows one variable the first time but it doesn't get updated after that, what am I doing wrong? I searched and people are suggestin calling Textbox.Update or Textbox.Refresh but I think these are old as they don't exist anymore.
Thanks
You can't use server side code to update client side values in this manner. Anyway, you seem to be missing some kind of pause (e.g. Thread.Sleep) as currently your loop will run wildly out of control.
You need to look into using client side scripting (I.e. JavaScript) along with something like setTimeout:
https://developer.mozilla.org/en/docs/Web/API/window.setTimeout
Here's an example:
http://jsfiddle.net/PXN9K/
Apologies for the poor formatting and slightly lazy naming/refactoring, but I'm doing this on my phone.
Here's the code from the fiddle, copied here for future posterity.
var i = 0;
var updateTextbox =function()
{ document.getElementById('textbox').value = '' + i; }
var update = function() {
window.setTimeout(function() {
i++;
updateTextbox();
update();
}, 1000);
};
updateTextbox();
update();
I am developing a Windows Forms application that access a WCF service. I ran into a great problem that I can't predict the reason of it. Even the Visual Studio debugger not showing any exception in the Output view. The scenario is like this, I have a custom user control that has a linkLabel on it. Whenever the link label is clicked, a form is opened and a class object is passed to it. The class definition of this object resides on WCF service on a remote server. Now the problem is that when I click the linkLabel, the form opens perfectly loading each of its component according to the class object passed to it. But when I close this form and click that linkLabel again, the form opens but immediately freezes after loading some elements. I tried many code variations. Edited many part of code that I think can affect. But none of them showed the difference. Since, I don't know where actually is the code has error, I am posting the linkLabel click code and functions that are called after it is clicked.
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Enabled = false;
string temp = Title.Text;
Title.Text = "Opening...";
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(openTopic));
t.Start();
Title.Text = temp;
Enabled = true;
}
void createTopicWindow()
{
TopicViewer t = new TopicViewer(t);
Invoke(new Action(() => t.Show()));
}
private void openTopic()
{
Invoke(new Action(() => createTopicWindow()));
}
The above is the edited code, since I was getting Cross thread exception before.
Following is the code of constructor of the form that is called when clicked the linkLabel:
try
{
InitializeComponent();
this.t = topic;
if (IsHandleCreated == false)
CreateHandle();
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(loadTopic));
th.Start();
Common.openedTopics.Add(this);
AddComment addComment1 = new AddComment();
addComment1.Topic = t;
addComment1.Dock = DockStyle.Fill;
panel5.Controls.Add(addComment1);
backgroundWorker1.RunWorkerAsync();
}
catch (Exception)
{ }
void loadTopic()
{
Invoke(new Action(()=>tHead = new TopicHeader()));
Global.SetControlPropertyThreadSafe(tHead,"Topic", t);
Global.SetControlPropertyThreadSafe(tHead,"Dock", DockStyle.Fill);
Invoke(new Action(()=>panel1.Controls.Add(tHead)));
Global.SetControlPropertyThreadSafe(this,"Text", t.Title + " - Topic Viewer");
if (t.Description.Trim().Length > 0)
{
Global.SetControlPropertyThreadSafe(webBrowser1, "DocumentText", t.Description);
}
else
{
Invoke(new Action(() => tabControl1.TabPages[0].Dispose()));
}
Global.SetControlPropertyThreadSafe(tabPage2, "Text", "Comments (" + client.getComCount(t.TopicID) + ") ");
}
TopicHeader is another small user control.
Please anyone tell me the solution to this?
If you are using .Net 4.5, then using async/await would be easiest solution. That way, you don't need any Invokes
async private void Form1_Load(object sender, EventArgs e)
{
string s = await Task<string>.Factory.StartNew(LongRunningTask,
TaskCreationOptions.LongRunning);
this.Text = s;
}
string LongRunningTask()
{
Thread.Sleep(10000);
return "------";
}
I can't give a direct answer to you question, but this may give a hold on.
public void Form_Load()
{
// do some stuff on the gui-thread
// i need to do something that takes a long time:
ThreadPool.QueueUserWorkItem((state) =>
{
// i'll execute it on the ThreadPool
// Long running code....
// update results in mainform on gui thread.
Invoke(new Action( delegate
{
// because the invoke will execute this on the gui-thread, you'll able to update controls.
// update my gui controls.
DataGrid.Source = myReceiveDataThing;
}));
}
}
You might expand the code, to check if the form is still valid.
This is in Win forms
On button click I want to change the color of the button temporarily say only for 1 second and then the button color should get back to the previous color. I used lambda expression and timer for this.
private void btn_Read_Click(object sender, EventArgs e)
{
System.Windows.Forms.Timer t1 = new System.Windows.Forms.Timer();
t1.Interval = 1000;
t1.Tick += (src, ee) =>
{
btn_Read.BackColor = Color.Transparent; t1.Stop();
};
t1.Start();
btn_Read.BackColor = Color.YellowGreen;
lvwMessages.Items.Clear();
string strcommand = "AT+CMGL=\"ALL\"";
objShortMessageCollection = ReadSMS(strcommand); // Line wher I am reading messages from the port
foreach (ShortMessage msg in objShortMessageCollection)
{
ListViewItem item = new ListViewItem(new string[] { msg.Sender, msg.Message, msg.Sent, msg.Index });
item.Tag = msg;
lvwMessages.Items.Insert(0, item);
}
if (lvwMessages.Items.Count == 0)
{
status_other.Visible = true;
status_other.Text = "No messages";
lbl_total.Text = "Total: 0";
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
timer1.Interval = 2000;
timer1.Tick += (source, ex) => { status_other.Visible = false; timer1.Stop(); };
timer1.Start();
}
else
{
status_other.Visible = false;
chk_selectmsg.Visible = true;
btn_delete.Visible = true;
lbl_total.Text = "Total: " + lvwMessages.Items.Count.ToString(); ;
}
}
Later in this code I am reading the data from serial port, displaying it, etc. The problem is that the button color doesnot change as I click the button. It takes some time and does not give the desired feel I want. Some times doesn't even change the color. What could be the reason?
A simple solution would be using mouse hover event and mouse leave event
Use it this way:
private void btn_Read_MouseHover(object sender, EventArgs e)
{
btn_Read.BackColor = Color.AliceBlue;
}
private void btn_Read_MouseLeave(object sender, EventArgs e)
{
btn_Read.BackColor = Color.AntiqueWhite;
}
This does'nt require any change in your code and definitely will give you the functionality. See if it helps!
you should avoid having work-intensive code on the UI thread
to get the desired effect, sepperate the code for the UI from the code that does the work ...
when the button is clicked, change its appearence and start some background task (threadpool,backgroundworker,etc) that does the work
be aware that you can interact with a control only from the thread it was created on, so to display your data or interact with the UI, you will have to invoke the UI thread (see Control.Invoke(...))
if you have a lot of UI reseting stuff like that, you should think about a timer on the form, to check every let's say 200ms if there is something to be reset/done
you could use a sorted list with tuples (Datetime,delegate) that are executed and removed once the time has come ...
Write the rest of code in thread and fire that thread. this will make your UI responsive and will give you the desired output you want for button. Or use btnedit.Refresh() just after changing color to force button to redraw itself