■ 가상 리스트 박스 사용하기

------------------------------------------------------------------------------------------------------------------------


TestProject.zip


VirtualListBox.cs

 

 

using System;

using System.Collections.ObjectModel;

using System.Collections.Specialized;

using System.ComponentModel;

using System.Diagnostics;

using System.Drawing;

using System.Windows.Forms;

 

namespace TestProject

{

    /// <summary>

    /// 가상 리스트 박스

    /// </summary>

    public class VirtualListBox : UserControl

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Event

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 마지막 렌더링 시간 변경시 - LastRenderTimeChanged

 

        /// <summary>

        /// 마지막 렌더링 시간 변경시

        /// </summary>

        public event EventHandler LastRenderTimeChanged;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 현재 항목 인덱스

        /// </summary>

        private int currentItemIndex;

 

        /// <summary>

        /// 버퍼 그래픽스

        /// </summary>

        private BufferedGraphics bufferedGraphics;

 

        /// <summary>

        /// 버퍼 그래픽스 컨텍스트

        /// </summary>

        private BufferedGraphicsContext bufferedGraphicsContext;

 

        /// <summary>

        /// 항목 컬렉션

        /// </summary>

        private ObservableCollection<string> itemCollection;

 

        /// <summary>

        /// 항목 높이

        /// </summary>

        private int itemHeight;

 

        /// <summary>

        /// 마지막 렌더링 시간

        /// </summary>

        private long lastRenderTime;

 

        /// <summary>

        /// 페인팅 여부

        /// </summary>

        private volatile bool painting;

 

        /// <summary>

        /// 보류 여부

        /// </summary>

        private volatile bool suspended;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 항목 컬렉션 - ItemCollection

 

        /// <summary>

        /// 항목 컬렉션

        /// </summary>

        [Browsable(false)]

        public ObservableCollection<string> ItemCollection

        {

            get

            {

                return this.itemCollection;

            }

        }

 

        #endregion

        #region 항목 높이 - ItemHeight

 

        /// <summary>

        /// 항목 높이

        /// </summary>

        [Category("VirtualListBox")]

        [Browsable(true)]

        [DefaultValue(18)]

        [Description("목록에서 항목 높이 입니다.")]

        public int ItemHeight

        {

            get

            {

                return this.itemHeight;

            }

            set

            {

                if(this.itemHeight != value)

                {

                    this.itemHeight = value;

                    

                    InvalidateBuffer();

                }

            }

        }

 

        #endregion

        #region 마지막 렌더링 시간 - LastRenderTime

 

        /// <summary>

        /// 마지막 렌더링 시간

        /// </summary>

        [Browsable(false)]

        public long LastRenderTime

