GridTemplateColumn databinding to complex properties - c#

I have 3 model classes as follows (Vegetable, Fruit and ItemInBasket):
public class Vegetable
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Vegetable(string _Name)
{
this.Name = _Name;
}
}
public class Fruit
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Fruit(string _Name)
{
this.Name = _Name;
}
}
public class ItemInBasket
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
object fruitorvegetable;
public object FruitOrVegetable
{
get { return fruitorvegetable; }
set { fruitorvegetable = value; }
}
int quantity;
public int Quantity
{
get { return quantity; }
set { quantity = value; }
}
public ItemInBasket(string _Name, object _FruitOrVegetable, int _Quantity)
{
this.Name = _Name;
this.FruitOrVegetable = _FruitOrVegetable;
this.quantity = _Quantity;
}
}
My ViewModel is:
public class ViewModel
{
private ObservableCollection<object> _availableItems;
public ObservableCollection<object> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
private ObservableCollection<ItemInBasket> _itemsInBasket;
public ObservableCollection<ItemInBasket> ItemsInBasket
{
get { return _itemsInBasket; }
set { _itemsInBasket = value; }
}
public ViewModel()
{
_availableItems = new ObservableCollection<object>();
_itemsInBasket = new ObservableCollection<ItemInBasket>();
this.GenerateAvailableItems();
this.GenerateItemsInBasket();
}
private void GenerateAvailableItems()
{
_availableItems.Add(new Vegetable("Broccoli")); // index 0
_availableItems.Add(new Vegetable("Kale")); // index 1
_availableItems.Add(new Vegetable("Spinach")); // index 2
_availableItems.Add(new Vegetable("Carrots")); // index 3
_availableItems.Add(new Vegetable("Garlic")); // index 4
_availableItems.Add(new Fruit("Apple")); // index 5
_availableItems.Add(new Fruit("Orange")); // index 6
_availableItems.Add(new Fruit("Pear")); // index 7
_availableItems.Add(new Fruit("Cherry")); // index 8
_availableItems.Add(new Fruit("Grape")); // index 9
}
private void GenerateItemsInBasket()
{
_itemsInBasket.Add(new ItemInBasket("Apples",_availableItems[5],3));
_itemsInBasket.Add(new ItemInBasket("Kale", _availableItems[1], 10));
_itemsInBasket.Add(new ItemInBasket("Grape", _availableItems[9], 2));
_itemsInBasket.Add(new ItemInBasket("Carrots", _availableItems[3], 1));
}
}
I am trying to be able to modify the FruitOrVegetable inside of each ItemInBasket displayed in the datagrid but I have an issue with the data binding. I am using Syncfusion datagrid but I think it shouldnt affect anything.
<syncfusion:SfDataGrid AutoGenerateColumns="False" SelectionMode="Single"
AllowEditing="True" AllowDeleting="True" ItemsSource="{Binding ItemsInBasket,Source={StaticResource viewModel}}">
<syncfusion:SfDataGrid.Columns>
<syncfusion:GridTemplateColumn MappingName="FruitOrVegetable" HeaderText="Order">
<syncfusion:GridTemplateColumn.EditTemplate>
<DataTemplate>
<ComboBox IsEditable="True" DisplayMemberPath="Name" SelectedValuePath="Name"
Text="{Binding FruitOrVegetable.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AvailableItems, Source={StaticResource viewModel}}"/>
</DataTemplate>
</syncfusion:GridTemplateColumn.EditTemplate>
<syncfusion:GridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</syncfusion:GridTemplateColumn.CellTemplate>
</syncfusion:GridTemplateColumn>
<syncfusion:GridNumericColumn HeaderText="Quantity" MappingName ="Quantity"/>
</syncfusion:SfDataGrid.Columns>
</syncfusion:SfDataGrid>

Your requirement to display the modified value of the FruitOrVegetable in SfDataGrid can be achieved by binding the SelectedValue property of ComboBox. Please refer to the below code snippet,
<ComboBox IsEditable="True" DisplayMemberPath="Name" SelectedValuePath="Name"
SelectedValue="{Binding Name}"
Text="{Binding FruitOrVegetable.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AvailableItems, Source={StaticResource viewModel}}"/>

