button1은 CustomPanel 하위 목록으로 들어가면서 추가 될 필드를 지정할 수 있습니다.

TableLayout 에서 Row, RowSpan 등의 속성이 추가되는 것이 궁금하여 찾아본 내용 정리입니다.

CustomPanel 하위 목록에 대해서 디자이너에서 보여줄 필드를 생성 가능

 

Panel 외에도 다른 컨트롤을 상속받아서도 가능합니다.

내부적으로 Dictionary 처리를 통해 값을 별도로 관리할 수 있습니다.

ProvideProperty 는 여러 개를 사용할 수 있습니다.

IExtenderProvider 를 상속받아야 하며 Setter와 Getter를 구현해주어야 사용 가능합니다.

아래는 예시 코드입니다.

using System.Collections.Generic;
using System.ComponentModel; //IExtenderProvider
using System.Windows.Forms;

namespace WindowsFormsApp6
{
    //해당 이름 사용. Get, Set 구현 필수
    //CustomField 이기때문에 GetCustomField(Control, 타입), SetCustomField(Control) 메소드 필요.
    [ProvideProperty("CustomField", typeof(Control))]
    class CustomPanel : Panel, IExtenderProvider
    {
        private readonly Dictionary<Control, int> fieldDict = new Dictionary<Control, int>();

        public bool CanExtend(object extendee)
        {
            Control control = extendee as Control;

            if (control == null)
            {
                return false;
            }

            //조건에 따라 전체 컴포넌트에 붙을 수 있음.
            return control.Parent == this; //자식 컴포넌트에만 붙여주기
        }

        [DisplayName("CustomField")] //없으면 클래스명의 필드로 이름 지정됨.
        public int GetCustomField(Control control)
        {
            if (fieldDict.TryGetValue(control, out int value))
            {
                return value;
            }

            return 0; //default value
        }

        public void SetCustomField(Control control, int value)
        {
            fieldDict[control] = value;
        }
    }
}

 

 

using System;
using System.IO;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string folderPath = Path.Combine(Directory.GetCurrentDirectory(), "targetFolder");
            string[] files = Directory.GetFiles(folderPath);

            DateTime now = DateTime.Now.AddDays(-3); //3일 전 파일까지 검출용.
            foreach (string file in files)
            {
                FileInfo fileInfo = new FileInfo(file);

                //A < B = 1, A == B = 0, A > B = -1
                if (DateTime.Compare(now, fileInfo.LastWriteTime) > 0) //기준보다 오래된 파일 삭제
                {
                    fileInfo.Delete();
                }
            }
        }
    }
}

 

폰트 적용

 

예제로 창원 단감 폰트 사용..!

ChangwonDangamAsac-Bold_0712.ttf

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

namespace ApplyFontFromCode
{
    public partial class Form1 : Form
    {
        private PrivateFontCollection privateFontCollection;

        public Form1()
        {
            InitializeComponent();

            //해당 리소스가 해제되면 폰트 오류가 발생되므로 전역 변수로 선언 필요.
            privateFontCollection = new PrivateFontCollection();
            privateFontCollection.AddFontFile(Path.Combine(Application.StartupPath, "ChangwonDangamAsac-Bold_0712.ttf"));

            SetControlFont(this);
        }

        private void SetControlFont (Control parent)
        {
            foreach (Control child in parent.Controls) //모든 하위 컨트롤들에 대해서 적용시켜줍니다.
            {
                child.Font = new Font(privateFontCollection.Families[0], child.Font.Size, child.Font.Style);

                if (child.Controls.Count > 0) //panel, groupbox 와 같이 하위 컨트롤이 더 있는 경우도 적용시켜줍니다.
                {
                    SetControlFont(child);
                }
            }
        }
    }
}

1) 상하좌우 크기 조절 기능.

2) 최대화, 최소화, 종료 버튼

3) 타이틀 버튼 설정 (마우스 클릭, 더블 클릭)

 

TestForm (FormParent 상속 받아서 기능 사용.)

namespace FormStyleNoneResize
{
    public partial class TestForm : FormParent
    {
        public TestForm()
        {
            InitializeComponent();

            this.SetTitle(label1); //타이틀 (마우스 움직임 등 설정)
            this.SetCloseButton(button1); //닫기 이벤트 추가
        }
    }
}

 

FormParent Class

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

