I'm new to WPF and MVVM and I'm developing a test WPF application following the MVVM design pattern. My database has 2 entities, Cards and Departments. Any card can have only 1 department, so it's a one-to-many relationship.
I've created the following ViewModel in order to bind to the view:
public class CardViewModel : INotifyPropertyChanged
{
public CardViewModel(Card card)
{
this.Card = card;
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = ".\\SQLExpress";
builder.InitialCatalog = "TESTDB";
builder.IntegratedSecurity = true;
SybaseDatabaseContext myDB = new SybaseDatabaseContext(builder.ConnectionString);
var query = from d in myDB.Departments
select d;
this.Departments = new ObservableCollection<Department>(query);
}
private Card _Card;
private ObservableCollection<Department> _Departments;
public Card Card
{
get { return _Card; }
set
{
if (value != this._Card)
{
this._Card = value;
SendPropertyChanged("Card");
}
}
}
public ObservableCollection<Department> Departments
{
get { return _Departments; }
set
{
this._Departments = value;
SendPropertyChanged("Departments");
}
}
#region INPC
// Logic for INotify interfaces that nootify WPF when change happens
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanged(String propertyName)
{
if ((this.PropertyChanged != null))
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
The CardForms' datacontext is currently being set to an instance of the CardViewModel in the code where the CardForm is being instantiated, but I'm going to create a IoC container or dependency injections down the line.
Everything binds correctly except for the ComboBox that should contain all departments and that has the current department in the Card instance selected (card.Department). Here's the XAML for the ComboBox:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="350,64,0,0"
Name="comboBoxDepartment" VerticalAlignment="Top" Width="120"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Departments}"
DisplayMemberPath="DepartmentName"
SelectedItem="{Binding Path=Card.Department, Mode=TwoWay}" />
The departments are displayed in the combobox, but the current department of the card isn't and if I try to change it I get and error saying "Cannot add an entity with a key that is already in use".
So, my question is, how do I bind this combobox correctly to my ViewModel?
P.S. I know populating the ObservableCollection<Department> in the ViewModel is probably not the right way to do it, but I could not think of a better way at the time. If you have any suggestions for this also, please let me know.
Additionally, this is the Card model:
[Table(Name = "Card")]
public class Card : INotifyPropertyChanged, INotifyPropertyChanging
{
private string _CardID;
private string _Holder;
private Int16? _DepartmentNo;
[Column(UpdateCheck = UpdateCheck.WhenChanged)]
public string CardID
{
get
{
return this._CardID;
}
set
{
if (value != this._CardID)
{
SendPropertyChanging();
this._CardID = value;
SendPropertyChanged("CardID");
}
}
}
[Column(UpdateCheck = UpdateCheck.WhenChanged)]
public string Holder
{
get
{
return this._Holder;
}
set
{
if (value != this._Holder)
{
SendPropertyChanging();
this._Holder = value;
SendPropertyChanged("Holder");
}
}
}
[Column(CanBeNull = true, UpdateCheck = UpdateCheck.WhenChanged)]
public Int16? DepartmentNo
{
get
{
return this._DepartmentNo;
}
set
{
if (value != this._DepartmentNo)
{
SendPropertyChanging();
this._DepartmentNo = value;
SendPropertyChanged("DepartmentNo");
}
}
}
private EntityRef<Department> department;
[Association(Storage = "department", ThisKey = "DepartmentNo", OtherKey = "DepartmentNo", IsForeignKey = true)]
public Department Department
{
get
{
return this.department.Entity;
}
set
{
Department previousValue = this.department.Entity;
if (((previousValue != value)
|| (this.department.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this.department.Entity = null;
previousValue.Cards.Remove(this);
}
this.department.Entity = value;
if ((value != null))
{
value.Cards.Add(this);
this._DepartmentNo = value.DepartmentNo;
}
else
{
this._DepartmentNo = default(Nullable<short>);
}
this.SendPropertyChanged("Department");
}
}
}
I edited the constructor in the CardViewModel to take the DataContext as a parameter and that did it. This is the new CardViewModel constructor:
public CardViewModel(Card card, SybaseDatabaseContext myDB)
{
this.Card = card;
var query = from d in myDB.Departments
select d;
this.Departments = new ObservableCollection<Department>(query);
}
Had to do a bit of research on this myself. Thought I would contribute with a self answered question, but found this open current question...
The ComboBox is designed to be a kind of textbox that restricts it's possible values to the contents of a given list. The list is provided by the ItemsSource attribute. The current value of the ComboBox is the SelectedValue property. Typically these attributes are bound to relevant properties of a corresponding ViewModel.
The following example shows wired ComboBox together with a TextBox control used to redundantly view the current value of the ComboBox by sharing a view model property. (It is interesting to note that when TextBox changes the shared property to a value outside the scope of the ComboBox's list of values, the ComboBox displays nothing.)
Note: the following WPF/C# example does does use code-behind and so presents the ViewModel as merely the datacontext of the view and not a partial class of it, a current implementation constraint when using WPF with F#.
WPF XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<m:MainWindowVM />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SelectedString}" />
<ComboBox ItemsSource="{Binding MyList}" SelectedValue="{Binding SelectedString}" />
</StackPanel>
</Window>
C# ViewModel
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication1
{
public class MainWindowVM : INotifyPropertyChanged
{
string selectedString;
void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string SelectedString
{
get { return selectedString; }
set
{
selectedString = value;
NotifyPropertyChanged("SelectedString");
}
}
public List<string> MyList
{
get { return new List<string> { "The", "Quick", "Brown", "Fox" }; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
By default, ToString() is used to interpret the objects in the list. However, ComboBox offers DisplayMemberPath and SelectedValuePath attributes for specifying paths to specific object properties for corresponding displayed and stored values. These paths are relative to the list object element so a path of "Name" refers to Name on a list object item.
The "Remarks" section of this MSDN link explains the interpretations of the IsEditable and IsReadOnly ComboBox properties.
Related
I have two comboboxes. One is grade category (e.g. elementary school, middle school, high school), and another for year. I want to make sure that the year combobox only shows the relevant ones, so that there there will be none of stuffs like 4th year of middle school. This is how I implemented my combobox.
Model
public class ComboBoxLogic {
private List<string> _Grade_category;
private List<string> _Year;
// There is no set property because all sets are done within the class.
public IList<string> public IList<string> Grade_category
{
get
{
return _Grade_category;
}
}
public IList<string> Year
{
get
{
return _Year;
}
}
public void Grade_category_selected(string s)
{
string temp;
_Grade.Clear();
if (s.Equals("Elementary"))
{
for (int i = 1; i <= 5; i++)
{
temp = i.ToString();
_Grade.Add(temp);
}
return;
}
else if (s.Equals("Middle"))
{
_Grade.Add("1");
_Grade.Add("2");
_Grade.Add("3");
return;
}
else if (s.Equals("High"))
{
_Grade.Add("1");
_Grade.Add("2");
_Grade.Add("3");
_Grade.Add("4");
return;
}
}
public ComboBoxLogic() {
_Grade_category = new List<string>() {"Elementary", "Middle", "High"}
_Year = new List<string>();
}
}
XAML
<Combobox
Name="Grade_category"
ItemsSource="{Bind Path=Grade_category}"
SelectedItem="{Bind Path=sGrade_category}" />
<Combobox
Name="Year"
ItemsSource="{Bind Path=Year}"
SelectedItem="{Bind Path=sYear}" />
View Model
class ComboBoxViewModel {
private ComboBoxLogic cbl;
public ComboBoxViewModel() {
cbl = new ComboBoxLogic();
}
public IList<string> Grade_category {
get { return cbl.Grade_category; }
}
public string Grade_category_selected {
set {cbl.Grade_category_selected(value);}
}
public IList<string> Year {
get { return cbl.Year; }
}
public string Year_selected {
set { }
}
}
There are 2 problems involved in this.
When I try this, it only works in the first time. From second selection of grade, year doesn't get updated properly. I'm thinking it's because I did not notify propertychanged for Year when grade_category changed. But, how do I do that?
In my project, I will have multiple of these comboboxes where one combobox depends on another. And, all implementation of dependency will be written in ComboboxLogic class. Is there a way to make this shorter instead of having to write every properties for ItemsSource and SelectedItem for every combobox?
"When I try this, it only works in the first time. From second
selection of grade, year doesn't get updated properly. I'm thinking
it's because I did not notify propertychanged for Year when
grade_category changed. But, how do I do that?"
Wrong, it's not about property changes. To propagate collection changes to the view the collection must implement INotifyCollectionChanged like ObservableCollection does. Replacing Years:IList with Years:ObservableCollection would do it.
"In my project, I will have multiple of these comboboxes where one
combobox depends on another. And, all implementation of dependency
will be written in ComboboxLogic class. Is there a way to make this
shorter instead of having to write every properties for ItemsSource
and SelectedItem for every combobox?"
In your specific case the trick is create a data model, that reflects the subject. All displayed data relates to a single object instance - the selected object (under different circumstances the solution would be to filter an ICollectionView).
A grade has a name and lasts a specific number of years.
The Grade class can look as follows:
Grade.cs
public class Grade : INotifyPropertyChanged
{
public string Name { get; set; }
public IEnumerable<int> Years { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Grade> Grades { get; set; }
private Grade selectedGrade;
public Grade SelectedGrade
{
get => this.selectedGrade;
set
{
this.selectedGrade = value;
OnPropertyChanged();
}
}
public ViewModel()
{
this.Grades = new ObservableCollection<Grade>
{
new Grade() {Name = "Elementary", Years = Enumerable.Range(1, 4)},
new Grade() {Name = "Middle", Years = Enumerable.Range(1, 3)},
new Grade() {Name = "High", Years = Enumerable.Range(1, 4)},
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DatContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Grades}"
SelectedItem="{Binding SelectedGrade}"
DisplayMemberPath="Name" />
<ComboBox ItemsSource="{Binding SelectedGrade.Years}" />
</StackPanel>
</Window>
I have a gridview shown as below in XAML
<ListView x:Name="listTasks">
<ListView.View>
<GridView x:Name="gridTasks">
<GridViewColumn Header="ID" HeaderStringFormat="Lowercase" Width ="26" DisplayMemberBinding="{Binding id}"/>
<GridViewColumn Header="Something" Width="113" DisplayMemberBinding="{Binding something}"/>
<GridViewColumn Header="State" Width="179" DisplayMemberBinding="{Binding currentState}"/>
</GridView>
</ListView.View>
</ListView>
and i have a button which adds to this gridview using the below
m.myList.Add(new mylistview.myitems
{
id = m.id,
something= m.something,
currentState = m.currentState,
});
This button works perfectly by adding the row into the gridview. However I would like to modify theCurrentState using a method that is running. How would I locate for example, ID = "8" and then modify theCurrentState for that row?
UPDATED CODE SHOWN
I've now replaced my list<Task> with ObservableCollection and managed to get it to add to my listview when I click onto my button. However, I am struggling to implement the iNotifyPropertyChanged into my code and getting it to work correctly... Below is my listview class
public class mylistview : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _currentState;
public string currentState
{
get { return _currentState; }
set
{
_currentState = value;
OnPropertyChanged();
}
}
public ObservableCollection<myitems> _myList = new ObservableCollection<myitems>();
public ObservableCollection<myitems> myList
{
get { return _myList; }
}
private static int _id = 0;
public class myitems
{
public int id { get; set; }
public string something{ get; set; }
public string currentState { get; set; }
}
public int id
{
get { return _id; }
set { _id = value; }
}
}
So I see you're using data bindings already, that's good. But your question makes me think you haven't quite grasped everything it can do for you yet.
My recommendation would be to forget about adding items directly to listOfTasks.Items. Instead you should make an ObservableCollection to hold that list and bind the listOfTasks to it. Like so:
ObservableCollection tasks = new ObservableCollection<mylistview.myitems>();
ListOfTasks.ItemsSource = tasks;
With that binding in place you should be able to simply add new items to the tasks list when they click your button:
tasks.Add(new mylistview.myitems
{
id = theId,
something= something,
currentState = theCurrentState,
});
and it should automatically update the GUI.
The last step is to make sure that the class mylistview.myitems implements INotifyPropertyChanged. This is easier than it sounds; you just need to have it trigger an event any time the property is set. Something like so:
public class exampleProperties: INotifyPropertyChanged
{
//this is the event you have to emit
public event PropertyChangedEventHandler PropertyChanged;
//This is a convenience function to trigger the event.
//The CallerMemberName part will automatically figure out
//the name of the property you called from if propertyName == ""
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
//Any time this property is set it will trigger the event
private string _currentState = "";
public string currentState
{
get { return _currentState; }
set
{
if (_currentState != value)
{
_currentState = value;
OnPropertyChanged();
}
}
}
}
Now that the gridview is bound to an ObservableCollection and the items held in that collection can notify interested GUI controls that their properties have changed, you should simply be able to update the GUI simply by changing the appropriate item in the collection.
And here's an example of a form that uses the whole technique: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).asp
edit
I forgot that you specifically need to bind to the ItemSource property of the ListView. The way I have done it in the past is to set ItemsSource={binding} in the ListView's xaml and then assign an ObservableCollection to ListView.DataContext. However I have found an easier way and updated the original post with it. Here's a reference: http://www.wpf-tutorial.com/listview-control/listview-with-gridview/
Edit 2
Aha, you're adding the iPropertyChangedNotify to the wrong thing. It goes on the myitems class like so:
public class myitems : iNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string something{ get; set; }
public string currentState { get; set; }
}
I leave updating the current state and something properties as an excersize. They also need to trigger the OnPropertyChanged event when their value is set.
Maybe with
listOfTasks.Items.Cast<ListViewItem>().First(item => item.ID == "8").theCurrentState = newState;
//I'm not sure about the Cast stuff, because I don't know what types the ListView uses for its items
Of course you could iterate through the items with a loop and check manually for the ID as well.
This is my first post in this forum, though I am a long-time lurker. I have started learning WPF for about a couple of months now, and I am trying to create an application just for training purposes.
I have a backend database which I have added to my application using EF6 ORM. In my application, I have a `ComboBox which needs to be populated by a column in a table of the database. That I can do using binding to a list.
The part I am having trouble with is the DataGrid. The columns of the DataGrid needs to be populated according to the Item chosen in the ComboBox.
My database:
As you can see, the school has several departments, and each of those department has a HOD and a student strength.
My application:
The ComboBox will be populated with school names. The DataGrid will be populated once the schoolname is selected. The DataGrid will have each row for each department available for the school. So I need to bind the corresponding columns with the departments of the corresponding schools. That much I get. However, then I want to save the user-entered comments in the Feedback TextBox.
I cannot understand how to create a class so that I can bind the DataGrid to the object of it. Is it possible to bind the DataGrid to an object and then bind the columns separately to another object?
EDIT
Apart from the entities created from the database, I have two classes:
class Feedback : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _school;
public string School
{
get
{
return _school;
}
set
{
_school = value;
OnPropertyChanged("School");
}
}
private ObservableCollection<FeedbackLine> _feedbackLines;
public ObservableCollection<FeedbackLine> FeedbackLines
{
get
{
return _feedbackLines;
}
set
{
_feedbackLines = value;
OnPropertyChanged("FeedbackLines");
}
}
public Feedback(string school)
{
//Insert some Linq Query to populate the FeedbackLines
//something like
//var FeedbackLines = Context.Schools.Where(c => c.SchoolName == school)
// .Select(c => new {Department = c.AvailableDepts.Dept, etc etc}.ToList();
//but then what?
}
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
}
This is supposed to be bound to the datagrid. And the FeedbackLine is:
public class FeedbackLine: INotifyPropertyChanged
{
private string _dept;
public string Department
{
get { return _dept; }
set { _dept = value;
OnPropertyChanged("Department");
}
}
private string _HOD;
public string HOD
{
get { return _HOD; }
set { _HOD = value;
OnPropertyChanged("HOD");
}
}
private int _strength;
public int Strength
{
get { return _strength; }
set { _strength = value;
OnPropertyChanged("Strength");
}
}
private bool _isSelected;
public bool Selected
{
get { return _isSelected; }
set { _isSelected = value;
OnPropertyChanged("Selected");
}
}
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value;
OnPropertyChanged("Comment");
}
}
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I haven't had much headway with the ViewModel. Problem is, I am not very good with LINQ. And there are too many classes and objects and I have no idea which one to bind with which. The only vague idea that I can get is that I have to use LINQ to query the database using the selected School, and then populate the FeedbackLines using that.
Edit 2:
For anyone who's interested, here's my model diagram in WPF:
Model Diagram
Edit 3:
I think I am confused about ViewModel. The data that will be displayed on the screen is not necessarily the data to be saved. For example, I don't want to save the unselected rows. My Feedback class tries to display the data as well as save it. And therein lies the problem. Can't a DataGrid be bound to an object, while its columns be bound to other objects? For example, if I choose to use a Combobox for Department. Then I need to use ItemsSource for displaying items, but need to save the SelectedItem only. I can't find a way to separate these two concerns.
I would change your Feedback constructor
public Feedback(string school, List<FeedbackLine> feedbackLines)
{
School = school;
FeedbackLines = new ObservableColleytion<FeedbackLine>(feedbackLines);
}
It's a better architecture if your data viewmodel does not have a connection to the database. You can put your select in a seperate class.
If you need help with your LINQ statement I can help you.
In your Feedback constructor you wrote
//but then what?
When you got your data you can create instances of FeedbackLines to add them in the new constructor I showed above.
When you did this your viewmodel (which is DataContext of your view) needs an
public void ObservableCollection<Feedback> Feedbacks
with INotifyPropertyChanged like you did it in the other viewmodels.
In your xaml you have your ComboBox with the schools. Give that combobox a name, e.g. SchoolsComboBox.
In your DataGrid write this line
Source={Binding ElementName=SchoolsComboBox, Path=SelectedItem.FeedbackLines}
/edit for adding LINQ
You created an anonymous type. Just create a FeedbackLine instead and you're fine.
var feedbackLines = Context.Schools.Where(c => c.SchoolName == school)
.Select(c => new FeedbackLine
{
Department = c.AvailableDepts.Dept,
HOD = c.AvailableDepts.HeadOfDept,
Strength = c.AvailableDepts.StudentStrength}
.ToList()
U can make Something like this. Im sure it can be writen better but it works.
In your ViewModel make 3 Properties that implements INotifyPropertyChanged
One for your collection that will you bind to your ComboBox(make it ObservableCollection), One for SelectedItem from your ComboBox( you bind it to SelectedItem in comboBox) and another ObservableCollection that you will Bind to DataGrid)
For example you have in XAML:
<Grid>
<ComboBox ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="200"
Margin="20"
IsSynchronizedWithCurrentItem="True" />
<DataGrid ItemsSource="{Binding SelectedOne}"
HorizontalAlignment="Right "
VerticalAlignment="Center"
Width="300"
IsSynchronizedWithCurrentItem="True">
</DataGrid>
and in your ViewModel you can have something like this.
public ObservableCollection<Product> Products
{
get { return _products; }
set
{
if (value != _products)
{
_products = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<Product> _selectedOne;
public ObservableCollection<Product> SelectedOne
{
get { return _selectedOne; }
set {
_selectedOne = value;
OnPropertyChanged();
}
}
public int SelectedProductId
{
get { return _selectedProductId; }
set
{
if (value != _selectedProductId)
{
_selectedProductId = value;
OnPropertyChanged();
}
}
}
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
if (value ! = _selectedProduct)
{
_selectedProduct = value;
// clear your list of selected objects and then add just selected one
// or you dont clear it, and items will be added in DataGrid when selected in ComboBox
SelectedOne.Clear();
SelectedOne.Add(_selectedProduct);
OnPropertyChanged();
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
Code to Populate Products, DataGrid will be populated by selecting Item from ComboBox.
u can go in ViewModel constructor and make something like this.
public MainWindowViewModel()
{
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
using (YourDbContext context = new YourDbContext ())
{
var productList = new ObservableCollection<Product>(context.Products);
productList.ToList()
Products = productsList;
}
}
}
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'm new to C# and I'm trying to create a code using MVVM pattern, but I don't know how to populate a combobox using that pattern. Please Give me help to create the ViewModel and the binding to the xaml.
Code Model:
public int Cd_Raca
{
get;
set
{
if(Cd_Raca != value)
{
Cd_Raca = value;
RaisePropertyChanged("Cd_Raca");
}
}
}
public string Nm_Raca
{
get;
set
{
if(Nm_Raca != value)
{
Nm_Raca = value;
RaisePropertyChanged("Nm_Raca");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml:
<ComboBox x:Name="dsCmbRaca" HorizontalAlignment="Left" Margin="438,4,0,0"
VerticalAlignment="Top" Width="94" Height="19"/>
Use the ItemsSource Property and set it to an enumeration of objects. With DisplayMemberPath you can set it to a property of a single object of your list if the list is not just a list of strings.
I.e. in my sample the object of the list has a Description property for display and a Value property for the selected value.
All bindings in the sample need to be a property in your ViewModel (=DataContext).
<ComboBox DisplayMemberPath="Description" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="120"
ItemsSource="{Binding myList}"
SelectedValue="{Binding mySelectedValue}" SelectedValuePath="Value" />
Edit:
The List property could look like this:
public IList<MyObject> myList { get { return new List<MyObject>();} }
The Object could look like this for example:
public class MyObject
{
public string Description { get; }
public enum Value { get;}
}
The Object is optional. You could just pass a list of strings.
Disclaimer: I hacked this in notepad. I hope it compiles.
UPDATE
Looking at your code at least from what you post your properties are not implemented correctly. You need a backing field if you code it like you have:
private int _cd_Raca;
private string _nm_Raca;
public int Cd_Raca
{
get{ return _cd_Raca;}
set
{
if(_cd_Raca != value)
{
_cd_Raca = value;
RaisePropertyChanged("Cd_Raca");
}
}
}
public string Nm_Raca
{
get{return _nm_Raca;}
set
{
if(_nm_Raca != value)
{
_nm_Raca = value;
RaisePropertyChanged("Nm_Raca");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Reading your comment to my first answer seems you might have a specific use case. So if this update does not help maybe you can add some more information to your question.