Bing Maps and MVVM
I'm trying to do a school project where we are to create a Silverlight application that uses bing map as a kind of map editor for placement of chargeable stations for cars.
Example:
There are some requirements, it must support drag and drop and we must use MVVM (Model View View-Model). Now we have finished the drag and drop feature by the having a MapLayer with an array of Image children, and then hooked up events that enable drag and drop (See the code under). But now we face a problem, how can we hook up a ViewModel to this, I simply just can’t see it :(
I am not asking for a complete solution, but some help would be great.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.IO;
using Microsoft.Maps.MapControl;
namespace BingMapDragDrop
{
public partial class MainPage : UserControl
{
private MapAddType AddType = MapAddType.None;
private Location _myhome = new Location(55.6686512716393, 12.5481431962938, 0);
private MapLayer EndNodeLayer;
private double HideEndNodeLayer = 10.0;
private MapLayer EndNodeIntermediatedLayer;
private double HideEndNodeIntermediatedLayer = 10.0;
private MapLayer RootNodeLayer;
private double HideRootNodeLayer = 0.0;
private MapLayer RootNodeIntermediatedLayer;
private double HideRootNodeIntermediatedLayer = 5.0;
public MainPage()
{
EndNodeLayer = new MapLayer();
EndNodeIntermediatedLayer = new MapLayer();
RootNodeLayer = new MapLayer();
RootNodeIntermediatedLayer = new MapLayer();
InitializeComponent();
BingMap.SetView(_myhome, 15);
BingMap.ViewChangeOnFrame += new EventHandler<MapEventArgs>(BingMap_ViewChangeOnFrame);
// Adding the layers
BingMap.Children.Add(EndNodeIntermediatedLayer);
BingMap.Children.Add(EndNodeLayer);
BingMap.Children.Add(RootNodeIntermediatedLayer);
BingMap.Children.Add(RootNodeLayer);
}
private void AddEndNode(Location location, MapAddType type)
{
string url;
if (type == MapAddType.Home)
url = "Images/Home.png";
else if (type == MapAddType.HomeWithChargingSpot)
url = "Images/HomeWithChargingSpot.png";
else if (type == MapAddType.Workplace)
url = "Images/Workplace.png";
else if (type == MapAddType.WorkplaceWithChargingSpot)
url = "Images/WorkplaceWithChargingSpot.png";
else if (type == MapAddType.PublicChargningSpot)
url = "Images/PublicChargningSpot.png";
else if (type == MapAddType.FastChargingStation)
url = "Images/FastChargingStation.png";
else
return;
var image = new Image
{
Source = new BitmapImage(new Uri(url, UriKind.RelativeOrAbsolute)),
Width = 50,
Height = 50
};
AddImageToLayerAsDragAbleObject(image, location, EndNodeLayer);
}
private void AddPowerPlant(Location location)
{
var image = new Image
{
Source = new BitmapImage(new Uri("Images/Powerplant-New.png", UriKind.RelativeOrAbsolute)),
Width = 50,
Height = 50
};
AddImageToLayerAsDragAbleObject(image, location, EndNodeLayer);
}
#region Bing Map Events, not related to D&D
// Some events dose not exists so we need to make some our self.
private double bingZoom = 0.0;
void BingMap_ViewChangeOnFrame(object sender, MapEventArgs e)
{
if (BingMap.ZoomLevel != bingZoom)
{
bingZoom = BingMap.ZoomLevel;
BingMapZoomLevelChanged(sender, e);
}
}
private void BingMap_Loaded(object sender, RoutedEventArgs e)
{
}
private void BingMap_MouseClick(object sender, MapMouseEventArgs e)
{
if(AddType == MapAddType.None)
return;
Location loc;
if (!BingMap.TryViewportPointToLocation(e.ViewportPoint, out loc))
return;
switch (AddType)
{
case MapAddType.Powerplant:
AddPowerPlant(loc);
break;
case MapAddType.FastChargingStation:
case MapAddType.PublicChargningSpot:
case MapAddType.WorkplaceWithChargingSpot:
case MapAddType.Workplace:
case MapAddType.HomeWithChargingSpot:
case MapAddType.Home:
AddEndNode(loc, AddType);
break;
}
AddType = MapAddType.None;
}
private void BingMapZoomLevelChanged(object sender, MapEventArgs e)
{
if (BingMap.ZoomLevel <= HideEndNodeLayer && EndNodeLayer.Visibility == Visibility.Visible)
EndNodeLayer.Visibility = Visibility.Collapsed;
else if (BingMap.ZoomLevel > HideEndNodeLayer && EndNodeLayer.Visibility == Visibility.Collapsed)
EndNodeLayer.Visibility = Visibility.Visible;
if (BingMap.ZoomLevel >= HideEndNodeIntermediatedLayer && EndNodeLayer.Visibility == Visibility.Visible)
EndNodeIntermediatedLayer.Visibility = Visibility.Collapsed;
else if (BingMap.ZoomLevel > HideEndNodeIntermediatedLayer && EndNodeLayer.Visibility == Visibility.Collapsed)
EndNodeIntermediatedLayer.Visibility = Visibility.Visible;
if (BingMap.ZoomLevel <= HideRootNodeLayer && EndNodeLayer.Visibility == Visibility.Visible)
RootNodeLayer.Visibility = Visibility.Collapsed;
else if (BingMap.ZoomLevel > HideRootNodeLayer && EndNodeLayer.Visibility == Visibility.Collapsed)
RootNodeLayer.Visibility = Visibility.Visible;
if (BingMap.ZoomLevel <= HideRootNodeIntermediatedLayer && EndNodeLayer.Visibility == Visibility.Visible)
RootNodeIntermediatedLayer.Visibility = Visibility.Collapsed;
else if (BingMap.ZoomLevel > HideRootNodeIntermediatedLayer && EndNodeLayer.Visibility == Visibility.Collapsed)
RootNodeIntermediatedLayer.Visibility = Visibility.Visible;
}
#endregion
#region This is where the dragging magic happens
private void AddImageToLayerAsDragAbleObject(Image image, Location location, MapLayer mapLayer)
{
image.MouseLeftButtonDown += new MouseButtonEventHandler(ImageMouseLeftButtonDown);
var position = PositionOrigin.Center;
mapLayer.AddChild(image, location, position);
}
private bool _isDragging = false;
private Image _dragingObject;
private void ImageMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isDragging = true;
// We need to save the object, so we are able to set the location on release
_dragingObject = (Image)sender;
// Here we add the events, be sure to remove them at release!
BingMap.MousePan += new EventHandler<MapMouseDragEventArgs>(BingMapMousePan);
BingMap.MouseLeftButtonUp += new MouseButtonEventHandler(BingMapMouseLeftButtonUp);
BingMap.MouseMove += new MouseEventHandler(BingMapMouseMove);
}
// Event that is called when an image is move
private void BingMapMouseMove(object sender, MouseEventArgs e)
{
var map = (Map)sender;
if (!_isDragging) return;
// The the location of the mouse
var mouseMapPosition = e.GetPosition(map);
var mouseGeocode = map.ViewportPointToLocation(mouseMapPosition);
// Set location
MapLayer.SetPosition(_dragingObject, mouseGeocode);
}
// Event that is called when an image is released
private void BingMapMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// Remove the events
BingMap.MousePan -= BingMapMousePan;
BingMap.MouseLeftButtonUp -= BingMapMouseLeftButtonUp;
BingMap.MouseMove -= BingMapMouseMove;
// Disable dragging
_isDragging = false;
}
// Event that is called when the map is panning
private void BingMapMousePan(object sender, MapMouseDragEventArgs e)
{
// We don't want the map to pan if we are dragging
if (_isDragging)
e.Handled = true;
}
#endregion
#region Menu
private void MenuMouseLeftButtonUp(object sender, MouseButtonEve开发者_如何学编程ntArgs e)
{
if ((String)((Image)sender).Tag == "Powerplant")
AddType = AddType == MapAddType.Powerplant ? MapAddType.None : MapAddType.Powerplant;
if ((String)((Image)sender).Tag == "Home")
AddType = AddType == MapAddType.Home ? MapAddType.None : MapAddType.Home;
if ((String)((Image)sender).Tag == "HomeWithChargingSpot")
AddType = AddType == MapAddType.HomeWithChargingSpot ? MapAddType.None : MapAddType.HomeWithChargingSpot;
if ((String)((Image)sender).Tag == "Workplace")
AddType = AddType == MapAddType.Workplace ? MapAddType.None : MapAddType.Workplace;
if ((String)((Image)sender).Tag == "WorkplaceWithChargingSpot")
AddType = AddType == MapAddType.WorkplaceWithChargingSpot ? MapAddType.None : MapAddType.WorkplaceWithChargingSpot;
if ((String)((Image)sender).Tag == "PublicChargningSpot")
AddType = AddType == MapAddType.PublicChargningSpot ? MapAddType.None : MapAddType.PublicChargningSpot;
if ((String)((Image)sender).Tag == "FastChargingStation")
AddType = AddType == MapAddType.FastChargingStation ? MapAddType.None : MapAddType.FastChargingStation;
}
#endregion
#region Cursor image
private bool IsCursorImageSet = false;
private void UserControl_MouseMove(object sender, MouseEventArgs e)
{
PlaceCursorImage(e.GetPosition(this));
}
private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
PlaceCursorImage(e.GetPosition(this));
}
private void PlaceCursorImage(Point location)
{
if (AddType == MapAddType.None && !IsCursorImageSet)
return;
if (AddType == MapAddType.None && IsCursorImageSet)
{
IsCursorImageSet = false;
CursorImage.Visibility = Visibility.Collapsed;
return;
}
Canvas.SetTop(CursorImage, location.Y + 5.0);
Canvas.SetLeft(CursorImage, location.X + 5.0);
if (!IsCursorImageSet)
{
IsCursorImageSet = true;
switch (AddType)
{
case MapAddType.Powerplant:
CursorImage.Source =
new BitmapImage(new Uri("Images/Powerplant-New.png", UriKind.RelativeOrAbsolute));
break;
case MapAddType.Home:
CursorImage.Source =
new BitmapImage(new Uri("Images/Home.png", UriKind.RelativeOrAbsolute));
break;
case MapAddType.HomeWithChargingSpot:
CursorImage.Source =
new BitmapImage(new Uri("Images/HomeWithChargingSpot.png", UriKind.RelativeOrAbsolute));
break;
case MapAddType.Workplace:
CursorImage.Source =
new BitmapImage(new Uri("Images/Workplace.png", UriKind.RelativeOrAbsolute));
break;
case MapAddType.WorkplaceWithChargingSpot:
CursorImage.Source =
new BitmapImage(new Uri("Images/WorkplaceWithChargingSpot.png", UriKind.RelativeOrAbsolute));
break;
case MapAddType.PublicChargningSpot:
CursorImage.Source =
new BitmapImage(new Uri("Images/PublicChargningSpot.png", UriKind.RelativeOrAbsolute));
break;
case MapAddType.FastChargingStation:
CursorImage.Source =
new BitmapImage(new Uri("Images/FastChargingStation.png", UriKind.RelativeOrAbsolute));
break;
default:
return;
}
CursorImage.Visibility = Visibility.Visible;
CursorImage.Width = 40;
CursorImage.Height = 40;
CursorImage.Stretch = Stretch.Uniform;
}
}
#endregion
}
}
My WPF code
<UserControl x:Class="BingMapDragDrop.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:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"
mc:Ignorable="d" MouseMove="UserControl_MouseMove" Width="800" Height="600" MouseLeftButtonUp="UserControl_MouseLeftButtonUp">
<Canvas IsHitTestVisible="True">
<StackPanel HorizontalAlignment="Left" Name="stackPanelMenu" Width="75" Margin="0,12,0,12" Canvas.Top="0" Height="588">
<Image Name="imagePowerplant" Stretch="Uniform" Width="35" Source="/BingMapDragDrop;component/Images/Powerplant-New.png" Tag="Powerplant" MouseLeftButtonUp="MenuMouseLeftButtonUp" />
<Image Name="imageHome" Stretch="Uniform" Width="35" Source="/BingMapDragDrop;component/Images/Home.png" Tag="Home" MouseLeftButtonUp="MenuMouseLeftButtonUp" />
<Image Name="imageHomeWithChargingSpot" Stretch="Uniform" Width="35" Source="/BingMapDragDrop;component/Images/HomeWithChargingSpot.png" Tag="HomeWithChargingSpot" MouseLeftButtonUp="MenuMouseLeftButtonUp" />
<Image Name="imageWorkplace" Stretch="Uniform" Width="35" Source="/BingMapDragDrop;component/Images/Workplace.png" Tag="Workplace" MouseLeftButtonUp="MenuMouseLeftButtonUp" />
<Image Name="imageWorkplaceWithChargingSpot" Stretch="Uniform" Width="35" Source="/BingMapDragDrop;component/Images/WorkplaceWithChargingSpot.png" Tag="WorkplaceWithChargingSpot" MouseLeftButtonUp="MenuMouseLeftButtonUp" />
<Image Name="imagePublicChargningSpot" Stretch="Uniform" Width="35" Source="/BingMapDragDrop;component/Images/PublicChargningSpot.png" Tag="PublicChargningSpot" MouseLeftButtonUp="MenuMouseLeftButtonUp" Height="49" />
<Image Name="imageFastChargingStation" Stretch="Uniform" Width="35" Source="/BingMapDragDrop;component/Images/FastChargingStation.png" Tag="FastChargingStation" MouseLeftButtonUp="MenuMouseLeftButtonUp" />
</StackPanel>
<m:Map x:Name="BingMap" CredentialsProvider="[Insert credentials here]" Mode="Aerial" Loaded="BingMap_Loaded" MouseClick="BingMap_MouseClick" Canvas.Left="72" Canvas.Top="0" Margin="0" Height="600" Width="728" />
<Image Name="CursorImage" Visibility="Collapsed" IsHitTestVisible="False" Opacity="0.5" />
</Canvas>
</UserControl>
I hate to tell you this, but the code you already wrote looks nothing like WPF or MVVM code. You are doing this in a very WinForms-ish way.
It won't be so much "converting" your code to MVVM as "redoing" it that way.
The first thing you want to ask is, "what should I have in my model?" Obviously the model consists of a list of objects such as homes and power plants. Each of these objects has at least a type and a location.
I recommend you define some kind of MappableObject class and use an ObservableCollection in your model to store your mappable objects.
Your main control should obviously be an ItemsControl that uses a Canvas for its ItemsPanel. The item template must set Canvas.Left and Canvas.Top to match the item's location. You would need a converter to convert from a Location to a Point.
Now if your ItemsControl has its ItemsSource bound to your ObservableCollection, every time a mappable object is added to the collection it will appear in its Location. Any time you change its Location it will move to the new place on the screen.
You will need something to handle the drag events and show the cursor. I would use code roughly like what you already have, except put it inside a UserControl that is used in your ItemTemplate. It can find its container and use that to map drag coordinates to map locations. As the drag progresses it can update the MappableObject's location property. Remember to transform all coordinates onto the map using TransformToVisual instead of using local coordinates for the control.
To get the right image for an object, use a converter: Your model knows what kind of object it is, and you can name your .png files to match so your converter can easily grab the right .png out of your Images directory by constructing the Url directly from the object type.
I hope these suggestions help you get moving in the right direction. Doing this in WPF using MVVM is quite simple and much cleaner than doing the old WinForms way, but you will need to use a lot of new techinques you aren't used to so there will be a learning curve.
Best wishes for your success.
精彩评论