namespace FormStyleNoneResize
{
    public partial class FormParent : Form
    {
        //https://docs.microsoft.com/ko-kr/windows/win32/inputdev/wm-nchittest
        private const int WM_NCHITTEST = 0x84; //마우스 커서 위치가 크기 조절 가능한 부분에 있는 경우
        private const int HTCLIENT = 1;
        private const int HTLEFT = 10;
        private const int HTRIGHT = 11;
        private const int HTTOP = 12;
        private const int HTTOPLEFT = 13;
        private const int HTTOPRIGHT = 14;
        private const int HTBOTTOM = 15;
        private const int HTBOTTOMLEFT = 16;
        private const int HTBOTTOMRIGHT = 17;

        private const int GRIP = 5; //리사이즈 잡는 영역 크기

        private Point prePosition;
        private Point mouseDownPoint;

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_NCHITTEST)
            {
                base.WndProc(ref m);

                if ((int)m.Result == HTCLIENT)
                {
                    Point position = this.PointToClient(Cursor.Position);

                    bool left = position.X <= GRIP;
                    bool right = this.Width - GRIP <= position.X;
                    bool top = position.Y <= GRIP;
                    bool bottom = this.Height - GRIP <= position.Y;

                    if (left && top) m.Result = (IntPtr)HTTOPLEFT;
                    else if (right && top) m.Result = (IntPtr)HTTOPRIGHT;
                    else if (left && bottom) m.Result = (IntPtr)HTBOTTOMLEFT;
                    else if (right && bottom) m.Result = (IntPtr)HTBOTTOMRIGHT;
                    else if (left) m.Result = (IntPtr)HTLEFT;
                    else if (right) m.Result = (IntPtr)HTRIGHT;
                    else if (top) m.Result = (IntPtr)HTTOP;
                    else if (bottom) m.Result = (IntPtr)HTBOTTOM;
                }

                return;
            }

            base.WndProc(ref m); //다른 처리를 위한 기존 콜백 다시 호출해주기
        }

        public FormParent()
        {
            InitializeComponent();
        }

        //마우스 이벤트가 발생할 타이틀바를 등록합니다.
        protected void SetTitle(Control titleControl)
        {
            titleControl.MouseDoubleClick += new MouseEventHandler(titleControl_MouseDoubleClick);
            titleControl.MouseDown += new MouseEventHandler(titleControl_MouseDown);
            titleControl.MouseMove += new MouseEventHandler(titleControl_MouseMove);
        }

        //최소화 버튼을 등록합니다.
        protected void SetMinimumButton(Button button)
        {
            button.Click += new EventHandler(this.button_minimum_Click);
        }

        //최대화 버튼을 등록합니다.
        protected void SetMaximumButton(Button button)
        {
            button.Click += new EventHandler(this.button_maximum_Click);
        }

        protected void SetCloseButton(Button button)
        {
            button.Click += new EventHandler(this.button_close_Click);
        }

        private void titleControl_MouseDoubleClick(object sender, MouseEventArgs e) //상단 타이틀 더블 클릭
        {
            if (e.Button != MouseButtons.Left) //왼쪽 버튼만 사용
            {
                return;
            }

            if (WindowState != FormWindowState.Maximized)
            {
                SetMaximize();
            }
            else
            {
                SetNormal();
            }
        }

        private void titleControl_MouseDown(object sender, MouseEventArgs e) //상단 타이틀 클릭 시 움직일 준비
        {
            if (e.Button != MouseButtons.Left) //왼쪽 버튼만 사용
            {
                return;
            }

            mouseDownPoint = new Point(e.X, e.Y);
        }

        private void titleControl_MouseMove(object sender, MouseEventArgs e) //상단 타이틀을 통한 이동
        {
            if ((e.Button & MouseButtons.Left) == MouseButtons.Left) //왼쪽 버튼이 눌린 경우만 움직여줍니다.
            {
                this.Left += e.X - mouseDownPoint.X;
                this.Top += e.Y - mouseDownPoint.Y;
            }
        }

        private void button_minimum_Click(object sender, EventArgs e) //최소화 버튼
        {
            WindowState = FormWindowState.Minimized;
        }

        private void button_maximum_Click(object sender, EventArgs e) //최대화 버튼
        {
            if (WindowState != FormWindowState.Maximized)
            {
                SetMaximize();
            }
            else
            {
                SetNormal();
            }
        }

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

        private void SetMaximize() //최대화
        {
            Screen screen = Screen.FromControl(this); //현재 Form과 가장 가까운 Screen 찾기.
            prePosition = this.Location; //노멀화 복귀 좌표값

            this.Location = screen.Bounds.Location; //위치 지정
            this.MaximizedBounds = new Rectangle(0, 0, screen.WorkingArea.Width, screen.WorkingArea.Height); //최대화 크기 변경
            this.WindowState = FormWindowState.Maximized;
        }

        //종료 버튼
        private void button_close_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

 

아래와 같이 이미지 및 문자 색상을 나타낼 수 있는 버튼을 만드는 코드입니다.

