ProConcepts Layouts

Uma Harano edited this page Jun 26, 2018 · 8 revisions

The layout functionality in ArcGIS Pro is delivered through the ArcGIS.Desktop.Layouts assembly. This assembly provides classes and members that support managing layouts, layout elements and working with layout views. This includes creating new layouts and layout elements, modifying existing elements, managing selections, and layout view control and navigation.

  • ArcGIS.Desktop.Core.dll

  • ArcGIS.Desktop.Layouts.dll

Language:      C# and Visual Basic
Subject:       Layouts
Contributor:   ArcGIS Pro SDK Team <arcgisprosdk@esri.com>
Organization:  Esri, http://www.esri.com
Date:          6/7/2018
ArcGIS Pro:    2.2
Visual Studio: 2015, 2017

In this topic

Layout class

The Layout class represents a page layout in a project and provides access to basic layout properties, including page information, access to elements, and export methods. When working with layouts, one of the primary steps is to either reference an existing layout or create a new layout. A project can contain zero to many layouts. Within the application, existing layouts appear in the Catalog pane as individual project items, or as open views. Layout views are associated with a single layout. You can have multiple views for a single layout. For a project, only one view can be active at a time. The active view may, or may not be a layout view.

Reference an existing layout

There are a couple of ways to reference an existing layout in a project. The easiest way is simply to reference a layout view that is already open and active in the application. The LayoutView class has an Active property that returns the current layout view if one exists. If there is indeed a active layout view, then the Layout property can be used to return the associated Layout.

//Reference a layout associated with an active layout view
LayoutView activeLytView = LayoutView.Active;
if (activeLytView != null)
{
  Layout lyt = activeLytView.Layout;
}

A layout view may not always be active so another way to reference an existing layout is to return a layout that is associated with a LayoutProjectItem located in the Catalog pane. If the item exists, then the GetLayout method can be used to return the associated Layout.

//Reference a layout associated with a layout project item by name
LayoutProjectItem lytItem = Project.Current.GetItems<LayoutProjectItem>().FirstOrDefault(item => item.Name.Equals("Some Layout Name"));

if (lytItem != null)
{
  //Get the layout associated with the layoutitem
  Layout lyt = await QueuedTask.Run(() => layoutItem.GetLayout());
}

Create a new layout

When you create a new layout using the SDK with the LayoutFactory.Instance.CreateLayout method, it creates a layout project item that automatically appears in the Contents pane but it does not automatically open the layout in a new layout view. This is a separate operation and will be covered in the LayoutView class section.

//Create a new layout using minimum properties and sets its name
QueuedTask.Run<Layout>(() =>
{
  newLyt = LayoutFactory.Instance.CreateLayout(8.5, 11, LinearUnit.Inches);
  newLyt.SetName("New Layout Name"); 
});

Layout Elements

After having a reference to a layout, the next logical step is to either modify existing page layout elements or create new elements. The SDK managed API provides access to the common members for each element type but understand that many more properties are available via the Cartographic Information Model (CIM) associated with each element. Several examples will be provided in the CIM Elements section. This section will focus on the managed API components only.

Reference an existing element

The easiest way to reference a layout element is to use the FindElement method on the Layout class. All layout elements within a single layout are forced to have a unique name so that they can be uniquely searched. It is highly recommended to give each element a meaningful name to simplify referencing the element. The FindElement method will search for all elements in the layout Contents pane even if the elements are organized in group elements.

//Find an element anywhere in the layout
QueuedTask.Run(() =>
{
  Layout lyt = layoutItem.GetLayout();
  if (lyt != null)
  {
    Element rect = lyt.FindElement("Rectangle") as Element;
  }
});

You can also search for an element in a collection based on how elements are organized in the layout Contents pane. For example, the Elements property called from the Layout class will only return root level elements. If the Elements property is called from a referenced GroupElement, then only the root level elements in that group are returned.

