C# 윈도우 폼 이벤트 핸들러 내 동작

안녕하세요 C# 윈도우 폼 공부 중에 이해가 안가는 부분이 있어서 질문 드립니다.
버튼의 클릭을 이벤트핸들러로 하여 픽쳐박스에 이미지를 넣은 후에 이미지를 Dispose할려는데
매개변수가 없다고 오류가 납니다.
아래와 같이 코드를 짰습니다.
private void button1_Click(object sender, EventArgs e)
{
this.pictureBox1.Image = openImage;
openImage.Dispose();
}
그래서 이벤트핸들러 내의 이벤트가 모두 종료되어야 폼에 적용되서 이미지가 없어서 오류가 난다고 생각하고있는데

아래같이 중간에 쇼로 다른 행동을 넣으면 Dispose가 됩니다.
private void button1_Click(object sender, EventArgs e)
{
this.pictureBox1.Image = openImage;
MessageBox.Show(“aaaaa”);
openImage.Dispose();
}

그래서 다른 이벤트를 넣어도 되는지 궁금해 텍스트박스를 넣으면 또 동작이 안됩니다.
private void button1_Click(object sender, EventArgs e)
{
this.pictureBox1.Image = openImage;
textBox1.Text = “4444”;
openImage.Dispose();
}

이벤트핸들러는 안에 내용이 모두 동작 후에 폼에 반영되어서 image가 이미 dispose되어서 픽쳐박스에 적용할 이미지가 없어서 오류가 나고
show는 새로운 폼을 생성하기에 폼을 생성하기 전까지 이벤트들을 모두 동작시키고
폼을 새로 생성하여 메인 폼에 픽쳐박스 이미지가 적용 된 후 Dispose를 하는 것 같다고 생각하는데

이게 어떻게 동작되는건지 정확히 알고 싶어서 질문 드립니다.

좋아요 1

이벤트 핸들러 내의 동작성의 문제가 아니라 PictureBox의 이미지를 Dispose 했을 때 생기는 문제입니다. Dispose 하지 않아야 합니다. 아래와 같이 사용하는 것이 맞습니다.

private void button1_Click(object sender, EventArgs e)
{
    var previousImage = this.pictureBox1.Image;
    this.pictureBox1.Image = openImage;

    if (previousImage != null)
        previousImage.Dispose();
}
좋아요 2

답변 감사합니다.

알려주신 코드로는 픽쳐박스의 새로운 이미지를 넣은 후에 Dispose로 이전에 사용한 이미지의 리소스 제거하는 것으로 이해 했습니다.

다른 의문점으로
이미지 하나만을 사용할 때 Dispose를 하면 안되는 건가요?
위 2번째 코드에서 Dispose를 해도 폼에서는 이미지가 그대로 남아있기에 이미지 하나일 때도 Dispose를 하는 게 좋을까 해서 짰썼습니다.
이 이미지를 계속 사용하면 Dispose하면 안되겠지만 한번 화면에 띄우기만 하는 것이라면 Dispose 가능할까요?

이와 별개로 이벤트 핸들러 내에 각각의 코드들이 언제 폼에 적용되는지 알고 싶습니다.
코드를 하나씩 진행시켰을 때
1번은 이벤트 핸들러 버튼클릭이 끝나고나서 폼에 이미지가(또는 다른 코드들) 적용되었는데
2번째 코드에서 show로 다른 폼을 생성하였을 때 그 위에있던 코드들이 폼에 적용되었는데
실제는 코드 한줄씩 폼에 반영되는데 디버그 과정에선 보여주지않는건지
아니면 폼에 적용되는 시기가 따로있는지 알고 싶습니다.

좋아요 2

네 맞습니다.

네 그렇습니다. PictureBox에서 사용하기 때문입니다.

값 적용은 즉각적으로 되나 화면에 표현되는 시점은 윈도우 메시지 루프를 타야 적용됩니다.
메시지 루프는 이벤트 핸들러에서 빠져나온 후 진입됩니다.

좋아요 3

일단 위의 코드는 "이벤트 핸들러"의 관점에서 보는게 아니라 "메인 스레드와 UI 스레드"의 관점에서 봐야합니다.

메인 스레드에서는 this.pictureBox1.Image = openImage;와 openImage.Dispose();는 순식간에 끝납니다.

하지만 this.pictureBox1.Image = openImage;가 호출되면 별도의 스레드(UI 스레드)에서 이미지 데이터를 가져와서 pictureBox1에 표시해주는 작업을 하게됩니다. 이렇게 폼 화면에 이미지를 표시하는데 "시간"이 소요되는데, 메인 스레드에서 openImage.Dispose();를 통해 이미지를 삭제해버리니까 UI 스레드에서 작업이 실패하는겁니다.

그럼 나중에 Dispose를 하면 폼의 화면에 이미지가 사라져야 하는 것 아닌가요?
→ this.pictureBox1.Image와 openImage는 바인딩(동기화)되어 있는게 아니라서 openImage를 삭제한다고 해서 이미 폼에 뿌려진 이미지가 바로 사라지지는 않습니다. 대신, 리프레쉬 이벤트가 발생되면 openImage를 다시 참조하려고하는데 그때 openImage가 없기 때문에 에러가 발생됩니다.

