I need help. I am kind of new in MVVM
I've got event datagrid selecteditem event in MVVM. After creating a source for grid items(void search) I need populate combobox source as a filter values based on main collection. I used(before MVVM) to work with event subscribing, but can not achieve it here. If I put OnPropertChanged("FamilyFilter") at the end of main function combobox selectionchanged event start working immediately. Here is the code:
public IList<Object> somelist { get; set; }
public CollectionView Items { get; set; }
private string _selectedFamily;
public string SelectedFamily
{ get { return _selectedFamily; }
set { Items = <<filteredcollection>>; OnPropertyChanged("SelectedFamily"); }
}
private List<string> _familyEntries;
public List<string> FamilyEntries
{ get { return _familyEntries; }
set { _familyEntries = value; OnPropertyChanged("FamilyEntries");}
}
public void search()
{
Items = (CollectionView)CollectionViewSource.GetDefaultView(somelist);
_familyEntries = somelist.GroupBy(x => x.Family).Select(z => z.First().Family).ToList();
OnPropertyChanged("Items");
OnPropertyChanged("FamilyEntries");
}
<ComboBox ItemsSource="{Binding FamilyEntries}" SelectedValue="{Binding SelectedFamily}"/>
Related
I am trying to change the selected item of a combo box based on a change in another combo box. The situation is complicated by the fact that both combo boxes appear in a list of item templates.
The XAML is as follows:
<ListBox ItemsSource="{Binding AncillaryExtendedPropertyViewModels}" ItemTemplateSelector="{StaticResource templateSelector}"/>
<DataTemplate x:Key="EnumDataTemplate"> <Grid Margin="4"
MinHeight="25"> <ComboBox SelectedItem="{Binding ExtendedPropertyEnum,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
ItemsSource="{Binding ExtendedPropertyEnumList}"
DisplayMemberPath="Value"/> </Grid> </DataTemplate>
The data context of the view containing the XAML is set to AncillaryBaseViewModel. The following is a cut down version of AncillaryBaseViewModel.
public class AncillaryBaseViewModel : ComplexOrderItemViewModel, IDataErrorInfo
{
private ObservableCollection<ExtendedPropertyViewModel> _ancillaryExtendedPropertyViewModels;
public ObservableCollection<ExtendedPropertyViewModel> AncillaryExtendedPropertyViewModels
{
get { return _ancillaryExtendedPropertyViewModels; }
set
{
_ancillaryExtendedPropertyViewModels = value;
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
}
and the ExtendedPropertyViewModel class....
public class ExtendedPropertyViewModel : DataTemplateSelector
{
private ExtendedProperty _extendedProperty;
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{ return ExtendedProperty.ExtendedPropertyEnum; }
set
{
if (ExtendedProperty.ExtendedPropertyEnum != value)
{
_extendedProperty.ExtendedPropertyEnum = value;
AncillaryBaseViewModel parent = RequestParent();
if (parent != null)
{
parent.AncillaryExtendedPropertyViewModels[7].ExtendedPropertyEnum =
ExtendedProperty.ExtendedPropertyEnum.DependentExtendedPropertyEnums[0];
}
parent.OrderItem.SetStockCode();
PropertyChanged += parent.OnExtendedPropertyChanged;
OnPropertyChanged();
}
}
}
and the ExtendedProperty class....
public class ExtendedProperty : ViewModelBase
{
private ExtendedPropertyEnum _extendedPropertyEnum;
public int ExtendedPropertyID { get; set; }
public int OrderItemTypeID { get; set; }
public string DisplayName {get; set;}
public ObservableCollection<ExtendedPropertyEnum> ExtendedPropertyEnumList { get; set; }
public string Value { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{
return _extendedPropertyEnum;
}
set
{
_extendedPropertyEnum = value;
OnPropertyChanged("ExtendedPropertyEnum");
}
}
}
To summarise, when I change the value of combo box A through the UI, this calls the ExtendedPropertyEnum setter within ExtendedPropertyViewModel, which changes the ExtendedPropertyEnum bound to another combo box B, which is in the same list. I would expect this to update the value displayed in combo box B accordingly, which it does not.
As an aside, changing the value of combo box A does update a label that is not within a data template. The XAML for this label is....
<Label Content="{Binding StockCode}" MinWidth="100"/>
This is updated by the following code within AncillaryBaseViewModel....
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
}
I thought I could change this to the following to force the combo boxes in the list to update.
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
However, this did not work either.
Any help greatly appreciated.
Roger.
if I understand the question correctly then you are expecting a changed value within an observablecollection to be reflected within the UI. This will not happen. observablecollections raise notifyproperty events when the collection itself changes, not when values within them change. You'll either need to raise a notifyproperty event on the value changing or remove the item from the list and add it back with a new value.
I'm building a WPF application and my UI consists of combobox and about a dozen other UI controls. I have a single business object class that contains a dozen or so properties and implements INotifyPropertyChanged.
Here's a snippet of my business object:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
private int _idNumber;
public int IdNumber
{
get { return _idNumber; }
set
{
if (_idNumber == value)
{
return;
}
_idNumber = value;
OnPropertyChanged(new PropertyChangedEventArgs("IdNumber"));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
private int _age;
public int Age
{
get { return _age; }
set
{
if (_age == value)
{
return;
}
_age = value;
OnPropertyChanged(new PropertyChangedEventArgs("Age"));
}
}
private string _address;
public string Address
{
get { return _address; }
set
{
if (_address == value)
{
return;
}
_address = value;
OnPropertyChanged(new PropertyChangedEventArgs("Address"));
}
}
private bool _employed;
public bool Employed
{
get { return _employed; }
set
{
if (_employed == value)
{
return;
}
_employed = value;
OnPropertyChanged(new PropertyChangedEventArgs("Employed"));
}
}
public MyBusinessObject(int idNumber)
{
this.IdNumber = idNumber;
// set default values here
}
}
As you might expect, the various UI controls will be bound to the properties of my business object. However, I need to create an array or list of business objects (10 of them to be specific) and bind my combobox to the IdNumber property. So my user will select the object that they want from the combobox and then the other UI controls should update to display the values for each of their bound properties for the selected object.
Right now, I just have one instance of my business object declared in my code behind like this:
public partial class MainWindow : Window
{
// this will be replaced with an array/list of business objects
MyBusinessObject myObject = new MyBusinessObject(1234); // 1234 = IdNumber
public MainWindow()
{
InitializeComponent();
this.DataContext = myObject;
}
}
And currently, my combobox is defined like this:
<ComboBox x:Name="selectedObjectComboBox" IsEditable="False"/>
Once I implement my array/list of business objects, can anyone tell me how I would bind the combobox to the array so that it will display the IdNumber for each object? Also, what, if anything, will I need to do to get the other bound controls to reflect the values of the selected object when the user changes their selection in the combobox?
You need to bind your ComboBox to your list, then use the DisplayMemberPath to specify which member you want displayed:
<ComboBox ItemsSource={Binding yourList} DisplayMemberPath="IdNumber"/>
If you want your other controls to update off of this value, you might want to consider making a SelectedItem property on your view model and bind the selected item to this. Then your other controls can bind to this.
EDIT
That can be achieved by doing something like:
<ComboBox ItemsSource={Binding yourList} SelectedItem={Binding SelectedBusinessObject} DisplayMemberPath="IdNumber"/>
If you don't want to make a backing field, you could do something like..
<ComboBox ItemsSource={Binding yourList} x:Name="BusinessComboBox" DisplayMemberPath="IdNumber"/>
<MyControl Item={Binding SelectedItem, ElementName=BusinessComboBox />
So I'm trying to learn the MVVM design patter in WPF, I want to do the following:
In external class I've got a ObservableCollection _students that is bound to a listview on the WPF window using MVVM design pattern. The listview shows only the Student's name and Age.
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Course { get; set; }
public DateTime JoiningDate { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Student> _students;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<Student> Students
{
get
{
return _students;
}
set
{
_students = value;
NotifyPropertyChanged("Students");
}
}
All good, but I want to put a TextBox and set it to show the listview's selected item's course property. This means I must
get the listview's selected index (ok)
bind the textbox.Text property to Students[that index].Course
I'm stuck at 2. Any help?
i would solve this by another way.
Take a look at this post
.
Another way would be that your ViewModel contains a Student-property(e.g. SelectedStudent) which is bind to the SelctedItem of the listView. Then you can handel this by
{Binding Path=SelectedStudent.Course}
Assume you bind the listview to a collection of type SampleData like below:
SampleData.cs
public class SampleData
{
public int Id { get; set; }
public string Text { get; set; }
public decimal Value { get; set; }
}
Then you bind the ListView ItemsSource to a collection. WIt does not matter if you bind ItemsSource property to a property on ViewModel or you bind it in code-behind like the code below.
var source = new List<SampleData>();
source.Add(new SampleData() { Id = 1, Text = "AAA" });
source.Add(new SampleData() { Id = 2, Text = "BBB" });
source.Add(new SampleData() { Id = 3, Text = "CCC" });
source.Add(new SampleData() { Id = 4, Text = "DDD" });
source.Add(new SampleData() { Id = 5, Text = "EEE" });
You can bind TextBox's Text property to the SelectedItem directly on the View.
<StackPanel Orientation="Horizontal">
<ListView x:Name="listView1" />
<TextBox Text="{Binding ElementName=listView1, Path=SelectedItem.Text}" />
</StackPanel>
I need to update the list of downloads when the progress has been changed.
XAML:
<ListView ItemsSource="{Binding UiList}" x:Name="MyListView">
<ListView.View>
<GridView>
<GridViewColumn Header="Title"/>
<GridViewColumn Header="Progress"/>
</GridView>
</ListView.View>
</ListView>
ViewModel that creates an instance of Logic class and updates the content of ListView:
class MainWindowViewModel : ViewModelBase
{
private readonly Logic _logic;
public List<Model> UiList { get; set; }
public MainWindowViewModel()
{
_logic = new Logic();
_logic.Update += LogicUpdate;
Start = new RelayCommand(() =>
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) => _logic.Start();
worker.RunWorkerAsync();
});
}
void LogicUpdate(object sender, EventArgs e)
{
UiList = _logic.List;
RaisePropertyChanged("UiList");
}
public ICommand Start { get; set; }
}
Logic:
public class Logic
{
readonly List<Model> _list = new List<Model>();
public event EventHandler Update;
public List<Model> List
{
get { return _list; }
}
public void Start()
{
for (int i = 0; i < 100; i++)
{
_list.Clear();
_list.Add(new Model{Progress = i, Title = "title1"});
_list.Add(new Model { Progress = i, Title = "title2" });
var time = DateTime.Now.AddSeconds(2);
while (time > DateTime.Now)
{ }
Update(this, EventArgs.Empty);
}
}
}
The code above would not update UI. I know two way how to fix this:
In xaml codebehind call: Application.Current.Dispatcher.Invoke(new Action(() => MyListView.Items.Refresh()));
In ViewModel change List<> to ICollectionView and use Application.Current.Dispatcher.Invoke(new Action(() => UiList.Refresh())); after the list has been updated.
Both ways cause the problem: the ListView blinks and Popup that should be open on user demand always closes after each "refresh":
<Popup Name="Menu" StaysOpen="False">
I can replace the popup with another control or panel, but I need its possibility to be out of main window's border (like on screen). But I believe that WPF has another way to update the content of ListView (without blinks).
PS: Sorry for long question, but I do not know how to describe it more briefly...
I think the reason this line doesn't work:
RaisePropertyChanged("UiList");
Is because you haven't actually changed the list. You cleared it and repopulated it, but it's still the reference to the same list. I'd be interested to see what happens if, instead of clearing your list and repopulating, you actually created a new list. I think that should update your ListView as you expected. Whether or not it has an effect on your popup, I don't know.
I've found the answer here: How do I update an existing element of an ObservableCollection?
ObservableCollection is a partial solution. ObservableCollection rises CollectionChanged event only when collection changes (items added, removed, etc.) To support updates of existent items, each object inside the collection (Model class in my case) must implement the INotifyPropertyChanged interface.
// I used this parent (ViewModelBase) for quick testing because it implements INotifyPropertyChanged
public class Model : ViewModelBase
{
private int _progress;
public int Progress
{
get { return _progress; }
set
{
_progress = value;
RaisePropertyChanged("Progress");
}
}
public string Title { get; set; }
}
I have a static collection of items (say numbers from 1 to 100) that I'm presenting in a ComboBox. I can bind these items to the ComboBox no problem.
My question is how to bind a second ComboBox to a subset of those items. The behavior I want is to have the second ComboBox bound to the subset of items remaining after the first ComboBox is selected. For example the first ComboBox would show 1,2,3...,100. If the number 43 is selected in the first ComboBox then the second ComboBox should show 44,45,...,100.
How can this be accomplished and have the second ComboBox update if the first is changed without a lot of code-behind?
I would do this with a using MVVM pattern.
Create a class that implements INotifyChange and expose three Property.
ICollection FullCollection
int FirstIndex
ICollection PartialCollection
Use this class as DataContext for your Control and bind SelectedIndex of the first combo box to FirstIndex property, ItemSource of first combobox to FullCollection and ItemSource of second collection to PartialCollection (Be sure that SelectedIndex binding mode is Two Way).
Then on set of FirstIndex property set the PartialCollection property as you want.
Remember that you have to use NotifyPropertyChange on set method of each Properties.
Hope this help.
I'd go with MVVM design but if you want to just do testing and want to see quick results without going into design patterns then I'd suggest using something like CLINQ / BLINQ / Obtics framework to leverege the power of LINQ while keeping the results live for the combo box.
Since LoSciamano has already posted a reply on that (while i was posting this!) I won't dive into details of MVVM implementation
I would expose the first collection as an ObservableCollection<T> and the second collection as a bare IEnumerable. You can then use the default collection view to handle the events necessary to re-filter the sub collection.
class FilteredVM : ViewModelBase
{
public ObservableCollection<MyVM> Items { get; private set; }
public IEnumerable<MyVM> SubItems { get; private set; }
public FilteredVM()
{
this.Items = new ObservableCollection<MyVM>();
this.SubItems = Enumerable.Empty<MyVM>();
var view = CollectionViewSource.GetDefaultView(this.Items);
view.CurrentChanged += (sender, e) => { SetupFilter(); };
view.CollectionChanged += (sender, e) => { SetupFilter(); };
}
private void SetupFilter()
{
var view = CollectionViewSource.GetDefaultView(this.Items);
var current = view.CurrentItem;
if (current != null)
{
this.SubItems = this.Items.Where((vm,idx) => idx > view.CurrentPosition);
}
else
{
this.SubItems = Enumerable.Empty<MyVM>();
}
this.OnPropertyChanged("SubItems");
}
}
Alternatively, if you'd like to keep CollectionViewSource out of your VM:
class FilteredVM : ViewModelBase
{
private MyVM selectedItem;
public MyVM SelectedItem
{
get { return this.selectedItem; }
set
{
if (value != this.selectedItem)
{
this.selectedItem = value;
this.OnPropertyChanged("SelectedItem");
this.SetupFilter();
}
}
}
public ObservableCollection<MyVM> Items { get; private set; }
public IEnumerable<MyVM> SubItems { get; private set; }
public FilteredVM()
{
this.Items = new ObservableCollection<MyVM>();
this.SubItems = Enumerable.Empty<MyVM>();
this.Items.CollectionChanged += (sender, e) => { this.SetupFilter(); };
}
private void SetupFilter()
{
if (this.SelectedItem != null)
{
var item = this.SelectedItem; // save for closure
this.SubItems = this.Items.SkipWhile(vm => vm != item).Skip(1);
}
else
{
this.SubItems = Enumerable.Empty<MyVM>();
}
this.OnPropertyChanged("SubItems");
}
}
Keep in mind this will require SelectedItem to be properly bound in the View to the ViewModel. The first approach listed allows the SelectedItem to be bound to anywhere (or nowhere).
Have 2 Observable collections so that when item in the 1st box is selected, it will kick off method which clears and re populates the 2nd collection. As it is observableCollection, it gets reflected in the WPF GUI automatically
Here is a concrete example (as usual, may be improved, but the idea is here) :
Code behind an view model :
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Initialsource = new ObservableCollection<int>();
for (int i = 0; i < 101; i++)
{
Initialsource.Add(i);
}
}
private int _selectedsourceItem;
public int SelectedsourceItem
{
get { return _selectedsourceItem; }
set
{
_selectedsourceItem = value;
SubsetSource = new ObservableCollection<int>(Initialsource.Where(p => p > _selectedsourceItem));
InvokePropertyChanged(new PropertyChangedEventArgs("SubsetSource"));
InvokePropertyChanged(new PropertyChangedEventArgs("SelectedsourceItem"));
}
}
private ObservableCollection<int> _initialsource;
public ObservableCollection<int> Initialsource
{
get { return _initialsource; }
set
{
_initialsource = value;
InvokePropertyChanged(new PropertyChangedEventArgs("Initialsource"));
}
}
private ObservableCollection<int> _subsetSource;
public ObservableCollection<int> SubsetSource
{
get { return _subsetSource ?? (_subsetSource = new ObservableCollection<int>()); }
set
{
_subsetSource = value;
InvokePropertyChanged(new PropertyChangedEventArgs("SubsetSource"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
XAML :
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<ComboBox Width="100" ItemsSource="{Binding Initialsource}" SelectedItem="{Binding SelectedsourceItem, Mode=TwoWay}"></ComboBox>
<ComboBox Width="100" ItemsSource="{Binding SubsetSource}"></ComboBox>
</StackPanel>
</Grid>
You can use my ObservableComputations library:
ObservableCollection<Item> itemsSubset = ItemsObservableCollection
.Skiping(fromIndex)
.Taking(itemsInSubsetCount);
itemsSubset reflacts all the changes in ItemsObservableCollection.