C#, Java 간 암복호화를 사용할 일이 있어서 정리해봅니다.

처리 방법은 2가지가 있습니다.

1. TransformFinalBlock : 바이트 전체를 한번에 처리

2. CryptoStream : 스트림 단위로 잘라서 처리 1MB 이상이면 권장된다고 합니다!

using System;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        private static readonly string KEY = "0123456789abcdef0123456789abcdef"; // 32-byte
        private static readonly string IV = "abcdef9876543210"; // 16-byte

        private static Aes aes;

        static void Main(string[] args)
        {
            aes = Aes.Create();
            aes.Key = Encoding.UTF8.GetBytes(KEY); //CBC므로 32바이트
            aes.IV = Encoding.UTF8.GetBytes(IV); //16바이트 (첫 XOR 값)
            aes.Mode = CipherMode.CBC; //16바이트 단위로 XOR 처리
            aes.Padding = PaddingMode.PKCS7; //패딩 끝 부분 바이트 처리 (부족한 길이 반복)

            string originalText = "Hello, AES-256!";
            Console.WriteLine("Original: " + originalText);

            string encryptedText = Encrypt(originalText);
            Console.WriteLine("Encrypted: " + encryptedText);

            string decryptedText = Decrypt(encryptedText);
            Console.WriteLine("Decrypted: " + decryptedText);
        }

        static string Encrypt(string plainText)
        {
            ICryptoTransform encryptor = aes.CreateEncryptor();
            byte[] inputBytes = Encoding.UTF8.GetBytes(plainText);

            //****************************************************************
            //통 Block 방식
            byte[] encryptedBytes = encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length);
            return Convert.ToBase64String(encryptedBytes); //통신용이라면 base64가 안전 (아니면 그냥 UTF8로 해도 괜찮습니다!)
            
            //****************************************************************
            //Stream 방식 (1MB 이상이면 스트림 방식 권장)
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    cs.Write(inputBytes, 0, inputBytes.Length);
                    cs.FlushFinalBlock();
                }
                return Convert.ToBase64String(ms.ToArray());
            }
        }

        static string Decrypt(string encryptedText)
        {
            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
            byte[] encryptedBytes = Convert.FromBase64String(encryptedText); //Encrypt 후반에 base64를 썻는지에 따라 처리!
            
            //****************************************************************
            //통 Block 방식
            byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
            return Encoding.UTF8.GetString(decryptedBytes);
            //****************************************************************

            //Stream 방식 (1MB 이상이면 스트림 방식 권장)
            using (MemoryStream ms = new MemoryStream(encryptedBytes))
            {
                using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (MemoryStream result = new MemoryStream())
                    {
                        cs.CopyTo(result);
                        return Encoding.UTF8.GetString(result.ToArray());
                    }
                }
            }
        }
    }
}

 

 

 

