Search code examples
c#multithreadingwinformscomboboxpicturebox

C# Multithreading dynamically created Pictureboxes & Comboboxes


As shown in the image below, i have a simple WinForms application which has a main Combo-box to the right where in which based on the number of screens I select, I get an equivalent number of dynamically created combo boxes and Picture boxes. In this example I have selected 4.


enter image description here

As shown in the code bellow, I am trying to create a multi threaded process where in which depending on which of the options(of available cameras) I select in the dynamically created combobox then I want to display the image stream in the same index picturebox. For example if I select a camera in the combobox 1, then I want to display it in the Picturebox 1.

private void comboBoxCameraSelection_SelectedIndexChanged(object sender, EventArgs e)
        {
            panelComboboxes.Controls.Clear();
            tableLayoutPanel1.Controls.Clear();
            string selectedValue = comboBoxCameraSelection.SelectedIndex.ToString();
            int numNewPictureBoxes = Int32.Parse(selectedValue) + 1;
            // Set cell border style with a margin of 1 pixel
            tableLayoutPanel1.CellBorderStyle = TableLayoutPanelCellBorderStyle.Inset;

            for (int i = 0; i < numNewPictureBoxes; i++)
            {
                numComboBoxes++;
                ComboBox newComboBox = new ComboBox();
                newComboBox.Name = "comboBox" + numComboBoxes.ToString();
                newComboBox.Size = new System.Drawing.Size(100, 21);
                newComboBox.Location = new System.Drawing.Point(10, 50 + numComboBoxes * 25);
                newComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
                newComboBox.Dock = DockStyle.Top;
                LoadCameraList(newComboBox);
                newComboBox.SelectedIndexChanged += new EventHandler(comboBox_SelectedIndexChanged);
                panelComboboxes.Controls.Add(newComboBox);

                PictureBox pb = new PictureBox();
                pb.SizeMode = PictureBoxSizeMode.StretchImage;
                pb.Dock = DockStyle.Fill;
                pb.Anchor = AnchorStyles.None;
                pb.Margin = new Padding(1); // Add a margin of 1 to the picture box
                tableLayoutPanel1.Controls.Add(pb);
            }
            numComboBoxes = 0;
        }

        private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            ComboBox? comboBox = sender as ComboBox;
            if (comboBox != null)
            {
                int cameraIndex = int.Parse(comboBox.Name.Replace("comboBox", ""));
                int selectedItemIndex = comboBox.SelectedIndex;
                Debug.WriteLine("Selected camera index: " + cameraIndex + ", Selected item index: " + selectedItemIndex);
                PictureBox pb = (PictureBox)tableLayoutPanel1.Controls[cameraIndex-1];
                camera = new Thread(() => CaptureCameraCallback(selectedItemIndex-1, pb));
                camera.Start();
            }

        }

        private void CaptureCameraCallback(int camInd,PictureBox pictureBox)
        {
            frame = new Mat();

            capture = new VideoCapture(camInd);

            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("image/jpeg"));

            var apiUrl = "http://192.168.0.136:5000/object_detection";
            //capture.Open(2);
            while (isCameraRunning == 1)
            {
                capture.Read(frame);

                // Convert the frame to a JPEG image
                var buffer = new byte[frame.Width * frame.Height * frame.ElemSize()];
                Cv2.ImEncode(".jpg", frame, out buffer);

                // Send the JPEG image to the Flask API
                var content = new ByteArrayContent(buffer);
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
                var response = client.PostAsync(apiUrl, content).Result;
                var imageData = response.Content.ReadAsByteArrayAsync().Result;
                Image img;
                using (var stream = new MemoryStream(imageData))
                {
                    img = Image.FromStream(stream);
                }
                pictureBox.Image = img;
            }
        }

After running my script I get no errors or any kind of exception. But nothing gets displayed to the pictureboxes.

I believe that I am making a mistake in the comboBox_SelectedIndexChanged Method, when passing the picture-box. Maybe I'm referencing it wrong?

I hope that the general approach that I am taking is correct and hope someone can help me out...

Thanks!


Solution

  • I assume CaptureCameraCallback is run on a background thread. You can only update the UI from the UI thread:

    pictureBox.BeginInvoke((Action)(() =>
    {
        pictureBox.Image = img;
    }));
    

    In some cases you might need to call Control.Invalidate() to force updates. But I think that should be done automatically in this case. It was a while I did winforms stuff.

    Note that if you want large images at high framerates, winforms, might not be sufficient. You would want to reduce the amount of copying and allocations as much as possible, and other, lower level, APIs might be better for that purpose.

    I would also suggest creating an abstraction layer from your image source, so you can test the image source and UI separately.

    You should also ensure all disposable objects are correctly disposed.