Click here to Skip to main content
15,900,973 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hello!

I tried to invoke a Location change from another class than the main Form1, but of course, I couldn't. So, how could I do this?
I have a multi-threaded operations.

So here it is, it's in the class called players, and I started a new Thread for shoot. It's a very very simple game.

public class players
    {
        public PictureBox pb;
        public PictureBox[] golyok;
        private Image img;
        private int angle;
        private int weaponid;
        public players()
        {
            //
            //Load data
            //
            golyok = new PictureBox[50];
            for (int i = 0; i < golyok.Length; i++)
                golyok[i] = new PictureBox();
            
            img = Image.FromFile(@"D:\test.jpg");
            pb = new PictureBox();
            pb.Location = new Point(0, 0);
            pb.Size = new Size(60, 60);
            pb.SizeMode = PictureBoxSizeMode.StretchImage;
            pb.Image = img;
            weaponid = 1;
        }
        public void rotate(int subangle)
        {
            //
            //Rotate
            //
            angle = subangle;
            Bitmap image = (Bitmap)img;
            RotateBilinear filter = new RotateBilinear(angle, true);
            Bitmap newImage = filter.Apply(image);
            pb.Image = newImage;
           
        }
        public void shoot(object e)
        {
            //
            //Shoot
            //
            int golyox = pb.Location.X + 200;
            int golyoy = pb.Location.Y + 200;
            double targetx = -1;
            double targety = -1;
            switch (weaponid)
            {
                case 1:
                    targetx = golyox + 1 * Math.Cos(angle);
                    targety = golyoy + 1 * Math.Sin(angle);
                    break;
            }
            int gox = ((int)targetx - pb.Location.X) / 30;
            int goy = ((int)targety - pb.Location.Y) / 30;
            int index = GetFreeSlot();
            Control.CheckForIllegalCrossThreadCalls = false;
            /*BeginInvoke(new MethodInvoker(delegate() {*/ golyok[index].Location = new Point(golyox, golyoy);// }));
            golyok[index].Size = new Size(10, 10);
            Bitmap bm = new Bitmap(10, 10);
            Graphics g = Graphics.FromImage(bm);
            g.FillEllipse(Brushes.Blue, 0, 0, 10, 10);
            golyok[index].Image = bm;
            while (golyox != targetx || golyoy != targety)
            {
                golyox += gox;
                golyoy += goy;
                /*InvokeHelper(*/
                golyok[index].Location = new Point(golyox, golyoy);//));
                Thread.Sleep(50);
                
            }

        }
        private int GetFreeSlot()
        {
            for (int i = 0; i < golyok.Length; i++)
            {
                if(golyok[i].Image == null)
                {
                    return i;
                }
            }
            return -1;
        }
    }
Posted
Updated 7-Jan-11 9:15am
v8
Comments
William Winner 7-Jan-11 13:56pm    
Invoke deals with cross-thread operations and you haven't mentioned that you have multiple threads. Are you working in a multi-threaded application?
velvet7 7-Jan-11 14:01pm    
Yes.
William Winner 7-Jan-11 15:21pm    
Are you getting any kind of error, or is it just not working as expected? I don't see where you start shoot in another thread, so I assume it must happen in the form itself...is that correct?

And why did you set CheckForIllegalCrossThreadCalls to false?

Also, did you ever initialize the PictureBoxes in your array? As in
golyok[index] = New PictureBox()

You would also need to add it to your form for it to be displayed...are you doing that?

1. Do not make the controls public.
2. If the second class sets basic properties for the controls in first one, you can make use of partial class.
3. If your purpose is different, expose the control properties which are to be set as public properties.
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 7-Jan-11 14:51pm    
@d@nish: you point out quite useful development practice rules. Unfortunately, they are not exactly helping to resolve the problem. They may help indirectly, by helping to remove some mess before going to the core of the issue though...

In fact, I think even after the inquirer applies your rules, there will be more mess left to clean-up...
Sergey Alexandrovich Kryukov 7-Jan-11 14:53pm    
Also, you should offer alternative to a public control (a very good rule, by the way, of must-follow kind); take into account inquirer's value.
dan!sh 9-Jan-11 14:08pm    
@SAKryukov: I already have. Although I have not provided the code since I do not have anything handy and was too lazy to write one.
I don't fully understand because the second class has no name. But if the label is part of Form1, you would need an instance of Form1 to address (public) items on it.

create an instance:
Form1 form1 = new Form();


Reference the label somewhat like this:
form1.label.Text = "...";


Good luck!
 
Share this answer
 
Comments
dan!sh 7-Jan-11 14:09pm    
Label being a control in the form should not be public.
OriginalGriff 7-Jan-11 15:32pm    
My vote of 1: Sorry, but D@nish is absolutely right. Making labels (and any other control on a form) public is very bad practice, and should not be encouraged. It locks the design of the form so that it cannot be changed without affecting other classes. A better way would be to provide a public property which affects the label, but care needs to be taken even then.
E.F. Nijboer 7-Jan-11 16:47pm    
Yes, I absolutely agree but sometimes for the sake of simplicity I keep the answers as straight forward as possible. The problem in the question was that the label wasn't properly addressed and would therefor not work. It should be common sense to expose only that what needs to be exposed.
label is private by default, set modifier to public in the designer.

