How to set FixedAspectRatio to False for Legend

2713
17
06-25-2014 08:50 AM
TimWhiteaker
Occasional Contributor II
I'm creating a legend. I want to set IBoundsProperties.FixedAspectRatio to False. When I run the code, I get "The method or operation is not implemented."

Code snippet (C#):
ILegend2 legend = mapSurroundFrame.MapSurround as ILegend2;
IBoundsProperties bounds = legend as IBoundsProperties;
bounds.FixedAspectRatio = false;


I get the same error if I cast to IBoundsProperties from the mapSurroundFrame object instead of the legend object.
0 Kudos
17 Replies
LeoDonahue
Occasional Contributor III
When I run the code, I get "The method or operation is not implemented."


That is because the method is not implemented on the ILengend2 interface, nor is it on the ILegend interface.  Look at the API.
http://resources.arcgis.com/en/help/arcobjects-java/api/arcobjects/com/esri/arcgis/carto/ILegend2.ht...

I know that's not a C# link, but you should get the same results looking at that documentation.

You need a concrete Legend object to call fixedAspectRatio.  The Legend class implements that behavior, not the interface.

"Program to an interface" doesn't literally mean, only use Interface types.  At some point, you need to instantiate a concrete class for certain behavior.
0 Kudos
TimWhiteaker
Occasional Contributor II
You need a concrete Legend object to call fixedAspectRatio.


Thanks for the fast response.  mapSurroundFrame.MapSurround returns an instance of the Legend coclass, so that should be a concrete object. I've continued digging into this problem and found that when I create a legend manually through the ArcMap GUI, I see that the checkbox for Preserve Aspect Ratio is disabled, so I'm guessing that changing it is not implemented for legends. 

For more background, I followed the example here:
http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/index.html#//00490000006w000000

When instantiated, a legend includes all layers in the map, so I then remove some of them. This changes the size of the legend that you see on screen. However, when you look at the properties for the legend's map surround frame, it still has its original width and height as generated in the code. This makes aligning and distributing the legend element with other elements problematic.  Even changing the anchor point through the GUI can result in unwanted resizing.

What I haven't attempted yet but may try is using IQuerySize to get the updated size in points for the legend, convert to page layout units, and assign that as the new geometry for the map surround frame element. This would help with manual positioning as long as the legend isn't edited, but after it is edited, I think it will still keep the last size as set by the code instead of responding appropriately to resizing through the GUI.  But that seems like a kluge (if it even works) so I'm hoping you or another expert on here knows of a better way.
0 Kudos
TimWhiteaker
Occasional Contributor II
What I haven't attempted yet but may try is using IQuerySize to get the updated size in points for the legend, convert to page layout units, and assign that as the new geometry for the map surround frame element.


That seemed to work.  Here's some C# code to handle reassigning the geometry to the element.

// Update size of element containing the legend
// av is IActiveView on an instance of the page layout
IQuerySize querySize = mapSurroundFrame.MapSurround as IQuerySize;
Double w = 0;
Double h = 0;
querySize.QuerySize(ref w, ref h);
// Convert from points to page units, e.g., inches
w = av.ScreenDisplay.DisplayTransformation.FromPoints(w);
h = av.ScreenDisplay.DisplayTransformation.FromPoints(h);
IEnvelope envelope = new EnvelopeClass();
// The legend is anchored at x=1 inch, y=2 inch on the layout
envelope.PutCoords(1, 2, (1 + w), (2 + h));
IElement element = mapSurroundFrame as IElement;
element.Geometry = envelope;


----------------------------------------------

For completeness, here are some other things I tried which didn't work:
ILegend.Refresh() -- I think that just helps the legend redraw on screen if you changed it programmatically. It didn't fix the geometry.

ILegend.QueryBounds -- Unlike on IElement, this QueryBounds method takes an old envelope and a new one. I thought the new one might be the correct geometry. Indeed the is returned from the method with a different size than the old envelope. However, assigning the new envelope as the element's geometry didn't solve the problem.
0 Kudos
LeoDonahue
Occasional Contributor III
mapSurroundFrame.MapSurround returns an instance of the Legend coclass

Really?

I don't think it says that exactly.

Looking at the .NET documentation for IMapSurroundFrame Interface:
http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/0012/001200000m4w000000.htm

Click on the MapSurround member link.. at the bottom it says: 
"The MapSurround property allows you to retrieve or set the surround object (north arrow, legend, or scale bar) stored within the frame."
  You are getting a "surround object" that is stored within the frame, it could be any of the CoClasses that implement the IMapSurround Interface.

Look at all the "concrete" CoClasses that implement the IMapSurround Interface:
http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/0012/001200000m47000000.htm

I'm not current on my .NET ArcObjects, but it seems to me that you still need an instance of a Legend CoClass somewhere.
0 Kudos
TimWhiteaker
Occasional Contributor II
I see the source of confusion.  I should have provided more code in my example.  Below is a  more fully fleshed-out example, showing how the legend is initialized by creating a MapSurroundFrame with UID "esriCarto.Legend".  Today I also discovered that I needed to add the element to the layout after I'm finished working with the legend.  Otherwise, if I mess with the legend and try to reassign its geometry, the new geometry doesn't stick.  It keeps the original one.

// Initialize a legend for the focus map
IGraphicsContainer gc = ArcMap.Document.PageLayout as IGraphicsContainer;
IActiveView av = gc as IActiveView;
IMap map = av.FocusMap;
IMapFrame mapFrame = gc.FindFrame(map) as IMapFrame;
ESRI.ArcGIS.esriSystem.IUID uid = new ESRI.ArcGIS.esriSystem.UIDClass();
uid.Value = "esriCarto.Legend";
IMapSurroundFrame mapSurroundFrame = mapFrame.CreateSurroundFrame((ESRI.ArcGIS.esriSystem.UID)uid, null);

// Size it
IQuerySize querySize = mapSurroundFrame.MapSurround as IQuerySize;
Double w = 0;
Double h = 0;
querySize.QuerySize(ref w, ref h);
Double aspectRatio = w / h;
IEnvelope envelope = new EnvelopeClass();
// The legend is anchored with bottom left corner at x=1, y=2
// Not sure why aspect ratio matters -- just following Esri's example
envelope.PutCoords(1, 2, (1 + 1), (2 + 1 / aspectRatio));
IElement element = mapSurroundFrame as IElement;
element.Geometry = envelope;

// Get the legend object and do something with it
ILegend2 legend = mapSurroundFrame.MapSurround as ILegend2;
// ... some work ... now assume the legend size has changed

// Update size of element containing the legend
querySize.QuerySize(ref w, ref h);
// Convert from points to page units, e.g., inches
w = av.ScreenDisplay.DisplayTransformation.FromPoints(w);
h = av.ScreenDisplay.DisplayTransformation.FromPoints(h);
envelope.PutCoords(1, 2, (1 + w), (2 + h));
element.Geometry = envelope;

// Now add the element to the layout.  
// If you add it earlier, from my experience, sometimes
//   the new geometry you assign doesn't stick
gc.AddElement(element, 0);
0 Kudos
LeoDonahue1
New Contributor II
Thanks for the example.

Your original post wanted to call: FixedAspectRatio

That method is not implemented in the ILegend, IBoundsProperties or the other Interfaces you have used to create an instance of a legend using IMapSurroundFrame.CreateSurroundFrame and passing the uid of a Legend. 

IBoundsProperties does indicate that any class that implements the IBoundsProperties Interface, provide some implementation for the FixedAspectRatio method.  So for example, Legend implements the IBoundsProperties Interface, which means that Legend needs to provide the "how" for the FixedAspectRatio method.

Interfaces are types, just like the CoClasses.  What you are doing in your code is declaring a IMapSurroundFrame type and assigning a reference value to that type of yet another Interface type.

What you could do is create a MapSurroundFrame variable or an actual Legend variable and assign that to your Interface type reference variable.

In Java, Interfaces as types work like this:
http://docs.oracle.com/javase/tutorial/java/IandI/interfaceAsType.html
If you define a reference variable whose type is an interface, any object you assign to it must be an instance of a class that implements the interface.


I see a lot of people on this forum, especially in the .NET user group, assign interface types to other interface variables.  I honestly don't know how that works or why more people don't run into problems doing that.
0 Kudos
LeoDonahue
Occasional Contributor III
Thanks for the example.

Your original post wanted to call: FixedAspectRatio

That method is not implemented in the ILegend, IBoundsProperties or the other Interfaces you have used to create an instance of a legend using IMapSurroundFrame.CreateSurroundFrame and passing the uid of a Legend. 

IBoundsProperties does indicate that any class that implements the IBoundsProperties Interface, provide some implementation for the FixedAspectRatio method.  So for example, Legend implements the IBoundsProperties Interface, which means that Legend needs to provide the "how" for the FixedAspectRatio method.

Interfaces are types, just like the CoClasses.  What you are doing in your code is declaring a IMapSurroundFrame type and assigning a reference value to that type of yet another Interface type.

What you could do is create a MapSurroundFrame variable or an actual Legend variable and assign that to your Interface type reference variable.

In Java, Interfaces as types work like this:
http://docs.oracle.com/javase/tutorial/java/IandI/interfaceAsType.html
If you define a reference variable whose type is an interface, any object you assign to it must be an instance of a class that implements the interface.


I see a lot of people on this forum, especially in the .NET user group, assign interface types to other interface variables.  I honestly don't know how that works or why more people don't run into problems doing that.
0 Kudos
RobertMaddox
New Contributor III
Your original post wanted to call: FixedAspectRatio

That method is not implemented in the ILegend, IBoundsProperties or the other Interfaces you have used to create an instance of a legend using IMapSurroundFrame.CreateSurroundFrame and passing the uid of a Legend.


Actually, IBoundsProperties does have a FixedAspectRatio property: http://help.arcgis.com/en/sdk/10.0/vba_desktop/componenthelp/000t/000t000002mz000000.htm

I see a lot of people on this forum, especially in the .NET user group, assign interface types to other interface variables.  I honestly don't know how that works or why more people don't run into problems doing that.


That is because that of how ESRI has designed ArcObjects and what they consider to be best practices, as shown in all of their examples. I used to think like you though about wanting to get away from interfaces and deal with concrete types. However, if you did get into .Net ArcObjects, you would see how ugly the classes are and that it's a complete waste of time to ever deal with them in any way other than creating a new object and assigning it to an interface.

If for example, I define a variable of type PolygonClass (Polygon is not the concrete class, PolygonClass is), my variable has, in addition to IsEmpty (from IGeometry), IGeometry2_IsEmpty, IGeometry3_IsEmpty, IGeometry4_IsEmpty, IGeometry5_IsEmpty, IPolygon_IsEmpty, IPolygon2_IsEmpty, IPolygon3_IsEmpty, IPolygon4_IsEmpty, ICurve_IsEmpty, IPolycurve_IsEmpty, and IPolycurve2_IsEmpty. That's 12 different IsEmpty properties! And there are many other examples just like that on this one class alone, not to mention all the other classes! Now in this case, each of those IsEmpty properties would probably all behave exactly the same, but I know there are other cases where properties on different interfaces have the exact same name, but have completely different meanings. If those two interfaces are implemented by a single class, then you really have to be careful about how you call it if your variable is of the class type and not an interface type.

I'm certainly not trying to beat you over the head with the "Program to the interface!" dogma. (I for one only define interfaces in my code when I need to have multiple classes implement some shared functionality that can't be done using a base class.) I also hate the way any time they need to add a new member to an interface, they just create a whole new interface ("IGeometry needs a new XYZ property? Say hello to IGeometry16!" :eek:), which half the time doesn't even inherit from the previous interface! :mad:

