-2

Im pretty new to C# but I have been playing around with it to learn. So far I have a nice app that does things like starts up the screen saver and controls the windows system volume.

However I'm having some trouble and I'm not sure what is wrong. I have already gone through a bunch of similar questions on the site but I don't think any of them apply to my specific case.

So I wanted to control the app from the web. My plan is to have the app check a webpage on my site for a command every couple seconds. Depending on what the site returns the app will do different things(mute, vol up, etc.) and reset the command on the website to blank. The website command part is all done in PHP and is quite simple, there is no problem with that part of it.

I created a button that calls a function that checks the page and performs the action. It worked fine. But when I tried to make it automatically check the site I'm getting errors. I'm pretty sure it is because I moved the check and perform action to a new thread. So lets move on to the code. These are not the full files but what I think you need to know to help. If you need anything more just let me know.

Form1.cs

public Form1(Boolean init = true)
    {
        if (init)
        {
            InitializeComponent(); //initialize UI
            startWebMonitor(); //starts web monitor thread for remote web commands
        }
    }

private void startWebMonitor()
    {
        Thread t = new Thread(WebMonitor.doWork);
        t.Start();
    }

public IntPtr getWindowHandle()
    {
        return this.Handle;
    }

WebMonitor.cs

public static void doWork()
    {
        while(true)
        {
            checkForUpdate();
            Thread.Sleep(1000);
        }
    }

private static void checkForUpdate()
    {
        lastCommand = getLastCommand();

        if (lastCommand.Equals(""))
        {
            //No Update
        }
        else
        {
            processCommand(lastCommand);
        }
    }

public static void processCommand(String command)
    {
        if(command.Equals("mute"))
        {
            VolumeCtrl.mute(Program.form1.getWindowHandle());
        }

        HTTPGet req2 = new HTTPGet();
        req2.Request("http://mywebsite.com/commands.php?do=clearcommand");
    }

VolumeCtrl.cs

private static IntPtr getWindowHandle()
    {
        Form1 form1 = new Form1(false); //false = do not initialize UI again
        return form1.getWindowHandle();
    }

    public static void mute(IntPtr handle)
    {
        SendMessageW(getWindowHandle(), WM_APPCOMMAND, getWindowHandle(), (IntPtr)APPCOMMAND_VOLUME_MUTE);
    }

Alright so basically the mute function requires the window handle in order to work. But when I try to get the window handle from the new thread it throws the error:

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

So how do I get around this? I have read other answers on here saying you need to use Invoke or Delegate but I'm not sure how those work or where to use them. I tried to follow one of the answers but I got even more errors.

mitchelltrout
  • 71
  • 1
  • 5
  • 3
    http://stackoverflow.com/q/142003/413032 did you search on SO? – Davut Gürbüz Apr 16 '13 at 15:36
  • 1
    And please don't use `Thread`s directly. For long-running I/O bound operations, use async/await. For long-running CPU bound operations, use `Task`s. – Daniel Mann Apr 16 '13 at 15:42
  • thanks for teaching me how to google.... how do you think i found this site? i went through about 7 other similar questions and none of it worked. so i asked my own question like it told me to do. – mitchelltrout Apr 16 '13 at 16:04
  • every other question is about updating the UI from another thread. but i dont want to update it i just want to grab a little bit of info(window handle) and return it. its the return part that does work with the other answers. – mitchelltrout Apr 16 '13 at 16:07
  • @mitchelltrout SOP for this site is to see if it has been asked before. When you ask a question, it did a search for similar. Copying your exact title yields a small handful that identify the same issue - that accessing any UI control from another thread causes this exception. – Adam Houldsworth Apr 16 '13 at 16:07
  • @AdamHouldsworth except its not the same issue. its the same error being thrown but not the same issue. please read my question in full before you assuming i did not already spend 3 hours reading answers on SO. – mitchelltrout Apr 16 '13 at 16:09
  • @mitchelltrout Clearly you haven't. And I also answered the issue in my comment. Accessing any UI control, whether reading or writing properties, from another thread is guarded. Stick it in a `Control.Invoke` and it'll likely start working. This is also likely the same sort of answer that would have been found in another similar question. `Control.Invoke` marshals the call from the other thread to the UI thread via the message pump in order to facilitate this sort of code. – Adam Houldsworth Apr 16 '13 at 16:10
  • 1
    The answer you accepted was nearly identical to the accepted answer in the question @DavutGürbüz linked to in his first comment. – JDB Apr 16 '13 at 16:18
  • @AdamHouldsworth after seeing the answer below i can see how similar the answer is. but trust me when i say i tried several of the other answers on similar questions and couldnt get it to work. my problem was with the return inside the invoke wasnt working. – mitchelltrout Apr 16 '13 at 16:19
  • @Cyborgx37 nearly yes. but it was a key difference that was keeping mine from working. and i could not figure it out myself. – mitchelltrout Apr 16 '13 at 16:24

2 Answers2

2

In this case, write invoke operation inside of method. Then you can access both from control's thread and other threads.

delegate IntPtr GetWindowHandleDelegate();

private IntPtr GetWindowHandle() {

    if (this.InvokeRequired) {

        return (IntPtr)this.Invoke((GetWindowHandleDelegate)delegate() {

            return GetWindowHandle();

        });
    }

    return this.Handle;
}

or, if you want to avoid delegate hell, you could write in more few code with built-in delegate and lambda.

private IntPtr GetWindowHandle() {
    if (this.InvokeRequired)
        return (IntPtr)this.Invoke((Func<IntPtr>)(GetWindowHandle));

    return this.Handle;
}
Mickael Bergeron Néron
  • 1,472
  • 1
  • 18
  • 31
Darksanta
  • 166
  • 1
  • 2
0

Try this code it will surely help you

BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s, e) => { };
bw.RunWorkerCompleted += (s, e) => 
 {
  //Call you windowsform method or anything which you want to do
 };
bw.RunWorkerAsync();
Pir Fahim Shah
  • 10,505
  • 1
  • 82
  • 81