There are many scenarios in Windows application development where you need to dynamically get the image displayed by a control, i.e., the control is converted to an image, which is used for displaying other interfaces, transmitting image data streams, saving it as a local image, and other purposes.
The following are some of the implementation methods and the main use scenarios.
RenderTargetBitmap
Control to image BitmapImage/BitmapSource, in WPF you can use RenderTargetBitmap to get the image of the captured control.
Below we show how easy and fast it is to get the control image:
1 private void CaptureButton_OnClick(object sender, RoutedEventArgs e) 2 { 3 var dpi = GetAppStartDpi(); 4 var bitmapSource = ToImageSource(Grid1, , , ); 5 = bitmapSource; 6 } 7 /// <summary> 8 /// Visual to Picture 9 /// </summary> 10 public static BitmapSource ToImageSource(Visual visual, Size size, double dpiX, double dpiY) 11 { 12 var validSize = > 0 && > 0; 13 if (!validSize) throw new ArgumentException($"Invalid {nameof(size)} value: ${},${}"); 14 if (() > 0.0001 && () > 0.0001) 15 { 16 RenderTargetBitmap bitmap = new RenderTargetBitmap((int)( * dpiX), (int)( * dpiY), dpiX * 96, dpiY * 96, PixelFormats.Pbgra32); 17 (visual); 18 return bitmap; 19 } 20 return new BitmapImage(); 21 }
Get the DPI of the screen where the current window is located and use the rendered size of the control to capture the rendered image of the specified control. By capturing the image BitmapSource, you can assign the bitmap to the Source property of the Image to display.
DPI can be obtained by referring to theC# Get current screen DPI - the Tang, Song, Yuan, Ming and Qing dynasties2188 - blogosphere ()
The above method gets a BitmapSource, which is an abstract base class for WPF bitmaps and inherits from ImageSource, so it can be used directly as the image source for WPF controls such as Image.RenderTargetBitmap and BitmapImage are derived implementations of BitmapSource. RenderTargetBitmap and BitmapImage are derived implementations of
RenderTargetBitmap here is used to render Visual objects to generate bitmaps, RenderTargetBitmap it can be used for splicing, merging (upper and lower layers superimposed), scaling the image, etc. BitmapImage is mainly used to load bitmaps from files, URLs and streams.
And capture the return of the base class BitmapSource can be used for general-purpose bitmap some operations (such as rendering, into stream data, save), BitmapSource if you need to be converted into a BitmapImage can be supported to support support a higher level of the image loading function and delayed loading mechanism, you can press the following operation:
1 /// <summary> 2 /// WPF Bitmap Conversion 3 /// </summary> 4 private static BitmapImage ToBitmapImage(BitmapSource bitmap,Size size,double dpiX,double dpiY) 5 { 6 MemoryStream memoryStream = new MemoryStream(); 7 BitmapEncoder encoder = new PngBitmapEncoder(); 8 ((bitmap)); 9 (memoryStream); 10 (0L, ); 11 12 BitmapImage bitmapImage = new BitmapImage(); 13 (); 14 = (int)( * dpiX); 15 = (int)( * dpiY); 16 = memoryStream; 17 (); 18 (); 19 return bitmapImage; 20 }
Here the Png encoder is chosen to convert the bitmapSource to an image stream first and then decode it to a BitmapImage.
The image encoder has a variety of uses, the above is to convert the stream to an in-memory stream, or to a file stream to save a local file:
1 var encoder = new PngBitmapEncoder(); 2 ((bitmapSource)); 3 using Stream stream = (imagePath); 4 (stream);
Going back to the control image capture, the above action is the scene after the interface controls have been rendered. If the control is not loaded, you need to update the layout under:
1 //Those not loaded into the visual tree are laid out at the specified size 2 //Display by size, if the design width and height is larger than size then crop by sie, if the design width is smaller than size then zoom in by size. 3 (size); 4 (new Rect(size));
There are also scenarios where the control is unsure of its exact dimensions and simply wants to capture the image, then the code is organized as follows:
1 public BitmapSource ToImageSource(Visual visual, Size size = default) 2 { 3 if (!(visual is FrameworkElement element)) 4 { 5 return null; 6 } 7 if (!) 8 { 9 if (size == default) 10 { 11 //Calculate the rendering size of an element 12 (new Size(double.PositiveInfinity, double.PositiveInfinity)); 13 (new Rect(new Point(), )); 14 size = ; 15 } 16 else 17 { 18 //Those not loaded into the visual tree are laid out at the specified size 19 //Display by size, if the design width and height is larger than size then crop by sie, if the design width is smaller than size then zoom in by size. 20 (size); 21 (new Rect(size)); 22 } 23 } 24 else if (size == default) 25 { 26 Rect rect = (visual); 27 if (()) 28 { 29 return null; 30 } 31 size = ; 32 } 33 34 var dpi = GetAppStartDpi(); 35 return ToImageSource(visual, size, , ); 36 }
When the control is not loaded, you can use DesiredSize to temporarily replace the operation, this type of solution to get the image width and height ratio may be less accurate. When the control has been loaded, you can use Bounds to get the rectangular area of the coordinates of the set of sub-elements of the visual tree.
kybs00/VisualImageDemo: RenderTargetBitmap to get control image ()
So controls to BitmapSource, save, etc., you can use RenderTargetBitmap to achieve the
VisualBrush
If the control is only displayed synchronously on other interfaces within the application, you don't need a RenderTargetBitmap, you can use VisualBrush directly.
For details, you can see the official websiteVisualBrush Class () | Microsoft Learn, here is a simple DEMO:
1 <Window x:Class="" 2 xmlns="/winfx/2006/xaml/presentation" 3 xmlns:x="/winfx/2006/xaml" 4 xmlns:d="/expression/blend/2008" 5 xmlns:mc="/markup-compatibility/2006" 6 xmlns:local="clr-namespace:VisualBrushDemo" 7 mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> 8 <Grid> 9 <> 10 <ColumnDefinition Width="*"/> 11 <ColumnDefinition Width="10"/> 12 <ColumnDefinition/> 13 </> 14 <Canvas x:Name="Grid1" Background="BlueViolet"> 15 <TextBlock x:Name="TestTextBlock" Text="Screenshot Test" VerticalAlignment="Center" HorizontalAlignment="Center" 16 Width="100" Height="30" Background="Red" TextAlignment="Center" LineHeight="30" Padding="0 6 0 0" 17 MouseDown="TestTextBlock_OnMouseDown" 18 MouseMove="TestTextBlock_OnMouseMove" 19 MouseUp="TestTextBlock_OnMouseUp"/> 20 </Canvas> 21 <Grid x:Name="Grid2" ="2"> 22 <> 23 <VisualBrush Stretch="UniformToFill" 24 AlignmentX="Center" AlignmentY="Center" 25 Visual="{Binding ElementName=Grid1}"/> 26 </> 27 </Grid> 28 </Grid> 29 </Window>
CS Code:
1 private bool _isDown; 2 private Point _relativeToBlockPosition; 3 private void TestTextBlock_OnMouseDown(object sender, MouseButtonEventArgs e) 4 { 5 _isDown = true; 6 _relativeToBlockPosition = (TestTextBlock); 7 (); 8 } 9 10 private void TestTextBlock_OnMouseMove(object sender, MouseEventArgs e) 11 { 12 if (_isDown) 13 { 14 var position = (Grid1); 15 (TestTextBlock, - _relativeToBlockPosition.Y); 16 (TestTextBlock, - _relativeToBlockPosition.X); 17 } 18 } 19 20 private void TestTextBlock_OnMouseUp(object sender, MouseButtonEventArgs e) 21 { 22 (); 23 _isDown = false; 24 }
The left side operates a control movement and the right side area dynamically synchronizes to show the left side vision.
You can directly bind the specified control, once binding, subsequent synchronization interface changes.
Below is a portion of the code where we see that there is listening within VisualBrush for changes in the content of the element:
1 // We need 2 ways of initiating layout on the VisualBrush root. 2 // 1. We add a handler such that when the layout is done for the 3 // main tree and LayoutUpdated is fired, then we do layout for the 4 // VisualBrush tree. 5 // However, this can fail in the case where the main tree is composed 6 // of just Visuals and never does layout nor fires LayoutUpdated. So 7 // we also need the following approach. 8 // 2. We do a BeginInvoke to start layout on the Visual. This approach 9 // alone, also falls short in the scenario where if we are already in 10 // () then we will do layout (for main tree), then look 11 // at Loaded callbacks, then render, and then finally the Dispather will 12 // fire us for layout. So during loaded callbacks we would not have done 13 // layout on the VisualBrush tree. 14 // 15 // Depending upon which of the two layout passes comes first, we cancel 16 // the other layout pass. 17 += OnLayoutUpdated; 18 _DispatcherLayoutResult = ( 19 , 20 new DispatcherOperationCallback(LayoutCallback), 21 element); 22 _pendingLayout = true;
While displaying the bound elements, VisualBrush internally is giving the image to the rendering context via the element method:
1 RenderContext rc = new RenderContext(); 2 (channel, ); 3 (rc, 0);
This type of VisualBrush program is suitable for creating preview displays, such as print previews, PPT page preview lists, etc.