ObservableCollection filter Selected Item - c#

I have a problem that I don't know how to solve.
I have a observable collection that I filter the items as I type in a textbox the problem is that when I select the filtered item I get the wrong selected index.
For example I have one item after filtering the real selected index is 2 but because it sets the collection as I type it set the index to one if the only filtered item left is one.
So how do I get the right item selected. Like in the mail application to make my question maybe easier to understand
Here is the selection changed event:
private void searchToDoItemsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (searchToDoItemsListBox.SelectedIndex == -1)
return;
NavigationService.Navigate(new Uri("/DetailsPage.xaml?selectedItemSearch=" + searchToDoItemsListBox.SelectedIndex, UriKind.Relative));
searchToDoItemsListBox.SelectedIndex = -1;
}
And here is for the details page:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
if (NavigationContext.QueryString.TryGetValue("selectedItemSearch", out selectedIndexSearch))
{
int indexSearch = int.Parse(selectedIndexSearch);
DataContext = App.ViewModel.AllToDoItems[indexSearch];
}
}

Bind to the SelectedItem
<ListBox SelectedItem="{Binding Selected, Mode=TwoWay}" ItemsSource="Binding={Items}">
</ListBox>
and you have to fields:
public ObservableCollection<ItemType> Items {get;set;} //setted while filtering, does it?
and
private ItemType _selected;
public ItemType Selected
{
get
{
return _selected;
}
set
{
_selected = value;
//here you can save the item.
//For example save the item id, and navigate to DetailsPage
}
}
And then, you can get the item from list:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
if (NavigationContext.QueryString.TryGetValue("selectedItemSearch", out selectedIndexSearch))
{
int id = int.Parse(selectedIndexSearch);
DataContext = GetById(id)
}
}
public ItemType GetByIf(id)
{
for(int i = 0; i < App.ViewModel.AllToDoItems.Count; i++)
{
if(App.ViewModel.AllToDoItems[i].Id == id) return App.ViewModel.AllToDoItems[i];
}
return null;
}

Have done like this now and i get nothing now at all. It navigates but nothings shows.
if (NavigationContext.QueryString.TryGetValue("selectedItemSearch", out selectedIndexSearch))
{
//int indexSearch = int.Parse(selectedIndexSearch);
//DataContext = App.ViewModel.AllToDoItems[indexSearch];
int id = int.Parse(selectedIndexSearch);
DataContext = GetById(id);
}
public object GetById(int id)
{
for(int i = 0; i < App.ViewModel.AllToDoItems.Count; i++)
{
if (App.ViewModel.AllToDoItems[i].ToDoItemId == id)
return App.ViewModel.AllToDoItems[i];
}
return null;
}
The AllToDoItems looks like below, its a observable collection;
This is in the ViewModel this below load the collection from the database.
ToDoItem is the table name in the Model.
// Specify the query for all to-do items in the database.
var toDoItemsInDB = from ToDoItem todo in toDoDB.Items
select todo;
// Query the database and load all to-do items.
AllToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
The Model looks like this:
public Table<ToDoItem> Items;
//public Table<ToDoFavCategory> Categories;
}
[Table]
public class ToDoItem : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property, and database column.
private int _toDoItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ToDoItemId
{
get { return _toDoItemId; }
set
{
if (_toDoItemId != value)
{
NotifyPropertyChanging("ToDoItemId");
_toDoItemId = value;
NotifyPropertyChanged("ToDoItemId");
}
}
}
Its easier maybe to take a look at this link where i have build it from
http://msdn.microsoft.com/en-us/library/hh286405(v=vs.92).aspx

Related

Xamarin forms set Picker SelectedItem

