How can I bind Pushpin location in WPF using MVVM? - wpf

I'm developing a Desktop application that uses Bing maps and MVVM.
In the application, a user adds a Pushpin in the map by double clicking on it, the Pushpin location gets saved in an Event class and the Event class is sent through a WCF Service.
I would like to get the Latitude and Longitude from the Pushpin using data binding, however the compiler complains about DependencyProperty when I try to do that.
I managed to set the Latitude and Longitude in the ViewModel from the View, however I don't know if it's valid in MVVM. I have seen examples using MapsItemControls but I don't understand them.
ViewModel
private Event evt;
public Event Evt
{
get
{
return this.evt;
}
set
{
this.evt = value;
OnPropertyChanged("Event");
}
}
Map xaml
<m:Map Grid.RowSpan="5" Grid.Column="3" Margin="3"
Name="operatorMap"
CredentialsProvider="Map_key"
Center="19.4000,-99.1333"
ZoomLevel="5"
MouseDoubleClick="SetPushpinLocation" />
CodeBehind
private MaintenanceFormViewModel viewModel = new MaintenanceFormViewModel();
private Pushpin pin = null;
public MainWindow()
{
InitializeComponent();
this.Loaded += (s, e) =>
{
this.DataContext = this.viewModel;
};
}
private void SetPushpinLocation(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
Point mousePosition = e.GetPosition((UIElement)sender);
Location pinLocation = operatorMap.ViewportPointToLocation(mousePosition);
if (pin == null)
{
pin = new Pushpin();
operatorMap.Children.Add(pin);
}
pin.Location = pinLocation;
this.viewModel.Evt.Latitude = pinLocation.Latitude;
this.viewModel.Evt.Longitude = pinLocation.Longitude;
}

Bing Maps uses the attached property MapLayer.Position for positioning elements on the map.
Given a view model with a property of type Location
public class ViewModel : INotifyPropertyChanged
{
private Location location;
public Location Location
{
get { return location; }
set
{
location = value;
OnPropertyChanged("Location");
}
}
...
}
you can bind the position of a Pushpin like this:
<bm:Pushpin bm:MapLayer.Position="{Binding Location}"/>
Note that in the Windows Store App version of the Bing Maps SDK there seems to be a bug when you try to setup a binding in XAML. It says (still with Bing Maps SDK version 1.313.825.0)
Failed to assign to property 'Bing.Maps.MapLayer.Position'
You can however create a binding in code behind:
pushpin.SetBinding(MapLayer.PositionProperty,
new Binding { Path = new PropertyPath("Location") });

Personally, I'd add an attached property to the Bing map which would allow you to bind the lat/long to properties to your ViewModel. This would follow the MVVM pattern.
Google "attached property wpf" for a tutorial on attached properties, there are some good ones out there.
This is not to say that using code behind is bad: usually I get it working with code behind first, then port it into an attached property to adhere to the MVVM pattern, and for reusability and maintainability.
You mentioned an error related to dependecy properties. These are completely different to attached properties.
You add a dependency property to a user control that you write yourself.
You add an attached property to another 3rd party control you cannot alter or do not have the source code for. The rule of thumb is this: if you start with any code behind in a user control, you can shift it into an attached property to keep in line with the MVVM pattern.
Yes, attached properties are a bit of a learning curve, but persevere: this is one technique you will have to master before you can become an MVVM expert.

Related

Triggering an action of a UIElement from a ViewModel

