Bing Map polyline using MVVM pattern XAML - xaml

I am working on widows phone 8.1 map based application.I want to know how can I draw a map polyline using MVVM pattern. I've already achieved this using the code behind for first creating the polyline and then adding it. My question is can I define a polyline in the XAML itself and give it a source binding to one of my observable collections of type BasicGeopositions in my viewmodel. If yes then how?
Data to be plotted using polyline:
is a list of BasicGeoposition that contains latitudes and longitudes of all the points I need to connect. I tried this way <Maps:MapPolyline Path="{Binding Trip.PTSPositions}"/> but it didn't work. PTSPositions is a list of BasicGeoposition.
What i want to perform:
I want to
MapPolyline polyLine = new MapPolyline() { StrokeColor = Colors.Blue, StrokeThickness = 5 };
polyLine.Path = new Geopath(Trip.PTSPositions);
MyMap.MapElements.Add(polyLine);
perform the above code behind code in XAML using MVVM where the Trip.PTSPositions would be fetched dynamically and the map polyline would be drawn using data binding.
I searched online a lot. I couldn't find anything that does not use code behind for polyline

Here is the implementation suggested up in the comments.
This is the attached bindable property implementation for MapControl and it stays in the Widows Phone 8.1 project:
public class Polyline
{
public static readonly DependencyProperty PathProperty =
DependencyProperty.RegisterAttached(
"Path",
typeof(IBasicGeoposition[]),
typeof(Polyline),
new PropertyMetadata(null, OnPathChanged));
public static void SetPath(UIElement element, IBasicGeoposition[] value)
{
element.SetValue(PathProperty, value);
}
public static IBasicGeoposition[] GetPath(UIElement element)
{
return (IBasicGeoposition[]) element.GetValue(PathProperty);
}
private static void OnPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var mapControl = d as MapControl;
if (mapControl == null)
{
throw new InvalidOperationException(
"Polyline.Track property can only be attached to a MapControl!");
}
mapControl.MapElements.Clear();
mapControl.MapElements.Add(CreateMapPolyline(GetPath(mapControl)));
}
private static MapPolyline CreateMapPolyline(IEnumerable<IBasicGeoposition> track)
{
return new MapPolyline
{
Path = new Geopath(track.Select(x =>
new BasicGeoposition
{
Altitude = x.Altitude,
Latitude = x.Latitude,
Longitude = x.Longitude,
})),
StrokeColor = Colors.Red,
StrokeThickness = 3,
StrokeDashed = false
};
}
}
This interface stays in the PCL, probably close to it's implementation (you'll have to add your custom class implementing the interface):
public interface IBasicGeoposition
{
double Altitude { get; set; }
double Latitude { get; set; }
double Longitude { get; set; }
}
Than in view model you have Trip.PTSPositions which is an array of IBasicGeoposition. And in the view (XAML), you'll have:
<maps:MapControl attached:Polyline.Path="{Binding Trip.PTSPositions}"/>

Related

Bind to an Item of a Dependency Collection