마우스 이동 시 이벤트가 자동으로 발생되어 지정한 이미지가 나타나도록 가능합니다. (텍스트 등 필요한 기능은 직접 추가!)


 

프로젝트에 Button을 상속받은 아래의 클래스를 생성 후 "프로젝트 빌드" 를 해주어야 사용 가능해집니다.

그리고 Image_Disable, Image_Disable_Color ... 에 대해 이미지 및 텍스트 색상 지정을 해주면 끝!

이미지 등록하는 모습

 

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

namespace WindowsFormsApp1
{
    public class MyButton : Button
    {
         public Image Image_Normal //초기 이미지
        {
            get
            {
                return image_Normal;
            }
            set
            {
                image_Normal = value;
                this.BackgroundImage = value; //디자이너에서 초기 이미지 변경 시 수정되도록 추가.
            }
        }
        private Image image_Normal;

        public Image Image_Hover { get; set; } //마우스 포커싱 이미지
        public Image Image_Press { get; set; } //마우스 누를시 이미지
        public Image Image_Disable { get; set; } //비활성화 이미지
        public Color Image_Normal_Color { get; set; } = Color.Black; //이미지 초기 글자색
        public Color Image_Hover_Color { get; set; } = Color.Black; //이미지 포커싱 글자색
        public Color Image_Press_Color { get; set; } = Color.White; //마우스 누를 시 글자색
        public Color Image_Disable_Color { get; set; } = Color.Black; //비활성화 글자색
        
        private bool isMouseDown;

        public CustomButton()
        {
            this.FlatStyle = FlatStyle.Flat;
            this.FlatAppearance.BorderSize = 0;
            this.FlatAppearance.MouseOverBackColor = Color.Transparent; //배경 투명
            this.FlatAppearance.MouseDownBackColor = Color.Transparent; //배경 투명
            this.FlatAppearance.CheckedBackColor = Color.Transparent; //배경 투명
            this.BackColor = Color.Transparent; //배경 투명

            this.BackgroundImageLayout = ImageLayout.Zoom; //이미지 보여주는 형태

            TabStop = false; //키보드 이동하여 테두리 그리기 방지
            SetStyle(ControlStyles.Selectable, false); //선택 테두리 방지
        }

        private void SetNormal()
        {
            this.BackgroundImage = Image_Normal;
            this.ForeColor = Image_Normal_Color;
        }

        private void SetHover()
        {
            this.BackgroundImage = Image_Hover;
            this.ForeColor = Image_Hover_Color;
        }

        private void SetPress()
        {
            this.BackgroundImage = Image_Press;
            this.ForeColor = Image_Press_Color;
        }

        private void SetDisable()
        {
            this.BackgroundImage = Image_Disable;
            this.ForeColor = Image_Disable_Color;
        }

        protected override void OnEnabledChanged(EventArgs e) //Enable에 따라 변경.
        {
            base.OnEnabledChanged(e);
            isMouseDown = false;

            if (!Enabled)
            {
                SetDisable();
                return;
            }

            SetHoverOrNormal(this.PointToClient(Cursor.Position)); //활성화 상태 복구
        }

        private void SetHoverOrNormal(Point point) //비활성화 혹은 포커싱 잃은 후 복구
        {
            if (ClientRectangle.Contains(point)) //Enable 시 마우스 위치 확인
            {
                SetHover();
            }
            else
            {
                SetNormal();
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (isMouseDown)
            {
                return;
            }

            SetHover();
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            SetNormal();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (e.Button != MouseButtons.Left) //왼쪽 버튼이 아니면 무시
            {
                return;
            }

            isMouseDown = true;
            SetPress();
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);

            if (e.Button != MouseButtons.Left) //왼쪽 버튼이 아니면 무시
            {
                return;
            }

            isMouseDown = false;
            SetHoverOrNormal(e.Location); //마우스 위치에 따라 상태 변경
        }
    }
}

ListView 혹은 ListBox를 이용한 섬네일 만드는 클래스 입니다.

class ThumbnailView : ListView

class ThumbnailBox : ListBox

상속을 통해 만들었고 필요한 함수는 커스텀해서 사용 가능합니다.

 

ListView는 가로로도 확장이 가능하고,

ListBox는 세로만 가능하게 만들었고, 사이즈가 가변적으로 변경됩니다.

 

먼저 결과 및 사용 방법.