C#가 호환되는 자바쪽 소스 (GPT가 알려준 소스..!)

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class Test {
    // 주어진 AES 256-bit (32-byte) Key와 16-byte IV
    private static final String KEY = "0123456789abcdef0123456789abcdef"; // 32-byte
    private static final String IV = "abcdef9876543210"; // 16-byte

    public static String encrypt(String plainText) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());

        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
        byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));

        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String decrypt(String encryptedText) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
        byte[] decrypted = cipher.doFinal(decodedBytes);

        return new String(decrypted, "UTF-8");
    }

    public static void main(String[] args) {
        try {
            String originalText = "Hello, AES-256!";
            System.out.println("Original: " + originalText);

            String encryptedText = encrypt(originalText);
            System.out.println("Encrypted: " + encryptedText);

            String decryptedText = decrypt(encryptedText);
            System.out.println("Decrypted: " + decryptedText);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

IPC (Inter Process Communication) 방식 중 네임드파이프 통신이 있습니다.

서로 다른 프로그램에서 정의된 파이프 이름만 알면 통신이 가능한 방식입니다.

아래는 결과와 소스코드!

 

왼쪽은 A_Pipe, 오른쪽은 B_Pipe

 

using System;
using System.IO;
using System.IO.Pipes;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class NamedPipe : Form
    {
        private const string myName = "A_Pipe"; //이름만 바꿔서 사용 가능
        private const string targetName = "B_Pipe"; //이름만 바꿔서 사용 가능

        private bool isOpen = true;

        public NamedPipe()
        {
            InitializeComponent();

            this.FormClosing += Form1_FormClosing;
            button1.Click += button1_Click;

            Task.Run(() =>
            {
                Receive();
            });
        }

        private void Receive()
        {
            while (isOpen)
            {
                try
                {
                    NamedPipeServerStream namedPipeServerStream = new NamedPipeServerStream(myName, PipeDirection.InOut, 10);
                    namedPipeServerStream.WaitForConnection(); //데이터 받을때까지 기다립니다.

                    StreamReader streamReader = new StreamReader(namedPipeServerStream);
                    string line = streamReader.ReadLine();

                    AddListItem(line);

                    streamReader.Close();
                    namedPipeServerStream.Close();
                }
                catch (Exception ex)
                {
                    AddListItem("Receive Error : " + ex.Message);
                }
            }
        }

        private void AddListItem(string message)
        {
            Invoke((MethodInvoker)delegate
            {
                listBox1.Items.Add(message);
            });
        }

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream(".", targetName, PipeDirection.InOut);
                namedPipeClientStream.Connect(10000); //10초

                StreamWriter streamWriter = new StreamWriter(namedPipeClientStream);

                streamWriter.WriteLine(textBox1.Text);
                streamWriter.Flush();

                streamWriter.Close();
                namedPipeClientStream.Close();
            }
            catch (Exception ex)
            {
                AddListItem("Send Error : " + ex.Message);
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            isOpen = false;
        }
    }
}

현재 영역에 대해서만 그려주기! (4개만 보이는 상태)

 

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form, IMessageFilter
    {
        private const int buttonOffset = 10;

        private List<Button> buttonList = new List<Button>();
        private List<Control> focusedList = new List<Control>();
        private int scrollPosition;

        public Form1()
        {
            InitializeComponent();

            panel1.AutoScroll = true; //스크롤 활성화

            //스크롤 이벤트 등록
            this.panel1.Resize += new System.EventHandler(this.panel1_Resize);
            this.panel1.Scroll += new System.Windows.Forms.ScrollEventHandler(this.panel1_Scroll);

            //버튼 이벤트 등록
            this.button_add.Click += new EventHandler(this.button_add_Click);

            Application.AddMessageFilter(this); //IMessageFilter 사용하기 위해 등록!
        }

        public bool PreFilterMessage(ref Message m) //IMessageFilter 콜백
        {
            if (m.Msg == 0x20a) // WM_MOUSEWHEEL (마우스 휠)
            {
                if (panel1.Bounds.Contains(PointToClient(Cursor.Position)))
                {
                    short x = (short)(((int)m.WParam >> 16) & 0xffff);

                    if (x < 0)
                    {
                        ScrollMove(20);
                    }
                    else
                    {
                        ScrollMove(-20);
                    }

                    return true;
                }
            }

            return false;
        }

        private void ScrollMove(int move)
        {
            int preValue = scrollPosition;

            scrollPosition += move;
            scrollPosition = Math.Max(scrollPosition, 0);
            scrollPosition = Math.Min(panel1.DisplayRectangle.Width - panel1.Width, scrollPosition);

            panel1.HorizontalScroll.Value = scrollPosition;

            //기존 값과 같다면 동작하지 않게 합니다.
            if (preValue == scrollPosition)
            {
                return;
            }

            UpdateThumbnail(scrollPosition);
        }

        private void button_add_Click(object sender, EventArgs e)
        {
            Button button = new Button();

            button.Text = buttonList.Count.ToString("D4");
            button.Hide();

            panel1.Controls.Add(button);
            panel1.AutoScrollMinSize = new Size(panel1.AutoScrollMinSize.Width + button.Width + buttonOffset, 0);

            buttonList.Add(button);
            UpdateThumbnail(scrollPosition);
        }

        private void UpdateThumbnail(int startPosition)
        {
            int totalWidth = 0;

            //돌면서 그려줘야 할 영역에 대해서만 그려주기!
            for (int i = 0; i < buttonList.Count; ++i)
            {
                totalWidth += buttonList[i].Width + buttonOffset;

                //처음으로 커지는 구간을 찾기! 시작 지점!
                if (totalWidth >= startPosition)
                {
                    int showPositionX = totalWidth - startPosition - buttonList[i].Width - buttonOffset;

                    List<Control> newFocusList = new List<Control>();

                    for (int j = i; j < buttonList.Count; ++j)
                    {
                        Point position = new Point(showPositionX, 0);
                        showPositionX += buttonList[j].Width + buttonOffset;

                        buttonList[j].Location = position;
                        buttonList[j].Show();

                        newFocusList.Add(buttonList[j]);

                        if (this.Width <= showPositionX)
                        {
                            break;
                        }
                    }

                    //현재 영역에 들어있지 않은 기존 항목은 제거해줍니다.
                    foreach (Control item in focusedList)
                    {
                        if (newFocusList.Contains(item) == false)
                        {
                            item.Hide();
                        }
                    }

                    //현재 항목을 갱신합니다.
                    focusedList = newFocusList;

                    break;
                }
            }
        }

        private void panel1_Scroll(object sender, ScrollEventArgs e)
        {
            scrollPosition = e.NewValue;
            Console.WriteLine("scrollPosition : " + scrollPosition);
            UpdateThumbnail(scrollPosition);
        }

        private void panel1_Resize(object sender, EventArgs e)
        {
            if (panel1.AutoScrollMinSize.Width - panel1.Width <= scrollPosition)
            {
                Console.WriteLine("*************************** 크기 변경!!!");

                scrollPosition = Math.Max(0, panel1.AutoScrollMinSize.Width - panel1.Width);
                panel1.HorizontalScroll.Value = scrollPosition;
            }

            UpdateThumbnail(scrollPosition);
        }
    }
}

 

