.NET Framework / .NET 6 이상
[App.config] 파일

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <appSettings>
    <add key="ServerUrl" value="127.0.0.1"/>
    <add key="ServerPort" value="5000"/>
  </appSettings>
</configuration>

//코드에서 호출방법
System.Configuration.ConfigurationManager.AppSettings["ServerUrl"]
System.Configuration.ConfigurationManager.AppSettings["ServerPort"]

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

.NET Core / .NET 5
[appsettings.json] 파일

{
  "ServerUrl": "https://example.com/api",
  "ServerPort": "5"
}

//코드에서 호출
var config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

string apiUrl = config["ServerUrl"];
string apiPort = config["ServerPort"];

프로세스 내부에서의 임계영역 설정 (운영체제에서 관리 x - 빠른 속도) - Monitor, lock

object obj = new object(); //타겟 오브젝트

// 1) Monitor와 같은 역할
lock (obj)
{
  //do something
}

// 2) lock과 같은 역할
Monitor.Enter(obj)
//do something
Monitor.Exit(obj)

 

 

다른 프로세스 간 임계영역 설정 (운영체제에서 관리) - Mutex, Semaphore, EventWaitHendle

1) Mutex, Semaphore (1개면 Mutex와 동일)

using System;
using System.Threading;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            //1) 뮤텍스가 이미 있는지 확인
            bool isExist;
            Mutex mutex2 = new Mutex(true, "Test2", out isExist); //시작이 true면 바로 락 (WaitOne 효과)

            if (isExist)
            {
                //이미 존재하는 뮤텍스...
            }
            else
            {
                //존재하지 않는 뮤텍스..
            }
            mutex2.ReleaseMutex();
            mutex2.Dispose();
            //*******************************************

            //2) 
            Mutex mutex = new Mutex(false, "Test"); //시작이 false면 최초 락 걸지 않음 (WaitOne 필요)

            //************************************
            //(WaitOne 성공 시 true, 다른 곳에서 사용 중이면 false)
            //bool canUse = mutex.WaitOne(5000); //5초 기다리고 바로 실행
            //bool canUse = mutex.WaitOne(0);    //바로 사용 가능한지 확인
            //************************************

            mutex.WaitOne(); //사용 가능할 때까지 대기

            Console.WriteLine("{0} : 임계영역 설정", DateTime.Now);
            Console.ReadLine();

            mutex.ReleaseMutex(); //임계영역 해제
            mutex.Dispose(); //메모리 누수 방지를 위해 Dispose 혹은 using 필요!

            Console.WriteLine("{0} : 임계영역 해제", DateTime.Now);
            Console.ReadLine();
        }
    }
}

 

(GPT 대답)

  • createdNew는 뮤텍스가 "생성됐는지" 여부
  • WaitOne(0)는 뮤텍스 "소유권을 즉시 획득할 수 있는지" 여부

 

EventWaitHendle 도 있는데 나중에 정리..

32bit 환경에서 2GB 이상 사용하게 되면 out of memory 가 발생되게 됩니다.

기본 플랫폼 대상이 x86 (32bit)

 

c++ 도구 중 editbin.exe 라는 프로그램이 있는데 64bit 환경에서 32bit 프로그램의 메모리 사용량을 4GB 가량으로 늘려주는 기능이 있습니다.

Visual Studio Installer 에서 C++ MSVC를 설치하면..!

 

아래 경로에 editbin.exe 파일이 설치됩니다. (각 버전에 따라 위치가 변경 될 수 있습니다.)
C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x86\

                                                                           (버전) (타입)                                        (버전)

editbin.exe

 

빌드 후 이벤트에 해당 프로그램을 넣어주어 exe 메모리 사용량 변경이 가능합니다.

프로젝트 빌드 후 이벤트에 추가

 

"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x86\editbin.exe" /LARGEADDRESSAWARE "$(TargetPath)"

 

※ $(TargetPath) 는 프로그램 exe 경로입니다.

 

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

 

메모리 사용량 확인

디버그 -> 성능 프로파일러 -> 메모리 사용량 -> 시작

비주얼 스튜디오 툴바

 

 

메모리 사용량 체크

 

editbin 메모리 증가

적용 전 (1.2GB 정도 사용 후 다운) 적용 후 (3.0GB 정도 사용 후 다운)

 

Xml 구조 읽는 방법 예제

 

결과

 

using System;
using System.Xml;

