I have :
MainWindow.xaml (where I have the frame)
LoginPage.xaml
SignUpPage.xaml
Here is the frame in MainWindow.xaml:
<Frame x:Name="MainPage"
Content="{Binding ApplicationViewModel.CurentPage,
Source={x:Static viewMod:ViewModelLocator.Instanze},
Converter={local:ApplicationPageValueConverter}}"/>
ApplicationViewModel is the application state as a view model :
public class ApplicationViewModel
{
/// <summary>
/// The current page of the application
/// </summary>
public ApplicationPage CurentPage { get; set; } = ApplicationPage.Login;
}
ViewModelLocator locates view models from the IoC for use in binding in Xaml files
public class ViewModelLocator
{
/// <summary>
/// Singleton instance of the locator
/// </summary>
public static ViewModelLocator Instance { get; private set; } = new ViewModelLocator();
/// <summary>
/// The application view model
/// </summary>
public static ApplicationViewModel ApplicationViewModel => IoC.Get<ApplicationViewModel>();
}
In ApplicationPageValueConverter I have this, to convert the page:
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((ApplicationPage)value)
{
case ApplicationPage.Login:
return new LoginPage();
case ApplicationPage.SignUp:
return new SignUpPage();
default:
Debugger.Break();
return null;
}
}
In the MainViewModel which is ViewModel for MainWindow.xaml.cs I have a button "SignUp", and when I click the button is going to execute ICommand whose is doing this:
public ICommand LoginCommand { get; set; }
LoginCommand = new RelayCommand(() => Login());
private void Login()
{
IoC.Get<ApplicationViewModel>().CurentPage = ApplicationPage.SignUp;
}
The value of ApplicationViewModel.CurentPage is changed to ApplicationPage.SignUp but it doesn't go to ApplicationPageValueConverter to convert/show the page.
Here is the IoC code where OnStartup I'm doing this :
base.OnStartup(e);
IoC.SetUp();
....
I can't get whay it doesn't show the page, what I'm doing wrong?
ApplicationViewModel should implement INotifyPropertyChanged and raise the PropertyChanged event whenever the CurrentPage property is set:
public class ApplicationViewModel : INotifyPropertyChanged
{
private ApplicationPage _currentPage = ApplicationPage.Login;
/// <summary>
/// The current page of the application
/// </summary>
public ApplicationPage CurentPage
{
get { return _currentPage; }
set { _currentPage = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
This is required for the view to be notified of the change and the converter to get invoked again.
Related
I created the INotifyPropertyChanged in a class
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
RaisePropertyChanged(propertyName);
}
protected void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now when I try to use it in usercontrol
public partial class myUserControl : UserControl, BindableBase
I encounter the following error
myUserControl can not have multiple base class
INotifyPropertyChanged is intended for view model classes, not for the views (or user controls) themselves. Therefore you don't normally need them in the views. You should use dependency properties instead, if you want to add fields to a user control.
See the example on UserControl:
/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(decimal), typeof(NumericUpDown),
new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
new CoerceValueCallback(CoerceValue)));
/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
This question already has answers here:
WPF MVVM command canexecute enable/disable button
(4 answers)
Disable button in WPF?
(5 answers)
How does one "disable" a button in WPF using the MVVM pattern?
(5 answers)
Command source disabling and enabling
(1 answer)
Closed 5 years ago.
I have the command
public class RelayActionCommand : ICommand
{
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; set; }
/// <summary>
/// The Delegate, used to represent the method which defines criteria for the execution
/// </summary>
public Predicate<object> CanExecuteAction { get; set; }
public bool CanExecute(object parameter)
{
if (CanExecuteAction != null)
{
return CanExecuteAction(parameter);
}
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteAction != null)
{
ExecuteAction(parameter);
}
}
}
To use it,
public RelayActionCommand SearchPersonCommnad { get; set; }
DataAccess objds;
public PersonViewModel()
{
Persons = new ObservableCollection<PersonInfo>();
objds = new DataAccess();
Persons = new ObservableCollection<PersonInfo>(objds.GetPersonData());
var defaultView = CollectionViewSource.GetDefaultView(Persons);
//based upon the data entered in the TextBox
SearchPersonCommnad = new RelayActionCommand()
{
CanExecuteAction = n=> !String.IsNullOrEmpty(Name),
ExecuteAction = n => defaultView.Filter = name => ((PersonInfo)name).FirstName.StartsWith(Name)
|| ((PersonInfo)name).LastName.StartsWith(Name)
|| ((PersonInfo)name).City==Name
};
At the beginning, the button is disabled. But in running time, it changes by different situations. My question is how to set up the button's IsEnabled property with it? Which means, when ExecuteAction I have to set up the property correctly.
UPDATE:
I use ICommand not DelegateCommand.
You can use the CanExecute method, but it is good practice is actually to avoid this, and bind the button's enabled state to a separate boolean property of the view model. Most other solutions will have unexpected effects, or be suboptimal. Why?
CanExecute is a method. This means that it needs to be polled for the button state to change. You can force the control that's using the command to re-poll on a status change, but the code is much cleaner and more straightforward if you just use a property on the view model. This is because as a method, you can't use INotifyPropertyChanged to notify for changes, whereas with a property you can.
The danger in using CanExecute is that the user will manage to click the button after the method would return false, but before the button's enablement has changed.
Edit: Code to do what you want:
public class ViewModel : INotifyPropertyChanged
{
private int someValue;
private bool isEnabled;
public ViewModel()
{
MyCommand = new RelayActionCommand(Click);
}
private void Click(object obj)
{
//Do something.
}
/// <summary>
/// Bind this to the IsEnabled property of the button, and
/// also the background using a convertor or see ButtonBackground.
/// </summary>
public bool IsEnabled => SomeValue < 5;
/// <summary>
/// Option 2 - use this property to bind to the background of the button.
/// </summary>
public Brush ButtonBackground => IsEnabled ? Brushes.SeaShell : Brushes.AntiqueWhite;
public int SomeValue
{
get { return someValue; }
set
{
if (value == someValue) return;
someValue = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsEnabled));
OnPropertyChanged(nameof(ButtonBackground));
}
}
/// <summary>
/// Bind this to the command of the button.
/// </summary>
public RelayActionCommand MyCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged
([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Relay command fixed up a bit to avoid using CanExecute:
public class RelayActionCommand : ICommand
{
public RelayActionCommand(Action<object> executeAction)
{
ExecuteAction = executeAction;
}
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; }
/// <summary>
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ExecuteAction?.Invoke(parameter);
}
//Deliberately empty.
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
EDIT 2: Code to do what you want using a DelegateCommand
Note, this does not use InvalidateRequerySuggested - mainly because it refreshes all buttons when any CanExecute changes, which is a poor solution. As you can see, this is less immediately straightforward than putting the code in the view model directly, but whatever floats your boat I guess.
public sealed class ViewModel : INotifyPropertyChanged
{
private int calls;
public ViewModel()
{
SafeOnceCommand = new RelayCommand(DoItOnce, HasDoneIt);
}
private bool HasDoneIt()
{
return Calls == 0;
}
private void DoItOnce()
{
if (Calls > 0) throw new InvalidOperationException();
Calls++;
}
public int Calls
{
get { return calls; }
set
{
if (value == calls) return;
calls = value;
OnPropertyChanged();
SafeOnceCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand SafeOnceCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class RelayCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool> canExecute;
private readonly List<EventHandler> invocationList = new List<EventHandler>();
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute();
}
public void Execute(object parameter)
{
execute();
}
/// <summary>
/// Method to raise CanExecuteChanged event
/// </summary>
public void RaiseCanExecuteChanged()
{
foreach (var elem in invocationList)
{
elem(null, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged
{
add { invocationList.Add(value); }
remove { invocationList.Remove(value); }
}
}
Can someone please tell me what is the mistake I have done ? Label in the view does not change even the Error.ErroMsg changed.
xaml view
<Label
x:Name="ErrorMsg"
Content="{Binding Path=Error.ErrorMsg, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="200"
Height="26"/>
View Model
private ErrorModel error = new ErrorModel();
public ErrorModel Error
{
get { return error; }
set { error = value; }
}
// This method will be called by a state machine using a delegate
public void displayErrorMessage(string message)
{
Error.ErrorMsg = message;
CommandManager.InvalidateRequerySuggested();
logger.Trace(" Successfully displayed the error message");
}
Model
// ModelBase inherited by INotifyPropertyChanged and IDisposable
class ErrorModel : ModelBase
{
private string errorMsg;
public string ErrorMsg
{
get { return errorMsg; }
set { errorMsg = value; OnPropertyChanged("ErrorMsg"); }
}
public ErrorModel() {
errorMsg = "TestHello";
}
}
ModelBase
public class ModelBase : INotifyPropertyChanged, IDisposable
{
///
/// Default constructor of view model base
///
protected ModelBase()
{
}
/// <summary>
/// Initialized property changed event handler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// This function will call by public setters of the model class
/// </summary>
/// <param name="propertyName">Specific property name that needs to identify unique model member</param>
protected virtual void OnPropertyChanged(string propertyName)
{
// Initialize handler by using property changed event handler delegate
PropertyChangedEventHandler handler = this.PropertyChanged;
// Check that current handler is available or not
if (handler != null)
{
// Initialize property changed event args
var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
// Call back using property changed event handler
handler(this, propertyChangedEventArgs);
}
}
/// <summary>
/// This is calle when leaving the view model
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Model dependent data is cleared here
/// </summary>
protected virtual void OnDispose() { }
}
//Calling displayErrorMessage through a delegate
string cpDoesntWorkError = "Charging point not available."
this.uiDelegator.showErrorMessage(cpDoesntWorkError);
GUIDelegator
public delegate void DisplayErrorMessageDelegate(string message);
/// <summary>
/// This will register the ErrorMesssage delegate passed by the RCU_GUI
/// </summary>
/// <param name="del">ErrorMessageDelegate object which need to registered will be passed</param>
public void setDisplayErrorMessageDelegate(DisplayErrorMessageDelegate del)
{
displayErrorMessageDelegate = del;
logger.Trace("Error message delegate successfully set by RCU_GUI");
}
/// <summary>
/// Call back to the displayErrorMessage which is in RCU_GUI
/// </summary>
/// <param name="message">Error message need to be displayed</param>
/// <returns></returns>
public void showErrorMessage(string message)
{
// If the error message delegate is not null
if (displayErrorMessageDelegate != null)
{
logger.Trace("Displaying the error message");
displayErrorMessageDelegate(message);
}
else
logger.Trace("null display error message delegate");
}
You have to be aware of few things:
Whether your method OnPropertyChanged in ModelBase properly raises PropertyChanged event
In your View Model in property Error you are not notifying view that this property changes. Then some other code might assign new ErrorModel instance before displayErrorMessage method is called. Due to that view is not aware that Error property changed, so it still display TestHello string. If that's the case you can modify your View Model:
public ErrorModel Error
{
get { return error; }
set { error = value; OnPropertyChanged("Error"); }
}
Your delegate which is calling displayErrorMessage method must use the same View Model object instance which is assigned to DataContext property of View. Because if not, then definitely View doesn't get notified about property changes.
I have a simple ViewModel which contains some buttons. The visibility of these buttons can be changed by events raised with the EventAggregator of PaP Prism which is also the only constructor parameter of this VM. The corresponding test works just fine when I don't use AutoFixture.
[Theory]
[InfrastructureAutoData]
public void AllButtonsAreShownWhenVisibilityStatusIsSet(
[Frozen]EventAggregator eventAggregator,
ActionBarViewModel sut)
{
eventAggregator
.GetEvent<ActionButtonActivationEvent>()
.Publish(VisibleActionButtons.All);
sut.CancelButtonVisibility.Should().Be(Visibility.Visible);
sut.BackButtonVisibility.Should().Be(Visibility.Visible);
sut.NextButtonVisibility.Should().Be(Visibility.Visible);
sut.Visiblity.Should().Be(Visibility.Visible);
}
Unfortunately, it does not function like it is given here because the EventAggregator instance injected to the ViewModel by AutoFixture is another instance than the one injected into the test.
public class InfrastructureAutoData : AutoDataAttribute
{
public InfrastructureAutoData()
{
Initialize();
}
private void Initialize()
{
this.Fixture.Customize(new AutoMoqCustomization());
Fixture.Register<IEventAggregator>(() => new EventAggregator());
}
}
public class ActionBarViewModel
{
public ActionBarViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<ActionButtonActivationEvent>()
.Subscribe(ActivateButtons);
ActivateButtons(VisibleActionButtons.None);
}
/// <summary>
/// Visibility of a button which cancels the current action.
/// </summary>
public Visibility CancelButtonVisibility { get; private set; }
/// <summary>
/// Visibility of a button which loads the previous screen.
/// </summary>
public Visibility BackButtonVisibility { get; private set; }
/// <summary>
/// Visibility of a button with which the next step can be reached.
/// </summary>
public Visibility NextButtonVisibility { get; private set; }
/// <summary>
/// Visibility of the complete view which will be automatically
/// set by the visibile buttons.
/// </summary>
public Visibility Visiblity { get; private set; }
private void ActivateButtons(VisibleActionButtons buttonVisibility)
{
if (buttonVisibility == VisibleActionButtons.All)
{
NextButtonVisibility =
CancelButtonVisibility =
BackButtonVisibility = Visibility.Visible;
}
else
{
NextButtonVisibility =
buttonVisibility == VisibleActionButtons.Next
? Visibility.Visible
: Visibility.Hidden;
CancelButtonVisibility =
buttonVisibility == VisibleActionButtons.Cancel
? Visibility.Visible
: Visibility.Hidden;
BackButtonVisibility =
buttonVisibility == VisibleActionButtons.Back
? Visibility.Visible
: Visibility.Hidden;
}
Visiblity =
buttonVisibility == VisibleActionButtons.None
? Visibility.Collapsed
: Visibility.Visible;
}
}
It seems to me, that the [Frozen] attribute does not work as expected but I am also not sure if a did every thing right. I was also wondering why Inject, instead of Register, does not work. I would have expected that their is no difference.
Thank you for your help.
Edit: I use version 3.7
You're freezing the concrete class EventAggregator, but injecting the interface IEventAggregator. They are two different types, so the IEventAggregator instance you get isn't the frozen EventAggregator.
[Frozen(As = typeof(IEventAggregator))]EventAggregator eventAggregator
should do the trick.
I am just getting started with MVVM so apologies if I've done something really stupid. I tried writing a very simple test to see if I could remember everything, and for the life of me I can't see why its not working.
In my view I have a textBox where its text property is bound to a value in the ViewModel. Then when pressing a button the value should be altered and the textBox update.
I can see the value does alter (I have added a MessageBox.Show() line in the buttom press command) however the textBox does not update.
I assume that this means I have not properly implemented the INotifyPropertyChanged event properly but am unable to see my mistake.
Could anyone point me in the right direction?
Here is the code:
View
<Window x:Class="Mvvm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBox Height="40" Width="200" Text="{Binding helloWorld.Message, UpdateSourceTrigger=PropertyChanged}"/>
<Button Command="{Binding UpdateTimeCommand}">Update</Button>
</StackPanel>
</Window>
Behind View
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel.MainWindowViewModel();
}
}
ViewModel
namespace Mvvm.ViewModel
{
internal class MainWindowViewModel
{
private HelloWorld _helloWorld;
/// <summary>
/// Creates a new instance of the ViewModel Class
/// </summary>
public MainWindowViewModel()
{
_helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
UpdateTimeCommand = new Commands.UpdateTimeCommand(this);
}
/// <summary>
/// Gets the HellowWorld instance
/// </summary>
public HelloWorld helloWorld
{
get
{
return _helloWorld;
}
set
{
_helloWorld = value;
}
}
/// <summary>
/// Updates the time shown in the helloWorld
/// </summary>
public void UpdateTime()
{
helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
}
public ICommand UpdateTimeCommand
{
get;
private set;
}
}
Model
namespace Mvvm.Model
{
class HelloWorld : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public HelloWorld(string helloWorldMessage)
{
Message = "Hello World! " + helloWorldMessage;
}
private string _Message;
public string Message
{
get
{
return _Message;
}
set
{
_Message = value;
OnPropertyChanged("Message");
}
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
}
Commands
namespace Mvvm.Commands
{
internal class UpdateTimeCommand : ICommand
{
private ViewModel.MainWindowViewModel _viewModel;
public UpdateTimeCommand(ViewModel.MainWindowViewModel viewModel)
{
_viewModel = viewModel;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_viewModel.UpdateTime();
}
}
}
Sorry for such a long post and it being a spot my mistake post but I've looked at it for so long and I don't know what I'm doing wrong
Thanks!
The Problem that you have is that you are changing the wrong Property. Instead of changing the HelloWorld.Message Property, you are changing MainWindowViewModel.HelloWorld property. Your code will work OK if you change this line:
public void UpdateTime()
{
helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
}
For this one
public void UpdateTime()
{
helloWorld.Message = "The time is " + DateTime.Now.ToString("HH:mm:ss");
}
If you want to keep your original code, then you need to implement INotifyPropertyChanged for your ViewModel, and rise the event when you change helloWorld object.
Hope this helps
I think you need to implement PropertyChanged notification on your ViewModel. You are creating a new HelloWorld in the UpdateTime method, but the UI doesn't know it.
Edit
I have a ViewModel base class which I derive all of my ViewModels from. It implements INotifyPropertyChanged, and has references to my relay command classes, and some other common stuff. I recommend always having INotifyPropertyChanged implemented on the ViewModel. The ViewModel is there to expose data to the UI, and it cant do that for data that changes without that interface.
i think your ViewModel needs to implement INotifyPropertyChanged too,
or you can set the DataContext before you call InitializeComponents(), if you do that you should change your code to NOT create a new instance every update like Agustin Meriles said.
i think you mistake Model and VM: Model is MainWindowViewModel and VM is HelloWorld
In your VM (class HelloWorld ) you need use your model
So, your classes will look like:
using System.ComponentModel;
namespace WpfApplication1
{
public sealed class TextVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextInfo _info;
public TextVM()
{
_info = new TextInfo();
}
public string MyText
{
get { return _info.MyText; }
set
{
_info.MyText = value;
OnPropertyChanged("MyText");
}
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
}
using System;
namespace WpfApplication1
{
public sealed class TextInfo
{
public TextInfo()
{
MyText = String.Empty;
}
public string MyText { get; set; }
}
}
inset inside your ICommands