This is where what we call an "interface" comes in handy. This gives you the surety that the properties are indeed present on this object. Consider it a contract between the class and interface to always include some properties.
You can learn more about it here
You can define an interface like this
public interface INamedItem
{
string Name {get;set;}
}
Now instead of the object type use this interface(first implement them in your vegetable and fruit class) so that you can easily bind to it(and many other benefits)
public class Vegetable : INamedItem
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Vegetable(string _Name)
{
this.Name = _Name;
}
}
public class Fruit: INamedItem
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Fruit(string _Name)
{
this.Name = _Name;
}
}
public class ItemInBasket
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
INamedItem fruitorvegetable;
public object FruitOrVegetable
{
get { return fruitorvegetable; }
set { fruitorvegetable = value; }
}
int quantity;
public int Quantity
{
get { return quantity; }
set { quantity = value; }
}
public ItemInBasket(string _Name, INamedItem _FruitOrVegetable, int _Quantity)
{
this.Name = _Name;
this.FruitOrVegetable = _FruitOrVegetable;
this.quantity = _Quantity;
}
}
For your available items it becomes:
private ObservableCollection<INamedItem> _availableItems;
public ObservableCollection<INamedItem> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
Now add normally as you have in your code so no more changes to that and check if it works :)

Related

WPF Datagrid for IList<String> - cannot add new values

