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;
Related
Problem:
I have a custom Dependency Property (DP) called SelectedStoreString in a custom control called StorePicker. The DP has a PropertyChangedCallback and a CoerceValueCallback. The DP is two-way bound to a property in the view model by the same name. When the source is assigned a value in the view model, the CoerceValueCallback gets called, and it returns the final value that should be propagated between the target DP and the source in the view model (it could be the same as the value assigned or different if it got changed in the handler).
The value is then sent to the PropertyChangedCallback. The problem is the coerced value returned from the CoerceValueCallback never propagates to the target or the source. I'm using the DevExpress WPF Framework's POCOViewModels, so the INotifyPropertyChanged interface is implemented in the background.
Code:
//--------------------Poco view model--------------------------------------------------
[POCOViewModel]
public class FindOrdersViewModel
{
public virtual string SelectedStoreString { get; set; }
public FindOrdersViewModel()
{
SelectedStoreString = "48,49,55";
}
public static FindOrdersViewModel Create()
{
return ViewModelSource.Create(() => new FindOrdersViewModel());
}
}
//--------------------Dependency Property and its Handlers-----------------------------
public static DependencyProperty SelectedStoreStringProperty = DependencyProperty.Register(nameof(SelectedStoreString), typeof(string), typeof(StorePicker), new FrameworkPropertyMetadata("",
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedStoreStringChanged),
new CoerceValueCallback(CoerceSelectedStoreString),
false,
UpdateSourceTrigger.PropertyChanged));
public string SelectedStoreString
{
get
{
return (string) GetValue(SelectedStoreStringProperty); // _selectedStoreString;
}
set
{
SetValue(SelectedStoreStringProperty, value);
}
}
private static void OnSelectedStoreStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StorePicker storePicker = d as StorePicker;
if (d != null)
{
Dictionary<int, Store> sDct = new Dictionary<int, Store>();
foreach (Store store in _stores)
sDct.Add(store.STORE, store);
string value = e.NewValue as string;
if (value != null && value != "")
{
string[] strs = value.Split(',');
foreach (string s in strs)
{
try
{
int x = Convert.ToInt32(s);
Store outStore = sDct[x];
if (outStore != null)
{
if (!outStore.InActive)
{
storePicker.listBoxEdit1.SelectedItems.Add(outStore);
}
}
}
catch
{
}
}
}
}
}
private static object CoerceSelectedStoreString(DependencyObject d, object v)
{
StorePicker storePicker = d as StorePicker;
string finalSelectedStoresString = ""; //because the selectedstorestring may contain an Inactive store code we may need to adjust the string that was assigned
if (d != null)
{
string[] storeCodes = ((string)v) == "" ? new string[0]: ((string)v).Split(',');
foreach (string s in storeCodes)
{
Store temp = _stores.Find(x => x.STORE == Convert.ToInt32(s) && !x.InActive);
if (temp != null)
{
finalSelectedStoresString += temp.STORE + ",";
}
}
if (finalSelectedStoresString != "")
{
finalSelectedStoresString = finalSelectedStoresString.Remove(finalSelectedStoresString.Length - 1, 1); //remove the ending comma from the string if it has one
}
}
return finalSelectedStoresString;
}
//------------------------------How the control is used in xml---------------------------------------
<ctl:StorePicker x:Name="StorePicker" Grid.Row="2" Grid.Column="1" Margin="10,-4,0,4" StoreType="All" SelectedStoreString="{Binding SelectedStoreString,Mode=TwoWay}" IsAllStoresSelected="{Binding IsAllStoresSelected, Mode=TwoWay}"/>
What I've tried:
Calling storePicker.SetCurrentValue (SelectedStoreStringProperty, e.NewValue as string) from the PropertyChangedCallback. This either resulted in an infinite loop of callbacks or absolutely nothing if I used a Boolean state variable to prevent the infinite loop. But nonetheless, did not update the binding.
Setting the binding mode to Explicit and calling storePicker.GetBindingExpression(SelectedStoreStringProperty).UpdateSource() and storePicker.GetBindingExpression(SelectedStoreStringProperty).UpdateTarget() to manually update the bindings. Still did not update the target or source.
Setting the DP using these following methods (all of which just destroyed the binding, thus defeating the purpose): storePicker.SetValue(SelectedStoreStringProperty, e.NewValue as string), SelectedStoreString = e.NewValue as string
What I need:
I need the ability to change Dependency Property values in the code-behind of a custom control and have those values be propagated to the target DP and the source property in the view model.
I have searched Google for a simple solution to this but no luck. I have a standard WPF combo box which I would simply like to be able to filter the list displayed according to the first 2 or 3 letters a users types when the combo box has focus. I tried some coding including some lamba expressions but the error "System.NotSupportedException" keeps getting thrown on the line where "combobox.Items.Filter" is specified. I'm not using MVVM and would just like this simple functionality available for the user. Please help! P.S. IsEditable, IsTextSearchEnabled and StaysOpenOnEdit properties are set to true but the desired functionality is not yet achieved.
I have developed a sample application. I have used string as record item, you can do it using your own entity. Backspace also works properly.
public class FilterViewModel
{
public IEnumerable<string> DataSource { get; set; }
public FilterViewModel()
{
DataSource = new[] { "india", "usa", "uk", "indonesia" };
}
}
public partial class WinFilter : Window
{
public WinFilter()
{
InitializeComponent();
FilterViewModel vm = new FilterViewModel();
this.DataContext = vm;
}
private void Cmb_KeyUp(object sender, KeyEventArgs e)
{
CollectionView itemsViewOriginal = (CollectionView)CollectionViewSource.GetDefaultView(Cmb.ItemsSource);
itemsViewOriginal.Filter = ((o) =>
{
if (String.IsNullOrEmpty(Cmb.Text)) return true;
else
{
if (((string)o).Contains(Cmb.Text)) return true;
else return false;
}
});
itemsViewOriginal.Refresh();
// if datasource is a DataView, then apply RowFilter as below and replace above logic with below one
/*
DataView view = (DataView) Cmb.ItemsSource;
view.RowFilter = ("Name like '*" + Cmb.Text + "*'");
*/
}
}
XAML
<ComboBox x:Name="Cmb"
IsTextSearchEnabled="False"
IsEditable="True"
ItemsSource="{Binding DataSource}"
Width="120"
IsDropDownOpen="True"
StaysOpenOnEdit="True"
KeyUp="Cmb_KeyUp" />
I think the CollectionView is what you are looking for.
public ObservableCollection<NdfClassViewModel> Classes
{
get { return _classes; }
}
public ICollectionView ClassesCollectionView
{
get
{
if (_classesCollectionView == null)
{
BuildClassesCollectionView();
}
return _classesCollectionView;
}
}
private void BuildClassesCollectionView()
{
_classesCollectionView = CollectionViewSource.GetDefaultView(Classes);
_classesCollectionView.Filter = FilterClasses;
OnPropertyChanged(() => ClassesCollectionView);
}
public bool FilterClasses(object o)
{
var clas = o as NdfClassViewModel;
// return true if object should be in list with applied filter, return flase if not
}
You wanna use the "ClassesCollectionView" as your ItemsSource for your Combobox
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 have MVVM silverlight app with toolkit charts.
In view model I created ObservableCollection property:
private ObservableCollection<LineSeries> _lines = new ObservableCollection<LineSeries>();
public ObservableCollection<LineSeries> Lines
{
get { return _lines; }
set
{
_lines = value;
NotifyPropertyChanged("Lines");
}
}
Then in some method I populate this collection with dynamic count lines:
List<SolidColorBrush> colors = BS2ColorSetHelper.GetSetColors();
for (int i = 0; i < remainderData.Count; i++)
{
LineSeries line = (colors.ElementAtOrDefault(i) != null)
? CreateNewLineSeriesWithColor(remainderData[i].DenominationName, remainderData[i].Coords, colors[i])
: CreateNewLineSeries(remainderData[i].DenominationName, remainderData[i].Coords);
line.Name = remainderData[i].DenominationName;
Lines.Add(line);
}
.........
Now I want to bind this ObservableCollection to toolkit chart series.
<toolkit:Chart Name="chart">
<toolkit:Chart.Series>
????
</toolkit:Chart.Series>
</toolkit:Chart>
I have tried
Series="{Binding Path=Lines}"
but it doesn't work. Visual Studio shows an error: Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Collections.ObjectModel.Collection`1[System.Windows.Controls.DataVisualization.Charting.ISeries]'. I think it's because Series are not dependency property.
Ok, we can't bind LineSeries to Series because Series are not Dependency property.
So we can create new UserControl with this dependency properties:
public class MultiChart : Chart
{
public IEnumerable SeriesSource
{
get
{
return (IEnumerable)GetValue(SeriesSourceProperty);
}
set
{
SetValue(SeriesSourceProperty, value);
}
}
public static readonly DependencyProperty SeriesSourceProperty = DependencyProperty.Register(
name: "SeriesSource",
propertyType: typeof(IEnumerable),
ownerType: typeof(MultiChart),
typeMetadata: new PropertyMetadata(
defaultValue: default(IEnumerable),
propertyChangedCallback: new PropertyChangedCallback(OnSeriesSourceChanged)
)
);
private static void OnSeriesSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IEnumerable newValue = (IEnumerable)e.NewValue;
MultiChart source = (MultiChart)d;
source.Series.Clear();
foreach (LineSeries item in newValue)
{
source.Series.Add(item);
}
}
}
Then we just bind LineSeries to newly created property:
<common:MultiChart Name="chart"
Title="{Binding Path=Title}"
SeriesSource="{Binding Path=Lines}" />
The View Model will be:
public class ChartDenominationViewModel : ViewModel
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
private ObservableCollection<LineSeries> _lines = new ObservableCollection<LineSeries>();
public ObservableCollection<LineSeries> Lines
{
get { return _lines; }
set
{
_lines = value;
NotifyPropertyChanged("Lines");
}
}
}
I don't know fully the information about the control Chart I would suggest you check the documentation provided by the control originator to find out the name of the bindable property.
But from what you post my guess would be to try:
<toolkit:Chart Name="chart" Series={Binding Lines}/>
guessing that your Datacontext is all in place. meaning the datacontext of the page is set to your viewmodel
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