I have a ShellViewModel with a BindableCollection which is bound to a Canvas with the help of ItemControl.
I add ChildViewModel derived from Screen, to this bindableCollection with the help of a button from ShellViewModel.
I have a closebutton in the ChildViewModel, and on clicking this button, i want to remove item from BindableCollection of ShellViewModel,
Please help.
If you'd like to maintain your existing implementation, you can always create an event and use the EventAggregator. Your ChildViewModel would need to publish the event, and the ShellViewModel would need to implement the IHandle<ChildViewModelMessage> interface. As part of that implementation, it would be able to remove the ChildViewModel from the BindableCollection. Generally, it would look something like this:
public class ChildViewModelMessage {
// Implementation here
}
public class ShellViewModel : IHandle<ChildViewModelMessage> {
...
public void Handle(ChildViewModelMessage message) {
// Handle here
}
}
public class ChildViewModel {
...
public IEventAggregator Events { get; set; }
protected void HandleClose() {
this.Events.Publish(new ChildViewModelMessage());
}
If you need to talk between different viewmodels, you should use a messenger. Here's an example from the mvvm light toolkit : http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx
Related
My app is a translation app. It contains a translation list that is passed to different viewmodel. Those viewmodels migth modify those lists including add and remove operations. For this purpose, I convert this list to an ObservableCollection in the constructor and my list is no longer modified. I know converting to an ObservableCollection creates a new object and the references are no longer the same. It is working perfectly for the concerned view, but once I want to change to another view, the list isn't updated. I was wondering what was the best way to solve this problem?
I thought I could create a custom ObservableCollection that would contain the corresponding list and automatically update it when an add or remove operation would be done. Something that'd look similar to this.
View
public partial class MainWindow : Window
{
private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
{
// this is where I instanciate the viewModel, and the
// list<Translation> isn't modify once I close the view
DataContext = new ModifyWordVM(translations);
}
}
ViewModel
public class ModifyWordVM: INotifyPropertyChanged
{
private ObservableCollection<TranslationVM> translations;
public ObservableCollection<TranslationVM> Translations
{
get { return translations; }
set { translations = value; OnPropertyChanged("Translations"); }
}
public ModifyWordVM(List<Translation> translations)
{
// Converting list to ObservableCollection
Translations = ConvertionHelper.ConvertTo(translations);
}
}
I'd like to know what is the cleaner way to get the modified list back.
You should encapsulate the traslations and their operations. To do this just introduce a class e.g. TranslationService which is shared between all relevant view models. To omit a smelly Singleton I added an instance of the service to the App.xaml resources.
The idea is that all modifications of the translation list take place in one location or type. The same type that is the binding source for the view. When adding a new translation the view should invoke a ICommand on the view model. This command will invoke the AddTranslation method on the TranslationService. Same for remove. Any changes to the translation collection will now reflect across the application.
If you also want to catch modifications of the actual translations (e.g. rename or edit) the TranslationService need to handle the PropertyChanged event of the ObservableCollection items as well.
When an items property changed the TranslationService must respond by raising the PropertyChanged event for the ObservableCollection property Translations. This would require the items to implement INotifyPropertyChanged too.
App.xaml
Shared TranslationService instance
<Application.Resources>
<TranslationService x:Key="TranslationService">
<TranslationService.DatabaseService>
<DatabaseService />
</TranslationService.DatabaseService>
</TranslationService>
</Application.Resources>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
{
// Instantiate the view model and initialize DataContext from XAML instead.
// This method became redundant.
}
}
MainWindow.xaml
<Window.DataContext>
<ModifyWordVM>
<ModifyWordVM.TranslationService>
<!-- Reference the shared instance -->
<StaticResource ResourceKey="TranslationService" />
</ModifyWordVM.TranslationService>
</ModifyWordVM>
</Window.DataContext>
ModifyWordVM.cs
public class ModifyWordVM: INotifyPropertyChanged
{
public ModifyWordVM()
{}
public AddTranslation(Translation translation) => this.translationService.AddTranslation(translation);
public RemoveTranslation(Translation translation) => this.translationService.RemoveTranslation(translation);
public TranslationService TranslationService {get; set;}
public ObservableCollection<TranslationVM> Translations => this.translationService.Translations;
}
TranslationService.cs
public class TranslationService
{
public TranslationService()
{}
public AddTranslation(Translation translation)
{
// Add translations
}
public RemoveTranslation(Translation translation)
{
// Remove translations
}
private DatabaseService databaseService;
public DatabaseService DatabaseService
{
get => this.databaseService;
set
{
this.databaseService = value;
this.Translations = databaseService.getTranslations;
}
}
private ObservableCollection<TranslationVM> translations;
public ObservableCollection<TranslationVM> Translations
{
get => this.translations;
set
{
this.translations = value;
OnPropertyChanged("Translations");
}
}
}
I'm not sure how to make navigation using mvvm. I'm a beginner so I haven't used any framework like mvvm light.
I found good example https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/. But it is not exactly what I'm looking for because in my app each view will cover all window. So when I will change page i will have no controls access from the mainview.
So I decided to make one MainViewModel for changing ViewModels (as in Rachel Blog) but each ViewModel should know about MainViewModel to execute change view. So when I create PageViewModel, I pass in constructor MainViewModel with public method, for example, changeview().
Is it a good way of doing this? Or, maybe, there's a better way to achieve this?
The child viewmodels should not know about main viewmodel.
Instead they should raise events with names like Forward or Back and so forth. ChangeView is the only example you give, so we’ll go with that.
We'll have the child viewmodel expose commands that cause the events to be raised. Buttons or MenuItems in the child view's XAML can bind to the commands to let the user invoke them. You can also do that via Click event handlers calling viewmodel methods in the child view code behind, but commands are more "correct", because at the cost of a little more work in the viewmodel, they make life a lot simpler for creators of views.
Main viewmodel handles those events and changes the active page viewmodel accordingly. So instead of child calling _mainVM.ChangeView(), child raises its own ChangeView event, and the main VM’s handler for that event on the child calls its own method this.ChangeView(). Main VM is the organizer VM, so it owns navigation.
It’s a good rule to make code as agnostic as possible about how and where it’s used. This goes for controls and viewmodels. Imagine if the ListBox class required the parent to be some particular class; that would be frustrating, and unnecessary as well. Events help us write useful child classes that don’t need to know or care anything about which parent uses them. Even if reuse isn’t a possibility, this approach helps you write clean, well-separated classes that are easy to write and maintain.
If you need help with the details, provide more code, and we can go through applying this design to your project.
Example
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
FooViewModel = new FooViewModel();
FooViewModel.Back += (object sender, EventArgs e) => Back();
}
public FooViewModel FooViewModel { get; private set; }
public void Back()
{
// Change selected page property
}
}
public class FooViewModel : ViewModelBase
{
public event EventHandler Back;
private ICommand _backCommand;
public ICommand BackCommand {
get {
if (_backCommand == null)
{
// It has to give us a parameter, but we don't have to use it.
_backCommand = new DelegateCommand(parameter => OnBack());
}
return _backCommand;
}
}
// C#7 version
public void OnBack() => Back?.Invoke(this, EventArgs.Empty);
// C# <= 5
//protected void OnBack()
//{
// var handler = Back;
// if (handler != null)
// {
// handler(this, EventArgs.Empty);
// }
//}
}
// I don't know if you already have a DelegateCommand or RelayCommand class.
// Whatever you call it, if you don't have it, here's a quick and dirty one.
public class DelegateCommand : ICommand
{
public DelegateCommand(Action<object> exec, Func<object, bool> canExec = null)
{
_exec = exec;
_canExec = canExec;
}
Action<object> _exec;
Func<object, bool> _canExec;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExec == null || _canExec(parameter);
}
public void Execute(object parameter)
{
if (_exec != null)
{
_exec(parameter);
}
}
}
How to invoke BackCommand from child XAML:
<Button Content="Back" Command="{Binding BackCommand}" />
I have a class called AppViewModel, this class it's responsible to control the screens. AppViewModel extends my BaseConductor:
public class BaseConductor : Conductor<Screen>.Collection.OneActive
{
...
}
Then, I call a viewmodel (UserControl) on the constructor of AppViewModel:
this.ActivateItem(new FirstViewModel());
On FirstViewModel, after the user clicks on a button I want to open SecondViewModel and close the FirstViewModel:
var conductor = this.Parent as IConductor;
conductor.DeactivateItem(this, true);
conductor.ActivateItem(new SecondViewModel(param));
I already tried to do this:
((IApp)this.Parent).ActivateItem(new SecondViewModel(param));
TryClose();
SecondViewModel extends my BaseScreen:
public class BaseSceen : Screen
{
...
}
I want to close the FirstViewModel, because on the FirstViewModel and SecondViewModel I have shortcuts. When I'm with the SecondViewModel opened I hit a shortcut, and the method that is executed it's from FirstViewModel. So, the FirstViewModel still running.
How can I close the FirstViewModel, and avoid this problem with shortcuts?
Thanks!
Do you really need to use Conductor<T>.Collection.OneActive? You can just use Conductor<T> so that activating an item will automatically deactivate and close the previously active item. And also, is it required that the button/action pair reside in the FirstViewModel? I suggest that you just put those in the AppViewModel and let it orchestrate the navigation and activation/deactivation of the two child screens.
public AppViewModel : Conductor<Screen>
{
public void AppViewModel()
{
ActivateItem(new FirstViewModel());
}
public void ActivateSecondViewModel()
{
// FirstViewModel will automatically be deactivated
// and closed since we are using plain Conductor<T>
ActivateItem(new SecondViewModel());
}
}
I found it! The shortcut event was attached to the window, not to the usercontrol.
So, event when usercontrol was ended the event still "attached" to the window. Now, I added an method that is called when UserControl is Unloaded, to "deattach" the event.
Bad mistake!
After a major edit to this quesiton, I'm hoping it's now clear.
I'm very lost with binding in WPF when 1 change should affect multiple properties.
I regularly use VVM to bind my ViewModel to my View and I would say I'm OK with it.
I am trying to implement a state controller. This means that, what ever settings I made in part of my UI, the reflection is through out.
For example in my part of my UI, I can toggle a feature on or off, such as "show images"
When I make this change, I'd like everything in my application to be notified and act accordingly.
So, my StateController class will have a property
public bool ShowImages
And in my View, I'd likely have something like
<image Visible ="{Binding ShowImages", Converter={StaticConverter ConvertMe}}" />
The problem I have is how I go about making the StateController alert all of my ViewModels of this.
Currently, in each ViewModel I'm assuming I'd have to have the same property repeated
public bool ShowImages
EG
public class StateController : BaseViewModel
{
public bool ShowImages{get;set;}//imagine the implementation is here
}
public class ViewModelB : BaseViewModel
{
public bool ShowImages{}//imagine the implementation is here
}
public class ViewModelB : BaseViewModel
{
public bool ShowImages{}//imagine the implementation is here
}
So, my question is, if I updated ViewModelB.ShowImages, how would I first inform the StateController which in turn updates all ViewModels.
Is this something the INotifyPropertyChanged can do automatically for me since they all share the same propertyName, or do I have to implement the logic manually, eg
public static class StateController
{
public bool ShowImages{get;set;}//imagine the implementation is here
}
public class ViewModelA : BaseViewModel
{
public bool ShowImages
{
get { return StateController.ShowImages; }
set { StateControllerShowImages = value;
OnPropertyChanged("ShowImages"); }
}
}
public class ViewModelB : BaseViewModel
{
public bool ShowImages
{
get { return StateController.ShowImages; }
set { StateControllerShowImages = value;
OnPropertyChanged("ShowImages"); }
}
}
I hate the idea of the above implementation but it does show what I'm trying to achieve. I just hope there is a better way!
The PropertyChange notification is only raised for that one object model.
So raising a change notification of the "Name" property of ClassA will only update the UI in cases where it's bound to that specific ClassA.Name. It won't trigger a change notification for any ClassB.Name, or other instances of ClassA.Name.
I would suggest using a Singleton here for your StateModel, and having your other models subscribe to the StateModel.PropertyChanged event to know if it should update, like this answer.
public ViewModelA
{
public ViewModelA()
{
StateController.Instance.PropertyChanged += StateController_PropertyChanged;
}
void StateController_PropertyChanged(object sender, NotifyPropertyChangedEventArgs e)
{
// if singleton's ShowImages property changed, raise change
// notification for this class's ShowImages property too
if (e.PropertyName == "ShowImages")
OnPropertyChanged("ShowImages");
}
public bool ShowImages
{
get { return StateController.Instance.ShowImages; }
set { StateController.Instance.ShowImages = value; }
}
}
If I understood you correctly, you are looking for a mechanism that allows your different ViewModels to communicate between each other.
One possible way would be to implement the Observer Pattern (a code example can be found here: "Observer pattern with C# 4"). In this way your ViewModel subscribe each other to receive change notifications from a "publisher", i.e. the ViewModel that had its value changed. You have a good control over who receives which notification from which publisher. The downside of this approach is a tight coupling between your models.
My approach would be this:
Use a message dispatcher. Your ViewModels can subscribe to a certain type of message, e.g. ShowImagesChanged. If any of your ViewModels changed the ShowImages property, that ViewModel calls the dispatcher to send out such a ShowImagesChanged message with your current values.
This way you can keep you ViewModels decoupled from each other. Still, although the ViewModels do not know each other this gives a way to exchange data between them.
Personally, I have used the Caliburn Micro MVVM framework several times for this, but there should be enough other MVVM frameworks that provide the same functionality to fit your taste.
The Calibiurn Micro documentation and how easily the dispatcher can be used is here: Event Aggregator
To avoid code repetition you can create a class derived from BaseViewModel that implements your property and have ViewModelA, ViewModelB extend it. However, this does not solve the problem of keeping each instance updated.
In order to do so, you may:
Use a static class (your current solution) or a Singleton as suggested in one of the comments. This is simple but has potential problems such as race conditions and coupling.
Have your ShowImages binding property repeated in each ViewModel and update it by subscribing to a ShowImagesChanged event. This could be published through a Command executed from the UI. I'd say this is the WPF approach and has the benefit of decoupling the ShowImages state management from its consumption.
Assign the ShowImagesupdate responsibility to a single ViewModel and subscribe to the its PropertyChanged in the other ViewModels so that they update accordingly. Better than the first option, but still huge coupling.
Why repeat properties at all? Just bind to StateController itself.
Say we have singleton StateController:
public class StateController : INotifyPropertyChanged
{
private static StateController instance;
public static StateController Instance {
get { return instance ?? (instance = new StateController()); }
}
//here`s our flag
private bool isSomething;
public bool IsSomething
{
get { return isSomething; }
set
{
isSomething = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsSomething"));
}
}
private StateController(){}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Then in base VM class just make a reference to this controller:
public StateController Controller { get { return StateController.Instance; } }
And where needed bind like this:
<CheckBox IsChecked="{Binding Controller.IsSomething}">
Test
</CheckBox>
This way every binding will work with one property and react to one property. If you need some custom code to work you can subscribe to PropertyChanged of StateController where needed and take action.
Suppose I have VM which has implemented INotifyPropertyChange:
public class MyViewModel{
public MyClass{get;set;}
...
}
but plain class MyClass not implemented INotifyPropertyChange, It only hold some properties, like:
public class MyClass
{
public MyClass()
{
}
public string P1 { get; set; }
...
}
in xaml, DataContext is MyViewModel. I set binding like:
Text = "{Binding MyClass.P1}"
Then in MyViewModel constructor, I set up instance of MyClass and fire property change like
this.RaisePropertyChanged("MyClass");
but the value of P1 does not display in UI. How to implement something like this.RaisePropertyChanged("MyClass.P1") in this case?
You can't.
You need to implement INotifyPropertyChanged in the class that owns the property.
If you can't make that class implement INotifyPropertyChanged, you should create a separate ViewModel class that wraps it and implements INotifyPropertyChanged.
There is no way to do this. The best option would be to either wrap this value in your ViewModel, which would allow you to raise the PropertyChanged event directly on the ViewModel.
However, if this is not an option for one reason or another, the other option is to call:
this.RaisePropertyChanged(string.Empty);
This will refresh all of the bindings on the View, including MyClass.P1. This is not always a great solution from a performance standpoint, however, as it forces a full binding refresh.
You could have an event with a name following this template:
public event EventHandler <PropertyName>Changed;
public event EventHandler P1Changed;
The binding will "auto-detect" the P1Changed event and use it for data binding.
All you need to do is raise the P1Changed event when needed.
Add a property of type MyClass to your ViewModel. In the setter of your property is where you would call RaisePropertyChanged().
For example,
public MyClass SomeName
{
get
{
return this._SomeName;
}
set
{
if (value != this._SomeName)
{
this._SomeName = value;
this.RaisePropertyChanged("SomeName");
}
}
}
private MyClass _SomeName;