I'm trying to create a custom control that has a header and a footer and body. The idea is that the body of the report is a custom stack panel control that will allow the user to indicate page orientation and grouping. I created a dependency property on the custom UC to accept an IList of the custom stack panel. What I am trying to do is bind to one of the stack panels in the list. But for some reason the binding is not working.
The ReportPage:
public class ReportPage : StackPanel
{
//Nothing right now but will eventually include controls for page orientation and size (8.5x11, 11x17, etc.)
}
The UserControl code behind:
public partial class Report : UserControl, INotifyPropertyChanged
{
public Report()
{
ReportPages = new List<ReportPage>();
}
public static readonly DependencyProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
ActivePage = value[0];
OnPropertyChanged(nameof(ActivePage));
}
}
private ReportPage _activePage;
public ReportPage ActivePage
{
get => _activePage;
set
{
_activePage = value;
OnPropertyChanged(nameof(ActivePage));
}
{
}
The UserControl xaml:
<Grid>
<!--Some xaml for the header and footer.-->
<ContentControl Content="{Binding ActivePage, RelativeSource={RelativeSource, FindAncestor, AncestorType=local:Report}}"/>
</Grid>
Here is how I am consuming the custom control. This should, in my mind at least, make three "pages" which I can toggle between using a button control that I didn't share.
<reportEngine:Report>
<reportEngine:Report.ReportPages>
<reportEngine:ReportPage>
<TextBlock>This is Page 1</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 2</TextBlock>
</reportEngine:ReportPage>
<reportEngine:ReportPage>
<TextBlock>This is Page 3</TextBlock>
</reportEngine:ReportPage>
</reportEngine:Report.ReportPages>
</reportEngine:Report>
Any Ideas why the binding isn't working?
So I at least found a quick work around. I utilized the Collection Changed Event handler pattern from this answer and modified it for static dependency properties. Then, to get the values from the collection bound to the dependency property I create a static instance of the Report object in the constructor and use that to pass various values back to the object from the collection. Something like this:
public partial class Report : UserControl, INotifyPropertyChanged
{
private static Report _thisReport;
public Report()
{
InitializeComponent();
ReportPages = new ObservableCollection<ReportPage>();
_thisReport = this;
}
public static readonly DependencyProperty ReportPagesProperty =
DependencyProperty.Register("ReportPages", typeof(IList), typeof(Report), new FrameworkPropertyMetadata(ReportPagesChanged));
public IList ReportPages
{
get => (IList)GetValue(ReportPagesProperty);
set
{
SetValue(ReportPagesProperty, value);
//Update some other properties associated with the control (Total Page Numbers, etc.)
}
}
private static void ReportPagesChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
var newColl = (INotifyCollectionChanged)eventArgs.NewValue;
if (newColl != null)
newColl.CollectionChanged += ReportPages_CollectionChanged;
var oldColl = (INotifyCollectionChanged)eventArgs.OldValue;
if (oldColl != null)
oldColl.CollectionChanged -= ReportPages_CollectionChanged;
}
private static void ReportPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs eventArgs)
{
var newPages = (IList<ReportPage>) sender;
//Updates properties of the Report control.
_thisReport.ActivePage = newPages[0];
_thisReport.TotalPageNumber = newPages.Count;
}
}
Whether this is "correct" or not I couldn't say, but it works. If someone has a better answer I will change the answer.

Map Polyline on Bing Map Control