I've implemented Wiesław Šoltés' awesome ZoomBorder, but I'm struggling a bit with WPF and MVVM. For the questions' completeness sake, ZoomBorder is a UIElement that inherits from Border and gives the user the possibility of zooming and panning the content of the inherited border. It also has the ability to reset the zoom and panning.
I'd like to make ZoomBorder react to an event being published by certain view model, so that when that event is published, ZoomBorder resets the zoom. In my implementation, the ZoomBorder's DataContext is a ContentViewModel, which has an IEventAggregator (Prism.Events) injected via Autofac. Ideally, I would have liked to inject the event aggregator directly into ZoomBorder, so that it can subscribe to the event, but I can't because the constructor needs to be parameterless.
So ContentViewModel has to subscribe to the event, but how would I invoke ZoomBorder's Reset method from ContentViewModel? I understand I'd be transgressing MVVM, but I don't know how else to do it. I thought about making ZoomBorder expose a Command via a dependency property, but then the Reset code would have to be on the view model, which it can't.
You can use the ServiceLocator inside of views or controls to resolve types from the container.
public ZoomBorder()
{
_eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
}
If you use AutoFac and the event aggregator without Prism, you can use the package Autofac.Extras.CommonServiceLocator and register your container to ServiceLocator.
var builder = new ContainerBuilder();
var container = builder.Build();
var csl = new AutofacServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => csl);
I would use binding.
Add a dependency property to Zoomborder.
public bool? ResetZoom
{
get
{
return (bool?)GetValue(ResetZoomProperty);
}
set
{
SetValue(ResetZoomProperty, value);
}
}
public static readonly DependencyProperty ResetZoomProperty =
DependencyProperty.Register("ResetZoom",
typeof(bool?),
typeof(CloseMe),
new PropertyMetadata(null, new PropertyChangedCallback(ResetZoomChanged)));
private static void ResetZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ZoomBorder zb = (ZoomBorder)d;
zb.Reset();
zb.SetCurrentValue(ResetZoomProperty, false);
}
You can then bind that to a public bool property in your ContentViewModel.
When set to true, the border will reset.
This video shows how to create abstractions from Views/UI components and call methods on them from a VM using an interface. Don't let the title fool you. This look like it will fit this scenario perfectly
"How to Close Windows from a ViewModel in C#"
https://youtu.be/U7Qclpe2joo
Instead of calling the Close method on a window, you can adapt it to call your Reset method on your control from the VM.

Custom Usercontrol with MVVM and Catel

I've created a custom usercontrol that's composed of a AutoCompleteBox with a Selected Item... till now I've implemented it in a way I don't like... I mean I've a XAML view, a Viewmodel and in the viewmodel I load data from a stored procedure.
Since the AutoComplete box is a third party UserControl I've added it to the XAML view and not defined as a custom usercontrol. What's the best practice to do so?
I think the fact that I'm using Catel as MVVM Framework is irrilevant right now..
Thanks
UPDATE #1
My usercontrols need to have some properties that are passed via XAML for example (LoadDefaultValue)
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding Model.PortfolioModel}" Height="25" LoadDefaultValue="True" Width="150" />
To achieve such a scenario I had to define a dependency property in my PortfolioChooserView defined as
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
Since if I would have defined it in Viewmodel only I wouldn't have been able to set it.
The odd thing is that in order to pass it to the viewmodel I had to do such a trick
public PortfolioChooserView()
{
InitializeComponent();
if (!isFirstLoad) return;
Focusable = true;
PortfolioCompleteBox.AllowDrop = true;
PortfolioCompleteBox.Focus();
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
DataContextChanged += PortfolioChooserView_DataContextChanged;
isFirstLoad = false;
}
void PortfolioChooserView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataContext = DataContext as PortfolioModel;
if (dataContext != null)
{
dataContext.LoadDefaultValue = LoadDefaultValue;
dataContext.AllowNull = AllowNull;
//var converter = new PortfolioConverter();
//var portfolio = (Portfolio) converter.Convert(SelectedItem, null, null, CultureInfo.CurrentCulture);
//dataContext.SelectedItem = portfolio;
}
}
But I really dislike to use the DataContextChanged event ...do you see a better approach?
Thank
UPDATE#2
I keep this toghether since It's a related question...
On some viewmodel I used DeferValidationUntilFirstSaveCall = true; in the Constructor to disable the validation at load but my custom usercontrols shows the red border around... what should I do to propagate that info to the nested usercontrols?
Thanks again
See Orc.Controls for tons of examples. It's an open-source library that has a lot of user controls built with Catel, even one with an auto complete box.

MVVM pattern violation: MediaElement.Play()

