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

 

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);
        }
    }
}

 

실행 결과

 

특정한 동작에 대해 클래스 내부에 대한 수정이 아니라 클래스 외부에서 수정하도록 설계 해둔 패턴!

인터페이스 또는 델리게이트를 사용하여 구현할 수 있겠습니다.

 

using System;

namespace ConsoleApp
{
    class Program
    {
        public static void Main (string[] args)
        {
            Unit marine = new Unit();
            Unit zerling = new Unit();
            Unit medic = new Unit();

            marine.SetAttack(new Marine());
            zerling.SetAttack(new Zergling());
            medic.SetHeal(new Medic());

            marine.Attack();
            marine.Heal();

            zerling.Attack();
            zerling.Heal();
            
            medic.Attack();
            medic.Heal();
        }
    }

    class Unit
    {
        private IAttack attack = null;
        private IHeal heal = null;

        public void Attack ()
        {
            if (attack != null)
            {
                attack.Attack();
            }
        }

        public void Heal ()
        {
            if (heal != null)
            {
                heal.Heal();
            }
        }

        public void SetAttack (IAttack attack)
        {
            this.attack = attack;
        }

        public void SetHeal (IHeal heal)
        {
            this.heal = heal;
        }
    }

    interface IAttack
    {
        void Attack();
    }

    interface IHeal
    {
        void Heal();
    }

    class Marine : IAttack
    {
        public void Attack ()
        {
            Console.WriteLine("가우스 라이플 공격");
        }
    }

    class Zergling : IAttack
    {
        public void Attack()
        {
            Console.WriteLine("발톱 공격");
        }
    }

    class Medic : IHeal
    {
        public void Heal ()
        {
            Console.WriteLine("치료");
        }
    }
}

 

실행 결과

 

using System;
using System.Net; //IPAddress, Dns
using System.Net.Sockets; //AddressFamily

namespace ConsoleServerTest
{
    class Program
    {
        public static void Main (string[] args)
        {
            PrintMyAddress();
        }

        private static void PrintMyAddress()
        {
            IPAddress[] host = Dns.GetHostAddresses(Dns.GetHostName());

            for (int i = 0; i < host.Length; ++i)
            {
                if (host[i].AddressFamily == AddressFamily.InterNetworkV6) //IPv6
                {
                    Console.WriteLine("IPv6 주소 [{0}]", host[i]);
                }

                if (host[i].AddressFamily == AddressFamily.InterNetwork) //IPv4
                {
                    Console.WriteLine("IPv4 주소 [{0}]", host[i]);
                }
            }
        }
    }
}

실행 결과

IP 주소는 고정 아이피를 신청하지 않는 이상 계속 바뀌게 됩니다.

다른 기기 (공유기, 스위칭 허브 등) 연결 없이 바로 연결했기 때문에

바로 제가 쓰고 있는 유동 아이피가 나온 모습입니다 ㅎㅎ

윈도우 폼을 이용하여 화면을 캡쳐하는 프로그램을 만들어 보겠습니다.

System.Drawing에서 제공되는 동작들을 통해 쉽게 결과를 얻을 수 있습니다.

using System;
using System.Windows.Forms;
using System.Drawing.Imaging; //PixelFormat, ImageFormat
using System.Drawing; //Bitmap, Graphics
using System.IO; //Path

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        private void ScreenShot(int width, int height, int x, int y)
        {
            Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            Graphics graphics = Graphics.FromImage(bitmap);

            graphics.CopyFromScreen(x, y, 0, 0, bitmap.Size);

            //현재 프로젝트 위치에 저장됩니다.
            string path = Environment.CurrentDirectory;
            string fileName = "image.png";

            //MessageBox.Show(Path.Combine(path + fileName));
            bitmap.Save(Path.Combine(path, fileName), ImageFormat.Png);
        }

        public Form1()
        {
            InitializeComponent();
            //Screen.PrimaryScreen : 1번 모니터
            //WorkingArea : 작업표시줄 제외한 범위
            ScreenShot(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height, 0, 0);
        }
    }
}

 

스크린샷 결과 화면

 

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices; //DLL import

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")] //C:\Windows\System32\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_SYSKEYDOWN = 260;

        private static IntPtr hookId = IntPtr.Zero;

        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_SYSKEYDOWN) //알트 키 눌림
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    if (key == Keys.PrintScreen)
                    {
                        MessageBox.Show("Alt + PrintScreen 키가 눌렸습니다.");
                        return (IntPtr)1; //1을 리턴하여 처리를 끝냅니다.
                    }
                }
                else if (wParam == (IntPtr)WM_KEYDOWN) //일반 키 눌림
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.A:
                            MessageBox.Show("A키가 눌렸습니다.");
                            return (IntPtr)1;

                        default:
                            break;
                    }
                }
                
            }

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

        public Form1()
        {
            InitializeComponent();
            SetHook();
        }

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

 

4개의 윈도우 API 함수가 필요하고 하는 일은 함수명을 보시면 짐작이 가실겁니다 ㅎㅎ

callback 함수에서 키에 대한 처리를 해주고 있는데

wParam는 어떤 타입의 키인지 정보가 들어있고 lParam 은 해당 키 값이 들어있습니다.

키값에 대해선 해당 주석 사이트에 나와있습니당!

어떤 문자를 입력 받아 해당 구문에 대해 올바른 문자열인지 판별할 때 Regex를 사용할 수 있습니다.

