Tuesday, December 27, 2011

Convert UIElement to Image in WPF

FramewrokElement class Provides a framework-level set of properties, events, and methods for Windows Presentation Foundation (WPF) elements.

FrameworkElement (FE) derives from UIElement.

UIElement  is a base class for WPF core level implementations building on Windows Presentation Foundation (WPF) elements and basic presentation characteristics.

Background

In this article, i am going to explain how to convert UIElement to ImageSource.
RenderTargetBitmap property of System.Windows.Media used to convert visual object into bitmap.
DrawingVisual used to render vector graphics.

Go Through code

I have created TreeView, and binds  parent and child treeitems from Collections.

Step 1 :Create Models.
Area.cs
  public class Area
    {
        public string AreaName { get; set; }
        public string Description { get; set; }        
    } 
This class is for child nodes.

Location.cs
  public class Location
    {
        public string LocationName { get; set; }
        public List<Area> AreaList { get; set; }      
    } 
This class contains properties for Parent tree nodes and child node collections for each parent nodes.

Step 2 :Create ViewModel to bind Collections.

MainViewmodel.cs
  public class MainViewmodel
    {
        private ObservableCollection<Location> _locations;
        public ObservableCollection<Location> Locations
        {
            get { return _locations; }
            set { _locations = value; }
        }
        public MainViewmodel()
        {
            Locations = new ObservableCollection<Location>();
            BindLocationList();
        }
        List<Area> GetAreaList()
        {
            List<Area> returnList = new List<Area>();
           returnList.Add(new Area() { AreaName = "Kankaria", 
                      Description = "The state has witnessed all-round progress in every field. });
           returnList.Add(new Area() { AreaName = "Swapnasrusthi", 
                      Description = "The state has witnessed all-round progress in every field. });
           returnList.Add(new Area() { AreaName = "Scien city", 
                      Description = "The state has witnessed all-round progress in every field.});

           returnList.Add(new Area() { AreaName = "Lal quila", 
                      Description = "The state has witnessed all-round progress in every field. });
           returnList.Add(new Area() { AreaName = "lake view", 
                      Description = "The state has witnessed all-round progress in every field. });
           returnList.Add(new Area() { AreaName = "Shilalekh", 
                      Description = "The state has witnessed all-round progress in every field. }); 
           returnList.Add(new Area() { AreaName = "Girnar", 
                      Description = "The state has witnessed all-round progress in every field. It has
           returnList.Add(new Area() { AreaName = "Zoo", 
                      Description = "The state has witnessed all-round progress in every field. });
           returnList.Add(new Area() { AreaName = "chandani chowk", 
                      Description = "The state has witnessed all-round progress in every field.});
           returnList.Add(new Area() { AreaName = "Akshradham", 
                      Description = "The state has witnessed all-round progress in every field. }); 
           returnList.Add(new Area() { AreaName = "Qutub minar", 
                      Description = "The state has witnessed all-round progress in every field. }); 
           return returnList;
        }
        void BindLocationList()
        {
            List<Area> areaList = GetAreaList();
            List<Location> locationList = new List<Location>();
            locationList.Add(new Location() { LocationName = "Ahmedabad", AreaList = areaList.Take(3).ToList() });
            locationList.Add(new Location() { LocationName = "Jamnagar", AreaList = areaList.Skip(3).Take(2).ToList() });
            locationList.Add(new Location() { LocationName = "Junagadh", AreaList = areaList.Skip(5).Take(3).ToList() });
            locationList.Add(new Location() { LocationName = "Delhi", AreaList = areaList.Skip(8).Take(3).ToList() });
            Locations = new ObservableCollection<Location>(locationList);
        }
    } 
In above code, i have created ObservableCollection of Locations which provides notification on add/edit items.
bind locations and areas within location.

Step 3 : Create Templates for TreeView.

MainWindow.xaml
 <DataTemplate x:Key="AreaDataTemplate">
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding AreaName}"
                           VerticalAlignment="Center"
                           Margin="5"
                           Style="{StaticResource TextBlockStyle2}" />
                <TextBlock Text="{Binding Description}"
                           Width="300"
                           Style="{StaticResource TextBlockStyle2}" />
            </StackPanel>
        </DataTemplate> 
 
DataTemplate provides you with great flexibility to define the presentation of your data.
DataTemplate  give you a very flexible and powerful solution to replace the visual appearance of a data item in a control like ListBox, ComboBox or ListView.
Bind child tree items and present layout of child nodes in treeview.
 <HierarchicalDataTemplate x:Key="LocationTemplate"
                                  ItemsSource="{Binding AreaList}"
                                  ItemTemplate="{StaticResource AreaDataTemplate}">
            <TextBlock Text="{Binding LocationName}"
                       Margin="5 5 10 10"
                       Style="{StaticResource TextBlockStyle}" />
        </HierarchicalDataTemplate>

HierarchicalDataTemplate Represents a DataTemplat  that supports HeaderedItemControl, such as TreeViewItems.  
Bind ItmesSource to AreaList(for child TreeViewItems) and ItemTemplate to child DataTemplate resource key.

Step 4 : Place TreeView into Window.
 
MainWindow.xaml
 <Grid Grid.Row="1"
                      MaxHeight="250">
                    <TreeView ScrollViewer.CanContentScroll="True"
                              BorderThickness="0"
                              Background="#FFF"
                              ItemsSource="{Binding Locations}"
                              ItemTemplate="{DynamicResource LocationTemplate}"
                              x:Name="LocationTreeView" />
                </Grid> 
 
I have placed TreeView within Grid and sets its CanContentScroll property to true, which adds scroll when treenode expands if height exceeds 250.
Bind ItemsSource with Locations and set ItemTemplate to HierarchicalDataTemplate.

Step 5 : Place Button, which Excutes code to convert UIElement to ImageSource.
Grid Grid.Row="1">
                <Button Content="Generate Image"
                        x:Name="convert"
                        Width="100"
                        Grid.Row="1"
                        Height="25"
                        Click="convert_Click" />
            </Grid>

Step 6 : Place Image into window to set Source from UIElement.
<Grid Grid.Row="2"
                  MaxWidth="400"
                  MinHeight="400">
                <Image x:Name="displayImage"
                       Grid.Row="2"
                       Stretch="Fill"
                       Margin="0 0 0 30" />
            </Grid>

Step 7 : Create Method to find UIElement from parent UIElement.
   private ScrollViewer GetTemplateChildByName(DependencyObject parent)
        {
            int childnum = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childnum; i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);
                if (child is ScrollViewer)
                {
                    return child as ScrollViewer;
                }
                else
                {
                    var s = GetTemplateChildByName(child);
                    if (s != null)
                        return s;
                }
            }
            return null;
        } 
 
This method find UIElement within parent UIElement. it will find child element using GetChild method of VisualTreeHelper.
VisualTreeHelper provides utility methods that perform common tasks involving nodes in a visual tree.
VisualTreeHelper.GetChild method Returns the child visual object from the specified collection index within a specified parent.
I have write this method for find ScrollViewer within TreeView.

Step 8 : Write code to Convert ImageSource from UIElement.
private void convert_Click(object sender, RoutedEventArgs e)
        {
            ScrollViewer scroll = GetTemplateChildByName(LocationTreeView);
            if (scroll != null)
            {
                ItemsPresenter item = scroll.Content as ItemsPresenter;
                double width = item.ActualWidth;
                double height = item.ActualHeight;
                RenderTargetBitmap bmpCopied =  
       new RenderTargetBitmap((int)Math.Round(width), (int)Math.Round(height), 100, 100, PixelFormats.Default);
                DrawingVisual drawingVisual = new DrawingVisual();
                using (DrawingContext drawingContext = drawingVisual.RenderOpen())
                {
                    VisualBrush visualBrush = new VisualBrush(item);
                    drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), new Size(width, height)));
                }
                bmpCopied.Render(drawingVisual);
                displayImage.Source = bmpCopied;
            }
        }

In Button click event handler, i wrote code to convert TreeView content into ImageSource.

First find ScrollViewer within TreeView.

after that get ItemPresenter from ScrollViewer Content, because we need to convert full expandable TreeView into ImageSource.

if you get ActualHeight of TreeView/ScrollViewer it will gives only visible area height, so it will convert only visible area to image not scroll area.

To convert hided scoll area as well as visible area, you have to get ScrollViewer Content as a ItemPresenter and Draw Image using ItemPresenter element and its ActualWidth/ActualHeight property.

Conclusion

when you work with ScrollViewer,
Always use its content area in width and height calculation.
it will gives ActualHeight/ActualWidth of content within ScrollViewer.

 You Can Download Source From : Convert UIElement to ImageSource

No comments:

Post a Comment