I am working on a UWP navigation app.
What I want to do?
I want to draw multiple (10 to be exact) polylines on the Map control.
I want one to be different color, while the others are grey.
Once either of the polyline is selected the new selected polyline becomes of the primary color while the others are greyed out. Something like the Maps app for multiple routes just on a larger scale that shows movements of shipping trucks.
Everywhere online there are ways to implement the polyline via c# or code behind, but I want to do it via XAML as adding 10 polylines from codebehind doesn't give much flexibility with events and opacity and Tags and Names.
What all have I tried:
I tried creating an attached polyline to the mapElement but the issue with the approach is that I'll have to each time remove and recreate the polyline to change colors. More about this here. Also, it's just a pretty way of implementing polyline from code behind.
What am I doing currently:
I added the DataTemplate of the PolyLine in my Page.Resources like below:
<DataTemplate x:Key="PolylineDataTemplate" x:DataType="polyLineData:IPolylinePath">
<Polyline Points="{x:Bind Polyline,Mode=OneWay}" Fill="{x:Bind PolylineColor,Mode=OneWay}" Tag="{x:Bind PolylineTag,Mode=OneWay}" StrokeThickness="{x:Bind PolylineThinkness}" />
</DataTemplate>`
Where the IPolylinePath is defined as:
public interface IPolylinePath
{
SolidColorBrush PolylineColor { get; set; }
int PolylineThinkness { get; set; }
string PolylineTag { get; set; }
IEnumerable<IBasicGeoposition> PolylinePoints { get; set; }
Geopath PolylineGeopath { get; }
PointCollection Polyline { get; }
}`
My Polyline property is populated as below:
public PointCollection Polyline
{
get
{
PointCollection returnObject = new PointCollection();
//could have used LINQ but wanted to check if the collection is being populated correctly
foreach (var location in PolylinePoints)
{
returnObject.Add(new Windows.Foundation.Point(location.Latitude, location.Longitude));
}
return returnObject;
}
}
And I am just calling it in the MapItems control like below:
<maps:MapControl x:Name="MyMap" >
<maps:MapItemsControl ItemTemplate="{StaticResource PolylineDataTemplate}" ItemsSource="{x:Bind ViewModel.PolylinePoints}"/>
</maps:MapControl>
The Issue is:
The code works perfectly well. Just the polyline is not visible.
I thought that it's just small that's why I can't see it. So I increased the size and the distance and it just appears as a small arc on the top left corner (with some spacing) and doesn't get scoped or panned.
Can anyone please help?
Just the polyline is not visible.
Firstly, it seems like you didn't give a Stoke property for the Polyline, by default it is null. Your code snippet set color with Fill property it is not for the color of the line, you may find the value of StrokeThickness has no influence with the Polyline and a straight line will not been seen without the Stroke property. So here the color should be bind to Stroke property.
it just appears as a small arc on the top left corner
It is because you build points of the Points property for the Polyline by code line new Windows.Foundation.Point(location.Latitude, location.Longitude).
The latitude and longitude defined the element location on the MapControl not the application view. In another words, actually you add a GeoPoint to the PointCollection, not a Point. So you may need to transfer the GeoPoint to Point by GetOffsetFromLocation(Geopoint, Point) method.
doesn't get scoped or panned.
For this, the Polyline is actually a shape not a MapElement. You should control its MapLocation by listening the map zoom events. If you want it be pan with the map, you should use Map​Polyline. For sample of this please reference the scenario 2 of the official sample. But MapPolyline cannot be added directly by binding, only code behind.
A completed simple sample based on yours for testing as follows:
XAML
<Page.Resources>
<DataTemplate x:Key="PolylineDataTemplate" x:DataType="local:PolylinePath">
<Polyline
Points="{x:Bind Polyline}"
Stroke="{x:Bind PolylineColor}"
StrokeThickness="{x:Bind PolylineThinkness}"
Tag="{x:Bind PolylineTag}" />
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<maps:MapControl x:Name="MyMap" Loaded="MyMap_Loaded">
<maps:MapItemsControl x:Name="mapitems" ItemTemplate="{StaticResource PolylineDataTemplate}" />
</maps:MapControl>
<Button
x:Name="btnaddpolyline"
Click="btnaddpolyline_Click"
Content="add" />
</Grid>
Code behind:
public sealed partial class MainPage : Page
{
public List<PolylinePath> polylines { get; set; }
Geopoint SeattleGeopoint = new Geopoint(new BasicGeoposition() { Latitude = 47.604, Longitude = -122.329 });
public MainPage()
{
this.InitializeComponent();
}
private void MyMap_Loaded(object sender, RoutedEventArgs e)
{
MyMap.Center = SeattleGeopoint;
MyMap.ZoomLevel = 16;
}
private void btnaddpolyline_Click(object sender, RoutedEventArgs e)
{
polylines = new List<PolylinePath>()
{
new PolylinePath(MyMap)
{
PolylineColor=new SolidColorBrush(Colors.Red),
PolylineThinkness=3,
PolylineTag="testing",
PolylinePoints = new List<BasicGeoposition>()
{
SeattleGeopoint.Position,
new BasicGeoposition()
{
Latitude = SeattleGeopoint.Position.Latitude + 0.003,
Longitude = SeattleGeopoint.Position.Longitude - 0.003
}
}
}
};
mapitems.ItemsSource = polylines;
}
}
public class PolylinePath
{
public PolylinePath(MapControl MyMap)
{
this.MyMap = MyMap;
}
MapControl MyMap;
public SolidColorBrush PolylineColor { get; set; }
public int PolylineThinkness { get; set; }
public string PolylineTag { get; set; }
public IEnumerable<BasicGeoposition> PolylinePoints { get; set; }
public PointCollection Polyline
{
get
{
PointCollection returnObject = new PointCollection();
//could have used LINQ but wanted to check if the collection is being populated correctly
foreach (var location in PolylinePoints)
{
Point actualpoint;
MyMap.GetOffsetFromLocation(new Geopoint(location), out actualpoint);
returnObject.Add(actualpoint);
}
return returnObject;
}
}
}

User notification MVVM

