Showing posts with label ArcGIS API for Silverlight. Show all posts
Showing posts with label ArcGIS API for Silverlight. Show all posts

Friday, December 16, 2011

How to customize Esri’s Silverlight TimeSlider

image

The Esri ArcGIS SIlverlight Toolkit SDK is a companion of Esri’s ArcGIS API for Silverlight. The toolkit includes a number of useful controls including a magnifier, scale bar and an info window to display map popup windows. This post will demonstrate how to customize (or style) the toolkit’s TimeSlider control.

To view a live application click here.
To download the full source code click here.

By default, the toolkit’s TimeSlider looks like this:

image

With a style applied it is possible to transform the TimeSlider to look like this:

image

To achieve this appearance you but include the follow two styles as a resource. The first style is for the Thumb.

<Style x:Key="SleekThumb" TargetType="Thumb">
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Height" Value="12"/>
    <Setter Property="Width" Value="12"/>
    <Setter Property="Cursor" Value="Hand"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Thumb">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver"/>
                            <VisualState x:Name="Pressed" />
                            <VisualState x:Name="Disabled" />
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused"/>
                            <VisualState x:Name="Unfocused"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Ellipse Fill="{TemplateBinding Background}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter
>
</
Style
>

And then the TimeSlider itself.

<Style x:Key="SleekTimeSlider" TargetType="esri:TimeSlider">
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="esri:TimeSlider">
                <Grid>
                    <!--VisualState wiring - not used-->
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver"/>
                            <VisualState x:Name="Disabled"/>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused" />
                            <VisualState x:Name="Unfocused"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>

                    <!-- Slider core -->
                    <Grid x:Name="HorizontalTrack">
                        <!--Hide tick marks (but still used for snapping)-->
                        <esriToolkitPrimitives:TickBar x:Name="TickMarks"
Margin="0,0,0,0"
IsHitTestVisible="False">
                            <esriToolkitPrimitives:TickBar.TickMarkTemplate>
                                <DataTemplate>
                                    <Grid />
                                </DataTemplate>
                            </esriToolkitPrimitives:TickBar.TickMarkTemplate>
                        </esriToolkitPrimitives:TickBar>

                        <!--Background Track-->
                        <Rectangle Height="3" Margin="5,0"
StrokeThickness="{TemplateBinding BorderThickness}"
Fill="LightGray"/>

                        <!--Left repeater button-->
                        <RepeatButton
                           x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton"
                                                           
                          
IsTabStop="False"
                                 
                          
HorizontalAlignment="Stretch"
                           Opacity="0" />

                        <!--Minimum thumb-->
                        <Thumb
                           x:Name="MinimumThumb"
                          
                          
DataContext="{TemplateBinding Value}"
                           ToolTipService.ToolTip="{Binding Start}"
 
                          
ToolTipService.Placement="Top"
                           HorizontalAlignment="Left"
                           Foreground="{TemplateBinding Foreground}"
                           Style="{StaticResource SleekThumb}" />

                        <!--Middle thumb-->
                        <Thumb
                           x:Name="HorizontalTrackThumb"
                                                                                                                         
                          
IsTabStop="False"
                           Cursor="Hand"
                           HorizontalAlignment="Left"
              
                          
Foreground="{TemplateBinding Foreground}">
                            <Thumb.Template>
                                <ControlTemplate>
                                    <Rectangle Height="3"
Fill="{TemplateBinding Foreground}" />
                                </ControlTemplate>
                            </Thumb.Template>
                        </Thumb>

                        <!--Maximum thumb-->
                        <Thumb
                           x:Name="MaximumThumb"
                                   
                          
DataContext="{TemplateBinding Value}"
ToolTipService.ToolTip="{Binding End}"
 
                          
ToolTipService.Placement="Top"
                           HorizontalAlignment="Left"
                           Foreground="{TemplateBinding Foreground}"
                           Style="{StaticResource SleekThumb}" />

                        <!--Right repeater button-->
                        <RepeatButton
                           x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton"
   
                          
IsTabStop="False"
                                   
                          
HorizontalAlignment="Stretch"
                           Opacity="0" />
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter
>
</
Style
>