I have a JSON model and on there an IList subordinates.
Nothing special and I wanted to put them into a datagrid where users can add and remove values from that list.
So first I hit the only length column problem because apparently C# finds it funny to pick the properties and that happens to be length.
So I got around that by another stackoverflow explaining this.
<DataGrid ItemsSource="{Binding Path=Job.Subordinates, UpdateSourceTrigger=PropertyChanged}" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" Height="Auto" HorizontalAlignment="Left" Grid.Column="1" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Subordinate Id">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
However now the end result is a read only DataGrid. I can't add new rows or edit an existing row if I load in a JSON.
Why would this behavior be happening ?
edit: the json object with its field
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
namespace GalaxyCreator.Model.Json
{
public class Job : ObservableObject
{
private String _id;
public String Id
{
get { return _id; }
set
{
Set(ref _id, value);
}
}
private String _name;
public String Name
{
get { return _name; }
set
{
Set(ref _name, value);
}
}
private Boolean _startActive;
public Boolean StartActive
{
get { return _startActive; }
set
{
Set(ref _startActive, value);
}
}
private Boolean _disabled;
public Boolean Disabled
{
get { return _disabled; }
set
{
Set(ref _disabled, value);
}
}
private Boolean _rebuild;
public Boolean Rebuild
{
get { return _rebuild; }
set
{
Set(ref _rebuild, value);
}
}
private Boolean _comandeerable;
public Boolean Commandeerable
{
get { return _comandeerable; }
set
{
Set(ref _comandeerable, value);
}
}
private Boolean _subordinate;
public Boolean Subordinate
{
get { return _subordinate; }
set
{
Set(ref _subordinate, value);
}
}
private bool _buildatshipyard = true;
public bool Buildatshipyard
{
get { return _buildatshipyard; }
set
{
Set(ref _buildatshipyard, value);
}
}
private JobLocation _jobLocation = new JobLocation();
public JobLocation JobLocation
{
get { return _jobLocation; }
set
{
Set(ref _jobLocation, value);
}
}
private JobCategory _jobCategory = new JobCategory();
public JobCategory JobCategory
{
get { return _jobCategory; }
set
{
Set(ref _jobCategory, value);
}
}
private JobQuota _jobQuota = new JobQuota();
public JobQuota JobQuota
{
get { return _jobQuota; }
set
{
Set(ref _jobQuota, value);
}
}
private IList<JobOrder> _orders = new List<JobOrder>();
public IList<JobOrder> Orders
{
get { return _orders; }
set
{
Set(ref _orders, value);
}
}
private String _basket;
public String Basket
{
get { return _basket; }
set
{
Set(ref _basket, value);
}
}
private String _encounters;
public String Encounters
{
get { return _encounters; }
set
{
Set(ref _encounters, value);
}
}
private String _time;
public String Time
{
get { return _time; }
set
{
Set(ref _time, value);
}
}
private Ship _ship = new Ship();
public Ship Ship
{
get { return _ship; }
set
{
Set(ref _ship, value);
}
}
private IList<String> _subordinates = new List<String>();
public IList<String> Subordinates
{
get { return _subordinates; }
set
{
Set(ref _subordinates, value);
}
}
}
}
edit: I tried a new approach but still no luck, this time I tried wrapping the String
Wrapper
public class SubordinateItem : ObservableObject
{
private String _value = default(String);
public SubordinateItem(string subordinate)
{
this._value = subordinate;
}
public String Value
{
get { return _value; }
set
{
if (value != _value)
{
_value = value;
Set(ref _value, value);
}
}
}
}
ViewModel
class JobEditorDetailViewModel : DialogViewModelBase
{
public Job Job { get; set; }
private ObservableCollection<SubordinateItem> _subordinateItems = new ObservableCollection<SubordinateItem>();
public ObservableCollection<SubordinateItem> SubordinateItems
{
get { return _subordinateItems; }
set
{
this.Job.Subordinates.Clear();
foreach (SubordinateItem item in value)
{
this.Job.Subordinates.Add(item.Value);
}
Set(ref _subordinateItems, value);
}
}
public JobEditorDetailViewModel(string message, Job job) : base(message)
{
this.Job = job;
this._saveCommand = new RelayCommand<object>((parent) => OnSaveClicked(parent));
this._cancelCommand = new RelayCommand<object>((parent) => OnCancelClicked(parent));
foreach(String subordinate in Job.Subordinates)
{
_subordinateItems.Add(new SubordinateItem(subordinate));
}
}
}
XAML
<DataGrid ItemsSource="{Binding SubordinateItems, NotifyOnTargetUpdated=True}" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" Height="Auto" Grid.Row="1" AutoGenerateColumns="False" HorizontalAlignment="Left" Width="572" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="500" Header="Subordinate Id" IsReadOnly="false" />
</DataGrid.Columns>
</DataGrid>
So after a long session on writing all kinds of hacks to try and fix it I got a solution with the following setup
public class SubordinateItem : ObservableObject
{
private String _value = default(String);
public SubordinateItem() { }
public SubordinateItem(string subordinate)
{
this._value = subordinate;
}
public String Value
{
get { return _value; }
set
{
if (value != _value)
{
_value = value;
Set(ref _value, value);
}
}
}
}
Notice the public constructor without params, this is needed to be able to add new items to the datagrid. Without it, it cannot instantiate new objects for it. Here is the view model
class JobEditorDetailViewModel : DialogViewModelBase
{
public Job Job { get; set; }
private RelayCommand<object> _saveCommand = null;
public RelayCommand<object> SaveCommand
{
get { return _saveCommand; }
set { _saveCommand = value; }
}
private RelayCommand<object> _cancelCommand = null;
public RelayCommand<object> CancelCommand
{
get { return _cancelCommand; }
set { _cancelCommand = value; }
}
private IList<SubordinateItem> _subordinateItems = new List<SubordinateItem>();
public IList<SubordinateItem> SubordinateItems
{
get { return _subordinateItems; }
set
{
Set(ref _subordinateItems, value);
}
}
public JobEditorDetailViewModel(string message, Job job) : base(message)
{
this.Job = job;
this._saveCommand = new RelayCommand<object>((parent) => OnSaveClicked(parent));
this._cancelCommand = new RelayCommand<object>((parent) => OnCancelClicked(parent));
foreach (String subordinate in Job.Subordinates)
{
_subordinateItems.Add(new SubordinateItem(subordinate));
}
}
private void OnSaveClicked(object parameter)
{
this.Job.Subordinates.Clear();
foreach (SubordinateItem item in _subordinateItems)
{
this.Job.Subordinates.Add(item.Value);
}
this.CloseDialogWithResult(parameter as Window, DialogResult.Yes);
}
private void OnCancelClicked(object parameter)
{
this.CloseDialogWithResult(parameter as Window, DialogResult.No);
}
}
So in the constructor of the viewmodel I take the data from the json object Job.Subordinates which is an IList of String and I wrap it in SubordinateItem and store it in _subordinateItems. The datagrid is bound to this and will work with it. Then I have a save button with a command behind it where I do the reverse. I take everything the DataGrid has in _subordinateItems and put it back into the json object. There was no way whatsoever it seemed for me to bind the datagrid directly to the json object IList of String
The Xaml relevant part
<Label Content="Subordinates" />
<DataGrid ItemsSource="{Binding SubordinateItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Height="Auto" Grid.Row="1"
AutoGenerateColumns="False"
HorizontalAlignment="Left"
Width="572" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="500" Header="Subordinate Id" IsReadOnly="false" />
</DataGrid.Columns>
</DataGrid>

WPF get different value from ComboBox

