Reputation: 57
I want to create an User control "JobEditor" in MVVM pattern to Edit a Job:
The consumer of the Usercontorl is holding an instance of third party object "Job" and it wants to pass in that object to the User control, in the User Control has a "Save" button, once user click on that button we need to inform the consumer and pass back the modifed "Job" object (instead of the viewmodel).
To avoid the dependency from the third part object "Job", I'm planing to:
Till here everything is fine, but the more importantly, what is the best way to info the consumer? the only way in my head is:
Expose the "JobEditorViewModel" to the consumer, define a Command in the viewmodel and binding the Save button to it, inside the Command handler I'll convert the viewmodel back to "Job" object and pass in to a abstract method call "Save" (and this will be only 'public' method in the view model to avoid expose unnecessary info) . consumer will create their own ViewModel by inherit from "JobEditorViewModel" and implement the abstract "Save" method.
Which I don't really like this solution.
I would appreciate any ideas!
--------------------------------------More info-------------
Ideally the consumer should be able to use this usercontrol in xaml and binding the "Save" event(an RoutedEvent) to its own ViewModel and the parameter of the "Save" event should be the updated "Order" object, so in the ViewModel of the usercontrol, I have define a command which binding to the Save button in View, once the user click on Save button, in the ViewModel command I would like to construct the Job object and trigger that RoutedEvent and pass the Job object as parameter
Event in usercontrol's code behind:
public partial class JobEditor : UserControl
{
public static readonly RoutedEvent OnSaveEvent = EventManager.RegisterRoutedEvent(SaveEvent,
RoutingStrategy.Bubble, typeof(RoutedEventHandler),typeof(JobEditor));
public event RoutedEventHandler OnSave
{
add { AddHandler(OnSaveEvent, value); }
remove { RemoveHandler(OnSaveEvent, value); }
}
}
public class SaveEventArgs : RoutedEventArgs
{
public Job Job{ get; private set; }
public SaveEventArgs(RoutedEvent routedEvent, object source,Job job):base(routedEvent,source)
{
this.Job = job;
}
}
Question: Is there a way to trigger this event from Usercontrol's ViewModel with parameter SaveEventArg?
Upvotes: 0
Views: 374
Reputation: 1778
Ok, I believe I understand what you're trying to do: I put a sample for you together: http://1drv.ms/1niUsMf
I will update this answer in a few minutes with the code samples:
Your EVIL consumer: :)
public class TheEvilConsumer { public TheEvilConsumer() { Job = new JobControlViewModel{ Name = "DoLaundry", Consumer = this}; }
public JobControlViewModel Job { get; set; } public void Do(Job job) { // do something... } }
The ViewModel Stuff:
public class JobControlViewModel : ViewModelBase
{
private string name;
private string someParam;
private DelegateCommand myCommand;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
public string SomeParam
{
get { return someParam; }
set
{
someParam = value;
OnPropertyChanged();
}
}
public DelegateCommand MyCommand
{
get
{
if (this.myCommand == null)
{
myCommand = new DelegateCommand(o => this.Consumer.Do(new Job{Name = this.Name, SomeParam = this.SomeParam}));
}
return this.myCommand;
}
private set
{
this.myCommand = value;
OnPropertyChanged();
}
}
public TheEvilConsumer Consumer { get; set; }
}
Your regular Job-thingy:
public class Job
{
public string Name { get; set; }
public string SomeParam { get; set; }
}
And the INotifyPropertyChanged implementation:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And then the control:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBox Text="{Binding SomeParam}" />
<Button Content="Create Job" Command="{Binding MyCommand}" />
</StackPanel>
</Grid>
As I don't know much about how you construct the consumer, I did it in the MainWindow, but you will figure that out, I guess. Or else just aks... :)
Of course you can use interfaces to communicate between the consumer and the viewmodel.
----- EDIT -------------- Loose coupling via Action Delegate:
in the JobControlViewModel:
public DelegateCommand MyCommand
{
get
{
if (this.myCommand == null)
{
myCommand = new DelegateCommand(o => this.NotficationAction(new Job { Name = this.Name, SomeParam = this.SomeParam }));
}
return this.myCommand;
}
private set
{
this.myCommand = value;
OnPropertyChanged();
}
}
public Action<Job> NotficationAction { get; set; }
And then in the Consumer:
public TheEvilConsumer()
{
Job = new JobControlViewModel{ Name = "DoLaundry", NotficationAction = job => Do(job)};
}
Consumer now only provides an Action to call with the job created after the button click...
----- EDIT----- Wasn't sure if you saw my answer in the chat: I fiddeled around a little. From what I found so far: Create A custom Control. In the Style (setter) bind your Job-DP to your internal viewmodel.Job and then bind the fields in the control against the vm.Name, etc. Properties which return the values from the Job.Name, etc. Props. Bind the command to the vm like in the sample. Inside the command you need to call a method in the view that raises the event the consumers subscribed for. Do that using a CallMethodAction (msdn.microsoft.com/en-us/…).
Upvotes: 1
Reputation: 2121
If you consider your ViewModel to be an implementation detail that is not accessible to the consumer, then create a dependency property in the View and bind it to a property of the ViewModel.
In your ViewModel, once you have the updated Job object, just set it to the property of the ViewModel that is bound to that dependency property and let binding handle the communication to the View (and to the consumer) that the Job has been updated.
Upvotes: 0