views:

106

answers:

3

I need to be able to let multiple instances of the same form be open as my application can be used in different places at once. On the other hand i need to be able to process the operations during the "OK" event one at a time to ensure data is stored safely and not overwritten by another form instance by accident.

I show my form using the .Show() method as i am using a few delegates in it:

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
    {
        bookingForm = new BookingForm(AddMemberBooking, AddUserBooking, CloseBooking);
        bookingForm.Show();
    }

I have tried to use the mutex to allow only one event of the OK button being pressed happen at a time, i have combined this with a Thread to meet the criteria i need.

When i click on the "OK" button i am given the following error:

"Cross-thread operation not valid: Control 'comboBoxDay' accessed from a thread other than the thread it was created on."

This is the code for my booking form class:

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; using System.Threading;

namespace Collection { //Allows the class to be serialized [Serializable()]

public delegate void AddMemberBookingMethod(int date, int time, int mNo);
public delegate void AddUserBookingMethod(int date, int time, string fName, string lName, string pCode);
public delegate void CloseBookingFormMethod();


public partial class BookingForm : Form
{
    public CloseBookingFormMethod CloseBookingForm;
    public AddMemberBookingMethod AddMemberBooking;
    public AddUserBookingMethod AddUserBooking;
    private Mutex bookingMut = new Mutex();
    private Thread thread;

    public bool IsUser;

    public BookingForm(AddMemberBookingMethod ambm, AddUserBookingMethod aubm, CloseBookingFormMethod cbfm)
    {
        InitializeComponent();
        AddMemberBooking = ambm;
        AddUserBooking = aubm;
        CloseBookingForm = cbfm;

        checkBoxMember.Checked = true;
        //Control.CheckForIllegalCrossThreadCalls = false;
    }

    private void checkBoxUser_CheckedChanged(object sender, EventArgs e)
    {
        if (checkBoxUser.Checked)
        {
            IsUser = true;
            checkBoxMember.CheckState = CheckState.Unchecked;
            textBoxMNo.Enabled = false;
            textBoxFName.Enabled = true;
            textBoxLName.Enabled = true;
            textBoxPCode.Enabled = true;
        }
        else
        {
            IsUser = false;
            checkBoxMember.CheckState = CheckState.Checked;
            textBoxMNo.Enabled = true;
            textBoxFName.Enabled = false;
            textBoxLName.Enabled = false;
            textBoxPCode.Enabled = false;
        }

    }

    private void checkBoxMember_CheckedChanged(object sender, EventArgs e)
    {
        if (checkBoxMember.Checked)
        {
            IsUser = false;
            checkBoxUser.CheckState = CheckState.Unchecked;
            textBoxFName.Enabled = false;
            textBoxLName.Enabled = false;
            textBoxPCode.Enabled = false;
        }
        else
        {
            IsUser = true;
            checkBoxUser.CheckState = CheckState.Checked;
            textBoxMNo.Enabled = false;
            textBoxFName.Enabled = true;
            textBoxLName.Enabled = true;
            textBoxPCode.Enabled = true;
        }

    }

    private void buttonOK_Click(object sender, EventArgs e)
    {
        this.thread = new Thread(new ThreadStart(MakeBooking));
        this.thread.Name = "bookingThread";
        this.thread.Start();
    }

    private void MakeBooking()
    {
        this.bookingMut.WaitOne();

        int date = this.comboBoxDay.SelectedIndex;
        int time = this.comboBoxTime.SelectedIndex;

        if (IsUser)
        {
            string fName = textBoxFName.Text;
            string lName = textBoxLName.Text;
            string pCode = textBoxPCode.Text;

            AddUserBooking(date, time, fName, lName, pCode);
        }
        else
        {
            int mNo = int.Parse(textBoxMNo.Text);

            AddMemberBooking(date, time, mNo);
        }

        this.bookingMut.ReleaseMutex();

        CloseBookingForm();
    }

    private void buttonClose_Click(object sender, EventArgs e)
    {
        CloseBookingForm();
    }
}

}

I realise i may not be doing this in the most efficient way but time is a bit of a factor. I've researched the error and have heard of using delegates and .Invoke() but im still not entirely sure how to fix it.

Thanks!

EDIT:

I've found this code snippet when searching for a fix to my problem, i don't understand where/how i would use it.

if(this.InvokeRequired)

{ this.Invoke(new MyEventHandler(this.CreateAForm())); return; }

EDIT2:

Seems the guy finally saw sense, by creating the from with the "new" word it apparently passes the criteria, i wish i'd of known this before trying to reinvent the wheel.

A: 

I think you could simply disable the OK buttons on other open forms to give users a visual cue. Then you shouldn't even have the issue. Provide a callback delegate to something in the application controller which knows which forms are open. Each form can provide a public method to disable the OK button. Disable to OK button on all the other forms.

I'm not really following your code too well. I would think the mutex could be outside of the form code in the first place (i.e. in the delegates that do the actual work), and if it is within a single application, you could just use the lock (object) method to ensure only one thread is executing a given bit of code.

