Add WinForm to class

I have written a class to backup and restore SQL databases using SMO.  What I am wanting to do is use a form to display a progressbar to display the percentage as the backup or restore happens.  I have the code to implement this and the delegate, but I wrote the class without thinking about the form.  I really want to initialize and use my class like this.

MySMO smo = new smo();

smo.BackupDB(database, filename);

I then want the form to open and display the progressbar.  Any ideas on how to implement this?

Thanks,
Brian
tech1984Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Mike TomlinsonMiddle School Assistant TeacherCommented:
Is your BackupDB() method properly threaded?  If not, you can use a BackgroundWorker() control on the Form to make it multi-threaded.

Does your class have custom events to report the progress of the backup?
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
Here's a simplified example of what it could look like:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {

        System.ComponentModel.BackgroundWorker BGW;

        public Form1()
        {
            InitializeComponent();
            this.Shown += new EventHandler(Form1_Shown);
        }

        void Form1_Shown(object sender, EventArgs e)
        {
            BGW = new BackgroundWorker();
            BGW.WorkerReportsProgress = true;
            BGW.DoWork += new DoWorkEventHandler(BGW_DoWork);
            BGW.ProgressChanged += new ProgressChangedEventHandler(BGW_ProgressChanged);
            BGW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BGW_RunWorkerCompleted);
            BGW.RunWorkerAsync();
        }

        void BGW_DoWork(object sender, DoWorkEventArgs e)
        {
            smo MySMO = new smo();
            MySMO.BackupProgress += new smo.BackupProgressDelegate(MySMO_BackupProgress);
            MySMO.BackupDB();
        }

        void MySMO_BackupProgress(smo sender, int progress)
        {
            BGW.ReportProgress(progress);
        }

        void BGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show("Done");
            this.Close();
        }

        void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }

    }

    public class smo
    {

        public delegate void BackupProgressDelegate(smo sender, int progress);
        public event BackupProgressDelegate BackupProgress;

        public void BackupDB()
        {
            for (int i = 0; i <= 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                if (BackupProgress != null)
                {
                    BackupProgress(this, i);
                }
            }
        }

    }

}

Open in new window

0
tech1984Author Commented:
Thanks for the reply, but I  am having a little trouble following the example.  I am not new to coding, but not a pro by any means so I apologize if I am over looking something.  I see the "public class smo" but no constructor.  I see the public method "BackupDB" but I don't see how it opens the form.  My goal is to somehow have the method handle the opening of the form instead of having to call Show or ShowDialog.  Does this make sense?
0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

Mike TomlinsonMiddle School Assistant TeacherCommented:
I didn't include a constructor as I put the bare minimum into the class to make it compile.  The smo class is there mainly to demonstrate the custom event that it raises to report progress.

The class does not open the form, the form instantiates the class.  Since a form is involved, the project started as a WinForms project which makes the form the entry point.  I start the ball rolling in the Shown() event of the Form.
0
tech1984Author Commented:
Here is my method
public void BackupDB(string database, string filename, bool verify)
        {
            //Create SQL connection
            connect();

            //Create new backup
            Backup bkp = new Backup();

                try
                {
                    
                    bkp.Action = BackupActionType.Database;
                    bkp.Database = database;
                    bkp.Devices.AddDevice(filename, DeviceType.File);
                    bkp.Initialize = true;
                    bkp.Incremental = false;
                    bkp.ExpirationDate = DateTime.Now;

                    //handle the percentages 
                    bkp.PercentCompleteNotification = 10;
                    bkp.PercentComplete += new PercentCompleteEventHandler(ProgressEventHandler);

                    bkp.SqlBackup(strSqlServer);

                    //MessageBox.Show("Database was successfully backed up to: " + filename, "Info");
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Backup Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    //Disconnect from SQL
                    disconnect();
                }
        }

Open in new window


My problem is I don't know the best way to show the form and implement the delegate.
0
tech1984Author Commented:
Sorry, I missed your last post before posting my method.  So if the form is the entry point, does that mean the only way to start the process is by calling the .Show or .ShowDialog?  This is the only way that I know how to open a form.  This isn't exactly what I was hoping for, but I didn't know how/if I could do it either.
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
For a WinForms project, the Form passed to the Application.Run() line in program.cs will be shown automatically for you.

For example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Open in new window

0
tech1984Author Commented:
Oh, I think I didn't properly explain myself to begin with.  Let me start again.

