C# - Filtering an ObservableCollection via subfield - c#

I have the following code:
SubjectsToChooseFrom = new ObservableCollection<SubjectDTO>(_model.Subjects);
SubjectsToChooseFrom = SubjectsToChooseFrom.Where(x => x.Id == (int)CurrentProgId);
SubjectsToChooseFrom is bound to a ComboBox. After this, only one element gets shown in the box instead of a list. Why is this, and how can I make sure all the values where x.Id == (int)CurrentProgId stay in SubjectsToChooseFrom?
UPDATE
XAML and code as requested:
<ComboBox Text="Choose program" Margin="5" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SubjectsToChooseFrom}"
SelectedValue="{Binding CurrentSubjectId, Mode=TwoWay}"
SelectedValuePath="Id"
DisplayMemberPath="SubjectName">
</ComboBox>
Declaration:
private IEnumerable<SubjectDTO> _subjectsToChooseFrom;
public IEnumerable<SubjectDTO> SubjectsToChooseFrom
{
get
{
return _subjectsToChooseFrom;
}
set
{
_subjectsToChooseFrom = value;
OnPropertyChanged();
}
}

Where returns an IQueryable. You need to use ToList() or ToArray() etc. to materialize the results of this query.
If you're using MVVM, much better is to expose both your collections as public properties and raise PropertyChanged whenever these collections change for any reason. You can then bind your ComboBoxes to these properties and they'll automatically get updated.

I ended up doing this by hand:
await _model.LoadAsync();
SubjectsToChooseFrom = new ObservableCollection<SubjectDTO>(_model.Subjects);
ObservableCollection<SubjectDTO> temp = new ObservableCollection<SubjectDTO>();
foreach (var subject in SubjectsToChooseFrom) {
if(subject.ProgId == CurrentProgId)
{
temp.Add(subject);
}
}
SubjectsToChooseFrom = temp;

Related

C# change the selected index of a ComboBox item using LINQ

Im not sure if the title represent the problem I have. The explanation here below should be clearer.
I cannot get my code to select the correct value in the ComboBox with the data from my List. I checked for similar problems here in SO but after reading several I cannot find one that matches my problem.
I fill the list with the follwing code:
if (roleComboBox.SelectionBoxItem.Equals("Player"))
{
memberID++;
Player player = new Player(memberID, team, firstName, lastName, age, salary, yearsActive, position, minutesPerGame);
players.Add(player);
PrintList();
}
Now I want to retrieve the data from the List and put it back into the TextBox/ComboBox it came from so I can change the data and put it back into the List (kinda a modify option). I have a different ComboBox with where I select an ID which matches a memberID which in its turn retrieves the data from the List. I use the following code for that:
private void ModifyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int modifyID = int.Parse(modifyComboBox.SelectedItem.ToString());
var player = players.Where(p => p.memberID == modifyID);
teamComboBox.SelectedValue = player.Select(p => p.team).FirstOrDefault(); // this isnt working as intended
firstNameTextBox.Text = player.Select(p => p.firstName).FirstOrDefault();
lastNameTextBox.Text = player.Select(p => p.lastName).FirstOrDefault();
ageTextBox.Text = player.Select(p => p.age.ToString()).FirstOrDefault();
salaryTextBox.Text = player.Select(p => p.salary.ToString()).FirstOrDefault();
yearsActiveTextBox.Text = player.Select(p => p.yearsActive.ToString()).FirstOrDefault();
minutesPerGameTextBox.Text = player.Select(p => p.minutesPerGame.ToString()).FirstOrDefault();
}
While the TextBoxes get filled with the correct data the ComboBox just gets blank.
This is de XAML code for the ComboBox if needed:
<ComboBox x:Name="teamComboBox" HorizontalAlignment="Left" Margin="200,38,0,0" VerticalAlignment="Top" Width="220">
<ComboBoxItem IsSelected="True">-Choose a team-</ComboBoxItem>
<ComboBoxItem>Minnesota Timberwolves</ComboBoxItem>
<ComboBoxItem>Charlotte Hornets</ComboBoxItem>
<ComboBoxItem>LA Lakers</ComboBoxItem>
</ComboBox>
In this case, the type of SelectedValue needs is ComboBoxItem instead of the string type you passed. If you want to set the SelectedValue programmatically, you can set the SelectedValuePath of teamComboBox as "Content" first, it means to set/get the Content of the ComboBoxItem(ie the string you set for the ComboBoxItem). Then you can set your p.team to SelectedValue. For example:
.xaml:
<ComboBox x:Name="teamComboBox" HorizontalAlignment="Left" SelectedValuePath="Content" Margin="200,38,0,0" VerticalAlignment="Top" Width="220">
<ComboBoxItem IsSelected="True">-Choose a team-</ComboBoxItem>
<ComboBoxItem>Minnesota Timberwolves</ComboBoxItem>
<ComboBoxItem>Charlotte Hornets</ComboBoxItem>
<ComboBoxItem>LA Lakers</ComboBoxItem>
</ComboBox>

