파일 저장, 불러오기 등에 사용되는 옵션이 있습니다.

 

양식은

(2개 이상 받기)

설명문|파일확장자;파일확장자2

 

(1개씩 받기)

파일명1 | 파일1확장자 | 파일2 | 파일2확장자

형태로 입력하면 됩니다.

 

 

using System.Windows.Forms;

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

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

            openFileDialog.Filter = "Tif(Tiff),JPG|*.tif;*.jpg|ALL|*.*";

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                //선택 된 경우!
            }
        }
    }
}

프로젝트에 리소스를 등록하여 사용하는 방법을 알아보겠습니다.

Properties.Resources.  을 이용하여 접근 가능합니다.

 

먼저 완성된 코드와 그림입니다.

using System.Windows.Forms;

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

            label1.Text = Properties.Resources.TestString;
            pictureBox1.Image = Properties.Resources.myImage;
        }
    }
}

 

 

1. 프로젝트 오른쪽 클릭하고 속성을 눌러줍니다.

 

 

2. 리소스 탭을 클릭하면 기본 문자열에 대해 입력이 가능해집니다.

 

3. 문자열 외에도 여러 리소스를 등록할 수 있습니다. (이미지 등록 등은 드래그 앤 드롭해서 사용해주면 됩니다!)

FormBorderStyle = None 인 폼은 기본적으로 크기 조절 및 이동이 불가능합니다.

해결해야 할 것은 3가지 입니다.

 

1. 마우스가 테두리에 위치하여 크기를 변경 가능

2. 마우스 이동에 따른 이벤트 추가 (혹은 WndProc에서 제어도 가능!)

