In my project I want to display a List on a user control. For that I have a CategoryView the user control with an ListView-control, where I want to display the List. And the CategoryViewModel. On the ViewModel I have a list - property where I also raise the property changed event.
public class CategoryViewModel : NotificationObject
{
private List<string> categoryList;
public List<string> CategoryList
{
get
{
return this.categoryList;
}
set
{
this.categoryList = value;
this.RaisePropertyChanged("CategoryList");
}
}
}
This List is binded to the ListView-element in the view.
If I change the List in the CategoryViewModel, it works fine and the property change event is raised. If I change the List from the MainWindowViewModel. No property Changed event is Raised and the View will not be updated. How do I have to do that?
On the MainWindowViewModel I change the CategoryList. The List will be filled correctly.
CategoryViewModel categoryViewModel = new CategoryViewModel();
categoryViewModel.CategoryList = logger.ReadLogfile(this.logFileName).ToList();
You seem to be somewhat confused. You have one ListView in your CategoryView UserControl. Its ItemsSource property can only be data bound to one collection, so clearly, when changing the collections in the main view model and the CategoryViewModel, only one will affect the ListView.
It seems from your code that the CategoryViewModel is set as the DataContext for the UserControl, so the collection in the main view model will not be connected to the ListView. If you want to data bind from the ListView to the collection in the main view model instead, then you'll need to use a RelativeSource Binding instead:
<ListView ItemsSource="{Binding SomeCollection, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourParentType}}}" ... />
Even so, now the collection in your CategoryViewModel will no longer be connected, so you'd better decide exactly what you want to do here.
Related
I have a ListView with items and when user clicks on an item, I want in other ListView to appear a list of other items, and those other items depend on first ListView's selected item's ID.
I have a ServiceManager class, which recieves objects from server asynchronously and passes them to DataManager class, which requires objects via ServiceManager when necessary and stores those objects.
Usually I would bind those objects in XAML like this: ItemsSource="Binding Instance.MyObjects, Source={StaticResource DataManager}", but this time I have to pass that ID as parameter to ServiceManager method.
So how do I update second ListView on first ListView's SelectionChanged event?
You can create CurrentItem property in ServiceManager:
public MyItemType CurrentItem
{
get {
return _CurrentItem;
}
set {
_CurrentItem = value;
if(_CurrentItem != null)
MyObjects = LoadMyObjects(_CurrentItem.ID);
else
MyObjects = null;
}
}
In CurrentItem property setter load data for second ListView. Bind CurrentItem property of the first ListView:
<ListView ItemsSource="{Binding DataManager.Items}" SelectedItem="{Binding DataManager.CurrentItem}" />
In this way when SelectedItem of the first ListView is changed the property setter of CurrentItem property is called and MyObjects is updating. (You should implement INotifyPropertChanged for ServiceManager and call PropertyChnaged in MyObjects property setter).
In my app I have a lot of ComboBox with item list predefined by users. I do not want add all lists on my ViewModel (maybe I'm wrong). I prefer to add an additional parameter to the MyComboBox control (new control which inherit from the ComboBox) with the id of list which I want load from database. E.g.:
<MyComboBox ItemsSourceId = "SAMPLE_ID" SelectedItem = "{Binding valueCode}" />
In behind code I will execute query and bind results to ItemSource.
SELECT itemCode, itemValue FROM UserDictionaries WHERE itemListCode = 'SAMPLE_ID'
It is good or bad idea? Maybe you have some sample code? ;)
Advantages of the solution: cleaner ViewModel. Disadvantages: database context in control.
Why don't you want to put it in the ViewModel? This is exactly what the ViewModel is for. If you were to have it on the code-behind of your custom ComboBox, this will defeat the purpose of MVVM, as the ComboBox is now dependent on the data-base. What you should strive for is to have 'loosely coupled' components, and let the ViewModel feed the data to the View.
You can do something like:
public class ViewModel
{
private _itemSource;
public List<Entity> ItemSource
{
get { return _itemSource; }
private set
{
_itemSource = value;
RaisePropertyChanged("ItemSource");
}
}
private void UpdateItemSource(int sampleId)
{
var newItems = SELECT itemCode, itemValue FROM UserDictionaries WHERE itemListCode = 'SAMPLE_ID';
ItemSource = null;
ItemSource = newItems;
}
}
and then update your XAML to:
<ComboBox ItemSource="{Binding ItemSource, UpdateSourceTrigger=PropertyChanged}" SelectedItem = "{Binding valueCode}" />
Description:
I have some View having its DataContext is already set to some List.
I also have in there a ComboBox that should trigger a Visibility event to a StackPanel. It is done through a property "SelectedVisibility" that is implementing a INotifyPropertyChanged.
Issue:
The property "SelectedVisibility" isn't part of the DataContext but in a ViewModel class and I cannot find any way to explicitely bind my ViewModel to reach that property.
Question:
Would you know how I could explicitely define my VM to be the DataContext of the SelectedValue binding in my ComboBox?
Code details:
View XAML:
<ComboBox ItemsSource="{Binding Source={StaticResource VisibilityEnum}}" SelectedValue="{Binding Path=SelectedVisibility}"/>
<StackPanel Visibility="{Binding Path=SelectedVisibility,Converter={StaticResource SelectedValueToVisible}}">
View Code behind:
public Counterparties_UserInputs()
{
// Cannot bind this as already bound
// this.DataContext = _VM;
InitializeComponent();
}
View Model:
public event PropertyChangedEventHandler PropertyChanged;
public string SelectedVisibility
{
get
{
return _selectedVisibility;
}
set
{
_selectedVisibility= value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedVisibility"));
}
}
}
Thank you in advance!
You can add a new dependancy property to your view, bind your view model to this property and then use this property as DataContext for your StackPanel and ComboBox. For example ("this" is the name of your view, "AdditionalContext" is the dependancy property you declare to store your viewmodel):
<StackPanel DataContext="{Binding AdditionalContext, ElementName=this}" Visibility="{Binding Path=SelectedVisibility, Converter={StaticResource SelectedValueToVisible}}"/>
However you should not do it, since it is a violation of MVVM pattern. The whole point of viewmodel is that you use it as DataContext for your view. The correct approach to your issue is to move List declaration to your viewmodel.
I have a simple combobox with a checkbox inside as such:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="158,180,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding collection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}"></CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The datacontext is simply the code behind, and to test it I use the following code:
public ObservableCollection<Foo> collection { get; set; }
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
}
When I set the ItemsSource as I have in the code, then it works fine, but I want to set the ItemsSource in the Xaml, however it does not work using the Xaml above. I have also tried setting it to Path = "". Anybody know why?
Thanks
You need to assign DataContext to the control. something like:
var window = new Window1();
window.DataContext = new WindowDC();
window.Show();
where Window1 class contains the combobox, and WindowDC is like:
public class WindowDC
{
public ObservableCollection<Foo> collection { get; set; }
}
That's how this will work.
What you actually do is that you place collection into control class, and set your datacontext for combobox only.
But, for testing purposes, you can still set Combox.Datacontext in control constuctor.
Bindings in WPF always have a Source. If you don't specify the source in the binding itself, then it will implicitly use the DataContext of the control or an ancestor of it. So if you want to bind to properties in your codebehind file, you have to set the DataContext to an object of the class which contains the collection property. In your case this is the instance of the Window (this).
DataContext = this;
As the commentor pointed out, it's not considered good style putting business logic or data inside the code behind file. So consider writing a separate class which contains your collection property and which you can use to initalize your DataContext. If you are writting bigger applications you should take a look at patterns like MVVM, which uses databinding to provide a better separation between your view and your model.
Edit: Changed ordering and incorporated feedback
Make sure there exist a public property collection in your code behind.
in the code behind also do this.DataContext = this
Finally implement INotifyPropertyChanged to tell the view that you have changed the collection once you add items in it
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
collection = new ObservableCollection<Foo>();
//this.comboBox1.ItemsSource = collection;
Foo f = new Foo("DSD");
collection.Add(f);
OnPropertyChanged("Collection");
}
It is working when you are setting combo's item source in code behind because the source of combo is getting updated like wise to set the item source in XAML you have to make a property with INotifyPropertyChanged that keep update the combo's itemsource every time you update your collection via this property..
private ObservableCollection<Foo> _Collection;
public ObservableCollection<Foo> Collection
{
get
{
return collection;
}
set
{
collection = value;
OnPropertyChanged("Collection");
}
Now as you are filling collection on button click you just have to set that collection in the property as..
_Collection = new ObservableCollection<Foo>();
Foo f = new Foo("DSD");
_Collection .Add(f);
Collection = _Collection ; //here property call OnPropertyChange
like wise you can provide data to any control. It is jsut the game of INotifyPropertyChanged property.
Hope this will help you
On my journey to learning MVVM I've established some basic understanding of WPF and the ViewModel pattern. I'm using the following abstraction when providing a list and am interested in a single selected item.
public ObservableCollection<OrderViewModel> Orders { get; private set; }
public ICollectionView OrdersView
{
get
{
if( _ordersView == null )
_ordersView = CollectionViewSource.GetDefaultView( Orders );
return _ordersView;
}
}
private ICollectionView _ordersView;
public OrderViewModel CurrentOrder
{
get { return OrdersView.CurrentItem as OrderViewModel; }
set { OrdersView.MoveCurrentTo( value ); }
}
I can then bind the OrdersView along with supporting sorting and filtering to a list in WPF:
<ListView ItemsSource="{Binding Path=OrdersView}"
IsSynchronizedWithCurrentItem="True">
This works really well for single selection views. But I'd like to also support multiple selections in the view and have the model bind to the list of selected items.
How would I bind the ListView.SelectedItems to a backer property on the ViewModel?
Add an IsSelected property to your child ViewModel (OrderViewModel in your case):
public bool IsSelected { get; set; }
Bind the selected property on the container to this (for ListBox in this case):
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
IsSelected is updated to match the corresponding field on the container.
You can get the selected children in the view model by doing the following:
public IEnumerable<OrderViewModel> SelectedOrders
{
get { return Orders.Where(o => o.IsSelected); }
}
I can assure you: SelectedItems is indeed bindable as a XAML CommandParameter
There is a simple solution to this common issue; to make it work you must follow ALL the following rules:
Following Ed Ball's suggestion, on your XAML command databinding, define the CommandParameter attribute BEFORE the Command attribute. This a very time-consuming bug.
Make sure your ICommand's CanExecute and Execute methods have a parameter of type object. This way you can prevent silenced cast exceptions that occur whenever the databinding's CommandParameter type does not match your Command method's parameter type:
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)
{
// Your code goes here
}
private bool OnDeleteSelectedItemsExecute(object SelectedItems)
{
// Your code goes here
}
For example, you can either send a ListView/ListBox's SelectedItems property to your ICommand methods or the ListView/ListBox itself. Great, isn't it?
I hope this prevents someone from spending the huge amount of time I did to figure out how to receive SelectedItems as a CanExecute parameter.
One can try creating an attached property.
Doing so will save one from adding the IsSelected property for each and every list you bind. I have done it for ListBox, but it can be modified for use a in a list view.
<ListBox SelectionMode="Multiple"
local:ListBoxMultipleSelection.SelectedItems="{Binding SelectedItems}" >
More info: WPF – Binding ListBox SelectedItems – Attached Property VS Style .
If you're using MVVM-LIGHT you can use this pattern:
https://galasoft.ch/posts/2010/05/handling-datagrid-selecteditems-in-an-mvvm-friendly-manner
Not especially elegant but looks like it should be reliable at least