I have a UserControl and a Data Object which I want to bind them together so The WPF UserControl always presents the data in the object:
public partial class PersonRectangle : UserControl
{
public PersonRectangle()
{
InitializeComponent();
}
}
public class Person
{
public string fname;
public string lname;
public Person()
{
}
}
What is the best way to wire any Person to the associated wpf View? Should I add a property of type Person to the partial class PersonRectangle? How should I do this considering MVVM paradigms?
DataContext property from UserControl is the key to mvvm implementation, Person is your model and should not be exposed directly to View but through ViewModel object.
public class PersonViewModel: INotifyPropertyChanged
{
public PersonViewModel()
{
/*You could initialize Person from data store or create new here but not necessary.
It depends on your requierements*/
Person = new Person();
}
private Person person;
public Person Person{
get {return person;}
set {
if ( person != value){
person = value;
OnPropertyChanged()
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in your View (UserControl):
public partial class PersonRectangle : UserControl
{
public PersonRectangle()
{
InitializeComponent();
DataContext = new PersonViewModel();
}
}
You already set DataContext so you the can bind your view controls to Person properties, note the use of Person property from ViewModel here:
<TextBox Text="{Binding Path=Person.Name, Mode=TwoWay}" />
My final words would be to suggest you to use an MVVM framework like Prism or Caliburn.Micro
EDIT:
You should consider to expose Person data as properties and not as public variables as you have now.
Related
I work on developing a Xamarin forms application about video games using MVVM for my pet project. I am new in Xamarin forms, I need your advice.
I had a few ViewModels with the same code in it. I decided to create one base ViewModel and inherit others from that.
I have ViewModelBase with PropertyChanged event:
public class ViewModelBase : INotifyPropertyChanged
{
private string _title;
public string Title
{
get => _title;
set => Set(ref _title, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I also have a base GamesViewModel from which others are inherited, there is a lot of code, that is why I will show only that I inherit everything properly:
public class GamesViewModel : ViewModelBase
Below are derived ViewModels:
public class NewGamesViewModel : GamesViewModel
and
public class SearchViewModel : GamesViewModel
Problem is I have SearchGame property in the base GamesViewModel:
private string _searchGame;
public string SearchGame
{
get => _searchGame;
set => Set(ref _searchGame, value);
}
When the program is running I put the value inside SearchGame property,and in GamesViewModel I can see that value assigned, but in derived ViewModels value is null:
For example, in debugging in SearchViewModel which is inherited from the GamesViewModel I check the value and it's null.
var test = SearchGame; - value is null here
I don't create any object of GamesViewModel in the project.
In the pages Code-behind files in BindingContext I do like this:
public partial class SearchGamePage : ContentPage
{
public SearchGamePage()
{
InitializeComponent();
BindingContext = new SearchViewModel();
}
}
I tried to explain as more as I can. Maybe in Xamarin forms inheritance with ViewModels work not obviously.
Thank you in advance for the help!
Have a nice day!
First of all, I made a ViewModelBase like following code.
public class ViewModelBase : INotifyPropertyChanged
{
private string _title;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs((propertyName)));
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
I set a text for SearchGame in the SearchViewModel.
public class SearchViewModel : GamesViewModel
{
public SearchViewModel()
{
SearchGame = "test";
}
}
Here is code about GamesViewModel.
public class GamesViewModel:ViewModelBase
{
private string _searchGame;
public string SearchGame
{
get => _searchGame;
set => SetProperty(ref _searchGame, value);
}
}
Then I make a Label to binding the SearchGame property like the xaml.
<Label Text="{Binding SearchGame}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
Here is layout background code.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
this.BindingContext = new SearchViewModel();
}
}
Here is running screenshot.
I sent a message to the GamesViewModel with search game value like this:
MessagingCenter.Send(this, "search_game", titleViewModel.SearchGame);
var detailPage = (Application.Current.MainPage as MasterDetailPage)?.Detail;
await detailPage.Navigation?.PushAsync(new SearchGamePage());
I subscribe to this message in GamesViewModel constructor and assigned to SearchGame property message value like this:
MessagingCenter.Subscribe<CustomTitleView, string>(this, "search_game", (sender, message) =>
{
SearchGame = message;
});
But as you can see in the code above I create SearchGamePage instance. SearchGamePage constructor calls and I create in it new SearchViewModel instance:
public partial class SearchGamePage : ContentPage
{
public SearchGamePage()
{
InitializeComponent();
BindingContext = new SearchViewModel();
}
}
That's why GamesViewModel constructor calls again and in SearchGame property assign null.
I solved this problem using DependencyService.Register in App.xaml.cs file:
public App()
{
InitializeComponent();
DependencyService.Register<MockDataStore>();
DependencyService.Register<IGameApiClient, GameApiClient>();
DependencyService.Register<IFavoriteGameService, FavoriteGameService>();
DependencyService.Register<GamesViewModel>();
DependencyService.Register<SearchViewModel>();
DependencyService.Register<NewGamesViewModel>();
DependencyService.Register<TitleViewModel>();
MainPage = new MainPage();
}
And finally, in SearchGamePage constructor I assign to BindingContext SearchViewModel like this:
public SearchGamePage()
{
InitializeComponent();
BindingContext = DependencyService.Get<SearchViewModel>();
}
Now everything is fine when I assign value to SearchGame property in GamesViewModel using messenger I can see the same value in the SearchGame property in SearchViewModel because this ViewModel wasn't recreated.
I understand that I didn't give full info when I asked a question. But I hope that if somebody else will face the same problem this answer will be useful.
I'm trying to fiddle a little with WPF bindings, so I created a simple project.
Here's the code:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age {
get { return age; }
set {
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if(PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
My viewmodel contains ObservableCollection of Person, and single Person to track selected Person.
I've bound listbox's ItemsSource to ObservableCollection, and SelectedItem to single Person, called CurrentPerson. Also, I've bound TextBox to CurrentPerson.Name.
Code works fine, but whenever I change content of TextBox - my listbox also changes. And no matter what combination of "OneWay, TwoWay, OneWayToSource" binding modes on listbox\selecteditem I cannot prevent listbox from updating from CurrentPerson.
How can I prevent this behavior? I'd like to update listbox from CurrentPerson only by using ICommand interface from VM.
There is only one copy of the Person object which is being used in both ListBox.ItemsSource and TextBox.Text, so naturally updating that object from one location will reflect the change in the other as well.
Two easy solutions would be
Change the BindingMode on TextBox.Text to Explicit, so it doesn't update the Person object until you tell it to
Use a separate string property for TextBox.Text and copy it over to your SelectedPerson.Name whenever the command executes
Personally I prefer the second option because I'm not a big fan of bindings that don't accurately reflect the data object behind the UI component, and it would allow the user to change the SelectedItem without resetting the TextBox value.
For an example of the second option, your ViewModel might look like this :
public class MyViewModel()
{
ObservableCollection<Person> People { get; set; }
Person SelectedPerson { get; set; }
string NewPersonName { get; set; }
ICommand UpdatePersonName { get; }
}
where the UpdatePersonName command would execute
SelectedPerson.Name = NewPersonName;
and the CanExecute would only return true if
SelectedPerson != null
&& !NewPersonName.IsNullOrWhiteSpace()
&& NewPersonName != SelectedPerson.Name
I'm not sure if I've followed the question properly.
So, we have a class Person as
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Age
{
get { return age; }
set
{
age = value;
FirePropertyChanged("Age");
}
}
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
private int age;
private string name;
}
And we have a view model as
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Person> List { get; set; }
Person currentPerson;
public Person CurrentPerson {
get { return currentPerson; }
set { currentPerson = value;
FirePropertyChanged("CurrentPerson");
}
}
private void FirePropertyChanged(string v)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(v));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The xaml is
<ListBox ItemsSource="{Binding List}" SelectedItem="{Binding CurrentPerson}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" Width="100" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And I bind the view model to the view via
ViewModel vm = new ViewModel();
vm.List = new ObservableCollection<Person>();
foreach (var i in Enumerable.Range(1,10))
{
vm.List.Add(new Person() { Name = "Test" + i.ToString(), Age= i });
}
vm.CurrentPerson = null;
this.DataContext = vm;
Whenever I change the value at textbox, it updates the name properly. I tried to add a handler for list changed, but it doesn't happen to get triggered.
vm.List.CollectionChanged += List_CollectionChanged;
void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
MessageBox.Show(e.Action.ToString());
}
Can you comment if it isn't the same as your problem statement?
If you want to control when and what is saved/updated, you obviously need is a ViewModel for editing your Person model.
When selecting a person in your Listbox, you have to pass the person's id (avoid passing the object itself) to the PersonEditViewModel which is bound to the properties that shall be edited, load the persons data into the PersonEditViewModel and then edit. Once you hit the "Save" button, it should commit the change and update the database or whatever you are using for persistence.
Use either events/messages to pass values/events back and forth, or use a navigation approach (like INavigationAware interface in Prism).
I have a listbox which i want to get updated when the items get added to a list. I understand I need to bind the listbox. I was trying to follow this question/answer.
I have a ViewModel which handles the list:
namespace TESTS
{
public class ViewModel : INotifyPropertyChanged
{
private List<Cars> _listCars;
public List<Cars> listCars
{
get
{
return _listCars;
}
set
{
if (_listCars == value)
{
return;
}
this.RaisePropertyChanged("Message");
_listCars = value;
this.RaisePropertyChanged("Message");
}
}
public ViewModel()
{
listCars = new List<Cars>();
}
protected void RaisePropertyChanged(string propertyName)
{
Debug.WriteLine("Property Changed");
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the class Cars:
public class Cars: INotifyPropertyChanged
{
public string model{ get; set; }
public string year{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
So I did the binding of listbox to the property path in my Viewmodel which is listCars.
<ListBox .... ItemsSource="{Binding listCars}">
So when in my Main.xaml.cs. I do a button click and add the item. It does not get added to the listbox even though its bind to the list on view model.
public sealed partial class MainPage : Page
{
public static ViewModel vm = new ViewModel();
public MainPage()
{
this.InitializeComponent();
this.DataContext = vm;
}
private void button_Click(object sender, RoutedEventArgs e)
{
Cars x = new Cars();
x.model = "Ford";
x.Year = "1998";
vm.listCars.Add(x);
}
}
I hope I explained what i implemented well enough. Is there something wrong in my implementation of ViewModel. I am new to MVVM. Please help.
Use ObservableCollection<T>, not List<T>. The former is designed to be used with MVVM, the latter is not. You'll get all your notifications automatically. It's doable with List<T>, but you'll have to write much more code and the performance will be much worse, especially with big collections. Just don't do it.
If you create the collection in the constructor, assign it to a read-only property and never change its instance (and this is the way you should do it), you don't even need to implement INPC.
When implementing INPC, you're expected to call RaisePropertyChanged after you've changed the property, once, and with the property name that has been changed, not a random unrelated string.
Often when I’m designing an MVVM application, the following scenario comes up. A Window, having multiple children, sharing the same data source.
I’m trying to decide on the best way to implement a single datasource for all children. There are 3 options I can think of, all with their own advantages and disadvantages.
Example
The Window has two child UserControls, each in their own tab.
UI is linked up like this.
In order to keep modularity and provide them with data, the same design is reflected in ViewModels.
The MainViewModel is set up like this.
public class MainViewModel : ViewModelBase
{
private readonly ChildViewModelA _childViewModelA = new ChildViewModelA();
private readonly ChildViewModelB _childViewModelB = new ChildViewModelB();
public ChildViewModelA ChildViewModelA { get { return this._childViewModelA; } }
public ChildViewModelB ChildViewModelB { get { return this._childViewModelB; } }
}
The MainWindow instantiates the MainViewModel and sets DataContext of the children. Child controls are bound to data properties.
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel = new MainViewModel();
public MainWindow()
{
InitializeComponent();
}
public MainViewModel ViewModel { get { return this._viewModel; } }
}
<Window x:Class="WpfApplication3.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfApplication3.View.Controls"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TabControl>
<TabItem Header="Tab 1">
<controls:ChildViewA DataContext="{Binding ViewModel.ChildViewModelA}"/>
</TabItem>
<TabItem Header="Tab 2">
<controls:ChildViewB DataContext="{Binding ViewModel.ChildViewModelB}"/>
</TabItem>
</TabControl>
</Grid>
</Window>
To prevent every ViewModel from retrieving the same data from the database, I want to load data in the MainViewModel and provide the children. However, there are multiple ways of doing this.
Example 1: Using a setter method on the children
public class MainViewModel : ViewModelBase
{
...
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
public MainViewModel()
{
this.CurrentPerson = _fakeDataManager.GetNextPerson();
}
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
this.ChildViewModelA.SetPerson(this.CurrentPerson);
this.ChildViewModelB.SetPerson(this.CurrentPerson);
}
}
public class ChildViewModelA : ViewModelBase
{
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
Easy to implement, however quickly get's hard to remain. Not a lot of code reuse. No loose coupling. Should not use this.
Example 2: Putting data in a container
public class MainViewDataContainer : INotifyPropertyChanged
{
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
public class MainViewModel : ViewModelBase
{
...
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
private readonly MainViewDataContainer _dataContainer = new MainViewDataContainer();
public MainViewModel()
{
this._childViewModelA = new ChildViewModelA(_dataContainer);
this._childViewModelB = new ChildViewModelB(_dataContainer);
this._dataContainer.CurrentPerson = _fakeDataManager.GetNextPerson();
}
public class ChildViewModelA : ViewModelBase
{
private readonly MainViewDataContainer _dataContainer;
public ChildViewModelA(MainViewDataContainer dataContainer)
{
this._dataContainer = dataContainer;
}
public MainViewDataContainer DataContainer { get { return this._dataContainer; } }
}
Easier to maintain, more code reuse. Looser coupling.
Example 3: Storing in MainViewModel Properties and provide to children through an interface
public interface IMainDataProvider
{
Person CurrentPerson { get; }
}
public class MainViewModel : ViewModelBase, IMainDataProvider
{
private readonly ChildViewModelA _childViewModelA;
private readonly ChildViewModelB _childViewModelB;
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
public MainViewModel()
{
this._childViewModelA = new ChildViewModelA(this);
this._childViewModelB = new ChildViewModelB(this);
this.CurrentPerson = _fakeDataManager.GetNextPerson();
}
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
public class ChildViewModelA : ViewModelBase
{
private readonly IMainDataProvider _dataProvider;
public ChildViewModelA(IMainDataProvider dataProvider)
{
this._dataProvider = dataProvider;
}
public IMainDataProvider DataProvider { get { return this._dataProvider; } }
}
Yet again easier to maintain, more code reuse. Loose coupling.
Example 3 seems to be the best solution, however is this true? How do you think about this? Are there better ways to solve this issue?
If CurrentPerson is used by both ChildViewModels and MainView is the one who can change the CurrentPerson. How I will proceed to solve this problem is:
Define one base VM ChildViewModelBase : ViewModelBase with CurrentPerson property (with INotifyPropertyChanged and all). All of my ChildViewModels will extend this VM.
Use Event Aggregator http://msdn.microsoft.com/en-us/library/ff921122.aspx. Using EventAggregator you can subscribe to CurrentPersonChanged event in your ChildViewModelBase.
From you MainViewModel once the CurrentPerson is loaded from db, raise CurrentPersonChanged event with new Person as event arguement.
In event handler in the ChildViewModelBase, set CurrentPerson that you get as event argument.
In this way it would not matter to your ChildViewModels who is loading the Person from DB. Today it is MainView, tommorow it can be some other View also. You will just need to raise the above event with the person object as argument and Child Views will get it.
I have a simple scenario with a View, a ViewModel and a custom type class.
The model class is something like:
public class Person : Validation.DataError, INotifyPropertyChanged
{
#region INotifyProperty
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public global::System.String name
{
get
{
return _name;
}
set
{
_name= value;
RaisePropertyChanged("name");
}
}
private global::System.String _name;
}
In the ViewModel I have a Person property:
private Model.Person person;
public Model.Person Person
{
get
{
return person;
}
set
{
this.person= value;
this.RaisePropertyChanged("Person");
this.SavePersonCommand.OnCanExecuteChanged();
}
}
In my View I have a textbox that is bound to Person.name
So the ViewModel is not executing the set method because the Person object is still the same... it is executing the set method in the Model property.
I want to let the user change the person name and make a call to another method (search through a web service and other stuff...) and I think this functionality should be in the ViewModel.
I'm using Messenger from MVVM Light toolkit to communicate between different viewmodels and between views and viewmodels.
Now I don't know if I should use a mediator too for this or if I should know another way to solve this.
Just subscribe to the PropertyChanged event of the Person in your ViewModel and check for the "Name" property, or whatever you wanna do:
Person.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Person_PropertyChanged);
void Person_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == "Name")
{
//do something
}
}