ListView, ListBox 베이스 섬네일

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

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

        //폴더에 있는 이미지 파일 불러오기
        private void button_add_Click(object sender, EventArgs e)
        {
            string folder = @"C:\Test"; //폴더 경로
            string[] files = Directory.GetFiles(folder);

            button_add.Enabled = false; //추가 버튼

            Task.Run(() =>
            {
                int viewCount = thumbnailView1.GetCount(); //View Item 개수
                int boxCount = thumbnailBox1.GetCount(); //Box Item 개수

                foreach (string file in files)
                {
                    thumbnailView1.AddThumbnail(file, viewCount++.ToString("D4")); //View에 섬네일 추가
                    thumbnailBox1.AddThumbnail(file, boxCount++.ToString("D4")); //Box에 섬네일 추가
                }

                Invoke((MethodInvoker)delegate
                {
                    button_add.Enabled = true; //추가 버튼
                });
            });
            
            //GetSelectedPathList, RemoveSelectedThumbnail, RemoveAllThumbnail 있음.
        }
    }
}

 

ListView 클래스 (빌드 후 사용)

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

namespace WindowsFormsApp1 //네임스페이스 맞춰줄 것.
{
    public class ThumbnailView : ListView
    {
        private int thumbnailWidth = 128;
        private int thumbnailHeight = 128;

        public ThumbnailView()
        {
            this.View = View.LargeIcon;
            this.MultiSelect = true; //여러 장 선택 여부
            this.OwnerDraw = false; //기본 그리기 사용 여부
            this.HideSelection = false; //포커싱 잃으면 선택 취소
            this.DoubleBuffered = true; //깜빡임 방지 더블버퍼

            ImageList imageList = new ImageList();
            imageList.ColorDepth = ColorDepth.Depth32Bit;
            imageList.ImageSize = new Size(thumbnailWidth, thumbnailHeight); // 원하는 썸네일 크기
            this.LargeImageList = imageList;
        }

        public int GetCount() //현재 개수 구하기
        {
            return this.Items.Count;
        }

        public void AddThumbnail(string path, string name) //섬네일 추가
        {
            using (Image original = Image.FromFile(path))
            {
                Image thumbnail = MakeThumbnail(original, thumbnailWidth, thumbnailHeight);
                int index = this.LargeImageList.Images.Count;

                InvokeAuto(() =>
                {
                    this.LargeImageList.Images.Add(thumbnail);
                    ListViewItem item = new ListViewItem();
                    item.Text = name; //섬네일 이름
                    item.ImageIndex = index; //새로운 인덱스
                    item.Tag = path; // 원본 경로 저장 (object형태 tag에 저장)
                    this.Items.Add(item);
                });
            }
        }

        private Image MakeThumbnail(Image image, int width, int height) //섬네일 생성
        {
            Bitmap bitmap = new Bitmap(width, height);
            using (Graphics g = Graphics.FromImage(bitmap))
            {
                g.Clear(Color.White);
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

                //이미지 비율 유지
                double ratio = Math.Min((double)width / image.Width, (double)height / image.Height);
                int newWidth = (int)(image.Width * ratio);
                int newHeight = (int)(image.Height * ratio);

                int x = (width - newWidth) / 2;
                int y = (height - newHeight) / 2;

                g.DrawImage(image, x, y, newWidth, newHeight); //섬네일 그리기
                g.DrawRectangle(Pens.Gray, 0, 0, width - 1, height - 1); //테두리 그리기
            }

            return bitmap;
        }

        private void InvokeAuto (Action action) //스레드 사용 대비
        {
            if (InvokeRequired)
            {
                Invoke((MethodInvoker)delegate
                {
                    action();
                });
            }
            else
            {
                action();
            }
        }

        public List<string> GetSelectedPathList() //선택 된 경로 반환
        {
            if (this.SelectedIndices == null)
            {
                return new List<string>();
            }

            List<string> pathList = new List<string>();

            foreach (int index in this.SelectedIndices)
            {
                pathList.Add(this.Items[index].Tag as string);
            }

            return pathList;
        }

        public void RemoveSelectedThumbnail() //선택 된 요소 삭제
        {
            if (this.SelectedIndices == null || this.SelectedIndices.Count <= 0)
            {
                return;
            }

            this.BeginUpdate();

            //뒷쪽부터 삭제
            for (int i = this.SelectedIndices.Count - 1; i >= 0; --i)
            {
                int index = this.SelectedIndices[i];
                Remove(index);
            }

            this.EndUpdate();
        }

        public void RemoveAllThumbnail() //모든 요소 삭제
        {
            if (this.Items.Count <= 0)
            {
                return;
            }

            this.BeginUpdate();

            for (int i = this.Items.Count - 1; i >= 0; --i)
            {
                Remove(i);
            }

            this.EndUpdate();
        }