//Find an element within a specific Elements collection
QueuedTask.Run(() =>
{
  Layout lyt = layoutItem.GetLayout();
  if (lyt != null)
  {
    Element rect = lyt.Elements.FirstOrDefault(item => item.Name.Equals("Rectangle"));
  }
});

Create a new element

The LayoutElementFactory class has an Instance property that allows you to create nearly all the different layout element types. When constructing a new element, the elementContainer parameter controls if the element gets created at the root level of the Contents pane or if it gets created within a group. If you pass in a reference to a Layout it gets created at the root level of the Contents pane and if you pass in a reference to a GroupElement the new element gets created in the referenced group. The second parameter represents the geometry of the element in page units and this will differ based on the element type. For example, some factories may require a rectangle or envelope to define the area on the layout for it to be displayed while other constructors may only take a simple point geometry. In most cases, the last, optional parameter defines the CIM symbol to be associated with the element. If not defined, then a default symbol will be assigned. The CIM elements section below provides an example of setting advanced symbology for layout elements.

//Create a simple 2D rectangle graphic with default symbology
QueuedTask.Run(() =>
{
  //Build 2D envelope geometry
  Coordinate2D rec_ll = new Coordinate2D(1.0, 4.75);
  Coordinate2D rec_ur = new Coordinate2D(3.0, 5.75);
  Envelope rec_env = EnvelopeBuilder.CreateEnvelope(rec_ll, rec_ur);

  //Create and add element to layout
  GraphicElement recElm = LayoutElementFactory.Instance.CreateRectangleGraphicElement(layout, env);
  recElm.SetName("New Rectangle");
});

There is a large collection of examples that create other layout elements types in the Layout ProSnippets wiki page.

Group elements

As discussed in the Create a new element section, elements can be created directly within an existing group element. You also have the ability to create new group elements, either at the root level of the layout Contents pane or create a group element within another group element. Again, if you pass in a reference to a Layout it gets created at the root level of the Contents pane and if you pass in a reference to a GroupElement the new element gets created in the referenced group.

//Create an empty group element at the root level of the contents pane
QueuedTask.Run(() =>
{
  GroupElement grp1 = LayoutElementFactory.Instance.CreateGroupElement(layout);
  grp1.SetName("Group");
});
//Create a group element inside another group element
QueuedTask.Run(() =>
{
  GroupElement grp2 = LayoutElementFactory.Instance.CreateGroupElement(grp1);
  grp2.SetName("Group in Group");
});

Ordering elements

There are two basic methods on the Element class that allow you to control the exact placement and order of elements in the layout Contents pane. A single element or an entire group element can be repositioned. The SetTOCPositionAbsolute method sets the position of an element either at the top or bottom of the targetContainer. So for example, if the targetContainer is a Layout and the isTop parameter is set to true, the element will be placed at the top of the Contents pane. Likewise, if the targetContainer is a GroupElement and the isTop parameter is set to false, the element is placed at the bottom of the group element.

//Place an element at the top of the layout contents pane
element.SetTOCPositionAbsolute(layout, true);

The SetTOCPositionRelative method sets the position of an element relative to another referenceElement either above or below it. This also works if the referenceElement is an element at the root level of the Contents pane or if it is an element in a group. If the isAbove parameter is set to false, the element will be placed below the reference layer.

//Place an element below the referenced element
element.SetTOCPositionRelative(ref_element, false);

CIM elements

The layout SDK managed API provides access to many of each layout element's common members but understand that many more properties are available via the Cartographic Information Model (CIM) associated with each element. You have the ability to either create new CIM elements or you can return the CIM definition for an existing element. A standard method that returns the CIM definition for an element is GetDefinition. Here is a list of all the CIM classes. The remainder of this section will simply demonstrate how CIM classes can be used to expand upon the managed API for layouts.

  1. The following example creates a new layout by passing in a new CIMPage with expanded capabilities verses creating a layout with only a few basic parameters exposed to the CreateLayout method.