I have a WinForm project created.  I wrote a class using SMO to handle the backup and restore of SQL databases.  I want to add graphical feedback in that class via a modal form pop-up style window (maybe even like a custom control; not familiar with creating).  I am hoping to still utilize the following syntax inside my main WinForm app.

MySMO smo = new MySMO();

smo.BackupDB(database, filename);   //this will popup a window with the progress


I kept asking about .Show and .ShowDialog because I had a WinForm project already.
Hope this makes more sense.  

Thank you for all your help!
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
I would recommend against making your MySMO() class create and show the progress dialog directly.  What if you want to use it without that dialog popping up?  Instead, make it raise custom progress events as demonstrated in my example.  Then, if you use it in a WinForms project, you can display that progress however you like using graphical controls.  If it's used in a console project, however, then you can still trap those custom events and display progress as text output.

If you still want to do it that way then you have two options:
(1) Create a dynamic form via code in your MySMO class.  
            Form progress = new Form();
            progress.ControlBox = false;
            progress.Text = "Backup Progress";
            ProgressBar pb = new ProgressBar();
            pb.Dock = DockStyle.Fill;
            progress.Controls.Add(pb);
            progress.Size = new Size(500, 75);
            progress.Show();

Open in new window

(2) Design the form in the IDE and instantiate from your class.
0
tech1984Author Commented:
I understand your concern about using it without a popup, but I think this is the way I want to go.  I think I would really like to go with option 2 and design the form in the IDE, but I am having trouble wrapping my head on how I would instantiate the form and pass  updates to it.  I think this is my lack of experience with delegates.  Quick example please?

Thanks again!
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
This assumes that the backup procedure is actually already threaded somehow.

The progress form is called ProgressForm.  On it would be a ProgresssBar called progressBar1 which has its Modifiers() property set to Public.

With that in mind, it could look something like this:
    public class MySMO
    {

        private ProgressForm progress = null;

        public void BackupDB(string database, string filename, bool verify)
        {
            progress = new ProgressForm(); // <-- create an instance of ProgressForm
            progress.Show();
            Application.DoEvents();

            //Create SQL connection
            connect();

            //Create new backup
            Backup bkp = new Backup();

            try
            {

                bkp.Action = BackupActionType.Database;
                bkp.Database = database;
                bkp.Devices.AddDevice(filename, DeviceType.File);
                bkp.Initialize = true;
                bkp.Incremental = false;
                bkp.ExpirationDate = DateTime.Now;

                //handle the percentages 
                bkp.PercentCompleteNotification = 10;
                bkp.PercentComplete += new PercentCompleteEventHandler(ProgressEventHandler);

                bkp.SqlBackup(strSqlServer);

                //MessageBox.Show("Database was successfully backed up to: " + filename, "Info");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Backup Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                //Disconnect from SQL
                disconnect();
                if (progress != null)
                {
                    progress.Close();
                    progress = null;
                }
            }
        }

        private void ProgressEventHandler() // <-- don't know the correct signature for this handler
        {
            int progressValue = 50; // <-- you're getting this somehow (probably thru parameters)
            UpdateProgress(progressValue);
        }

        private delegate void UpdateProgressDelegate(int value);

        private void UpdateProgress(int value)
        {
            if (progress != null)
            {
                if (progress.InvokeRequired)
                {
                    progress.BeginInvoke(new UpdateProgressDelegate(UpdateProgress), new object[] { value });
                }
                else
                {
                    progress.progressBar1.Value = value;
                }
            }
        }

    }

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
tech1984Author Commented:
Alright, I think I see how it all flows together.  Please allow me a day or so to get the time to try to implement and I will get back with you.

Thank you so much for the help.
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
Good luck...ask as many questions as needed.
0
tech1984Author Commented:
Thanks!  I am pretty busy tonight, but hope to get to it tomorrow.  One other question you may or may not want to tackle.  You stated that you assumed that my backup procedure was already threaded somehow.  This is actually not the case.  What I posted is actually my private method.  I have to overloaded public methods that I can call depending on what properties have previously been assigned in the class.  They then call the private method for the backup.  Any recommendations on threading this?  I have been trying to get into multi-threaded apps and recently the new Task with .Net4, but it is still all pretty new to me.  If this falls outside the scope of my original question I understand.

