C# 윈도우 폼에서 자주 사용하는 뷰는

textBox,

ListBox,

ListView,

DataGridView

 

정도가 되겠습니다. 이 중 ListView가 무언가를 보여줄 때 가장 효과적인 것 같습니다.

기본 사용법을 살펴보겠습니다.

 

리스트뷰를 가져온 상태입니다. 처음엔 아무것도 없는 백지인 상태!

 

오른쪽 상단의 > 화살표를 누르면 위와 같이 팝업창이 뜹니다.

천천히 알아보겠습니다.

그 전에 가장 보편적인 형태로 보도록 <뷰:  LargeIcon> 을 Details로 바꿔줍니다.

(뷰에 관한 것은 나중에 보여드릴께요~)

 

짠~ 아무것도 변한게 없습니다...

이제 [열 편집] 을 눌러주겠습니다.

 

추가를 눌러주고 싶게 생긴 ColumnHeader 편집기

추가 한 3개만 눌러봅시다. 그러면!!

 

리스트뷰에서 볼 수 있는 헤더가 생성되었습니다. 

컬럼명을 수정해 봅시다. 오른쪽에 Text 속성을 변경하면 됩니다. 차례대로 1, 2, 3 !

 

좋습니다. 나머지 속성은 딱 보면 아시겠죠?

TextAlign : 텍스트 정렬 위치 (Left, Center, Right)

Width : 헤더의 넓이

ImageIndex와 ImageKey 는 나중에 Image 를 집어넣을 때 사용할 수 있고

DisplayIndex를 통해 위치를 변경해줄 수 있습니다. columnHeader3의 DisplayIndex를 0으로 잠깐 해보면

3, 1, 2 순서로 변경되었습니다.

(하지만 말 그대로 보여지는 순서일 뿐 실제로 위 헤더 인덱스는 [0] 1, [1] 2, [2] 3 상태로 존재하게 됩니다.)

 

이번엔 내용을 추가해보겠습니다.

다시 팝업 메뉴로 돌아가서 이번엔 [ 항목 편집 ] 을 눌러줍니다.

 

마찬가지로 추가를 눌러줍니다.

 

오른쪽의 SubItems 를 선택하면 아래와 같은 창이 뜹니다.

2개를 추가한 상태이고 오른쪽에 Text 가 비어있기에 아무것도 보이지 않습니다.

각각 1111, 2222로 주면 리스트뷰에서 해당 값을 볼 수 있습니다.

다만 맨 첫 번째 값은 무시가 되었는데 이 녀석은 바로 요기에 있답니다.

 

위의 과정을 코드로 작성하게 되면 아래와 같이 되겠습니다.

using System.Windows.Forms;

namespace WindowsFormsTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            ListViewItem item = new ListViewItem();
            item.Text = "0000";
            item.SubItems.Add("1111");
            item.SubItems.Add("2222");

            listView1.Items.Add(item); //해당 아이템 추가!
        }
    }
}

 

그리고 값을 접근하기 위해선 listView1.Items 의 배열을 통하면 됩니다.

위의 0000, 1111, 2222 값은

 

listView1.Items[0].SubItems[0].Text // 0000

listView1.Items[0].SubItems[1].Text // 1111

listView1.Items[0].SubItems[2].Text // 2222

 

이런식으로 접근할 수 있고 수정도 가능하답니다.

1편은 여기서 끝!

.ini 파일은 메모장으로도 열리고 initialization 을 의미 합니다.

초기화 할 때 사용할 영역(section), 키(key), 값(value)을 가지고 있습니다.

저처럼 쫄지마세요.. 되게 간단합니다!

 

Write할 때 딱 세 가지만 아시면 됩니다.

1. 섹션이 없다면 섹션이 생성되고 키도 없다면 생성 됩니다. (키, 값이 해당 섹션으로 추가 됩니다.)

2. 동일한 키가 있다면 덮어 씌여집니다.

3. 삭제는 null 값으로 써주면 됩니다. (키 삭제, 섹션 삭제 가능)

 

예제 코드와 결과입니다.

using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices; //DllImport

namespace ConsoleApp2
{
    class Program
    {
        [DllImport("kernel32.dll")]
        private static extern uint GetPrivateProfileString(string section,
                                                           string key,
                                                           string defaultValue, //키값이 없을 때의 기본 값
                                                           StringBuilder returnedString,
                                                           uint size,
                                                           string filePath);

