среда, 24 апреля 2013 г.

Bing Maps. Стилизация Pushpin.

В этой статье я продолжу обзор использования Bing Maps в приложениях Windows Store. Но на этот раз я расскажу как стилизовать Pushpin-объекты на карте для различных типов объектов. Сразу приведу результат программы, чтобы было понятнее о чем идет речь.




Как видно, на карте расположено несколько объектов различного типа (магазины, кинотеатры, POI). Все эти объекты находятся в одной коллекции MapItemsControl объекта Map.


MainPage.xaml:
<common:LayoutAwarePage x:Class="WpfCsharp.BingMaps.MainPage"
                       DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
                       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                       xmlns:common="using:WpfCsharp.BingMaps.Common"
                       xmlns:selector="using:WpfCsharp.BingMaps.Selector"
                       xmlns:maps="using:Bing.Maps">

    <common:LayoutAwarePage.Resources>
        <CollectionViewSource x:Name="collectionViewSource" Source="{Binding Items}"/>

        <selector:MapItemTemplateSelector x:Key="mapItemTemplateSelector" DefaultTemplateKey="MapTemplate" IsCacheEnabled="True"/>

    </common:LayoutAwarePage.Resources>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <maps:Map x:Name="map" Credentials="{YOU MAP KEY}">
            <maps:MapItemsControl ItemsSource="{Binding Source={StaticResource collectionViewSource}}" ItemTemplateSelector="{StaticResource mapItemTemplateSelector}"/>
        </maps:Map>
    </Grid>
</common:LayoutAwarePage>

Данная коллекция наследуется от ItemsControl и соответственно имеет свойство ItemTemplateSelector, с помощью которого мы определим собственную логику выбора шаблона для каждого отображаемого элемента:

MapItemTemplateSelector.cs:
    public class MapItemTemplateSelector : DataTemplateSelector
    {
        #region Fields

        private Dictionary<String, DataTemplate> cachedDataTemplates;

        #endregion

        #region Properties

        public string DefaultTemplateKey { get; set; }

        public bool IsCacheEnabled { get; set; }

        #endregion

        #region DataTemplateSelector Members

        protected override DataTemplate SelectTemplateCore(Object item, DependencyObject container)
        {
            if (item == null) return null;
            var key = String.Concat(item.GetType().Name, DefaultTemplateKey);
            var dataTemplate = GetCachedDataTemplate(key);
            if (dataTemplate != null) { return dataTemplate; }
            try
            {
                var frameworkElement = container as FrameworkElement;
                while (frameworkElement != null)
                {
                    dataTemplate = FindTemplate(frameworkElement, key);
                    if (dataTemplate != null) { return dataTemplate; }
                    frameworkElement = VisualTreeHelper.GetParent(frameworkElement) as FrameworkElement;
                }
                dataTemplate = FindTemplate(null, key);
                return dataTemplate;
            }
            finally
            {
                if (dataTemplate != null)
                {
                    AddCachedDataTemplate(key, dataTemplate);
                }
            }
        }

        #endregion

        #region Private Members

        private DataTemplate GetCachedDataTemplate(string key)
        {
            if (!IsCacheEnabled) { return null; }
            VerifyCachedDataTemplateStorage();
            return cachedDataTemplates.ContainsKey(key) ? cachedDataTemplates[key] : null;
        }

        private void AddCachedDataTemplate(string key, DataTemplate dt)
        {
            if (!IsCacheEnabled) { return; }
            VerifyCachedDataTemplateStorage();
            cachedDataTemplates[key] = dt;
        }

        private void VerifyCachedDataTemplateStorage()
        {
            if (cachedDataTemplates == null)
            {
                cachedDataTemplates = new Dictionary<string, DataTemplate>();
            }
        }

        private static DataTemplate FindTemplate(object source, string key)
        {
            var frameworkElement = source as FrameworkElement;
            var resourceDictionary = frameworkElement == null ? Application.Current.Resources : frameworkElement.Resources;
            object value;
            if (resourceDictionary.TryGetValue(key, out value))
            {
                var dataTemplate = value as DataTemplate;
                if (dataTemplate != null) return dataTemplate;
            }
            return null;
        }

        #endregion
    }

