본문 바로가기
C++ 200제/코딩 IT 정보

C# state pattern, PictureBox 응용 구현 예제

by vicddory 2017. 3. 1.

디지인 패턴 중 상태패턴 (C# state pattern)을 활용한 응용 예제입니다. 실행 화면은 아래와 같습니다.


c# 스테이트 패턴 프로그램[디자인 패턴 예제 C#] 실행 화면


State Pattern 프로젝트 - TestImageButton(State).zip

스테이트 패턴 실행파일 TestImageButton(State)exe.zip


전체 소스는 바로 위 링크를 참조하세요.


화면에서 위로 버튼을 누르면, 12가 증가하고, 아래로 버튼을 누르면, 12가 감소합니다. C# state pattern 상태 패턴 예제 소스 설명 시작합니다.



c# State Pattern 구현할 기능이 담긴 인터페이스를 하나 선언합니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace TestImageButton_State_
{
    interface IState
    {
        // 그림이랑 값을 바꾸는거 두 개 밖에 없음
        // 추가하고 싶은 상태는 여기에 미리 정의
        void ChangePicture();
        void ChangeTestValue(int isIncrease);
    }
}
cs



이어서, c# 스테이트 패턴의 State를 제어할 컨트롤 클래스를 하나 선언합니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.Windows.Forms;
using System.Drawing;
 
namespace TestImageButton_State_
{
    class State
    {
        // 0~9까지의 버튼, 폼의 100, 10, 1자리 픽쳐 박스
        // 픽쳐 박스의 경우엔 박싱/언박싱 문제가 있을 수 있음
        public ImageList ilNo;
        public PictureBox[] pbValue = new PictureBox[3];
 
        IState state;
        IState nullState;
        IState valueChange;
 
        private int _controlValue = 500;
 
        public int controlValue
        {
            get { return _controlValue; }
            set { _controlValue = value; }
        }
 
        public int StateOfObject
        {
            set
            {
                if (value == 0) state = nullState;
                else if (value == 1) state = valueChange;
                else state = nullState;
            }
        }
 
        public State()
        {
            nullState = new NullState(this);
            valueChange = new ValueChange(this);
 
            ilNo = new ImageList();
            ilNo.ImageSize = new Size(3345);
            ilNo.Images.Add(Properties.Resources.no0);
            ilNo.Images.Add(Properties.Resources.no1);
            ilNo.Images.Add(Properties.Resources.no2);
            ilNo.Images.Add(Properties.Resources.no3);
            ilNo.Images.Add(Properties.Resources.no4);
            ilNo.Images.Add(Properties.Resources.no5);
            ilNo.Images.Add(Properties.Resources.no6);
            ilNo.Images.Add(Properties.Resources.no7);
            ilNo.Images.Add(Properties.Resources.no8);
            ilNo.Images.Add(Properties.Resources.no9);
        }
 
        public void ReleaseMemory()
        {
            // 이미지 리스트 메모리 해제
            for (int ilCount = 0; ilCount < ilNo.Images.Count; ilCount++)
            {
                ilNo.Images[ilCount].Dispose();
            }
 
            ilNo.Dispose();
 
            // 픽쳐 박스 메모리 해제
            pbValue[0].Dispose();
            pbValue[1].Dispose();
            pbValue[2].Dispose();
 
            GC.Collect();
        }
 
        public void ChangePicture()
        {
            state.ChangePicture();
        }
 
        public void ChangeTestValue(int isIncrease)
        {
            state.ChangeTestValue(isIncrease);
        }
    }
}
cs



그리고, 각 상태에 따른 c# State Pattern 동작을 구현해야합니다.

스테이트 패턴의 특징이 바로 구현부를 따로 떼어낸다는 것입니다.


아래는 스테이트 상태가 Null일 경우입니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.Windows.Forms;
 
namespace TestImageButton_State_
{
    class NullState : IState
    {
        State st;
 
        public NullState(State _st)
        {
            this.st = _st;
        }
 
        public void ChangePicture()
        {
            MessageBox.Show("그림 못바꿔요");
        }
 
        public void ChangeTestValue(int isIncrease)
        {
            MessageBox.Show("값도 못바꿔요");
        }
    }
}
cs



다음은, 스테이트 패턴에서 실제로 값을 변경하는 경우입니다.


상속받은 State Pattern 인터페이스를 먼저 정의합니다. ValueChange() 함수에서 조작해야 할 State를 상황에 맞게 변경해 주죠.


ChangePicture() 함수가 호출된다면, 100 자리의 수를 1자리 씩 끊어 별도의 변수에 저장합니다. 세 자리를 한꺼번에 다루는 상황이 아니기에 부득이 저렇게 해야 합니다.


계산이 끝나면, 일단 픽처 박스의 이미지를 비웁니다.

Dispose() 함수를 이용해서요.


이 소스를 생략하고 바로 새 이미지로 덮어씌워도 되는데, 이러면 메모리 누수가 발생합니다. 개발자는 이미지 교체를 생각했지만, 컴파일러 입장에선 이미지 위에 다른 이미지를 올리는 것이니깐요.


이점만 주의하면 됩니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace TestImageButton_State_
{
    class ValueChange : IState
    {
        State st;
 
        public ValueChange(State _st)
        {
            this.st = _st;
        }
 
        public void ChangePicture()
        {
            int hundred, ten, one, toBeChange;
            toBeChange = st.controlValue;
 
            hundred = Convert.ToInt32(Math.Truncate(toBeChange * 0.01));
            ten = Convert.ToInt32(Math.Truncate((toBeChange - (hundred * 100)) * 0.1));
            one = Convert.ToInt32(toBeChange - (hundred * 100- (ten * 10));
 
            // 각각의 이미지에 대한 Dispose를 해주지 않으면 메모리 좔좔 샘
            // 가비지 컬렉터는 완벽하지 않음
            st.pbValue[0].Image.Dispose();
            st.pbValue[1].Image.Dispose();
            st.pbValue[2].Image.Dispose();
 
            st.pbValue[0].Image = st.ilNo.Images[hundred];
            st.pbValue[1].Image = st.ilNo.Images[ten];
            st.pbValue[2].Image = st.ilNo.Images[one];
 
            GC.Collect();
        }
 
        public void ChangeTestValue(int tmpRefValue)
        {
            int tmp = 12 * tmpRefValue;
            st.controlValue += tmp;
            st.controlValue = (st.controlValue > 999) ? 999 : st.controlValue;
            st.controlValue = (st.controlValue < 100) ? 100 : st.controlValue;
        }
    }
}
cs



마지막으론, c# State Pattern 폼의 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace TestImageButton_State_
{
    public partial class Form1 : Form
    {
        State stateController;
 
        readonly int stateIsNull = 0;
        readonly int stateIsChange = 1;
        PictureBox[] pbValue = new PictureBox[3];
 
        public Form1()
        {
            InitializeComponent();
 
            pbValue = new PictureBox[3];
            pbValue[0= pbCount1;
            pbValue[1= pbCount2;
            pbValue[2= pbCount3;
 
            stateController = new State
            {
                pbValue = this.pbValue
            };
 
            // 처음엔 state 상태를 Null로
            stateController.StateOfObject = stateIsNull;
        }
 
        private void pbControl_MouseUp(object sender, MouseEventArgs e)
        {
            string targetName = ((PictureBox)sender).Name.ToString();
 
            // 마우스를 뗐으니 평상시 버튼으로 변경
            if (targetName.CompareTo("pbUp"== 0
            {
                pbUp.Image.Dispose();
                pbUp.Image = Properties.Resources.SubForm___Home___ArrowUp;
            }
 
            if (targetName.CompareTo("pbDown"== 0)
            {
                pbDown.Image.Dispose();
                pbDown.Image = Properties.Resources.SubForm___Home___ArrowDown;
            }
 
            GC.Collect();
 
 
            // 마우스를 뗐으니 왼쪽에 값 변경
            stateController.ChangePicture();
        }
 
        private void pbControl_MouseDown(object sender, MouseEventArgs e)
        {
            string targetName = ((PictureBox)sender).Name.ToString();
 
            // 선택된 버튼으로 교체
            if (targetName.CompareTo("pbUp"== 0)
            {
                pbUp.Image.Dispose();
                pbUp.Image = Properties.Resources.SubForm___Home___ArrowUp_sel;
            }
 
            if (targetName.CompareTo("pbDown"== 0)
            {
                pbDown.Image.Dispose();
                pbDown.Image = Properties.Resources.SubForm___Home___ArrowDown_sel;
            }
 
            GC.Collect();
 
            
            // state 설정
            stateController.StateOfObject = stateIsChange;
 
            // + 또는 - 설정
            int tmpRefValue = (targetName.CompareTo("pbUp")) == 0 ? 1 : -1;
            stateController.ChangeTestValue(tmpRefValue);
        }
 
        private void CreatePictureBox(string pbName)
        {
 
        }
 
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            stateController.ReleaseMemory();
 
            pbValue[0].Dispose();
            pbValue[1].Dispose();
            pbValue[2].Dispose();
 
            GC.Collect();
        }
    }
}
cs



pbControl_MouseUp() 함수와 pbControl_MouseDown() 함수는 화살표가 갖는 이벤트 함수가 되겠습니다.


종종, C#의 가비지 컬렉터를 지나치게 신뢰한 나머지, 메모리 해제의 필요성에 대해 아예 생각지도 않은채 넘어가는 경우가 많은데, 메모리 해제는 꼭 해줘야 합니다. 가비지 컬렉터는 똑똑할지언정 완벽한 존재는 아닙니다.


이것으로 C# 스테이트 패턴 예제 소개를 마칩니다.




State Pattern 프로젝트 - TestImageButton(State).zip

스테이트 패턴 실행파일 TestImageButton(State)exe.zip


ps. 이미지는 절대 경로로 하세요. 리소스에서 불러오는 것보다 훨씬 빨라요.



관련 글


[C# 디자인 패턴] 팩토리 메소드 패턴 예제 (Factory Pattern)


C# 팩토리 패턴 예제 (추상 Abstract Factory Method Pattern)


C++ 싱글톤 패턴 + Friend 키워드 예제, 디자인패턴 사용 방법



ⓒ written by vicddory

댓글