        private void Remove(int index)
        {
            this.Items.RemoveAt(index);

            this.LargeImageList.Images[index].Dispose();
            this.LargeImageList.Images.RemoveAt(index);
        }
    }
}

 

 

ListBox 클래스

 

※ ItemHeight가 변경 되면 공백이 남는 경우가 생깁니다.

이에따라 고정크기 panel 하위에 listbox를 생성하고, 현재 보여지는 크기에 맞추어 ItemHeight가 가변적으로 변경됩니다.

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

namespace WindowsFormsApp1
{
    class ThumbnailBox : Panel
    {
        private class ThumbnailInfo
        {
            public Image NormalImage { get; set; }
            public Image FocusImage { get; set; }
            public string Path { get; set; }
            public string Name { get; set; }
        }

        private readonly Color highlighColor = Color.FromArgb(80, 0, 120, 215);

        private readonly int thumbnailMaxSize = 128; //최대 사이즈 고정 (255넘으면 섬네일 생성 실패)
        private readonly int thumbnailSize = 128;

        private readonly int imageFixedHeight = 110;
        private readonly int textHeight = 30;
        private readonly int margin = 3;

        private List<ThumbnailInfo> thumbnailInfoList = new List<ThumbnailInfo>();
        private ListBox listBox = new ListBox();

        public ThumbnailBox()
        {
            //리스트박스 사이즈가 가변일 수 있어 panel하위에서 크기 체크.
            this.Controls.Add(listBox);
            this.SizeChanged += panel_SizeChanged;
            this.BackColor = Color.White;
            this.BorderStyle = BorderStyle.FixedSingle;
            this.Margin = new Padding(0);

            listBox.Margin = new Padding(0);
            listBox.BackColor = Color.Gray;
            listBox.Dock = DockStyle.Fill;
            listBox.DrawMode = DrawMode.OwnerDrawFixed; //직접 그려주기
            listBox.DrawItem += ThumbnailBox_DrawItem; //직접 그려주기
            listBox.ItemHeight = imageFixedHeight; //아이템 높이 설정
            listBox.BorderStyle = BorderStyle.None;
            listBox.SelectionMode = SelectionMode.MultiExtended; //다중 선택
        }

        private void panel_SizeChanged(object sender, EventArgs e)
        {
            int panelHeight = this.ClientSize.Height; //실제 표시 가능한 영역

            int visibleCount = (int)Math.Round((double)panelHeight / imageFixedHeight); //패널 높이에 따라 표시할 아이템 개수 결정
            visibleCount = Math.Max(visibleCount, 1); //최소 1개 보장

            listBox.ItemHeight = panelHeight / visibleCount; //꽉 차게 분배
            listBox.Invalidate();
        }

        public int GetCount() //현재 개수 구하기
        {
            return listBox.Items.Count;
        }

