Select item from combo box WPF from view model in C# WPF - c#

I have the following view model:
public sealed class FileViewModel : AbstractPropNotifier
{
private string _path;
private CategoryViewModel _category;
public string Path
{
get
{
return _path;
}
set
{
_path = value;
OnPropertyChanged(nameof(Path));
OnPropertyChanged(nameof(Title));
}
}
public string Title => System.IO.Path.GetFileNameWithoutExtension(Path);
public CategoryViewModel Category
{
get
{
return _category;
}
set
{
_category = value;
OnPropertyChanged(nameof(Category));
}
}
}
and Category view model:
public sealed class CategoryViewModel : IEquatable<CategoryViewModel>
{
public string Title { get; set; }
public EMyEnum Value { get; set; }
public bool Equals(CategoryViewModel other)
{
return Title.Equals(other.Title) && Value == other.Value;
}
public static CategoryViewModel From(EMyEnum eCat)
{
return new CategoryViewModel
{
Title = eCat.DescriptionAttr(),
Value = eCat
};
}
}
I set data context to my view model like:
public sealed class MainViewModel
{
public MainViewModel()
{
Files = new ObservableCollection<FileViewModel>();
Categories = GetCategories();
}
public ObservableCollection<FileViewModel> Files { get; set; }
public CategoryViewModel[] Categories { get; set; }
private CategoryViewModel[] GetCategories()
{
var enums = Enum.GetValues(typeof(EMyEnum));
var list = new List<CategoryViewModel>();
foreach (var en in enums)
{
EMyEnum cat = (EMyEnum)en;
list.Add(CategoryViewModel.From(cat));
}
return list.ToArray();
}
}
and
_model = new MainViewModel();
DataContext = _model;
and XAML:
<Window.Resources>
<CollectionViewSource x:Key="Categories" Source="{Binding Categories}"/>
</Window.Resources>
and in DataGrid element
<DataGridComboBoxColumn SelectedItemBinding="{Binding Category}" ItemsSource="{Binding Source={StaticResource Categories}}" Header="Category" Width="2*" DisplayMemberPath="Title"/>
The dropdown is populated correctly but cannot select automatically from dropdown a specific Category, means the Category column from Datagrid is empty.
I expected to select automatically from dropdown with correspondent Category...
Where is my mistake ? I tried with SelectedItemBinding and SelectedValueBinding but same issue. Nothing selected from dropdown.
To be clear:
For a file, I set a category but nothing is selected:
But dropdown has items:

There are probably different instances of CategoryViewModels in your MainViewModel compared to the ones in the FileViewModels.
You should either override Equals and GetHashCode in your CategoryViewModel class or make sure that you set the Category property of each FileViewModel to a CategoryViewModel that's actually present in the CategoryViewModel[] array of the MainViewModel.

Related

DataContext: Updating comboBox's ItemSource

I want to be able to update a ComboBox within my Gird. I'm assuming I need some sort of event system.
I've bound it as follows:
<ComboBox Name="ScreenLocations" Grid.Row="1" Margin="0,0,0,175" ItemsSource="{Binding Path=CurrentPlayer.CurrentLocation.CurrentDirections}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectedValue="{Binding Path= Location}"/>
my xaml.cs is as follows:
public partial class MainWindow : Window
{
GameSession _gameSession;
public MainWindow()
{
InitializeComponent();
_gameSession = new GameSession();
DataContext = _gameSession;
}
}
I want to be able to change the CurrentDirections property and to have it updated in the UI.
The class and properties I have it bound to is:
public class Location
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Quest[] AvailableQuests { get; set; }
public Monster[] LocationMonsters { get; set; }
public Location[] CurrentDirections { get; set; }
public Location(string name, string description, Quest[] availableQuests, int id)
{
Name = name;
Description = description;
AvailableQuests = availableQuests;
ID = id;
CurrentDirections = new Location[] { };
LocationMonsters = new Monster[] { };
AvailableQuests = new Quest[] { };
}
}
You just need to implement interface System.ComponentModel.INotifyPropertyChanged on class Location. This will oblige you to define a PropertyChanged event that interested parties (such as the bound ComboBox) can subscribe to in order to detect changes, and you can then reimplement CurrentDirections as follows, such that it notifies interested parties of the change via this event :
private Location[] currentDirections;
public Location[] CurrentDirections
{
get {return currentDirections;}
set {currentDirections = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("CurrentDirections"));}
}
For completeness you should consider implementing this interface on Player, and for the other properties of Location.

Cannot display item in combobox from ObservableCollection