3. 최대화 시 작업표시줄을 가리지 않게 수정 (타이틀이 없기에 크기가 더 커집니다.)

 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace MoveableForm
{
    public partial class Form1 : Form
    {
        private int titleHeight = 32;
        private int padding = 5;

        private int offsetX;
        private int offsetY;
        private bool isMoving;

        //테스트용 출력 값
        private Dictionary<int, string> positionDict = new Dictionary<int, string>()
        {
            {0, "pass" },
            //{1, "clientInner" },
            //{2, "moveable and changeSize" },
            {10, "left" },
            {11, "right" },
            {12, "top" },
            {15, "bottom" },
            {13, "topLeft" },
            {14, "topRight" },
            {16, "bottomLeft" },
            {17, "bottomRight" }
        };

        public Form1()
        {
            InitializeComponent();
        }

        private IntPtr GetMousePosition (Point point)
        {
            int x = point.X;
            int y = point.Y;

            int width = this.ClientRectangle.Width;
            int height = this.ClientRectangle.Height;

            bool innerWidth = padding <= x && x < width - padding;
            bool innerHeight = padding <= y && y < height - padding;

            bool isTop = y <= padding / 2; //타이틀쪽 살짝 보정
            bool isBottom = height - padding <= y;
            bool isLeft = x <= padding;
            bool isRight = width - padding <= x;

            if (isLeft && innerHeight)  return (IntPtr)10; //left
            if (isRight && innerHeight) return (IntPtr)11; //right
            if (isTop && innerWidth)    return (IntPtr)12; //top
            if (isBottom && innerWidth) return (IntPtr)15; //bottom

            if (isTop && isLeft)        return (IntPtr)13; //topLeft
            if (isTop && isRight)       return (IntPtr)14; //topRight
            if (isBottom && isLeft)     return (IntPtr)16; //bottomLeft
            if (isBottom && isRight)    return (IntPtr)17; //bottomRight

            //1 : 클라이언트 내부인 경우.
            //2 : 윈도우창 움직이거나 더블클릭이 가능.
            //return y <= titleHeight ? (IntPtr)2 : (IntPtr)1;

            return (IntPtr)0;
        }

        protected override void WndProc(ref Message m)
        {
            //https://learn.microsoft.com/ko-kr/windows/win32/inputdev/wm-nchittest
            //WM_NCHITTEST = 0x84
            if (m.Msg == 0x84)
            {
                Point point = new Point(m.LParam.ToInt32());
                point = PointToClient(point);
                
                m.Result = GetMousePosition(point);
                label3.Text = positionDict[(int)m.Result]; //테스트용 값입니다.
                return;
            }

            base.WndProc(ref m);
        }

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

        private void panel_title_MouseDown(object sender, MouseEventArgs e)
        {
            if (this.WindowState == FormWindowState.Maximized)
            {
                return;
            }

            offsetX = Cursor.Position.X - Location.X;
            offsetY = Cursor.Position.Y - Location.Y;

            isMoving = true;
        }

        private void panel_title_MouseMove(object sender, MouseEventArgs e)
        {
            if (isMoving)
            {
                Location = new Point(Cursor.Position.X - offsetX, Cursor.Position.Y - offsetY);
            }
        }

        private void panel_title_MouseUp(object sender, MouseEventArgs e)
        {
            isMoving = false;
        }

        private void panel_title_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                //화면 최대화 시 하단의 작업표시줄을 덮지 않도록 해줍니다.
                this.MaximumSize = new Size(Screen.FromControl(this).WorkingArea.Width,
                                            Screen.FromControl(this).WorkingArea.Height);

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

 

SendMessage 와 윈도우 콜백 함수 (WndProc) 를 이용하여 다음과 같이 프로그램 간 통신을 할 수 있습니다.

 

프로그램 간 메시지를 송신/수신하여 처리 된 결과

 

 

[보내는 프로그램]

using System;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsForms_SendProcess
{
    public partial class Form1 : Form
    {
        //명령어 모음
        //https://wiki.winehq.org/List_Of_Windows_Messages
        private const int WM_COPYDATA = 0x4A;

        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, uint wParam, ref COPYDATASTRUCT lParam);

        public struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;

            public string message;
        }

        public Form1()
        {
            InitializeComponent();

            textBox_processName.Text = "WindowsForms ReceiveProcess";
        }

        private void button_sendMessage_Click(object sender, EventArgs e)
        {
            Process[] process = Process.GetProcessesByName(textBox_processName.Text);
            string message = textBox_message.Text;

            for (int i = 0; i < process.Length; ++i)
            {
                COPYDATASTRUCT cds = new COPYDATASTRUCT();

                cds.dwData = IntPtr.Zero;

                //영어 문자 1byte, 한글 2byte
                byte[] buffer = Encoding.Default.GetBytes(message);

                cds.message = message;

                cds.cbData = buffer.Length + 1;  //보내는 string 길이 + 1 (문자의 끝 의미)

                SendMessage(process[i].MainWindowHandle, WM_COPYDATA, 0, ref cds);
            }
        }
    }
}

 

[받는 프로그램]

using System;
using System.Windows.Forms;

namespace WindowsForms_ReceiveProcess
{
    public partial class Form1 : Form
    {
        //명령어 모음
        //https://wiki.winehq.org/List_Of_Windows_Messages
        private const int WM_COPYDATA = 0x4A;

        public struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;

            public string message;
        }

        public Form1()
        {
            InitializeComponent();
        }

        protected override void WndProc(ref Message m)
        {
            try
            {
                switch (m.Msg)
                {
                    case WM_COPYDATA:

                        COPYDATASTRUCT cds = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));

                        listBox1.Items.Add(cds.message);
                        listBox1.SelectedIndex = listBox1.Items.Count - 1;
                        break;

                    default:

                        base.WndProc(ref m);
                        break;

                }

            }
            catch (Exception ex)
            {
                MessageBox.Show("WinForm Error : " + ex.Message);
            }
        }
    }
}

URI (별칭식 경로 : Uniform Resource Identifier 통합 자원 식별자)
URI는 특정 리소스를 식별하는 경로를 의미한다. 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스다.

URL (절대 경로 : Uniform Resource Locator 또는 통칭 web address)
URL은 흔히 웹 주소라고도 하며, 컴퓨터 네트워크 상에서 리소스가 어디 있는지 알려주기 위한 규약이다. URI의 서브셋이다.

 

POST 방식은 기본 (URL)에 요청할 때 BODY 부분에 데이터를 넘겨주는 방식입니다.

