Thursday, December 23, 2010

Remove map whitespace with an alternative “full extent”

This post describes a technique for calculating an adjusted “full extent” for a layer so that content can uniformly fill a map.

The following XAML will display simple map using ESRI’s ArcGIS API for Silverlight. The map contains the world topo map service from ArcGIS Online.

<UserControl
   x:Class="ESRI.PrototypeLab.DynamicDesign.MainPage"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:esri="http://schemas.esri.com/arcgis/client/2009" 
   mc:Ignorable="d"
   d:DesignHeight="600"
   d:DesignWidth="800"
   >
    <Grid Background="Gray">
        <esri:Map x:Name="Map">
            <esri:Map.Layers>
                <esri:ArcGISTiledMapServiceLayer
ID="topo"
Visible="True"
Url="http://services.arcgisonline.com/
ArcGIS/rest/services/World_Topo_Map/MapServer"
/>
            </esri:Map.Layers>
        </esri:Map>
    </Grid>
</UserControl>

The code behind below will zoom to the full extent of topo map service.

using System.Windows.Controls;

namespace ESRI.PrototypeLab.DynamicDesign {
    public partial class MainPage : UserControl {
        public MainPage() {
            InitializeComponent();

            // Zoom to full extent
            this.Map.Layers[0].Initialized += (s, e) => {
                this.Map.ZoomTo(this.Map.Layers[0].FullExtent);
            };
        }
    }
}

Depending on the aspect ratio of the browser, this will result in a map with “white space” (or whatever color you use for the background) at either the top/bottom or left/right as shown below.

Too much "white space"Too much "white space"

To address this I extended the Layer class with a method to return an adjusted full extent for the parent map.

using System.Windows;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;

namespace ESRI.PrototypeLab.DynamicDesign {
    public static class LayerExtension {
        public static Envelope AdjustedFullExtent(this Layer layer,
FrameworkElement parent) {
            Envelope extent = layer.FullExtent;
            double ratioMap = parent.ActualHeight / parent.ActualWidth;
            double ratioLay = extent.Height / extent.Width;
            if (ratioMap < ratioLay) {
                return new Envelope() {
                    XMin = extent.XMin,
                    YMin = extent.GetCenter().Y - 0.5d * ratioMap *
extent.Width,
                    XMax = extent.XMax,
                    YMax = extent.GetCenter().Y + 0.5d * ratioMap *
extent.Width
                };
            }
            return new Envelope() {
                XMin = extent.GetCenter().X - 0.5d *
extent.Height / ratioMap,
                YMin = extent.YMin,
                XMax = extent.GetCenter().X + 0.5d *
extent.Height / ratioMap,
                YMax = extent.YMax
            };
        }
    }
}

Let’s modify the code behind to use the new method, AdjustedFullExtent, defined above.

using System.Windows.Controls;

namespace ESRI.PrototypeLab.DynamicDesign {
    public partial class MainPage : UserControl {
        public MainPage() {
            InitializeComponent();

            // Zoom to full extent
            this.Map.Layers[0].Initialized += (s, e) => {
                this.Map.ZoomTo(
this.Map.Layers[0].AdjustedFullExtent(this.Map));
            };
        }
    }
}

Now, when the application starts, the layer will fill the entire map regardless of the browsers aspect ratio.

No more "white space" No more "white space"

Tuesday, December 7, 2010

How to add “ScrollIntoView” to an ItemsControl

The ListBox control (in Silverlight or WPF) has a handy method called ScrollIntoView that forces a listbox scroll so that the parsed item is in view.  This post will describe how to add this capability to an ItemsControl.

Firstly, to enable vertical scrolling in an ItemsControl apply the following template.

<ItemsControl x:Name="myItemsControl">

    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer Padding="{TemplateBinding Padding}">
                <ItemsPresenter />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

Next, add the following class that extends the ItemsControl class with an overloaded “Scroll Into View” method.

using System.Windows;
using System.Windows.Controls;

namespace myNamespace {
    public static class Extensions {
        public static void ScrollIntoView(
this ItemsControl control,
object item) {
            FrameworkElement framework =
control.ItemContainerGenerator.ContainerFromItem(item)
as FrameworkElement;
            if (framework == null) { return; }
            framework.BringIntoView();
        }
        public static void ScrollIntoView(this ItemsControl control) {
            int count = control.Items.Count;
            if (count == 0) { return; }
            object item = control.Items[count - 1];
            control.ScrollIntoView(item);
        }
    }
}

With this extension you can force the ItemsControl to scroll to a specific item, or alternatively, using the overloaded method, simply scroll to the last item in the collection.  For example.

this.myItemsControl.ScrollIntoView();