I'd also like to add that a mutex is not going to stop multiple users on different machiens being able to click OK at the same time. I'm not sure if that's what you meant in your question by a form being run in different places.

I think that AddUserBooking and the other delegate should be responsible for ensuring that they are threadsafe and this should not be part of the UI. If they aren't threadsafe, why aren't they? It's relatively easy to make database commit functions each have their own connection to the database during their operations and thread-safety should not be an issue.

Cade Roux
I was given the impresion that the mutex would allow only one form to process at a time. Would lock fix the error I'm getting? Which part of the code don't you follow?
Jamie Keeling
What are you starting a thread instead of just calling MakeBooking directly?
Cade Roux
I thought by creating a seperate thread it would allow me to have different users pressing "ok" at the same time.
Jamie Keeling
Different users on the same machine pressing the button at the same time? Threads only run on the machine, they have no built-in awareness of threads on any other machine (or even threads in other processes on the same machine).
Cade Roux
In summary, if you are trying to avoid people on different machines pressing OK at the same time, you are solving the wrong problem. There's no way you can stop people clicking OK at the same time without a **lot** more work. What you can do is ensure that when two people click OK at the same time (and they will), you can ensure the behavior is expected.
Cade Roux
Its a difficult thing to design for, the brief we've been given states at some point it will use network communication yet we don't need to implement it, so i don't get why it needs to be able to "lock" the OK to one user at a time.
Jamie Keeling
It soulds like there is no need to use a separate thread (despite the spec). Threads will not facilitate locking between different systems. I would have your code which handles the processing sinmply open its own connection to the database and ensure that it is threadsafe and that all database operations are wrapped in an appropriate transaction (presumably in a stored procedure). The simultaneous actions on the same or different machines are all good.
Cade Roux
At the moment all bookings are being stored into a collection within the program, when the program closes (Or if they press "Save") then the collection is written to a .txt file.
Jamie Keeling
A: 

One simple way to do this is to use the overload of the Thread.Start method that accepts an object: Thread.Start Method (Object). In this object you will store all the data/state necessary in order to make the update.

All the code that references the form and its controls needs to be moved into the OK click event method or refactored out to a method that just returns a data object. Then pass this object into the thread start method.

Some pseudo code:

on_click_event()
{
   object data=getFormData();
   thread.start(data);
}

There are better ways to do this but this is a quick fix for your code.

TskTsk
A: 

You are getting this exception because your thread is accessing controls. That's not legal, control properties must only ever be accessed from the UI thread. You're okay on the TextBox.Text property, that one happens to be cached. But not ComboBox.SelectedIndex. And closing the form from another thread is going to bomb too.

Your mutex has nothing to do with it, but keep it if you want to prevent threads from overlapping. Using a delegate's Invoke method isn't going to solve it, that just starts a thread as well. You'll need to collect the info that the thread is going to need in a little helper class and pass that as the argument to the Thread.Start() method.

Closing the form is a bit tricky too, the user might well have already closed it while the thread was running. That's going to cause an ObjectDisposed exception. A quick fix is to set the form's Enabled property to false so the user can't close it. You'll need to use the form's Invoke() method to ensure the closing is done on the UI thread.

Last but not least, if these threads don't take a lot of time (a second or so), consider not using threads at all and display a wait cursor instead.

Hans Passant
I don't want to use thread at all, i know that the benefits heavily depend on whether the application is suited to use threading but unfortunately it's a part of the criteria i need to meet.I need to be able to allow multiple booking forms to be open but only one can be processed at a time.How does the .Invoke() operation work exactly?
Jamie Keeling
Threading is *not* required to have more than one form open at the same time. The Windows message loop ensures that all of them stay responsive. Serializing the processing is automatic.
Hans Passant
So is it safe to assume that what im trying to do simply "shouldn't" be done?
Jamie Keeling
You haven't explained what you are trying to do. If you use threading only to allow more than one form to be displayed then, no, you shouldn't do that. If you use threading because the processing is slow and you don't want the UI to freeze then, yes, that's a good idea. You just have to follow the threading rulez. There's basically only one: you can't touch any controls in a thread.
Hans Passant
This is what i'm trying to do as described in the Spec:"As already mentioned, the implementation of the booking system will require concurrent access. Forms which add, remove or modify (actually, you can omit modify if you are pushed for time – the penalty will be minimal) should be modeless dialogs and must lock the bookings timetable while updating it","There will be special requirements for threading for one part of the assignment (making bookings)." The part im stuck on is the best way to do it. I know i shouldnt be using threading but im forced to use it.
Jamie Keeling
"The penalty will me minimal", that's good. What are the "special requirements?" That's rather key to the question. Interpreting a spec in a forum doesn't really work, we don't have the spec. It is best done with the writer of the spec.
Hans Passant
The "special requirments" are just to use threading to allow multiple booking forms to be open. This is the reasoning behind it: "Booking sessions, however, could be done by several people potentially at the same time. We will therefore want several booking session forms to be open at the same time, but the form itself to be locked while any actual update occurs.". Everything i've put now is all thats in the spec, he's a bit vague =/
Jamie Keeling