I understand that ViewModel shouldn't have any knowledge of View, but how can I call MediaElement.Play() method from ViewModel, other than having a reference to View (or directly to MediaElement) in ViewModel?
Other (linked) question: how can I manage View's controls visibility from ViewModel without violating MVVM pattern?
1) Do not call Play() from the view model. Raise an event in the view model instead (for instance PlayRequested) and listen to this event in the view:
view model:
public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
this.PlayRequested(this, EventArgs.Empty);
}
view:
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
this.myMediaElement.Play();
};
2) You can expose in the view model a public boolean property, and bind the Visibility property of your controls to this property. As Visibility is of type Visibility and not bool, you'll have to use a converter.
You can find a basic implementation of such a converter here.
This related question might help you too.
For all the late-comers,
There are many ways to achieve the same result and it really depends on how you would like to implement yours, as long as your code is not difficult to maintain, I do believe it's ok to break the MVVM pattern under certain cases.
But having said that, I also believe there is always way to do this within the pattern, and the following is one of them just in case if anyone would like to know what other alternatives are available.
The Tasks:
we don't want to have direct reference from the ViewModel to any UI elements, i.e. the the MediaElement and the View itself.
we want to use Command to do the magic here
The Solution:
In short, we are going to introduce an interface between the View and the ViewModel to break the dependecy, and the View will be implementing the interface and be responsible for the direct controlling of the MediaElement while leaving the ViewModel talking only to the interface, which can be swapped with other implementation for testing purposes if needed, and here comes the long version:
Introduce an interface called IMediaService as below:
public interface IMediaService
{
void Play();
void Pause();
void Stop();
void Rewind();
void FastForward();
}
Implement the IMediaService in the View:
public partial class DemoView : UserControl, IMediaService
{
public DemoView()
{
InitializeComponent();
}
void IMediaService.FastForward()
{
this.MediaPlayer.Position += TimeSpan.FromSeconds(10);
}
void IMediaService.Pause()
{
this.MediaPlayer.Pause();
}
void IMediaService.Play()
{
this.MediaPlayer.Play();
}
void IMediaService.Rewind()
{
this.MediaPlayer.Position -= TimeSpan.FromSeconds(10);
}
void IMediaService.Stop()
{
this.MediaPlayer.Stop();
}
}
we then do few things in the DemoView.XAML:
Give the MediaElement a name so the code behind can access it like above:
<MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
Give the view a name so we can pass it as a parameter, and
import the interactivity namespace for later use (some default namespaces are omitted for simplicity reason):
<UserControl x:Class="Test.DemoView"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="MediaService">
Hookup the Loaded event through Trigger to pass the view itself to the view model through a Command
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction>
</ia:EventTrigger>
</ia:Interaction.Triggers>
last but not least, we need to hookup the media controls through Commands:
<Button Command="{Binding PlayCommand}" Content="Play"></Button>
<Button Command="{Binding PauseCommand}" Content="Pause"></Button>
<Button Command="{Binding StopCommand}" Content="Stop"></Button>
<Button Command="{Binding RewindCommand}" Content="Rewind"></Button>
<Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>
We now can catch everything in the ViewModel (I'm using prism's DelegateCommand here):
public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest
{
public IMediaService {get; private set;}
private DelegateCommand<IMediaService> loadedCommand;
public DelegateCommand<IMediaService> LoadedCommand
{
get
{
if (this.loadedCommand == null)
{
this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) =>
{
this.MediaService = mediaService;
});
}
return loadedCommand;
}
}
private DelegateCommand playCommand;
public DelegateCommand PlayCommand
{
get
{
if (this.playCommand == null)
{
this.playCommand = new DelegateCommand(() =>
{
this.MediaService.Play();
});
}
return playCommand;
}
}
.
. // other commands are not listed, but you get the idea
.
}
Side note: I use Prism's Auto Wiring feature to link up the View and ViewModel. So at the View's code behind file there is no DataContext assignment code, and I prefer to keep it that way, and hence I chose to use purely Commands to achieve this result.
I use media element to play sounds in UI whenever an event occurs in the application. The view model handling this, was created with a Source property of type Uri (with notify property changed, but you already know you need that to notify UI).
All you have to do whenever source changes (and this is up to you), is to set the source property to null (this is why Source property should be Uri and not string, MediaElement will naturally throw exception, NotSupportedException I think), then set it to whatever URI you want.
Probably, the most important aspect of this tip is that you have to set MediaElement's property LoadedBehaviour to Play in XAML of your view. Hopefully no code behind is needed for what you want to achieve.
The trick is extremely simple so I won't post a complete example. The view model's play function should look like this:
private void PlaySomething(string fileUri)
{
if (string.IsNullOrWhiteSpace(fileUri))
return;
// HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI.
this.Source = null;
this.Source = new Uri(fileUri);
}
Here is the Source property, nothing special about it:
#region Source property
/// <summary>
/// Stores Source value.
/// </summary>
private Uri _Source = null;
/// <summary>
/// Gets or sets file URI to play.
/// </summary>
public Uri Source
{
get { return this._Source; }
private set
{
if (this._Source != value)
{
this._Source = value;
this.RaisePropertyChanged("Source");
}
}
}
#endregion Source property
As for Visibility, and stuff like this, you can use converters (e.g. from bool to visibility, which you can find on CodePlex for WPF, SL, WP7,8) and bind your control's property to that of the view model's (e.g. IsVisible). This way, you control parts of you view's aspect. Or you can just have Visibility property typed System.Windows.Visibility on your view model (I don't see any pattern breach here). Really, it's not that uncommon.
Good luck,
Andrei
P.S. I have to mention that .NET 4.5 is the version where I tested this, but I think it should work on other versions as well.