        [DllImport("kernel32.dll")]
        private static extern bool WritePrivateProfileString(string section,
                                                             string key,
                                                             string value,
                                                             string filePath);

        static void Main(string[] args)
        {
            string filePath = Path.Combine(Environment.CurrentDirectory, "test.ini");

            //파일이 없다면 만들어줍니다. 해당 프로젝트 Debug 폴더에 위치합니다.
            if (File.Exists(filePath) == false)
            {
                File.Create(filePath);
            }

            WritePrivateProfileString("Section1", "key1", "value1", filePath);
            WritePrivateProfileString("Section1", "key2", "value2", filePath);
            WritePrivateProfileString("Section1", "key333", "ooo", filePath);

            System.Threading.Thread.Sleep(1000); //Write 이 후 바로 못 읽기에 시간을 줍니다..

            StringBuilder sb = new StringBuilder { Capacity = 100 }; //프로퍼티 초기화 방식

            GetPrivateProfileString("Section1", "key1", "Nothing..", sb, (uint)sb.Capacity, filePath);
            Console.WriteLine(string.Format("[Section1]'s key1 = {0}", sb.ToString())); //Section1의 key1 값을 가져옵니다.

            GetPrivateProfileString("Section2", "key11", "Nothing..", sb, (uint)sb.Capacity, filePath);
            Console.WriteLine(string.Format("[Section1]'s key11 = {0}", sb.ToString())); //Section2가 없으므로 Nothing..이 나옵니다.

            WritePrivateProfileString("Section1", "key333", null, filePath); //key333을 지웁니다.

            /*
            WritePrivateProfileString("Section1", null, null, filePath); //Section1 전체가 사라집니다.
            */
        }
    }
}

섹션도 더 추가할 수 있어요~!!

 

직렬화의 의미는 데이터를 바이트화 시켜서 저장 및 불러오기가 가능하다고 이해하면 되겠습니다.

기존의 바이너리, 스트림을 이용해서 파일을 접근해도 되지만

시리얼라이저블을 이용하여 아주 편리하게 데이터를 쉽게 불러올 수 있습니다.

그 밖에도 Json, Soap 정도가 있겠지만 우선은 까먹기전에 정리해둡니다!

 

(멤버 변수에 위에 [NonSerialized] 선언을 해주면 해당 변수는 제외할 수 있습니다.)

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary; //BinaryFormatter

namespace ConsoleApp2
{
    class Program
    {
        [Serializable] //클래스는 반드시 시리얼라이즈 가능하다고 명시를 해주어야 가능합니다.
        class TestClass
        {
            public int value1;
            public int value2;
            public string value3;
        }

        private const string fileName = "testFile.txt";

        static void Main(string[] args)
        {
            TestClass testClass = new TestClass();

            testClass.value1 = 30;
            testClass.value2 = 50;
            testClass.value3 = "Test";

            string path = Path.Combine(Environment.CurrentDirectory, fileName);

            //저장 준비
            FileStream fileStream = new FileStream(path, FileMode.Create);
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            
            //저장!
            binaryFormatter.Serialize(fileStream, testClass);
           
            fileStream.Close();
        
            //저장 된 폴더를 여는 작업
            //.NET Framework 에서만 동작됩니다. .NET Core에선 권한이 필요해서 안되네요!
            System.Diagnostics.Process.Start(Environment.CurrentDirectory);


            //다시 읽어오는 코드입니다.
            FileStream fs = new FileStream(path, FileMode.Open);
            BinaryFormatter bf = new BinaryFormatter();

            TestClass t = (TestClass)bf.Deserialize(fs); //object 타입으로 반환되므로 캐스팅 해줍니다.

            Console.WriteLine("읽어 온 값 : ");
            Console.WriteLine("value1 : {0}, value2 : {1}, value3 : {2}", t.value1, t.value2, t.value3);

            fs.Close();
        }
    }
}

 

파일을 열어볼 수 있게 txt파일로 저장했습니다.

 

실행 결과

 

https://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations

 

C# Image/PictureBox Rotations

How to rotate any image from the center of that image

www.codeproject.com

이 곳의 소스를 참고하였습니다! ㅎㅎ

 

주요 준비물 (button, textBox, label, pictureBox, timer)

 

실행 결과

 

아래는 전체 코드입니다.

timer1_Tick 에 주요 이미지 복사, 회전 코드가 있고 흐름을 살펴보면

 

