I am writing a program using Xamarin, Shell and MVVM.I want to send a parameter to next page and I am using the following code:
await Shell.Current.GoToAsync($"//{nameof(CopyBooksPage)}?RegisteredUserId={registeredUser.Id}");
Question: How to get the parameter before binding?
First wrong solution:
In CopyBooksPage.xaml I have binding to properties in VM but I have no affiliation with the VM.
I do this:
[XamlCompilation(XamlCompilationOptions.Compile)]
[QueryProperty(nameof(RegisteredUserId), "RegisteredUserId")]
public partial class CopyBooksPage : ContentPage
{
private int _registeredUserId;
public int RegisteredUserId
{
get { return _registeredUserId; }
set
{
_registeredUserId = value;
CopyBooksViewModel copyBooksViewModel = App.GetViewModel<CopyBooksViewModel>();
copyBooksViewModel.RegisteredUserId = _registeredUserId;
BindingContext = copyBooksViewModel;
copyBooksViewModel.RefreshBinding();
}
}
public CopyBooksPage()
{
InitializeComponent();
}
}
Call order:
construktor
properties RegisteredUserId
The problem is twofold.
After calling the constructor, I get information about binding errors (because there is no VM). It's not annoying but I want (need) to get rid of it.
I do the binding only in the propertis and this causes the problem that for each VM propertis I have to call the OnPropertyChanged method to refresh the binding. And this is troublesome for me. I do not want to do it.
Second wrong solution
Code behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CopyBooksPage : ContentPage
{
public CopyBooksPage()
{
InitializeComponent();
BindingContext = App.GetViewModel<CopyBooksViewModel>();
}
}
ViewModel:
class CopyBooksViewModel : BaseViewModel, IQueryAttributable
{
private int registeredUserId;
//read from database
private CopyBook copyBookModel;
public string BookTitle
{
get { return copyBookModel.Title; }
set
{
copyBookModel.Title = value;
OnPropertyChanged(nameof(Title));
}
}
public CopyBooksViewModel()
{
}
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
if (query.ContainsKey("RegisteredUserId"))
{
registeredUserId = int.Parse(HttpUtility.UrlDecode(query["RegisteredUserId"]));
copyBookModel = ReadFromDatabase(registeredUserId);
}
}
}
Call order:
construktor
Binding
method ApplyQueryAttributes
When binding the BookTitle, I reference the copyBookModel which is null. I could secure it.
The problem is that the ApplyQueryAttributes method is called last. In it again I would have to call OnPropertyChanged for all propertis. I do not want to do it.
I am learning to create a WPF application following the MVVM patern. I'm try change data in viewmodel from service class but it can work, here is example code:
In MainWindow.xaml:
<Grid Grid.Row="6">
<TextBox materialDesign:HintAssist.Hint="Status"
Text="{Binding Status, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
VerticalAlignment="Center" />
</Grid>
In MainViewModel.cs:
public class MainViewModel : BaseViewModel
{
private static MainViewModel _instance = new MainViewModel();
public static MainViewModel Instance { get { return _instance; } }
//...
// Status
private string _Status = "Status";
public string Status { get => _Status; set { _Status = value; OnPropertyChanged(); } }
public MainViewModel()
{
////
// => This command can change status
// Start
StartCommand = new RelayCommand<object>((p) => { return true; }, (p) =>
{
OutStatus("Task success!");
});
}
public void OutStatus(string status)
{
Status = status;
}
}
In UtilitiesService.cs
public static class UtilitiesService{
public static void SetStatus(){
// => Here i can't change Status and can't binding to MainWindow.xaml
MainViewModel.Instance.OutStatus("Change Status in service");
}
}
So how can I change a property in manviewmodel in service file.Sorry Im so noob :))
You are using different instances of MainViewModel for data binding and for updating.
Also don't use public static instances or members across the application. Instead directly pass around the instance (in your case MainViewModel and UtilitiesService).
Generally static class members like properties or fields introduce a potential memory leak, because the garbage collector can't collect them to free memory. It also makes unit testing difficult and defies the concept of object oriented language key features like encapsulation. It will make code hard to modify.
In the simplest scenario, you can create the MainViewModel instance in your MainWindow. You can also create a shared instance of UtilitiesService at this point as well.
It's unclear what purpose UtilitiesService has. If it is meant to update MainViewModel by other View Model classes you can do it your way. If it is meant to be used in the Model, then you shouldn't do it your way. In this case your MainVoewModel would listen to the UtilitiesService events to update itself. Because in MVVM the Model does never talk to the View Model.
The recommended C# naming convention suggests to name fields using the camelCase pattern (starting with a lower case letter). Microsoft Docs: Naming Guidelines
A TextBox.Text binding that is configured to bind OneWay is pretty useless. In this case the TextBox only serves as display. You should then use TextBlock instead.
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
//...
// Status
private string _status = "Status";
public string Status { get => _status; set { _status = value; OnPropertyChanged(); } }
public MainViewModel()
{
////
// => This command can change status
// Start
StartCommand = new RelayCommand<object>((p) => { return true; }, (p) =>
{
SetStatus("Task success!");
});
}
public void SetStatus(string status)
{
Status = status;
}
}
UtilitiesService.cs
public class UtilitiesService
{
private MainViewModel MainViewModel { get; }
public void UtilitiesService(MainViewModel mainViewModel)
{
this.MainViewModel = mainViewModel;
}
public void SetStatus()
{
// Change MainViewModel.Status and update bindings in MainWindow.xaml
this.MainViewModel.SetStatus("Change Status in service");
}
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var mainViewModel = new MainViewModel();
this.DataContext = mainViewModel;
var sharedUtilitiesService = new UtilitiesService(mainViewModel);
// Pass the shared UtilitiesService instance to other view model classes
// to allow them to update the MainViewModel anonymously.
var otherViewModel = new OtherViewModel(sharedUtilitiesService);
}
}
MainWindow.xaml
<Window>
<TextBlock Text="{Binding Status}" />
</Window>
I want to change value of ViewModel property (which is binded with DataContext). Extremely easy with classic Events, with Commands it becomes formidable task. This is my code:
public partial class MainWindow : Window
{
ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
_vm.BtnClick = new BtnClick();
DataContext = _vm;
}
}
public class BtnClick : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Debug.WriteLine(parameter.ToString());
}
}
public class ViewModel
{
public ICommand BtnClick { get; set; }
public string Input { get; set; }
public string Output { get; set; }
}
<StackPanel>
<TextBox Text="{Binding Input}"></TextBox>
<TextBlock Text="{Binding Output}"></TextBlock>
<Button Command="{Binding Path=BtnClick}" CommandParameter="{Binding Input}">Translate</Button>
</StackPanel>
Command properly takes value from TextBox, now i want to do things with this value and save it to Output. And problem is from Command perspective i cannot access both DataContext and ViewModel.
The implementation of any command is usually in a viewmodel.
A framework or helper class is routinely used.
For example:
https://riptutorial.com/mvvm-light/example/32335/relaycommand
public class MyViewModel
{
.....
public ICommand MyCommand => new RelayCommand(
() =>
{
//execute action
Message = "clicked Button";
},
() =>
{
//return true if button should be enabled or not
return true;
}
);
Here, there is an anonymous method with that "clicked button" in it.
This will capture variables in the parent viewmodel.
You may therefore set a public property in the viewmodel that's bound to the text property in your view.
For the view to respond you will need to implement inotifypropertychanged and raise property changed in the setter of that public property.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification.
From the above.
If PersonName was bound to a textblock in the view.
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged();
}
}
In the command you can do:
PersonName = "Andy";
Which calls the setter of PersonName and a textblock bound to PersonName will read the new value.
According to this answer I should not need to bother about NotifyPropertyChanges Bubbling up the hierachie, still I can't get it to work with a (simplified test-) structure like that:
a Data-Holding Class
public class TestNotifyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _Test = "default";
public string Test
{
get
{
return _Test;
}
set
{
if(_Test!=value)
{
_Test = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Test"));
}
}
}
}
A ViewModel that used that Test-Class and Test-Property:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TestNotifyChanged tnc = new TestNotifyChanged(); // only to init, otherwise VS screams at me
public ViewModel(TestNotifyChanged tnc)
{
tnc = tnc; // getting an instance of TestNotifyChanged from "Master" passed in, which hopefully will be replaces by a singleton class.
}
private string _Test;
public string Test
{
get
{
return tnc.Test; // this might be the crucial part!?
}
set
{
if (_Test != value) // never hits that, as I would expect, but you never know..
{
_Test = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Test")); // of course also never hit, as expected
}
}
}
}
And finally my MainWindow cs
public partial class MainWindow : Window
{
TestNotifyChanged tnc;
public MainWindow()
{
InitializeComponent();
tnc = new TestNotifyChanged();
DataContext = new ViewModel(tnc); // pass in my Test-Object that has the Values.
}
private void ButtonGet_Click(object sender, RoutedEventArgs e)
{
tnc.Test = "new Value";
MessageBox.Show($"{tnc.Test}"); // got "new Value" here!
}
}
And in xaml I have besides that one button a simple TextBlock that is bound to the ViewModel's Test Property:
<TextBlock x:Name="OutputId" Text="{Binding Path=Test, Mode=OneWay}"/>
What is happening now:
The default value "default" is shown in TextBlock.
When I click the button the messageBox shows the "new Value"
TextBlock is not updating to "new Value"
What I want to achieve:
seems easy: TextBlock should update to "new Value"
I can easily make this work when I directly set the Test Value on the ViewModel - but this doesn't seem right and is far away from what I thought I could structure my app/code. The future goal is to have a Singleton (static won't work I figured out) "RecordStore" that has most of the data (and gets it from an API, from local Database, or just from Memory if any of these are done)
So the question is:
Why is the NotifyPropertyChange not bubbling up to the View/ViewModel?
Or is there another issue I don't see?
I've read INotifyPropertyChanged bubbling in class hierarchy
and
What is a good way to bubble up INotifyPropertyChanged events through ViewModel properties with MVVM?
and
https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface
and
OnPropertyChange called but not taking any effect on UI
most of those questions are also quite old...
EDIT:
I tried #MineR 's suggestion that way:
// made tnc public in ViewModel
public TestNotifyChanged tnc = new TestNotifyChanged();
// changed Binding directly to that (and it's Property):
<TextBlock x:Name="OutputId" Text="{Binding Path=tnc.Test, Mode=OneWay}"/>
Unfortunately now I don't even get the default, so I must have misunderstood smth.
EDIT2:
I did one thing wrong in the 1st edit:
// this isn't recognized as bindable parameter:
public TestNotifyChanged tnc = new TestNotifyChanged();
// it instead has to be
public TestNotifyChanged tnc { get; }
And I made it TNC, removed the local Test parameter, bound directly to Path=TNC.Test
So I understood, that PropertyChanges do not bubble up the way I hoped/thought, it's better to bind directly down to the nested object.
"Bubbling" is a concept of routed events. A regular event like PropertyChanged doesn't "bubble up".
Besides the apparent bug tnc = tnc; in the ViewModel (which should be this.tnc = tnc;) the Test properties of the two classes are unrelated. In order to update its own Test property, ViewModel must register a PropertyChanged event handler at tnc. And it must update the property of tnc when its own Test property changes.
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TestNotifyChanged tnc;
public ViewModel(TestNotifyChanged t)
{
tnc = t;
tnc.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Test) || string.IsNullOrEmpty(e.PropertyName))
{
Test = tnc.Test; // update ViewModel.Test from TestNotifyChanged.Test
}
};
}
private string test;
public string Test
{
get
{
return test; // always return own value
}
set
{
if (test != value)
{
test = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Test)));
tnc.Test = Test; // update TestNotifyChanged.Test from ViewModel.Test
}
}
}
}
Alternatively, drop the Test property's backing field and only operate on tnc.Test:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TestNotifyChanged tnc;
public ViewModel(TestNotifyChanged t)
{
tnc = t;
tnc.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Test) || string.IsNullOrEmpty(e.PropertyName))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Test)));
}
};
}
public string Test
{
get { return tnc.Test; }
set { tnc.Test = Test; }
}
}
Fortunately, it is not necessary at all.
There could instead just be a public Tnc property like
public class ViewModel
{
public TestNotifyChanged Tnc { get; }
public ViewModel(TestNotifyChanged tnc)
{
Tnc = tnc;
}
}
with a Binding like this:
<TextBlock Text="{Binding Tnc.Test}"/>
So my first attempt did everything out of the code behind, and now I'm trying to refactor my code to use the MVVM pattern, following the guidance of the MVVM in the box information.
I've created a viewmodel class to match my view class, and I'm moving the code out of the code behind into the viewmodel starting with the commands.
My first snag is trying to implement a 'Close' button that closes the window if the data has not been modified. I've rigged up a CloseCommand to replace the 'onClick' method and all is good except for where the code tries to run this.Close(). Obviously, since the code has been moved from a window to a normal class, 'this' isn't a window and therefore isn't closeable. However, according to MVVM, the viewmodel doesn't know about the view, so i can't call view.Close().
Can someone suggest how I can close the window from the viewmodel command?
I personally use a very simple approach: for every ViewModel that is related to a closeable View, I created a base ViewModel like this following example:
public abstract class CloseableViewModel
{
public event EventHandler ClosingRequest;
protected void OnClosingRequest()
{
if (this.ClosingRequest != null)
{
this.ClosingRequest(this, EventArgs.Empty);
}
}
}
Then in your ViewModel that inherits from CloseableViewModel, simply call this.OnClosingRequest(); for the Close command.
In the view:
public class YourView
{
...
var vm = new ClosableViewModel();
this.Datacontext = vm;
vm.ClosingRequest += (sender, e) => this.Close();
}
You don't need to pass the View instance to your ViewModel layer. You can access the main window like this -
Application.Current.MainWindow.Close()
I see no issue in accessing your main window in ViewModel class as stated above. As per MVVM principle there should not be tight coupling between your View and ViewModel i.e. they should work be oblivious of others operation. Here, we are not passing anything to ViewModel from View. If you want to look for other options this might help you - Close window using MVVM
My solution to close a window from view model while clicking a button is as follows:
In view model
public RelayCommand CloseWindow;
Constructor()
{
CloseWindow = new RelayCommand(CloseWin);
}
public void CloseWin(object obj)
{
Window win = obj as Window;
win.Close();
}
In View, set as follows
<Button Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=WindowNameTobeClose}" Content="Cancel" />
I do it by creating a attached property called DialogResult:
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 && (bool?)e.NewValue == true)
window.Close();
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
then write this to you XAML, in the window tag
WindowActions:DialogCloser.DialogResult="{Binding Close}"
finally in the ViewModel
private bool _close;
public bool Close
{
get { return _close; }
set
{
if (_close == value)
return;
_close = value;
NotifyPropertyChanged("Close");
}
}
if you change the Close to true, the window will be closed
Close = True;
Here is the simplest and pure MVVM solution
ViewModel Code
public class ViewModel
{
public Action CloseAction { get; set; }
private void CloseCommandFunction()
{
CloseAction();
}
}
Here is XAML View Code
public partial class DialogWindow : Window
{
public DialogWindow()
{
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.CloseAction = Close;
}
}
This solution is quick and easy. Downside is that there is some coupling between the layers.
In your viewmodel:
public class MyWindowViewModel: ViewModelBase
{
public Command.StandardCommand CloseCommand
{
get
{
return new Command.StandardCommand(Close);
}
}
public void Close()
{
foreach (System.Windows.Window window in System.Windows.Application.Current.Windows)
{
if (window.DataContext == this)
{
window.Close();
}
}
}
}
MVVM-light with a custom message notification to avoid the window to process every notificationmessage
In the viewmodel:
public class CloseDialogMessage : NotificationMessage
{
public CloseDialogMessage(object sender) : base(sender, "") { }
}
private void OnClose()
{
Messenger.Default.Send(new CloseDialogMessage(this));
}
Register the message in the window constructor:
Messenger.Default.Register<CloseDialogMessage>(this, nm =>
{
Close();
});
This is very similar to eoldre's answer. It's functionally the same in that it looks through the same Windows collection for a window that has the view model as its datacontext; but I've used a RelayCommand and some LINQ to achieve the same result.
public RelayCommand CloseCommand
{
get
{
return new RelayCommand(() => Application.Current.Windows
.Cast<Window>()
.Single(w => w.DataContext == this)
.Close());
}
}
using MVVM-light toolkit:
In the ViewModel:
public void notifyWindowToClose()
{
Messenger.Default.Send<NotificationMessage>(
new NotificationMessage(this, "CloseWindowsBoundToMe")
);
}
And in the View:
Messenger.Default.Register<NotificationMessage>(this, (nm) =>
{
if (nm.Notification == "CloseWindowsBoundToMe")
{
if (nm.Sender == this.DataContext)
this.Close();
}
});
This is taken from ken2k answer (thanks!), just adding the CloseCommand also to the base CloseableViewModel.
public class CloseableViewModel
{
public CloseableViewModel()
{
CloseCommand = new RelayCommand(this.OnClosingRequest);
}
public event EventHandler ClosingRequest;
protected void OnClosingRequest()
{
if (this.ClosingRequest != null)
{
this.ClosingRequest(this, EventArgs.Empty);
}
}
public RelayCommand CloseCommand
{
get;
private set;
}
}
Your view model, inherits it
public class MyViewModel : CloseableViewModel
Then on you view
public MyView()
{
var viewModel = new StudyDataStructureViewModel(studyId);
this.DataContext = viewModel;
//InitializeComponent(); ...
viewModel.ClosingRequest += (sender, e) => this.Close();
}
Given a way, Please check
https://stackoverflow.com/a/30546407/3659387
Short Description
Derive your ViewModel from INotifyPropertyChanged
Create a observable property CloseDialog in ViewModel, Change CloseDialog property whenever you want to close the dialog.
Attach a Handler in View for this property change
Now you are almost done. In the event handler make DialogResult = true
first of all give your window a name like
x:Name="AboutViewWindow"
on my close button I've defined Command and Command Parameter like
CommandParameter="{Binding ElementName=AboutViewWindow}"
Command="{Binding CancelCommand}"
then in my view model
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
{
_cancelCommand = new DelegateCommand<Window>(
x =>
{
x?.Close();
});
}
return _cancelCommand;
}
}
Most MVVM-compliant solution using HanumanInstitute.MvvmDialogs
Implement ICloseable interface in your ViewModel and that's it!
No code in your view whatsoever.