Using WPF, MVVM and Bindings with a WinForm UserControl, how to integrate successfully?

I have a WinForm UserControl inside a WPF window and the WPF code is using the MVVM pattern.
What is the best way to successfully integrate the WinForm control into the MVVM pattern?
Can I use some form of binding from the WPF side?
Let's say that I want to handle some events from the WF control, is there a way to fully go MVVM?
Thanks.
Note that this doesn't really answer the questions (I should have read better). If you're interested in using a WPF control in a WinForms app, here's an approach. My scenario is:
1) Have a WinForms control that is used many places in my app.
2) Want to develop a WPF implementation that will use the MVVM pattern.
3) Want to write the control as a proper WPF control complete with dependency properties so it can be used properly when my app is eventually all WPF.
4) Want to keep the same WinForms control and API to not break existing client code in my app.
Most everything was straightforward except for having my WinForms control raise events when properties of my WPF control changed. I wanted to use a binding but since the source of a binding must be a DependencyObject and a System.Windows.Forms.UserControl is not, I had to make a simple nested class. I wrote my WPF control exactly as if I was integrating it into a WPF application, and just did some extra thunking to get my WinForms wrapper to work.
Here's code for my WPF control:
public partial class MonkeySelector : UserControl
{
public static readonly DependencyProperty SelectedMonkeyProperty =
DependencyProperty.Register(
"SelectedMonkey", typeof(IMonkey),
typeof(MonkeySelector));
public MonkeySelector()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// Note: No code is shown for binding the SelectedMonkey dependency property
// with the ViewModel's SelectedMonkey property. This is done by creating
// a Binding object with a source of ViewModel (Path = SelectedMonkey) and
// target of the SelectedMonkey dependency property. In my case, my
// ViewModel was a resource declared in XAML and accessed using the
// FindResource method.
}
public IMonkey SelectedMonkey
{
get { return (IMonkey)GetValue(SelectedMonkeyProperty); }
set { SetValue(SelectedMonkeyProperty, value); }
}
}
Here's the code for my WinForms control:
public partial class WinFormsMonkeySelector : UserControl
{
public event EventHandler SelectedMonkeyChanged;
private MonkeySelector _monkeySelector;
private WpfThunker _thunker;
public WinFormsMonkeySelector()
{
InitializeComponent();
_monkeySelector = new MonkeySelector();
_elementHost.Child = _monkeySelector;
System.Windows.Data.Binding binding = new System.Windows.Data.Binding("SelectedMonkey");
binding.Source = _monkeySelector;
binding.Mode = System.Windows.Data.BindingMode.OneWay;
_thunker = new WpfThunker(this);
// Note: The second parameter here is arbitray since we do not actually
// use it in the thunker. It cannot be null though. We could declare
// a DP in the thunker and bind to that, but that isn't buying us anything.
System.Windows.Data.BindingOperations.SetBinding(
_thunker,
MonkeySelector.SelectedMonkeyProperty,
binding);
}
protected virtual void OnSelectedMonkeyChanged()
{
if (SelectedMonkeyChanged != null)
SelectedMonkeyChanged(this, EventArgs.Empty);
}
public IMonkey SelectedMonkey
{
get { return _monkeySelector.SelectedMonkey; }
set { _monkeySelector.SelectedMonkey = value; }
}
private class WpfThunker : System.Windows.DependencyObject
{
private WinFormsMonkeySelector _parent;
public WpfThunker(WinFormsMonkeySelector parent)
{
_parent = parent;
}
protected override void OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
// Only need to check the property here if we are binding to multiple
// properties.
if (e.Property == MonkeySelector.SelectedMonkeyProperty)
_parent.OnSelectedMonkeyChanged();
}
}
}
Personally, I would handle this by creating a WPF UserControl that wraps the Windows Forms control. This would allow you to encapsulate all of the required code-behind into your WPF Control, and then use it in a pure MVVM manner.
It will be difficult to stay "pure" MVVM using a Windows Forms control directly, as Windows Forms controls typically require a different binding model, as well as typically requiring direct event handling.
You might have a look at the WAF Windows Forms Adapter. It shows a possible way to use Windows Forms together with MVVM.