Данный класс позволяет найти визуальную структуру объекта данных начиная поиск с ресурсов текущего элемента управления и завершая в ресурсах всего приложения. Он также может кэшировать шаблоны (свойство IsCacheEnabled) для более быстрого поиска нужной визуальной структуры. Поиск проводится по имени типа объекта с добавлением ключа по умолчанию (DefaultTemplateKey). Т.е. если объекты на карте в коде выглядят следующим образом:

Place.cs:
    /// <summary>
    /// The Place class.
    /// </summary>
    public abstract class Place
    {
        /// <summary>
        /// Get or set title.
        /// </summary>
        public String Title { get; set; }

        /// <summary>
        /// Get or set latitude.
        /// </summary>
        public Double Latitude { get; set; }

        /// <summary>
        /// Get or set longitude.
        /// </summary>
        public Double Longitude { get; set; }
    }

    /// <summary>
    /// Shop class.
    /// </summary>
    public class Shop : Place { }

    /// <summary>
    /// Poi class.
    /// </summary>
    public class Poi : Place { }

    /// <summary>
    /// Cinema class.
    /// </summary>
    public class Cinema : Place { }

то их шаблоны (с учетом того, что DefaultTemplateKey = MapTemplate) будут выглядеть так:

MainPage.xaml:
        <DataTemplate x:Key="CinemaMapTemplate">
            <maps:Pushpin Width="32" Height="32" Tapped="OnPushpinTapped">
                <maps:MapLayer.Position>
                    <maps:Location Latitude="{Binding Latitude}" Longitude="{Binding Longitude}"></maps:Location>
                </maps:MapLayer.Position>
                <maps:Pushpin.Template>
                    <ControlTemplate>
                        <Grid RenderTransformOrigin="0.5,0.5">
                            <Image Source="/Assets/cinema.png" Width="32" Height="32" />
                            <Grid.RenderTransform>
                                <TranslateTransform X="-16" Y="-16"/>
                            </Grid.RenderTransform>
                        </Grid>
                    </ControlTemplate>
                </maps:Pushpin.Template>
            </maps:Pushpin>
        </DataTemplate>

        <DataTemplate x:Key="PoiMapTemplate">
            <maps:Pushpin Width="32" Height="32" Tapped="OnPushpinTapped">
                <maps:MapLayer.Position>
                    <maps:Location Latitude="{Binding Latitude}" Longitude="{Binding Longitude}"></maps:Location>
                </maps:MapLayer.Position>
                <maps:Pushpin.Template>
                    <ControlTemplate>
                        <Grid RenderTransformOrigin="0.5,0.5">
                            <Image Source="/Assets/poi.png" Width="32" Height="32" />
                            <Grid.RenderTransform>
                                <TranslateTransform X="-16" Y="-16"/>
                            </Grid.RenderTransform>
                        </Grid>
                    </ControlTemplate>
                </maps:Pushpin.Template>
            </maps:Pushpin>
        </DataTemplate>

        <DataTemplate x:Key="ShopMapTemplate">
            <maps:Pushpin Width="32" Height="32" Tapped="OnPushpinTapped">
                <maps:MapLayer.Position>
                    <maps:Location Latitude="{Binding Latitude}" Longitude="{Binding Longitude}"></maps:Location>
                </maps:MapLayer.Position>
                <maps:Pushpin.Template>
                    <ControlTemplate>
                        <Grid RenderTransformOrigin="0.5,0.5">
                            <Image Source="/Assets/shop.png" Width="32" Height="32" />
                            <Grid.RenderTransform>
                                <TranslateTransform X="-16" Y="-16"/>
                            </Grid.RenderTransform>
                        </Grid>
                    </ControlTemplate>
                </maps:Pushpin.Template>
            </maps:Pushpin>
        </DataTemplate>