The XAML page containing two styles above must have the following namespaces defined.

xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
xmlns:esriToolkitPrimitives=
"clr-namespace:ESRI.ArcGIS.Client.Toolkit.Primitives;
assembly=ESRI.ArcGIS.Client.Toolkit"

Finally, apply the style to the TimeSlider as shown below.

<esri:TimeSlider
   Grid.Row="1"
   x:Name="TimeSlider"
   TimeMode="TimeExtent"
   Foreground="Black"

    Style="{StaticResource SleekTimeSlider}"
/>

In closing, the controls included in the ArcGIS Silverlight Toolkit are no different from other controls with respect to styling and templating. To assist with styling other toolkit controls I would recommend browse the toolkit’s source code on codeplex. Alternatively you can just tweak the XAML in the post to fit your needs.

Monday, May 23, 2011

Landsat Touch for Silverlight

clip_image002

The Lab is proud to release Landsat Touch, a Silverlight based web application for browsing the newly published Landsat imagery.

Try the application here:
http://maps.esri.com/sldemos/landsat/default.html
(click the “need help” button in the lower left hand corner for usage tips)

Download the source code from here:
http://www.arcgis.com/home/item.html?id=04de702976434f4d8c054005ce4603a2

Earlier this year Esri announced the publication of more than 8TB of Landsat imagery as image services on ArcGIS Online. These services can be consumed by any ArcGIS client, the ChangeMatters viewer or by any custom app using the REST API.

Landsat Touch is a custom web application developed using the ArcGIS API for Silverlight. The objective of this project was to create an easy to use application that could browse, compare and contrast Landsat content and provide support for touch devices (if available). The application is fully functional using a mouse on Windows or Mac computer, but the application is optimized for Windows 7 touch devices.

Multi-touch behavior is provided using the Multi-Touch Manipulation library from CodePlex which is partially derived from Microsoft’s Manipulation and Inertial sample.

The ability for this application to compare two layers of content is conceptually similar to ArcMap’s swipe layer tool. An important distinction with Landsat Touch is that users can explicitly define the “swiped” area by manipulating the size, orientation and location of windows. However the most exciting features demonstrated by this application is its ability to be multi-touch and multi-user. On large touch devices, say 40”+ diagonally, it is conceivable that two or more users can simultaneously add and manipulate Landsat windows.

Wednesday, February 9, 2011

Custom Magnifier for Esri’s Silverlight SDK

Custom Magnifier for Esri’s Silverlight SDK

Map magnifiers are very intuitive and compelling for end users. They allow a user to examine a map in more detail without having to to change the map scale (or “zoom in”). Magnifiers can also be used to like an “x-ray” to reveal alternative content. For example, if a base map shows a street map information for a city, a magnifier could reveal satellite imagery at the same scale.

The ArcGIS Silverlight Toolkit extends ESRI’s ArcGIS API for Microsoft Silverlight with useful widgets like a navigation control, toolbar and two types of magnifiers.

The toolkit’s Magnifier is a control that allows the user to temporarily swipe a stylized magnifying glass over the map. Developers can specify any magnification and any map content for the magnifier.

Magnifier

The toolkit’s MagnifyingGlass is an alternative magnifier with a simpler design. Developers can specify any magnification but are limited to single tiled map service layer for the contents. Unlike the Magnifier described above, this control can resided in the web application permanently. As the base map updates, the extent in the MagnifyingGlass will also update.

MagnifyingGlass

These two controls are very useful but did not satisfy my requirement to have permanent magnifier that supported dynamic layers such as an image service layer. The code below is a blend of both toolkit magnifiers.

Here is the XAML:

<UserControl
   x:Class="TestMagnify.Magnify"
   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="225"
   d:DesignWidth="225"
   Width="225"
   Height="225"
   >
    <Grid x:Name="LayoutRoot">
        <Grid.RenderTransform>
            <TranslateTransform x:Name="Translate" />
        </Grid.RenderTransform>
        <Ellipse x:Name="MagShadow" Margin="-2" Fill="Black">
            <Ellipse.Effect>
                <BlurEffect Radius="25"/>
            </Ellipse.Effect>
        </Ellipse>
        <Ellipse x:Name="MagFrameBack" Margin="0">
            <Ellipse.Fill>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF000000" Offset="1"/>
                    <GradientStop Color="#FFFFFFFF" Offset="0"/>
                    <GradientStop Color="#FF353535" Offset="0.875"/>
                    <GradientStop Color="#FF515151" Offset="0.21"/>
                </LinearGradientBrush>
            </Ellipse.Fill>
        </Ellipse>
        <Ellipse x:Name="MagFrameFront" Margin="5">
            <Ellipse.Fill>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF000000"/>
                    <GradientStop Color="#FFFFFFFF" Offset="1"/>
                    <GradientStop Color="#FF505050" Offset="0.134"/>
                    <GradientStop Color="#FE787878" Offset="0.728"/>
                    <GradientStop Color="#FE9D9D9D" Offset="0.915"/>
                </LinearGradientBrush>
            </Ellipse.Fill>
        </Ellipse>
        <Grid Margin="10">
            <Grid.OpacityMask>
                <RadialGradientBrush GradientOrigin="0.5,0.5"
Center="0.5,0.5">
                    <GradientStop Color="White" Offset="0.9999"/>
                    <GradientStop Offset="1"/>
                </RadialGradientBrush>
            </Grid.OpacityMask>
            <esri:Map x:Name="MagMap"
                     IsLogoVisible="False"
                     IsHitTestVisible="False"
                     PanDuration="00:00:00"
                     ZoomDuration="00:00:00"/>
        </Grid>
        <Ellipse x:Name="MagGlass" Stroke="#FF000000" Margin="10">
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="1, 1" Center="0.5,0.5">
                    <GradientStop Color="#48FFFFFF" Offset="0.009"/>
                    <GradientStop Color="#22FFFFFF" Offset="0.107"/>
                    <GradientStop Color="#00BBBBBB" Offset="0.567"/>
                    <GradientStop Color="#00BCBCBC" Offset="0.585"/>
                    <GradientStop Color="#00BBBBBB" Offset="0.625"/>
                    <GradientStop Color="#04C4C4C4" Offset="0.696"/>
                    <GradientStop Color="#1AC4C4C4" Offset="0.888"/>
                    <GradientStop Color="#2FFFFFFF" Offset="1"/>
                </RadialGradientBrush>
            </Ellipse.Fill>
        </Ellipse>
    </Grid>
</UserControl>

Here is the code behind:

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