기능들이 많지만 사실 다 필요한 건 아니고 간단하게 쓸거잖아요? ㅎㅎ

일단 훑어봅시다.


위키백과 정규표현식 (보기 싫게 생겼다)


개인적으로 필요한 기능은 한글과 영어 숫자에 대한 입력만을 확인하기 위한 것이었습니다.

a~z, A~Z, 가~히, ㄱ~ㅎ, ㅏ~ㅣ, 0~9

 

using System;
using System.Text.RegularExpressions; //Regex

namespace ConsoleApp
{
    class Program
    {
        private static bool IsValidString (string input)
        {
            //^는 시작 의미
            //+는 최소 1개 이상을 의미
            //$는 끝을 의미
            //@" " 로 쓰는 이유는 \를 문자로 다루기 위해서입니다.
            //"c:\\" == @"c:\"
            return Regex.IsMatch(input, @"^[a-zA-Z0-9가-히ㄱ-ㅎㅏ-ㅣ]+$");

            //아래 코드와 같은 동작을 합니다..
            //for (int i = 0; i < input.Length; ++i)
            //{
            //    char ch = input[i];
            //    if (('a' <= ch && ch <= 'z') ||
            //        ('A' <= ch && ch <= 'Z') ||
            //        ('0' <= ch && ch <= '9') ||
            //        ('ㄱ' <= ch && ch <= 'ㅎ') ||
            //        ('ㅏ' <= ch && ch <= 'ㅣ') ||
            //        ('가' <= ch && ch <= '힣') ||
            //        ch == '_')
            //    {
            //        //ok
            //    }
            //    else
            //    {
            //        return false;
            //    }
            //}

            //return true;
        }

        static void Main(string[] args)
        {
            for (int i = 0; i < 5; ++i)
            {
                Console.Write("input : ");
                string input = Console.ReadLine();

                if (IsValidString(input))
                {
                    Console.WriteLine("유효한 문자열입니다.");
                }
                else
                {
                    Console.WriteLine("유효하지 않은 문자열입니다.");
                }
                Console.WriteLine();
            }
        }
    }
}

 

 

추가적으로 알아야 할 것은 input 길이가 더 길어도 패턴에 매칭되면 무조건 true를 반환한다는 것입니다.

 

. : 1개의 문자를 의미

 ex) Regex.IsMatch("123", @"...") == true     (3개가 매칭됩니다.)

      Regex.IsMatch("12344", @"...") == true  (3개가 매칭되면 true가 됩니다.)

      Regex.IsMatch("12", @"...")  == false     (2개만 매칭되어 false가 됩니다.)

 

[ ] : 내부의 모든 문자를 의미

   [a-z]  - 표시를 통해 a ~ z 까지의 의미를 표현합니다.

   [a-z] == [abcd...xyz]

   

   [^a-z] 내부에 ^문자가 들어가면 부정형태 즉 a~z까지 들어가면 false가 됩니다.

 

 ex) Regex.IsMatch("z", @"[a-z]")  == true

      Regex.IsMatch("z25", @"[a-z]" == true (앞의 1개가 매칭되어 true가 됩니다.)

      Regex.IsMatch("1", @"[a-z]" == false

using System;
using System.Diagnostics; //Process

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Process[] process = Process.GetProcesses();
            for (int i = 0; i < process.Length; ++i)
            {
                Console.WriteLine(process[i].Id + " " + process[i].ProcessName);
            }
        }
    }
}

위의 코드로 현재 내 컴퓨터에서 실행 중인 모든 프로세스 정보를 볼 수 있습니다.

실행 결과 화면

 

그리고 현재 포커싱 되어있는 프로세스에 접근하기 위해선 윈도우 API가 필요합니다.

 

C:\Windows\System32\user32.dll (dynamic link library) 요기에 있는 함수

 

IntPtr GetForegroundWindow() : 현재 맨 앞의 윈도우 핸들(hWnd) 가져오기 (최상위 프로세스)

int GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId) : 핸들을 이용하여 프로세스 아이디 가져오기

 

2개가 필요합니다.

실행 시켜준 후 다른 프로세스로(실행 중인 프로그램) 포커스를 옮겨보았습니다.

using System;
using System.Diagnostics; //Process
using System.Runtime.InteropServices; //DLL Import
using System.Threading; //Thread

namespace ConsoleApp
{
    class Program
    {
        [DllImport("user32.dll")]
        public static extern int GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        private static void CheckCurrentProcess ()
        {
            for (int i = 0; i < 5; ++i)
            {
                IntPtr hWnd = GetForegroundWindow(); //맨 앞의 프로그램 핸들 반환
                uint processId;

                GetWindowThreadProcessId(hWnd, out processId); //해당 프로세스 id 반환
                Process currentProcess = Process.GetProcessById((int)processId);

                Console.WriteLine(currentProcess.Id + " " + currentProcess.ProcessName);

                Thread.Sleep(3000); //3초
            }
        }

        static void Main(string[] args)
        {
            Thread thread = new Thread(CheckCurrentProcess);
            thread.Start();
            thread.Join();
        }
    }
}

결과 화면

스타크래프트 키는데 6초 걸렸네요 :)

저는 개인용 캡쳐 프로그램을 만들기 위해 해당 기능들을 사용할 생각입니다.

찾아보면 윈도우 API 기능들이 유용한 것들이 많이 있네요!

+ Recent posts