//Set up a page
CIMPage newPage = new CIMPage();

//required properties
newPage.Width = 17;
newPage.Height = 11;
newPage.Units = LinearUnit.Inches;

//optional rulers
newPage.ShowRulers = true;
newPage.SmallestRulerDivision = 0.5;

layout = LayoutFactory.Instance.CreateLayout(newPage);
layout.SetName("New CIM Layout");
  1. This next example creates a new rectangle element like in the Create a new element section but this time uses the CIMStroke and CIMPolygonSymbol to improve the element's appearance.
QueuedTask.Run(() =>
{
  //Build 2D envelope geometry
  Coordinate2D rec_ll = new Coordinate2D(1.0, 4.75);
  Coordinate2D rec_ur = new Coordinate2D(3.0, 5.75);
  Envelope rec_env = EnvelopeBuilder.CreateEnvelope(rec_ll, rec_ur);

  //Set symbolology, create and add element to layout
  CIMStroke outline = SymbolFactory.Instance.ConstructStroke(ColorFactory.Instance.BlackRGB, 5.0, SimpleLineStyle.Solid);
  CIMPolygonSymbol polySym = SymbolFactory.Instance.ConstructPolygonSymbol(ColorFactory.Instance.GreenRGB, SimpleFillStyle.DiagonalCross, outline);
  GraphicElement recElm = LayoutElementFactory.Instance.CreateRectangleGraphicElement(layout, rec_env, polySym);
  recElm.SetName("New Rectangle");
});
  1. This example first returns the CIM definition for the layout and then uses the members exposed to the CIMLegend class to make modifications that can't be done using the mangaged API.
await QueuedTask.Run(() =>
{
  //Get LayoutCIM and iterate through its elements
  var layoutDef = layout.GetDefinition();
        
  foreach (var elem in layoutDef.Elements)
  {
    if (elem is CIMLegend)
    {
      var legend = elem as CIMLegend;
      foreach (var legendItem in legend.Items)
      {
        //Change the text for item label to GREEN
        var itemLabel = legendItem.LabelSymbol.Symbol as CIMTextSymbol;
        foreach(var symlayer in ((CIMPolygonSymbol)itemLabel.Symbol).SymbolLayers) 
        {
           if (symlayer is CIMSolidFill)
           {
              var itemLabelSym = (CIMSolidFill)symlayer;
              itemLabelSym.Color.Values[0] = 0;
              itemLabelSym.Color.Values[1] = 255;
              itemLabelSym.Color.Values[2] = 0;
           }
        }
      }
      break;
    }
  }
  //Apply the changes back to the layout
  layout.SetDefinition(layoutDef);
});
  1. This example shows how you can set up a spatial map series using the CIMSpatialMapSeries class and then apply that to the referenced layout. Map series support will be enhanced in the layout API across the upcoming releases.
//Get the Layout CIM
CIMLayout layCIM = layout.GetDefinition();

//Create a new MapSeries CIM
layCIM.MapSeries = new CIMSpatialMapSeries();
CIMSpatialMapSeries ms = layCIM.MapSeries as CIMSpatialMapSeries;

//Set map series properties
ms.Enabled = true;
ms.MapFrameName = "New Map Frame";
ms.StartingPageNumber = 1;
ms.CurrentPageID = 1;
ms.IndexLayerURI = "CIMPATH=map/greatlakes.xml";
ms.NameField = "NAME";
ms.SortField = "NAME";
ms.SortAscending = true;
ms.ScaleRounding = 1000;
ms.ExtentOptions = ExtentFitType.BestFit;
ms.MarginType = ArcGIS.Core.CIM.UnitType.Percent;
ms.Margin = 0;

//Apply the map series CIM back to the layout
layout.SetDefinition(layCIM);

LayoutView class