좋아요 3

답변 감사드립니다.

사소한 의문이지만
3번째 답변에서 메시지 루프를 타야 폼에 적용된다는 것은 제 2번째 코드에 show를 넣었을 때 show가 메시지 루프를 동작시키고 다시 이벤트 핸들러로 돌아온다는 건가요?
이게 맞다면 메시지 루프를 동작시키는 메소드도 따로 있나요? 또 show같은 자기 동작을 하면서 메시지 루프를 타는 메소드들이 무엇이 있나요?

좋아요 2

답변 감사합니다.

윗분의 응답에서는 폼에 적용되는게 이벤트핸들러가 종료 후 메시지 루프에 진입하여 폼에 적용된다고 하였는데 단지 시간이 지나면 메시지 루프와 상관없이 폼에 적용된다는 뜻인가요? 아니면 시간이 지나면 자동으로 메시지 루프에 진입한다는 건가요?
messageBox.show 대신에 쓰레드 슬립을 넣어서 긴시간 동작시켜도 오류가 났습니다.

다른 질문으로 pictureBox.Image는 참조라 하셨는데 그럼 나중에 Dispose를 하면 폼의 화면에 이미지가 사라져야 하는 것 아닌가요? 그러나 Dispose후에 값은 사라지는 걸 확인했는데 폼 화면에는 이미지가 표시되기에 왜 남아있는지 궁금합니다.

좋아요 2

답변 감사합니다.

Dispose후에 이미지가 남는 것은 이해했습니다.

스레드 관점으로 보아서
this.pictureBox1.Image = openImage;가 호출되면 별도의 스레드(UI 스레드)에서 이미지 데이터를 가져와서 pictureBox1에 표시해주는 작업을 하게됩니다. 라고 설명 주셨는데 이 코드 밑에 thread.sleep()을 적용하여 오랜시간 지나도 적용 되지않았습니다. 이에 UI 스레드도 슬립할 수 도 있다 생각하여
while(true){ textbox1.text = “1”;} 로 위 image와 전혀 상관없는 무한 루프를 생성하여 적용되기 까지 시간을 기다렸는데도 적용 되지않았습니다.

좋아요 2

올려주신 코드를 직접 테스트 해보진 않았지만…
아래와 같은 고전 코드를 써먹을 수도 있겠네요.
Application.DoEvents();

참고로 제 기억에 해당 명령은 MS에서 사용을 권장하진 않았습니다.

그리고 핵심은 UI 스레드 슬립도 아니고 뭐도 아닌, '참조의 제거’입니다.
this.pictureBox1.Image 에서 참조된 객체가 사라져버려서 그런거니까…

pictureBox1.Image?.Dispose();
pictureBox1.Image = null;

이렇게 처리해주시는게 좋을거 같습니다.

좋아요 1

답변 감사합니다.
주신 코드를 통해 이미지가 잘 지워지는지 확인했습니다.

Application.DoEvents(); 를 써서 dispose 하면 잘 동작 되는 것을 했습니다.

이 과정이 제가 이해한 바로는
private void button1_Click(object sender, EventArgs e)
{
this.pictureBox1.Image = openImage; // Application 쓰레드의 메시지 큐에 메시지를 쌓는다.
Application.DoEvents(); // 메시지 큐에있는 메시지를 처리한다.
openImage.Dispose();
}
이러한 과정으로 진행되어서 동작하는게 맞나요?

결국 그럼 아래와 같이 코드를 작성하면
private void button1_Click(object sender, EventArgs e)
{
this.pictureBox1.Image = openImage; //Application 쓰레드의 메시지 큐에 메시지를 쌓는다
} // 이벤트 핸들러를 빠져나가면서 Application의 메시지 큐를 동작(Application.DoEvents(); 동작이 포함됨)
이벤트 핸들러를 빠져나가면서 Application.DoEvents();이 적용된다고 생각해도 되는 건가요?

그렇다면 본 글의 2번째 코드에서 동작이 되었던 이유가
private void button1_Click(object sender, EventArgs e)
{
this.pictureBox1.Image = openImage;
MessageBox.Show(“aaaaa”);
openImage.Dispose();
}
MessageBox.show(); 에 Application.DoEvents();이 포함되어있어서 인가요?

좋아요 2

저도 이 부분에 대해서는 참조 오류나 메모리 누수만 안 나오면 장땡이다… 하고
그냥 Image Dispose에만 신경을 썼지 내부적인 로직에 대해서는 생각해본 적이 없네요;;

MessegeBox.Show() 함수 내부를 보면

Application.BeginModalMessageLoop();

이 부분이 있는데… 이 부분에서 메시지 루프를 돌리는 것 같기도 하고요…ㅎㅎ

좋아요 1

https://www.csharpstudy.com/DevNote/Article/21

이 글이 궁금증을 해결하는데 조금이나마 도움이 되셨으면 좋겠습니다~

좋아요 1

답변 감사드립니다.

덕분에 제가 궁금했던 부분들 해결되었습니다.
윈도우폼에서 동작에 큰 틀을 이해했습니다.
Control.Show와 MessegeBox.Show의 정의가 완전 달랐었는데 Control 쪽만 확인해서 놓쳤었네요.
감사합니다.

좋아요 4