Spring 공부를 하면 해당 API 들을 직접 호출 및 테스트 해볼 수 있게 만들면 좋겠네요.

POST 방식은 코드만 올리겠습니다.

1개 보내는 방식, multipart 로 2개 이상의 다른 데이터 보내는 방식입니다.

using System;
using System.IO;  //Stream
using System.Net; //HttpWebRequest, WebRequest 등 사용
using System.Windows.Forms;

using System.Net.Security; //SSL (Secure Sockets Layer) 관련
using System.Security.Cryptography.X509Certificates; //암호화 된 인증서 관련
using System.Text;

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

            PostRequest();
        }

        private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            //인증서에 대한 결과를 보려면 여기서 브레이크 걸고 certificate, chain 등을 살펴보면 됩니다.
            //권장하지 않는 방법이지만 인증 기관에 대해 true로 설정해둡니다.
            return true;
        }

        private void PostRequest()
        {
            //****************************** 이 부분은 첫 사용하기 전에만 넣어주면 됩니다. ***********************************
            //웹 통신 시 System.Net.WebException : 기본 연결이 닫혔습니다. 관련 에러 방지용 프로토콜 정의입니다.
            //Secure Sockets Layer, Transport Layer Secure
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls |
                                                   SecurityProtocolType.Tls11 |
                                                   SecurityProtocolType.Tls12 |
                                                   SecurityProtocolType.Ssl3;

            //https (hyper text transfer protocol Secure : 전송 규약 보안) 보안이 올바른지 확인하는 구문입니다. 
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
            //****************************************************************************************************************

            //[ContentType] : Body에서 전송되는 콘텐츠의 유형을 서버에 알려줍니다.
            //                MIME (Multipurpose Internet Mail Extensions) 표준을 따릅니다.
            //
            //ex)
            //1. multipart/form-data            2개 이상으로 한 번에 보낼 때 boundary와 함께 사용
            //2. application/json               제이슨 형태
            //3. application/x-www-urlencoded   기본 타입
            //4. image/tiff                     이미지, 확장자
            //   등 등

            //한 개의 데이터를 POST 방식으로 보내기
            PostRequestOne();

            //2개 이상의 데이터를 POST 방식으로 보내기
            //multipart/form-data 방식으로 사용되며 줄 구분을 위한 boundary가 필요   
            PostRequestAll();
        }

        private void PostRequestOne ()
        {
            string uri = textBox_uri.Text;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);   //요청 준비
            HttpWebResponse response = null;

            try
            {
                request.Method = "POST"; //기본 값은 GET 입니다.
                request.ContentType = "application/x-www-form-urlencoded"; //url encoded 방식 변수=값&변수1=값..

                string userName = "name";
                string password = "1234"; //서버와 협의하여 해쉬 등으로 암호화
                string data = string.Format("userName={0}&password={1}", userName, password);

                //request body에 데이터 채우기
                byte[] buffer = Encoding.UTF8.GetBytes(data);
                Stream requestStream = request.GetRequestStream(); //request에 보낼 데이터 입력용
                requestStream.Write(buffer, 0, buffer.Length);
                requestStream.Close();

                //응답 결과 받기
                response = (HttpWebResponse)request.GetResponse();
                Stream responseStream = response.GetResponseStream();
                StreamReader responseStreamReader = new StreamReader(responseStream);

                //응답 값 받아서 리턴하거나 사용하면 됩니다.
                string result = responseStreamReader.ReadToEnd();

                responseStreamReader.Close();
                responseStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                //로그 남기면 좋을 듯
                MessageBox.Show("Request Error" + Environment.NewLine + ex.ToString());
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        private void PostRequestAll ()
        {
            string uri = textBox_uri.Text;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);   //요청 준비
            HttpWebResponse response = null;

            try
            {
                request.Method = "POST"; //기본값은 GET
                                         //그 밖에도 timeout, credential 등 다양한 옵션을 줄 수 있습니다.

                //https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
                //2개 이상 multipart로 보내는 방법
                //보낼 값) field1 = value, 파일 = byte file

                //경계를 통해 데이터 타입 명시 및 다음과 같은 형태로 만들어서 보내면 됩니다.
                /*
                    Content-Type: multipart/form-data; boundary=isBoundary
                
                    --isBoundary
                    Content-Disposition: form-data; name="field1"

                    value
                    --isBoundary
                    Content-Disposition: form-data; name="파일"; filename="myImage.tiff" (filename은 부가적인 부분으로 없어도 된다고 봤네요.)
                    Content-Type: image/tiff (타입에 대한 명시 필요)

                    byte file
                    --isBoundary-- (경계의 끝은 -- 붙여주기)
                 */

                string endLine = "\r\n";            //웹에서 개행을 나타내는 문자열입니다. 
                string boundary = "isBoundary";     //구분자입니다. --isBoundary ~ --isBoundary-- 로 끝나게 됩니다.

                request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
                byte[] boundaryBytes = Encoding.ASCII.GetBytes(endLine + "--" + boundary + endLine);

                Stream requestStream = request.GetRequestStream();
                requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
                //
                //--isBoundary
                //

                string field1 = "field1";
                string value = "value";
                string data = string.Format("Content-Disposition: form-data; name=\"{0}\"{1}{2}{3}", field1, endLine, endLine, value);
                byte[] dataBytes = Encoding.UTF8.GetBytes(data);

                requestStream.Write(dataBytes, 0, dataBytes.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value

                requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value
                //--isBoundary
                //

                string name = "파일";
                string filename = "myImage.tiff";

                string data2 = string.Format("Content-Disposition: form-data; name=\"{0}\" filename=\"{1}\"{2}Content-Type: image/tiff{3}{4}",
                                                name, filename, endLine, endLine, endLine);
                byte[] data2Bytes = Encoding.UTF8.GetBytes(data2);

                requestStream.Write(data2Bytes, 0, data2Bytes.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value
                //--isBoundary
                //Content-Disposition: form-data; name="파일" filename="myImage.tiff"
                //Content-Type: image/tiff
                //
                //

                //예시 경로 파일 바이트로 읽어서 보내기
                string imagePath = @"c:\temp\myImage.tiff";
                FileStream fileStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
                byte[] imageBuffer = new byte[4096];
                int bytesRead = 0;
                while (true)
                {
                    bytesRead = fileStream.Read(imageBuffer, 0, imageBuffer.Length);
                    if (bytesRead <= 0) //이미지 바이트 다 읽은 경우
                    {
                        break;
                    }
                    requestStream.Write(imageBuffer, 0, bytesRead);
                }
                fileStream.Close();

                byte[] endBoundaryByte = Encoding.ASCII.GetBytes(endLine + "--" + boundary + "--" + endLine);
                requestStream.Write(endBoundaryByte, 0, endBoundaryByte.Length);
                //[진행 현황]
                //
                //--isBoundary
                //Content-Disposition; form-data; name="field1"
                //
                //value
                //--isBoundary
                //Content-Disposition: form-data; name="파일" filename="myImage.tiff"
                //Content-Type: image/tiff
                //
                //byte file
                //--isBoundary--

                response = (HttpWebResponse)request.GetResponse(); //요청 후 결과 반환
                Stream responseStream = response.GetResponseStream();   //요청에 대한 stream 가져오기
                StreamReader responseStreamReader = new StreamReader(responseStream); //해당 stream 읽기 위한 streamReader

                //응답값 받아서 리턴하거나 사용하면 됩니다.
                string result = responseStreamReader.ReadToEnd();

                responseStreamReader.Close();
                responseStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                //로그 남기면 좋을 듯
                MessageBox.Show("Request Error" + Environment.NewLine + ex.ToString());
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }
    }
}

 

URI (별칭식 경로 : Uniform Resource Identifier 통합 자원 식별자)
URI는 특정 리소스를 식별하는 경로를 의미한다. 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스다.

URL (절대 경로 : Uniform Resource Locator 또는 통칭 web address)
URL은 흔히 웹 주소라고도 하며, 컴퓨터 네트워크 상에서 리소스가 어디 있는지 알려주기 위한 규약이다. URI의 서브셋이다.

 

GET 방식은 헤더 부분인 주소(URI)에 변수와 값 을 통해 결과를 얻어오는 방법 입니다.

주소 뒷 쪽에 ? 변수1 = 값1 & 변수2 = 값2

 

식으로 변수와 값을 넘겨줄 수 있습니다. 아래는 구글에 간단하게 hi 를 GET으로 요청한 결과 입니다.

https://google.com/search 에 대해해 뒷 편에 ? q = hi 를 붙인 것이죠.

                                                                    (q는 검색어 변수, hi는 값이 되겠죠)

구글에서 hi 검색한 결과

C#에서 호출 한 소스는 아래와 같습니다.

통신을 위해서 json 을 반환시키기도 하며 200(응답 성공), 400(클라이언트 에러), 500(서버쪽 에러) 를 통해

결과를 알 수도 있습니다.

 

.NET 에 따라 SecurityProtocol 값이 달라질 수 있습니다. (현재 .NET 4.7.2 로 작성 되었습니다.)

(ServicePointManager 첫 연결 시만 등록해주면 됩니다.)

using System;
using System.IO;  //Stream
using System.Net; //HttpWebRequest, WebRequest 등 사용
using System.Windows.Forms;

using System.Net.Security; //SSL (Secure Sockets Layer) 관련
using System.Security.Cryptography.X509Certificates; //암호화 된 인증서 관련

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

        private void button_request_Click(object sender, EventArgs e)
        {
            //****************************** 이 부분은 첫 사용하기 전에만 넣어주면 됩니다. ***********************************
            //웹 통신 시 System.Net.WebException : 기본 연결이 닫혔습니다. 관련 에러 방지용 프로토콜 정의입니다.
            //Secure Sockets Layer, Transport Layer Secure
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls |
                                                   SecurityProtocolType.Tls11 |
                                                   SecurityProtocolType.Tls12 |
                                                   SecurityProtocolType.Ssl3;

            //https (hyper text transfer protocol Secure : 전송 규약 보안) 보안이 올바른지 확인하는 구문입니다. 
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
            //****************************************************************************************************************

            string uri = textBox_uri.Text;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);   //요청 준비
            HttpWebResponse response = null;

            try
            {
                response = (HttpWebResponse)request.GetResponse(); //요청 후 결과 반환

                Stream stream = response.GetResponseStream();   //요청에 대한 stream 가져오기
                StreamReader reader = new StreamReader(stream); //해당 stream 읽기 위한 streamReader

                string result = reader.ReadToEnd(); //결과 읽어오기

                reader.Close();
                stream.Close();

                textBox_result.Text = result;
            }
            catch (Exception ex)
            {
                textBox_result.Text = "Request Error" + Environment.NewLine + ex.ToString();
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                }
            }
        }

        private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            //인증서에 대한 결과를 보려면 여기서 브레이크 걸고 certificate, chain 등을 살펴보면 됩니다.
            //권장하지 않는 방법이지만 인증 기관에 대해 true로 설정해둡니다.
            return true;
        }
    }
}

 

