C# Winforms - Cannot Access Disposed Object - c#

C# .net 3.5 Winforms application.
So here is the situation. I have a form that runs some code to that opens another forms in another thread. When that form opens, it performs certain tasks and then needs to close if those conditions are met. Problem is, it keeps throwing this exception and I have NO idea why.
]1)
Here are my snippets of code used in all this.:
Here is the function that opens the form in another thread: Program.cs
public static void new_downloader_thread()
{
Thread t = new Thread(new ThreadStart(open_downloader));
t.SetApartmentState(ApartmentState.STA);
if (Open_Downloader)
{
t.Start();
}
}
public static void open_downloader()
{
try
{
Application.Run(new DownloadInstall());
}
catch (Exception e)
{
MessageBox.Show(Convert.ToString(e));
}
}
Here is the code from the form that opens the form giving me the trouble: Manager.cs
private void new_System_Click(object sender, EventArgs e)
{
if (Program.Open_Downloader == false)
{
Program.Current_Download = "newsys.exe";
Program.Open_Downloader = true;
Program.new_downloader_thread();
}
else
{
download_busy();
}
}
Here is the form that is opening: DownloadInstall.cs
public partial class DownloadInstall : Form
{
/// Opening Declarations
private static System.Net.WebClient web_client;
private static System.Diagnostics.Stopwatch stop_watch;
private static bool downloading { get; set; }
private static bool is_name_update { get; set; }
private static int download_state { get; set; }
private static int elapsed { get; set; }
private static int opening { get; set; }
//private static int counter;
/// --------------------
public DownloadInstall()
{
InitializeComponent();
is_name_update = false;
downloading = false;
opening = 0;
web_client = new System.Net.WebClient();
web_client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Download_Progress);
web_client.DownloadFileCompleted += new AsyncCompletedEventHandler(Download_Complete);
stop_watch = new System.Diagnostics.Stopwatch();
/// Keypress event call and blanking out the box text.
this.name_update_field.Text = "";
this.name_update_field.KeyPress += new KeyPressEventHandler(no_period);
/// --------------------------------------------------
/// Changes the text to what file we are downloading.
this.file_name.Text = string.Format("Downloading {0}", Program.Current_Download);
/// -------------------------------------------------
/// Sets a forms closed event handler
this.FormClosing += new FormClosingEventHandler(Download_Window_Closing);
/// ---------------------------------
/// Creates folder where files are to be downloaded and deletes any residual files.
try
{
System.IO.Directory.CreateDirectory(#"C:\Downloads");
}
catch
{
}
if (System.IO.File.Exists(string.Format(#"C:\Downloads\{0}", Program.Current_Download)))
{
try
{
System.IO.File.Delete(string.Format(#"C:\Downloads\{0}", Program.Current_Download));
}
catch
{
if (Program.Disconnecting == false)
{
opening = 1;
}
}
}
/// -------------------------------------------------------------------------------
switch (opening)
{
/// Case 0 Starts the download or name update code normally.
case 0:
if (Program.Current_Download == "Name Update")
{
file_name.Text = "Name Update Downloader";
is_name_update = true;
this.restart_or_start.Text = "Start";
this.cid.Visible = true;
this.name_update_field.ReadOnly = false;
this.name_update_field.Visible = true;
}
else
{
Download_Begin();
}
break;
/// Case 1 will close the downloader.
case 1:
MessageBox.Show("It is possible this file was already downloaded and is already open on this computer.", "Hmmmmm...", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
this.Close();
break;
}
}
/// Function that is called when the window is closed.
private void Download_Window_Closing(object sender, FormClosingEventArgs e)
{
if (downloading)
{
web_client.CancelAsync();
}
Program.Current_Download = "";
Program.Open_Downloader = false;
}
/// --------------------------------------------------
Not sure why this is throwing the exception. It seems if I remove code that closes the form, it works, and I can close the form properly from other parts of the code. Any help would be great!

Solved!!! The problem is when trying to close the form before it has finished construction.
Changed:
this.Close();
to:
Load += (s, e) => Close();

Related

Synchronize an async Task in WinForms-Validating-Event

I have a classic WinForms app which communicates async with the server api. That is realized by async await pattern.
Now I have a problem with the 'Validating'-Event. We use this event for client-side Validation (check if input is null) and set cancel.
If input is valid then I send the input to server async and await the result.
There is my problem. Now the control means that it is valid. When the callback set the cancel event to false it is too late.
Here ist my code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; }
private async void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
var isValidAndSaved = await SaveOnServerAsync(CustomerName);
// Here is the Problem: Setting e.Cancel in callback is too late.
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000); // Send given customerName to server in real app.
return customerName.Contains("%") ? false : true;
}
}
I tried classic programming (synchron) for this case by 'Wait' the taks in main thread. But then I generated a deadlock.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; } = "3";
private void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
// Deadlock because MainThread waits for callback.
var isValidAndSaved = SaveOnServerAsync(CustomerName).Result;
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000); // Send given customerName to server in real app.
return customerName.Contains("%") ? false : true;
}
}
Has anyone an idea for a good pattern?
If I set Cancel to false before await the async task then my problem would be that the user click is discarded and the user experience is sufficient.
I found a working way. Except the first Task all other tasks need "ConfigureAwait(false)".
See method 'SaveOnServerAsync'.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; } = "3";
private void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
var isValidAndSaved = SaveOnServerAsync(CustomerName).Result;
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000)
// The solution is that all deeper Tasks need line below.
.ConfigureAwait(false);
;
return customerName.Contains("%") ? false : true;
}
}
I found many articles about that problem. It sounds a bit of missconcept from MS.
Figure 3 of this MS article explains the deadlock situation:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
The solution to avoid deadlocks is explained here:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#configure-context