namespace TestMagnify {
    public partial class Magnify : UserControl {
        private Point _begin;
        private Point _current;
        private Cursor _cursor = null;
        private bool _isdrag = false;
        //
        public Magnify() {
            InitializeComponent();
            this.MouseLeftButtonDown += this.MagnifyBox_MouseLeftButtonDown;
            this.MouseLeftButtonUp += this.MagnifyBox_MouseLeftButtonUp;
            this.MouseMove += this.MagnifyBox_MouseMove;
        }
        public static readonly DependencyProperty MapProperty =
            DependencyProperty.Register(
                "Map",
                typeof(Map),
                typeof(Magnify),
                new PropertyMetadata(Magnify.OnMapPropertyChanged));
        public static readonly DependencyProperty LayersProperty =
            DependencyProperty.RegisterAttached(
                "Layers",
                typeof(LayerCollection),
                typeof(Magnify),
                new PropertyMetadata(Magnify.OnLayersPropertyChanged));
        public static readonly DependencyProperty ZoomFactorProperty =
            DependencyProperty.Register(
                "ZoomFactor",
                typeof(double),
                typeof(Magnify),
                new PropertyMetadata(2d,
Magnify.OnZoomFactorPropertyChanged));
        public Map Map {
            get { return (Map)this.GetValue(MapProperty); }
            set { this.SetValue(MapProperty, value); }
        }
        public LayerCollection Layers {
            get { return (LayerCollection)GetValue(LayersProperty); }
            set { SetValue(LayersProperty, value); }
        }
        public double ZoomFactor {
            get { return (double)this.GetValue(ZoomFactorProperty); }
            set { this.SetValue(ZoomFactorProperty, value); }
        }
        private static void OnLayersPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
            Magnify magnify = d as Magnify;
            if (magnify.MagMap != null) {
                magnify.MagMap.Layers = e.NewValue as LayerCollection;
            }
        }
        private static void OnMapPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
            Magnify glass = d as Magnify;
            Map mapOld = e.OldValue as Map;
            if (mapOld != null) {
                mapOld.ExtentChanged -= glass.Map_ExtentChanged;
            }
            Map mapNew = e.NewValue as Map;
            if (mapNew != null) {
                mapNew.ExtentChanged += glass.Map_ExtentChanged;
            }
        }
        private static void OnZoomFactorPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
            (d as Magnify).UpdateMagnifier();
        }
        private void Map_ExtentChanged(object sender, ExtentEventArgs e) {
            this.UpdateMagnifier();
        }
        private void MagnifyBox_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e) {
            this._isdrag = true;
            this._cursor = this.Cursor;
            this._begin = e.GetPosition(null);
            this.Cursor = Cursors.None;
            this.CaptureMouse();
        }
        private void MagnifyBox_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e) {
            if (this._isdrag) {
                this._isdrag = false;
                this.ReleaseMouseCapture();
                this.UpdateMagnifier();
            }
            this.Cursor = this._cursor;
        }
        private void MagnifyBox_MouseMove(object sender, MouseEventArgs e) {
            if (this._isdrag) {
                this._current = e.GetPosition(null);
                double x = this._current.X - this._begin.X;
                double y = this._current.Y - this._begin.Y;
                if (this.FlowDirection == FlowDirection.RightToLeft) {
                    x *= -1d;
                }
                this.Translate.X += x;
                this.Translate.Y += y;
                this._begin = this._current;
            }
        }
        private void UpdateMagnifier() {
            if (this.Visibility == Visibility.Collapsed) { return; }
            if (this.Map == null) { return; }
            Point point = this.TransformToVisual(this.Map).Transform(
                new Point(
                    this.RenderSize.Width * 0.5d + this.Translate.X,
                    this.RenderSize.Height * 0.5d + this.Translate.Y
                )
            );
            MapPoint center = this.Map.ScreenToMap(point);
            double resolution = this.Map.Resolution;
            double zoomResolution = resolution / this.ZoomFactor;
            double width = 0.5d * this.MagMap.ActualWidth * zoomResolution;
            Envelope envelope = new Envelope() {
                XMin = center.X - width,
                YMin = center.Y - width,
                XMax = center.X + width,
                YMax = center.Y + width,
                SpatialReference = this.MagMap.SpatialReference
            };
            this.MagMap.Extent = envelope;
        }
    }
}

Here is how to use it:

<UserControl
   x:Class="TestMagnify.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"
   xmlns:local="clr-namespace:TestMagnify"
   mc:Ignorable="d"
   d:DesignHeight="300" d:DesignWidth="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <esri:Map x:Name="Map">
            <esri:ArcGISImageServiceLayer
               Url="http://sampleserver3.arcgisonline.com ~
/ArcGIS/rest/services/Portland/Aerial/ImageServer"

               ImageFormat="JPGPNG" />
        </esri:Map>
        <local:Magnify x:Name="Magnify"
                      Map="{Binding ElementName=Map}"
                      ZoomFactor="5">
            <local:Magnify.Layers>
                <esri:LayerCollection>
                    <esri:ArcGISImageServiceLayer
                       Url="http://sampleserver3.arcgisonline.com ~
/ArcGIS/rest/services/Portland/Aerial/ImageServer"

                       ImageFormat="JPGPNG" />
                </esri:LayerCollection>
            </local:Magnify.Layers>
        </local:Magnify>
    </Grid>
</UserControl>

Note: “~” denotes an editorial line continuation.

In conclusion, based on source code extracted from Esri’s ArcGIS Silverlight toolkit project page on codeplex, I was able to create a new magnifier that could support any layer or layers regardless of type. In order to support dynamic layers such an image service layers the new magnifier had to reserve updates until after a drag operation is completed. This is not ideal but unavoidable.

Using this code I hope you can explore some innovative variants like “x-ray” controls. Fun and productive controls like these are being increasingly important as web apps are becoming more tactile with touch support in new hardware like this.