Then you can do

 Form1
 {
   static Form1 Instance;
   Form1()
   {
   ...
     Instance = this;
   ... 
   }
 }

Form1.Instance.label.Text = "whatever";


Regards
Espen Harlinn
 
Share this answer
 
Comments
William Winner 7-Jan-11 13:59pm    
That's an interesting difference between VB and C#. VB defaults to adding controls to forms as Friend. So, you can access them automatically within the assembly.
dan!sh 7-Jan-11 14:02pm    
Making controls public is really bad practice.
Espen Harlinn 7-Jan-11 14:08pm    
Well, yes, it's far from ideal - but it is short and hopefully not too confusing. It's a "Quick answer" ;->
Of course you could "invoke" location change from anywhere; but your question if not formulated correctly. Strictly speaking, calls are done not with classes, but with objects (class instances); in the context of your question this is important.

Now, what is called "invocation" in .NET as opposed to normal call have nothing to do with difference in classes or instances. There are at least two different notions:

1) Invoke a call based on method meta-data and some object (optionally null (method is static)) which is supposed to play the role of "this" on a non-static method (and also optional parameters) -- this is all about Reflection.

2) Invocation of some method to be called in a different thread then a thread causing the invocation; the invocation is not actually a call at all; this is just posting information on desired call and parameters in some queue; all the rest relies on the foreign thread -- this is all about threading and so called dispatching.

If you do something like
C#
MyForm.Location =
    new System.Drawing.Point(myForm.Location.X + 100, 0);


it works; you simply need an instance of your form MyForm passed to the point of the call.

If you want this effect from the other thread though, this is harder to do. Now you really need invocation:

C#
MyForm.Invoke(
    new Action<Form>(
        (form) =>
        { //anonymous method body starts here
            form.Location =
                new System.Drawing.Point(
                    form.Location.X + 200,
                    10);
        } //anonymous method body end
    ),
MyForm);


Here, form if a formal parameter of the complite-time type Form, (run-time type will be the same as of MyForm), MyForm is the instance used twice: in first line it is used as this argument of Invoke needed to detect current UI thread and perform invocation; in last line, it is needed to pass the instance to the formal parameter form of the anonymous method, so the form.Location property knew what exactly to move.

Sometimes, you do not know if you really run this code from the same thread as UI or a different one. In this case, you can write more effective code which work faster (through immediate call) if the threads are the same. No need to examine and compare thread object. This is done through the predicate property Control.InvokeRequired. This is how:

C#
if (MyForm.InvokeRequired)
    MyForm.Invoke(
        new Action<form>(
            (form) => { //anonymous method body starts here
                form.Location =
                    new System.Drawing.Point(
                        form.Location.X + 200,
                        10);
            } //anonymous method body end 
        ),
    MyForm);
else
    MyForm.Location =
        new System.Drawing.Point(
            MyForm.Location.X + 200,
            10);

</form>


Looks tricky? Well, with experience and IntelliSense you do it almost automatically; much harder to explain it.

Despite some common misconception such inter-thread invocation can only be done to UI thread. The misconception is based on Dispatcher which can be called for anuwhere, but... only formally, in default case the effect will be equivalent the the call on a current thead. For non-UI thread the only way is to create all similar functionality from scratch: your own queue, dispatcher and invoke methods.
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 7-Jan-11 14:28pm    
Wow! That was a race! I started when there was no answers, when I posted it, there were 4.
velvet7 7-Jan-11 14:40pm    
Thanks for the long, and understandable answer, but unfortunately it still doesn't work when I try to call Form1.Invoke from class called Players. It says that an object reference is requeried for the non-static field, so yes, and that is clear.
Sergey Alexandrovich Kryukov 7-Jan-11 14:43pm    
It means you're missing something more basic. Please, post sample code, point out what's missing and describe desired effect precisely. Do not forget to explain the purpose of this effect.
Sergey Alexandrovich Kryukov 7-Jan-11 14:46pm    
Is some code in the class Players run in a different thread, not UI? Please post the sample code where you try Invoke. Did you set a break point there? Do you reach the point where you're calling Invoke under debugger?
velvet7 7-Jan-11 14:50pm    
Edited above.
As this is something like a threaded game, I have completely different advice.

I know many attempts to use motion of controls to render dynamic scenes.

Almost 100% of such project failed. This is because such design is by far inadequate. Many subtle difficulties are accumulated gradually as you develop; and performance degrade.

The approach should be very different.

1. With System.Windows.Forms, use just one main control to render the whole scene. (You can aux. controls like options, start/stop/pause, etc.). Use the surface of this main control (a panel of custom control, if fact) to render everything in Paint event (or OnPaint overridden method) and its Even Argument's Graphics to render all on-canvas. In this case, you almost never use Invoke, use Mutex objects instead, which is much, much easier. You scenario code thread will merely post changing data structures to be used by rendering and periodically invoke Control.Invalidate (this is nearly the only use of <code>Invoke).

2. Do pretty much the same for WPF. This is easier then System.Windows.Forms. You should use Canvas. With WPF, you do not care about rendering at all, instead, you simply move graphical elements on the canvas. You better use vector images (through XAML; you can pre-draw them in InkScape and convert to XAML).

I remember someone else in Answers proposed similar thing (as in (1)) in much more simple project.

Honestly, moving controls with System.Windows.Forms will get you anywhere.
 
Share this answer
 
v4

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900