Async-await in multiple Winforms projects

We're trying to get status updates in the UI while the threads are running in the background. The following code is supposed to allow it but in practice we get the updates only once all threads are done and not while they are running. We also don't see significant performance improvement compared to running the task in serial so we might be doing something wrong here
The solution includes two projects with winForm with the first calling the second. WinClient namespace is used for the Winform client. It calls Services.modMain:
namespace WinClient
{
static class Program
{
[STAThread]
static void Main()
{
//call another winform project and wait for it to complete
Services.modMain.loadObjects().Wait();
//run local form
Application.Run(new Form1());
}
}
}
Service.modMain is where the application is continuously getting data and updating it in memory. When it does, it writes status messages to a splash form which remains open all the time. Once Service.modMain finishes the initial data load, Form1 (empty form in this exampl) should open while splashForm remains open as well
namespace Services
{
public static class modMain
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main()
{
}
public static async Task loadObjects()
{
frmSplash.DefInstance.LoadMe();
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
frmSplash.DefInstance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task<bool>.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
frmSplash.DefInstance.PrintToSplashWindow(e);
}
}
}
PrintToSplashWindow is just a utility class to store progress data:
namespace Services
{
public class PrintToSplashMessage
{
public string Message { get; set; }
public Color MessageColor { get; set; }
public bool OnNewLine { get; set; }
public bool PrintToLog { get; set; }
public PrintToSplashMessage(String theMessage, Color theMessageColor, bool isOnNewLine, bool needPrintToLog)
{
Message = theMessage;
MessageColor = theMessageColor;
OnNewLine = isOnNewLine;
PrintToLog = needPrintToLog;
}
}
}
Finally, here's frmSplash:
namespace Services
{
public partial class frmSplash : Form
{
public frmSplash() :base()
{
InitializeComponent();
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if ( rtbErrorDisplay.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.SelectionColor = lngColor;
rtbErrorDisplay.SelectedText = strNewLine + strShortMsg;
rtbErrorDisplay.SelectionStart = rtbErrorDisplay.Text.Length;
rtbErrorDisplay.ScrollToCaret();
Application.DoEvents();
}
}
}
What we expect is that frmSplash would show the progress messages as the tasks are runing in the background. In practice, it only show all at bulk when everything is done.
Short version: the only thing that ever processes window messages in the code you posted is a call to Application.DoEvents(). But the code likely never gets that far, or if it does, the call happens on the wrong thread.
Longer version:
You didn't include an actual MCVE, so I didn't bother to test, but the Progress class relies on a synchronization context to work. Since you haven't called Application.Run(), there's may be no sync context at all. In which case Progress is just going to use the thread pool to invoke whatever handlers subscribed to it.
That would mean that when you call Application.DoEvents(), you're in a thread pool thread, not the thread that owns your splash window.
Windows are owned by threads, and their messages go to that thread's message queue. The Application.DoEvents() method will retrieve messages for the current thread's message queue, but does nothing to process messages for other threads' queues.
In the worst case, there is a sync context for that thread (I can't recall…it's possible that since the thread is STA, the framework has created one for you), but since you have no message loop, nothing queued to it ever gets dispatched. The progress reports just keep piling up and never processed.
You should abandon Application.DoEvents() altogether. Calling DoEvents() is always a kludge, and there's always a better option.
In this case, use Application.Run() for the first form as well (the splash screen). Create that form and subscribe to its FormShown event so that you know when to call loadObjects(). At the end of that method, close the form, so Application.Run() will return and go on to the next Application.Run() call.
Here is a sample based on the code you did post, with me filling in the details (for both forms, just use the Designer to create a default Form object…the rest of the initialization is in the user code below).
For the splash screen class, I inferred most of it, and took the rest straight from your code. The only change I made to your code was to remove the call to Application.DoEvents():
partial class SplashScreen : Form
{
public static SplashScreen Instance { get; } = new SplashScreen();
private readonly RichTextBox richTextBox1 = new RichTextBox();
public SplashScreen()
{
InitializeComponent();
//
// richTextBox1
//
richTextBox1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
richTextBox1.Location = new Point(13, 13);
richTextBox1.Name = "richTextBox1";
richTextBox1.Size = new Size(775, 425);
richTextBox1.TabIndex = 0;
richTextBox1.Text = "";
Controls.Add(richTextBox1);
}
public void PrintToSplashWindow(PrintToSplashMessage theMessage)
{
print_to_window(theMessage.Message, theMessage.MessageColor, theMessage.OnNewLine);
}
public void print_to_window(string strShortMsg, Color lngColor, bool blnOnNewLine)
{
string strNewLine = String.Empty;
if (blnOnNewLine)
{
if (richTextBox1.Text.Length > 0)
{
strNewLine = Environment.NewLine;
}
else
{
strNewLine = "";
}
}
else
{
strNewLine = "";
}
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.SelectionColor = lngColor;
richTextBox1.SelectedText = strNewLine + strShortMsg;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
}
}
It's not clear to me why you have two different classes, both of which seem to be set up as the entry point for the program. I consolidated those into a single class:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
loadObjects();
Application.Run(new Form1());
}
public static void loadObjects()
{
SplashScreen.Instance.Shown += async (sender, e) =>
{
Progress<PrintToSplashMessage> messageToWindow = new Progress<PrintToSplashMessage>();
messageToWindow.ProgressChanged += reportProgress;
SplashScreen.Instance.print_to_window("Starting Services", Color.Black, true);
Task<bool> load1Task = load1(messageToWindow);
Task<bool> load2Task = load2(messageToWindow);
await Task.WhenAll(load1Task, load2Task);
SplashScreen.Instance.Close();
};
SplashScreen.Instance.ShowDialog();
}
private static async Task<bool> load2(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load2, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load2, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static async Task<bool> load1(IProgress<PrintToSplashMessage> progress)
{
return await Task.Run(() =>
{
PrintToSplashMessage theMessage = new PrintToSplashMessage("Load1, please wait...", Color.Black, true, false);
progress.Report(theMessage);
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(TimeSpan.FromSeconds(1)); // CPU-bound work
theMessage.Message = $"Load1, i = {i}";
progress.Report(theMessage);
}
return true;
});
}
private static void reportProgress(object sender, PrintToSplashMessage e)
{
SplashScreen.Instance.PrintToSplashWindow(e);
}
}