Thanks!
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
In my first code example, I demonstrated the use of the BackgroundWorker() control.  It is the easiest way to multi-thread something.
0
tech1984Author Commented:
so you would have a private BackgroundWorker for the MySMO class, initialize it in the constructor, and then in my public method for Backup or Restore where you called
BGW.DoWork += new DoWorkEventHandler(BGW_DoWork);

I could replace the BGW_DoWork with my private method for either BackupDB or RestoreDB after changing the arguments to the correct structure and properly sending my arguments over.  This about right?
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
You should only subscribe to the DoWork() event once, from the constructor.  To start it, call RunWorkerAsync() which fires off the DoWork() event.  From the DoWork() event, which is running in the new thread, call your backup or restore method.
0
tech1984Author Commented:
Ok, I think I see what you are getting at.  So should I start off with 2 BackgroudWorkers; 1 to handle the BackupDB and 1 to handle the RestoreDB and then I can run the appropriate RunWorkerAsync() depending on which is needed?
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
Either that, or create some kind of boolean variable that keeps track of whether you're backing up or restoring, and then check that variable in the DoWork() handler to determine which action to take.
0
tech1984Author Commented:
oh, ok.. I'll give it a shot and let you know how it all goes.  Last time I worked with BackgroundWorkers I remember that arguments had to be passed as objects or something.. is this correct or am I way off base.  Any tips or quick articles on passing all the arguments around that I will need.
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
You can pass whatever you want into RunWorkerAysnc().  Then, in the DoWork() handler, you cast the "e.Arguments" parameter to the correct type so you can use it.
0
tech1984Author Commented:
Ok, finally got to try it out and not exactly working.  I got the class working with BackgroundWorking and passed a object array to the RuncWorkerAsync and then broke everything back out in the DoWork.  I then determined if I need to run my BackupDB or RestoreDB.  The rest is setup like you suggested.

Once the ProgressEventHandler gets called with the updated percentages from the SMO, then passes it off to the updateProgress method to update the progress bar; things appear fine but bar doesn't move.  As I debug, I noticed that the InvokeRequired is always false so the delegate is never used.  I tossed an Application.DoEvents() in the updateProgress after setting the value and got the bar to move if I sent a value before actually starting the backup process.  Does not update if set the value from the RunWorkerCompleted (but can call MessageBox so I know it works).

Any thoughts?
0
Mike TomlinsonMiddle School Assistant TeacherCommented:
With the BackgroundWorker(), the ProgressChanged() event is ALREADY marshaled to the main UI thread for you so no Invoke()/Delegates should be necessary.  

If the ProgressBar is not moving, however, then two possibilities spring to mind:
(1) You've incorrectly marshaled the work back to the main UI thread using a delegate.
(2) The Backup & Restore methods are forcing the work to occur on the main thread without your knowledge.  This can be the case when using some third-party controls/libraries that utilize COM, or simply just don't play nice.

Either way, I'd need to see more complete code to suggest which is the cause.
0
tech1984Author Commented:
Haven't implemented for the restore yet, just started testing with the backup DB.  Let me know if this is not complete enough.  I'll post whatever you need.

I have another method that I pass a SQL connect string to it and am able to parse it to populate most properties.  This is still minimum code.  The starting point would be the public method of BackupDB(filename, verify).