I've this combobox:
<ComboBox x:Name="notification_mode" ItemsSource="{Binding NotificationMode}"
DisplayMemberPath="Text"/>
in my model I've created a class that add also the value to comboboxitem:
public class ComboboxItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Text;
}
}
so in my viewmodel I've created an observableCollection that store all the items:
private ObservableCollection<Models.ComboboxItem> _notificationMode = new ObservableCollection<Models.ComboboxItem>();
public ObservableCollection<Models.ComboboxItem> NotificationMode
{
get
{
return _notificationMode;
}
set
{
Models.ComboboxItem item = new Models.ComboboxItem();
item.Text = "Con sonoro";
item.Value = 0;
_notificationMode.Add(item);
}
}
The problem's that in the combobox item isn't displayed nothing. Why happen this?
This is the standard Property declaration
public ObservableCollection<Models.ComboboxItem> NotificationMode
{
get
{
return _notificationMode;
}
set
{
_notificationMode = value;
OnPropertyChanged("NotificationMode");
}
}
You can initialize the above defined property in the ViewModel constructor
public YourViewModel()
{
Models.ComboboxItem item = new Models.ComboboxItem();
item.Text = "Con sonoro";
item.Value = 0;
_notificationMode.Add(item);
}

WPF Binding ComboBox to my ViewModel

I am trying to bind my ViewModel to my ComboBox. I have ViewModel class defined like this:
class ViewModel
{
public ViewModel()
{
this.Car= "VW";
}
public string Car{ get; set; }
}
I set this ViewModel as DataContext in Window_Load like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new CarModel();
}
Then in my xaml, I do this to bind my ComboBox to this ViewModel. I want to show the "VW" as selected by default in my ComboBox:
<ComboBox Name="cbCar" SelectedItem="{Binding Car, UpdateSourceTrigger=PropertyChanged}">
<ComboBoxItem Tag="Mazda">Mazda</ComboBoxItem>
<ComboBoxItem Tag="VW">VW</ComboBoxItem>
<ComboBoxItem Tag="Audi">Audi</ComboBoxItem>
</ComboBox>
I have 2 questions:
How do I set default value selected in Combo Box to "VW" (once form loads, it should show "VW" in combo box).
Instead of setting ComboBoxItems like above in xaml, how to I set it in my ViewModel and then load these in ComboBox?
Thanks,
UPDATE:
So far, I manage to implement this but I get error as below in the ViewModel c-tor:
namespace MyData
{
class ViewModel
{
public ViewModel()
{
this.Make = "";
this.Year = 1;
this.DefaultCar = "VW"; //this is where I get error 'No string allowed'
}
public IEnumerable<Car> Cars
{
get
{
var cars = new Car[] { new Car{Model="Mazda"}, new Car{Model="VW"}, new Car{Model="Audi"} };
DefaultCar = cars.FirstOrDefault(car => car.Model == "VW");
}
}
public string Make { get; set; }
public int Year { get; set; }
public Car DefaultCar { get; set; }
}
class Car
{
public string Model { get; set; }
}
}
As you are going to implement MVVM it will be a lot better if you start to think in objects to represent Cars in your application:
public class ViewModel
{
public Car SelectedCar{ get; set; }
public ObservableCollection<Car> Cars{
get {
var cars = new ObservableCollection(YOUR_DATA_STORE.Cars.ToList());
SelectedCar = cars.FirstOrDefault(car=>car.Model == "VW");
return cars;
}
}
}
public class Car
{
public string Model {get;set;}
public string Make { get; set; }
public int Year { get; set; }
}
Your Xaml:
<ComboBox SelectedItem="{Binding SelectedCar}", ItemsSource="{Binding Cars}"
UpdateSourceTrigger=PropertyChanged}"/>
Default Value:
If you set viewModel.Car = "VW", then it should auto-select that item in the combo box.
For this to work you will need to either implement INotifyPropertyChanged or set Car before you set DataContext.
INotifyPropertyChanged implementation might look like:
class ViewModel : INotifyPropertyChanged
{
private string car;
public ViewModel()
{
this.Car = "VW";
this.Cars = new ObservableCollection<string>() { "Mazda", "VW", "Audi" };
}
public string Car
{
get
{
return this.car;
}
set
{
if (this.car != value)
{
this.car = value;
OnPropertyChanged();
}
}
}
public ObservableCollection<string> Cars { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2.
Bind ItemsSource and SelectedItem.
<ComboBox ItemsSource="{Binding Cars}"
SelectedItem="{Binding Car, Mode=TwoWay}">
</ComboBox>
You can also set ComboBox.ItemTemplate if your source or view is more complex than just displaying a string:
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
In the view model just add a list property:
public ObservableCollection<string> Cars { get; set; }
It doesn't have to be ObservableCollection but that type will auto-update the UI whenever you change the collection.

WPF Datagrid - How to make a change in one row affect other rows

I have a WPF datagrid that is bound to a collection of Item objects. The datagrid has a checkbox column. I would like to implement it so that when the checkbox is checked/unchecked from any row, all other rows are checked/unchecked. Is there a good MVVM way to do this?
XAML
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding MyProperty}" />
<DataGrid.Columns>
</DataGrid>
C#
public class DataGridViewModel
{
public ObservableCollection<Item> Items { get; set; }
}
public class Item
{
public bool MyProperty { get; set; } // Set all Item.MyProperties when any are set
}
Based on a Previous Answer:
Use this as your data items:
public class Selectable<T>: ViewModelBase //ViewModelBase should Implement NotifyPropertyChanged.
{
private T _model;
public T Model
{ get { return _model; }
set
{
_model = value;
NotifyPropertyChange("Model");
}
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
NotifyPropertyChange("IsSelected");
if (OnIsSelectedChanged != null)
OnIsSelectedChanged(this);
}
}
public Action<Selectable<T>> OnIsSelectedChanged;
}
Then change your ViewModel to look like so:
public class DataGridViewModel
{
public ObservableCollection<Selectable<Item>> Items { get; set; }
public void SomeMethod()
{
Items = new ObservableCollection<Selectable<Item>>();
//Populate the Collection here!
foreach (var item in Items)
item.OnIsSelectedChanged = OnItemSelectedChanged;
}
private void OnItemSelectedChanged(Selectable<Item> item)
{
if (item.IsSelected)
{
var itemsToDeselect = Items.Where(x => x != item);
foreach (var itemToDeselect in itemsToDeselect)
itemToDeselect.IsSelected = false;
}
}
}

