I am having a few struggles on how to wire up my interconnected comboboxes when using MVVM. I have a DTO that represents an Order which contains a CustomerId and an OrderTypeId. Thesere are then wrapped inside an OrderViewModel. I also have an EditOrderViewModel which loads from a db a list of Customers.
What I would like to do is Load an Order from the DB (similar to the Load function) choose the right item in the ComboBox (the items source of which is a List, display the name of the selected customer in a text block to the right of the combobox and finally load the list of OrderTypes that belong to that Customer in the next combobox and again select the correct OrderType and display the OrderTypeName in a TextBlock to the right.
I have managed to get some of the behaviour to work when I use SelectedItem from the combobox but this is only when I select the item manually as I am not sure how in my viewmodel I can convert Order.CustomerId (type int) into the correct SelectedItem (type CustomerDTO). Below is some code which shows generally what I am trying to achieve whilst using in-memory datasources. Thanks Alex
<ComboBox Height="25" Width="150" ItemsSource="{Binding Path=Customers}" SelectedValue="{Binding Path=Order.CustomerId}" SelectedValuePath="Id">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Code}"></TextBlock>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="{Binding Path=Name,Mode=OneWay,NotifyOnSourceUpdated=True}"></TextBlock>
public class EditOrderViewModel : VMBase
{
public OrderViewModel Order{get;set;}
public void Load()
{
Order = new OrderViewModel(new OrderDto{CustomerId=1,OrderTypeId=2});
Order.PropertyChanged += MainWindowViewModel_PropertyChanged;
}
public EditOrderViewModel()
{
Order = new OrderViewModel(new OrderDto());
Order.PropertyChanged += OrderViewModel_PropertyChanged;
Customers = new List<CustomerDto> {
new CustomerDto{ Id = 1, Code = "ACME", Name = "ACME CORP" },
new CustomerDto{ Id = 2, Code = "MSFT", Name="MICROSOFT CORP" },
new CustomerDto{ Id = 3, Code = "APP", Name = "APPLE" }};
OrderTypes = new List<OrderTypeDto>{
new OrderTypeDto{OrderTypeId=1, CustomerId =1, Name = "Cake Order"},
new OrderTypeDto{OrderTypeId=2, CustomerId =1, Name = "Sandwich Order"},
new OrderTypeDto{OrderTypeId=3, CustomerId =2, Name = "Chocolate Order"},
new OrderTypeDto{OrderTypeId=4, CustomerId =2, Name = "Bread Order"},
new OrderTypeDto{OrderTypeId=5, CustomerId =3, Name = "Drinks Order"}};
}
void OrderViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "OrderTypeId":
break;
case "SelectedCustomer":
break;
default:
break;
}
}
public List<OrderTypeDto> OrderTypes { get; set; }
public List<CustomerDto> Customers { get; set; }
}
public class OrderDto
{
public int CustomerId { get; set; }
public int OrderTypeId { get; set; }
}
public class CustomerDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
public class OrderViewModel : VMBase
{
private OrderDto _orderDto;
private string _customerName;
public OrderViewModel(OrderDto orderDto)
{
_orderDto = orderDto;
}
public int CustomerId {
get { return _orderDto.CustomerId; }
set
{
_orderDto.CustomerId = value;
RaisePropertyChanged("CustomerId");
}
}
public string CustomerName {
get { return _customerName; }
set {_customerName = value;
RaisePropertyChanged("CustomerName");
}
}
public int OrderTypeId
{
get { return _orderDto.OrderTypeId; }
set
{
_orderDto.OrderTypeId = value;
RaisePropertyChanged("OrderTypeId");
}
}
}
Don't set ComboBox.SelectedValue in your XAML binding. You should be binding ComboBox.SelectedItem to your model so you can have the CustomDTO easily available. You should add a property to your OrderViewModel called Customer (of type CustomerDTO) instead of trying to recreate the CustomerDTO using several properties (CustomerID, CustomerName, etc.).
Related
Accessibility Narrator reads the class name of the selected Item in the ComboBox instead of the DisplayMemberPath value
public class Student
{
public int Id { get; set; }
public string? Name { get; set; }
}
public class MainViewModel : ViewModelBase
{
private ObservableCollection<Student> _students;
private Student _selectedStudents;
public ObservableCollection<Student> Students
{
get
{
if (_students == null)
{
_students = new ObservableCollection<Student>();
_students.Add(new Student() { Id = 1, Name = "A" });
_students.Add(new Student() { Id = 2, Name = "B" });
_students.Add(new Student() { Id = 3, Name = "C" });
}
return _students;
}
}
public Student SelectedStudents
{
get
{
return _selectedStudents;
}
set
{
_selectedStudents = value;
RaisePropertyChanged(nameof(SelectedStudents));
}
}
}
XAML
<ComboBox
Height="30"
AutomationProperties.Name="Students"
DisplayMemberPath="Name"
ItemsSource="{Binding Path=Students}"
SelectedItem="{Binding SelectedStudents}"/>
When I select the first item of the ComboBox Narrator reads the Student class name instead of the name value "A"
Any suggestions?
I fixed this by overriding ToString in the model class:
public class Student
{
public int Id { get; set; }
public string? Name { get; set; }
public override string ToString()
{
return Name ?? "Student";
}
}
I have datagrid wpf and one column is as combobox. For each row I need different item list.
I have this structure
public class DuplicateType:List<string>
{
public string d { get; set; }
}
public class StructurDatLegend : ObservableObject
{
public bool selected { get; set; }
public string VName { get; set; }
public View VLeg { get; set; }
public XYZ VPol { get; set; }
public DuplicateType DuplicateT { get; set; }
}
foreach (Tuple<View, XYZ> LegItem in duplicatesheet.Getlegendcollection(vs))
{
DuplicateType o = new DuplicateType();
o.Add("tee");
o.Add("fdvxvx");
Legend.Add(new StructurDatLegend { selected = false, VName = LegItem.Item1.Name, VLeg = LegItem.Item1, VPol = LegItem.Item2 , DuplicateT = o });
}
How can I load this data in xaml code? This code doesn't work
<DataGridComboBoxColumn Header =" Duplicate" Width="100" x:Name="DuplicateType" DisplayMemberPath="d" SelectedItemBinding="{Binding Path= DuplicateT}" SelectedValuePath="{Binding DuplicateT}" />
Sorry because my English. Did you set DataContext for datagrid? In your xaml code, you set DisplayMemberPath="d" but in behind code d not be set.
I have two drop-downs A and B. Based on what I select in drop-down A, I want to limit the items shown in B. Is there a property I can bind in xaml or is there any other way to achieve it? I'm using VS 2012, WPF, MVVM model and telerik controls.
Let us consider the example of Country and State combo box where state combo box will be populated according to the selection on the Country combo box.
So, If I speak in terms of XAML properties, here you want to update ItemsSource property of State combo box on basis of SelectedItem property of Country combo box.
To achieve this, add a new property "SelectedCountry" in ViewModel which will hold the selection of Country combo box. In Setter of this "SelectedCountry" property, set StateCollection as per your need.
Make sure to implement INotifyPropertyChanged interface and use ObservableCollection type for both the collections.
Following is the code sample for the same:
Model Classes:
public class Country
{
public string CountryName { get; set; }
public int CountryId { get; set; }
public List<State> States { get; set; }
}
public class State
{
public string StateName { get; set; }
public int StateId { get; set; }
}
ViewModel :
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
CountriesCollection = new ObservableCollection<Country>();
StateCollection = new ObservableCollection<State>();
LoadData();
}
private ObservableCollection<Country> _CountriesCollection;
public ObservableCollection<Country> CountriesCollection
{
get { return _CountriesCollection; }
set
{
_CountriesCollection = value;
NotifyPropertyChanged("CountriesCollection");
}
}
private ObservableCollection<State> _StatesCollection;
public ObservableCollection<State> StateCollection
{
get { return _StatesCollection; }
set
{
_StatesCollection = value;
NotifyPropertyChanged("StateCollection");
}
}
private Country _SelectedCountry;
public Country SelectedCountry
{
get { return _SelectedCountry; }
set
{
_SelectedCountry = value;
if (_SelectedCountry != null && _SelectedCountry.States != null)
{
StateCollection = new ObservableCollection<State>(_SelectedCountry.States);
}
NotifyPropertyChanged("SelectedCountry");
}
}
private void LoadData()
{
if (CountriesCollection != null)
{
CountriesCollection.Add(new Country
{
CountryId = 1,
CountryName = "India",
States = new List<State>
{
new State { StateId = 1, StateName = "Gujarat"},
new State { StateId = 2, StateName = "Punjab"},
new State { StateId = 3, StateName = "Maharastra"}
}
});
CountriesCollection.Add(new Country
{
CountryId = 2,
CountryName = "Chine",
States = new List<State>
{
new State { StateId = 4, StateName = "Chine_State1"},
new State { StateId = 5, StateName = "Chine_State2"},
new State { StateId = 6, StateName = "Chine_State3"}
}
});
CountriesCollection.Add(new Country
{
CountryId = 3,
CountryName = "japan",
States = new List<State>
{
new State { StateId = 7, StateName = "Japan_State1"},
new State { StateId = 8, StateName = "Japan_State2"},
new State { StateId = 9, StateName = "Japan_State3"}
}
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
XALM :
<StackPanel Orientation="Horizontal" >
<ComboBox
Height="30" Width="100"
HorizontalAlignment="Left" Margin="30"
ItemsSource="{Binding CountriesCollection}"
SelectedItem="{Binding SelectedCountry}"
DisplayMemberPath="CountryName">
</ComboBox>
<ComboBox
Height="30" Width="100"
HorizontalAlignment="Left" Margin="30"
ItemsSource="{Binding SelectedCountry.States}"
DisplayMemberPath="StateName">
</ComboBox>
</StackPanel>
XAML.CS
InitializeComponent();
this.DataContext = new MainWindowViewModel();
I hope this example will make things clear for you. Let me know if you need any more information on this.
The idea is to have both list exist in the viewmodel, plus two properties to hold the selected item(data item).
Have the two ComboBox binded to those properties, and in the viewmodel handle the 'FirstSelectedItem' property changed(or entry to its setter).
That way you can modify the the members of the second list.
I'm using MVVM pattern, developing my WPF application. I also use Entity Framework as ORM. Here're my models (EF):
public class User : BaseEntity
{
public string Name { get; set; }
public int OfficeId { get; set; }
public Office Office { get; set; }
}
public class Office : BaseEntity
{
public string Name { get; set; }
public int DepartmentId { get; set; }
public Department Department { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class Department : BaseEntity
{
public string Name { get; set; }
public virtual ICollection<Office> Offices { get; set; }
}
Let's assume, that I've got an instance of User class from my context:
var userInstance = context.Get<User>().Single(user => user.ID == 1);
Now I'd like to pass this instance to my View to make some changes for concrete user (called, for example, UserEditView), so I have to create a UserModel class to deal with User data according to MVVM. So, here's what I think I have to write in my code:
public class UserModel : ObservableObject
{
private User user;
public string Office Office
{
get
{
return this.user.Office.Name;
}
set
{
//what shoud I write Here??
if(value != user.Office)
{
user.Office=value;
OnPropertyChanged("Office");
}
}
}
}
I'm really frustrated! How should I deal with that? There're thousands of examples, but they are so simple. I'm wondering what should I do to have a ComboBox in my EditView with a list of all Offices, existing in my DB. And list of Offices should depend on another one Combobox, which contains a list of Departments.
But where should I get this lists from?
Should I pass a collection from my UserModel? Or what?
Can anybody give me a simple example about how to do this correctly?
PS: Of course, I know couple ways to implement such behaviour, but in that case my code seems to be ugly and not maintainable. Please, help. Thanks a lot!
this is depends on your DB architecture. Here is some common suggestion (but there can be a lot of others).
Don't panic - you have a correct question.
Create the view model set it to be a main view model of your window.
In your view model create two collections Users (containing UserModels) and Departments (containing DepartmentMode), since you want to change offices each time you re-select department, you don't need the Offices collection in main view model.
Pull each collection data from your data base.
Implement each model with INPC.
Take in account the WPF MVVM best practices.
Apply a correct bindings.
And be happy - you are a programmer.
Updates - 1
XAML code
<Grid x:Name="LayoutRoot">
<Grid.DataContext>
<someBindingExampleSoHelpAttempt:MainViewModel/>
</Grid.DataContext>
<ListView ItemsSource="{Binding Users}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="someBindingExampleSoHelpAttempt:UserModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="{Binding Name, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="{Binding LastName, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
<ComboBox Grid.Column="2"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
TextSearch.TextPath="DepartmentName"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListView}}, Path=DataContext.Departments}"
SelectedValue="{Binding Department}"
DisplayMemberPath="DepartmentName"
IsEditable="True"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<ComboBox Grid.Column="3"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
StaysOpenOnEdit="True"
IsEditable="True"
TextSearch.TextPath="OfficeName"
ItemsSource="{Binding OfficesCollection}"
SelectedValue="{Binding Office}"
DisplayMemberPath="OfficeName"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView></Grid>
VM and models
public class MainViewModel:BaseObservableObject
{
private DepartmentModel _selectedDepartment;
private OfficeModel _selectedOffice;
public MainViewModel()
{
Dal = new DataLayer();
Users = new ObservableCollection<UserModel>();
Departments = new ObservableCollection<DepartmentModel>(Dal.GetAllDepartments());
InitUsersCollection();
}
private void InitUsersCollection()
{
if(Departments == null) return;
Departments.ToList().ForEach(model =>
{
model.Offices.ToList().ForEach(officeModel =>
{
if (officeModel.Users == null) return;
officeModel.Users.ToList().ForEach(userModel => Users.Add(userModel));
});
});
}
public ObservableCollection<UserModel> Users { get; set; }
public ObservableCollection<DepartmentModel> Departments { get; set; }
private DataLayer Dal { get; set; }
}
public class DataLayer
{
public List<DepartmentModel> GetAllDepartments()
{
//pull and map your using your DB service
//For example:
return new List<DepartmentModel>
{
new DepartmentModel
{
DepartmentId = 1,
DepartmentName = "A",
Offices = new ObservableCollection<OfficeModel>
{
new OfficeModel
{
DepartmentId = 1,
OfficeName = "AA",
Users = new ObservableCollection<UserModel>(new List<UserModel>
{
new UserModel {Name = "Avicenna", LastName = "Abu Ali Abdulloh Ibn-Sino"},
new UserModel {Name = "Omar", LastName = "Khayyam"},
new UserModel {Name = "RAMBAM", LastName = "Moshe ben Maimon"}
})
},
new OfficeModel
{
DepartmentId = 1,
OfficeName = "AB",
Users = new ObservableCollection<UserModel>(new List<UserModel>
{
new UserModel {Name = "Leo", LastName = "Tolstoi"},
new UserModel {Name = "Anton", LastName = "Chekhov"},
})},
}
},
new DepartmentModel
{
DepartmentId = 2,
DepartmentName = "B",
Offices = new ObservableCollection<OfficeModel>
{
new OfficeModel
{
DepartmentId = 2, OfficeName = "BA",
Users = new ObservableCollection<UserModel>(new List<UserModel>
{
new UserModel {Name = "B", LastName = "O"},
new UserModel {Name = "B", LastName = "N"},
}),
},
new OfficeModel
{
DepartmentId = 2, OfficeName = "BB",
Users = new ObservableCollection<UserModel>(new List<UserModel>
{
new UserModel {Name = "John", LastName = "Walker"},
new UserModel {Name = "Gregory", LastName = "Rasputin"},
}),
},
}
},
new DepartmentModel
{
DepartmentId = 3,
DepartmentName = "C",
Offices = new ObservableCollection<OfficeModel>
{
new OfficeModel {DepartmentId = 3, OfficeName = "CA"},
new OfficeModel {DepartmentId = 3, OfficeName = "CB"},
new OfficeModel {DepartmentId = 3, OfficeName = "CC"}
}
}
};
}
}
public class OfficeModel:BaseObservableObject
{
private int _departmentModel;
private string _officeName;
private DepartmentModel _department;
private ObservableCollection<UserModel> _users;
public int DepartmentId
{
get { return _departmentModel; }
set
{
_departmentModel = value;
OnPropertyChanged();
}
}
public DepartmentModel Department
{
get { return _department; }
set
{
_department = value;
OnPropertyChanged();
}
}
public string OfficeName
{
get { return _officeName; }
set
{
_officeName = value;
OnPropertyChanged();
}
}
public ObservableCollection<UserModel> Users
{
get { return _users; }
set
{
_users = value;
OnPropertyChanged(()=>Users);
}
}
}
public class DepartmentModel:BaseObservableObject
{
private string _departmentName;
public string DepartmentName
{
get { return _departmentName; }
set
{
_departmentName = value;
OnPropertyChanged();
}
}
public int DepartmentId { get; set; }
public ObservableCollection<OfficeModel> Offices { get; set; }
}
public class UserModel:BaseObservableObject
{
private string _name;
private string _lastName;
private DepartmentModel _department;
private OfficeModel _office;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged();
}
}
public DepartmentModel Department
{
get { return _department; }
set
{
_department = value;
OnPropertyChanged();
OnPropertyChanged(()=>OfficesCollection);
}
}
public ObservableCollection<OfficeModel> OfficesCollection
{
get { return Department.Offices; }
}
public OfficeModel Office
{
get { return _office; }
set
{
_office = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
But please take in account that this is only one of hundreds ways to do that.
The SO here if you will need the code example.
Regards.
So I have the following model:
public class Person
{
public String FirstName { get; set; }
public String LastName { get; set; }
public String Address { get; set; }
public String EMail { get; set; }
public String Phone { get; set; }
}
public class Order
{
public Person Pers { get; set;}
public Product Prod { get; set; }
public List<Person> AllPersons { get; set; }
public Order(Person person, Product prod )
{
this.Pers = person;
this.Prod = prod;
AllPersons = database.Persons.GetAll();
}
}
And I have a WPF window used to edit an order.
I set the DataContext to Order.
public SetDisplay(Order ord)
{
DataContext = ord;
}
I have the following XAML:
<ComboBox Name="myComboBox"
SelectedItem = "{Binding Path=Pers, Mode=TwoWay}"
ItemsSource = "{Binding Path=AllPersons, Mode=OneWay}"
DisplayMemberPath = "FirstName"
IsEditable="False" />
<Label Name="lblPersonName" Content = "{Binding Path=Pers.FirstName}" />
<Label Name="lblPersonLastName" Content = "{Binding Path=Pers.LastName}" />
<Label Name="lblPersonEMail" Content = "{Binding Path=Pers.EMail}" />
<Label Name="lblPersonAddress" Content = "{Binding Path=Pers.Address}" />
However, the binding does not seem to work.......When I change the selected item , the labels do not update ....
Regards!!
Any reply is appreciated !!
Your model will need to fire change notifications. See INotifyPropertyChanged and INotifyCollectionChanged.
For INotifyPropertyChanged, you could use a base ViewModel class such as this one. For collections, ObservableCollection<T> does the hard work for you. However, in your case your collection won't change after the UI is bound to it, so you shouldn't need an observable collection. Regardless, I'd generally recommend using observable collections in your view model layer to save head-scratching should the code ever change.
An example of what this would look like is:
public class Person : ViewModel
{
private string firstName;
private string lastName;
private string email;
private string phone;
public string FirstName
{
get
{
return this.firstName;
}
set
{
if (this.firstName != value)
{
this.firstName = value;
OnPropertyChanged(() => this.FirstName);
}
}
}
public string LastName
{
get
{
return this.lastName;
}
set
{
if (this.lastName != value)
{
this.lastName = value;
OnPropertyChanged(() => this.LastName);
}
}
}
// and so on for other properties
}
public class Order : ViewModel
{
private readonly ICollection<Person> allPersons;
private Person pers;
private Product prod;
public Person Pers
{
get
{
return this.pers;
}
set
{
if (this.pers != value)
{
this.pers = value;
OnPropertyChanged(() => this.Pers);
}
}
}
public Product Prod
{
get
{
return this.prod;
}
set
{
if (this.prod != value)
{
this.prod = value;
OnPropertyChanged(() => this.Prod);
}
}
}
// no need for setter
public ICollection<Person> AllPersons
{
get
{
return this.allPersons;
}
}
public Order(Person person, Product prod )
{
this.Pers = person;
this.Prod = prod;
// no need for INotifyCollectionChanged because the collection won't change after the UI is bound to it
this.allPersons = database.Persons.GetAll();
}
}