You might notice some code that is commented out that references public methods in the SQLFeedback form.  Prior to posting, I wrote public methods that I could call from this class to update the controls on the form.  I didn't feel this was the "correct" way to do it; that is why I started the post.  I don't have all the code removed and replaced, because I don't have working methods/delegates to handle everything yet.

        string strServer = "";
        string strDatabase = "";
        bool bolAuth = true;
        string strUserName = "";
        string strPassword = "";
        Server strSqlServer;
        SQLFeedback sf = null;
        BackgroundWorker BGW;

       public SqlSMO()
        {
            BGW = new System.ComponentModel.BackgroundWorker();
            BGW.WorkerReportsProgress = true;
            BGW.DoWork += new DoWorkEventHandler(BGW_DoWork);
            BGW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BGW_RunWorkerCompleted);
        }

       public void BackupDB(string FileName, Boolean Verify)
        {
            //make sure db isn't empty
            if (strDatabase == "")
            {
                MessageBox.Show("You must specify a database to backup", "Specify Database", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            //Backup the database
            BGW.RunWorkerAsync(new object[] { true, strDatabase, FileName, Verify });
        }

private void BGW_DoWork(object sender, DoWorkEventArgs e)
        {
            object[] args = e.Argument as object[];
            bool isBackup = (bool)args[0];
            string database = (string)args[1];
            string filename = (string)args[2];
            bool verify = (bool)args[3];

            //MessageBox.Show("IsBackup: " + isBackup.ToString() + "  Database: " + database + "  Filename: " + filename +  "   Verify: " + verify.ToString());

            if (isBackup)
            {
                //Run backup
                backupDB(database, filename, verify);
            }
            
        }

        private void BGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //Done
            //MessageBox.Show("Do Work is Done!!!", "Async Done", MessageBoxButtons.OK, MessageBoxIcon.Information);
            updateProgress(100);

        }

private void ProgressEventHandler(object sender, PercentCompleteEventArgs e)
        {
            //update progressbar in form
            updateProgress(e.Percent);
            
        }

private delegate void updateProgressDelegate(int value);

        private void updateProgress(int val)
        {
            if (sf != null)
            {
                if (sf.InvokeRequired)
                {
                    sf.BeginInvoke(new updateProgressDelegate(updateProgress), new object[] { val });
                }
                else
                {
                    sf.progBar.Value = val;
                    Application.DoEvents();
                }

            }
        }

private void backupDB(string database, string filename, bool verify)
        {
            //Create SQL connection
            connect();

            //Create new backup
            Backup bkp = new Backup();

            try
            {
                //Change Cursor to Wait
                //sf.SetCursor(Cursors.WaitCursor);

                try
                {
                    sf = new SQLFeedback();
                    sf.Show();
                    Application.DoEvents();
                    

                    bkp.Action = BackupActionType.Database;
                    bkp.Database = database;
                    bkp.Devices.AddDevice(filename, DeviceType.File);
                    bkp.Initialize = true;
                    bkp.Incremental = false;
                    bkp.ExpirationDate = DateTime.Now;

                    //handle the percentages 
                    updateProgress(10);
                    bkp.PercentCompleteNotification = 10;
                    bkp.PercentComplete += new PercentCompleteEventHandler(ProgressEventHandler);

                    bkp.SqlBackup(strSqlServer);

                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, "Backup Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    //Disconnect from SQL
                    disconnect();
                    //sf.SetProgressValue(0);
                }

                //Verify
                if (verify)
                {
                    bool bolVerify = verifyBackup(filename);
                    if (bolVerify)
                    {
                        //sf.SetVerifyText("Database: " + database + " Verify Successfull!!");
                    }
                    else
                    {
                        //sf.SetVerifyText("Database: " + database + " Verify Failed!!");
                    }
                }
            }
            finally
            {
                //sf.SetCursor(Cursors.Default);
            }
        }

private void connect()
        {
            //Make sure server is not empty
            if (strServer == "")
            {
                MessageBox.Show("You must specify the Server before connecting", "Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            //Check for username if using SQL auth
            if (bolAuth == false)
            {
                if (strUserName == "")
                {
                    MessageBox.Show("You must specify a User Name when using SQL authentication.", "Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }
            }

            //Connect to SQL
            strSqlServer = new Server(strServer);
            strSqlServer.ConnectionContext.LoginSecure = bolAuth;

            if (bolAuth == false)
            {
                strSqlServer.ConnectionContext.Login = strUserName;
                strSqlServer.ConnectionContext.Password = strPassword;
            }

            try
            {
                strSqlServer.ConnectionContext.Connect();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "SQL Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

        }

private void disconnect()
        {
            try
            {
                //disconnect from the SQL server
                strSqlServer.ConnectionContext.Disconnect();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "SQL Disconnect Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

Open in new window

0
Mike TomlinsonMiddle School Assistant TeacherCommented:
I think the problem is that you are creating your instance of the Form (SQLFeedback) from within the thread itself.

Try creating and displaying it before you call RunWorkerAsync():
       public void BackupDB(string FileName, Boolean Verify)
        {
            //make sure db isn't empty
            if (strDatabase == "")
            {
                MessageBox.Show("You must specify a database to backup", "Specify Database", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            sf = new SQLFeedback();
            sf.Show();

            //Backup the database
            BGW.RunWorkerAsync(new object[] { true, strDatabase, FileName, Verify });
        }

Open in new window


Be sure to remove those lines the backupDB() method.
0
tech1984Author Commented:
Amazing!!  Works perfectly!  Thank you so much for your time and patience.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
C#

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.