c# MVVM update Datagrid after combobox selection

I am creating a small wpf programme which filters a dataset by date. It will eventually group and sum the data, but at the moment I am just trying to display the data rows. I want a window with a ComboBoxat the top with possible dates to select and a DataGridshowing the correct records. I have bound the ComboBoxto a list of possible dates, and I have bound a label to the value that the ComboBoxupdates. This label updates when I select a value from the ComboBox. I cannot however get the DataGrid to update to show the new data.
A DataSet is passed to the ViewModel constructor which extracts two DataTables. One is used to create an ObservableCollection<DateTime> for the ComboBoxs items to bind to (through WorkItemsDates). The other is stored for the DataGrid to bind to via a filtering (and eventually grouping/summing etc.) property WorkItems. The ComboBox is bound to the DateTime SelectedDate. The label is also bound to SelectedDate to ensure it is being updated by the ComboBox
The Xaml View is as follows:
<Window ...>
<DockPanel>
<ComboBox DockPanel.Dock="Top"
ItemsSource="{Binding WorkItemsDates}"
SelectedItem="{Binding SelectedDate, Mode=TwoWay}"
ItemStringFormat="ddd d MMM yyyy"
IsSynchronizedWithCurrentItem="True" />
<Label DockPanel.Dock="Bottom"
Content="{Binding SelectedDate, FallbackValue=99/99/9999}"
ContentStringFormat="dd MM yyyy" />
<DataGrid Name="TimeTotalsDataGrid" AutoGenerateColumns="True"
ItemsSource="{Binding WorkItems, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True" ></DataGrid>
</DockPanel>
</Window>
And code behind:
public partial class TheView: Window
{
public UserControl1(DataSet tigerDataSet)
{
InitializeComponent();
DataContext = new TimeTotalsDateSelectorViewModel(tigerDataSet);
}
}
The ViewModel is as follows:
internal class TimeTotalsDateSelectorViewModel
{
private ObservableCollection<DateTime> _workItemsDates;
private DataTable _workItems;
private DateTime _selectedDate;
public TimeTotalsDateSelectorViewModel(DataSet tigerDataSet)
{
if (tigerDataSet == null)
throw new ArgumentNullException("workItemsDates");
if (tigerDataSet.Tables["WorkItemsDates"] == null)
throw new ArgumentNullException("tigerDataSet.Tables[WorkItemsDates]");
if (tigerDataSet.Tables["WorkItems"] == null)
throw new ArgumentNullException("tigerDataSet.Tables[WorkItems]");
_workItems = tigerDataSet.Tables["WorkItems"];
_workItemsDates = new ObservableCollection<DateTime>();
foreach (DataRow row in tigerDataSet.Tables["WorkItemsDates"].Rows)
{
_workItemsDates.Add((DateTime)row["FinishDate"]);
}
SelectedDate = _workItemsDates[0];
}
public ObservableCollection<DateTime> WorkItemsDates
{
get { return _workItemsDates; }
}
public DateTime SelectedDate
{
get { return _selectedDate; }
set { _selectedDate = value; }
}
public DataTable WorkItems
{
get
{
DataRow[] _workItemsToShow = _workItems.Select("FinishTime>='" + _selectedDate.ToString() + "' AND FinishTime<'" + _selectedDate.AddDays(1).ToString() + "'");
return _workItemsToShow.Count() != 0 ? _workItemsToShow.CopyToDataTable() : null;
}
}
First thing, as you are using MVVM, so you will have to notify your properties by implementing INotifyPropertyChanged interface.
Second from the Setter of your SelectedItem property you will have to notify your WorkItems property as well, so that when you change the date from dropdown, it will also update the ItemsSource of the DataGrid.
In the setter of SelectedDate, call the code that gets the values for the grid. You should be implementing INotifyPropertyChanged as well, so the UI will update the data and the user can see it.

Binding a Dictionary to a Combobox in WPF displays incorrectly

Resolved - The theme was interfering with the display
This is my first experience with WPF so there may be an obvious answer to this.
I'm trying to display a Month selection combobox where the month names are displayed, and when a selection is made the integer value is captured.
XAML
<ComboBox Margin="5" IsEditable="False"
IsEnabled="{Binding IsCompanyFileUploadPeriodEnabled}"
ItemsSource="{Binding StartMonths}"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding SelectedStartMonthID}"
Width="50"></ComboBox>
Edit:
The ViewModel extends the Galasoft MvvmLight ViewModelBase, which provides the RaisePropertyChanged method.
ViewModel
Dictionary<string, int> _startMonths;
public Dictionary<string, int> StartMonths
{
get
{
if (_startMonths == null)
{
_startMonths = new Dictionary<string, int>();
for (int i = 1; i < 13; i++)
{
_startMonths.Add(System.Globalization.DateTimeFormatInfo.CurrentInfo.GetMonthName(i),
i);
}
}
return _startMonths;
}
}
int _selectedStartMonthID;
public int SelectedStartMonthID
{
get
{
return _selectedStartMonthID;
}
set
{
_selectedStartMonthID = value;
RaisePropertyChanged(() => SelectedStartMonthID);
}
}
But for some reason when I run the app the combobox is displaying as
[January, 1]
[February, 2]
etc
Does anyone know why it might be ignoring the DisplayMemberPath instruction? The SelectedValuePath setting seems to be working fine when an element is selected.
ComboBox DisplayMemberPath binding is broken by Themes BureauBlue and WhistlerBlue
http://wpf.codeplex.com/workitem/10129
Take a look at StringFormat.
MSDN StringFormat

