Lusterdude
Lusterdude

Reputation: 31

c# - Form "lags" after button being pressed

I made a extension to a game that I made which automatically presses a button when you press "Search Pixel" But each time I press the button whether it succeeds or fails, the memory for the application rises by about 30 Mega Bytes. Starts at 4.5 MB then once the button is click, it rises to 33.3 MB here is the code


    {

        public Form1()
        {
            InitializeComponent();
        }

        private const UInt32 MOUSEEVENTF_LEFTDOWN = 0x0002;
        private const UInt32 MOUSEEVENTF_LEFTUP = 0x0004;

        [DllImport("user32.dll")]
        private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, uint dwExtraInf);

        [DllImport("user32.dll")]
        private static extern bool SetCursorPos(int x, int y);

        private void OnButtonSearchPixelClick(object sender, EventArgs e)
        {
            // 01. get hex value from input field
            string inputHexColorCode = textBox1.Text;
            SearchPixel(inputHexColorCode);
        }

        private bool SearchPixel(string hexcode)
        {

            // Take an image from the screen
            // Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); // Create an empty bitmap with the size of the current screen 

            Bitmap bitmap = new Bitmap(SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height); // Create an empty bitmap with the size of all connected screen 

            Graphics graphics = Graphics.FromImage(bitmap as System.Drawing.Image); // Create a new graphics objects that can capture the screen

            graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size); // Screenshot moment → screen content to graphics object

            Color desiredPixelColor = ColorTranslator.FromHtml(hexcode);

            // Go one to the right and then check from top to bottom every pixel (next round -> go one to right and go down again)
            for (int x = 0; x < SystemInformation.VirtualScreen.Width; x++)
            {
                for (int y = 0; y < SystemInformation.VirtualScreen.Height; y++)
                {
                    // Get the current pixels color
                    Color currentPixelColor = bitmap.GetPixel(1550, 412);

                    // Finally compare the pixels hex color and the desired hex color (if they match, we found a pixel)
                    if (desiredPixelColor == currentPixelColor)
                    {
                        DoubleClickAtPosition(1387, 414);

                        return true;
                    }

                }
            }

            return false;
        }

        private void DoubleClickAtPosition(int posX, int posY)
        {
            SetCursorPos(posX, posY);

            Click();
            Thread.Sleep(250);
            Click();
        }

        private new void Click()
        {
            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
        }
    }
}

This code is from a video that I watched. If you need me to do anything like post the video or post any other code just let me know please I really need help. I also wanna just say that I'm really new to coding in c#.

Upvotes: 2

Views: 506

Answers (1)

Prime
Prime

Reputation: 2482

Explanation

Windows Forms uses a single threaded apartment model. This means everything in Windows Forms, from rendering to processing Windows messages (like mouse and keyboard events), occurs in a single thread.

You may have noticed in all of your WinForms applications you can see a [STAThreadAttribute] in your Program.cs. The 'STA' stands for 'Single Thread Apartment'.

Because WinForms runs in a single thread apartment, any intensive code you run in your WinForms STA thread will block WinForms from rendering your form and processing its mouse and keyboard events as it's only able to do one thing at a time.

The solution

The solution here is to run your intensive SearchPixel() function in a separate thread to the WinForms STA thread.

There are many ways to achieve this, such as a BackgroundWorker, or a hard Thread. However the easiest and most acceptable way would be to use a Task which is automatically deferred to a thread pool.

This is as simple as changing your code like so:

private void OnButtonSearchPixelClick(object sender, EventArgs e)
{
    // 01. get hex value from input field
    string inputHexColorCode = textBox1.Text;
    Task.Run(() => {
        SearchPixel(inputHexColorCode);
    });
}

Caveat

As your code will be running in a thread other than WinForms' STA thread it is not possible to interact with any object that WinForms is using without doing an invoke.

For example:

Task.Run(() => {
    label1.Text = "Test";
});

Will cause an InvalidOperationException, as you're attempting to access an object that WinForms expects to only be accessed in its single thread apartment.

You must tell the STA thread to invoke an action, which will cause the code to be run in the STA thread. Like so:

Task.Run(() => {
    //Code in here will run in a thread pool

    this.Invoke((Action)delegate {
        //Code in here will run in the STA thread
       label1.Text = "Test";
    });
});

Upvotes: 4

Related Questions