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.
Related
I have created a dynamically generated TabControl by binding ItemsSource to MyUnicornsViewModel.
As new items are added to MyUnicornsViewModel... new tab items are created. However, the newly added tabs are not automatically selected in the TabControl.
How can I get new tabs to be selected when they are added?
<TabControl ItemsSource="{Binding MyUnicornsViewModel}" SelectedItem="{Binding SelectedItem}">
<TabControl.ItemTemplate>
<!-- header template -->
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- body template-->
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
At first, I was hoping there was an event for "ItemsChanged" or "ItemAdded" in the TabControl, that way I can set the SelectedIndex in the code-behind as new items are added.
Another thing I tried was to bind the TabControl.SelectedItem to a SelectedItem property in MyUnicornsViewModel. Sadly, that didn't work either.
MyUnicornsViewModel:
public class MyUnicornsViewModel : ObservableCollection<UnicornViewModel>
{
...
private void AddNewUnicorn()
{
var awesomeUnicorn = new UnicornViewModel();
Add(awesomeUnicorn);
SelectedItem = awesomeUnicorn; //I expected my TabControl to have 'awesomeUnicorn' selected.
}
public UnicornViewModel SelectedItem { get; set; }
}
There are a couple of issues here:
It's very odd to derive a "view model" from ObservableCollection. A view model should contain an observable collection.
View models need to implement the INotifyPropertyChanged interface; it's not clear from the code provide if UnicornViewModel implements this interface, however, MyUnicornsViewModel absolutely does not.
Here's some suggestions:
A view model base class that implements the INotifyPropertyChanged interface will really help get you most of the way. You can write your own using the INotifyPropertyChanged documentation or look for an MVVM framework that fits well with your project (e.g. Prism, MVVM Light, ReactiveUI). Each of these will provide a base class to use for view models - BindableBase, ViewModelBase, ReactiveObject respectively for each of the frameworks above.
MyUnicornsViewModel should have:
An ObservableCollection for the collection of unicorns; this will be bound to the ItemsSource property on your TabControl.
The SelectedItem property must fire the PropertyChanged event when set.
Here's a quick sample using Prism:
public sealed class UnicornViewModel : BindableBase
{
public UnicornViewModel(string name, string content)
{
Name = name;
Content = content;
}
// these properties don't change and therefore don't need to raise property changed
public string Name { get; }
public string Content { get; }
}
public sealed class UnicornsViewModel : BindableBase
{
private UnicornViewModel _selectedUnicorn;
public UnicornsViewModel()
{
AddUnicornCommand = new DelegateCommand(AddUnicorn);
ClearUnicornsCommand = new DelegateCommand(ClearUnicorns, () => HasUnicorns).ObservesProperty(() => HasUnicorns);
}
public ObservableCollection<UnicornViewModel> Unicorns { get; } = new ObservableCollection<UnicornViewModel>();
public UnicornViewModel SelectedUnicorn
{
get => _selectedUnicorn;
set => SetProperty(ref _selectedUnicorn, value, () => RaisePropertyChanged(nameof(HasUnicorns)));
}
public DelegateCommand AddUnicornCommand { get; }
public DelegateCommand ClearUnicornsCommand { get; }
private bool HasUnicorns => Unicorns.Any(); // helper property for the clear command's can execute
private void AddUnicorn()
{
Unicorns.Add(new UnicornViewModel($"Unicorn {Unicorns.Count + 1}", Guid.NewGuid().ToString()));
SelectedUnicorn = Unicorns.Last();
}
private void ClearUnicorns()
{
SelectedUnicorn = null;
Unicorns.Clear();
}
}
I have a WPF project with a view model and some nested UI elements. Here is the (relevant section of) XAML:
<UserControl> // DataContext is MyVM (set programmatically)
<TreeView ItemsSource="{Binding Trees}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Subtrees}">
<StackPanel>
<ListView ItemsSource="{Binding Contents}"
SelectedValue="{Binding SelectedContent}" // won't work: Tree has no such property
SelectionMode="Single"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>
Here the code for the ViewModel class:
public class MyVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public IEnumerable<Tree> Trees { get; set; }
private object _selectedContent;
public string SelectedContent
{
get => _selectedContent;
set
{
_selectedContent = value;
OnPropertyChanged();
}
}
}
Here the code for class Tree:
public class Tree
{
public IEnumerable<Tree> Subtrees { get; set; }
public IEnumerable<string> Contents { get; set; }
}
I want to allow only one selection globally for all ListViews. Just like here, I want to bind all ListViews to the property SelectedContent in the view model MyVM.
The problem is that the data context of the ListView is a Tree, and not the MyVM from the top user control. (It should be Tree, since we want to show the Contents.) I know I can bind downwards using SelectedValuePath, but how do I go up instead in order to bind SelectedValue to the MyVM property SelectedContent?
I tried SelectedValue="{Binding RelativeSource={RelativeSource AncestorType ={x:Type UserControl}}, Path=SelectedContent}", but it did not work.
There is a comment here, saying:
Just wanted to note here that if you want to bind to a property in the
DataContext of the RelativeSource then you must explicitly specify it:
{Binding Path=DataContext.SomeProperty, RelativeSource=.... This was
somewhat unexpected for me as a newbie when I was trying to bind to a
parent's DataContext within a DataTemplate.
This comment deserves more attention, so I'll use it as the correct answer.
Try to use
SelectedValue="{Binding RelativeSource={RelativeSource
Mode=FindAncestorBindingContext, AncestorType ={x:Type MyVM}},
Path=SelectedContent}"
Maybe, you'll need to describe your model's namespace in header:
xmlns:viewmodel="clr-namespace:_your_namespace_.ViewModels"
and use
SelectedValue="{Binding RelativeSource={RelativeSource
Mode=FindAncestorBindingContext, AncestorType ={x:Type viewmodel:MyVM}},
Path=SelectedContent}"
So I just setup a project and added a custom UserControl that looks like this.
<Grid>
<ItemsControl ItemsSource="{Binding UserViewModel.Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<controls:UserCard/>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
As you can see I tried binding the Text property buti it doesn't bind.
Now there could be a lot of reasons to why it's behaving like this so I will try to narrow it down.
I've created a BaseViewModel that will hold my ViewModels and it looks like this.
public class BaseViewModel : ObservableObject
{
public UserViewModel UserViewModel { get; set; } = new UserViewModel();
}
And then I've setup my ViewModel like this
public class UserViewModel : ObservableObject
{
public ObservableCollection<User> Users { get; set; } = new ObservableCollection<User>();
public UserViewModel()
{
Users.Add(new User{Name = "Riley"});
Users.Add(new User{Name = "Riley1"});
}
}
Simple, now I do have a ObservableObject that looks like this and deals with the INPC
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And in my MainView.xaml
I've set the DataContext like so
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new BaseViewModel();
}
}
It's the exact same for the UserControl
And this is where I actually add the UserControl so it displays in the MainWindow
<ItemsControl ItemsSource="{Binding UserViewModel.Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<controls:UserCard/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now the issue is that it doesn't bind the Data, I want to display the Name property from the Model but it's not displaying it and I am not sure why, if I try to bind it to a TextBlock property in the MainView directly it works fine.
I am unsure to why it's behaving like this and I would like to understand why.
Do I need to make use of DependencyProperties? Or is it just a case of me creating a new instance of the BaseViewModel? Where did I go wrong?
Your MainViewWindow contains an ItemsControl with the binding ItemsSource="{Binding UserViewModel.Users}", with each item being displayed with a <controls:UserCard/>. But your user control is then trying to bind to the list again with "{Binding UserViewModel.Users}". Why are you trying to display a list inside another list?
I suspect the problem here is that you think your custom UserControl's DataContext is still pointing to the BaseViewModel, like its parent. It isn't. The DataContext of each item in an ItemsControl points to it's own associated element in the list, i.e. an instance of type User.
UPDATED: Let's say you have a main view model with a list of child view models, like this:
public class MainViewModel : ViewModelBase
{
public MyChildViewModel[] MyItems { get; } =
{
new MyChildViewModel{MyCustomText = "Tom" },
new MyChildViewModel{MyCustomText = "Dick" },
new MyChildViewModel{MyCustomText = "Harry" }
};
}
public class MyChildViewModel
{
public string MyCustomText { get; set; }
}
And let's say you set your MainWindow's DataContext to an instance of MainViewModel and add a ListView:
<ListView ItemsSource="{Binding MyItems}" />
If you do this you'll see the following:
What's happening here is that the ListView is creating a container (of type ContentPresenter) for each of the three elements in the list, and setting each one's DataContext to point to its own instance of MyChildViewModel. By default ContentPresenter just calls 'ToString()' on its DataContext, so you're just seeing the name of the class it's pointing to. If you add a ToString() operator to your MyChildViewModel like this:
public override string ToString()
{
return $"MyChildViewModel: {this.MyCustomText}";
}
... then you'll see that displayed instead:
You can also override the ListViewItem's template entirely, and since it already points to its associated instance of MyChildViewModel you can just bind directly to its properties:
<ListView ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- One of these gets created for each element in the list -->
<Border BorderBrush="Black" BorderThickness="1" Background="CornflowerBlue" CornerRadius="5" Padding="5">
<TextBlock Text="{Binding MyCustomText}" Foreground="Yellow" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Which will change the display to this:
Make sense?
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.
I'm very new to MVVM and bindings and I'm trying to learn to work with it.
I run into the problem of binding my viewmodel to the view in particular binding an observable collection to a listbox.
this is what my viewmodel looks like:
namespace MyProject
{
using Model;
public class NetworkViewModel: INotifyPropertyChanged
{
private ObservableCollection<Person> _networkList1 = new ObservableCollection<Person>();
public ObservableCollection<Person> NetworkList1 //Binds with the listbox
{
get { return _networkList1; }
set { _networkList1 = value; RaisePropertyChanged("_networkList1"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public NetworkViewModel()
{
_networkList1 = new ObservableCollection<Person>()
{
new Person(){FirstName="John", LastName="Doe"},
new Person(){FirstName="Andy" , LastName="Boo"}
};
}
}
in the view I have
namespace MyProject
{
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
}
and in the XAML I have
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock >
<Run Text="{Binding Path=FirstName}"/>
<Run Text="{Binding Path=LastName}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It seems like you might have a typo in your view model.
RaisePropertyChanged("_networkList1");
You want to raise the property changed notification for the public property not the private variable.
RaisePropertyChanged("NetworkList1");
This might be preventing your view from updating properly.
In addition to Gaurav answer, if _networkList1 is a private field in your NetworkViewModel class, how is it possible to get access to it in Networking window? I mean what's the meaning of the following line?
lb1.ItemsSource = _networkList1;
when you define a Property (NetworkList1), you have to use it in order to get advantages of its features (e.g. to get RaisePropertyChanged working). Otherwise what's the point, you could have just defined a field (_networklist1). So changing
_networkList1 = new ObservableCollection<Person>()
to
NetworkList1 = new ObservableCollection<Person>()
results in actually setting NetworkList1 and therefore RaisePropertyChanged("NetworkList1") to be fired. (however if you want to just show data in a your listbox this is unnecessary)
and if i'm getting it right, changing this:
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
to
public partial class Networking : Window
{
public NetworkViewModel MyViewModel { get; set; }
public Networking()
{
InitializeComponent();
MyViewModel = new NetworkViewModel();
this.DataContext = MyViewModel;
}
}
should get your binding to work.
*Note that when you set DataContext to NetworkViewModel, then the binding in
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
works, because NetworkList1 is a Property of NetworkViewModel.
Do not call RaisePropertyChanged() method on ObservableCollection<T>, for god's sake. This is a common mistake in a majority of cases (however, there are cases, where you need to reset ObservableCollection<T> using new keyword, but they are kinda rare).
This is a special type of collection which notifies UI internally about all the changes of its content (like add, remove etc.). What you need is to set the collection using new keyword once in a lifetime of your ViewModel, and then manipulate your items via Add(T item), Remove(T item), Clear() methods etc.
and UI will get notified about it and updated automatically.