MVVM WPF Master Detail Comboboxes

Thanks to some of the advice I have got previously on Stack Overflow I have been making good progress in my understanding of MVVM. However, it is when things start to get more complicated that I am still struggling.
I have the view below which is for the purpose of entering orders. It is bound to a DataContext of OrderScreenViewModel.
<StackPanel>
<ComboBox Height="25" Width="100" DisplayMemberPath="CustomerCode" SelectedItem="{Binding Path=Order.Customer}" ItemsSource="{Binding Path=Customers}"></ComboBox>
<ComboBox Height="25" Width="100" DisplayMemberPath="ProductCode" SelectedItem="{Binding Path=CurrentLine.Product}" ItemsSource="{Binding Path=Products}"></ComboBox>
</StackPanel>
The first combobox is used to select a Customer. The second combobox is used to select a ProductCode for a new OrderLine.
There are the items that I cannot work out how to achieve in MVVM:
1) When a Customer is selected update the Products combobox so that its item source only shows Products that have the same CustomerId as the CustomerDto record selected in the combobox
2) When Load is called set the SelectedItem in the Customers combobox so that it displays the Customer with the CustomerId equal to the one on the OrderDto.
3) Apply, the same process as 1) so that only Products belonging to that Customer are shown / loaded and set the SelectedItem on the Products combobox so that it is pointing to the entry with the same ProductId as is contained on the OrderLineDto
I am not sure how to proceed or even if I have got the responsibilities of my viewmodels correct. Maybe it has something to do with NotifyPropertyChanged? Any pointers on how I can achieve the above will be greatly appreciated. I am sure if I get this right it will help me greatly in my app. Many thanks Alex.
public class OrderScreenViewModel
{
public WMSOrderViewModel Order { get; private set; }
public WMSOrderLineViewModel CurrentLine { get; private set; }
public OrderScreenViewModel()
{
Order = new WMSOrderViewModel();
CurrentLine = new WMSOrderLineViewModel(new OrderLineDto());
}
public void Load(int orderId)
{
var orderDto = new OrderDto { CustomerId = 1, Lines = new List<OrderLineDto> { new OrderLineDto{ProductId = 1 }} };
Order = new WMSOrderViewModel(orderDto);
}
public List<CustomerDto> Customers
{
get{
return new List<CustomerDto> {
new CustomerDto{CustomerId=1,CustomerCode="Apple"},
new CustomerDto{CustomerId=1,CustomerCode="Microsoft"},
};
}
}
public List<ProductDto> Products
{
get
{
return new List<ProductDto> {
new ProductDto{CustomerId=1,ProductId=1,ProductCode="P100",Description="Pepsi"},
new ProductDto{CustomerId=1,ProductId=2,ProductCode="P110",Description="Coke"},
new ProductDto{CustomerId=2,ProductId=3,ProductCode="P120",Description="Fanta"},
new ProductDto{CustomerId=2,ProductId=4,ProductCode="P130",Description="Sprite"}
};
}
}
public class WMSOrderLineViewModel
{
private ProductDto _product;
private OrderLineDto _orderLineDto;
public WMSOrderLineViewModel(OrderLineDto orderLineDto)
{
_orderLineDto = orderLineDto;
}
public ProductDto Product { get { return _product; }
set{_product = value; RaisePropertyChanged("Product"); }
}
public class WMSOrderViewModel
{
private ObservableCollection<WMSOrderLineViewModel> _lines;
private OrderDto _orderDto;
public ObservableCollection<WMSOrderLineViewModel> Lines { get { return _lines; } }
private CustomerDto _customer;
public CustomerDto Customer { get{return _customer;} set{_customer =value; RaisePropertyChanged("Customer") }
public WMSOrderViewModel(OrderDto orderDto)
{
_orderDto = orderDto;
_lines = new ObservableCollection<WMSOrderLineViewModel>();
foreach(var lineDto in orderDto.Lines)
{
_lines.Add(new WMSOrderLineViewModel(lineDto));
}
}
public WMSOrderViewModel()
{
_lines = new ObservableCollection<WMSOrderLineViewModel>();
}
}
You need to make Products and Customers type ObservableCollection.
When you change these observablecollections in your viewmodel they will update the view, because OC's already implement INotifyPropertyChanged.
Order and CurrentLine should just be a type and not really be called a ViewModel.
1) You're going to have to do this when the setter is called on the SelectedItem of the Customer combobox is selected.
2) You'll need to do this probably in the ctr of the OrderScreenViewModel by using your logic to determine what Customer to change the CurrentLine.Customer too. If you do this in the ctr, this will set the value before the binding takes place.
3) Again, as long as you make changes to the ObservableCollection the combobox is bound to, it will update the UI. If you make a change to what the SelectedItem is bound to just make sure you call RaisedPropertyChanged event.
ETA: Change the xaml to this, bind to SelectedProduct and SelectedCustomer for the SelectedItem properties
<StackPanel>
<ComboBox Height="25" Width="100" DisplayMemberPath="CustomerCode" SelectedItem="{Binding Path=SelectedCustomer}" ItemsSource="{Binding Path=Customers}"></ComboBox>
<ComboBox Height="25" Width="100" DisplayMemberPath="ProductCode" SelectedItem="{Binding Path=SelectedProduct}" ItemsSource="{Binding Path=Products}"></ComboBox>
</StackPanel>
this should get you started in the right direction, everything, all logic for building customers and products by the customer id needs to happen in your repositories.
public class OrderScreenViewModel : INotifyPropertyChanged
{
private readonly IProductRepository _productRepository;
private readonly ICustomerRepository _customerRepository;
public OrderScreenViewModel(IProductRepository productRepository,
ICustomerRepository customerRepository)
{
_productRepository = productRepository;
_customerRepository = customerRepository;
BuildCustomersCollection();
}
private void BuildCustomersCollection()
{
var customers = _customerRepository.GetAll();
foreach (var customer in customers)
_customers.Add(customer);
}
private ObservableCollection<Customer> _customers = new ObservableCollection<Customer>();
public ObservableCollection<Customer> Customers
{
get { return _customers; }
private set { _customers = value; }
}
private ObservableCollection<Product> _products = new ObservableCollection<Product>();
public ObservableCollection<Product> Products
{
get { return _products; }
private set { _products = value; }
}
private Customer _selectedCustomer;
public Customer SelectedCustomer
{
get { return _selectedCustomer; }
set
{
_selectedCustomer = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));
BuildProductsCollectionByCustomer();
}
}
private Product _selectedProduct;
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
_selectedProduct = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
DoSomethingWhenSelectedPropertyIsSet();
}
}
private void DoSomethingWhenSelectedPropertyIsSet()
{
// elided
}
private void BuildProductsCollectionByCustomer()
{
var productsForCustomer = _productRepository.GetById(_selectedCustomer.Id);
foreach (var product in Products)
{
_products.Add(product);
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
public interface ICustomerRepository : IRepository<Customer>
{
}
public class Customer
{
public int Id { get; set; }
}
public interface IProductRepository : IRepository<Product>
{
}
public class Product
{
}
Here's what the standard IRepository looks like, this is called the Repository Pattern:
public interface IRepository<T>
{
IEnumerable<T> GetAll();
T GetById(int id);
void Save(T saveThis);
void Delete(T deleteThis);
}

Categories