class Test
{
    static void Main()
    {
        string testData =
                "<main>" +
                " <sub attribute1=\"SubAttribute\">" +
                "  <field name=\"first\">1</field>" +
                "  <field name=\"second\">" +
                "   <test>" +
                "    2" +
                "   </test>" +
                "  </field>" +
                " </sub>" +
                "</main>";

        XmlDocument xml = new XmlDocument();

        //파일 경로로 읽는 방식
        //xml.Load("파일 경로");

        //string 읽는 방식
        xml.LoadXml(testData);

        //<sub> 가져오기
        XmlNode subNode = xml.SelectSingleNode("main/sub"); //첫 번째 노드 가져옴

        //<sub> attribute1 가져오기
        string attribute1 = subNode.Attributes["attribute1"]?.Value;
        Console.WriteLine($"attribute1 : {attribute1}");

        //<sub><field> 가져오기
        XmlNodeList fieldList = subNode.SelectNodes("field"); //Node의 Node 방식

        //<field> 값들 순회
        foreach (XmlNode node in fieldList)
        {
            string innterText = node.InnerText; //순수 값.
            string innerXml = node.InnerXml; //xml 값 전체

            Console.WriteLine($"innerText: {innterText}, innerXml: {innerXml}");
        }

        //번외) 항상 첫 번째 값을 가져옴 (field가 여러개)
        string x = subNode["field"].Attributes["name"]?.Value; //"first"
        Console.WriteLine($"first node : {x}");
    }
}

Base64는 byte 배열을 읽는 방식을 의미합니다. (보통 우리가 사용하는 UTF8, ASCII 등이 있지요)

Base64는 3byte (24bit) 단위로 자르고, 6bit씩을 취해 해당 값에 매칭되는 base64테이블로 매핑해줍니다.

이 때, 원문이 없는 경우는 "=" 문자열을 넣어 길이를 채워줍니다.

아래는 aB1@ 를 base64로 변환 한 결과입니다.

 

Base64 Table : ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

 

base64 나눠지는 형식

 

 

일단 C# 에서 제공되는 코드가 있는데 내부 구현이 궁금하여 테스트 해보게 되었습니다.

 