컴파일러서비스를 통해 어디서 누가 호출을 했는지 확인 할 수 있습니다.

로그를 남길 때 별도의 호출 함수 양식을 작성하지 않고도 사용할 수 있는 방법입니다!

 

DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"); 를 통해

년-월-일 시:분:초

를 정의할 수도 있습니다. 아래는 호출 예제입니다.

 

using System;
using System.Windows.Forms;
using System.Runtime.CompilerServices; //[Caller 사용]

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        Test test = new Test();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            test.ShowCaller();
            Log("로그 내용");
        }
        
        private void Log (string message, [CallerMemberName] string name = "")
        {
            string log = string.Format("{0} [{1}] : {2}{3}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"), name, message, Environment.NewLine);
            MessageBox.Show(log);
        }
    }

    class Test
    {
        public void ShowCaller([CallerMemberName] string name = "", [CallerFilePath] string path = "", [CallerLineNumber] int line = 0)
        {
            MessageBox.Show("name : " + name + "\n" +
                            "path : " + path + "\n" +
                            "line : " + line);
        }
    }
}

대충 만든 폼! ㅎㅎ

 

test.ShowCaller() 내부의 메시지박스

 

Log("로그 내용") 내부의 메시지 박스

 

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

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

    public partial class Form1 : Form
    {
        private HookManager hookManager = new HookManager();

        public Form1()
        {
            InitializeComponent();

            this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.Form1_FormClosed);
            hookManager.SetHook();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            hookManager.UnHook(); //프로그램이 종료될 때 반드시 후킹을 꺼줘야합니다.
        }
    }

    public class HookManager
    {
        #region WindowsAPI

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

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

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

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

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

        #endregion

        //구조체 크기를 맞춰줘야 올바른 값을 받을 수 있습니다.
        public struct HookStruct
        {
            public int vkCode;
            int scanCode;
            public int flags;
            int time;
            int dwExtraInfo;
        }

        //WM_Keydown
        //https://wiki.winehq.org/List_Of_Windows_Messages
        const int WH_KEYBOARD_LL = 13; //저수준 특수키 관련 Alt + .. Ctrl + ..
        const int WM_KEYDOWN = 256;
        const int WM_KEYUP = 257;
        const int WM_SYSKEYDOWN = 260;
        const int WM_SYSKEYUP = 261;

        private static int hookId = 0;

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

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

        public static int HookProc(int code, int wParam, ref HookStruct lParam)
        {
            if (code >= 0)
            {
                switch (wParam)
                {
                    case WM_KEYDOWN:
                    case WM_KEYUP:
                    case WM_SYSKEYDOWN:
                    case WM_SYSKEYUP:

                        Keys key = (Keys)lParam.vkCode;

                        if ((key == Keys.Tab    && lParam.flags == 32) ||   // Alt+Tab
                            (key == Keys.Escape && lParam.flags == 32) ||   // Alt+Esc
                            (key == Keys.F4     && lParam.flags == 32) ||   // Alt+F4
                            (key == Keys.Escape && lParam.flags == 0)  ||   // Ctrl+Esc
                            (key == Keys.LWin   && lParam.flags == 1)  ||   // Left Windows Key 
                            (key == Keys.RWin   && lParam.flags == 1))      // Right Windows Key 
                        {
                            return 1; //Do not handle key events
                        }
                        break;
                }
            }

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

 

 Custom URI 를 만들어 웹페이지에서 내 PC에 있는 exe 파일을 실행시키는 방법입니다.

아래와 같이 작동 됩니다.

 

웹페이지에서 아이디 비밀번호와 같은 입력 값을 넘겨주며 .exe 파일을 실행시킵니다.
실행 버튼을 누른 결과가 출력됩니다.

 (주의 사항이 하나 있는데 위와 같이 변환 된 결과를 보면 웹 url에서 특수 문자가 다르게 취급됩니다.

UTF-8 과 System.Net.WebUtilty.UrlDecode 기능을 잘 섞어서 필요한 내용으로 만들어주면 되겠습니다.)

 

 

 먼저 레지스트리에 해당 파일을 실행할 수 있도록 등록해주어야 합니다.

 exe파일 실행 시 Main(string[] args) 부분에 대한 처리가 필요합니다.

 차근차근 해봅시다.

 

  1. 윈도우키 + R을 눌러 "실행" 창을 띄워줍니다. (혹은 시작->실행 검색)

  2. regedit 입력을 해줍니다.

실행 창에서 regedit 입력 후 확인

  3. HKEY_CLASSES_ROOT 오른쪽 클릭 후 새로 만들기 -> 키 를 선택해줍니다.

   이름은 "Test"로 설정해주겠습니다.

  4. 아래와 같이 키를 생성해줍니다.

    Test에서 shell, shell 에서 open, open 에서 command까지 생성해줍니다.

  5. Test 에서 새로 만들기 -> 문자열 값 을 통해 새 값을 만들어줍니다.

   해당 값의 이름을 URL Protocol 로 바꿔줍니다.

  6. 이번엔 command 쪽의 기본 값을 수정해줍니다. (기본 값을 더블 클릭 해줍니다.)

  아래와 같이 문자열 편집이 뜹니다. exe파일의 경로가 되겠습니다.

  "C:\Test\test.exe" "%1"

 C드라이브 테스트 폴더의 test.exe 실행을 하고, %1은 실행 시 매개변수 값을 하나 줄 수 있다는 의미입니다.

  7. 비주얼 스튜디오에서 다음과 같은 코드를 작성하고 exe 파일을 생성합니다.

using System;

namespace ConsoleApp5
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("받아 온 값");

            if (args.Length > 0)
            {
                Console.WriteLine(args[0]); //받아온 값을 보여줍니다.
            }

            Console.ReadLine(); //값을 보기 위해 대기
        }
    }
}

 

  8. 해당 파일을 C:\Test\Test.exe 로 옮겨줍시다.

  9. 웹에서 실행합니다. 간단하게 url 창에서 실행해보겠습니다.

  test://hihi ook

  원하는 값을 입력해줍니다. 그리고 엔터를 치면

  10. 짜잔 실행이 됩니다.

    저 띄어쓰기 부분은 %20 으로 나오고 맨 마지막에도 / 가 자동으로 붙게 됩니다.

   우선 간단하게 값을 받아오고 실행에 성공을 하였습니다. 

