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).
Related
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}" />
I have a Datagrid with a list binded to ItemsSource and the SelectedItem is binded a single object of this list. My ViewModel implements INotifyPropertyChanged.
The binding works fine, except when there's a variable (canSelectOtherObject = false) that prevents myObject of changing it's value. Even thought myObject doesn't modify it's value, the datagrid on the View selects other object. How can I prevent this?
View:
<DataGrid ItemsSource="{Binding MyObjectList}" SelectedItem="{Binding MyObjectSelected, Mode=TwoWay}">
ViewModel:
private ObservableCollection<MyObject> myObjectList;
private MyObject myObjectSelected;
private bool canSelectOtherObject;
public ObservableCollection<MyObject> MyObjectList
{
get { return myObjectList; }
set { myObjectList = value; }
}
public MyObject MyObjectSelected
{
get { return myObjectSelected; }
set
{
if(canSelectOtherObject)
{
myObjectSelected = value;
OnPropertyChanged("MyObjectSelected");
}
}
}
Thanks!
INotifyPropertyChanged is used to notify the UI to update bindings when the properties of an object change, I think you are describing a situation where the object itself changes.
Given your binding:
<DataGrid ItemsSource="{Binding MicrophoneList}" SelectedItem="{Binding MicrophoneSelected, Mode=TwoWay}">
It's the difference between updating one of the properties of the selected microphone (would require INotifyPropertyChanged), and changing SelectedItem to a different microphone altogether (binding updates whether you notify or not).
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.
I have a combobox and a listbox in a WPF window.
The combobox's itemssource is set to a List of all Team objects. Team has 2 properties (TeamId and TeamName).
The listbox's itemssource is set to a List of all Player objects. Player on of Players properties is TeamId.
I would like to filter the list of Players in the Listbox to only show those Players whose TeamId matches the TeamId of the SelectedItem in my combobox.
I would prefer to do this all in XAML but I'm not really sure on what the correct way to do it in C# would be either. Any help would be appreciated.
I'm not sure you can do it entirely in xaml, i think you might need a tiny bit of work somewhere else. This is how i did it for something else.
Wrap your collection with a CollectionViewSource in your xaml (this makes one that has a sort on a specific property name):
<CollectionViewSource x:Key="ViewName" Source="{Binding YourBinding}">
<CollectionViewSource.SortDescriptions>
<comp:SortDescription PropertyName="Name" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
somewhere else, bind your listview to have this source as the itemssource:
<ListView x:Name="MyList" ItemsSource="{Binding Source={StaticResource ViewName}}" />
then somewhere in code, i have mine on a textbox property change listener, but you get the general idea. the ICollectionView interface has a filter member that you can use to filter things out.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var text = FilterTextBox.Text;
var source = MyList.Items as ICollectionView;
if (string.IsNullOrWhiteSpace(filter))
{
source.Filter = null;
}
else
{
source.Filter = delegate(object item)
{
var s = item as INamedItem;
return s.Name.IndexOf(filter, StringComparison.CurrentCultureIgnoreCase) != -1;
};
}
}
Firstly, change all your bound collections to ObservableCollection.
Then, on the combobox, bind the SelectedValue to another property on your DataContext of type Team (you have implemented INotifyPropertyChanged right?).
When the SelectedValue changes, refresh the ListBox's bound collection with a filtered list from the collection of all players:
public ObservableCollection<Team> Teams { get;set;}
public ObservableCollection<Player> Players { get;set;}
private List<Player> AllPlayers {get;set}
public Team CurrentTeam
{
get
{
return this._currentTeam;
}
set
{
this._currentTeam = value;
this.Players = new ObservableCollection(this.AllPlayers.Where(x => x.TeamId = this._currentTeam.TeamId));
RaisePropertyChanged("CurrentTeam");
}
}
This is the quickest and easist way to do it. You could probably achieve this through CollectionView, but I think this is simpler to understand.
I have this ListBox which is bound to an ObservableCollection. Each object in the list implements an interface called ISelectable
public interface ISelectable : INotifyPropertyChanged
{
event EventHandler IsSelected;
bool Selected { get; set; }
string DisplayText { get; }
}
I want to keep track of which object is selected regardless of how it is selected. The user could click on the representation of the object in the ListBox but it could also be that an object is selected via code. If the user selects an object via the ListBox I cast the the selected item to an ISelectable and set the Selected property to true.
ISelectable selectable = (ISelectable)e.AddedItems[0];
selectable.Selected = true;
My problem is that when I select the object using code I can't get ListBox to change the selected item. I'm using a DataTemplate to show the selected object in a different color which means everything is displayed correctly. But the ListBox has the last object the user clicked as the SelectedItem which means that item can't be clicked without first selecting another object in the list.
Anyone got any idea on how to solve this? I pretty sure I can accomplish what I want by writing some custom code to handle the Mouse and Keyboard events but I rather not. I have tried adding a SelectedItem property to the collection and bind it to the ListBox's SelectItemProperty but no luck.
You could also accomplish this by data binding ListBoxItem.IsSelected to your Selected property. The idea is to set the binding for each of the ListBoxItems as they are created. This can be done using a style that targets each of the ListBoxItems generated for the ListBox.
This way when an item in the ListBox is selected/unselected, the corresponding Selected property will be updated. Likewise setting the Selected property in code will be reflected in the ListBox
For this to work the Selected property must raise the PropertyChanged event.
<List.Resources>
<Style TargetType="ListBoxItem">
<Setter
Property="IsSelected"
Value="{Binding
Path=DataContext.Selected,
RelativeSource={RelativeSource Self}}"
/>
</Style>
</List.Resources>
Have you looked at the list box's SelectedItemChanged and SelectedIndexChanged events?
These should be triggered whenever a selection is changed, no matter how it is selected.
I think you should fire the propertyChanged event when the select has changed. Add this code to the object that implements ISelectable. You'll end up with something like:
private bool _Selected;
public bool Selected
{
get
{
return _Selected;
}
set
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Selected"));
_Selected = value;
}
}
I've tried the folowing code:
public ObservableCollection<testClass> tests = new ObservableCollection<testClass>();
public Window1()
{
InitializeComponent();
tests.Add(new testClass("Row 1"));
tests.Add(new testClass("Row 2"));
tests.Add(new testClass("Row 3"));
tests.Add(new testClass("Row 4"));
tests.Add(new testClass("Row 5"));
tests.Add(new testClass("Row 6"));
TheList.ItemsSource = tests;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
tests[3].Selected = true;
TheList.SelectedItem = tests[3];
}
where testClass implements ISelectable.
The is a piece of xaml, nothing fancy:
<ListBox Grid.Row="0" x:Name="TheList"></ListBox>
<Button Grid.Row="1" Click="Button_Click">Select 4th</Button>
I hope this helps.