A LayoutView is simply a view of a layout. Layout views are the primary interface used to display, navigate, select, and edit layout elements in the layout. The layout being visualized in the view can be accessed via the Layout property (e.g. LayoutView.Active.Layout). There are several scenarios that need to be evaluated when trying to reference a layout view:

  • A layout view can only exist if there is layout project item.
  • If a layout project item does exist, it doesn't mean that a layout view is open in the application.
  • Multiple layout views that reference the same layout can exist.
  • A layout view may be open but it may not be active.
  • The active view is not necessarily a layout view.

Activate an existing view

In the Reference an existing layout section there is a sample that shows how to reference a layout associated with the active layout view. But another scenario involves making an already open layout view active. This requires that you iterate through the pane collection available to the FrameWorkApplication class, isolate the pane of interest, and then active it.

//Check to see if a layout view exists.  If it does, activate it.

//Confirm if layout exists as a project item
LayoutProjectItem layoutItem = Project.Current.GetItems<LayoutProjectItem>().FirstOrDefault(item => item.Name.Equals("some layout"));
if (layoutItem != null)
{
  Layout lyt = await QueuedTask.Run(() => layoutItem.GetLayout());
        
  //Next check to see if a layout view is already open that referencs the Game Board layout
  foreach (var pane in ProApp.Panes)
  {
    var lytPane = pane as ILayoutPane; 

    //if not a layout view, continue to the next pane
    if (lytPane == null)  //if not a layout view, continue to the next pane
      continue;
    
    //if there is a match, activate the view
    if (lytPane.LayoutView.Layout == lyt) 
    {
      (lytPane as Pane).Activate();
      return;
    }
  }

Open a new view

As mentioned in the Create a new element section, when a layout is created using the SDK, a layout view is not automatically generated. You must open the layout in a new view using the CreateLayoutPaneAsync method. Be sure to call this on the main GUI thread and use the await statement.

  //As a continuation of the code in the previous section
  //If panes don't exist, then open a new pane
  await ProApp.Panes.CreateLayoutPaneAsync(lyt);
  return;

Element selection and navigation

The LayoutView class has several methods for managing layout element selection: ClearElementSelection, GetSelectedElements, SelectAllElements, and SelectElements. When either getting or setting the selection, a list collection is used. The LayoutView class also has multiple methods for navigating the layout.

// Find two graphic rectangle elements and set them to be selected and then zoom the page to the selection.    
if (LayoutView.Active != null)
{
  LayoutView lytView = LayoutView.Active;
  Layout lyt = await QueuedTask.Run(() => lytView.Layout);
  Element rec = lyt.FindElement("Rectangle");
  Element rec2 = lyt.FindElement("Rectangle 2");

  List<Element> elmList = new List<Element>();
  elmList.Add(rec);
  elmList.Add(rec2);
  
  //Set selection
  lytView.SelectElements(elmList);

  //Zoom to selection
  await QueuedTask.Run(() => lytView.ZoomToSelectedElements());
}

A complete working example

This section provides an example of code that takes many of the elements mentioned in the previous sections and combines them into a single, working example that can be copied into a Visual Studio solution. It first confirms that the layout does not exist in a pane or as a layout project item, then it creates a new layout, then adds elements like map frames and text elements, sets element symbology, creates a new view, and finally, clears the current selection.

   async protected override void OnClick()
    {

      //Check to see if the the layout already exists
      LayoutProjectItem layoutItem = Project.Current.GetItems<LayoutProjectItem>().FirstOrDefault(item => item.Name.Equals("some layout"));
      if (layoutItem != null)
      {
        Layout lyt = await QueuedTask.Run(() => layoutItem.GetLayout());
        
        //Next check to see if a layout view is already open that references the layout
        foreach (var pane in ProApp.Panes)
        {
          var lytPane = pane as ILayoutPane;
          if (lytPane == null)  //if not a layout view, continue to the next pane
            continue;
          if (lytPane.LayoutView.Layout == lyt) //if there is a match, activate the view
          {
            (lytPane as Pane).Activate();
            System.Windows.MessageBox.Show("Activating existing pane");
            return;
          }
        }

        //If panes don't exist, then open a new pane
        await ProApp.Panes.CreateLayoutPaneAsync(lyt);
        System.Windows.MessageBox.Show("Opening already existing layout");
        return;
      }

      //The layout does not exist so create a new one
      Layout layout = await ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run<Layout>(() =>
      {
        //*** CREATE A NEW LAYOUT ***
        
        //Set up a page
        CIMPage newPage = new CIMPage();
        //required properties
        newPage.Width = 17;
        newPage.Height = 11;
        newPage.Units = LinearUnit.Inches;

        //optional rulers
        newPage.ShowRulers = true;
        newPage.SmallestRulerDivision = 0.5;

        layout = LayoutFactory.Instance.CreateLayout(newPage);
        layout.SetName("Game Board");

        //*** INSERT MAP FRAME ***

        // create a new map with an ArcGIS Online basemap
        Map map = MapFactory.Instance.CreateMap("World Map", MapType.Map, MapViewingMode.Map, Basemap.NationalGeographic);

        //Build map frame geometry
        Coordinate2D ll = new Coordinate2D(4, 0.5);
        Coordinate2D ur = new Coordinate2D(13, 6.5);
        Envelope env = EnvelopeBuilder.CreateEnvelope(ll, ur);

        //Create map frame and add to layout
        MapFrame mfElm = LayoutElementFactory.Instance.CreateMapFrame(layout, env, map);
        mfElm.SetName("Main MF");

        //Set the camera
        Camera camera = mfElm.Camera;
        camera.X = 3365;
        camera.Y = 5314468;
        camera.Scale = 175000000;
        mfElm.SetCamera(camera);

        //*** INSERT TEXT ELEMENTS ***

        //Title text
        Coordinate2D titleTxt_ll = new Coordinate2D(6.5, 10);
        CIMTextSymbol arial36bold = SymbolFactory.Instance.ConstructTextSymbol(ColorFactory.Instance.BlueRGB, 36, "Arial", "Bold");
        GraphicElement titleTxtElm = LayoutElementFactory.Instance.CreatePointTextGraphicElement(layout, titleTxt_ll, "Feeling Puzzled?", arial36bold);
        titleTxtElm.SetName("Title");

        //Service layer credits
        Coordinate2D slcTxt_ll = new Coordinate2D(0.5, 0.2);
        Coordinate2D slcTxt_ur = new Coordinate2D(16.5, 0.4);
        Envelope slcEnv = EnvelopeBuilder.CreateEnvelope(slcTxt_ll, slcTxt_ur);
        CIMTextSymbol arial8reg = SymbolFactory.Instance.ConstructTextSymbol(ColorFactory.Instance.BlackRGB, 8, "Arial", "Regular");
        String slcText = "<dyn type='layout' name='Game Board' property='serviceLayerCredits'/>";
        GraphicElement slcTxtElm = LayoutElementFactory.Instance.CreateRectangleParagraphGraphicElement(layout, slcEnv, slcText, arial8reg);
        slcTxtElm.SetName("SLC");
        return layout;  
      });

      //*** OPEN LAYOUT VIEW (must be in the GUI thread) ***
      var layoutPane = await ProApp.Panes.CreateLayoutPaneAsync(layout);
      var sel = layoutPane.LayoutView.GetSelectedElements();
      if (sel.Count > 0)                                     
      {
        layoutPane.LayoutView.ClearElementSelection();        
      }
    }
  }
}

Home

Developing with ArcGIS Pro


Framework

    Add-ins

    Configurations

    Customization

    Styling


Content


CoreHost


DataReviewer


Editing


Geodatabase


Geometry

    Relational Operations


Geoprocessing


Layouts


Map Authoring


Map Exploration

    Map Tools


Raster


Sharing


Tasks


Utility Network


Workflow Manager


Reference

    2.0 Migration

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.