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에 의해 멈추기도함)

 

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

세 개의 버튼을 만들고 테스트 해봅시다.

 

프로그램 중간에 종료를 시켜야 하는 경우 위의 3가지 방식을 사용할 수 있습니다.

각 특징은 아래와 같습니다.

 

Close() : 해당되는 폼만 닫습니다.

                

Application.Exit() : 다른 서브 폼에서 호출 해도 메인 폼의 Close()까지 동작되게 됩니다.

 

Environment.Exit(0) : 이 곳에서 바로 종료됩니다.

 

using System;
using System.Windows.Forms;

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

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
            //이 후 로직도 타게 됩니다.
            //무언가 로직 실행... (+ FormClosing)
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Application.Exit();
            //이 후 로직도 타게 됩니다.
            //무언가 로직 실행... (+ FormClosing)
        }

        private void button3_Click(object sender, EventArgs e)
        {
            Environment.Exit(0);
            //바로 종료 됩니다. (FormClosing을 타지 않습니다.)
            //실행되지 않는 이 후 로직!
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //Close(), Application.Exit() 시에 이 곳까지 도달합니다.

            e.Cancel = true; //프로그램이 종료되지 못하도록 막습니다.
        }
    }
}

+ Recent posts