66using System . Diagnostics ;
77using System . Drawing ;
88using System . Drawing . Imaging ;
9+ using System . Reflection ;
910using System . Windows . Forms ;
1011using SIL . FieldWorks . Common . Framework . DetailControls ;
1112using SIL . FieldWorks . Common . RootSites ;
@@ -19,7 +20,10 @@ namespace SIL.FieldWorks.Common.RenderVerification
1920 /// splitters, section headers) but produces black rectangles for Views engine content
2021 /// inside <see cref="ViewSlice"/> controls. The fix is a two-pass approach:
2122 /// <list type="number">
22- /// <item>Capture the entire DataTree via <c>DrawToBitmap</c> (gets all WinForms chrome)</item>
23+ /// <item>Capture each slice locally via <c>DrawToBitmap</c> and composite it into the
24+ /// full bitmap (gets per-slice WinForms chrome without relying on giant control coordinates)</item>
25+ /// <item>Repaint DataTree-owned separator rules directly onto the bitmap, since those
26+ /// thin gray lines are drawn by the parent control rather than by each slice</item>
2327 /// <item>For each <see cref="ViewSlice"/>, render its <c>RootBox</c> via
2428 /// <c>VwDrawRootBuffered</c> into the correct region, overlaying the black rectangles</item>
2529 /// </list>
@@ -81,21 +85,22 @@ public static Bitmap CaptureDataTree(DataTree dataTree)
8185 dataTree . ClientSize = new Size ( width , totalHeight ) ;
8286 dataTree . PerformLayout ( ) ;
8387
84- // Pass 1: Capture WinForms chrome via DrawToBitmap .
85- // Pre-fill with white so areas beyond slice content (and the
86- // DataTree's grey SystemColors.Control background) render as
87- // white, producing deterministic output regardless of system
88- // theme or control background color .
88+ // Pass 1: Capture WinForms chrome slice-by-slice .
89+ // A single DrawToBitmap over a very tall DataTree can drop left-side
90+ // labels and separator lines for slices near the bottom of the image.
91+ // Compositing each slice locally avoids that large-coordinate WinForms
92+ // capture failure while preserving the existing root-box overlay pass .
8993 var bitmap = new Bitmap ( width , totalHeight , PixelFormat . Format32bppArgb ) ;
90- using ( var g = Graphics . FromImage ( bitmap ) )
91- {
92- g . Clear ( Color . White ) ;
93- }
94- dataTree . DrawToBitmap ( bitmap , new Rectangle ( 0 , 0 , width , totalHeight ) ) ;
94+ CaptureSliceChrome ( dataTree , bitmap ) ;
9595
9696 // Pass 2: Overlay Views engine content for each ViewSlice
9797 OverlayViewSliceContent ( dataTree , bitmap ) ;
9898
99+ // Pass 3: Repaint parent-drawn separator rules.
100+ // These thin gray lines come from DataTree.OnPaint rather than from the
101+ // slice controls, so the slice-by-slice pass above will not capture them.
102+ PaintDataTreeSeparators ( dataTree , bitmap , width , totalHeight ) ;
103+
99104 return bitmap ;
100105 }
101106 finally
@@ -193,6 +198,90 @@ private static void EnsureAllSlicesInitialized(DataTree dataTree)
193198 dataTree . PerformLayout ( ) ;
194199 }
195200
201+ /// <summary>
202+ /// Captures WinForms chrome one slice at a time and composites the result into the
203+ /// final bitmap. This avoids whole-control <c>DrawToBitmap</c> failures when slice
204+ /// coordinates become very large in tall DataTree captures.
205+ /// </summary>
206+ private static void CaptureSliceChrome ( DataTree dataTree , Bitmap bitmap )
207+ {
208+ using ( var mainGraphics = Graphics . FromImage ( bitmap ) )
209+ {
210+ mainGraphics . Clear ( Color . White ) ;
211+
212+ if ( dataTree . Slices == null )
213+ return ;
214+
215+ foreach ( Slice slice in dataTree . Slices )
216+ {
217+ if ( slice == null )
218+ continue ;
219+
220+ try
221+ {
222+ CaptureSingleSliceChrome ( slice , dataTree , mainGraphics ) ;
223+ }
224+ catch ( Exception ex )
225+ {
226+ Trace . TraceWarning (
227+ $ "[CompositeViewCapture] Failed to capture slice chrome '{ slice . Label } ': { ex . Message } ") ;
228+ }
229+ }
230+ }
231+ }
232+
233+ private static void CaptureSingleSliceChrome ( Slice slice , DataTree dataTree , Graphics mainGraphics )
234+ {
235+ var sliceRect = GetControlRectRelativeTo ( slice , dataTree ) ;
236+ if ( sliceRect . Width <= 0 || sliceRect . Height <= 0 )
237+ return ;
238+
239+ using ( var sliceBitmap = new Bitmap ( sliceRect . Width , sliceRect . Height , PixelFormat . Format32bppArgb ) )
240+ {
241+ using ( var sliceGraphics = Graphics . FromImage ( sliceBitmap ) )
242+ {
243+ sliceGraphics . Clear ( Color . White ) ;
244+ }
245+
246+ slice . DrawToBitmap ( sliceBitmap , new Rectangle ( 0 , 0 , sliceRect . Width , sliceRect . Height ) ) ;
247+ mainGraphics . DrawImageUnscaled ( sliceBitmap , sliceRect . Location ) ;
248+ }
249+ }
250+
251+ private static void PaintDataTreeSeparators ( DataTree dataTree , Bitmap bitmap , int width , int totalHeight )
252+ {
253+ const BindingFlags privateInstance = BindingFlags . Instance | BindingFlags . NonPublic ;
254+ var paintLinesMethod = typeof ( DataTree ) . GetMethod ( "PaintLinesBetweenSlices" , privateInstance ) ;
255+ if ( paintLinesMethod == null )
256+ {
257+ Trace . TraceWarning ( "[CompositeViewCapture] Could not locate DataTree.PaintLinesBetweenSlices." ) ;
258+ return ;
259+ }
260+
261+ using ( var graphics = Graphics . FromImage ( bitmap ) )
262+ {
263+ try
264+ {
265+ paintLinesMethod . Invoke ( dataTree , new object [ ]
266+ {
267+ graphics ,
268+ new Rectangle ( 0 , 0 , width , totalHeight ) ,
269+ width ,
270+ } ) ;
271+ }
272+ catch ( TargetInvocationException ex )
273+ {
274+ Trace . TraceWarning (
275+ $ "[CompositeViewCapture] Failed to paint DataTree separators: { ex . InnerException ? . Message ?? ex . Message } ") ;
276+ }
277+ catch ( Exception ex )
278+ {
279+ Trace . TraceWarning (
280+ $ "[CompositeViewCapture] Failed to paint DataTree separators: { ex . Message } ") ;
281+ }
282+ }
283+ }
284+
196285 /// <summary>
197286 /// Iterates all ViewSlice descendants and renders their RootBox content
198287 /// via VwDrawRootBuffered into the correct region of the bitmap.
0 commit comments