1. 새로운 비트맵을 만들어 기존 이미지 크기와 맞추어 그릴 준비를 합니다.

2. Graphics로 비트맵을 지정해줍니다.

3. 피벗을 센터로 옮겨줍니다. (처음엔 0, 0입니다.)

4. 회전 후 다시 피벗을 원 위치 시켜줍니다.

5. 새 이미지 등록 후 기존 이미지 리소스 해제합니다.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private Image masterImage;

        private float centerX;
        private float centerY;

        private float currentAngle = 0;
        private float angle = 1;

        private bool timerOn;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            DialogResult result = openFileDialog.ShowDialog();

            if (result == DialogResult.OK)
            {
                string filePath = openFileDialog.FileName;
                masterImage = new Bitmap(filePath);

                //미리 센터 좌표를 구해둡니다.
                centerX = masterImage.Width / 2;
                centerY = masterImage.Height / 2;

                pictureBox1.Image = (Image)masterImage.Clone(); //참조가 아닌 복사를 해줍니다.
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (masterImage == null)
            {
                MessageBox.Show("이미지가 먼저 등록되어야 합니다.");
                return;
            }

            if (timerOn)
            {
                timer1.Stop();
                timerOn = false;
            }
            else
            {
                float newAngle;
                //텍스트 내용이 숫자가 아닐 수도 있으니 조심해줍니다.
                if (float.TryParse(textBox1.Text, out newAngle) == false)
                {
                    newAngle = 1;
                }
                
                angle = newAngle;
                timer1.Start();
                timerOn = true;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            currentAngle += angle;

            if (currentAngle >= 360)
            {
                currentAngle -= 360;
            }

            label4.Text = string.Format("{0:F2}", currentAngle);

            Bitmap bitmap = new Bitmap(masterImage.Width, masterImage.Height);
            bitmap.SetResolution(masterImage.HorizontalResolution, masterImage.VerticalResolution);

            Graphics graphics = Graphics.FromImage(bitmap);
            graphics.TranslateTransform(centerX, centerY);      //센터로 피벗을 옮겨줍니다.
            graphics.RotateTransform(currentAngle);             //센터를 중심으로 돌려줍니다.
            graphics.TranslateTransform(-centerX, -centerY);    //다시 피벗을 원위치 시켜줍니다.
            graphics.DrawImage(masterImage, PointF.Empty);      //bitmap에 masterImage를 0,0 기준으로 그려줍니다.

            Image oldImage = pictureBox1.Image; //기존 이미지 메모리 해제를 위해 저장
            pictureBox1.Image = bitmap; //새로운 이미지로 대체해줍니다.
            
            if (oldImage != null) //기존 이미지 리소스 해제
            {
                oldImage.Dispose();
            }
        }
    }
}

 

 

C#에서 마우스 매크로 만드는 것을 차례대로 진행 해보겠습니다. (코드는 맨 마지막에 있습니다!)

만들어본 후엔 필요한 기능을 추가하셔서 사용하시면 되겠습니다. (어렵지 않아요~)

 

먼저 Visual Studio에서 Windows Form 만들기를 해줍니다.

전 .NET Framework 를 사용하였는데 .NET Core를 선택하셔도 상관 없습니다.

.NET Framework는 Windows 에서만 사용 가능하고

.NET Core는 다른 운영체제에서도 사용 가능합니다. 점차 .NET Core를 밀고 있는 추세입니다만

전 윈도우로 하니깐 그냥 프레임워크로 가겠슴돠

 

이름은 MouseMacro 입니다. ㄱㄱ

 

다른 기능 다 필요없습니다. 일단 폼 크기를 마음대로 줄여줍니다.

그리고 오른쪽 하단에 있는 속성 창에서 딱 두가지만 세팅해줍니다. (폼 클릭하고도 속성창이 안보이면 F4 누르세요)

FormBorderStyle = FixedToolWindow

Text = Mouse Macro

로 설정해주겠습니다. 훌륭한 UI 세팅으로 여기까지 따라하셨다면 벌써 70% 완성했습니다.

 

이제 F7을 눌러서 (혹은 마우스 오른쪽 클릭-> 코드 보기) 코드 보기로 갑시다.

MouseMacro를 오른쪽 클릭하면 사진처럼 새 항목을 추가할 수 있게 됩니다. 눌러줍시다.

 