Mainform.cs read routine more than twice

real problem:
Mainform read routine more than twice. and all components and objects inside of MainForm empty
Example:
button first run of excellent
add my code:
namespace CheckNet
{
/// <summary>
/// Description of MainForm.
/// </summary>
delegate void Function();
public partial class MainForm : Form
{
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
Btn_Inicio.Click += new EventHandler (Btn_InicioClick);
// VerificationForm += new EventHandler (VerificationForm);
//
// TODO: Add constructor code after the InitializeComponent() call.
//
}
private DPFP.Template Template;
private DPFP.Template myTemplate;
//
void Btn_InicioClick(object sender, EventArgs e)
{
// call rutine ejecutar boton primera vez
//
if (this.panelContenedor.Controls.Count > 0)
this.panelContenedor.Controls.RemoveAt(0);
VerificationForm hijos = new VerificationForm();
hijos.TopLevel = false;
hijos.FormBorderStyle = FormBorderStyle.None;
hijos.Dock = DockStyle.Fill;
this.panelContenedor.Controls.Add(hijos);
this.panelContenedor.Tag = hijos;
hijos.Show();
}
void Button7Click(object sender, EventArgs e)
{
AddFormInPanel(new Hijo2());
}
private void AddFormInPanel(object formHijo)
{
if (this.panelContenedor.Controls.Count > 0)
this.panelContenedor.Controls.RemoveAt(0);
Form fh = formHijo as Form;
fh.TopLevel = false;
fh.FormBorderStyle = FormBorderStyle.None;
fh.Dock = DockStyle.Fill;
this.panelContenedor.Controls.Add(fh);
this.panelContenedor.Tag = fh;
fh.Show();
}
void Close_bottonClick(object sender, EventArgs e)
{
Close();
}
}
}
////////// call Form VerificationForm
namespace CheckNet
{
/* NOTE: This form is inherited from the CaptureForm,
so the VisualStudio Form Designer may not load it properly
(at least until you build the project).
If you want to make changes in the form layout - do it in the base CaptureForm.
All changes in the CaptureForm will be reflected in all derived forms
(i.e. in the EnrollmentForm and in the VerificationForm)
*/
public class VerificationForm : CaptureForm
{
public void Verify(DPFP.Template template)
{
Template = template;
ShowDialog();
}
protected override void Init()
{
base.Init();
base.Text = "Fingerprint Verification ";
Verificator = new DPFP.Verification.Verification(); // Create a fingerprint template verificator
UpdateStatus(0);
}
protected override void Process(DPFP.Sample Sample)
{
base.Process(Sample);
// Process the sample and create a feature set for the enrollment purpose.
DPFP.FeatureSet features = ExtractFeatures(Sample, DPFP.Processing.DataPurpose.Verification);
// Check quality of the sample and start verification if it's good
// TODO: move to a separate task
template= TomarHuellaBd();
Template=template;
if (features != null)
{
// Compare the feature set with our template
DPFP.Verification.Verification.Result result = new DPFP.Verification.Verification.Result();
Verificator.Verify(features, Template, ref result);
UpdateStatus(result.FARAchieved);
if (result.Verified)
{
MakeReport2(" ");
}
else
MakeReport("Huella no Identificado.");
}
}
private void UpdateStatus(int FAR)
{
// Show "False accept rate" value
SetStatus(String.Format("False Accept Rate (FAR) = {0}", FAR));
}
private DPFP.Template TomarHuellaBd()
{
// Verifica la Huella desde una base de Datos
bool Bandera=true;
try { // Validar Numero
int idEmpleado =int.Parse(Global_ip.Globalip.ToString());
} catch {
MessageBox.Show("Error Numero de Empleado ", Global_ip.Globalip.ToString());
Bandera=false;
//
return null;
//
}
try
{
int NumEmpleado=int.Parse(Global_ip.Globalip.ToString());
enlacedb db = new enlacedb();
FbConnection conexion2 = new FbConnection(db.connectionString);
conexion2.Open();
FbCommand Frda = new FbCommand("SELECT * FROM REGHUMAN WHERE ID_EMPLEADO=#ID_EMPLEADO", conexion2);
Frda.Parameters.Add("#ID_EMPLEADO",SqlDbType.VarChar).Value = NumEmpleado;
FbDataReader leerF = Frda.ExecuteReader();
bool fseek= leerF.Read();
if (fseek) {
Global_Nombre.GlobalNombre= leerF.GetValue(4).ToString();
Byte[] imageF = new Byte[Convert.ToInt32 ((leerF.GetBytes(2, 0,null, 0, Int32.MaxValue)))];
leerF.GetBytes(2, 0, imageF, 0, imageF.Length);
MemoryStream memfpt = new MemoryStream(imageF);
DPFP.Template template = new DPFP.Template(memfpt);
return template;
}
else
{
return null;
}
}
catch (Exception err2) {
MessageBox.Show(err2.ToString());
}
return null;
}
private DPFP.Template Template;
private DPFP.Template template;
private DPFP.Verification.Verification Verificator;
}
}
if the user requests running second time the same button
VerificationForm routines and residents are CaptureForm Digital Persona device and can not read the fingerprint
how to empty or reset the procedure or routine (MainForm, VerificationForm and CaptureForm)
Thank.
The Solution is:
modify the program.cs
private static void Main (string [] args)
{
Application.EnableVisualStyles ();
Application.SetCompatibleTextRenderingDefault (false);
Fm1 MainForm = new MainForm ();
Application.Run (fm1);
if (fm1.ToRestart)
Application.Restart ();
}
start
ToRestart public bool = false;
void Btn_InicioClick (object sender, EventArgs e)
{
ToRestart = true;
this.Close ();
}
to reset the fingerprint reader and winform controls
Thank

