11using DirectN ;
2+ using System . ComponentModel ;
23using System . Runtime . InteropServices ;
34
45namespace DXControls ;
56
67public class DXControl : Control
78{
89 private float _dpi = 96.0f ;
10+ private readonly VerticalBlankTicker _ticker = new ( ) ;
11+ private bool _enableAnimation = true ;
12+
13+ private int _currentFps = 0 ;
14+ private int _lastFps = 0 ;
15+ private DateTime _lastFpsUpdate = DateTime . UtcNow ;
16+
917
1018 protected IComObject < ID2D1Factory > Direct2DFactory ;
1119 protected IComObject < IDWriteFactory > DWriteFactory ;
1220 protected ID2D1HwndRenderTarget ? RenderTarget ;
1321 protected ID2D1DeviceContext ? DeviceContext ;
14- protected DXGraphics ? DGraphics ;
22+ protected DXGraphics ? D2Graphics ;
23+
24+ /// <summary>
25+ /// Request to update frame by <see cref="OnFrame"/> event.
26+ /// </summary>
27+ protected bool RequestUpdateFrame { get ; set ; } = true ;
28+
29+ /// <summary>
30+ /// Enable FPS measurement.
31+ /// </summary>
32+ protected bool CheckFPS { get ; set ; } = false ;
33+
1534
35+ public event EventHandler < RenderEventArgs > ? RenderDX ;
36+ public event EventHandler < PaintEventArgs > ? RenderGDIPlus ;
37+ public event EventHandler < FrameEventArgs > ? Frame ;
1638
39+
40+ /// <summary>
41+ /// Gets, sets the DPI for drawing when using <see cref="DXGraphics"/>.
42+ /// </summary>
1743 public float BaseDpi
1844 {
1945 get => _dpi ;
@@ -26,9 +52,48 @@ public float BaseDpi
2652 }
2753 }
2854
55+
56+ /// <summary>
57+ /// Gets, sets the DPI for text drawing when using <see cref="DXGraphics"/>.
58+ /// </summary>
2959 public float TextDpi { get ; set ; } = 96.0f ;
3060
3161
62+ /// <summary>
63+ /// Gets FPS info when the <see cref="CheckFPS"/> is set to <c>true</c>.
64+ /// </summary>
65+ public int FPS => _lastFps ;
66+
67+
68+ /// <summary>
69+ /// Enables animation support for the control.
70+ /// </summary>
71+ [ Category ( "Animation" ) ]
72+ [ DefaultValue ( true ) ]
73+ public bool EnableAnimation
74+ {
75+ get => _enableAnimation ;
76+ set
77+ {
78+ _enableAnimation = value ;
79+
80+ if ( ! _enableAnimation )
81+ {
82+ if ( _ticker . IsRunning ) _ticker . Stop ( 1000 ) ;
83+ }
84+ else
85+ {
86+ if ( ! _ticker . IsRunning )
87+ {
88+ _ticker . ResetTicks ( ) ;
89+ _ticker . Start ( ) ;
90+ }
91+ }
92+ }
93+ }
94+
95+
96+
3297 public DXControl ( )
3398 {
3499 SetStyle ( ControlStyles . OptimizedDoubleBuffer | ControlStyles . SupportsTransparentBackColor , true ) ;
@@ -47,10 +112,14 @@ protected override void CreateHandle()
47112 if ( DesignMode ) return ;
48113
49114 DoubleBuffered = false ;
50-
51115 CreateGraphicsResources ( ) ;
116+
117+
118+ _ticker . Tick += Ticker_Tick ;
119+ _ticker . Start ( ) ;
52120 }
53121
122+
54123 protected override void WndProc ( ref Message m )
55124 {
56125 const int WM_ERASEBKGND = 0x0014 ;
@@ -90,15 +159,17 @@ protected override void WndProc(ref Message m)
90159 }
91160 }
92161
162+
93163 protected override void Dispose ( bool disposing )
94164 {
95- base . Dispose ( disposing ) ;
165+ _ticker . Stop ( 1000 ) ;
166+ _ticker . Tick -= Ticker_Tick ;
96167
97168 Direct2DFactory . Dispose ( ) ;
98169 DWriteFactory . Dispose ( ) ;
99170
100- DGraphics ? . Dispose ( ) ;
101- DGraphics = null ;
171+ D2Graphics ? . Dispose ( ) ;
172+ D2Graphics = null ;
102173
103174 if ( DeviceContext != null )
104175 {
@@ -111,6 +182,10 @@ protected override void Dispose(bool disposing)
111182 Marshal . ReleaseComObject ( RenderTarget ) ;
112183 RenderTarget = null ;
113184 }
185+
186+ GC . Collect ( ) ;
187+
188+ base . Dispose ( disposing ) ;
114189 }
115190
116191
@@ -133,6 +208,10 @@ protected override void OnSizeChanged(EventArgs e)
133208 }
134209
135210
211+ /// <summary>
212+ /// Control background is painted in <see cref="OnPaint(PaintEventArgs)"/>.
213+ /// </summary>
214+ /// <param name="e"></param>
136215 protected override void OnPaintBackground ( PaintEventArgs e )
137216 {
138217 if ( DesignMode )
@@ -142,7 +221,9 @@ protected override void OnPaintBackground(PaintEventArgs e)
142221 }
143222
144223
145- [ Obsolete ( "Use 'OnRender' to paint the control." , true ) ]
224+ /// <summary>
225+ /// Use <see cref="OnRender(DXGraphics)"/> or <see cref="OnRender(Graphics)"/> to paint.
226+ /// </summary>
146227 protected override void OnPaint ( PaintEventArgs e )
147228 {
148229 if ( DesignMode )
@@ -158,17 +239,38 @@ protected override void OnPaint(PaintEventArgs e)
158239
159240 // make sure the
160241 CreateGraphicsResources ( ) ;
161- if ( DeviceContext == null || DGraphics == null ) return ;
242+ if ( DeviceContext == null || D2Graphics == null ) return ;
162243
163244
245+ // Use Direct2D graphics to draw
164246 // start drawing session
165247 var bgColor = BackColor . Equals ( Color . Transparent ) ? Parent . BackColor : BackColor ;
166- DGraphics . BeginDraw ( new ( bgColor . ToArgb ( ) ) ) ;
167-
168- OnRender ( DGraphics ) ;
248+ D2Graphics . BeginDraw ( new ( bgColor . ToArgb ( ) ) ) ;
249+ OnRender ( D2Graphics ) ;
169250
170251 // end drawing session
171- DGraphics . EndDraw ( ) ;
252+ D2Graphics . EndDraw ( ) ;
253+
254+
255+ // Use GDPI+ to draw
256+ e . Graphics . Clear ( bgColor ) ;
257+ OnRender ( e . Graphics ) ;
258+
259+
260+ // calculate FPS
261+ if ( CheckFPS )
262+ {
263+ if ( _lastFpsUpdate . Second != DateTime . UtcNow . Second )
264+ {
265+ _lastFps = _currentFps ;
266+ _currentFps = 0 ;
267+ _lastFpsUpdate = DateTime . UtcNow ;
268+ }
269+ else
270+ {
271+ _currentFps ++ ;
272+ }
273+ }
172274 }
173275
174276
@@ -178,13 +280,31 @@ protected override void OnPaint(PaintEventArgs e)
178280 #region Virtual functions
179281
180282 /// <summary>
181- /// Paints the control using <see cref="DXGraphics"/>.
283+ /// Paints the control using Direct2D <see cref="DXGraphics"/>.
182284 /// </summary>
183285 protected virtual void OnRender ( DXGraphics g )
184286 {
185-
287+ RenderDX ? . Invoke ( this , new RenderEventArgs ( g ) ) ;
288+ }
289+
290+
291+ /// <summary>
292+ /// Paints the control using GDI+ <see cref="Graphics"/>.
293+ /// </summary>
294+ protected virtual void OnRender ( Graphics g )
295+ {
296+ RenderGDIPlus ? . Invoke ( this , new ( g , ClientRectangle ) ) ;
186297 }
187298
299+
300+ /// <summary>
301+ /// Process animation logic when frame changes
302+ /// </summary>
303+ protected virtual void OnFrame ( FrameEventArgs e )
304+ {
305+ Frame ? . Invoke ( this , e ) ;
306+ }
307+
188308 #endregion
189309
190310
@@ -211,8 +331,17 @@ private void CreateGraphicsResources()
211331
212332
213333 DeviceContext = ( ID2D1DeviceContext ) RenderTarget ;
334+ D2Graphics = new DXGraphics ( DeviceContext , DWriteFactory ) ;
335+ }
336+ }
337+
214338
215- DGraphics = new ( DeviceContext , DWriteFactory ) ;
339+ private void Ticker_Tick ( object ? sender , VerticalBlankTickerEventArgs e )
340+ {
341+ if ( EnableAnimation && RequestUpdateFrame )
342+ {
343+ OnFrame ( new ( e . Ticks ) ) ;
344+ Invalidate ( ) ;
216345 }
217346 }
218347
0 commit comments