My ComboBox looks like this:
<ComboBox Name="cbMDLName" Style="{StaticResource ComoboBox}"
DisplayMemberPath="Name" ItemsSource="{Binding Path=MDLCollection}"
SelectionChanged="cbMDLName_SelectionChanged" Grid.Row="0" Grid.Column="1"/>
The DataContext is set from the code behind file.
this.DataContext = this.ManagerRoot.MDLM;
The ItemSource of the ComboBox is set to ObservableCollection<MDL> MDLCollection {get;set;}.
MDL.cs
public class MDL : INotifyPropertyChanged
{
public AdditionManager AdditionManager { get; set; }
private string _name;
public string Name
{
get { return this._name; }
set
{
this._name = value;
this.OnPropertyChanged("Name");
}
}
private int _index;
public int Index
{
get { return this._index; }
set
{
this._index = value;
}
}
}
Now I want to get the index from the selected entry. How can I do that?
You may try this.
int selectedValue = (int)cbMDLName.SelectedValue;

C# Changing other values on everytime something is changed using mvvm

I have a DataGrid that is bound to an ObservableCollection<Item>. When I change the Quantity of an Item I would like to automatically change the total of the item. Which is Quantity*Cost=Total
<DataGrid ItemsSource="{Binding Invoices.Items}" AutoGenerateColumns="False">
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" />
<DataGridTextColumn Header="Cost" Binding="{Binding Cost}" />
<DataGridTextColumn Header="Total" Binding="{Binding Total}" />
</DataGrid>
The ViewModel is vary simple"
public class ViewModel:BaseClass
{
public ViewModel()
{
FillInvoice();
}
private Invoice _invoice;
public Invoice Invoice
{
get { return _invoice; }
set
{
if (_invoice!=value)
{
_invoices = value;
OnPropertyChanged();
}
}
}
private void FillInvoice()
{
var customer = new Customer() {Id=1,Name = "James"};
var invoice = new Invoice() {Customer = customer, Id = 1,CustomerId = 1};
var item = new Item() {Cost = Convert.ToDecimal(12.50),Id = 1,Name = "Item"};
for (int i = 0; i < 10; i++)
{
invoice.Items.Add(item);
}
Invoices = invoice;
}
}
The Invoice Look like:
public Invoices()
{
Items=new ObservableCollection<Item>();
}
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public ObservableCollection<Item> Items { get; set; }
My Item Looks like:
public class Item:BaseClass
{
private string _name;
private decimal _cost;
public int Id { get; set; }
public string Name
{
get { return _name; }
set
{
if (_name!=value)
{
_name = value;
OnPropertyChanged();
}
}
}
private int _quantity;
public int Quantity
{
get { return _quantity; }
set
{
if (_quantity!=value)
{
_quantity = value;
OnPropertyChanged();
}
}
}
public decimal Cost
{
get { return _cost; }
set
{
if (_cost!=value)
{
_cost = value;
OnPropertyChanged();
}
}
}
private decimal _total;
public decimal Total
{
get { return _total; }
set
{
if (_total != value)
{
_total = value;
OnPropertyChanged();
}
}
}
}
What I was thinking was adding an eventhandler to the Quantity item that will calculate the total for me but I am not sure how to do that I have Tried adding.
public ViewModel(){
Invoice.Items.Quantity.PropertyChanged += (s,e)
{
Total = Cost*Quantity
}
}
public Item()
{
Quantity.PropertyChanged += (s,e)
{
Total = Cost*Quantity
}
}
but it doesn't compile on them.
You could just implement Total as a calculated property, if it is always equal to Cost*Quantity, as there is no point in storing redundant data :
public decimal Total
{
get { return Cost * Quantity; }
}
This should then just work, assuming OnPropertyChanged() with no parameters fires a generic/null property change event which will cause all subscribed properties to be rechecked.
Try adding OnPropertyChanged("Total"); to your Quantity property:
private int _quantity;
public int Quantity
{
get { return _quantity; }
set
{
if (_quantity!=value)
{
_quantity = value;
OnPropertyChanged();
Total = Cost*Quantity
OnPropertyChanged("Total");
}
}
}
You could have the code written within your Property like so:
private decimal _total;
private int _quantity;
private decimal _cost
public decimal Quantity
{
get { return _quantity; }
set
{
_quantity = value;
Total = _quantity * _cost;
}
}
public decimal Total
{
get { return _total; }
set
{
if (_total != value)
{
_total = value;
OnPropertyChanged();
}
}
}

