ClemensFischer / XAML-Map-Control

XAML Map Control Library
Microsoft Public License
204 stars 61 forks source link

Drag items on the map #72

Closed mennowo closed 3 years ago

mennowo commented 3 years ago

Hello, would it be do-able to enable the user to drag items on the map, and monitor changes in locations via databinding? I am having a hard time getting my head around what template/style I should use for this (Thumb?) and how to databind Location from the map to the ViewModel. Sorry to be using an issue as for a question like this, feel free to just close this. Any kind of pointer would be welcome. Greets, Menno

mennowo commented 3 years ago

Meanwhile, I managed doing this. For future reference, here is a small class for this purpose:

public class DragPin : Pushpin
{
    private bool _isDragging = false;

    public DragPin() : base()
    {
        MouseLeftButtonDown += OnMouseLeftButtonDown;
        MouseRightButtonDown += OnMouseRightButtonDown;
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (sender is DragPin p)
        {
            var map = p.GetVisualAncestor<Map>();
            if (map != null)
            {
                map.MouseUp += ParentMap_MouseLeftButtonUp;
                map.PreviewMouseMove += ParentMap_MouseMove;

                _isDragging = true;
            }
        }
    }

    private void ParentMap_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        // Left Mouse Button released, stop dragging the Pushpin
        if (sender is Map m)
        {
            m.MouseUp -= ParentMap_MouseLeftButtonUp;
            m.MouseMove -= ParentMap_MouseMove;
        }
        _isDragging = false;
    }

    void ParentMap_MouseMove(object sender, MouseEventArgs e)
    {
        if (sender is Map m)
        {
            // Check if the user is currently dragging the Pushpin
            if (_isDragging)
            {
                // If so, the Move the Pushpin to where the Mouse is.
                var mouseMapPosition = e.GetPosition(m);
                var mouseGeocode = m.ViewToLocation(mouseMapPosition);
                Location = mouseGeocode;
                e.Handled = true;
            }
        }
    }
}

This class uses an extension method VisualTreeExtensions.GetVisualAncestor, which can be easily found online, for example here.

To use the class, use it in the Template for a given set of MapItems. For example:

<Style x:Key="DraggablePinStyle" TargetType="map:MapItem" d:DataContext="{d:DesignInstance viewModels:DragPinViewModel}">
    <Setter Property="map:MapPanel.Location" Value="{Binding Location,Mode=TwoWay}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="map:MapItem">
                <views:DragPin map:MapPanel.Location="{Binding Path=Location,Mode=TwoWay}" Style="{StaticResource DragPinStyle}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Here DragPinStyle obiously is a style that defines what the pin will look like on the map. Now we can easily databind a set of items to be displayed - and dragged - on the map:

<map:MapItemsControl ItemsSource="{Binding Path=DraggablePins}"
    ItemContainerStyle="{StaticResource DraggablePinStyle}" />

It may not be the most elegant way, but this allows databinding Locations for items that can be dragged by the user.