받아온 값을 원하는 값으로 변경해서 사용해주면 됩니다!

 

 설명이 너무 길어져서 나머지는 예제 파일로 남겨두겠습니다.

비주일 스튜디오 2019로 작업한 파일과 레지스트리 생성, test.exe 파일이 들어있습니다.

웹에서 어떻게 호출하는지와 C#에서 값을 어떻게 받아오는지도 있으니 필요하면 확인해보시길!

커스텀 URI 만들기.zip
0.83MB

 

코드 정리도 따로 올립니다.

html 코드

 

[파일명]

custom uri.html

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <script>
   
   function play ()
   {
    let id = document.getElementById("ID").value;
    let pw = document.getElementById("PW").value;

    let url = "Test://" + id + "&" + pw + "&"; // 레지스트리에 등록 된 Test:// 호출 부분입니다.
    //마지막에 &를 추가해줬는데 끝 부분에 / 와 같은 문자가 붙을 수 있어 경계 탐지용으로..
    let exec = document.createElement("a");
    exec.setAttribute("href", url);
    exec.click();
   }   

  </script>
 </head>
 <body>
  <div> 아이디 <input id="ID" type="text" value="testID!@"></div>
  <div> 비밀번호 <input id="PW" type="text" value="!@#$%^&*()"></div>
  <div> <button id="call" onclick="play()"> 실행 </button> </div>
 </body>
