728x90
728x170
▶ 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
그리드형(광고전용)
'C# > WinForm' 카테고리의 다른 글
[C#/WINFORM] TreeView 클래스 : 특정 레벨까지 노드 확장하기 (0) | 2019.09.15 |
---|---|
[C#/WINFORM] UserControl 클래스 : 환형 진행바 사용하기 (0) | 2019.08.31 |
[C#/WINFORM] Bitmap 클래스 : 투명 배경 비트맵 구하기 (0) | 2019.08.30 |
[C#/WINFORM] Form 클래스 : KeyPreview 속성을 사용해 단축키 설정하기 (0) | 2019.08.30 |
[C#/WINFORM] DesignerSerializationVisibilityAttribute 클래스 : 디자이너 모드에서 속성 코드 자동 생성 막기 (0) | 2019.08.26 |
[C#/WINFORM] Process 클래스 : GetProcessesByName 정적 메소드를 사용해 프로그램 중복 실행 여부 구하기 (0) | 2019.08.16 |
[C#/WINFORM] TextBox 클래스 : ScrollToCaret 메소드를 사용해 마지막 라인으로 스크롤하기 (0) | 2019.08.16 |
[C#/WINFORM] 투명 배경 스플래시 이미지 사용하기 (0) | 2019.08.15 |
[C#/WINFORM] InstalledFontCollection 클래스 : 영문 폰트명 리스트 구하기 (0) | 2019.08.02 |
[C#/WINFORM] 타원과 원 내부 마우스 위치 여부 구하기 (0) | 2019.07.14 |