이미지 회전에 따른 여백 생성!

 

우선은 코드만 올립니다..

using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private string workFolder = Path.Combine(Application.StartupPath, "workFolder");
        private int sequence = 0;

        public Form1()
        {
            InitializeComponent();

            //이미지 확인을 위해 오토사이즈로 해줍니다.
            pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
            pictureBox2.SizeMode = PictureBoxSizeMode.AutoSize;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();

            //파일을 불러옵니다.
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                pictureBox1.Image = Image.FromFile(ofd.FileName);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (Directory.Exists(workFolder) == false)
            {
                Directory.CreateDirectory(workFolder);
            }

            sequence++; //돌린 횟수! (파일명 겹치지 않도록 해줍니다.)
            Image originImage = pictureBox1.Image; //사용될 원본 이미지!

            for (int rotation = 1; rotation <= 360; ++rotation) //360도 회전시켜줍니다.
            {
                int maxAngle = 91;

                int width = originImage.Width;
                int height = originImage.Height;

                int rot; //항상 0 ~ 90도로 만들어줍니다.

                if (90 < rotation && rotation <= 181) //해당 구간에서는 반전이 필요합니다.
                {
                    rot = 180 - rotation;
                }
                else if (270 < rotation && rotation <= 360) //해당 구간에서는 반전이 필요합니다.
                {
                    rot = 360 - rotation;
                }
                else
                {
                    rot = rotation % maxAngle;
                }

                double widthRadian = rot * Math.PI / 180;
                double heightRadian = (90 - rot) * Math.PI / 180; //90도 기준으로 반전 시켜줍니다.

                double w = Math.Cos(widthRadian) * width + Math.Sin(widthRadian) * height;
                double h = Math.Cos(heightRadian) * width + Math.Sin(heightRadian) * height;

                //기존 이미지 기준으로 돌려줍니다.
                DrawImage(originImage,
                          pictureBox1,
                          rotation,
                          width,
                          height,
                          width,
                          height,
                          "origin_");

                //크기가 확장 된 이미지 기준으로 돌려줍니다.
                DrawImage(originImage,
                          pictureBox2,
                          rotation,
                          (int)w,
                          (int)h,
                          width,
                          height,
                          "extend_");
            }

            originImage.Dispose();

            Process.Start(workFolder);
        }

        private void DrawImage(Image originImage,
                                PictureBox pictureBox,
                                int rotation,
                                int width,
                                int height,
                                int centerX,
                                int centerY,
                                string imageType)
        {
            Color backgroundColor = Color.FromArgb(255, 255, 0, 0);      //빨강 배경 (알파값을 통해 투명하게 가능!)

            Bitmap bitmap = new Bitmap(width, height);
            Graphics graphics = Graphics.FromImage(bitmap);

            graphics.Clear(backgroundColor);                             //배경 칠해주기
            graphics.TranslateTransform(width / 2, height / 2);          //이미지 가운데로 이동
            graphics.RotateTransform(rotation);                          //가운데에서 회전
            graphics.DrawImage(originImage, centerX / -2, centerY / -2); //이미지를 가운데 그려줍니다.

            string fileName = string.Format("{0}_{1}{2}.png", sequence.ToString("D4"), imageType, rotation.ToString("D4"));
            string savePath = Path.Combine(workFolder, fileName);

            bitmap.Save(savePath);

            graphics.Dispose();
            bitmap.Dispose();

            pictureBox.Image = Image.FromFile(savePath);
            pictureBox.Update();
        }
    }
}

 

 