Binding ComboBox SelectedItem using MVVM

I have a problem with the SelectedItem in my ComboBox.
<ComboBox Name="cbxSalesPeriods"
ItemsSource="{Binding SalesPeriods}"
DisplayMemberPath="displayPeriod"
SelectedItem="{Binding SelectedSalesPeriod}"
SelectedValuePath="displayPeriod"
IsSynchronizedWithCurrentItem="True"/>
If I open the ComboBox, I see the values.
If I select an item, the selected Item won't be shown.
Has anybody an idea?
In my ViewModel I have these two properties:
public ObservableCollection<SalesPeriodVM> SalesPeriods { get; private set; }
private SalesPeriodVM selectedSalesPeriod;
public SalesPeriodVM SelectedSalesPeriod
{
get { return selectedSalesPeriod; }
set
{
if (selectedSalesPeriod != value)
{
selectedSalesPeriod = value;
RaisePropertyChanged("SelectedSalesPeriod");
}
}
}
These are a few properties from the class :
public SalesPeriodVO Vo
{
get { return period; }
}
public int Year
{
get { return period.Year; }
set
{
if (period.Year != value)
{
period.Year = value;
RaisePropertyChanged("Year");
}
}
}
public int Month
{
get { return period.Month; }
set
{
if (period.Month != value)
{
period.Month = value;
RaisePropertyChanged("Month");
}
}
}
public string displayPeriod {
get
{
return this.ToString();
}
}
public override string ToString()
{
return String.Format("{0:D2}.{1}", Month, Year);
}
EDIT:
The Following happens If I remove the Property DisplayMemberPath:
You seem to be unnecessarily setting properties on your ComboBox. You can remove the DisplayMemberPath and SelectedValuePath properties which have different uses. It might be an idea for you to take a look at the Difference between SelectedItem, SelectedValue and SelectedValuePath post here for an explanation of these properties. Try this:
<ComboBox Name="cbxSalesPeriods"
ItemsSource="{Binding SalesPeriods}"
SelectedItem="{Binding SelectedSalesPeriod}"
IsSynchronizedWithCurrentItem="True"/>
Furthermore, it is pointless using your displayPeriod property, as the WPF Framework would call the ToString method automatically for objects that it needs to display that don't have a DataTemplate set up for them explicitly.
UPDATE >>>
As I can't see all of your code, I cannot tell you what you are doing wrong. Instead, all I can do is to provide you with a complete working example of how to achieve what you want. I've removed the pointless displayPeriod property and also your SalesPeriodVO property from your class as I know nothing about it... maybe that is the cause of your problem??. Try this:
public class SalesPeriodV
{
private int month, year;
public int Year
{
get { return year; }
set
{
if (year != value)
{
year = value;
NotifyPropertyChanged("Year");
}
}
}
public int Month
{
get { return month; }
set
{
if (month != value)
{
month = value;
NotifyPropertyChanged("Month");
}
}
}
public override string ToString()
{
return String.Format("{0:D2}.{1}", Month, Year);
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (string propertyName in propertyNames) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
PropertyChanged(this, new PropertyChangedEventArgs("HasError"));
}
}
}
Then I added two properties into the view model:
private ObservableCollection<SalesPeriodV> salesPeriods = new ObservableCollection<SalesPeriodV>();
public ObservableCollection<SalesPeriodV> SalesPeriods
{
get { return salesPeriods; }
set { salesPeriods = value; NotifyPropertyChanged("SalesPeriods"); }
}
private SalesPeriodV selectedItem = new SalesPeriodV();
public SalesPeriodV SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; NotifyPropertyChanged("SelectedItem"); }
}
Then initialised the collection with your values:
SalesPeriods.Add(new SalesPeriodV() { Month = 3, Year = 2013 } );
SalesPeriods.Add(new SalesPeriodV() { Month = 4, Year = 2013 } );
And then data bound only these two properties to a ComboBox:
<ComboBox ItemsSource="{Binding SalesPeriods}" SelectedItem="{Binding SelectedItem}" />
That's it... that's all you need for a perfectly working example. You should see that the display of the items comes from the ToString method without your displayPeriod property. Hopefully, you can work out your mistakes from this code example.
I had a similar problem where the SelectedItem-binding did not update when I selected something in the combobox. My problem was that I had to set UpdateSourceTrigger=PropertyChanged for the binding.
<ComboBox ItemsSource="{Binding SalesPeriods}"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}" />
<!-- xaml code-->
<Grid>
<ComboBox Name="cmbData" SelectedItem="{Binding SelectedstudentInfo, Mode=OneWayToSource}" HorizontalAlignment="Left" Margin="225,150,0,0" VerticalAlignment="Top" Width="120" DisplayMemberPath="name" SelectedValuePath="id" SelectedIndex="0" />
<Button VerticalAlignment="Center" Margin="0,0,150,0" Height="40" Width="70" Click="Button_Click">OK</Button>
</Grid>
//student Class
public class Student
{
public int Id { set; get; }
public string name { set; get; }
}
//set 2 properties in MainWindow.xaml.cs Class
public ObservableCollection<Student> studentInfo { set; get; }
public Student SelectedstudentInfo { set; get; }
//MainWindow.xaml.cs Constructor
public MainWindow()
{
InitializeComponent();
bindCombo();
this.DataContext = this;
cmbData.ItemsSource = studentInfo;
}
//method to bind cobobox or you can fetch data from database in MainWindow.xaml.cs
public void bindCombo()
{
ObservableCollection<Student> studentList = new ObservableCollection<Student>();
studentList.Add(new Student { Id=0 ,name="==Select=="});
studentList.Add(new Student { Id = 1, name = "zoyeb" });
studentList.Add(new Student { Id = 2, name = "siddiq" });
studentList.Add(new Student { Id = 3, name = "James" });
studentInfo=studentList;
}
//button click to get selected student MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
Student student = SelectedstudentInfo;
if(student.Id ==0)
{
MessageBox.Show("select name from dropdown");
}
else
{
MessageBox.Show("Name :"+student.name + "Id :"+student.Id);
}
}