클래스를 새로 만들겁니다. 이름은 HookManager로 결정하겠습니다.

Win32 API 를 사용할 것이기 때문에 코드를 복사 붙여넣기 하면 됩니다.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace MouseMacro
{
    //C:\Windows\System32\user32.dll 참조
    public class HookManager
    {
        #region WindowsAPI_Mouse Event

        [DllImport("user32.dll")]
        static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);

        //Microsoft MouseEvent Document
        //https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event

        const uint MOUSEEVENTF_MOVE = 0x0001;      // 마우스 이동
        const uint MOUSEEVENTF_ABSOLUTE = 0x8000;   // 전역 위치
        const uint MOUSEEVENTF_LEFTDOWN = 0x0002;    // 왼쪽 마우스 버튼 눌림
        const uint MOUSEEVENTF_LEFTUP = 0x0004;      // 왼쪽 마우스 버튼 떼어짐
        const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;    // 오른쪽 마우스 버튼 눌림
        const uint MOUSEEVENTF_RIGHTUP = 0x00010;      // 오른쪽 마우스 버튼 떼어짐
        const uint MOUSEEVENTF_WHEEL = 0x0800;        // 마우스 휠 (dwData 값으로 조정)

        #endregion

        #region WindowsAPI_Key Hook

        //Microsoft Hook Document
        //https://docs.microsoft.com/en-us/windows/win32/winmsg/hooks

        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc callback, IntPtr hInstance, uint threadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hInstance);

        [DllImport("user32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        private LowLevelKeyboardProc lowLevelKeyboardProc = HookProc;

        //WM_Keydown
        //https://wiki.winehq.org/List_Of_Windows_Messages
        const int WH_KEYBOARD_LL = 13;
        const int WM_KEYDOWN = 256;
        const int WM_KEYUP = 257;
        const int WM_SYSKEYDOWN = 260; //Alt Key Down

        private static IntPtr hookId = IntPtr.Zero;

        #endregion

        //Custom Variables
        private static Point preMousePosition;

        private static int leftControl = 1;
        private static int controlKey = 0;

        private static uint wheel = 120;
        private static uint backWheel = unchecked((uint)-120);

        public void SetHook()
        {
            IntPtr hInstance = LoadLibrary("User32");
            hookId = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, hInstance, 0);
        }

        public void UnHook()
        {
            UnhookWindowsHookEx(hookId);
        }

        public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code >= 0)
            {
                if (wParam == (IntPtr)WM_KEYDOWN)
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.NumPad0:
                            mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
                            mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                            return (IntPtr)1;

                        case Keys.NumPad1:
                            Cursor.Position = new Point(preMousePosition.X, preMousePosition.Y);
                            return (IntPtr)1;

                        case Keys.NumPad4:
                            preMousePosition = Cursor.Position;
                            return (IntPtr)1;

                        case Keys.LControlKey:
                            controlKey |= leftControl;
                            break;

                        case Keys.Up:
                            if (controlKey > 0)
                            {
                                mouse_event(MOUSEEVENTF_WHEEL, 0, 0, wheel, 0);
                                return (IntPtr)1;
                            }
                            break;

                        case Keys.Down:
                            if (controlKey > 0)
                            {
                                mouse_event(MOUSEEVENTF_WHEEL, 0, 0, backWheel, 0);
                                return (IntPtr)1;
                            }
                            break;
                    }
                }
                else if (wParam == (IntPtr)WM_KEYUP)
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.LControlKey:
                            controlKey &= ~leftControl;
                            break;
                    }
                }
            }

            return CallNextHookEx(hookId, code, (int)wParam, lParam);
        }
    }
}

코드가 좀 길지만 다른 곳은 보지 말고 (관심 있다면 살펴봐도 좋습니다.)

public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)

이 곳만 보고 입 맛에 맞게 수정하면 됩니다.

 

로직을 설명해보면

if (code >= 0) // 무언가 키 이벤트가 발생했다.

  if (wParam == (IntPtr)WM_KEYDOWN) //ㅇㅇ 키가 눌렸다고!

    Keys key = (Keys)Marshal.ReadInt32(lParam)

 

      key == Keys.???

      이제 key가 무엇인지에 따라 동작을 해주면 됩니다.

      (참고로 Alt 키는 wParam == WM_SYSKEYDOWN)

      (Label 또는 MessageBox에 출력해서 값을 봐보는 것도 좋습니당)

     