간단한 용어 정리

Invoke : 메인 쓰레드에서 동작하도록 해주는 기능 (다른 스레드 => 메인UI 스레드 동작 시켜줌)

InvokeRequired : Invoke가 필요한 상황인지 체크. (메인UI 스레드 != 현재 실행중인 스레드 체크)

 

Windows Form에서 UI는 메인 스레드에서만 변경이 가능하며 그 외의 접근에 대해서는 예외가 발생됩니다.

 

우선 사용 방법은 다음과 같습니다. 방법1 혹은 방법2를 참고하여 사용하면 됩니다.

(예외와 주의 사항은 더 아래쪽에 적어두겠습니다.)

using System;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form3 : Form
    {
        //방법2를 위한 delegate ******************
        private delegate void SetLabelDelegate();
        private SetLabelDelegate setLabelDelegate;
        //****************************************

        private string message1 = "";
        private string message2 = "";
        private string message3 = "";
        
        public Form3()
        {
            InitializeComponent();

            //방법2를 위한 delegate 등록 *************
            setLabelDelegate += SetLabel;
            //****************************************
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(ChangeUI);

            message1 = "mainThread " + AppDomain.GetCurrentThreadId().ToString();
            thread.Start();
        }

        private void ChangeUI ()
        {
            message2 += "subThread " + AppDomain.GetCurrentThreadId().ToString();

            if (InvokeRequired)
            {
                //방법1. MethodInvoker, delegate 조합
                Invoke((MethodInvoker)delegate
                {
                    message3 = "InvokeThread " + AppDomain.GetCurrentThreadId().ToString();
                    SetLabel();
                });

                //방법2. delegate 호출
                //Invoke(setLabelDelegate);
            }
            else
            {
                SetLabel();
            }
        }

        private void SetLabel ()
        {
            label1.Text = message1;
            label2.Text = message2;
            label3.Text = message3;
        }
    }
}

 

결과 (SetLabel은 메인쓰레드 7724로 실행)

 

 

오류 예시.

[System.InvalidOperationException - Cross Thread]

: 메인 스레드가 아닌 다른 스레드에서 UI 변경을 한 경우 발생

using System;
using System.Threading;
using System.Windows.Forms;

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

        private void button1_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(ChangeUI);

            MessageBox.Show(AppDomain.GetCurrentThreadId().ToString()); //main thread number
            thread.Start();
        }

        private void ChangeUI ()
        {
            MessageBox.Show(AppDomain.GetCurrentThreadId().ToString()); //other thread number
            label1.Text = "1";
        }
    }
}

 

결과 : 당연하게 오류 발생

 

 

주의 사항)

 쓰레드 번호를 확인할 수 있도록 코드를 작성해놨습니다.

이것으로 현재 함수가 어떤 쓰레드에서 실행 중인지 볼 수 있는데,

Invoke를 통해 MainThread -> SubThread -> MainThread 방식의 흐름이 가능합니다.

반드시 필요한 곳에서만 Invoke를 사용해야 올바른 비동기 코드가 진행이 될 수 있습니다.

잘 못된 코드와 올바른 코드 예시를 남깁니다.

 

