1

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!

  • 1
    May you identify the error ? Is it an exception ? where ? The instruction "pictureBox.Image = img;;" is a source of error because you modify your form from another thread (https://stackoverflow.com/questions/661561/how-do-i-update-the-gui-from-another-thread) – Graffito Apr 20 '23 at 22:40
  • Hello graffito thanks for your response. I updated my post, as I am getting no exception or errors. But nothing seems to get displayed to the picturebox – Nicolantonio De Bari Apr 20 '23 at 23:31
  • 1
    Anything that interacts with the UI, must be performed on the Main UI thread. Else you will either get errors or if you are lucky nothing happens! So the updating of the picturebox image must be done on the UI thread. There are many examples how to do it, so take a look here: https://stackoverflow.com/questions/661561/how-do-i-update-the-gui-from-another-thread – jason.kaisersmith Apr 21 '23 at 05:20
  • 1
    First, I would make sure that the code for capturing a single frame, without any background threads, works, and that I can display the frame in the picture box. Then I would use the same code in the background thread, but as the others said, I would use the Invoke() or BeginInvoke() method to assign the picture to the picture box, since it needs to be done in the main thread. – Jiri Volejnik Apr 21 '23 at 06:21
  • 1
    In he threads, store your frames in a FIFO. In the UI, use a timer to get and display the frames. Even if such solution will (probably) not provide the expected performances, it will allow you to test the acquisition process. – Graffito Apr 21 '23 at 09:12

1 Answers1

1

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.

JonasH
  • 28,608
  • 2
  • 10
  • 23