</html>

 

WindowsForm

[Program.cs]

using System;
using System.Windows.Forms;

namespace Custom_uri_Example
{
    static class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
         
            if (args.Length > 0) //외부 호출하여 인자값이 있는 경우
            {
                Application.Run(new Form1(args));
            }
            else //바로 .exe 실행
            {
                Application.Run(new Form1());
            }
        }
    }
}

 

[Form1.cs]

using System.Windows.Forms;

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

        public Form1(string[] args) : this()
        {
            textBox_all.Text = args[0];

            string data = args[0].Replace("test://", ""); //앞에 불필요한 문자열은 지워줍니다.

            string[] separate = data.Split('&'); //사전에 약속 된 구분자로 나눠줍니다.

            string utf_id = separate[0];
            string utf_pw = separate[1];

            textBox_utf_id.Text = utf_id;
            textBox_utf_pw.Text = utf_pw;

            //웹에서 받아온 문자 중 특수문자가 다른 경우가 있을 때 WebUtility를 사용해줍니다.
            //System.Net 사용이 안되면 참조 추가에서 System.Net을 추가해주면 됩니다.
            string web_id = System.Net.WebUtility.UrlDecode(separate[0]);
            string web_pw = System.Net.WebUtility.UrlDecode(separate[1]);

            textBox_web_id.Text = web_id;
            textBox_web_pw.Text = web_pw;
        }
    }
}

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; //최대화
        }
    }
}

+ Recent posts