ListView binding cast items to ListViewItem

I have a ListView bound to an instance of data
ObservalbeCollection<ActivityItem> ActivityItems
<ListView
x:Name="ActivityItemsList"
ItemsSource="{Binding ActivityItems}"
ItemTemplate="{StaticResource Herke80ItemTemplate}"
Header="{Binding DateFilterListBox.SelectedItem}" />
When I run a filter, I want to select the ListViewItem bound to the ActivityItem within the ListView, and change its visibility depending on the filter selected.
I was doing this by keeping another ObservableCollection instance, meaning the data instance was duplicated. I then removed or added items accordingly. This took up a lot of loading time. So I figured I'd try keep to one binding, and disable or enable the UI elements.
foreach (ActivityItem activityItem in ActivityItemsList.Items)
{
if (activityItem == null) continue;
var index = ActivityItemsList.Items.IndexOf(activityItem);
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Collapsed;
int startComparer = DateTime.Compare(activityItem.Start, selectedStartDate);
int endComparer = DateTime.Compare(selectedEndDate, activityItem.End);
if (OverdueToggleSwitch.IsOn)
{
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Visible;
}
else
{
if (startComparer >= 0 && endComparer >= 0)
{
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Visible;
}
}
}
ex is a NullReferenceException, due to the fact that the ListViewItem is not actually a ListViewItem but an ActivityItem.
What is an alternative or the correct way of doing this?
I think you have over complicated it. If you use Snoop you can see that when you bind a ListView to a collection via ItemSource it ends up populated with a collection of ListViewItems where each ListViewItem has two things.
a DataContext set to the data (e.g. ActivityItem)
a ContentPresenter filled with the controls defined in your DataTemplate
This means that in theory you can browse both collections
from x in listView.Items select x as ListViewItem
or
from x in listView.Items select x.DataContext as ActivityItem
However if you simply want a filtered list then can I suggest changing your binding,
<ListView
x:Name="ActivityItemsList"
ItemsSource="{Binding FilteredItems}"
and
public class MyViewModel :INotifyPropertyChanged
{
public IEnumerable<ActivityItem> AllItems {get;set;} //needs to NotifyPropertyChanged(FilteredItems)
public Func<ActivityItem, bool> Filter { get;set;} //needs to NotifyPropertyChanged(FilteredItems)
public IEnumerable<ActivityItem> FilteredItems { get { return from x in AllItems where Filter(x) select x; }}
}
If you are still struggling with performance issues then have a quick read through Bea Costa's series on TreeView performance part one, part two and part three. It might help you to get WPF doing all the hard work for you.