But I digress... Basically, in the (.net) ArcObjects world, it's just always best to only use interface typed variables.

As for why it works and doesn't break. Sometimes it does break. You always need to check when you are casting types that aren't always compatible (IE: IPolygon and IPolygon4 are always compatible both ways and an IPolygon can always be cast to an IGeometryCollection, but not the other way around) and that you aren't directly in control of.

Hope that helps! 😉
0 Kudos
LeoDonahue
Occasional Contributor III
Robby,

When I said that IBoundsProperty does not implement FixedAspectRatio, it was not just some uninformed opinion. It is a fact.
http://help.arcgis.com/en/sdk/10.0/vba_desktop/componenthelp/000t/000t000002mw000000.htm

CoClasses "implement" the behavior defined by the Interface. 
Interfaces only define a contract, there is nothing between the curly braces, i.e. no implementation.

What that is saying is that if you could look at the source code for the IBoundsProperty FixedAspectRatio method, there would be nothing in it.  All of those CoClasses that "implement" the interface have to define what FixedAspectRatio means and it could very well be different for different CoClasses.

I think perhaps we should take the time to educate a little bit on the concept of Interfaces.

http://msdn.microsoft.com/en-us/library/87d83y5b.aspx
An interface contains only the signatures of methods, properties, events or indexers. A class or struct that implements the interface must implement the members of the interface that are specified in the interface definition.


