Solved

Add WinForm to class

Posted on 2012-04-04
26
432 Views
Last Modified: 2012-04-08
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
0
Comment
Question by:tech1984
  • 13
  • 13
26 Comments
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37809125
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37809143
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
 

Author Comment

by:tech1984
ID: 37809217
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37809242
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
 

Author Comment

by:tech1984
ID: 37809253
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
 

Author Comment

by:tech1984
ID: 37809325
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37809363
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
 

Author Comment

by:tech1984
ID: 37810787
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37810966
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
 

Author Comment

by:tech1984
ID: 37811705
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
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 500 total points
ID: 37812272
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
 

Author Comment

by:tech1984
ID: 37812908
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37812919
Good luck...ask as many questions as needed.
0
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 

Author Comment

by:tech1984
ID: 37814321
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37814420
In my first code example, I demonstrated the use of the BackgroundWorker() control.  It is the easiest way to multi-thread something.
0
 

Author Comment

by:tech1984
ID: 37814896
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37814959
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
 

Author Comment

by:tech1984
ID: 37815864
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37815868
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
 

Author Comment

by:tech1984
ID: 37815878
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37815892
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
 

Author Comment

by:tech1984
ID: 37821872
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
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 37821984
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
 

Author Comment

by:tech1984
ID: 37822039
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
 
LVL 85

Assisted Solution

by:Mike Tomlinson
Mike Tomlinson earned 500 total points
ID: 37822204
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
 

Author Comment

by:tech1984
ID: 37822235
Amazing!!  Works perfectly!  Thank you so much for your time and patience.
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

Suggested Solutions

We all know that functional code is the leg that any good program stands on when it comes right down to it, however, if your program lacks a good user interface your product may not have the appeal needed to keep your customers happy. This issue can…
More often than not, we developers are confronted with a need: a need to make some kind of magic happen via code. Whether it is for a client, for the boss, or for our own personal projects, the need must be satisfied. Most of the time, the Framework…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

757 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now