        public void AddThumbnail(string path, string name) //섬네일 추가
        {
            try
            {
                ThumbnailInfo thumbnailInfo = new ThumbnailInfo
                {
                    Path = path,
                    Name = name,
                };

                using (Image image = Image.FromFile(path))
                {
                    int width = image.Width;
                    int height = image.Height;

                    double ratio = (double)Math.Min(thumbnailSize, thumbnailMaxSize) / (double)Math.Max(width, height);

                    int newWidth = (int)(width * ratio);
                    int newHeight = (int)(height * ratio);

                    //섬네일 생성
                    Image thumbnail = image.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
                    thumbnailInfo.NormalImage = thumbnail;

                    //선택 시 하이라이트 섬네일 생성
                    Bitmap bitmap = new Bitmap(thumbnail.Width, thumbnail.Height);
                    using (Graphics g = Graphics.FromImage(bitmap))
                    {
                        g.DrawImage(thumbnail, 0, 0, bitmap.Width, bitmap.Height);

                        using (Brush brush = new SolidBrush(highlighColor))
                        {
                            g.FillRectangle(brush, 0, 0, bitmap.Width, bitmap.Height);
                        }
                    }
                    thumbnailInfo.FocusImage = bitmap;
                }

                thumbnailInfoList.Add(thumbnailInfo);

                //이름으로 아이템 추가
                if (InvokeRequired)
                {
                    Invoke((MethodInvoker)delegate
                    {
                        listBox.Items.Add(name);
                    });
                }
                else
                {
                    listBox.Items.Add(name);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        private void ThumbnailBox_DrawItem(object sender, DrawItemEventArgs e)
        {
            e.DrawBackground();

            int index = e.Index;
            if (index < 0)
            {
                return;
            }

            //일반 or 선택 시 백그라운드 및 이미지
            Brush background = Brushes.White;
            Image image = thumbnailInfoList[index].NormalImage;
            int itemHeight = listBox.ItemHeight;

            if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) //선택 된 상태
            {
                background = Brushes.DodgerBlue;
                image = thumbnailInfoList[index].FocusImage;
            }

            //배경 그리기
            e.Graphics.FillRectangle(background, e.Bounds.X + margin, e.Bounds.Y + margin, e.Bounds.Width - margin * 2, e.Bounds.Height - margin);

            //이미지 그리기
            e.Graphics.DrawImage(image, e.Bounds.Left + margin, e.Bounds.Top + margin, e.Bounds.Width - margin * 2, itemHeight - textHeight);

            //텍스트 그리기
            using (StringFormat stringFormat = new StringFormat() { Alignment = StringAlignment.Center }) //텍스트 가운데 정렬
            {
                e.Graphics.DrawString(thumbnailInfoList[index].Name,
                                      e.Font, //폰트
                                      Brushes.Black, //색상
                                      (e.Bounds.Left + e.Bounds.Right) / 2, //위치x
                                      e.Bounds.Top + itemHeight - textHeight + 10, //위치y
                                      stringFormat);
            }

            //선택 시 테두리
            e.DrawFocusRectangle();
        }

        public List<string> GetSelectedPathList() //선택 된 경로 반환
        {
            if (listBox.SelectedIndices == null)
            {
                return new List<string>();
            }

            List<string> pathList = new List<string>();

            foreach (int index in listBox.SelectedIndices)
            {
                pathList.Add(thumbnailInfoList[index].Path);
            }

            return pathList;
        }

        public void RemoveSelectedThumbnail() //선택 된 요소 삭제
        {
            if (listBox.SelectedIndices == null || listBox.SelectedIndices.Count <= 0)
            {
                return;
            }

            listBox.BeginUpdate();
            //뒷쪽부터 삭제
            for (int i = listBox.SelectedIndices.Count - 1; i >= 0; --i)
            {
                int index = listBox.SelectedIndices[i];
                Remove(index);
            }

            listBox.EndUpdate();
            listBox.Refresh();
        }

        public void RemoveAllThumbnail() //모든 요소 삭제
        {
            if (listBox.Items.Count <= 0)
            {
                return;
            }

            listBox.BeginUpdate();

            //뒷쪽부터 삭제
            for (int i = listBox.Items.Count - 1; i >= 0; --i)
            {
                Remove(i);
            }

            listBox.EndUpdate();
            listBox.Refresh();
        }

        private void Remove(int index)
        {
            listBox.Items.RemoveAt(index);

            //이미지 리소스 해제 필요
            thumbnailInfoList[index].NormalImage.Dispose();
            thumbnailInfoList[index].FocusImage.Dispose();
            thumbnailInfoList.RemoveAt(index);
        }
    }
}

C# 에서 기본적으로 제공되는 압축 해제 기능으로

System.IO.Compression 가 있습니다.

암호화는 지원하지 않지만 기본 내장되어 있고 사용도 편리합니다.

 

ZipFile 클래스 사용을 위해선 System.IO.Compression.FileSystem 참조가 필요합니다.

 

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression; //ZipFile 에 필요
using System.Windows.Forms;

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

        private void button1_Click(object sender, EventArgs e) //zip 생성
        {
            string workFolder = Application.StartupPath;
            string targetDirectory = Path.Combine(workFolder, "TestFiles");
            string savePath = Path.Combine(workFolder, "Zipfile.zip");

            if (File.Exists(savePath)) //기존 파일은 제거
            {
                File.Delete(savePath);
            }

            ZipFile.CreateFromDirectory(targetDirectory, savePath);
            Process.Start(workFolder); //폴더 열어서 확인
        }

        private void button2_Click(object sender, EventArgs e) //zip 해제
        {
            string workFolder = Application.StartupPath;
            string targetPath = Path.Combine(workFolder, "Zipfile.zip");
            string saveFolder = Path.Combine(workFolder, "unZip");

            if (Directory.Exists(saveFolder)) //기존 파일은 제거
            {
                Directory.Delete(saveFolder, true);
            }
            
            ZipFile.ExtractToDirectory(targetPath, saveFolder);
            Process.Start(workFolder); //폴더 열어서 확인
        }
    }
}

 

 

파일 압축 및 풀기 결과

 

그 밖에도 다른 라이브러리들이 있는데, NuGet 에서 받아서 사용이 가능합니다.

GPT에서 제공한 표를 남겨두겠습니다.

 

- GPT 정리 내용

라이브 러리 암호화 포맷 속도 최신 유지장점 요약 (2025년 10월 06일 기준!)