Filter a DataGrid in WPF

I load a lists of objects in a datagrid with this:
dataGrid1.Items.Add(model);
The model become data from a database. It has a Id(int), Name(string) and Text(string)
In my datagrid I show only the Name of the model. How can I filter the datagrid now, when I enter something in a textbox?
I was at this page: http://msdn.microsoft.com/en-us/library/vstudio/ff407126(v=vs.100).aspx but I don't understand the code from there and I can not explain how I should transpose that for my problem.
there are multiple way's to filter Collection
let's suggesting this is your Item Class
public class Model
{
public string Name
{
get;
set;
}
}
and your collection looks like
var ObColl = new ObservableCollection<Model>();
ObColl.Add(new Model() { Name = "John" });
ObColl.Add(new Model() { Name = "Karl" });
ObColl.Add(new Model() { Name = "Max" });
ObColl.Add(new Model() { Name = "Mary" });
Way 1 (Predicate):
public MainWindow()
{
InitializeComponent();
// Collection which will take your ObservableCollection
var _itemSourceList = new CollectionViewSource() { Source = ObColl };
// ICollectionView the View/UI part
ICollectionView Itemlist = _itemSourceList.View;
// your Filter
var yourCostumFilter= new Predicate<object>(item => ((Model)item).Name.Contains("Max"));
//now we add our Filter
Itemlist.Filter = yourCostumFilter;
dataGrid1.ItemsSource = Itemlist;
}
Way 2 (FilterEventHandler):
public MainWindow()
{
InitializeComponent();
// Collection which will take your Filter
var _itemSourceList = new CollectionViewSource() { Source = ObColl };
//now we add our Filter
_itemSourceList.Filter += new FilterEventHandler(yourFilter);
// ICollectionView the View/UI part
ICollectionView Itemlist = _itemSourceList.View;
dataGrid1.ItemsSource = Itemlist;
}
private void yourFilter(object sender, FilterEventArgs e)
{
var obj = e.Item as Model;
if (obj != null)
{
if (obj.Name.Contains("Max"))
e.Accepted = true;
else
e.Accepted = false;
}
}
extended Information to Way 1
if need multiple conditions or some complex Filter you can add a method to your Predicat
// your Filter
var yourComplexFilter= new Predicate<object>(ComplexFilter);
private bool ComplexFilter(object obj)
{
//your logic
}
This is a simple implementation of using the Filter property of ICollectionView. Suppose your XAML contains this:
<TextBox x:Name="SearchTextBox" />
<Button x:Name="SearchButton"
Content="Search"
Click="SearchButton_OnClick"
Grid.Row="1" />
<DataGrid x:Name="MyDataGrid"
Grid.Row="2">
<DataGrid.Columns>
<DataGridTextColumn Header="Lorem ipsum column"
Binding="{Binding}" />
</DataGrid.Columns>
</DataGrid>
Then in the constructor you can get the default view for your data where you can set the filter predicate which will be executed for every item of your collection. The CollectionView won't know when it should update the collection, so you have to call Refresh when the user clicks the search button.
private ICollectionView defaultView;
public MainWindow()
{
InitializeComponent();
string[] items = new string[]
{
"Asdf",
"qwer",
"sdfg",
"wert",
};
this.defaultView = CollectionViewSource.GetDefaultView(items);
this.defaultView.Filter =
w => ((string)w).Contains(SearchTextBox.Text);
MyDataGrid.ItemsSource = this.defaultView;
}
private void SearchButton_OnClick(object sender, RoutedEventArgs e)
{
this.defaultView.Refresh();
}
At this url you can find a more detailed description of CollectionViews:
http://wpftutorial.net/DataViews.html
#WiiMaxx, can't comment as not enough rep. I would be a bit more careful about the direct casts there. They can be slow for one thing and for another, if the same filter was applied to a grid holding different complex type data you would have an InvalidCastException.
// your Filter
var yourCostumFilter= new Predicate<object>(item =>
{
item = item as Model;
return item == null || item.Name.Contains("Max");
});
This will not break you datagrid and will not filter the results if the cast fails. Less impact to your users if you get the code wrong. On top of that the filter will be faster due to the "as" operator not doing any explicit type coercion as the direct cast operation will.
You can use dataview filter in order to filter the datagrid rows.
DataView dv = datatable.DefaultView;
StringBuilder sb = new StringBuilder();
foreach (DataColumn column in dv.Table.Columns)
{
sb.AppendFormat("[{0}] Like '%{1}%' OR ", column.ColumnName, "FilterString");
}
sb.Remove(sb.Length - 3, 3);
dv.RowFilter = sb.ToString();
dgvReports.ItemsSource = dv;
dgvReports.Items.Refresh();
Where the "datatable" is datasource given to your datagrid and using string builder you build the filter query where "Filter String" is the text you want to search in your datagrid and set it to dataview and finally set the dataview as itemsource to your datagrid and refresh it.
take at look at DataBinding --> in your case dont add items to your grid, but set the itemssource
<Datagrid ItemsSource="{Binding MyCollectionOfModels}" />
or
dataGrid1.ItemsSource = this._myCollectionOfModels;
and if you want some kind of filtering,sorting, grouping look at CollectionView
I found a dumb method and know this is an old question but ... Just use the Filter function on items property on the DataGrid object. Like this: (I'm sorry but i learned only VB)
Public Property SearchName As String
Get
Return _SearchName
End Get
Set
_SearchName = Value
DG_drw_overview.Items.Filter = New Predicate(Of Object)(Function(x) x.Name.Contains(Value))
End Set
End Property
This property is changed every time you type something in the textbox. DG_drw_overview is the DataGrid instance. In Predicate the object represents the object you put in the DataGrid.
Then bind the SearchName to textbox
<TextBox x:Name="TB_search"
Text="{Binding SearchName, UpdateSourceTrigger=PropertyChanged}"/>
Set datacontext of the textbox to the main class (usually after InitializeComponent())
TB_search.DataContext = Me
Leaving this here incase like me you couldn't get the other methods above to work properly.
//Example of my Transaction class
//Transaction(ID, Amount, Description)
ListCollectionView collectionView = new ListCollectionView(List<Transaction>);
collectionView.Filter = (e) =>
{
Transaction transaction = e as Transaction;
if (transaction.Amount >= 0) //When this is true it returns all positive transactions
{
return true;
}
else
{
return false;
}
};
dataGrid.ItemsSource = collectionView;
I used this enclosed in if statements to make a filter by combobox for the datagrid.
The combobox has 3 options Credit/Debit/Credit & Debit, depending on the option I chose it would filter on a selection change event
I got this from here: http://dotnetpattern.com/wpf-datagrid-filtering

Categories

Resources