Drag and Drop in MVVM with ScatterView

I'm trying to implement drag and drop functionality in a Surface Application that is built using the MVVM pattern. I'm struggling to come up with a means to implement this while adhering to the MVVM pattern. Though I'm trying to do this within a Surface Application I think the solution is general enough to apply to WPF as well.
I'm trying to produce the following functionality:
User contacts a FrameworkElement within a ScatterViewItem to begin a drag operation (a specific part of the ScatterViewItem initiates the drag/drop functionality)
When the drag operation begins a copy of that ScatterViewItem is created and imposed upon the original ScatterViewItem, the copy is what the user will drag and ultimately drop
The user can drop the item onto another ScatterViewItem (placed in a separate ScatterView)
The overall interaction is quite similar to the ShoppingCart application provided in the Surface SDK, except that the source objects are contained within a ScatterView rather than a ListBox.
I'm unsure how to proceeded in order to enable the proper communication between my ViewModels in order to provide this functionality. The main issue I've encountered is replicating the ScatterViewItem when the user contacts the FrameworkElement.
You could use an attached property. Create an attached property and in the setproperty method bind to the droped event :
public static void SetDropCommand(ListView source, ICommand command)
{
source.Drop += (sender, args) =>
{
var data = args.Data.GetData("FileDrop");
command.Execute(data);
};
}
Then you can bind a command in your view model to the relevant control on the view. Obviously you may want to make your attached property apply to your specific control type rather than a listview.
Hope that helps.
I had a go at getting Steve Psaltis's idea working. It took a while - custom dependency properties are easy to get wrong. It looks to me like the SetXXX is the wrong place to put your side-effects - WPF doesn't have to go though there, it can go directly to DependencyObject.SetValue, but the PropertyChangedCallback will always be called.
So, here's complete and working the code for the custom attached property:
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public static class PropertyHelper
{
public static readonly DependencyProperty DropCommandProperty = DependencyProperty.RegisterAttached(
"DropCommand",
typeof(ICommand),
typeof(PropertyHelper),
new PropertyMetadata(null, OnDropCommandChange));
public static void SetDropCommand(DependencyObject source, ICommand value)
{
source.SetValue(DropCommandProperty, value);
}
public static ICommand GetDropCommand(DependencyObject source)
{
return (ICommand)source.GetValue(DropCommandProperty);
}
private static void OnDropCommandChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ICommand command = e.NewValue as ICommand;
UIElement uiElement = d as UIElement;
if (command != null && uiElement != null)
{
uiElement.Drop += (sender, args) => command.Execute(args.Data);
}
// todo: if e.OldValue is not null, detatch the handler that references it
}
}
}
In the XAML markup where you want to use this, you can do e.g.
xmlns:local="clr-namespace:WpfApplication1"
...
<Button Content="Drop here" Padding="12" AllowDrop="True"
local:PropertyHelper.DropCommand="{Binding DropCommand}" />
.. And the rest is just making sure that your ViewModel, bindings and command is right.
This version passes an IDataObject through to the command which seems better to me - you can query it for files or whatever in the command. But that's just a current preference, not an essential feature of the answer.

Resources