0

I'm trying to add custom controls on a secondary thread, but when I close the window while the thread is still running i get this exception:

Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

I don't know if the reason for getting this exception is because of a missused thread or becuse I'm closing the window while the thread is still running.

this is the code that i get the exception for:

panelWall.Invoke(new Action(() =>
            {
                postControl = new FBPostUserControl(m_LoggedInUser.Name, m_LoggedInUser.ImageNormal, post.CreatedTime);
                postControl.PostBody = post.Message;
                postControl.Image = postImage;
                postControl.Dock = DockStyle.Top;
                postControl.BringToFront();
            }));

this is the code of my custom control:

public partial class FBPostUserControl : UserControl
{
    private readonly string m_UserName = string.Empty;
    private readonly Image m_UserProfileImage = null;
    private readonly DateTime? m_DatePosted = null;
    private Image m_Image = null;
    private string m_PostBody = string.Empty;

    public string UserName
    {
        get { return m_UserName; }
    }

    public DateTime? DatePosted
    {
        get { return m_DatePosted; }
    }

    public Image Image
    {
        get { return m_Image; }
        set
        {
            if (value == null)
            {
                pictureBoxImage.Visible = false;
            }
            else
            {
                pictureBoxImage.Visible = true;
                pictureBoxImage.Image = value;
                updateImageSize();
            }
        }
    }

    private void updateImageSize()
    {
        if (pictureBoxImage.Image != null)
        {
            double ratio = pictureBoxImage.Image.Width / pictureBoxImage.Image.Height;
            pictureBoxImage.Height = (int)(pictureBoxImage.Width / ratio);
            pictureBoxImage.SizeMode = PictureBoxSizeMode.Zoom;
        }
    }

    public string PostBody
    {
        get { return m_PostBody; }
        set
        {
            if (string.IsNullOrWhiteSpace(value) == false)
            {
                labelPostBody.Visible = true;
                labelPostBody.Text = value;
            }
            else
            {
                labelPostBody.Visible = false;
            }
        }
    }

    public Image UserProfileImage
    {
        get { return m_UserProfileImage; }
    }

    public FBPostUserControl(string i_Name, Image i_ProfileImage, DateTime? i_PostDate)
    {
        InitializeComponent();
        m_UserName = i_Name;
        m_UserProfileImage = i_ProfileImage;
        m_DatePosted = i_PostDate;

        refreshHeader();
    }

    private void refreshHeader()
    {
        pictureBoxUserImage.Image = m_UserProfileImage;
        labelName.Text = m_UserName;

        if (labelDate != null)
        {
            labelDate.Text = m_DatePosted.ToString();
        }
        else
        {
            labelDate.Visible = false;
        }
    }
}
Shay Lugassy
  • 119
  • 1
  • 2
  • 6
  • 1
    90% sure it is because of "I'm closing the window while the thread is still running." – Scott Chamberlain Sep 16 '16 at 14:45
  • I have gotten that error when attempting to close a window with an active thread. I ended up using a layman's test (if(this.FormClosing)return; but I'm sure there are much more elegant solutions out there. – Shannon Holsinger Sep 16 '16 at 14:46
  • 2
    Your thread keeps using the control even though it doesn't exist anymore. Kaboom of course. It is up to you to ensure that the thread stops before you allow the window to close. http://stackoverflow.com/a/1732361/17034 – Hans Passant Sep 16 '16 at 14:46

1 Answers1

1

12/1/2020 EDIT START

There are problems awaiting Task.Yield because of Dispatcher Priority as mentioned in https://getandplay.github.io/2019/05/15/transfer-of-execution-rights-Task-Yield-Dispatcher-Yield/

It is safer to await System.Windows.Threading.Dispatcher.Yield()

12/1/2020 EDIT START

First, I don't see that you're launching the operation in a new thread, because the Invoke method just post the action to the dispatcher queue in the UI thread.

So there's no real multithreading in your code, but while the action is performed, the user has had the opportunity to post a CLOSE FORM windows message, and it can be processed before your next Invoke. So to avoid the exceptión, check if the form is closed before your next Invoke.

By the way, I believe there's no real advantage in launching a new thread just to update graphic elements, because, at last, they have to be updated in the UI thread, and you're just spending time and resources in the round trip.

If you have a long graphic operation and you are targeting NET Framework 4.5 or higher, the standard way to do it is await an async method for the long graphics operation, and internally await Task.Yield() at intervals to give the user the opportunity to cancel, close the window, etc.

Basically Task.Yield() posts the method continuation to the UI dispatcher, and when it returns, you can check the form and cancel the long operation if the form is closed:

    async Task LongJustGraphicsOperation()
    {
        while (true)
        {
            //do some work and give a pause
            await Task.Yield();
            if (formIsClosed) break;
        }
    }

Task.Yield() is the Task version of the old VB doevents.

Note. Is somewhat tricky to check if a winform is closed Detect when a form has been closed

SERWare
  • 392
  • 3
  • 15