I am trying to create a user notification. Ideally a toast-like notification that shows up in the corner for about three seconds.
I am using MVVM-light and I think the notification could be done using its messenger-service.
I have this class:
public class NotificationSync
{
public string Messages { get; set; }
}
In one viewmodel i set up the Messenger like this:
Messenger.Default.Send(new NotificationSync()
{
Messages = "message"
});
And in my MainviewModel (which is the datacontext of the view) I listen for it like this:
Messenger.Default.Register<NotificationSync>(this, (action) =>
Mess = action.Messages );
Mess is a string property on the viewmodel:
private string mess;
public string Mess
{
get { return mess; }
set
{
mess = value;
RaisePropertyChanged("Mess");
}
}
What I would like to do with mess is to bind it to my view in a toast-like manner. I.E display it for some seconds in my view. Any tips on how to do this? Thank you.
What about a Visibility property for your toast plus a timer?
Messenger.Default.Register<NotificationSync>(this, (action) =>
Mess = action.Messages
ShowToast();
);
private void ShowToast()
{
IsToastVisible = true;
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = TimeSpan.FromSeconds(3);
dispatcherTimer.Start();
}
void OnTimerTick(Object sender, EventArgs args)
{
IsToastVisible = false;
}
This assumes the textbox to which Mess is bound, is also bound to IsToastVisible and it's using a VisibilityConverter.

GridView does not refresh changes to items when using LayoutAwarePage.DefaultViewModel

In a "Blank App" (Visual C#, Windows Store), I create a new "Grouped Items Page", then declare a MyItemViewModel class deriving from DependencyObject with a Dependency Property for a String Title.
This is the page's LoadState method:
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
this.DefaultViewModel["Groups"] = this.items;
this.items.Add(new MyItemViewModel { Title = "My Title" });
for (int i = 0; i < 5; i++)
{
await Task.Delay(1000);
this.items.First().Title += ".";
}
}
The expectation is that dots appear after the item's title every second. The actual output is simply "My Title" and nothing else happens.
By adding the following unreferenced dependency property the dots will then appear:
public MyItemViewModel blah
{
get { return (MyItemViewModel)GetValue(blahProperty); }
set { SetValue(blahProperty, value); }
}
public static readonly DependencyProperty blahProperty =
DependencyProperty.Register("blah", typeof(MyItemViewModel), typeof(GroupedItemsPage1), new PropertyMetadata(0));
Why does the GridView only refresh the Title property of the item view model when there is an unused dependency property with the same type?
Do view model classes always have to be explicitly declared as a dependency property somewhere in an app at least once?
DependencyObject is usually inherited by UIElement (e.g., Grid, TextBlock, etc.), and their properties are DependencyProperty which allows, for example, Binding.
A ViewModel should implement INotifyPropertyChanged instead of inherit from DependencyObject. If you look at sample templates like GridApp, you will see that BindableBase implements INotifyPropertyChanged.

Setting internal properties in composite WF4 Activities at design time

I want to create a composite Windows Workflow Activity (under .NET 4) that contains a predefined ReceiveAndSendReply Activity. Some of the properties are predefined, but others (particularly ServiceContractName) need to be set in the designer.
I could implement this as an Activity Template (the same way ReceiveAndSendReply is implemented), but would rather not. If I later change the template, I'd have to update all previously created workflows manually. A template would also permit other developers to change properties that should be fixed.
Is there a way to do this from a Xaml Activity? I have not found a way to assign an Argument value to a property of an embedded Activity. If not, what technique would you suggest?
I haven't done this using a composite XAML activity and am getting some errors when I try but doing so through a NativeActivity is no problem. See the example code below.
public class MyReceiveAndSendReply : NativeActivity
{
private Receive _receive;
private SendReply _sendReply;
public string ServiceContractName { get; set; }
public string OperationName { get; set; }
protected override bool CanInduceIdle
{
get { return true; }
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
_receive = _receive ?? new Receive();
_sendReply = _sendReply ?? new SendReply();
_receive.CanCreateInstance = true;
metadata.AddImplementationChild(_receive);
metadata.AddImplementationChild(_sendReply);
_receive.ServiceContractName = ServiceContractName;
_receive.OperationName = OperationName;
var args = new ReceiveParametersContent();
args.Parameters["firstName"] = new OutArgument<string>();
_receive.Content = args;
_sendReply.Request = _receive;
var results = new SendParametersContent();
results.Parameters["greeting"] = new InArgument<string>("Hello there");
_sendReply.Content = results;
base.CacheMetadata(metadata);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(_receive, ReceiveCompleted);
}
private void ReceiveCompleted(NativeActivityContext context, ActivityInstance completedInstance)
{
context.ScheduleActivity(_sendReply);
}
}