using System;
using System.Threading;
using System.Windows.Forms;

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

        private void button1_Click(object sender, EventArgs e)
        {
            Thread validInvokeThread = new Thread(ChangeUI);
            validInvokeThread.Start();

            //이렇게 호출되면 안됨.
            //Thread invalidInvokeThread = new Thread(ChangeUI2);
            //invalidInvokeThread.Start();
        }

        private void ChangeUI()
        {
            //스레드 동작과 메인UI 스레드가 원하는대로 잘 동작하게 됩니다.
            SetLabel(label1, AppDomain.GetCurrentThreadId().ToString());
            Thread.Sleep(1000);
            SetLabel(label2, AppDomain.GetCurrentThreadId().ToString());
            Thread.Sleep(1000);
            SetLabel(label3, AppDomain.GetCurrentThreadId().ToString());
        }

        private void ChangeUI2()
        {
            //이렇게 사용하면 MainThread에서 모든 것을 처리해주게 됩니다.
            //즉 "스레드의 사용 의미가 없다" 입니다.
            if (InvokeRequired)
            {
                Invoke((MethodInvoker)delegate
                {
                    ChangeUI2();
                });
            }
            else
            {
                SetLabel(label1, AppDomain.GetCurrentThreadId().ToString());
                Thread.Sleep(1000);
                SetLabel(label2, AppDomain.GetCurrentThreadId().ToString());
                Thread.Sleep(1000);
                SetLabel(label3, AppDomain.GetCurrentThreadId().ToString());
            }
        }

        private void SetLabel(Label label, string text) //delegate로 함수를 만들어서 처리도 가능
        {
            if (InvokeRequired)
            {
                Invoke((MethodInvoker)delegate
                {
                    label4.Text = "메인스레드 : " + AppDomain.GetCurrentThreadId().ToString();
                    label.Text = text;
                });
            }
            else
            {
                label4.Text = "메인스레드 : " + AppDomain.GetCurrentThreadId().ToString();
                label.Text = text;
            }
        }
    }
}

 

ChangeUI (UI는 메인에서, Sleep은 다른 스레드에서 처리)

ChangeUI2 (메인 스레드에서 처리. Sleep에 의해 멈추기도함)

 

[디렉토리 이동]

cd 

예시) cd C:\       (띄어쓰기 등이 있는 경우는 "  " 로 감싸주기)

 

[파일 확인]

dir

예시) dir

 

(공통 옵션)

/s - 하위목록도 삭제

/q - 삭제여부 확인 x

 

[파일 삭제]

del

예시) del /s /q *.txt

 

[폴더 삭제]

rmdir

예시) rmdir /s /q C:\temp\....

MSBuild.exe 파일의 경로를 찾아줍니다.

 

 ex) 전 2019 Community 버전을 사용하고 있습니다.

    C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin

 

text 파일로 아래와 같이 작성을 해준 후 .bat 파일로 저장하면 완성입니다!

 

:msbuild 파일이 있는 곳으로 이동 (참고로 배치에서 : 은 주석!)
cd C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin

:빌드할 솔루션 지정
msbuild C:\Users\dlaeh\source\repos\WindowsFormsApp1\WindowsFormsApp1.sln

: 결과를 보고 싶으면 pause 명령을 통해 멈춰줍니다.
pause

 

해당 배치파일 실행한 결과!!

 

좀 더 옵션을 줄 수도 있습니다.

 

:msbuild 파일이 있는 곳으로 이동
cd C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin

:빌드할 솔루션 지정
msbuild C:\Users\dlaeh\source\repos\WindowsFormsApp1\WindowsFormsApp1.sln /t:Rebuild /p:Configuration="Release" /p:Platform="Any CPU"

:/t:Rebuild 		재빌드
:/p:Configuration="Release"   	디버그 혹은 릴리즈 혹은 사용자 지정 세팅
:/p:Platform="Any CPU"  	타겟 플랫폼 지정
:/v:quiet 			조용히 빌드하기 

: 결과를 보고 싶으면 pause 명령을 통해 멈춰줍니다.
pause

 

Configuration과 Platform 은 구성 관리자에서 추가도 가능합니다.

 

 

'기타 > 유용한 것' 카테고리의 다른 글