라이브러리 암호화 포맷 속도 최신 유지 장점 요약
System.IO.Compression ZIP ✅ 빠름 ✅ 최신 .NET 내장, 간단
DotNetZip (Ionic.Zip) ZIP ⚪ 보통 ❌ 구버전 암호 ZIP 지원
SharpZipLib ZIP, TAR, GZIP 등 ⚪ 보통 ✅ 유지보수 중 다양한 포맷
SharpCompress ⚠️ 제한적 ZIP, 7z, TAR 등 ⚪ 보통 ✅ 최신 스트리밍 지원
SevenZipSharp ✅ AES 7z ⚠️ 느림 ⚪ 부분적 고압축, 보안

 

DotNetZip 간단 사용법 예시

using Ionic.Zip;

// 압축
using (var zip = new ZipFile())
{
    zip.Password = "1234"; // 암호 설정
    zip.AddDirectory(@"C:\TestSource");
    zip.Save(@"C:\Result.zip");
}

// 해제
using (var zip = ZipFile.Read(@"C:\Result.zip"))
{
    zip.Password = "1234";
    zip.ExtractAll(@"C:\Extracted", ExtractExistingFileAction.OverwriteSilently);
}

DateTime 으로 시간을 확인 할 수 있지만, Stopwatch 를 통해 경과 시간을 확인 할 수 있습니다.

내부 요소를 통해 아래의 방법대로 표현도 가능합니다.

 

 

using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch stopWatch = Stopwatch.StartNew(); //객체 만들자마자 Start()까지 실행됨
            
            while (stopWatch.ElapsedMilliseconds < 5000) //5초
            {
                try
                {
                    using (TcpClient tcpClient = new TcpClient()) //연결만 확인.
                    {
                        Task task = tcpClient.ConnectAsync("127.0.0.1", 8080); //연결할 주소
                        if (task.Wait(3000) && tcpClient.Connected) //연결 확인
                        {
                            tcpClient.Close();
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    //Console.WriteLine(ex); //서버 없으면 오류나서 주석처리
                }

                Console.WriteLine($"tick: {stopWatch.ElapsedTicks}");
                Console.WriteLine($"milliseconds: {stopWatch.ElapsedMilliseconds}");

                TimeSpan timeSpan = stopWatch.Elapsed;
                Console.WriteLine($"기본: {timeSpan}");
                Console.WriteLine($"초: {timeSpan.TotalSeconds}"); //전체 초 단위
                Console.WriteLine($"ms: {timeSpan.TotalMilliseconds}"); //전체 밀리초 단위
                Console.WriteLine($"포맷: {timeSpan:hh\\:mm\\:ss\\.fff}"); //00:00:00.000

                Console.WriteLine();
            }

            Console.ReadLine();
        }
    }
}

오랜만에 기본기에 대해 생각을 해보게 되었습니다.

값 복사 형식과 참조 형식.. (잊고 있던 포인터..)

코드에 대부분 클래스를 사용하다보니 잊고 있었는데 복습할 겸 글을 남겨봅니다.

가독성을 위해 foreach 를 사용하려고 하는데 구조체에 대해서는 메모리 낭비가 발생 되고,

foreach 내부에서 원본의 값을 수정할 수 없다는 단점이 있더군요..!

추가로 배열 사이즈 변경도 남겨봅니다.

 

 

using System;
using System.Collections.Generic;

namespace ConsoleApp2
{
    class Program
    {
        struct ValueType
        {
            public int a;
            public int b;
        }

        class ReferenceType
        {
            public int a;
            public int b;
        }

        static void Main(string[] args)
        {
            ValueType valueType; //new ValueType() 없이 사용 가능.
            valueType.a = 1;
            valueType.b = 2;

            ValueType valueType2 = valueType; //값 복사됨.
            valueType2.a = 3;
            valueType2.b = 4;

            ReferenceType referenceType = new ReferenceType(); //반드시 메모리 할당 필요
            referenceType.a = 1;
            referenceType.b = 2;

            ReferenceType referenceType2 = referenceType; //같은 메모리 바라봄
            referenceType2.a = 3;
            referenceType2.b = 4;

            Console.WriteLine("valueType : {0} {1}", valueType.a, valueType.b);
            Console.WriteLine("valueType2 : {0} {1}", valueType2.a, valueType2.b);

            Console.WriteLine("referenceType : {0} {1}", referenceType.a, referenceType.b);
            Console.WriteLine("referenceType2 : {0} {1}", referenceType2.a, referenceType2.b);

            List<ValueType> valueTypeList = new List<ValueType>();
            List<ReferenceType> referenceTypeList = new List<ReferenceType>();

            //ValueType은 foreach에서 값 변경이 불가능하다, 또한 메모리 낭비가 발생 될 수 있다.
            foreach (ValueType sub in valueTypeList)
            {
                Console.WriteLine("{0} {1}", sub.a, sub.b);
                //sub.a = 40; //구조체는 오류 발생. 복사 값이므로 원본을 바꿀 수 없어 컴파일 오류.
            }

            foreach (ReferenceType sub in referenceTypeList) //foreach 도 참조형태 - 원본 값 바뀜
            {
                Console.WriteLine("{0} {1}", sub.a, sub.b);
                sub.a = 40; //원본 값 변경 됨.
            }

            ValueType[] valueTypeArray = null;
            Array.Resize(ref valueTypeArray, 5); //new ValueType[5]

            for (int i = 0; i < valueTypeArray.Length; ++i)
            {
                Console.WriteLine("배열 개수 : {0}", i + 1);
            }

            Console.ReadLine();
        }
    }
}

