FormBorderStyle = None 을 주고 사용자 지정 타이틀을 만들다보면 최대화 시켰을 때 맨 아래 작업 표시줄이 가려집니다.

 

(왼쪽이 평상시 상태, 오른쪽은 가려진 형태)

 

이에따라 현재 스크린에 따른 최대화 사이즈 지정이 필요합니다.

 

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

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

        private void button1_Click(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                Size size = new Size();

                //현재 폼이 가장 가까운 모니터에 대한 작업 영역 크기를 가져옵니다.
                size.Width = Screen.FromControl(this).WorkingArea.Width;
                size.Height = Screen.FromControl(this).WorkingArea.Height;

                this.MaximumSize = size;
                this.WindowState = FormWindowState.Maximized;
            }
            else
            {
                this.WindowState = FormWindowState.Normal;
            }
        }
    }
}

 

 

 

 

 

======================================================================================

이전 코드...

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

namespace WindowsFormsApp6
{
    public partial class Form1 : Form
    {
        private Point prePosition;

        public Form1()
        {
            InitializeComponent();
        }

        private void button_close_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void button_sizeChange_Click(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                SetMaximize();
            }
            else
            {
                SetNormal();
            }
        }

        private void SetNormal ()
        {
            this.Location = prePosition; //크기 변경 시 원 위치로 복귀
            this.WindowState = FormWindowState.Normal;
        }

        private void SetMaximize ()
        {
            Screen[] screens = Screen.AllScreens;
            int length = screens.Length;
            int area;

            for (area = 0; area < length; ++area) //모든 모니터 영역 확인
            {
                if (screens[area].WorkingArea.Contains(this.Location))
                {
                    break;
                }
            }

            //화면 밖인 경우 첫번째 모니터로 해줍니다.
            if (length <= area) 
            {
                area = 0;
            }

            prePosition = this.Location; //노멀화 시킬 때 이동시킬 좌표값.

            //화면 기준점으로 이동 후 최대화 시켜주어야 정상적인 결과를 얻습니다.
            this.Location = new Point(screens[area].Bounds.X, screens[area].Bounds.Y);

            this.MaximizedBounds = new Rectangle(MaximizedBounds.X,
                                                 MaximizedBounds.Y,
                                                 screens[area].WorkingArea.Width,  //width 와 height를 바꿔줍니다.
                                                 screens[area].WorkingArea.Height);

            this.WindowState = FormWindowState.Maximized; //최대화
        }
    }
}

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편은 여기서 끝!

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 를 수정해서 사용해보세요~

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

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 은 해당 키 값이 들어있습니다.

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

+ Recent posts