update a richtextbox from a static class

I have the following code:
namespace SSS.RemoteTruckService
{
public partial class Startup : Form
{
private Timer _gpsTimer;
private Timer _ppsTimer;
private Timer _creditCardTimer;
private Timer _iniTimer;
public string Message
{
get { return richTextBox_Message.Text; }
set
{
richTextBox_Message.Invoke((MethodInvoker)(()
=> richTextBox_Message.Text = DateTime.Now + " " +
value + Environment.NewLine + richTextBox_Message.Text));
}
}
public Startup()
{
InitializeComponent();
}
private void ButtonStartClick(object sender, EventArgs e)
{
StartRemoteTruck();
}
private void ButtonPauseClick(object sender, EventArgs e)
{
if (_gpsTimer.Enabled) _gpsTimer.Enabled = false;
if (_ppsTimer.Enabled) _ppsTimer.Enabled = false;
if (_creditCardTimer.Enabled) _creditCardTimer.Enabled = false;
if (_iniTimer.Enabled) _iniTimer.Enabled = false;
ProcessIniFile.StopProcess();
}
public void StartRemoteTruck()
{
Message = "RemoteTruck started.";
if (Settings.GlobalSettings == null)
{
Message = "GlobalSettings was null or not loaded. Cannot continue.";
Logging.Log("GlobalSettings was null or not loaded. Cannot continue.", "RemoteTruck", Apps.RemoteTruckService);
Environment.Exit(0);
}
if (Settings.GlobalSettings.IniFileWatcherEnabled)
{
ProcessIniFile.StartProcess();
}
CreateTimers();
}
And in the ProcessIniFile.StartProcess() I have the code:
namespace SSS.RemoteTruckService.inifile
{
public static class ProcessIniFile
{
private static DateTime _iniLastWriteTime;
private static readonly string Inifile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "sss.ini");
private static FileSystemWatcher _watcher;
public static void StartProcess()
{
ReadIniFile();
SaveCurrentIniReadings();
CreateIniFileWatcher();
}
public static void StopProcess()
{
if (_watcher != null)
{
_watcher.EnableRaisingEvents = false;
_watcher = null;
}
}
private static void CreateIniFileWatcher()
{
_watcher = new FileSystemWatcher
{
Path = Environment.GetFolderPath(Environment.SpecialFolder.Windows),
NotifyFilter = NotifyFilters.LastWrite,
Filter = "sss.ini"
};
_watcher.Changed += SssIniWatcherChanged;
_watcher.EnableRaisingEvents = true;
}
I'd like to pass back the the calling form the status of the reads of the file watcher.
Maybe I'm overthinking this, but if I want to add to the Message on the main form, how do I get to it?
You can use Events for that. Your process can send events and your form can handle them.
More info: http://msdn.microsoft.com/en-us/library/awbftdfh.aspx
The simple but not pretty way I like to use is to make that part of the form static as well. For example, creating a static variable WriteMessage, and in your Form Load or Startup(), you can set it:
WriteMessage = (s) => Message = s;
Sure this has some issues, but it's a quick way to get it done. One of those issues is that, you may need to use Dispatcher.invoke if you're not on the UI thread.

Cross-thread cross-form. Display a splash screen with a progress bar

My Solution:
So I managed to find another tutorial http://www.codeproject.com/KB/dotnet/Yet_Another_Splash_Screen.aspx and the sourcecode seemed to make more sense to me. Here is the code i'm using now. Main() is left untouched.
Splash.cs
`
public partial class Frm_Splash : Form
{
delegate void ProgressDelegate(int percent);
delegate void SplashShowCloseDelegate();
/// <summary>
/// To ensure splash screen is closed using the API and not by keyboard or any other things
/// </summary>
bool CloseSplashScreenFlag = false;
/// <summary>
/// Base constructor
/// </summary>
///
public Frm_Splash()
{
InitializeComponent();
progress_Splash.Show();
this.ClientSize = this.BackgroundImage.Size;
}
public void ShowSplashScreen()
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new SplashShowCloseDelegate(ShowSplashScreen));
return;
}
this.Show();
Application.Run(this);
}
/// <summary>
/// Closes the SplashScreen
/// </summary>
public void CloseSplashScreen()
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new SplashShowCloseDelegate(CloseSplashScreen));
return;
}
CloseSplashScreenFlag = true;
this.Close();
}
/// <summary>
/// Update text in default green color of success message
/// </summary>
/// <param name="Text">Message</param>
public void Progress(int percent)
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new ProgressDelegate(Progress), new object[] { percent });
return;
}
// Must be on the UI thread if we've got this far
progress_Splash.Value = percent;
// Fade in the splash screen - looks pro. :D
if (percent < 10)
this.Opacity = this.Opacity + .15;
}
/// <summary>
/// Prevents the closing of form other than by calling the CloseSplashScreen function
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SplashForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (CloseSplashScreenFlag == false)
e.Cancel = true;
}
}`
Form1.cs
public partial class Frm_Main : Form
{
Frm_Splash frm_Splash = new Frm_Splash();
public Frm_Main()
{
this.Hide();
Thread splashthread = new Thread(new ThreadStart(frm_Splash.ShowSplashScreen));
splashthread.IsBackground = true;
splashthread.Start();
InitializeComponent();
CenterToScreen();
}
private void Frm_Main_Load(object sender, EventArgs e)
{
if (PassedAll() == true)
FillMovieLB();
if (FillMovieProgress == 100)
{
//Throw in this sleep so the user can see the progress bar reach all the way to the end.
Thread.Sleep(1000);
this.Show();
frm_Splash.CloseSplashScreen();
this.Activate();
}
}
Original Question
G'day all,
I'm very new to programming in C# and i'm having a problem with the http://www.codeproject.com/KB/cs/prettygoodsplashscreen.aspx tutorial and implementing it within my application. I'm finding it a little difficult to understand what the problem is. I know there is alot of stuff about getting this splash screen to work but I can't get my head around it.
When I start the program, the Frm_Main will display, you can see the listbox being populated, because i've placed it in BackgroundWorker.DoWork(), and then afterwards my frm_Splash will show after the work is done. Obviously, the way it should be working is, frm_Splash will show during the work being done on Frm_Main, and the progress bar will show the progress of the loading (this part I haven't implemented yet).
Edit: I may not have been clear, but the question is: How can I get my splashscreen to display while the work is being done and before the main form is displayed?
Thanks everybody. :)
Here is my code:
static Frm_Splash frm_Splash = new Frm_Splash();
public delegate void ShowFormDelegate();
public void ShowForm()
{
frm_Splash.Show();
}
public Frm_Main()
{
InitializeComponent();
CenterToScreen();
if (PassedAll() == true)
{
back_loadprog.RunWorkerAsync();
}
}
private void back_loadprog_DoWork(object sender, DoWorkEventArgs e)
{
Invoke(new ShowFormDelegate(ShowForm));
Invoke(new FillMovieLBDelegate(FillMovieLB));
}
Here, have some code... Works for me.
Splash Form:
namespace Screens.Forms
{
public partial class Splash : DevExpress.XtraEditors.XtraForm
{
public Splash()
{
InitializeComponent();
}
string RandomLoadingMessage()
{
string[] lines ={
"Pripremam warp pogon",
"Moj drugi ekran za učitavanje je brži, probaj njega",
"Verzija programa koju imam u testiranju imala je smiješnije poruke"
};
return lines[new Random().Next(lines.Length)];
}
public void RandomizeText()
{
lblMessage.Text = RandomLoadingMessage();
}
private void Splash_Load(object sender, EventArgs e)
{
RandomizeText();
}
private static Splash _splash;
private static bool _shouldClose;
static void ThreadFunc()
{
_splash = new Splash();
_splash.Show();
while (!_shouldClose)
{
Application.DoEvents();
Thread.Sleep(100);
if (new Random().Next(1000) < 10)
{
_splash.Invoke(new MethodInvoker(_splash.RandomizeText));
}
}
for (int n = 0; n < 18; n++)
{
Application.DoEvents();
Thread.Sleep(60);
}
if (_splash != null)
{
_splash.Close();
_splash = null;
}
}
static public void ShowSplash()
{
_shouldClose = false;
Thread t = new Thread(ThreadFunc);
t.Priority = ThreadPriority.Lowest;
t.Start();
}
internal static void RemoveSplash()
{
_shouldClose = true;
}
internal static void ShowSplash(List<string> fromTwitterMessages)
{
ShowSplash();
}
}
}
Show it with:
Splash.ShowSplash();
Do the work you need, then when done:
Splash.RemoveSplash();
You need to take this a step further back to your Main() function of the application.
In general you could do this:
Create a ManualResetEvent or better ManualResetEventSlim if you are on .NET 4
Start a new thread displaying your SplashScreen, use Application.Run
In your SplashScreen you should create a time which polls the created ManualResetEvent
frequently, a nice animation could be placed here also
If the event is set you should close the form
Back in your Main() do your stuff like creating forms etc.
When finish set the event, so that the SplashScreen can be closed
To be sure that your MainForm is not shown before your SplashScreen is closed you can use another event

Categories

Resources