I have the following:
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding items}" DisplayMemberPath="s"/>
<TextBlock Text="{Binding SelectedItem.s}"/>
This is definition of SelectedItem
public MemEntity SelectedItem {get; set;}
MemEntity is a class containing
public String s {get; get;}.
Basically, I want s of the selected item to be shown in the TextBlock (same property as shown in ListBox). This doesn't work, so what am I doing wrong?
Try this,
<TextBlock ... Text="{Binding ElementName=items, Path=SelectedItem.s}" />
then add a name to your ListBox as,
<ListBox x:Name="items" SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding items}" DisplayMemberPath="s"/>
There are multiple way to do this. One option has already been provided in another answer that focusing on achieving the desired functionality by binding to a view element. Here is another option.
The view is unaware that selected item has changed. look into using INotifyPropertyChanged
You can create a base ViewModel to encapsulate the repeated functionality
public abstract class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Have the view models inherit from this base class in order for the view to be aware of changes when binding.
public class ItemsViewModel : ViewModelBase {
public ItemsViewModel() {
items = new ObservableCollection<MemEntity>();
}
private MemEntity selectedItem;
public MemEntity SelectedItem {
get { return selectedItem; }
set {
if (selectedItem != value) {
selectedItem = value;
OnPropertyChanged(); //this will raise the property changed event.
}
}
}
public ObservableCollection<MemEntity> items { get; set; }
}
The view will now be aware when ever the SelectedItem property changes and will update the view accordingly.
Related
So I am trying to bind the following ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListBoxItem> _PlacesOrCities;
public ObservableCollection<ListBoxItem> PlacesOrCities
{
get { return _PlacesOrCities; }
set { _PlacesOrCities = value; RaisePropertyChanged("PlacesOrCities"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModel()
{
_PlacesOrCities = new ObservableCollection<ListBoxItem>();
}
}
To the following xaml:
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=(gms:MainWindow.ViewModel).PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the codebehind as such:
public ViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new ViewModel();
DataContext = ViewModel;
}
And upon firing a button click event- I try to set the values of the observable collection using a in memory list:
private void StateProvince_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_CurrentSelectionPlaces = Canada.Provinces
.FirstOrDefault(x => x.Abbreviation == _SelectedStateProvince_ShortName)
.Place.OrderBy(x => x.Name).ToList();
foreach (var currentSelectionPlace in _CurrentSelectionPlaces)
{
ViewModel.PlacesOrCities.Add(currentSelectionPlace);
}
}
But it seems like none of the items are being added to the collection. Am I binding it incorrectly?
I've tried quite a few solutions but none of them seem to change the result- where no items in the list are being loaded into the collection properly.
EDIT:
It may be worth noting that the ListBoxItem as seen in the ViewModel is a custom model:
public class ListBoxItem
{
[J("Name")] public string Name { get; set; }
[J("PostalCodes")] public string[] PostalCodes { get; set; }
public Visibility Visibility { get; set; } = Visibility.Visible;
}
You should try to fit to the MVVM pattern, so the population of the list should occur at viewmodel level and not in the view's code behind.
You mentioned that you use a click event, instead of doing so, try to bind the command property of the button to a command in the viewmodel, see this link with an explanation of several types of commands and how to use them: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
In the other hand, if you already set the data context in the window constructor, to bind the ListBox items source you only need the name of the property to bind, "PlacesOrCities":
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It would also be recommendable trying to load the items in the list without any template, you can use ListBox DisplayMemberPath property to display the name, and once you are able to load items, apply the style.
Also in the way you use ObservableCollection, you actually need to replace the whole collection instead of adding to fire RaisePropertyChanged, try a normal property instead.
public ObservableCollection<ListBoxItem> PlacesOrCities {get;set;} = new ObservableCollection<ListBoxItem>();
Modifying the collection will update the UI, so whenever you use Add or Clear, the UI should know it.
Hope it helps.
I am on a MVVM C# project.
I want to display a list of objects.
I want to add and remove items in this list and ALSO change items in this list.
So I choosed the BindingList<> over the ObservableCollection<>, which would not get noticed if an item has changed.
(I also tested the ObservableCollectionEx which is out there in the web, but this has the same behavior like the BindingList for me).
But the Listbox is not changing when items are changed.
(Adding and removing items is updated in the Listbox)
In my XAML
<ListBox DisplayMemberPath="NameIndex" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}">
or alternative with the ItemTemplate
<ListBox DockPanel.Dock="Right" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameIndex}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel (ViewModelBase is implementing INotifyPropertyChanged etc)
public class ProfileListViewModel : ViewModelBase
{
private BindingList<Profile> profiles;
public BindingList<Profile> Profiles
{
get
{
return profiles;
}
set
{
profiles = value;
RaisePropertyChanged();
}
}
My items are also implementing INotifyPropertyChanged and I am calling OnPropertyChanged("Name") in my Setters.
My model
public class Profile : INotifyPropertyChanged
{
public Profile(){}
public int ProfileID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wiring the View with the ViewModel (BindingList is initialized before View)
ProfileListViewModel plvw= new ProfileListViewModel(message.Content);
var profileView = new ProfileListView(plvw);
profileView.ShowDialog();
In the View.xaml.cs
public ProfileListView(ProfileListViewModel plvw)
{
InitializeComponent();
DataContext = plvw;
}
When I am changing the name of an object then I get the ListChanged event to which I have subscribted in my ViewModel (Profiles.ListChanged += Profiles_ListChanged;) for testing BUT the items in the ListBox are NOT changing.
What am I doing wrong?
How can I get a updated Listbox?
Since your DisplayIndex is the computed property NameIndex, you need to call OnPropertyChanged("NameIndex") when its value changes due to a change in other properties, e.g.:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameIndex");
}
}
Use
Profiles.ResetBindings() to bind it again.
I have a column class which uses view model base to implement INotifyPropertyChanged (lazy I know):
public class Column : ViewModelBase
{
public string ColumnName { get; set; }
public bool Anonymize { get; set; }
}
And then a list of columns:
public class Columns : ObservableCollection<Column>
{
}
In my view model I have a property columns and I am binding that to a combo box with a checkbox and textblock:
private Columns _tableColumns;
public Columns TableColumns
{
get
{
return _tableColumns;
}
set
{
_tableColumns = value;
OnPropertyChanged("TableColumns");
}
}
<ComboBox Name="cbColumns" ItemsSource="{Binding TableColumns}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Anonymize, Mode=TwoWay}" />
<TextBlock Text="{Binding ColumnName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When I change the Anonymize property through the checkbox on an item, how do make the Columns property change in the view model to reflect this?
Your Column class needs to implement INotifyPropertyChanged (which you say it does). You also need to raise that event it when the value of Anonymize changes (which you don't).
If you want to change the Anonymize property only from the UI, you are done. If you'd like to see the changes(from the backend) on the UI, you have to implement the INotifyPropertyChanged interface in the Column class.
public class Column : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string columnName;
public bool anonymize;
public string ColumnName
{
get { return columnName; }
set
{
columnName = value; RaiseOnPropertyChanged("ColumnName");
}
}
public bool Anonymize
{
get { return anonymize; }
set { anonymize = value; RaiseOnPropertyChanged("Anonymize"); }
}
public void RaiseOnPropertyChanged(string propertyName)
{
var eh = PropertyChanged;
if (eh != null)
eh(this, new PropertyChangedEventArgs(propertyName));
}
}
When the Anonymize state changes it will need to notify the view model that it needs to modify the collection of columns. The way I've solved this before is to add a CheckChanged event to the Column class that is raised when Anonymize. The the view model subscribes to the event after it creates the Column object but it is added to the Columns collection. When CheckChanged is raised the view model adds/removes the item from the Columns collection.
I have a ComboBox with ValueMember="Id" and DisplayMember="Id" and want to display selected item property Name in label, after some value in ComboBox in selected. I'm using some sort of MVVM pattern. So, I'm binding SelectedId to Model and ItemsSource to ViewModel.
(If I put ItemsSource in Model it is easy - using setters and OnPropertyChanged(), but now I have in model a lot of fields which are used only for UI representation)
I've managed to shown selected item's Name using combobox LostFocus event, where I call ViewModel method in which I get property Name using LINQ (from List,
where l.Id=Model.SelectedId).
But, is there a simpler way?
You could bind directly to the combobox for your label:
<ComboBox Name="MyCombo" ItemsSource="{Binding ComboListObjects}" SelectedItem="{Binding ComboSelection}" />
<Label Content="{Binding ElementName=MyCombo, Path=SelectedValue}"/>
However, since you are using a ViewModel, you should probably change things around a bit. There is no SelectedId on Combobox, so I assume you mean SelectedValue. Instead, I suggest creating a property on your ViewModel to hold the SelectedItem. I have included a sample:
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
public List<MyObject> ComboListObjects
{
get{
return new List<MyObject>(); // <-- fill this
}
}
private MyObject _selectedItem = null;
public MyObject ComboSelection
{
get { return _selectedItem; }
set {
_selectedItem = value;
NotifyPropertyChanged("ComboSelection");
}
}
}
View:
<ComboBox Name="MyCombo" ItemsSource="{Binding ComboListObjects}" SelectedItem="{Binding ComboSelection}" />
<Label Content="{Binding ComboSelection.Id}"/>
<Label Content="{Binding ComboSelection.Name}"/>
<Label Content="{Binding ComboSelection.OtherInfo}"/>
If you are going to be using MVVM, avoid using the codebehind, especially events, unless you are writing a custom control.
Hope that helps...
Here is an example of how to bind a combo box to a list of books and display the title of the book on a label using MVVM.
In xaml markup, bind the combobox to your view model using the ItemsSource and SelectedItem properties
<ComboBox Name="cbBook" ItemsSource="{Binding Books}" SelectedItem="{Binding SelectedBook, Mode=TwoWay}" />
<Label DataContext="{Binding SelectedBook}" Content="{Binding Title}" />
In your view, set the DataContext to your view model class
public partial class MyView : UserControl
{
public MyView()
{
InitializeComponent();
DataContext = new BookViewModel();
}
}
The view model should have public properties you'll be binding to.
public class BookViewModel : BaseViewModel
{
public BookViewModel()
{
Books = new ObservableCollection<Book>();
}
public ObservableCollection<Book> Books { get; set; }
private Book _selectedBook;
public Book SelectedBook
{
get { return _selectedBook; }
set
{
_selectedBook = value;
NotifyPropertyChanged(() => SelectedBook);
}
}
}
This base class implements the INotifyPropertyChanged interface. I'm using a lambda expression for type safety.
public abstract class BaseViewModel : INotifyPropertyChanged
{
protected void NotifyPropertyChanged<T>(Expression<Func<T>> expression)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(((MemberExpression)expression.Body).Member.Name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I got a sample mvvm app. The UI has a textbox, a button and a combobox. when I enter something in the textbox and hit the button, the text I enter gets added to an observablecollection. The Combobox is bound to that collection. How do I get the combobox to display the newly added string automaticly?
As I understand correctly, you want to add an item and select it.
Here is the example how it can be done using ViewModel and bindings.
Xaml:
<StackPanel>
<TextBox Text="{Binding ItemToAdd}"/>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
ViewModel:
public class MainViewModel:INotifyPropertyChanged
{
public ObservableCollection<string> Items { get; set; }
public string ItemToAdd { get; set; }
private string selectedItem;
public string SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public void AddNewItem()
{
this.Items.Add(this.ItemToAdd);
this.SelectedItem = this.ItemToAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The MainViewModel has 3 properties (one for the TextBox and two other for the ComboBox) and the method AddNewItem without parameters.
The method can be triggered from a command, but there is no standard class for commands, so I will call it from the code-behind:
((MainViewModel)this.DataContext).AddNewItem();
So you must explicitly set an added item as selected after you add it to a collection.
Because the method OnItemsChanged of the ComboBox class is protected and can't be used.
If the ComboBox is bound to an ObservableCollection, the ComboBox will be updated as soon as the collection is changed.
That's the advantage of using an ObservableCollection - you don't need to do any extra coding to update the UI.
If this is not the behavior you're seeing, perhaps you can post some code/xaml.