I'm building a N tier WPF app. I want zero codebehind.
Let's say I have 3 normalized related tables to record sales transactions.
TRANSACTIONS:
TransactionId,
ItemId,
SupplierId,
Price
SUPPLIERS:
SupplierId,
SupplierName
ITEMS:
ItemId,
ItemName.
For each table I have a Base class that reflects the fields. Then a data layer that populates a collection of base objects as required.
I want to have a Listbox on the page showing a list of all of the transactions, 1 transaction per row, the rows should look something like this...
"Trainers FootLocker €99"
"Trousers TopShop €45"
"Coat TopShop €49"
If I use the
<ListBox
ItemsSource="{Binding Path=Transactions}"
SelectedItem="{Binding CurrentTransaction}"
then I end up with rows of IDs from the Transactions table and not the Name values from the Items and Suppliers tables.
Given that I have collection of transactions filled with only IDs to the other tables, what is the best approach to populating the listbox?
One thing I'm wondering is, should my Transactions Base object contain Item item populated there instead of int ItemId?
Transaction Base Model:
using System;
using System.ComponentModel;
using PFT;
using PFT.Data;
namespace PFT.Base
{
public class Transaction : INotifyPropertyChanged
{
public int Id { get; set; }
private int _itemId;
public int ItemId
{
get { return _itemId; }
set {
_itemId = value;
ItemData id = new ItemData();
this.Item = id.Select(value);
NotifyPropertyChanged("ItemId");
}
}
private Item _item;
public Item Item
{
get { return _item; }
set { _item = value; }
}
private float _price;
public float Price
{
get { return _price; }
set {
_price = value;
NotifyPropertyChanged("Price");
}
}
private DateTime _date;
public DateTime Date
{
get { return _date; }
set {
_date = value;
NotifyPropertyChanged("Date");
}
}
private string _comment;
public string Comment
{
get { return _comment; }
set
{
_comment = value;
NotifyPropertyChanged("Comment");
}
}
private int _traderId;
public int TraderId
{
get { return _traderId; }
set
{
_traderId = value;
NotifyPropertyChanged("TraderId");
}
}
private Trader _trader;
public Trader Trader
{
get { return _trader; }
set { _trader = value;
TraderData t = new TraderData();
this.Trader = t.Select(value);
}
}
private string _insertType;
/// <summary>
/// A - Auto, M - Manual, V - Verified
/// </summary>
public string InsertType
{
get { return _insertType; }
set { _insertType = value;
NotifyPropertyChanged("InsertType");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
//private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ITEMS BASE CLASS
using System.ComponentModel;
namespace PFT.Base
{
public class Item : INotifyPropertyChanged
{
private int _id;
public int Id
{
get { return _id; }
set { _id = value;
NotifyPropertyChanged("Id");
}
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value;
NotifyPropertyChanged("Name");
}
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value;
NotifyPropertyChanged("Description");
}
}
private float _defaultPrice;
public float DefaultPrice
{
get { return _defaultPrice; }
set { _defaultPrice = value;
NotifyPropertyChanged("DefaultPrice");
}
}
private bool _isIncome;
public bool IsIncome
{
get { return _isIncome; }
set { _isIncome = value;
NotifyPropertyChanged("IsIncome");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The way you'd do this with viewmodels would be to give Transaction a Supplier property and an Item property. Those properties would be references to the actual Item and Supplier objects in their own collections. If the relationship is one ItemID and one SupplierID per transaction, that's the object equivalent. If a transaction can be multiple records with the same transaction ID and different supplier or item IDs, then Transaction needs collections of Item and Supplier. We can do that in WPF too, but it'll take a lot more angle brackets than the trivial example below.
You would set that up when you get your items from the database (however you're doing that), or maybe Entity Framework can do that for you.
Real simple listbox displaying item names: Add DisplayMemberPath.
<ListBox
ItemsSource="{Binding Transactions}"
SelectedItem="{Binding CurrentTransaction}"
DisplayMemberPath="Item.Name"
/>
More complicated:
<ListBox
ItemsSource="{Binding Transactions}"
SelectedItem="{Binding CurrentTransaction}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Item.Name, Mode=OneWay}" />
<Run Text=" - " />
<Run Text="{Binding Supplier.Name, Mode=OneWay}" />
<Run Text=" " />
<Run Text="{Binding Price, Mode=OneWay, StringFormat=c}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You could also look into a columned control like a ListView or DataGrid.
Slightly off topic, zero code-behind is a bit extreme. It's a last resort, not a third rail. Minimal code-behind is a sound general principle. Don't go crazy trying to avoid it; it's there for a reason.
UPDATE
public class Transaction : INotifyPropertyChanged
{
// ... stuff ...
public Item Item
{
get { return _item; }
set {
_item = value;
// If this property is ever set outside the Transaction
// constructor, you ABSOLUTELY MUST raise PropertyChanged here.
// Otherwise, make the setter private. But just raise the event.
// This has nothing whatsoever to do with when or whether the Item
// class raises PropertyChanged, because this is not a property of the
// Item class. This is a property of Transaction.
NotifyPropertyChanged("Item");
}
}
// ... more stuff ...
Related
I have implemented my first WPF MVVM Window. On loading the window a method is called that fetches data from an Access database, and all appropriate textboxes are bound to the EmployeeModel's properties.
I also have a button called Refresh that runs the same method to fetch the Employee data from the database, for testing purposes I have made the Refresh button fetch a different employee ID.
However none of the window's fields update with the new EmployeeModel. I've used breakpoints to find there is no issue saving the new data to the EmployeeModel, but no textbox appear to be updated with this new data.
Following guides online, I have used INotifyProperyChanged and use UpdateSourceTrigger in my view.
EmployeeModel
class EmployeeModel : MyDbConnector, INotifyPropertyChanged
{
private int _employeeId;
private string _firstname;
public int EmployeeId
{
get => _employeeId;
set
{
if (_employeeId != value)
{
_employeeId = value;
OnPropertyChanged();
}
}
}
public string Firstname
{
get => _firstname;
set
{
if (_firstname != value)
{
_firstname = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string caller = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
public static EmployeeModel FindById(int id)
{
//code omitted
}
EmployeeViewModel
class EmployeeViewModel
{
public EmployeeModel Employee { get; set; }
public EmployeeViewModel()
{
LoadEmployee(100);
}
public bool LoadEmployee(int employeeId)
{
if (!DbConnector.OpenDB())
return false;
Employee = EmployeeModel.FindById(employeeId);
DbConnector.CloseDB();
if (Employee == null)
return false;
return true;
}
public void Refresh()
{
// Load a different employee
if (!LoadEmployee(102))
MessageBox.Show("An error has occurred");
}
}
View
<Label Width="100" Content="Employee ID"/>
<TextBox Width="100" IsEnabled="False" Text="{Binding Employee.EmployeeId, UpdateSourceTrigger=PropertyChanged}"/>
<Label Width="100" Content="First name"/>
<TextBox Text="{Binding Employee.Firstname, TargetNullValue='', UpdateSourceTrigger=PropertyChanged}"/>
You're changing the entire Model for a new Model object but you're not notifying the UI that you did it. Your EmployeeViewModel class should implement INotifyPropertyChanged just like you did with the employee model and then the property Employee should call OnPropertyChanged method
private EmployeeModel _employee;
public EmployeeModel Employee {
get { return _employee; }
set {
if(_employee!=value) {
_employee=value;
OnPropertyChanged();
}
}
}
Excuse me if I didn't formulate good the question, but don't know how to name it better...
I have a project with some ListView, binded to ObservableCollection.
When I make a right click on my ListView, to change name, or other parameter, the ListView doesn't automatically refresh until I go out, then open again.
Here is my behind code for context :
public class Contexte : INotifyPropertyChanged
{
private Affaire affaireSelectionnee;
public Affaire AffaireSelectionnee
{
get { return affaireSelectionnee; }
set
{
if (value == affaireSelectionnee) return;
affaireSelectionnee = value;
NotifyPropertyChanged("AffaireSelectionnee");
}
}
private ObservableCollection<Affaire> listeDesAffairesSelectionnees;
public ObservableCollection<Affaire> ListeDesAffairesSelectionnees
{
get { return listeDesAffairesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesAffairesSelectionnees, value); }
}
private ObservableCollection<Phase> listeDesPhasesSelectionnees;
public ObservableCollection<Phase> ListeDesPhasesSelectionnees
{
get { return listeDesPhasesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesPhasesSelectionnees, value); }
}
private ObservableCollection<Assemblage> listeDesAssemblagesSelectionnees;
public ObservableCollection<Assemblage> ListeDesAssemblagesSelectionnees
{
get { return listeDesAssemblagesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesAssemblagesSelectionnees, value); }
}
private ObservableCollection<Repere> listeDesReperesSelectionnees;
public ObservableCollection<Repere> ListeDesReperesSelectionnees
{
get { return listeDesReperesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesReperesSelectionnees, value); }
}
private ObservableCollection<Affaire> listeDesAffaires;
public ObservableCollection<Affaire> ListeDesAffaires
{
get { return listeDesAffaires; }
set { NotifyPropertyChanged(ref listeDesAffaires, value); }
}
private ObservableCollection<Phase> listeDesPhases;
public ObservableCollection<Phase> ListeDesPhases
{
get { return listeDesPhases; }
set { NotifyPropertyChanged(ref listeDesPhases, value); }
}
private ObservableCollection<Assemblage> listeDesAssemblages;
public ObservableCollection<Assemblage> ListeDesAssemblages
{
get { return listeDesAssemblages; }
set { NotifyPropertyChanged(ref listeDesAssemblages, value); }
}
private ObservableCollection<Repere> listeDesReperes;
public ObservableCollection<Repere> ListeDesReperes
{
get { return listeDesReperes; }
set { NotifyPropertyChanged(ref listeDesReperes, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string nomPropriete)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
}
private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
{
if (object.Equals(variable, valeur)) return false;
variable = valeur;
NotifyPropertyChanged(nomPropriete);
return true;
}
}
I load the context once when loading the programm :
DBConnect DataBase = new DBConnect();
string requete = "SELECT * FROM affaire ORDER BY ID";
List<Affaire> liste = DataBase.Select_affaire(requete, true);
contexte = new Contexte { ListeDesAffaires = new ObservableCollection<Affaire>(liste), ListeDesPhases = new ObservableCollection<Phase>(), ListeDesAssemblages = new ObservableCollection<Assemblage>(), ListeDesReperes = new ObservableCollection<Repere>(), AffaireSelectionnee = new Affaire(), ListeDesAffairesSelectionnees = new ObservableCollection<Affaire>(liste), ListeDesPhasesSelectionnees = new ObservableCollection<Phase>(), ListeDesAssemblagesSelectionnees = new ObservableCollection<Assemblage>(), ListeDesReperesSelectionnees = new ObservableCollection<Repere>() };
DataContext = contexte;
Then my function that may update property :
foreach (Phase ph in contexte.ListeDesPhasesSelectionnees)
{
Phase ph_find = contexte.ListeDesPhases.First(s=>s==ph);
ph_find.Priorite = new_priorite;
}
ph_find.Priorite is well updated, as is my Observable Collection "contexte.ListeDesPhases", but no refresh is made on the ListView.
Edit : Well I could solve the problem adding a ListView1.Items.Refresh()...
I am not sure this is the most correct way(is not bidding supposed to refresh the listview automaticaly?), but for now it works
Edit2 :
My XAML code (ListView of the phase) :
<ListView x:Name="ListView2" ItemsSource="{Binding ListeDesPhases}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" MouseDoubleClick="ListView_MouseDoubleClick" GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler" SelectionChanged="ListView_SelectionChanged" >
<ListView.View>
<GridView AllowsColumnReorder="true" x:Name="GridView2">
<GridViewColumn DisplayMemberBinding="{Binding ID}" Header="ID" Width="50"/>
<GridViewColumn DisplayMemberBinding= "{Binding NomPhase}" Header="{x:Static p:Resources.Nom}" Width="200"/>
<GridViewColumn DisplayMemberBinding="{Binding IdAffaire}" Header="{x:Static p:Resources.IdAffaire}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding CommPhase}" Header="{x:Static p:Resources.Commentaire}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Priorite}" Header="{x:Static p:Resources.Priorite}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
My Phase class :
public class Phase
{
public string NomPhase { get; set; }
public long IdAffaire { get; set; }
public string CommPhase { get; set; }
public int Priorite { get; set; }
public long ID { get; set; }
public List<Assemblage> ListAssemblages { get; set; }
public Phase()
{
this.NomPhase = "";
this.IdAffaire = 0;
this.CommPhase = "";
this.Priorite = 0;
this.ID = 0;
this.ListAssemblages = new List<Assemblage>();
}
...
}
Edit3 :
Tried to modify as indicated by Netstep, but still the same :
public ObservableCollection<Phase> ListeDesPhases
{
get { return listeDesPhases; }
set { NotifyPropertyChanged(ref listeDesPhases, value);
NotifyPropertyChanged("Priorite");
}
}
Edit 4 :
Well, I now understand that nothing was happening, I read that course http://www.wpf-tutorial.com/data-binding/responding-to-changes/ to understand it...
So example given by NetStep was the good one (just didn't understand what is the RaisePropertyChanged(() => Priorite); part? Is this due to the use of mvvmlight.net library?
public class Phase : INotifyPropertyChanged
{
private string nomPhase;
public string NomPhase
{
get { return this.nomPhase; }
set
{
if (this.nomPhase != value)
{
this.nomPhase = value;
this.NotifyPropertyChanged("NomPhase");
}
}
}
private int priorite;
public int Priorite
{
get { return this.priorite; }
set
{
if (this.priorite != value)
{
this.priorite = value;
this.NotifyPropertyChanged("Priorite");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
...//all of my other functions
}
So this is what I did and it works good now.
Then I have the following question : I have 4 different objects displayed in 4 ListView(Contract, Subcontract, Phase and Detail).
Contract is the "Mother Class", it countains parameters, but also contains a list of Subcontracts. Subcontracts contains several parameters, and a list of Phases, and each Phase contains some parameters, with a list of Details.
Each of them is displayed in a different ListView(4 ListView).
May I define 4 different ObservableCollection, or is there a way to define only one ObservableCollection for all the "tree", then bind on parameters of my ObservableCollection>?
To have the field properly updated in UI, please ensure that Phase class also implement INotifyPropertyChanged someway and call
NotifyPropertyChanged("Priorite")
in set accessor. Just using the ObservableCollection is not enough, it handles notifying only Add/Remove operations. And you right - ListView1.Items.Refresh() is a workaround in this case.
Hope this will help, otherwise please share Phase class code and your xaml code to get more clear comment/answer.
Here is the example, based on MVVM light library:
using GalaSoft.MvvmLight;
namespace WpfApp1
{
public class Phase : ViewModelBase
{
private int _priorite;
public int Priorite
{
get { return _priorite; }
set
{
_priorite = value;
RaisePropertyChanged(() => Priorite);
}
}
}
}
All the rest of code can remain unchanged. You also can inherit you Context class from ViewModelBase
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.
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.