Command binding not working - c#

I'm developing my first WPF app for university using MVVM. I cannot get this specific binding to work, although i've followed the steps used previously that have been successful.
I have the following xaml snippet:
<Button Command="{Binding GetTicketsCommand}" Canvas.Left="50" Canvas.Top="202" Content="Refresh List" Height="25" Width="137" />
The view initialises the VM as such:
public JobListView()
{
InitializeComponent();
viewModel = new JobListViewModel(this);
this.DataContext = viewModel;
}
The viewmodel has the command called GetTicketsCommand which the button binds to, but when I click the button the command Execute or CanExecute methods do not get called. The command that I created is getting instantiated in the VM constructor.
Any ideas?
Edit:
The command class is like this:
public class GetTicketsCommand : ICommand
{
private readonly JobListViewModel viewModel;
public GetTicketsCommand(JobListViewModel viewModel)
{
this.viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
viewModel.GetTickets();
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
And in the viewmodel I simply create an instance of it in the constructor:
public JobListViewModel()
{
GetTicketsCommand = new GetTicketsCommand(this);
dataAccess = new DataLayerClient();
}

Bindings only work with properties. Make sure that your GetTicketsCommand command within your view model is a property:
public ICommand GetTicketsCommand { get; set; }
rather than a field:
public ICommand GetTicketsCommand;

For MVVM scenarios a RelayCommand or DelegateCommand is a better fit than the RoutedCommand which is provided with WPF.
The DelegateCommand is provided in the Prism framework, and see http://msdn.microsoft.com/en-us/magazine/dd419663.aspx for more details for an implementation of a RelayCommand (based on the DelegateCommand).
Commanding has limitations though. For example, a button would be disabled if the command states that it can't execute. What if you wanted to hide the button instead? Commands limit your scope in reimagining the UI.
You might want to look at Caliburn.Micro which implements Actions. These let you invoke verbs on your view model from default events of the control type, all based on conventions.
As an example, if you had a button on your view with a name of Save, then the Save method on your view model will be invoked when the button is clicked. No explicit plumbing is required. You then have further flexibility in the behaviour of the button if the CanSave property on your view model returns false.

Related

How to share a binding value between multiple views?

I am new to WPF and MVVM (coming in from WinForms and Events), so please bear with me!
I am trying to figure out how to use the same INotifyPropertyChanged value binding between multiple views. I am using MVVM Light. I have ViewModels that inherit from ViewModelBase backing my Views (with no code behind). I'm not sure how to explain the issue, but I think an example will make it clear what I'm after.
I have a main window. It has a standard TabControl. On the Login TabItem I have a custom login control. Below the TabControl, I have a custom status bar control. The desired behavior is that when the user logs in, the status bar is updated with their login status and name, and the other TabItems on the main window become enabled (they should be disabled when not logged in).
So to summarize I have:
MainWindow (view) with MainWindowViewModel
Login (view) with LoginViewModel (in TabControl of MainWindow)
StatusBar (view) with StatusBarViewModel (at bottom of MainWindow)
Here is what my StatusBarViewModel looks like:
public class StatusBarViewModel : ViewModelBase, IStatusBarViewModel
{
private bool _isLoggedIn;
public bool IsLoggedIn
{
get { return _isLoggedIn; }
set { Set(ref _isLoggedIn, value); RaisePropertyChanged(); }
}
// other properties follow
}
I inject (using Ninject) the (singleton) concrete instance of IStatusBarViewModel into the LoginViewModel via constructor injection:
public class LoginViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public LoginViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And I do the same for the MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public bool IsLoggedIn => _statusBar.IsLoggedIn;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
Note: I think this is where my problem is... Not sure if MVVM Light interprets this as a bindable property and applies the proper change notifications. If I add a setter (which I don't need here), that won't work because A property or indexer may not be passed as an out or ref parameter. So I'm unclear on what is going on when I do this.
Back on track here, so when the login is successful, I am able to update the IsLoggedIn property from the LoginViewModel like so:
_statusBar.IsLoggedIn = true;
I set up the binding in my MainWindow XAML like so:
<TabItem Header="Event" IsEnabled="{Binding IsLoggedIn}">
<views:Events/>
</TabItem>
The binding works correctly when the view is first loaded, but subsequent changes to the property don't trigger a change in IsEnabled. The StatusBar (view) however does update accordingly.
I had tossed around the idea of injecting references to both the StatusBarViewModel and the MainWindowViewModel in to my LoginViewModel (and then having to set two properties after login), but that made me think that I'm not approaching this correctly because I'm creating dependencies.
So basically the question is:
Is my approach correct, per the MVVM pattern?
Am I on the right track and just need to modify the code a bit?
If not, what is the (or a) standard pattern to handle this scenario?
Your guess is correct. The problem is here:
public bool IsLoggedIn => _statusBar.IsLoggedIn;
... because it's not going to generate the change notification. What you could do is just expose the IStatusBarViewModel via a public property and then bind to its own IsLoggedIn property directly.
In the ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private IStatusBarViewModel _statusBar;
public IStatusBarViewModel StatusBar => _statusBar;
public MainWindowViewModel(IStatusBarViewModel statusBar)
{
_statusBar = statusBar;
}
}
And in the View:
<TabItem Header="Event" IsEnabled="{Binding StatusBar.IsLoggedIn}">
<views:Events/>
</TabItem>

WPF Commands---How to attach to ViewModel properly from View for logic?

I have seen quite a few different posts regarding this, but am still confused as to what the proper way to do this is. I am implementing RelayCommand and IRequireViewID to do things like call a WindowsManager class to close windows from the view where it doesn't need to know what window it is calling, etc.
However, situations that arise like this is where I am unsure how to implement things properly.
So basically I have a command in the viewModel where you click a button and I need to save data to a DB once this happens. How do I have the command in the View but then put the logic for this into the ViewModel? IE, basically the ViewModel would need to know when the Command is called. I mean I could always put a static method in the viewmodel and call it from the view but I am assuming that probably is not a good way to implement it, ie MyViewModel.RedValidation from the view.
RelayCommand Class
public class RelayCommand : ICommand
{
private Action commandTask;
public RelayCommand(Action commandToRun)
{
commandTask = commandToRun;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
commandTask();
}
}
View
public ICommand ValidateRed
{
get => new RelayCommand(RedValidation);
}
private void RedValidation()
{
//SAVE Data To DB <----This should be in the ViewModel though right?
}
XAML:
<Button Name="ValidateBtn" Style="{StaticResource mainButtons}" Content="Validate Email" Width="100"
HorizontalAlignment="Left" Command="{Binding ValidateRed}"/>
Everything you have in "View" should be in "ViewModel". Commands are properties of the view model, not the view (also, you shouldn't be binding against the view 99% of the time).
Once you have changed that, it should fall into place the way you expect.

Make binding work on property in a different class

So I have a class MainViewModel in which I have a button. The button navigates to a view which has its own view model, let's call in ListViewModel. It resides inside MainViewModel. It has an ObservableCollection called WorkOrders.
In my main view model, I have a property, which returns the number of items, in the list in my ListViewModel. However, if I bind my button text to this property (NumberOfWorkOrders), then nothing happens, when WorkOrders.Count() changes. Even if I call OnPropertyChanged("NumberOfWorkOrders").
However, it does work, if I bind to the identical property inside the ListViewModel. How come it does not work, with the property in the MainViewModel? Is it because the notification from INotifyPropertyChanged does not work in a different view model?
Button binding which DOES NOT work (uses property from MainViewModel)
<Button
Content="{Binding NumberOfWorkOrders}"
ContentStringFormat="WorkOrders ({0})" />
Button binding which DOES work (uses property from ListViewModel)
<Button
DataContext="{Binding LVM}"
Content="{Binding NumberOfWorkOrders}"
ContentStringFormat="WorkOrders ({0})" />
MainViewModel.cs
public class MainViewModel : BindableBase
{
// INotifyPropertiesChanged is implemented in BindableBase
private ListViewModel listViewModel = new ListViewModel();
// This is where I would like to bind my button text to
public int NumberOfWorkOrders
{
get { return listViewModel.WorkOrders.Count(); }
}
// I would prefer not to have this
public ListViewModel LVM
{
get { return listViewModel; }
}
}
ListViewModel.cs
public class ListViewModel : BindableBase
{
// INotifyPropertiesChanged is implemented in BindableBase
public ObservableCollection<WorkOrder> WorkOrders
{
get; set;
}
// I would prefer to use the version of this in the MainViewModel
public int NumberOfWorkOrders
{
get { return listViewModel.WorkOrders.Count(); }
}
public void RefreshWorkOrders()
{
(...) // Load work orders and add them to collection
OnPropertyChanged("NumberOfWorkOrders");
}
}
You are running into this problem
, where you have aggregated property which required additional job: you will have to subscribe to ListViewModel.PropertyChanged and rise notification for MainViewModel.NumberOfWorkOrders property:
public class MainViewModel : BindableBase
{
readonly ListViewModel listViewModel = new ListViewModel();
public int NumberOfWorkOrders => listViewModel.WorkOrders.Count();
public MainViewModel()
{
// since you already rise notification in ListViewModel
// listen to it and "propagate"
listViewModel.PropertyChanged += (s, e) =>
{
if(e.PropertyName == nameof(ListViewModel.NumberOfWorkOrders))
OnPropertyChanged(nameof(NumberOfWorkOrders));
};
}
}
First button (which does NOT work) has MainViewModel as data context, binding will only listen for notification in this class.
As a simple fix you can include LVM in Path of binding. Binding is smart and will start listening to both events: of main view model and of that instance given by LVM property:
<Button Content="{Binding LVM.NumberOfWorkOrders}" ... />
you can delete NumberOfWorkOrders from MainViewModel then.
The reason your MainViewModel binding doesn't work, because you call OnPropertyChanged("NumberOfWorkOrders") from ListViewModel context.
I would suggest in MainViewModel to sign to changes in listViewModel.WorkOrders, and fire OnPropertyChanged("NumberOfWorkOrders") from MainViewModel, when WorkOrders changed. You need to look into documentation of ObservableCollection to find how to sign for collection changed notifications.

Close a view from a viewmodel in MVVM

I have seen many examples over the net but I am not able to figure out what i am doing wrong here.
I need to close a view from a view model. Below is the code i have tried:
ViewModel:
public class ViewModel
{
public event EventHandler RequestClose;
public ViewModel()
{
}
}
//Calling the event from view model to close the view from a method in View Model
This event is called on a button click
private void Download()
{
//Download Logic
if(RequestClose != null)
RequestClose(this,EventArgs.Empty);
}
View:
ViewModel vm = new ViewModel();
vm.RequestClose += delegate(object sender, EventArgs args) {this.Close();}
this.DataContext = vm;
You fire the RequestClose event in the ViewModel constructor which is too early to be catched by the event registration.
The best MVVM solution is to use an attached behavior, as outlined in the top rated answer to this question How should the ViewModel close the form?
I faced a similar problem earlier, and did the following: In the viewmodel, create a command that you can bind to (I personally use MvvmLight and its RelayCommand)
public class ViewModel
{
public RelayCommand<object> CloseWindowCommand {get; private set;}
public ViewModel()
{
CloseWindowCommand = new RelayCommand<object>(CloseTheWindow);
}
private void CloseWindow(object obj)
{
var window = obj as Window;
if(window != null)
window.Close();
}
}
In my view, I have button that triggers this command, e.g.
Button Content="Close" Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=NameOvViewModel}"
I realize now, that this may require the use of MvvmLight, but I hope it offers some guidance on a possible solution to your question.

Update Parent View From Child MVVM

I'm building a WPF MVVM application but I'm having problems updating a parent view from a child view model.
I call the doAction() method on my parent view model which updates a property and raises a PropertyChangeEvent. When this method is called from the MainViewModel everything works great however when I call the same method from my child view model the PropertyChangedEvent get's raised but the view doesn't update.
Example:
ChildViewModel()
{
private ParentViewModel parent;
parent.doAction(); // Raised event but MainView doesn't update
}
ParentViewModel()
{
public void doAction()
{
this.Property = true;
OnPropertyChange("Property");
}
}
My Views are created using XAML:
<MainView>
<TabItem>
<view:ChildView/>
</TabItem>
</MainView>
Propery Change event is raised like so:
protected void OnPropertyChanged(string name)
{
LOGGER.Info("Property Changed: " + name);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
My question is how do I get the parent view to listen and update to a property change event raised by a child view.
Edit:
Base Class:
public abstract class AbstractBaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand CloseCommand { get; set; }
public AbstractBaseViewModel()
{
this.CloseCommand = new CloseCommand(this);
}
public void CloseWindow()
{
Application.Current.MainWindow.Close();
}
protected void OnPropertyChanged(string name)
{
LOGGER.Info("Property Changed: " + name);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Parent ViewModel:
public class ParentViewModel : AbstractBaseViewModel
{
private Dispatcher dispatcher;
private bool visible;
public bool Visible
{
get { return visible; }
set { visible= value; OnPropertyChanged("Visible"); }
}
public MainWindowViewModel()
{
this.dispatcher = Dispatcher.CurrentDispatcher;
this.manager = manager;
}
public void ShowTab(){
this.Visible = true;
}
}
Child View Model:
public class ChildViewModel : AbstractBaseViewModel
{
private ParentViewModel parentVm;
public GeneralViewModel(ParentViewModel vm)
{
this.parentVm= vm;
}
public void Command(){
vm.ShowTab();
}
}
ParentView Xaml:
<TabItem Header="ViewWeWantToHide/Show"
Visibility="{Binding Visible,Converter={converter:BooleanToVisibilityConverter}}">
<views:SomeOtherView/>
</TabItem>
<TabItem Header="ChildView Tab">
<views:ChildView/>
</TabItem>
Without seeing all of your code, it'd be hard to guess what is causing your problem. However, I'd have to say that it is much more common in WPF to display view model instances in the UI and have WPF automatically display the relevant views, rather than displaying the views directly as you have. If you use view models rather than views then you'll have access to the child view model(s) from the parent view models.
If you did, or do have access to the child view model from the parent view model then I would advise that you use a simple delegate to 'pass your message' from the child view model to the parent view model instead. You can even define a parameter-less delegate and use it to just send a signal, rather than any property value.
You can declare a delegate, add a property of that type to your child UserControl and attach a handler in the main view model. Then you can call the delegate from your child UserControl and in the handler in the parent view model, you can call your method. For more information on this technique, please see my answer to the Passing parameters between viewmodels question (which answers a similar, but not exactly the same problem).
Thank you everyone for your assistance.
I found the issue which was unrelated to WPF and was actually a product of how I was setting the datacontext on my child views. In my parent window I was creating and registering a singleton instance of my ParentViewModel with the Unity container. This instance would then be injected in to all child views, the problem was the InitializeComponent method was being called before my parent view model was created and registered with the unity container meaning all child views were receiving completely a different instance.
Not working:
public MainWindow()
{
InitializeComponent();
if (DesignerProperties.GetIsInDesignMode(this))
{
this.DataContext = new ParentViewModel();
}
else
{
IUnityContainer container = UnityFactory.Retrieve();
ParentViewModel parentVM = container.Resolve<ParentViewModel>();
container.RegisterInstance<ParentViewModel>(parentVM);
this.DataContext = parentVM;
}
}
Working:
public MainWindow()
{
if (DesignerProperties.GetIsInDesignMode(this))
{
this.DataContext = new ParentViewModel();
}
else
{
IUnityContainer container = UnityFactory.Retrieve();
ParentViewModel parentVM = container.Resolve<ParentViewModel>();
container.RegisterInstance<ParentViewModel>(parentVM );
this.DataContext = parentVM;
}
/**
* Initialize after registering parent VM and setting the datacontext
*/
InitializeComponent();
}

Categories