[C# base64 기존 제공]

string text = "aB1@"; //원본
byte[] bytes = Encoding.UTF8.GetBytes(plainText); //원하는 인코딩으로 byte 만들기
string base64String = Convert.ToBase64String(bytes); //byte배열 -> base64 텍스트 변환
byte[] base64bytes = Convert.FromBase64String(base64String); //base64 텍스트 -> byte배열 변환
string originText = Encoding.UTF8.GetString(base64bytes); //원본과 일치

 

 

[C# 직접 구현 - 4Byte int 형으로 비트 값 연산에 사용 (GPT 도움..!)]

using System;
using System.Collections.Generic;
using System.Text;

class Test
{
    private const string table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    private const string plainText = "aB1@";

    static void Main()
    {
        byte[] bytes = Encoding.UTF8.GetBytes(plainText);

        string base64Text = Base64Encoding(bytes); //직접 구현
        string base64Text2 = Convert.ToBase64String(bytes); //기존 제공

        Console.WriteLine("base64 텍스트 : " + base64Text);
        Console.WriteLine("base64 텍스트2 : " + base64Text2);

        byte[] base64bytes = Base64Decoding(base64Text); //직접 구현
        byte[] base64bytes2 = Convert.FromBase64String(base64Text2); //기존 제공

        Console.WriteLine("원본 복원 : " + Encoding.UTF8.GetString(base64bytes));
        Console.WriteLine("원본 복원2 : " + Encoding.UTF8.GetString(base64bytes2));
    }

    private static string Base64Encoding(byte[] bytes)
    {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < bytes.Length; i += 3)
        {
            int value = 0;
            int paddingCount = 0;

            //3byte씩 합쳐주기
            for (int j = i; j < i + 3; ++j)
            {
                value = value << 8;

                if (j < bytes.Length)
                {
                    value |= bytes[j];
                }
                else
                {
                    paddingCount++;
                }
            }

            //Masking
            //1111 1100 0000 0000 0000 0000 (0x3F << 6 << 6 << 6)
            //0000 0011 1111 0000 0000 0000 (0x3F << 6 << 6)
            //0000 0000 0000 1111 1100 0000 (0x3F << 6)
            //0000 0000 0000 0000 0011 1111 (0x3F)
            int masking = 0x3F << 6 << 6 << 6;

            //3byte를 6bit로 쪼개기 (없다면 패딩처리 '=')
            for (int j = 0; j < 4; ++j)
            {
                if (paddingCount > 3 - j)
                {
                    sb.Append("=");
                }
                else
                {
                    int v = (value & masking) >> 6 * (3 - j);
                    sb.Append(table[v]);
                }

                masking = masking >> 6;
            }
        }

        return sb.ToString();
    }

    private static byte[] Base64Decoding(string text)
    {
        List<byte> byteList = new List<byte>();

        for (int i = 0; i < text.Length; i += 4)
        {
            int value = 0;
            int paddingCount = 0;

            for (int j = i; j < i + 4; ++j)
            {
                value = value << 6;

                if (text[j] != '=')
                {
                    value |= table.IndexOf(text[j]);
                }
                else
                {
                    paddingCount++;
                }
            }

            int masking = 0xFF << 8 << 8;

            for (int j = 0; j < 3; ++j)
            {
                if (paddingCount > (2 - j))
                {
                    break;
                }
                else
                {
                    int v = (value & masking) >> 8 * (2 - j);
                    byteList.Add((byte)v);
                }

                masking = masking >> 8;
            }

            //Console.WriteLine("Processing... " + Encoding.UTF8.GetString(byteList.ToArray()));
        }

        return byteList.ToArray();
    }
}

 

 

하위 form2, form3 은 항상 form1보다 앞에 위치!

(Winform) 하위 form2, form3이 form1 앞에 위치


(WPF에 Winform 삽입) form1이 MainWindow 앞에 위치

 

Form 앞쪽에 고정 시키고 싶은 Form이 있는 경우입니다.

Winform에서는 Owner 속성을 지정해주면 해당 Form보다 항상 앞에 위치하게 됩니다.

WPF에서는 창의 Handle 값을 가져와서 Winform에서 Show 할 때 지정해줄 수 있습니다.

 

[Winform]

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private Form2 form2 = new Form2();
        private Form3 form3 = new Form3();
        private int titleSize = 30;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            form2.Owner = this; //Form3은 Form1 앞에 위치
            form3.Owner = this; //Form3은 Form1 앞에 위치

            form2.Size = new Size(this.Width / 2, this.Height - titleSize);
            form3.Size = new Size(this.Width / 2, this.Height - titleSize);

            form2.Show();
            form3.Show();

            form2.Location = new Point(this.Location.X, this.Location.Y + titleSize);
            form3.Location = new Point(this.Location.X + form2.Size.Width, this.Location.Y + titleSize);
        }

        private void Form1_Move(object sender, EventArgs e)
        {
            form2.Location = new Point(this.Location.X, this.Location.Y + titleSize);
            form3.Location = new Point(this.Location.X + form2.Size.Width, this.Location.Y + titleSize);
        }
    }
}

 

[WPF 에서 Winform]

using System;
using System.Windows;
using System.Windows.Interop;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            Form1 form1 = new Form1();

            WindowInteropHelper helper = new WindowInteropHelper(this);
            IntPtr handle = helper.Handle; //WPF 핸들값 가져오기

            form1.Show(new Win32Window(handle)); //form1이 항상 윗쪽에!
        }
    }

    class Win32Window : System.Windows.Forms.IWin32Window
    {
        private IntPtr handle;
        public IntPtr Handle
        {
            get
            {
                return handle;
            }
        }

        public Win32Window(IntPtr handle)
        {
            this.handle = handle;
        }
    }
}

Windows 환경에서 사용되는 코드입니다.

WIN API 기반으로 이름(2번째 인자)에 해당하는 Named Mutex 를 통해 내부적 처리를 한다고 합니다.

 

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

namespace WindowsFormsApp1
{
    static class Program
    {
        /// <summary>
        /// 해당 애플리케이션의 주 진입점입니다.
        /// </summary>
        [STAThread]
        static void Main()
        {
            bool isRunning;
            Mutex mutex = new Mutex(true, "UniqueMutexName", out isRunning); //뮤택스 확인

            if (!isRunning) //이미 해당이름으로 Mutex가 걸려있으면 false 없다면 true
            {
                return;
            }
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());

            mutex.ReleaseMutex();  // 해제
            mutex.Dispose();  // 참조 해제
        }
    }
}

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

 

+ Recent posts