위의 샘플 코드는 numpad4 : 현재 마우스 위치를 저장하고

                       numpad1 : 저장 된 마우스 위치로 마우스 커서 이동

                       numpad0 : 마우스 클릭 발생

 

                       ctrl + 마우스 위/아래 : ctrl + wheel을 구현한 것으로 확대, 축소 기능입니다.

 

자세한 것은 코드를 분석해보시면 되겠습니다. 친절하게 마우스와 키 값에 대한 링크도 해두었습니다!

 

이제 마무리로 HookManager를 인스턴스화 해서 사용하면 됩니다.

이벤트 두 개만 추가해봅시다. 다시 폼 뷰로 돌아와서 속성 창의 번개 모양을 클릭해줍니다.

그러면 이벤트를 등록할 수 있게 됩니다.

Load를 더블 클릭하면 코드가 자동으로 생성되는데 다시 폼뷰로 돌아와

FormClosing도 더블 클릭해줍니다.

 

이제 마지막 코드입니다. 간단해유

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MouseMacro
{
    public partial class Form1 : Form
    {
        private HookManager hookManager;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //화면 가장 오른쪽에 띄우도록 합니다.
            int x = Screen.PrimaryScreen.Bounds.Width - this.Size.Width;
            int y = 0;

            this.Location = new Point(x, y);

            hookManager = new HookManager();
            hookManager.SetHook();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            hookManager.UnHook();
        }
    }
}

로드 될 때 폼을 오른쪽 상단에 배치하고 훅을 시작합니다.

폼이 꺼질 때 훅을 해제 해주며 마무리 합니다.

여기까지 다 만드셨다면 간단한 마우스 매크로 성공입니다! 실행해서 확인해 보시고..

 

추가 기능은 HookManager의 HookProc 를 수정해서 사용해보세요~

1. int, float 등은 숫자 입력이지만

    ? (물음표) 키워드를 이용해서 명시적으로 null 값을 줄 수 있습니다.

 

2. ?연산자로 null값이면 실행되지 않도록 조건문 대신 사용할 수 있습니다.

 

3. ?? 두개를 통해 null 값이면 다른 값으로 설정합니다.

 

코드가 짧아지는 장점이 있습니다.

하지만 무분별하게 사용하게 되면 가독성을 헤치게 됩니다.

using System;
using System.Collections;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            int? a = null;
            Console.WriteLine("a의 값 {0}", a);
            float? b = null;
            Console.WriteLine("b의 값 {0}", b);


            ArrayList arraylist = null;
            arraylist?.Add(30); //arraylist가 null이 아니면 Add(30)
            /*
            if (arraylist == null)
            {
                arraylist.Add(30);
            }
            */

            Console.WriteLine(arraylist?[0]); //null 이지만 오류가 나지 않습니다.

            a = a ?? 20; //a가 null이면 20
            b = b ?? 30.9f; //b가 null 이면 30.9f
            Console.WriteLine("a = {0}, b = {1}", a, b);
        }
    }
}

 

[실행 결과] null은 공백으로 출력됩니다.

 

1. 바이너리 파일로 저장하고 읽어오는 코드입니다.

 여러 타입을 저장할 수 있는데 읽어올 때 반드시 저장된 순서대로 읽어줘야 됩니다.

 (문자열은 앞에 문자열의 길이 + 문자열이 저장되어 해당 길이만큼 읽어옵니다.)

using System;
using System.IO; //...Stream

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            //바이너리 파일 생성 및 저장
            FileStream fs = new FileStream("Test.bin", FileMode.Create);
            BinaryWriter bw = new BinaryWriter(fs);

            string input1 = Console.ReadLine();
            int input2 = Convert.ToInt32(Console.ReadLine());
            float input3 = Convert.ToSingle(Console.ReadLine());

            bw.Write(input1); //string
            bw.Write(input2); //int
            bw.Write(input3); //float

            bw.Close();
            fs.Close();

            //바이너리 파일 읽어오기
            FileStream fs2 = new FileStream("Test.bin", FileMode.Open);
            BinaryReader br = new BinaryReader(fs2);

            Console.WriteLine(br.ReadString()); //string
            Console.WriteLine(br.ReadInt32()); //int
            Console.WriteLine(br.ReadSingle()); //float

            br.Close();
            fs2.Close();
        }
    }
}

 

입력을 받고 읽어온 모습
저장된 데이터

 