MVVM model - association between models

Let's say I have following models:
class Worker
{
int Id;
string firstname;
string lastname;
}
class Department
{
string title;
string description;
List<Worker> workers;
}
I want to display, on UI, department's title,description and list of workers inside listbox (in listbox I want to display only firstname and lastname).
Do I need to create ONE viewmodel that will wrap this relation or I must I create a viewmodel for every model that I have?
You can create on ViewModel Which wrap both of them like:
namespace XXXX.ViewModel
{
public class MainViewModel : ViewModelBase
{
private int _id;
private string _total;
private string _description;
private ObservableCollection<Worker> _workers;
public int Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
RaisePropertyChanged("Id");
}
}
public string Total
{
get { return _total; }
set
{
if (value == _total) return;
_total = value;
RaisePropertyChanged("Total");
}
}
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
RaisePropertyChanged("Description");
}
}
public ObservableCollection<Worker> Workers
{
get { return _workers; }
set
{
if (value == _workers) return;
_workers = value;
RaisePropertyChanged("Workers");
}
}
//****************** You Logic *************************
public MainViewModel()
{
Department department = new Department();
}
//****************** You Logic *************************
}
}
You wouldn't have ViewModel for every Model, in MVVM you should have a unique ViewModel for almost every view. You would then map the Model to the ViewModel.
For example:
public class DepartmentViewModel
{
public string title { get; set; }
public string description { get; set; }
public IEnumerable<Worker> workers { get; set; }
//Additional ViewModel properties here
//These may or may not be items that exist in your Model
/// <summary>
/// Mapped to the description but truncated to 10 characters and followed by an elispe (...)
/// </summary>
public string ShortDescription
{
get
{
return description.Substring(0,10) + "...";
}
}
}
I realize at first this looks a little redundant. However, there could be other less 1:1 type of views you might create from the model.
Also check out automapper.org, this is a great tool for mapping object to object.
You have 1 view model that contains both the workers and the department.
If the view only wants to show certain attributes of the workers, then the view should do that filtering. Try using an item template:
<ListBox x:Name="_workers" ItemsSource="{Binding Workers}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding firstname}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding lastname}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The view model should contain:
private string _title;
public string Title {
get {return _title;}
set {_title = value; RaisePropertyChanged("Title");}
}
private string _description;
public string Description {
get {return _description;}
set {_description= value; RaisePropertyChanged("Description");}
}
public ObservableCollection Workers {get; private set;}
public Constructor()
{
Workers = new ObservableCollection();
}
//This method is called by the model once it has fetched data.
//This can be done as a callback or in an event handler
public CalledByTheModelAfterLoadingData(Department department)
{
Title = department.Title;
Description = department.Description;
foreach (var worker in department.Workers)
{
Workers.Add(worker);
}
}

Categories