[cmd] 명령어 정리하기  (0) 2024.04.20
SFTP 구축 및 연결 (freeFTPd, WinSCP/FileZilla)  (0) 2023.04.09
NSIS 설치 파일 만들기  (0) 2022.08.18

GraphicsPath 를 이용하여 둥근 버튼, 별표 등 다양한 형태의 도형을 만들 수 있습니다.

둥근 버튼

 

1. 새 항목 추가로 클래스를 만들어줍니다.

 

2. 아래와 같이 Button을 상속 받고 로직을 작성해줍니다.

  (path 첫 위치에 따라 회전각도가 달라집니다. 회전은 0도 = 12시 방향이고, 시계방향으로 돕니다!)

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

namespace MyButton
{
    class CustomButton : Button
    {
        public int Radius { get; set; } //외부에서 크기를 받습니다.
        public bool IsMaximum { get; set; } //외부에서 최대 사이즈를 받습니다.

        public CustomButton ()
        {
            Radius = 20;
            IsMaximum = true;

            this.BackColor = Color.Red; //기본 색을 지정해줍니다.
            this.FlatStyle = FlatStyle.Flat; //버튼을 플랫하게 만들어줍니다.
            this.FlatAppearance.BorderSize = 0; //보더가 지저분해지기에 제거해줍니다.
        }

        protected override void OnPaint(PaintEventArgs p)
        {
            GraphicsPath path = new GraphicsPath();
            Rectangle rectangle = ClientRectangle;

            int radius = Math.Min(this.Height, Radius); //Height를 넘어가면 모양이 이상해집니다!

            if (IsMaximum) //최대 사이즈인 경우는 Height로!
            {
                radius = this.Height;
            }

            int x = rectangle.X;
            int y = rectangle.Y;
            int width = rectangle.Width;
            int height = rectangle.Height;

            //그리는 순서가 굉장히 중요합니다!
            path.AddArc(x, y, radius, radius, 180, 90); //왼쪽 상단
            path.AddArc(x + width - radius, y, radius, radius, 270, 90); //오른쪽 상단
            path.AddArc(x + width - radius, y + height - radius, radius, radius, 0, 90); //오른쪽 하단
            path.AddArc(x, y + height - radius, radius, radius, 90, 90); //왼쪽 하단

            path.CloseAllFigures();

            this.Region = new Region(path); //영역을 둥근형태로 만들어줍니다.

            base.OnPaint(p);
        }
    }
}

 

프로젝트를 "빌드" 하면 아래와 같이 구성요소에서 재정의한 버튼을 볼 수 있습니다.

해당 버튼을 알맞게 배치하고, 값을 설정하여 사용합시다!

디자인 문서개요는 컨트롤들의 순서 등 정리가 아주 편리합니다. (특히 도킹 관련해서 순서 지정이 가능!)

(+ 꿀팁 : 컨트롤에 포커싱 있는 상태에서 ESC 키를 통해 상위 컨트롤로 선택이 가능합니다.)

 

보기 => 다른 창 => 문서 개요  를 통해 복잡한 오브젝트를 쉽게 볼 수 있습니다.

 

문서 개요 매뉴

 

왼쪽 문서 개요 뷰에서 요소의 순서를 바꾸거나 삽입 / 제거 도 쉽답니다.

 

간단한 구조가 마음에 들어 까먹기 전에 다시 작성해두기!

using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

namespace LogTest
{
    public enum LogLevel
    {
        Debug, Info, Error
    }

    public static class Log
    {
        private static LogLevel logLevel;
        private static string logPath;

        public static void Init(LogLevel level) //최초 한번 실행해주기!
        {
            logLevel = level;

            //폴더 등 설정
            logPath = Path.Combine(Application.StartupPath, "test.log");
            //기존 불필요한 로그 삭제하기.
        }

        public static void Write(string message, LogLevel level, [CallerMemberName] string callFunction = "")
        {
            if (logLevel <= level)
            {
                string type = level.ToString();

                string logContent = string.Format("[{0}] {1} ({2}) : {3}{4}",
                                                type, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"), callFunction, message, Environment.NewLine);

                File.AppendAllText(logPath, logContent);
            }
        }
    }
}

+ Recent posts