첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestProject.zip
0.01MB

▶ 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
    }
}

 

728x90

 

▶ 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
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요