I'm starting Caliburn Micro development and I have thought of an architecture where a viewmodel has properties, injected by MEF, which are other viewmodels. That way I can use contentcontrols in the view to position them the way I want.
public class ContactsProfileViewModel : Conductor<IContentItem>, IContactsModuleViewModel, IModule, IPartImportsSatisfiedNotification
{
private string name;
private string nameCaption;
private ISingleLineTextContentItem firstName;
private ISingleLineTextContentItem lastName;
public ContactsProfileViewModel()
{
this.DisplayName = "Contact Tab";
}
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
this.NotifyOfPropertyChange(() => Name);
}
}
public string NameCaption
{
get
{
return this.nameCaption;
}
set
{
this.nameCaption = value;
this.NotifyOfPropertyChange(() => NameCaption);
}
}
[Import(typeof(ISingleLineTextContentItem))]
public ISingleLineTextContentItem FirstName
{
get { return this.firstName; }
set
{
this.firstName = value;
this.NotifyOfPropertyChange(() => FirstName);
}
}
[Import(typeof(ISingleLineTextContentItem))]
public ISingleLineTextContentItem LastName
{
get { return this.lastName; }
set
{
this.lastName = value;
this.NotifyOfPropertyChange(() => LastName);
}
}
The viewmodel of SingleLineTextContentItem looks like this:
[Export(typeof(ISingleLineTextContentItem))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class SingleLineTextContentItemViewModel : PropertyChangedBase, ISingleLineTextContentItem
{
private string textBoxText;
private string caption;
public string TextBoxText
{
get { return textBoxText; }
set
{
textBoxText = value;
this.NotifyOfPropertyChange(() => TextBoxText);
}
}
public string Caption
{
get { return caption; }
set
{
this.caption = value;
this.NotifyOfPropertyChange(() => Caption);
}
}
}
Now, I need a way to bind the NameCaption property to the Caption property in a two-way manner. Is that possible? I'm I on the right track with this or is there a better way to do this?
Thanks,
Roland
What I do is instead of having a backing field just route to the other view model
public string NameCaption
{
get
{
return FirstName.Caption;
}
set
{
FirstName.Caption = value;
this.NotifyOfPropertyChange(() => NameCaption);
}
}
However if the Caption property on the ISingleLineTextContentItem can get set independently then you need to register changes on the event and have the view model listen to changes. So instead you need somthing along the lines of:
public string NameCaption
{
get
{
return FirstName == null ? string.Empty : FirstName.Caption;
}
set
{
if(FirstName != null)
FirstName.Caption = value;
}
}
[Import(typeof(ISingleLineTextContentItem))]
public ISingleLineTextContentItem FirstName
{
get { return this.firstName; }
set
{
if(this.FirstName != null)
this.FirstName.PropertyChanged -= FirstNameChanged;
this.firstName = value;
if(this.FirstName != null)
this.FirstName.PropertyChanged += FirstNameChanged;
this.NotifyOfPropertyChange(() => FirstName);
this.NotifyOfPropertyChange(() => NameCaption);
}
}
private void FirstNameChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertName == "Caption")
this.NotifyOfPropertyChange(() => NameCaption);
}
Since either the Caption property or the FirstName property can change then we need to raise the event in the FirstName property and in the handler.
Related
I Have Two ComboBoxes for example Country and State where State ComboBox's ItemSource depends on Selected Country of First ComboBox.
Country and State Properties are defined in separate CountryModel(CountryID,CountryName) and StateModel(CountryID,StateID,StateName).
Now there is the Model "UserModel" is like :
public class UserModel:ViewModelBase
{
private string _userName;
public string UserName
{
get { return _userName; }
set { _userName = value; OnPropertyChanged("UserName"); }
}
private long _stateID;
public long StateID
{
get { return _stateID; }
set { _stateID = value; OnPropertyChanged("StateID"); }
}
private int _countryID;
public int CountryID
{
get { return _countryID; }
set { _countryID = value; OnPropertyChanged("CountryID"); }
}
}
It has a Service "UserModelService" for populating :
public class UserModelService
{
public UserModelService()
{
}
public ObservableCollection<UserModel> GetUserList()
{...}
public ObservableCollection<CountryModel> GetCountryList()
{...}
public ObservableCollection<StateModel> GetStateList()
{...}
}
Now The UserViewModel is Like:
public class UserViewModel:ViewModelBase,IPageViewModel
{
UserModelService modelService;
public UserViewModel()
{
modelService = new UserModelService();
GetData();
}
private void GetData()
{
UserList = modelService.GetUserList();
CountryList = modelService.GetCountryList();
StateList = modelService.GetStateList();
}
private UserModel _currentUser=new UserModel();
public UserModel CurrentUser
{
get { return _currentUser; }
set
{
if (value == _currentUser) return;
_currentUser = value; OnPropertyChanged("CurrentUser");
}
}
private ObservableCollection<UserModel> _userList;
public ObservableCollection<UserModel> UserList
{
get { return _userList; }
set
{
if (value == _userList) return;
_userList = value;
OnPropertyChanged("UserList");
}
}
private ObservableCollection<CountryModel> _countryList;
public ObservableCollection<CountryModel> CountryList
{
get { return _countryList; }
set { _countryList = value; OnPropertyChanged("CountryList"); }
}
private ObservableCollection<StateModel> _stateList;
public ObservableCollection<StateModel> StateList
{
get { return _stateList; }
set
{
_stateList = value;
OnPropertyChanged("StateList");
}
}
}
Now the Country and State ComboBox's ItemSource is binded to UserViewModels "CountryList" and "StateList" respectively. In this scenario how to I repopulate StateList OnPropertyChange of CurrentUser.CountryID?
EDIT: I Solved it by introducing separate property for Selected Country and OnPropertyChanged CurrentUser.CountryID=selectedCountry. Thanks
Handle the PropertyChanged event for the current user:
public UserModel CurrentUser
{
get { return _currentUser; }
set
{
if (value == _currentUser)
return;
if (_currentUser != null)
_currentUser.PropertyChanged -= OnCurrentUserPropertyChanged;
_currentUser = value;
if (_currentUser != null)
_currentUser.PropertyChanged += OnCurrentUserPropertyChanged;
OnPropertyChanged("CurrentUser");
}
}
private void OnCurrentUserPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(UserModel.CountryID))
{
UserModel userModel = (UserModel)sender;
var states = ...; //get states by userModel.CountryID
StateList = states;
}
}
namespace colourchanges
{
public class Group
{
public string Name { get; set; }
//its a class for adding parent list using group class
}
public class EmployeeTree : INotifyPropertyChanged
{
public EmployeeTree()
{
this.GroupStaff = new List<Group>();
GroupStaff.Add(new Group { Name = "Designers" });
GroupStaff.Add(new Group { Name = "Developers" });
GroupStaff.Add(new Group { Name = "Managers" });
//here we are declaring list for adding parent list
}
private List<Group> _GroupStaff;
public List<Group> GroupStaff
{
get { return _GroupStaff; }
set
{
_GroupStaff = value;
RaisePropertyChanged("GroupStaff");
}
}
//creates a list for parentlist
private Group _selectedGroupStaff;
public Group selectedGroupStaff
{
get { return _selectedGroupStaff; }
set
{
_selectedGroupStaff = value;
if (selectedGroupStaff.Name == "Designers")
{
City = "Chennai";
Country = "India";
Email = "Designer#gmail.com";
MobileNo = 9094117917;
Address = "Annanagar";
}
else if (selectedGroupStaff.Name == "Developers")
{
City = "Trichy";
Country = "India";
Email = "Developer#gmail.com";
MobileNo = 9094667878;
Address = "Koyambedu";
}
else if (selectedGroupStaff.Name == "Managers")
{
City = "Salem";
Country = "India";
Email = "Manager#gmail.com";
MobileNo = 9094154678;
Address = "Arumbakkam";
}
RaisePropertyChanged("selectedGroupStaff");
}
}//for selecting parent list in order to bind to textbox
private string _City;
private string _Country;
private string _Email;
private long _MobileNo;
private string _Address;
//properties of parent list to bind to textbox
public string City
{
get { return _City; }
set
{
_City = value;
RaisePropertyChanged("City");
}
}
public string Country
{
get { return _Country; }
set
{
_Country = value;
RaisePropertyChanged("Country");
}
}
public string Email
{
get { return _Email; }
set
{
_Email = value;
RaisePropertyChanged("Email");
}
}
public long MobileNo
{
get { return _MobileNo; }
set
{
_MobileNo = value;
RaisePropertyChanged("MobileNo");
}
}
public string Address
{
get { return _Address; }
set
{
_Address = value;
RaisePropertyChanged("Address");
}
}
///raise property changed event handler code
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
//how to add sub list for designers developers and managers in the constructor
Let the following be your Model class
public class Group
{
private string _City;
private string _Country;
private string _Email;
private long _MobileNo;
private string _Address;
public Group()
{
Items = new ObservableCollection<Group>();
}
public ObservableCollection<Group> Items { get; set; }
}
and at the ViewModel's constructor, you can add the Items.
public EmployeeTree()
{
this.GroupStaff = new List<Group>();
Group rootGroup = new Group(){Name ="Manager"};
Group childGroup = new Group(){Name = "Developer"};
rootGroup.Items.Add(childGroup);
this.GroupStaff.Add(rootGroup);
}
This is for Hierarchical structure. Hope you are looking for this.
And your XAML should be like this
<TreeView Name="GroupTreeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
I have a puzzled problem of databinding in WPF.
There is a listbox in XAML which it has linked with ItemSource,
but when it runs, it shows the lists of class names.
so I have applied to DisplayMemberPath, but it doesn't helpful.
and also I'm wondering how I can access inside class from generic class.
Thanks.
result
puzzled.Member
puzzled.Member
puzzled.Member
puzzled.Member
<DockPanel>
<ListBox Name="lbxMbrList" DockPanel.Dock="Left" Width="200" Padding="10"></ListBox>
<ContentControl />
</DockPanel>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
members.Add(new Member("superman", "123-1234567", "address1"));
members.Add(new Member("batman", "111-111111", "address2"));
members.Add(new Member("goodman", "222-222222", "address3"));
members.Add(new Member("badman", "333-333333", "address4"));
lbxMbrList.ItemsSource = members;
lbxMbrList.DisplayMemberPath = members.MemberDetails; //<<it won't helpful
//var i = members.member.Name; //<<how can I access inside class?
//if (i == "superman")
//{
// MessageBox.Show("superman");
//}
}
public class Member
{
private string _name;
private string _phone;
private string _address;
public string Name { get { return _name; } set { _name = value; } }
public string Phone { get { return _phone; } set { _phone = value; } }
public string Address { get { return _address; } set { _address = value; } }
public Member() { }
public Member(string name, string phone, string address)
{
_name = name; _phone = phone; _address = address;
}
public string lbxMember
{
get { return string.Format("{0} - {1}", Name, Phone, Address); }
}
}
class MemberList : IEnumerable<Member>
{
private ObservableCollection<Member> memberList = new ObservableCollection<Member>();
public Member this[int i]
{
get {return memberList[i];}
set {memberList[i] = value;}
}
public void Add(Member member)
{
memberList.Add(member);
}
public void Remove(Member member)
{
memberList.Remove(member);
}
public IEnumerator<Member> GetEnumerator()
{
return memberList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public Member member { get; set; } //<< it think I has misunderstood it
public string MemberDetails
{
get
{ return string.Format("{0} - {1}", member.Name, member.Phone, member.Address); }
}
}
You are assigning the output of your MemberDetails property to the DisplayMemberPath. Instead, you need to assign the name of the property as a string.
lbxMbrList.DisplayMemberPath = "MemberDetails";
For what its worth, this will be easier to work with if you use an ItemTemplate in the ListBox.
[Edit]
Also, as #Blam mentions in his answer, your MemberDetails property is defined in the wrong class, it needs be in the Member class.
lbxMbrList.DisplayMemberPath = "lbxMember";
or
lbxMbrList.DisplayMemberPath = "MemberDetails";
And MemberDetails need to be a property of Member (not MemberList)
I have a project in WPF with the MVVM-Pattern.
The view has a listview of Persons(FirstName, LastName, Town) and next to the list are the details of the person(FirstName,LastName,ZIP) with a button(Save).
If you click to a person on the left side, the person details on the right side will load automatically.
In the ViewModel I have an ObservableCollection of Person-Models. (Person list)
The Person Model includes some propertys like FirstName, LastName, Town, Zip. (Details).
When I change the ZIP, the Town will updated automatically(in the setter switch-case) e.g. ZIP '11111' Town 'Town1' / ZIP '22222' Town 'Town2'.
The Person Model includes also an ICommand "SavePerson" to Save Changes.
Now when i click to an item in the listview the details will load automatically.
When I change the FirstName or LastName and click "Save" the listview will change the First and the LastName of the selected item, that's ok.
Now when I change the ZIP from '12345' into '11111' the town in the listview is still the old one and not 'Town1'.
Have you an idea to fix this problem, without to implement the INotifyPropertyChanged-Interface in the Model?
Some code:
Model:
public class Person
{
private string _firstName = string.Empty;
private string _lastName = string.Empty;
private string _zip = string.Empty;
private string _town = string.Empty;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
public string LastName
{
get { return _lastName; }
set { _lastName= value; }
}
public string ZIP
{
get { return _zip; }
set
{
switch (value)
{
case "11111":
this.Town = "Town1";
break;
case "22222":
this.Ort = "Town2";
break;
default:
break;
}
_zip = value;
}
}
public string Town
{
get { return _town; }
set { _town= value; }
}
public ICommand SaveNameCommand
{
get
{
return new DelegateCommand((param) => this.SaveName(param));
}
}
private void SaveName(object parameter)
{
string[] param = ((string)parameter).Split(new char[] { ':' });
FirstName = param[0];
LastName = param[1];
PLZ = param[2];
}
}
ViewModel:
public class PersonList
{
private readonly ObservableCollection<Person> _persons = new ObservableCollection<Person>();
private Person _currentSelectedPerson = new Person();
public PersonList()
{
this._persons.Add(new Person() { FirstName = "First1", LastName = "Last1", Ort = "Ort1", PLZ = "112" });
this._persons.Add(new Person() { FirstName = "First2", LastName = "Last2", Ort = "Ort2", PLZ = "122" });
this._persons.Add(new Person() { FirstName = "First3", LastName = "Last3", Ort = "Ort3", PLZ = "1132" });
}
public IEnumerable<Person> Persons
{
get { return this._persons; }
}
}
Since your Model should be loosely-coupled from your ViewModel, it makes sense that you might not want to implement INotifyPropertyChanged in your Model since it's most commonly associated with WPF; however, it's a C# interface and can be used in any type of application, so there's no harm implementing it. In fact, I'd suggest it's probably the best way. However, if you really don't want to implement it in your model classes, consider an event-subscriber model, where the Model raises an event when changed, and the ViewModel subscribes to it.
model.ValuesChanged += model_ValuesChanged;
private void model_ValuesChanged(object sender, EventArgs e)
{
RaisePropertyChanged("MyProperty");
}
I have absolutely no idea why you would want to not implement the INotifyPropertyChanged interface on your model class(es). Really, your only other option would be to implement it in your view model and expose all of your model properties there:
public string FirstName
{
get { return _currentSelectedPerson .FirstName; }
set { _currentSelectedPerson .FirstName = value; NotifyPropertyChanged("FirstName"); }
}
public string LastName
{
get { return _currentSelectedPerson .LastName; }
set { _currentSelectedPerson .LastName= value; NotifyPropertyChanged("LastName"); }
}
...
WPF and the INotifyPropertyChanged interface go hand in hand... at some stage, you're going to have to implement it.
The model Should implement INotifyPropertyChanged
chack INotifyPropertyChanged WPF
any other search of INotifyPropertyChanged will do as well
You need notification your View about changed in ViewModel.
For this use INotifyPropertyChanged. Any class that implements this interface,
notifies any listeners when a property has changed.
So you need to modify our Person class a little bit more:
public class Person : INotifyPropertyChanged
{
private string _firstName = string.Empty;
private string _lastName = string.Empty;
private string _zip = string.Empty;
private string _town = string.Empty;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName= value;
RaisePropertyChanged("LastName");
}
}
public string ZIP
{
get { return _zip; }
set
{
switch (value)
{
case "11111":
this.Town = "Town1";
break;
case "22222":
this.Town = "Town2";
break;
default:
break;
}
_zip = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("Town");
}
}
public string Town
{
get { return _town; }
set
{
_town= value;
RaisePropertyChanged("Town");
}
}
public ICommand SaveNameCommand
{
get
{
return new DelegateCommand((param) => this.SaveName(param));
}
}
private void SaveName(object parameter)
{
string[] param = ((string)parameter).Split(new char[] { ':' });
FirstName = param[0];
LastName = param[1];
PLZ = param[2];
}
}
I have a WFP Project and i am using MVVM Pattern.
I have AddressView User control which i used in CustomerView UserControl.
<my:AddressVeiw Width="340" DataContext="AddressViewModel"/>
My AddressVeiw userControl has a AddressViewModel and CustomerView has a CustomerViewModel
Code for CustomerViewModel
public DelegateCommand<object> SaveCommand { get; set; }
private string firstName;
public string FirstName
{
get { return firstName; }
set {
firstName = value;
RaisePropertyChanged("FirstName");
SaveCommand.RaiseCanExecuteChanged();
}
}
private string lastName;
public string LastName
{
get { return lastName; }
set {
lastName = value;
RaisePropertyChanged("LastName");
SaveCommand.RaiseCanExecuteChanged();
}
}
private AddressViewModel addressViewModel;
public AddressViewModel AddressViewModel
{
get { return addressViewModel; }
set { addressViewModel = value; }
}
private string middleName;
public string Middlename
{
get { return middleName; }
set
{
middleName = value;
RaisePropertyChanged("MiddleName");
SaveCommand.RaiseCanExecuteChanged();
}
}
private string fullName;
public string FullName
{
get { return fullName; }
set {
fullName = value;
RaisePropertyChanged("FullName");
}
}
private void InitializeCommands()
{
SaveCommand = new DelegateCommand<object>(OnSaveCommand, CanSaveExcute);
}
private bool CanSaveExcute(object obj)
{
if (string.IsNullOrEmpty(firstName) ||string.IsNullOrEmpty(lastName))
return false;
return true;
}
private void OnSaveCommand(object obj)
{
FullName = FirstName + " " + LastName;
}
}
Code for AddressViewModel
private ObservableCollection<Country> countryList = new ObservableCollection<Country>();
public ObservableCollection<Country> CountryList
{
get { return countryList; }
set { countryList = value; }
}
public DelegateCommand<object> SaveCommand { get; set; }
private void Load()
{
try
{
CountryList = (new CountryRepository().GetAll());
}
catch (Exception ex)
{
OnSetStatusBarText("Error: " + ex.Message.ToString());
}
}
private void OnSetStatusBarText(string message)
{
var evt = eventAgg.GetEvent<StatusBarMessageEvent>();
evt.Publish(message);
}
private void InitializeCommands()
{
SaveCommand = new DelegateCommand<object>(OnSaveCommand, CanSaveExcute);
}
private bool CanSaveExcute(object obj)
{
return true;
}
private void OnSaveCommand(object obj)
{
}
Some how i can hook my AdddressViewModel To my AddressView, Customer Works fine...
What Must be done to resolve this problem?
Thanks
You need to use a binding expression for the DataContext of your AddressView. Instead of this...
<my:AddressVeiw Width="340" DataContext="AddressViewModel"/>
...try this...
<my:AddressVeiw Width="340" DataContext="{Binding AddressViewModel}"/>
You're close, but you need a binding:
<my:AddressVeiw Width="340" DataContext="{Binding AddressViewModel}"/>