It is correct to use Interface types as reference variables, but what I was trying to tell the original poster is that you cannot expect to assign Interface types to Interface types and expect to call a method that is only implemented on one of the CoClasses.  I was hoping that by reading this he would know how to fix his problem. He needs to instantiate an instance of whatever CoClass he wants and assign it to an Interface.  He was trying to get a Legend by calling some method that returns an Interface and then assign that Interface to his IBoundsProperty Interface and then he didn't know why he couldn't call FixedAspectRatio.

I also hate the way any time they need to add a new member to an interface, they just create a whole new interface ("IGeometry needs a new XYZ property? Say hello to IGeometry16!" ), which half the time doesn't even inherit from the previous interface!

What do you expect to happen here? 

If you create an Interface called YourInterface and that Interface defines two methods:

doStuff{}
doMoreStuff{}


Every CoClass that implements the "YourInterface" Interface will be *required* to implement any additional methods that you add to it, such as doEvenMoreStuff{}.  That is how it works in Java.  I don't know how .NET handles that.

Adding functionality to existing Interfaces breaks all of the CoClass implementations.  This is why you see IInterface, IInterface2, IInterface3, etc.  Each of those additional Interfaces should be implementing the previous Interface, which is why you only see "additional" methods being defined in those new ones. 

I don't know about "half the time".  Do you have a specific example of on which Interface that happens?
0 Kudos