I have a list box containing many items and I have a combobox in C# WPF.
I would like to bind the itemsource of the combobox to that of the listbox but I want to filter out some of the items (it's an xml datasource, I want to filter by the contents of a certain element of the item element).
Example item:
<Item>
<itemtype>A</itemtype>
<itemname>Name</itemname>
</item>
<Item>
<itemtype>B</itemtype>
<itemname>Name</itemname>
</item>
I thought of manually adding an item to the combobox when adding an item to the listbox but then the 'name' value of the item is not updated when it is changed in the listbox.
What is a good way to do this? Let's say I only want to show all item names with itemtype B. Can it be done in wpf binding or do I have to do some code behind?
It's a reference to the datasource, e.g. to a collection in your viewmodel if you use MVC pattern. The amazing thing is, that it is so simple in usage. The view is updated an has it's own refresh handling. I'll make a little example:
In WPF:
<ListBox ItemsSource={Binding Path=MySource} ItemTemplate="{StaticResource myItemTemplate}" />
The code logic:
public class Item
{
public string Name { get; set; }
public string Type { get; set; }
}
public class MyViewModel
{
public ObservableCollection<Item> MySource { get; set; }
public MyViewModel()
{
this.MySource = new ObservableCollection<Item>();
this.MySource.Add(new Item() { Name = "Item4", Type = "C" });
this.MySource.Add(new Item() { Name = "Item1", Type = "A" });
this.MySource.Add(new Item() { Name = "Item2", Type = "B" });
this.MySource.Add(new Item() { Name = "Item3", Type = "A" });
// get the viewsource
ListCollectionView view = (ListCollectionView)CollectionViewSource
.GetDefaultView(this.MySource);
// first of all sort by type ascending, and then by name descending
view.SortDescriptions.Add(new SortDescription("Type", ListSortDirection.Ascending));
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Descending));
// now i like to group the items by type
view.GroupDescriptions.Add(new PropertyGroupDescription("Type"));
// and finally i want to filter all items, which are of type C
// this is done with a Predicate<object>. True means, the item will
// be shown, false means not
view.Filter = (item) =>
{
Item i = item as Item;
if (i.Type != "C")
return true;
else
return false;
};
// if you need a refreshment of the view, because of some items were not updated
view.Refresh();
// if you want to edit a single item or more items and dont want to refresh,
// until all your edits are done you can use the Edit pattern of the view
Item itemToEdit = this.MySource.First();
view.EditItem(itemToEdit);
itemToEdit.Name = "Wonderfull item";
view.CommitEdit();
// of course Refresh/Edit only makes sense in methods/callbacks/setters not
// in this constructor
}
}
Interesting is, that this pattern directly affects the listbox in the gui. If you add the grouping / sorting, this will affect the listbox's display behavior, even if the itemssource is only bound to the viewmodel.
The problem with Binding in this case, is that its only set once. So if you bind it, the sources are synchron. You could use a converter for the binding, which filters the items you not like to bind. Another way would be to use WPF's wonderful CollectionViewSource.
You can add Groupings/Sortings and a Filter to a CollectionViewSource on any kind of ItemsSource. Use the ListCollectionView for example.
ListCollectionView view =
(ListCollectionView)CollectionViewSource.GetDefaultView(yourEnumerableSource);
Related
I have a problem, I have a wpf form with a tabcontrol, inside the tabcontrol there are multiple tabitems. By the way i'm doing the mvvm design patten with mvvmlight. Ok, now I want to pass some data from the viewmodel that is bound to the first tabitem to the other the second viewmodel that is bound to the second tabitem but only when the second tabitem is clicked. Thanks in advance
This sounds like a really odd requirement, please correct me if I'm wrong but I suspect that the way you've worded it isn't actually what you're trying to accomplish. The usual way to manage tabs with MVVM is to start by creating a view model for your tab panels:
public class TabItemViewModel
{
public string Header { get; set; }
// fields for the actual panel items go here
public override string ToString() => this.Header;
}
Then back in your view model you create an observable collection of these and a property to track which tab is currently selected:
public ObservableCollection<TabItemViewModel> MyItems { get; } = new ObservableCollection<TabItemViewModel>
{
new TabItemViewModel { Header = "Tab Page 1" },
new TabItemViewModel { Header = "Tab Page 2" },
new TabItemViewModel { Header = "Tab Page 3" }
};
private TabItemViewModel _CurrentTab;
public TabItemViewModel CurrentTab
{
get { return this._CurrentTab; }
set
{
if (this._CurrentTab != value)
{
this._CurrentTab = value;
RaisePropertyChanged(() => this.CurrentTab);
}
}
}
}
Your XAML then binds to the collection and the property:
<TabControl ItemsSource="{Binding MyItems}" SelectedItem="{Binding CurrentTab}" />
Result:
Since CurrentTab tracks the currently selected tab your view model code can easily check at any time to see if that's the one that the user has currently selected, so there's no need to mess around with the bindings themselves. Since the binding is two-way the view model can also control which tab is currently active, which is particularly handy when adding navigational helpers to your app.
If you genuinely want to remove bindings then it's easy enough to add an extra data field to your view model and then set/clear it in the CurrentTab setter.
I have a list of objects (let's call them Category) which holds a list of Items. So:
ObservableCollection<Category> Categories = new ObservableCollection<Category>();
public class Category
{
ObservableCollection<Item> Items { get; set; }
public Category()
{
Items = new ObservableCollection<Item>();
}
}
Now. I have one listbox which is bound to the Categories and I have another that is bound to the Items of the SelectedItem in the first listbox. So:
<ListBox x:Name="FirstListBox" ItemsSource={Binding Categories} />
<ListBox ItemsSource="{Binding Path=SelectedItem.Items, ElementName=FirstListBox}" />
Now, this works nicely. However, I am getting new data for the listboxes at set intervals. The problem I have is that when the new data comes in and I set it to the Categories, the SelectedItem of FirstListBox changes, and so I lose the populated items in the second ListBox. I presume because I am clearing the categories and adding them back in every time I poll that data.
currentData.Categories.Clear();
foreach (Category category in newData.Categories)
{
currentData.Categories.Add(category);
}
Does anyone know of a way around this?
I am new to wpf and MVVM, and I've spent all day trying to get the value of a ComboBox to my ViewModel on SelectionChanged. I want to call a function in the selection changed process. In mvvm, what is the solution for it?
In MVVM, we generally don't handle events, as it is not so good using UI code in view models. Instead of using events such as SelectionChanged, we often use a property to bind to the ComboBox.SelectedItem:
View model:
public ObservableCollection<SomeType> Items { get; set; } // Implement
public SomeType Item { get; set; } // INotifyPropertyChanged here
View:
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding Item}" />
Now whenever the selected item in the ComboBox is changed, so is the Item property. Of course, you have to ensure that you have set the DataContext of the view to an instance of the view model to make this work. If you want to do something when the selected item is changed, you can do that in the property setter:
public SomeType Item
{
get { return item; }
set
{
if (item != value)
{
item = value;
NotifyPropertyChanged("Item");
// New item has been selected. Do something here
}
}
}
I have a WPF ComboBox and am using MVVM to bind the ItemsSource and SelectedItem properties. Basically what I want to do is when a user selects a specific item in the combobox, the combobox instead selects a different item.
<ComboBox ItemsSource="{Binding TestComboItemsSource}" SelectedItem="{Binding TestComboItemsSourceSelected}"></ComboBox>
For demo purposes, I also have a button to update the SelectedItem.
<Button Command="{Binding DoStuffCommand}">Do stuff</Button>
I have this in my viewModel:
public ObservableCollection<string> TestComboItemsSource { get; private set; }
public MyConstructor()
{
TestComboItemsSource = new ObservableCollection<string>(new []{ "items", "all", "umbrella", "watch", "coat" });
}
private string _testComboItemsSourceSelected;
public string TestComboItemsSourceSelected
{
get { return _testComboItemsSourceSelected; }
set
{
if (value == "all")
{
TestComboItemsSourceSelected = "items";
return;
}
_testComboItemsSourceSelected = value;
PropertyChanged(this, new PropertyChangedEventArgs(TestComboItemsSourceSelected))
}
}
private ICommand _doStuffCommand;
public ICommand DoStuffCommand
{
get
{
return _doStuffCommand ?? (_doStuffCommand = new RelayCommand(p =>
{
TestComboItemsSourceSelected = "items";
})); }
}
OK, so I want to have the ComboBox select the item "items" whenever the user selects the item "all".
Using the button, I am able to update the combobox's SelectedItem, and I can see this reflected in the UI
I have similar logic to update the viewModel in my setter of the TestComboItemsSourceSelected property. If the user selects "all", instead set the SelectedItem to "items" So code-wise, the viewmodel property gets changed, but this is not reflected in the UI for some reason. Am I missing something? Is there some sort of side-effect of the way I've implemented this?
Well, this is because you change the property while another change is in progress. WPF will not listen to the PropertyChanged event for this property while setting it.
To workaround this, you can "schedule" the new change with the dispatcher, so it will be executed after it is done with the current change:
public string TestComboItemsSourceSelected
{
get { return _testComboItemsSourceSelected; }
set
{
if (value == "all")
{
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
TestComboItemsSourceSelected = "items";
}));
return;
}
_testComboItemsSourceSelected = value;
PropertyChanged(this, new PropertyChangedEventArgs(TestComboItemsSourceSelected))
}
}
The behaviour you are describing seems very weird for me, but if you want a "Select All" feature, the standar way is to create a combobox where items has a CheckBox.
Each item is represented by a small ViewModel (tipically with Id, Name and IsChecked properties), and you manually create a "select all item" that is added first in the ObservableCollection and subscribe to its PropertyChanged in order to set the rest o the items IsChecked property to true.
I would like to have a combobox that allows selection from a list of values and also allow a custom value from the typed in text. For display reasons the items are a complex type (lets say the combobox item template displays a patch of color and a flag indicating if it is a custom color).
public class ColorLevel
{
public decimal Intensity { get; set; }
public bool IsCustom { get; set; }
public Color BaseColor { get; set; }
public override ToString() { return string.Format("{0}", Intensity*100); }
}
Example items
var items = new [] {
new ColorLevel { Intensity = 0.9m, IsCustom = false, BaseColor = Color.Red },
new ColorLevel { Intensity = 0.7m, IsCustom = false, BaseColor = Color.Red }
}
XAML
<ComboBox SelectedItem="{Binding Path=SelectedColorLevel}"
IsEditable="true" IsTextSearchEnabled="true">
</ComboBox>
So the above markup works when an item is selected from the item list. And as you type with the text search the matching items are selected. If the typed text doesn't match an item then the SelectedColorLevel is set to null.
The question is at what point (and how) is it best to create a new custom item that can be set to the SelectedColorLevel when the typed text doesn't match an item.
For example I would want to assign a new item to the selected value such as
new ColorLevel { Intensity = decimal.Parse(textvalue), IsCustom = true }
or using an appropriate converter and databinding to the Text property.
Not sure if i fully understood..
You could use the KeyDown event to add a new ColorLevel, for example when Return is pressed.
If items is an ObservableCollection and you set it as the ComboBox's ItemsSource, the new ColorLevel added to items should be available in the list and become the SelectedItem.