I am working with a xamarin Forms.
I am using Picker for DropDownList.
How can I set selectedItem to Picker?
My code
<Picker x:Name="VendorName" Title="Select" ItemDisplayBinding="{Binding VendorName}" SelectedItem="{Binding VendorName}" Style="{StaticResource PickerStyle}"></Picker>
and server side code is
Device.BeginInvokeOnMainThread(() =>
{
VendorName.ItemsSource = VendorList;
});
var currentVendor = new List<Vendor>();
currentVendor.Add(new Vendor { VendorID = "111", VendorName = "aaaa" });
VendorName.SelectedItem = currentVendor;
This may not be the most efficient but you could loop to find the index and set that way.
for (int x = 0; x < VendorList.Count; x++)
{
if (VendorList[x].VendorName == currentVendor .VendorName )
{
VendorName.SelectedIndex = x;
}
}
After adding all values as list in Picker
just treat with it as an array
so if you want to set selected item just set selected item index
currentVendor.SelectedIndex = 0;
zero means you make selected item is the first one you added to Picker
If you are using MVVM, and want to set SelectedItem from the view model, things get tricky. There seems to be a bug in Xamarin that prevents us from using SelectedItem with a two way binding. More info: Xamarin Forms ListView SelectedItem Binding Issue and https://xamarin.github.io/bugzilla-archives/58/58451/bug.html.
Luckily, we can easily write our own Picker.
public class TwoWayPicker : Picker
{
public TwoWayPicker()
{
SelectedIndexChanged += (sender, e) => SelectedItem = ItemsSource[SelectedIndex];
}
public static new readonly BindableProperty SelectedItemProperty = BindableProperty.Create(
nameof(SelectedItem), typeof(object), typeof(TwoWayPicker), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
public new object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (TwoWayPicker)bindable;
control.SetNewValue(newValue);
}
private void SetNewValue(object newValue)
{
if (newValue == null)
{
return;
}
for(int i = 0; i < ItemsSource.Count; i++)
{
if (ItemsSource[i].Equals(newValue))
{
SelectedIndex = i;
return;
}
}
}
}
Because is uses the same SelectedItem property, it is a drop-in replacement for Picker.
Note that if you want value equality rather than reference equality for the item class, you'll also need to override Equals like this:
public override bool Equals(object obj)
{
var other = obj as YourClass;
if (other == null)
{
return false;
}
else
{
return other.SomeValue == SomeValue; // implement your own
}
}
If you define the item class as a record instead of a class then it can select the item programmatically using the SelectedItem property.
In your case change
public class Vendor { // your class properties }
to
public record Vendor { // your class properties }
This will now work
VendorName.SelectedItem = currentVendor;

Making a dynamic set of 3 pickers on Xamarin.Forms

we are developing a cross platform app on Xamarin.Forms.
On one of the pages we need to display a set of 3 pickers with the same list of items. The idea is that when you select an item on one of the pickers it gets removed from the item-source of the other two.
To do this we developed the following code:
We started with a list of Items called BaseList which we get from a web service. We also create 3 separate lists (ListA, ListB and ListC) and 3 Items to store the selected Items of each picker (SelectedA, SelectedB and SelectedC).
private List<Item> BaseList;
private List<Item> _ListA;
private Item _SelectedA;
private List<Item> _ListB;
private Item _SelectedB;
private List<Item> _ListC;
private Item _SelectedC;
…
//Api Calls
private void LoadData()
{
…
BaseList = new List<Item> (ListFromWebServices);
_ListA = new List<Item>(BaseList);
OnPropertyChanged(nameof(ListA));
_ListB = new List<Item>(BaseList);
OnPropertyChanged(nameof(ListB));
_ListC = new List<Item>(BaseList);
OnPropertyChanged(nameof(ListC));
}
…
//Public Fields
public List<Item> ListA
{
get
{
return _ListA;
}
}
public Item SelectedA
{
get
{
return _SelectedA;
}
set
{
SetProperty(ref _SelectedA, value, nameof(SelectedA));
}
}
public List<Item> ListB
{
get
{
return _ListB;
}
}
public Item SelectedB
{
get
{
return _SelectedB;
}
set
{
SetProperty(ref _SelectedB, value, nameof(SelectedB));
}
}
public List<Item> ListC
{
get
{
return _ListC;
}
}
public Item SelectedC
{
get
{
return _SelectedC;
}
set
{
SetProperty(ref _SelectedC, value, nameof(SelectedC));
}
}
This code is on our ViewModel, so we use SetProperty to set the referenced property to the value and invoke PropertyChangedEventArgs from INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In order to update the ItemSource's, whenever a selected item is changed we call OnSelectedItemChanged from the setters of SelectedA, SelectedB and SelectedC. This method receives an index which indicates which Picker triggered it:
private void OnSelectedItemChanged(int index)
{
Item CurrentA = SelectedA;
Item CurrentB = SelectedB;
Item CurrentC = SelectedC;
int i;
switch (index)
{
case 0:
_ListB = new List<Item> (BaseList);
_ListB.Remove(CurrentA);
_ListB.Remove(CurrentC);
OnPropertyChanged(nameof(ListB));
_ListC = new List<Item>(BaseList);
_ListC.Remove(CurrentA);
_ListC.Remove(CurrentB);
OnPropertyChanged(nameof(ListC));
i = ListB.IndexOf(CurrentB);
if (i > -1)
{
_SelectedB = ListB[i];
}
OnPropertyChanged(nameof(SelectedB));
i = ListC.IndexOf(CurrentC);
if (i > -1)
{
_SelectedC = ListC[i];
}
OnPropertyChanged(nameof(SelectedC));
break;
case 1:
_ListA = new List<Item>(BaseList);
_ListA.Remove(CurrentB);
_ListA.Remove(CurrentC);
OnPropertyChanged(nameof(ListA));
_ListC = new List<Item>(BaseList);
_ListC.Remove(CurrentA);
_ListC.Remove(CurrentB);
OnPropertyChanged(nameof(ListC));
i = ListA.IndexOf(CurrentA);
if (i > -1)
{
_SelectedA = ListA[i];
}
OnPropertyChanged(nameof(SelectedA));
i = ListC.IndexOf(CurrentC);
if (i > -1)
{
_SelectedC = ListC[i];
}
OnPropertyChanged(nameof(SelectedC));
break;
case 2:
_ListA = new List<Item>(BaseList);
_ListA.Remove(CurrentB);
_ListA.Remove(CurrentC);
OnPropertyChanged(nameof(ListA));
_ListB = new List<Item>(BaseList);
_ListB.Remove(CurrentA);
_ListB.Remove(CurrentC);
OnPropertyChanged(nameof(ListB));
i = ListA.IndexOf(CurrentA);
if (i > -1)
{
_SelectedA = ListA[i];
}
OnPropertyChanged(nameof(SelectedA));
i = ListB.IndexOf(CurrentB);
if (i > -1)
{
_SelectedB = ListB[i];
}
OnPropertyChanged(nameof(SelectedB));
break;
}
}
What we do here is basically save the current selected item for each picker on a separate variable, copy the BaseList into the two pickers that didn't call the event, then on the each new list remove all the options in use by the other pickers, set again the selected Item on each new list to the one that was selected originally and finally call OnPropertyChanged() to inform the views of the change.
The issue here is that when we change the ItemSource on a Picker it sets the SelectedItem to null. calling OnPropertyChanged() on the setter after OnSelectedItemChanged()was called leads to an infinite loop of one Picker updating the other, and adding a filter that checks if the value isn't null before setting it makes the Picker display no selected item, while the value is already set.
just in case anyone has the same issue, we found a solution for this. Turns out if you make CurrentA, CurrentB and CurrentC global variables and add on each case an if ((CurrentA != SelectedA) && (!(SelectedA is null))) { ... (do all the stuff) } break; and at the end you set
_SelectedA = CurrentA;
OnPropertyChanged(nameof(SelectedA));
_SelectedB = CurrentB;
OnPropertyChanged(nameof(SelectedB));
_SelectedC = CurrentC;
OnPropertyChanged(nameof(SelectedC));
it works. We don't know why tho :)

I'm retrieving a value for a combobox with custom class items but I can't make it show the item

I wanted to have items and hidden values which I could call later so I used this Article to create my custom items.
But now that I'm calling one value I cannot make it show the proper item. The combobox stays null.
if (reader.HasRows)
{
reader.Read();
namebox.Text = reader["c_Name"].ToString();
lastbox.Text = reader["c_LastName"].ToString();
genderbox.SelectedItem = reader["c_gender"].ToString();
}
Here is what I add to my combobox and what I want to show accoring to what value I get from the reader
private void editcust_Load(object sender, EventArgs e)
{
genderbox.Items.Add(new ComboBoxItem("male", "1"));
genderbox.Items.Add(new ComboBoxItem("female", "0"));
}
Please let me know if I need to add more code or provide more information.
I'm a junior developer so please excuse my terrible mistakes and bad formulation.
First, override Equals and GetHashCode methods in your class:
public class ComboBoxItem()
{
string displayValue;
string hiddenValue;
//Constructor
public ComboBoxItem (string d, string h)
{
displayValue = d;
hiddenValue = h;
}
//Accessor
public string HiddenValue
{
get
{
return hiddenValue;
}
}
public override bool Equals(object obj)
{
ComboBoxItem item = obj as ComboBoxItem;
if (item == null)
{
return false;
}
return item.hiddenValue == this.hiddenValue;
}
public override int GetHashCode()
{
if (this.hiddenValue == null)
{
return 0;
}
return this.hiddenValue.GetHashCode();
}
//Override ToString method
public override string ToString()
{
return displayValue;
}
}
Then assign a new Item to the SelectedItem property:
genderbox.SelectedItem = new ComboBoxItem(string.Empty, reader["c_gender"].ToString());
When you assign a value to the SelectedItem property of ComboBox, it looks in it's items collection and tries to find an item that is equal to the assigned value. If it find an item equal to the value, that item gets selected. In the process, comparison is done by the Equals method of each item.
By overriding the method, you tell ComboBox to compare items using the "hiddenValue" field, so when you assign a new item with ite's hiddenValue set, combobox can find it in it's items collection. If you don't do that, equality comparison will be done using object references instead.
Use the DisplayMember & ValueMember properties of the ComboBox-Class and assign a DataSource.
ie. Your Data Class:
private class yourDataClass
{
public string DisplayMemberProperty { get; set; }
public int IDMember { get; set; }
}
Assign the datasource with values to the combobox
var dataSource = new ArrayList();
dataSource.Add(new yourDataClass() { DisplayMemberProperty = "Hello", IDMember = 1 });
dataSource.Add(new yourDataClass() { DisplayMemberProperty = "Hello2", IDMember = 2 });
dataSource.Add(new yourDataClass() { DisplayMemberProperty = "Hello3", IDMember = 2 });
this.comboBox1.DataSource = dataSource;
this.comboBox1.DisplayMember = "DisplayMemberProperty";
this.comboBox1.ValueMember = "IDMember";
Retreive the selected value...
var value = this.comboBox1.SelectedValue;
Agreed the question is unclear but if you mean that this call fails:
genderbox.SelectedItem = reader["c_gender"].ToString();
It's probably because that you need to use the same kind of value that you originally populated the list with.
i.e. if you populated it with instances of class x you need to set selectedItem to an instance of class x.

Multibinding XamDataGrid

I am trying to use the following code example from the Infragistics site and I'd like edits in the XamDataCards to be reflected in the XamDataGrid. However, my DataSource for the XamDataGrid is an ObservableCollection<Companies> in my ViewModel. How can I also bind to the card and relay updates back to my Companies object in the ViewModel?
<igDP:XamDataGrid x:Name="dgCompanies" Theme="IGTheme" DataSource="{Binding Companies}" SelectedDataItemsScope="RecordsOnly">
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings CellClickAction="SelectCell" AllowEdit="True"/>
</igDP:XamDataGrid.FieldSettings>
</igDP:XamDataGrid>
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding Path=SelectedDataItems, ElementName=dgCompanies}"
Theme="IGTheme">
Edit: Added ViewModel
public class CompanyMgmtViewModel : ViewModelBase
{
private ObservableCollection<Object> _Companies = null;
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
RaisePropertyChanged(GetPropertyName(() => Companies));
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = from res in AODB.Context.TCompanies
select res;
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
The Model/Context code is just EF Database First generated.
You would need to bind your XamDataGrid's SelectedDataItems property to a property of type object[] ie. SelectedCompanies in your ViewModel and bind to that for your XamDataCards' datasource.
The accepted answer in this thread has a sample that shows how to do this, albeit with a ListBox instead of XamDataCards:
http://www.infragistics.com/community/forums/t/89122.aspx
Just replace that ListBox with your XamDataCards control, it works and updates the XamDataGrid. The ViewModel in the example is contained in the MainWindow code-behind, so it is MVVM like you want.
more info:
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/xamDataGrid_Selected_Data_Items.html
IG's SelectedDataItems is an object[] :
http://help.infragistics.com/Help/Doc/WPF/2014.1/CLR4.0/html/InfragisticsWPF4.DataPresenter.v14.1~Infragistics.Windows.DataPresenter.DataPresenterBase~SelectedDataItems.html
I couldn't have gotten to this answer without Theodosius' and Ganesh's input - so thanks to them, they both had partial answers.
I first tried to bind the SelectedDataItems of the XamDataGrid to the XamDataCards by way of a property on the ViewModel as Theodosius suggested, but that wasn't enough. Thanks to Ganesh, I implemented INotifyPropertyChanged on my model objects, by inheriting from ObservableObject in MVVMLight (how did I not know the Model needed this?).
Below are the relevant pieces of code to make it work.
I also implemented PropertyChanged.Fody as documented here; that's where the TypedViewModelBase<T> and removal of RaisePropertyChanged() comes from.
I'm also creating my Model objects by using a LINQ/Automapper .Project().To<T>() call which can be found here.
Model
public class Company : ObservableObject
{
public Company() { }
public int id { get; set; }
public string strName { get; set; }
public string strDomicileCode { get; set; }
}
ViewModel
public class CompanyMgmtViewModel : TypedViewModelBase<Company>
{
private ObservableCollection<Object> _Companies = null;
private Object[] _selectedCompany = null;
public Object[] Company
{
get { return _selectedCompany; }
set
{
if (_Company != value)
{
_selectedCompany = value;
}
}
}
public ObservableCollection<Object> Companies
{
get { return _Companies; }
set
{
if (_Companies != value)
{
_Companies = value;
}
}
}
public CompanyMgmtViewModel()
{
this.LoadData();
}
public void LoadData()
{
ObservableCollection<Object> records = new ObservableCollection<Object>();
var results = AODB.Context.TCompanies.Project().To<Company>();
foreach (var item in results)
if (item != null) records.Add(item);
Companies = records;
}
}
View
<igDP:XamDataGrid x:Name="dgCompanies"
Theme="IGTheme"
DataSource="{Binding Companies, Mode=OneWay}"
SelectedDataItemsScope="RecordsOnly"
SelectedDataItems="{Binding Company}">
...
<igDP:XamDataCards x:Name="XamDataCards1"
Grid.Row="1"
DataSource="{Binding ElementName=dgCompanies, Path=SelectedDataItems}"
Theme="IGTheme">

Datagridview databinding when create new object on create new row

I've got a little problem with data-binding between DataGridView and a PropertyGrid.
Here is the code from the object I am binding to and the DataGridView:
public class Effort
{
public BindingList<EffortCalculationRelation> CalculationRelations { get; set; }
public int ID { get; set; }
// more properties
public Effort()
{
CalculationRelations = new BindingList<EffortCalculationRelation>();
CalculationRelations.Clear();
for (int i=0;i<10;i++)
{
CalculationRelations.Add( new EffortCalculationRelation() { ID = i, Name = "Round:" + i.ToString(), calculation = "Some calc" });
}
}
public Effort(int id) : this()
{
this.ID = id;
// Load all other properties
}
public class EffortCalculationRelation
{
public int ID { get; set; }
public string Name { get; set; }
public string calculation { get; set; }
public int Save()
{
// save or insert and return id or 0 on fail
if (this.ID > 0)
{
return this.Update();
}
else
{
return this.Insert();
}
}
public string Delete()
{
// delete and return "" or errormsg on fail
return "";
}
private int Insert()
{
// insert and return id or 0 on fail
return ID;
}
private int Update()
{
// return affected rows or 0 on fail
return 1;
}
public string Representation
{
get { return String.Format("{0}: {1}", ID, Name); }
}
}
}
The datagridview connection is realy simple an only just a little style:
public test()
{
effort = new Effort(1209);
dgv.DataSource = effort.CalculationRelations;
dgv.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
dgv.AllowUserToAddRows = true;
//this.dgv.AllowUserToDeleteRows = false;
dgv.AllowUserToResizeRows = false;
dgv.ReadOnly = true;
dgv.SelectionChanged += (sender, args) =>
{
var selectedObjects =
(from System.Windows.Forms.DataGridViewRow r in dgv.SelectedRows
where r.DataBoundItem != null && r.DataBoundItem.GetType() == typeof(EffortCalculationRelation)
select r.DataBoundItem).ToArray();
// pg is a propertygrid
this.pg.SelectedObjects = selectedObjects;
};
}
So and my problem is, when I select the new row in the datagridview, that no properties are displayed in the propertygrid.
When I select a row that has an object in the list at the moment I load it, then I can edit the properties.
So could you please help?
The reason the new row does not show in the property grid is that its DataBoundItem is null so is removed by your LINQ statement where r.DataBoundItem != null. 1
I agree with you that this is very annoying behaviour, particularly since the object has in some sense been created.
I had thought there was a viable workaround of in certain circumstance binding the property grid to the new object in either the parent Effort object or in a BindingSource using some code like:
var selectedObjects =
(from System.Windows.Forms.DataGridViewRow r in dataGridView1.SelectedRows
where r.DataBoundItem != null && r.DataBoundItem.GetType() == typeof(Effort.EffortCalculationRelation)
select r.DataBoundItem).ToArray();
if (dataGridView1.CurrentRow.IsNewRow && dataGridView1.SelectedRows.Count == 1)
{
// I tried accessing the parent object like this:
//Effort.EffortCalculationRelation ecr = effort.CalculationRelations[effort.CalculationRelations.Count - 1];
//propertyGrid1.SelectedObject = ecr;
// Or accessing a binding source like:
propertyGrid1.SelectedObject = calculationRelations.Current;
}
else
{
propertyGrid1.SelectedObjects = selectedObjects;
}
I experimented with these variations a bit, as well as adding these items into the SelectedObjects array, thinking something would meet your requirements but what I eventually realised was shifting focus from the new row to the property grid before the DataGridView had committed the new row meant that the new row was lost and could no longer be edited.
So - what to do?
If I was in your spot I'd consider one of two things:
Allow direct editing in the grid of some form - maybe just to the new row.
Something like this in the selection changed event would work:
if (dataGridView1.CurrentRow.IsNewRow)
{
dataGridView1.CurrentRow.ReadOnly = false;
}
else
{
dataGridView1.CurrentRow.ReadOnly = true;
}
Keep the grid as is but don't allow new rows - instead handle new objects between a seperate row creation panel.
1 This works this way apparently by design - the DataBoundItem is not committed uptil you leave the grid. There is a little discussion including the DataGridView code in question here.

Categories

Resources