I fear, this is something obvious I'm overlooking, but I'm a bit stuck:
I'm using Gong Solution Drag&Drop library for drag&drop from one view to another. I have written a custom drop handler, which handles my data transfer as well as updating the data on source and target (ListView in this case).
Now I want my ViewModel class corresponding to the source view to subscribe to the drop event in the target view. However, I can't find the drop event exposed by the gong library. My next thought was to write my own event within my custom drag or drop handler, however this classes are completely decoupled from the source ViewModel, so how would I subscribe to the event?
A bit of code (relevant parts):
Target Grid:
<Grid dd:DragDrop.IsDropTarget="True" dd:DragDrop.DropHandler="{StaticResource CustomDropHandler}">
<!-- Stuff in my view -->
</Grid>
Custom Drag Handler (doesn't do anything really, but fires a dropped method on the base class, when I drop an item)
public class CustomDragHandler : DefaultDragHandler
{
public override void Dropped(IDropInfo dropInfo)
{
base.Dropped(dropInfo);
}
}
Custom Drop Handler (This is where I handle all my data transfer stuff):
public class CustomDropHandler : IDropTarget
{
public void DragOver(IDropInfo dropInfo)
{
dropInfo.Effects = DragDropEffects.Move;
dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight;
}
public void Drop(IDropInfo dropInfo)
{
// Whole lot of logic in here, fired on drop.
}
}
Related
I'm working on a silverlight project that has a file viewer. This file viewer does not have a Source property or other property that accepts a stream. It does have a LoadDocument(Stream file) method. Since the file will be loaded asynchronously I need to "Notify" the view that the buffer is available and then let the View call the LoadDocument method.
In MVVMLight I could do this with the "Messenger" functionality. I see the EventAggregator but everything I see has the communications going the other way. I feel like this should be really easy to do but I just don't see it.
Is there a way in the Views constructor to bind a method to a property of the ViewModel? It seems to be this is the same functionality that is done in xaml I just want to do it in the code behind.
Thanks
dbl
Not sure by the post but it sounds like you want to bind an event from a control to a method on your view
In that case:
<SomeControl cal:Message.Attach="[Event SomeEvent] = [Action SomeMethod($eventArgs)]" />
If it's the other way round you can either use the event aggregator (the view can subscribe to events... why not, it's decoupled still...)
VM:
SomeEventAggregator.Publish(new SomeMessageInstanceThatTheViewWillSubscribeTo());
View:
class SomeView : UserControl, IHandle<SomeMessageInstanceThatTheViewWillSubscribeTo>
// dont forget to...
SomeEventAggregator.Subscribe(this);
Alternatively - implement an interface on the view
class SomeView : UserControl, IAcceptSomeNotificationMessage
{
public void Notify() { // blah
}
}
VM:
class SomeViewModel : Screen // whatever
{
void SomeMethod()
{
// The VM should be IViewAware so will implement GetView()
var view = GetView();
if(view is IAcceptSomeNotificationMessage)
(view as IAcceptSomeNotificationMessage).Notify();
}
}
Pick one of the above - I'm sure there are more ways. I usually use an event aggregator - of course it depends on how much IoC you are using and how decoupled everything is.
I'm working on converting some code to a more proper MVVM implementation using DataTemplates and am having problems with certain kinds of UI validation.
I've got no problems with validation in the View Models -- IDataErrorInfo is implemented and everything is fine. What I've got a problem with is UI binding errors where they might put letters in a TextBox bound to an int.
Previously, I used :
System.Windows.Controls.Validation.AddErrorHandler(userControl, handler)
... and kept a count of errors added and removed to know whether all the form's data was OK.
But now that I'm doing MVVM I don't have access to the userControl to set up this handler. So I don't really have a hook to get this started.
Is there some sort of global DataTemplateApplied event handler available where I could do something like:
void OnDataTemplateApplied(object data, Control template)
{
if (data is MyViewModelBase)
{
Validation.AddErrorHandler(template, handler);
}
}
Alternatively, maybe I can call AddErrorHandler once in the bootstrapper for the outer Shell window, and then each time the event is fired somehow figure out which ViewModel is powering that particular control?
I know some people like making all VM fields strings and doing lots of type conversion in the VM -- that's not going to be realistic for our system for a variety of reasons.
You might be interested in this answer: https://stackoverflow.com/a/13335971/1094526
The main idea is exactly what you said (subscribe to the error handler). As I understand, the problem is you don't have access to the control from the ViewModel, but it isn't hard to solve
In a project I'm working, I exposed two methods from my ViewModel: AddUIError and RemoveUIError. I create an event handler in my View and there I cast the DataContext to the type of my ViewModel and call AddUIError or RemoveUIError depending on what happened.
I am using DataTemplates to associate a View with a ViewModel, so when the template is applied, the DataContext is automatically set to the ViewModel. If you want, you can store your ViewModel in a private field (in the View) and update the reference each time the DataContext changed (there is a DataContextChanged event)
If this will be done in multiple ViewModels, you can put both methods (AddUIError and RemoveUIError) in a class like ViewModelBase and move the ValidationError event handling to a Behavior and use it in each view.
More info about the behavior part:
The Behavior class is part of the Expression Blend SDK, so you will need it if you want to follow this way.
Behaviors are useful to attach some common functionality to many components without creating derived classes, for example.
First, we need to define the AddUIError and RemoveUIError in a class named ViewModelBase (which is, of course, the base class for all other ViewModels):
class ViewModelBase {
public void AddUIError(...) {/* Details ommitted */ }
public void RemoveUIError(...) {/* Details ommitted */ }
}
Then, create a Behavior by subclassing Behavior. We use FrameworkElement as the template argument so this behavior can be attached to any FrameworkElement (or derived class) instance:
class NotifyDataErrorsBehavior : Behavior<FrameworkElement>
{
// Called when the the Behavior is attached
protected override void OnAttached()
{
base.OnAttached();
// Initialize the handler for the Validation Error Event
_handler = new RoutedEventHandler(OnValidationRaised);
// Add the handler to the event from the element which is attaching this behavior
AssociatedObject.AddHandler(System.Windows.Controls.Validation.ErrorEvent, _handler);
}
protected override void OnDetaching()
{
base.OnDetaching();
// Remove the event handler from the associated object
AssociatedObject.RemoveHandler(System.Windows.Controls.Validation.ErrorEvent, _handler);
}
private RoutedEventHandler _handler = null;
private void OnValidationRaised(object sender, RoutedEventArgs e)
{
var args = (System.Windows.Controls.ValidationErrorEventArgs)e;
ViewModelBase viewModel = AssociatedObject.DataContext as ViewModelBase;
if (viewModel != null)
{
// You can add only Exception validation errors if you want..
if (args.Action == ValidationErrorEventAction.Added)
viewModel.AddUIValidationError(...);
else if (args.Action == ValidationErrorEventAction.Removed)
viewModel.RemoveUIValidationError(...);
else
throw new NotSupportedException("ValidationErrorEventAction has changed");
}
}
}
And finally just use it in XAML:
1. Add a reference to the namespace where NotifyDataErrorsBehavior is located, and also a reference to System.Windows.Interactivity namespace (from Expression Blend SDK):
<UserControl
...
xmlns:behavior="clr-namespace:MyApp.Behaviors"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
>
2. Add the behavior (at the same level as the content of your UserControl:
<i:Interaction.Behaviors>
<behavior:NotifyDataErrorsBehavior/>
</i:Interaction.Behaviors>
Ex:
<UserControl
...
xmlns:behavior="clr-namespace:MyApp.Behaviors"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
>
<i:Interaction.Behaviors>
<behavior:NotifyDataErrorsBehavior/>
</i:Interaction.Behaviors>
<Grid>
...
</Grid>
</UserControl>
Basically, I added a custom event to my TreeView derivative. It is meant to replace the AfterSelected Event, which I disable, because I require a different Event handling mechanism for when people click on tree items.
The event handling itself works well. But I want my new event to be the one wired when a custom control is doubled-clicked in the design view.
The custom class is called CustomTreeView, and this is how it declares its new event.
public event EventHandler CustomSelect;
So basically, when double clicked in the design view, I want this to appear in the MyForm.Designer.cs
this.MyCustomTreeView.CustomSelect += new System.Windows.Forms.TreeViewEventHandler(this.MyCustomTreeView_CustomSelect);
And obviously, the MyCustomTreeView_CustomSelect() function should be added to MyForm.cs
so how do I set this up in my custom control?
You're looking for the DefaultEventAttribute
[DefaultEvent("CustomSelect")]
public class MyCustomTreeView : TreeView
{
//...
}
I am using MVVM Light toolkit in my WPF application. I would like to know what is the best approach for opening a new window from an existing window. I have got this MainViewModel, which is responsible for MainWindow of my application. Now in the MainView, on a button click, I would like to open a second window on top of it. I have got RelayCommmand binded to the Button's Command. In the RelayCommand's method, I can create a new window object and simply call Show(), something like this:
var view2 = new view2()
view2.Show()
but I don't think the ViewModel should be responsible for creating the new view2 object. I have read this post WPF MVVM Get Parent from VIEW MODEL where Bugnion has suggested to pass message to the view1 from the viewmodel1 and then view1 should create the new view2. But I am not sure what does he actually mean by passing the message to the view1? How should the view1 handle the message? In it's code behind or what?
Regards,
Nabeel
Passing a message from ViewModel1 to View1 means to use the messaging capabilities in the MVVM Light Toolkit.
For example, your ViewModel1 could have a command called ShowView2Command, then it would send a message to display the view.
public class ViewModel1 : ViewModelBase
{
public RelayCommand ShowView2Command { private set; get; }
public ViewModel1() : base()
{
ShowView2Command = new RelayCommand(ShowView2CommandExecute);
}
public void ShowView2CommandExecute()
{
Messenger.Default.Send(new NotificationMessage("ShowView2"));
}
}
View1 would register to receive messages in its code behind and display View2 when it receives the correct message.
public partial class View1 : UserControl
{
public View1()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "ShowView2")
{
var view2 = new view2();
view2.Show();
}
}
}
Why do you go this route? Its simple. If you replace your button with a toggleButton, or a hyperlink, or any other number of button-like controls, you don't need to update your "code behind" - its a basic principle of the MVVM pattern. In your new toggleButton (or whatever), you still end up binding to the same exact Command.
For example, I'm creating a project for a client who wants to have 2 UI's - one is going to be fundamentally different in every way, in terms of presentation. Horizontal tabs vs Vertical RadPanelBar (think Accordion) for navigation. Both of these views can point to the same viewModel - when a user clicks the Work Order tab in View 1, it fires the same "WorkOrderCommand" that's fired in the Work Order Header in the panel bar.
In a code-behind model, you'd have to code two separate events. Here you only have to code one.
Furthermore, it allows a designer using Blend to create any layout they want. As long as they have the hooks (EventToCommand control) in place, myself (as a developer) couldn't care less what the final product looks like.
Loose coupling is incredibly powerful.
You can do in this way like you need to create some events and register those in view and call these in view model.and open that pop up window.
Like This example
public class Mainclass : MainView
{
public delegate abc RegisterPopUp(abc A);
public RegisterPopUp POpUpEvent ;
public RelayCommand ShowCommand { private set; get; }
public void ShowCommand()
{
ShowCommand("Your parameter");
}
}
inside the view MainView mn=new MainView();
Register the event here like thake mn.POpUpEvent += than click on tab button double time
and in registers popup method right the code for opening the pop up window.
Unless I am missing the point here - if I were to use the code behind, then why not directly implement button_click event and open the second view?
What Bugnion seems to be suggesting is view1 -> button click -> relay command -> viewmodel1 -> message -> view1 -> view1.cs -> open view 2.
You are going to sacrifice testability anyhow by writing code-behind, so why take such a long route?
You can abstract the view specific features into services using generic interface. In the view layer you can provide concrete instances of these services and build view models using the IoC container and Dependency Injection technique.
In your case you can build an interface IWindowManager or something similar which has the required method. This can be implmented in your view layer. I wrote a small blog post recently demonstrating how to abstract the dialog behaviour out of view model. Similar apporach can be used for any user interface related service like Navigation, MessageBoxes etc.
This link might be helpful for you http://nileshgule.blogspot.com/2011/05/silverlight-use-dialogservice-to.html
Many people also use the approach of firing events from view models which are subscribed on the view.cs file and from there the MessageBox or any other UI related action is performed. I personally like the approach of injecting services because then you can provide multiple implementations of the same service. A simple example would be how navigation is handled in Silverlight and Windows Phone 7 applications. You can use the same view model but inject different implementations of the Navigation service based on the application type.
I find the best way to approach this, is opening and closing the window from the ViewModel. As this link suggests,
Create a DialogCloser class
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(DialogCloser), new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null) window.Close();
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
Create a Base ViewModel inheriting from GalaSoft.MvvmLight.ViewModelBase with there additional members. Once done, use this viewmodel as base for other viewmodels.
bool? _closeWindowFlag;
public bool? CloseWindowFlag
{
get { return _closeWindowFlag; }
set
{
_closeWindowFlag = value;
RaisePropertyChanged("CloseWindowFlag");
}
}
public virtual void CloseWindow(bool? result = true)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() =>
{
CloseWindowFlag = CloseWindowFlag == null ? true : !CloseWindowFlag;
}));
}
In the view, Bind the DialogCloser.DialogResult dependency property with the CloseWindowFlag property in the base viewmodel.
Then you can open/close/hide the window from the viewmodel.
i'm just starting with the mvvm model in Silverlight.
In step 1 i got a listbox bound to my viewmodel, but now i want to propagate a click in a button and a selecteditemchanged of the listbox back to the viewmodel.
I guess i have to bind the click event of the button and the selecteditemchanged of the listbox to 2 methods in my viewmodel somehow?
For the selecteditemchanged of the listbox i think there must also be a 'return call' possible when the viewmodel tries to set the selecteditem to another value?
i come from a asp.net (mvc) background, but can't figure out how to do it in silverlight.
Roboblob provides excellent step-by-step solution for Silverlight 4. It strictly follows MVVM paradigm.
I would not bind or tie the VM in any way directly to the events of controls within the View. Instead, have a separate event that is raised by the View in response to the button click.
[disclaimer: this code is all done straight from my head, not copy & pasted from VS - treat it as an example!!]
So in pseudo code, the View will look like this:
private void MyView_Loaded(...)
{
MyButton.Click += new EventHandler(MyButton_Click);
}
private void MyButton_Click(...)
{
//Raise my event:
OnUserPressedGo();
}
private void OnUserPressedGo()
{
if (UserPressedTheGoButton != null)
this.UserPressedTheGoButton(this, EventArgs.Empty);
}
public EventHandler UserPressedTheGoButton;
and the VM would have a line like this:
MyView.UserPressedTheGoButton += new EventHandler(myHandler);
this may seem a little long-winded, why not do it a bit more directly? The main reason for this is you do not want to tie your VM too tightly (if at all) to the contents of the View, otherwise it becomes difficult to change the View. Having one UI agnostic event like this means the button can change at any time without affecting the VM - you could change it from a button to a hyperlink or that kool kat designer you hire may change it to something totally weird and funky, it doesn't matter.
Now, let's talk about the SelectedItemChanged event of the listbox. Chances are you want to intercept an event for this so that you can modify the data bound to another control in the View. If this is a correct assumption, then read on - if i'm wrong then stop reading and reuse the example from above :)
The odds are that you may be able to get away with not needing a handler for that event. If you bind the SelectedItem of the listbox to a property in the VM:
<ListBox ItemSource={Binding SomeList} SelectedItem={Binding MyListSelectedItem} />
and then in the MyListSelectedItem property of the VM:
public object MyListSelectedItem
{
get { return _myListSelectedItem; }
set
{
bool changed = _myListSelectedItem != value;
if (changed)
{
_myListSelectedItem = value;
OnPropertyChanged("MyListSelectedItem");
}
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.NotifyPropertyChanged != null)
this.NotifyPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
To get that NotifyPropertyChanged event, just implement the INotifyPropertyChanged interface on your VM (which you should have done already). That is the basic stuff out of the way... what you then follow this up with is a NotifyPropertyChanged event handler on the VM itself:
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MyListSelectedItem":
//at this point i know the value of MyListSelectedItem has changed, so
//i can now retrieve its value and use it to modify a secondary
//piece of data:
MySecondaryList = AllAvailableItemsForSecondaryList.Select(p => p.Id == MyListSelectedItem.Id);
break;
}
}
All you need now is for MySecondaryList to also notify that its value has changed:
public List<someObject> MySecondaryList
{
get { return _mySecondaryList; }
set
{
bool changed = .......;
if (changed)
{
... etc ...
OnNotifyPropertyChanged("MySecondaryList");
}
}
}
and anything bound to it will automatically be updated. Once again, it may seem that this is the long way to do things, but it means you have avoided having any handlers for UI events from the View, you have kept the abstraction between the View and the ViewModel.
I hope this has made some sense to you. With my code, i try to have the ViewModel knowing absolutely zero about the View, and the View only knowing the bare minimum about the ViewModel (the View recieves the ViewModel as an interface, so it can only know what the interface has specified).
Regarding binding the button click event I can recommend Laurent Bugnion's MVVM Light Toolkit (http://www.galasoft.ch/mvvm/getstarted/) as a way of dealing with this, I'll provide a little example, but Laurent's documentation is most likely a better way of understanding his framework.
Reference a couple of assemblies in your xaml page
xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
add a blend behaviour to the button
<Button Content="Press Me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<command:EventToCommand Command="{Binding ViewModelEventName}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
and create the event within your viewmodel which will be called when the button is clicked
public RelayCommand ViewModelEventName { get; protected set; }
...
public PageViewModel()
{
ViewModelEventName = new RelayCommand(
() => DoWork()
);
}
This supports passing parameters, checking whether execution is allowed etc also.
Although I haven't used it myself, I think the Prism framework also allows you to do something similar.