И чтобы получить тот результат, который я привел в самом начале статьи, создадим коллекцию объектов Place и добавим в нее несколько объектов:

MainPage.xaml.cs:
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            Items = new ObservableCollection<Place>();
            DefaultViewModel["Items"] = Items;
            // Create shop
            Items.Add(new Shop { Title = "Shop №1", Latitude = 55.801843, Longitude = 37.531883 });
            Items.Add(new Shop { Title = "Shop №2", Latitude = 55.748922, Longitude = 37.538284 });
            Items.Add(new Shop { Title = "Shop №3", Latitude = 55.748922, Longitude = 37.565991 });
            Items.Add(new Shop { Title = "Shop №4", Latitude = 55.845758, Longitude = 37.665674 });
            Items.Add(new Shop { Title = "Shop №5", Latitude = 55.754775, Longitude = 37.621324 });
            // Create cinema
            Items.Add(new Cinema { Title = "Cinema №1", Latitude = 55.766203, Longitude = 37.607514 });
            Items.Add(new Cinema { Title = "Cinema №2", Latitude = 55.7436842, Longitude = 37.508058 });
            Items.Add(new Cinema { Title = "Cinema №3", Latitude = 55.726944, Longitude = 37.579712 });
            // Create Poi
            Items.Add(new Poi { Title = "Poi №1", Latitude = 55.680868, Longitude = 37.601534 });
            Items.Add(new Poi { Title = "Poi №2", Latitude = 55.677425, Longitude = 37.695776 });
            Items.Add(new Poi { Title = "Poi №3", Latitude = 55.706514, Longitude = 37.678524 });
            Items.Add(new Poi { Title = "Poi №4", Latitude = 55.774617, Longitude = 37.753170 });
            Items.Add(new Poi { Title = "Poi №5", Latitude = 55.656901, Longitude = 37.554735 });
        }

Это все интересно, но куда интересней предоставить пользователю более подробную информацию об объекте на карте когда он выберет тот или иной объект. Для этого в шаблонах я подписался на событие Tapped, в обработке которого я буду заменять выделенный объект другим:

MainPage.xaml.cs:
        private void OnPushpinTapped(object sender, TappedRoutedEventArgs e)
        {
            var pin = sender as Pushpin;
            if(pin == null) return;
            var place = pin.DataContext as Place;
            if(place == null) return;
            var index = Items.IndexOf(place);
            if(index > 0)
            {
                Items[index] = new Balloon(place);
            }
        }

Объект Balloon также наследуется от класса Place, но будет имеет немного другую визуальную структуру:

MainPage.xaml:
        <DataTemplate x:Key="BalloonMapTemplate">
            <maps:Pushpin Text="{Binding Title}" Width="256" Height="128">
                <maps:MapLayer.Position>
                    <maps:Location Latitude="{Binding Latitude}" Longitude="{Binding Longitude}"></maps:Location>
                </maps:MapLayer.Position>
                <maps:Pushpin.Template>
                    <ControlTemplate>
                        <Grid x:Name="ContentGrid" RenderTransformOrigin="0.5,0.5">
                            <Path Width="256" Height="128" Stretch="Fill" Fill="WhiteSmoke" Stroke="Black" StrokeThickness="1"
                                  Data="F1 M 33,51L 36.4167,61.75L 24,51L 19,51L 19,22L 57,22L 57,51L 33,51 Z "/>
                            <ContentPresenter RenderTransformOrigin="0.5,0.5" Content="{Binding Title}" Foreground="Black" FontSize="24"
                                              VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" Margin="0,-30,0,0"/>
                            <Grid.RenderTransform>
                                <TranslateTransform X="-120" Y="-128"/>
                            </Grid.RenderTransform>
                        </Grid>
                    </ControlTemplate>
                </maps:Pushpin.Template>
            </maps:Pushpin>
        </DataTemplate>

Этот шаблон не что иное как облако, которое содержит название объекта. Тем самым окончательный результат будет выглядеть немного изящней:


Исходный код

Комментариев нет:

Отправить комментарий