2. StreamReader/StreamWriter 를 이용한 모습입니다. 문자열 그대로 저장이 됩니다.

using System;
using System.IO; //...Stream

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            StreamWriter sw = new StreamWriter("Test.dat");
            
            for (int i = 0; i < 5; ++i) //5개의 입력
            {
                sw.WriteLine(Console.ReadLine());
            }

            sw.Close();

            StreamReader sr = new StreamReader("Test.dat");

            while (!sr.EndOfStream) //sr.ReadLine() == null인 경우입니다.
            {
                Console.WriteLine(sr.ReadLine());
            }

            sr.Close();
        }
    }
}

5개의 입력을 받고 다시 출력해준 모습
저장된 데이터

 

보안적인 측면은 바이너리 코드가 읽기가 어려워지기 때문에 더 좋습니다.

아니면 암호화나 해시를 하는 방법을!

부모 클래스 또는 인터페이스를 이용하여 하위 요소를 생성 및 사용할 때 이용되는 패턴입니다.

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            int size = 3;
            int player = 0;
            Unit[] unit = new Unit[size];

            while (player < size)
            {
                Console.WriteLine("{0}p 종족을 입력하세요: (Zerg, Protoss, Terran)", player + 1);
                string race = Console.ReadLine();

                switch (race)
                {
                    case "Zerg":
                        unit[player] = new Zerg();
                        player++;
                        break;
                    case "Protoss":
                        unit[player] = new Protoss();
                        player++;
                        break;
                    case "Terran":
                        unit[player] = new Terran();
                        player++;
                        break;
                    default:
                        break;
                }
            }

            Console.WriteLine();
            foreach (Unit u in unit)
            {
                u.ShowType();
            }
        }
    }

    abstract class Unit
    {
        public abstract void ShowType();
    }

    class Zerg : Unit
    {
        public override void ShowType()
        {
            Console.WriteLine("Zerg");
        }
    }

    class Protoss : Unit
    {
        public override void ShowType()
        {
            Console.WriteLine("Protoss");
        }
    }

    class Terran : Unit
    {
        public override void ShowType()
        {
            Console.WriteLine("Terran");
        }
    }
}

실행 결과

 

단 하나의 인스턴스를 통해 해당 클래스에 접근 및 사용할 수 있습니다.

다른 객체들간의 통신 등에도 유용하게 쓸 수 있는 방법!

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            GameManager.GetInstance().PrintHello();
        }
    }

    class GameManager
    {
        private static GameManager instance = new GameManager();
        public static GameManager GetInstance ()
        {
            return instance;
        }

        private GameManager()
        {
            //private로 외부에서 생성을 못하게 막아줍니다.
        }

        public void PrintHello()
        {
            Console.WriteLine("Hello");
        }
    }
}

등록 된 관측자들에게 메시지를 전달해주는 패턴. (EventListener 등과 같이 알게 모르게 많이 쓰이고 있습니다.)

인터페이스보다 델리게이트 사용이 더 간단한 듯합니다.

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main (string[] args)
        {
            Teacher t = new Teacher();
            Observer_Student marine = new Observer_Student("마린");
            Observer_Student zealot = new Observer_Student("질럿");
            Observer_Student zergling = new Observer_Student("저글링");

            t.AddStudent(marine);
            t.AddStudent(zealot);
            t.AddStudent(zergling);

            Console.WriteLine();
            t.ClassStart("달리기");

            Console.WriteLine();
            t.RemoveStudent(marine);

            Console.WriteLine();
            t.ClassStart("근접 공격");
            
            Console.ReadKey();
        }
    }

    class Teacher
    {
        private delegate void Teach(string subject);
        private Teach teach;
        
        public void AddStudent (Observer_Student student)
        {
            Console.WriteLine("{0}이 등록됩니다.", student.name);
            teach += student.Learn;
        }

        public void RemoveStudent (Observer_Student student)
        {
            Console.WriteLine("{0}이 등록 해제됩니다.", student.name);
            teach -= student.Learn;
        }

        public void ClassStart (string subject)
        {
            teach(subject);
        }
    }

    class Observer_Student
    {
        public string name;
     
        public Observer_Student(string name)
        {
            this.name = name;
        }

        public void Learn (string subject)
        {
            Console.WriteLine("{0}은 {1} 수업을 받고 있습니다.", name, subject);
        }
    }
}

 

실행 결과

 

+ Recent posts