        {

            get

            {

                return this.lastRenderTime;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - VirtualListBox()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public VirtualListBox()

        {

            DoubleBuffered = true;

 

            SetStyle

            (

                ControlStyles.AllPaintingInWmPaint  |

                ControlStyles.Opaque                |

                ControlStyles.OptimizedDoubleBuffer |

                ControlStyles.UserPaint,

                true

            );

 

            this.currentItemIndex = -1;

 

            this.bufferedGraphics = null;

 

            this.bufferedGraphicsContext = BufferedGraphicsManager.Current;

 

            this.itemCollection = new ObservableCollection<string>();

 

            this.itemHeight = 18;

 

            this.lastRenderTime = 0;

 

            this.painting = false;

 

            this.suspended = false;

 

            this.itemCollection.CollectionChanged += itemCollection_CollectionChanged;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 업데이트 시작하기 - BeginUpdate()

 

        /// <summary>

        /// 업데이트 시작하기

        /// </summary>

        public void BeginUpdate()

        {

            this.suspended = true;

        }

 

        #endregion

        #region 업데이트 종료하기 - EndUpdate()

 

        /// <summary>

        /// 업데이트 종료하기

        /// </summary>

        public void EndUpdate()

        {

            this.suspended = false;

 

            InvalidateBuffer();

        }

 

        #endregion

        #region 항목 인덱스 구하기 - GetItemIndex(point)

 

        /// <summary>

        /// 항목 인덱스 구하기

        /// </summary>

        /// <param name="point">위치</param>

        /// <returns>항목 인덱스</returns>

        public int GetItemIndex(Point point)

        {

            int start = itemHeight > 0 ? (int)Math.Floor((double)Math.Abs(AutoScrollPosition.Y) / itemHeight) : 0;

 

            int visibleItemCount = (int)Math.Ceiling((double)Height / (double)itemHeight);

 

            Rectangle boundRectangle = new Rectangle(0, -(int)(Math.Abs(AutoScrollPosition.Y) % itemHeight), Width, itemHeight);

 

            for(int i = 0; 0 <= start + i && start + i < itemCollection.Count && visibleItemCount > i; i++)

            {

                if(boundRectangle.Contains(point))

                {

                    return start + i;

                }

 

                boundRectangle.Y += itemHeight;

            }

 

            return -1;

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 크기 조정시 처리하기 - OnResize(e)

 

        /// <summary>

        /// 크기 조정시 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnResize(EventArgs e)

        {

            base.OnResize(e);

 

            InvalidateBuffer();

        }

 

        #endregion

        #region 마우스 이동시 처리하기 - OnMouseMove(e)

 

        /// <summary>

        /// 마우스 이동시 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnMouseMove(MouseEventArgs e)

        {

            base.OnMouseMove(e);

 

            int index = GetItemIndex(e.Location);

 

            if(index != currentItemIndex)

            {

                currentItemIndex = index;

 

                InvalidateBuffer();

            }

        }

 

        #endregion

        #region 마우스 휠 처리하기 - OnMouseWheel(e)

 

        /// <summary>

        /// 마우스 휠 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnMouseWheel(MouseEventArgs e)

        {

            base.OnMouseWheel(e);

 

            InvalidateBuffer();

        }

 

        #endregion

        #region 스크롤시 처리하기 - OnScroll(e)

 

        /// <summary>

        /// 스크롤시 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnScroll(ScrollEventArgs e)

        {

            base.OnScroll(e);

 

            InvalidateBuffer();

        }

 

        #endregion

        #region 페인트시 처리하기 - OnPaint(e)

 

        /// <summary>

        /// 페인트시 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnPaint(PaintEventArgs e)

        {

            Stopwatch stopwatch = Stopwatch.StartNew();

 

            if(this.bufferedGraphics == null)

            {

                Rectangle boundRectangle = new Rectangle(0, 0, Width, Height);

 

                this.bufferedGraphics = this.bufferedGraphicsContext.Allocate(e.Graphics, boundRectangle);

 

                using(Brush brush = new SolidBrush(BackColor))

                {

                    this.bufferedGraphics.Graphics.FillRectangle(brush, boundRectangle);

 

                    brush.Dispose();

                }

 

                if(itemCollection.Count > 0 && itemHeight > 0)

                {

                    int start = 0;

 

                    if(itemHeight > 0)

                    {

                        start = (int)Math.Floor((double)Math.Abs(AutoScrollPosition.Y) / itemHeight);

                    }

 

                    StringFormat stringFormat = new StringFormat()

                    {

                        Alignment     = StringAlignment.Near,

                        LineAlignment = StringAlignment.Center

                    };

 

                    int visibleItemCount = (int)Math.Ceiling((double)Height / (double)itemHeight);

 

                    int offset = -(int)(Math.Abs(AutoScrollPosition.Y) % itemHeight);

 

                    for(int i = 0; visibleItemCount > i && itemCollection.Count > start + i && 0 <= start + i; i++)

                    {

                        Brush foregroundBrush = (currentItemIndex == start + i) ? Brushes.White : Brushes.Black;

 

                        Rectangle itemBoundRectangle = new Rectangle(0, i * itemHeight + offset, Width, itemHeight);

 

                        if(currentItemIndex == start + i)

                        {

                            using(Brush brush = new SolidBrush(Color.FromArgb(50, 142, 254)))

                            {

                                bufferedGraphics.Graphics.FillRectangle(brush, itemBoundRectangle);

 

                                brush.Dispose();

                            }

                        }

 

                        itemBoundRectangle.X += 10;

 

                        itemBoundRectangle.Width -= 20;

 

                        bufferedGraphics.Graphics.DrawString

                        (

                            itemCollection[start + i],

                            Font,

                            foregroundBrush,

                            itemBoundRectangle,

                            stringFormat

                        );

                    }

                }

            }

 

            this.bufferedGraphics.Render(e.Graphics);

 

            stopwatch.Stop();

 

            this.lastRenderTime = stopwatch.ElapsedMilliseconds;

 

            if(LastRenderTimeChanged != null)

            {

                LastRenderTimeChanged.Invoke(this, EventArgs.Empty);

            }

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region 항목 컬렉션 컬렉션 변경시 처리하기 - itemCollection_CollectionChanged(sender, e)

 

        /// <summary>

        /// 항목 컬렉션 컬렉션 변경시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void itemCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

        {

            InvalidateBuffer();

        }

 

        #endregion

        #region 버퍼 무효화 하기 - InvalidateBuffer()

 

        /// <summary>

        /// 버퍼 무효화 하기

        /// </summary>

        private void InvalidateBuffer()

        {

            if(this.painting || this.suspended)

            {

                return;

            }

 

            this.painting = true;

 

            if(this.bufferedGraphics != null)

            {

                this.bufferedGraphics.Dispose();

 

                this.bufferedGraphics = null;

            }

 

            AutoScrollMinSize = new Size(0, ItemHeight * itemCollection.Count);

 

            Invalidate();

 

            this.painting = false;

        }

 

        #endregion

    }

}

 

 

MainForm.cs

 

 

using System;

using System.Drawing;

using System.Windows.Forms;

 

namespace TestProject

{

    /// <summary>

    /// 메인 폼

    /// </summary>

    public partial class MainForm : Form

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - MainForm()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainForm()

        {

            InitializeComponent();

 

            #region 가상 리스트 박스를 설정한다.

 

            this.virtualListBox.BackColor = Color.White;

 

            this.virtualListBox.LastRenderTimeChanged += (s, e) => this.timeLabel.Invoke

            (

                (MethodInvoker)delegate {

                

                    this.timeLabel.Text = "렌더링 시간 : " + this.virtualListBox.LastRenderTime + "밀리초";

                }

            );

 

            this.virtualListBox.BeginUpdate();

 

            for(int i = 0; 1000000 > i; i++)

            {

                this.virtualListBox.ItemCollection.Add(string.Format("가상 리스트 박스 항목 {0}", i + 1));

            }

 

            this.virtualListBox.EndUpdate();

 

            #endregion

 

            this.addButton.Click                      += addButton_Click;

            this.itemHeightNumericUpDown.ValueChanged += itemHeightNumericUpDown_ValueChanged;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        //////////////////////////////////////////////////////////////////////////////// Event

 

        #region 추가 버튼 클릭시 처리하기 - addButton_Click(sender, e)

 

        /// <summary>

        /// 추가 버튼 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void addButton_Click(object sender, EventArgs e)

        {

            Random random = new Random();

 

            this.virtualListBox.BeginUpdate();

            

            for(int i = 0; 100000 > i; i++)

            {

                this.virtualListBox.ItemCollection.Add(string.Format("추가 가상 리스트 박스 항목 {0}", random.Next(10000000, 99999999)));

            }

 

            this.virtualListBox.EndUpdate();

        }

 

        #endregion

        #region 항목 높이 숫자 UP/DOWN 값 변경시 처리하기 - itemHeightNumericUpDown_ValueChanged(sender, e)

 

        /// <summary>

        /// 항목 높이 숫자 UP/DOWN 값 변경시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void itemHeightNumericUpDown_ValueChanged(object sender, EventArgs e)

        {

            this.virtualListBox.ItemHeight = (int)this.itemHeightNumericUpDown.Value;

        }

 

        #endregion

    }

}

 

------------------------------------------------------------------------------------------------------------------------

Posted by 사용자 icodebroker
TAG