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.
Related
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.
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.
Quick question..
I have a List of objects of this class:
public class Whatever
{
public string Name { get; set; }
public List<blaBla> m_blaBla { get; set; }
// ..
}
And I want to link the List<Whatever> to a ComboxBox, where the user sees the Name of each Whatever object. How can I do that?
You could either use ComboBox.ItemTemplate like this:
C#:
List<Whatever> lst = new List<Whatever>();
public MainWindow()
{
InitializeComponent();
cmb.ItemsSource = lst;
}
XAML:
<ComboBox Name="cmb">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Or use DisplayMemberPath:
<ComboBox Name="cmb" DisplayMemberPath="Name">
</ComboBox>
Or just override the ToString() function and it will do the job for you:
public class Whatever
{
public string Name { get; set; }
public List<blaBla> m_blaBla { get; set; }
// ..
public override string ToString()
{
return Name;
}
}
And then:
List<Whatever> MyList = new List<Whatever>();
public MainWindow()
{
InitializeComponent();
MyComboBox.ItemsSource = MyList;
}
Create a Viewmodel:
public ObservableCollection<Whatever> WhCol
{
get { return this.Name; }
set { }
}
And then a Matching View
<ComboBox DisplayMemberPath="Name" ItemsSource="{Binding WhCol}" />
According to Model-View-Modelview Pattern
This is more suited if you wan't to make changes based on user input. (Which is kinda rare for a combobox).
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);
}
}
i have a class
class Names {
public int id get; set;};
public string name {get ; set};
public string til {set{
if (this.name == "me"){
return "This is me";
}
}
i have a list (ListNames) which contains Names added to it and binding with a combo box
<ComboBox SelectedValue="{Binding Path=id, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding
ListNames}" DisplayMemberPath="name" SelectedValuePath="id" />
every thing works
i want to display the "Tip" on another label field when user selects an item.
is it possible?
help!
I'm assuming you're using MVVM.
You need to create in your window's viewmodel, a property "CurrentName" of type "Names", binded to the ComboBox SelectedItem property.
This property must raise the NotifyPropertyChanged event.
Then, bind your label field, to this "CurrentName" property.
When the SelectedIem property will change on the combobox, your label field will then be updated.
Something like this:
Your Model:
public class Names
{
public int Id { get; set; }
public string Name { get; set; }
public string Tip {
get
{
return Name == "me" ? "this is me" : "";
}
}
}
Your ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Names> _namesList;
private Names _selectedName;
public ViewModel()
{
NamesList = new ObservableCollection<Names>(new List<Names>
{
new Names() {Id = 1, Name = "John"},
new Names() {Id = 2, Name = "Mary"}
});
}
public ObservableCollection<Names> NamesList
{
get { return _namesList; }
set
{
_namesList = value;
OnPropertyChanged();
}
}
public Names SelectedName
{
get { return _selectedName; }
set
{
_selectedName = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And finally your View:
<Window x:Class="Combo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Combo="clr-namespace:Combo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<Combo:ViewModel/>
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Path=NamesList}" DisplayMemberPath="Name"
SelectedValue="{Binding Path=SelectedName}"/>
<Label Content="{Binding Path=SelectedName.Name}"/>
</StackPanel>