A프로그램에서 B프로그램에게 요청하여 동작을 할 일이 있었습니다.

B프로그램은 초기화 되며 열려있는 창들이 닫혀야 했는데 이 부분에 대한 구현이 필요했습니다.

 

ex)

Program (B) 기능 -> OpenFileDialog (윈도우 API) Custom Form (사용자Form)

 

Program (B) 가 ShowDialog 로 잡혀있는 상황입니다.

윈도우API 종료는 SendMessage 를 사용해야 하고, Form은 Close를 통해 종료해 줄 수 있습니다.

 

(NamedPipe를 사용하였고, BlockingCollection Queue 를 사용하여 순서유지를 보장하였습니다.

코드가 좀 길어졌지만.. 남겨봅니다!

 

ShowDialog() 종료하는 프로그램

 

A프로그램 (명령 내리는 코드)

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

namespace WindowsFormsApp6
{
    public partial class Form2 : Form
    {
        private const string targetName = "A_Pipe";

        public Form2()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            SendNamedPipe("CloseAPI");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            SendNamedPipe("CloseSubForm");
        }

        private void SendNamedPipe(string message)
        {
            try
            {
                NamedPipeClientStream namedPipeClientStream = new NamedPipeClientStream(".", targetName, PipeDirection.InOut);
                namedPipeClientStream.Connect(10000); //10초

                StreamWriter streamWriter = new StreamWriter(namedPipeClientStream);

                streamWriter.WriteLine(message);
                streamWriter.Flush();

                streamWriter.Close();
                namedPipeClientStream.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

 

 

B프로그램 (ShowDialog 하는 프로그램)

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form3 : Form
    {
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        private const uint WM_CLOSE = 0x0010;
        private const string myName = "A_Pipe"; //이름만 바꿔서 사용 가능

        private BlockingCollection<string> queue = new BlockingCollection<string>();
        private int queueCount = 0;

        private void ProcessQueue()
        {
            //내부적으로 monitor lock 걸려있어 순서 및 단일 실행 보장
            foreach (string message in queue.GetConsumingEnumerable())
            {
                if (message == "CloseAPI")
                {
                    CloseAPI();
                }
                else if (message == "CloseSubForm")
                {
                    CloseSubForm();
                }

                Invoke((MethodInvoker)delegate
                {
                    queueCount++;
                    listBox1.Items.Add(string.Format("{0}_{1}", queueCount, message));
                });
            }
        }

        public Form3()
        {
            InitializeComponent();

            //큐에서 관리
            Task.Run(() =>
            {
                ProcessQueue();
            });

            //네임드파이프 통신
            Task.Run(() =>
            {
                Receive();
            });
        }

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

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

                    queue.Add(message); //큐에 데이터 쌓아주기

                    streamReader.Close();
                    namedPipeServerStream.Close();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "불러오기 테스트";

            //테스트하기 위해 띄워주기만 함.
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                //정상 OK 눌렀을 때 실행
            }
            else
            {
                //강제 종료시 이 곳을 실행.
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //테스트용으로 단순 띄워주기만 진행
            new Form1().ShowDialog();
        }

        private void CloseAPI()
        {
            IntPtr hWnd = FindWindow(null, "불러오기 테스트"); //타이틀명으로 찾기
            if (hWnd != IntPtr.Zero)
            {
                SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); //종료 메시지 보내기
            }
        }

        private void CloseSubForm()
        {
            List<Form> formList = new List<Form>();

            foreach (Form form in Application.OpenForms)
            {
                // 메인폼 제외
                if (form is Form3 == false)
                {
                    formList.Add(form);
                }
            }

            foreach (Form form in formList)
            {
                Invoke((MethodInvoker)delegate
                {
                    form.Close();
                });
            }
        }
    }
}

+ Recent posts