I am using ComboBox ItemsSource property binding to display items from a List to combo box.
Following is the code:
<ComboBox x:Name="Cmb_Tax" ItemsSource="{Binding TaxList}"
DisplayMemberPath="ChargeName" SelectedItem="{Binding
SelectedTax,UpdateSourceTrigger=PropertyChanged}" IsEditable="True"
IsTextSearchEnabled="True" SelectionChanged="Cmb_Tax_SelectionChanged"/>
Classes.Charges _selected_tax = new Classes.Charges();
public Classes.Charges SelectedTax
{
get
{
return _selected_tax;
}
set
{
_selected_tax = value;
}
}
List<Classes.Charges> _taxlist = new List<Classes.Charges>();
public List<Classes.Charges> TaxList
{
get
{
return _taxlist;
}
set
{
_taxlist = value;
OnPropertyChanged("TaxList");
}
}
It displays the items in the combo box correctly.
There is a particular item in TaxList "No Tax" which I want to be selected by default in the combo box. This item can be present at any index in the list (Not necessary first or last item of the list).
I am trying to use the following code to set the selected index property of combo box, but sadly its not working.
TaxList = Classes.Charges.GetChargeList("Tax");
Cmb_Tax.DataContext = this;
int i = TaxList.FindIndex(x => x.ChargeName == tax_name);
Cmb_Tax.SelectedIndex = i;
The Method FindIndex() returns the index of the "No Tax" correctly but when I try assigning it to SelectedIndex of combo the SelectedIndex doesn't change. It stays at -1.
Update1
private void Cmb_Tax_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show(SelectedTax.ChargeName);
}
Update2
Updated the code as per suggested by #ElectricRouge
<ComboBox x:Name="Cmb_Tax" ItemsSource="{Binding TaxList, Mode=TwoWay}"
DisplayMemberPath="ChargeName" SelectedItem="{Binding SelectedTax,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
IsEditable="True" IsTextSearchEnabled="True"
SelectionChanged="Cmb_Tax_SelectionChanged"/>
Classes.Charges _selected_tax = new Classes.Charges();
public Classes.Charges SelectedTax
{
get
{
return _selected_tax;
}
set
{
_selected_tax = value;
OnPropertyChanged("SelectedTax");
}
}
ObservableCollection<Classes.Charges> _taxlist = new ObservableCollection<Classes.Charges>();
public ObservableCollection<Classes.Charges> TaxList
{
get
{
return _taxlist;
}
set
{
_taxlist = value;
OnPropertyChanged("TaxList");
}
}
public void Load_Tax(string tax_name = null, Classes.Charges selected_tax = null)
{
TaxList = Classes.Charges.GetParticularChargeList("Tax");
Cmb_Tax.DataContext = this;
//Cmb_Tax.SelectedValue = tax_name;
SelectedTax = selected_tax;
//int i = TaxList.FindIndex(x => x.ChargeName == tax_name);
//Cmb_Tax.SelectedIndex = i;
}
Any idea why this must be happening?
Also please suggest any other approach to display default in combo box.
Here's a working sample:
Viewmodel:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
this.DataContext = vm;
this.Loaded += (o,e) => vm.LoadData();
}
public class ViewModel : INotifyPropertyChanged
{
private IList<Charges> taxList;
public ICollectionView TaxList { get; private set; }
public void LoadData()
{
taxList = Charges.GetChargeList("taxes");
TaxList = CollectionViewSource.GetDefaultView(taxList);
RaisePropertyChanged("TaxList");
TaxList.CurrentChanged += TaxList_CurrentChanged;
var noTax = taxList.FirstOrDefault(c => c.ChargeName == "No Tax");
TaxList.MoveCurrentTo(noTax);
}
void TaxList_CurrentChanged(object sender, EventArgs e)
{
var currentCharge = TaxList.CurrentItem as Charges;
if(currentCharge != null)
MessageBox.Show(currentCharge.ChargeName);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
View:
<ComboBox x:Name="cboTaxList"
ItemsSource="{Binding TaxList}"
DisplayMemberPath="ChargeName"
IsSynchronizedWithCurrentItem="True" />
List does not implement INotifyCollectionChanged make it ObservableCollection
ObservableCollection<Classes.Charges> _taxlist = new ObservableCollection<Classes.Charges>();
public ObservableCollection<Classes.Charges> TaxList
{
get
{
return _taxlist;
}
set
{
_taxlist = value;
OnPropertyChanged("TaxList");
}
}
And try setting the Mode=TwoWay
SelectedItem="{Binding SelectedTax,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
Related
I am creating a WPF app containing a ComboBox which shows some data. I want to use the combobox-integrated text seach. But the problem is, if the user searchs for "llo", the list should show all items, containing this text snippet, like "Hallo", "Hello", "Rollo" ... But the search returns no result because the property name of no item starts with "llo". Has somebody an idea how to achieve this?
I am using the MVVM-pattern. The view is binded to a collection of DTOs (property of the viewmodel), in the DTO there are two properties which are relevant for the search.
<ComboBox
ItemsSource="{Binding Path=Agencies}"
SelectedItem="{Binding Path=SelectedAgency}"
IsTextSearchEnabled="True"
DisplayMemberPath="ComboText"
IsEnabled="{Binding IsReady}"
IsEditable="True"
Grid.Column="0"
Grid.Row="0"
IsTextSearchCaseSensitive="False"
HorizontalAlignment="Stretch">
</ComboBox>
public class Agency
{
public int AgencyNumber { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string ContactPerson { get; set; }
public string ComboText => $"{this.AgencyNumber}\t{this.Name}";
}
Ginger Ninja | Kelly | Diederik Krols definitely provide a nice all in one solution, but it may be a tad on the heavy side for simple use cases. For example, the derived ComboBox gets a reference to the internal editable textbox. As Diederik points out "We need this to get access to the Selection.". Which may not be a requirement at all. Instead we could simply bind to the Text property.
<ComboBox
ItemsSource="{Binding Agencies}"
SelectedItem="{Binding SelectedAgency}"
Text="{Binding SearchText}"
IsTextSearchEnabled="False"
DisplayMemberPath="ComboText"
IsEditable="True"
StaysOpenOnEdit="True"
MinWidth="200" />
Another possible improvement is to expose the filter, so devs could easily change it. Turns out this can all be accomplished from the viewmodel. To keep things interesting I chose to use the Agency's ComboText property for DisplayMemberPath, but its Name property for the custom filter. You could, of course, tweak this however you like.
public class MainViewModel : ViewModelBase
{
private readonly ObservableCollection<Agency> _agencies;
public MainViewModel()
{
_agencies = GetAgencies();
Agencies = (CollectionView)new CollectionViewSource { Source = _agencies }.View;
Agencies.Filter = DropDownFilter;
}
#region ComboBox
public CollectionView Agencies { get; }
private Agency selectedAgency;
public Agency SelectedAgency
{
get { return selectedAgency; }
set
{
if (value != null)
{
selectedAgency = value;
OnPropertyChanged();
SearchText = selectedAgency.ComboText;
}
}
}
private string searchText;
public string SearchText
{
get { return searchText; }
set
{
if (value != null)
{
searchText = value;
OnPropertyChanged();
if(searchText != SelectedAgency.ComboText) Agencies.Refresh();
}
}
}
private bool DropDownFilter(object item)
{
var agency = item as Agency;
if (agency == null) return false;
// No filter
if (string.IsNullOrEmpty(SearchText)) return true;
// Filtered prop here is Name != DisplayMemberPath ComboText
return agency.Name.ToLower().Contains(SearchText.ToLower());
}
#endregion ComboBox
private static ObservableCollection<Agency> GetAgencies()
{
var agencies = new ObservableCollection<Agency>
{
new Agency { AgencyNumber = 1, Name = "Foo", Title = "A" },
new Agency { AgencyNumber = 2, Name = "Bar", Title = "C" },
new Agency { AgencyNumber = 3, Name = "Elo", Title = "B" },
new Agency { AgencyNumber = 4, Name = "Baz", Title = "D" },
new Agency { AgencyNumber = 5, Name = "Hello", Title = "E" },
};
return agencies;
}
}
The main gotchas:
When the user enters a search and then selects an item from the filtered list, we want SearchText to be updated accordingly.
When this happens, we don't want to refresh the filter. For this demo, we're using a different property for DisplayMemberPath and our custom filter. So if we would let the filter refresh, the filtered list would be empty (no matches are found) and the selected item would be cleared as well.
On a final note, if you specify the ComboBox's ItemTemplate, you'll want to set TextSearch.TextPath instead of DisplayMemberPath.
If you refer to this answer
This should put you in the correct direction. It operated in the manner i believe you need when i tested it. For completeness ill add the code:
public class FilteredComboBox : ComboBox
{
private string oldFilter = string.Empty;
private string currentFilter = string.Empty;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null)
{
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
case Key.Enter:
IsDropDownOpen = false;
break;
case Key.Escape:
IsDropDownOpen = false;
SelectedIndex = -1;
Text = currentFilter;
break;
default:
if (e.Key == Key.Down) IsDropDownOpen = true;
base.OnPreviewKeyDown(e);
break;
}
// Cache text
oldFilter = Text;
}
protected override void OnKeyUp(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
break;
case Key.Tab:
case Key.Enter:
ClearFilter();
break;
default:
if (Text != oldFilter)
{
RefreshFilter();
IsDropDownOpen = true;
}
base.OnKeyUp(e);
currentFilter = Text;
break;
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
ClearFilter();
var temp = SelectedIndex;
SelectedIndex = -1;
Text = string.Empty;
SelectedIndex = temp;
base.OnPreviewLostKeyboardFocus(e);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
}
private void ClearFilter()
{
currentFilter = string.Empty;
RefreshFilter();
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (Text.Length == 0) return true;
return value.ToString().ToLower().Contains(Text.ToLower());
}
}
The XAML I used to test:
<Window x:Class="CustomComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowVM/>
</Window.DataContext>
<Grid>
<local:FilteredComboBox IsEditable="True" x:Name="MyThing" HorizontalAlignment="Center" VerticalAlignment="Center"
Height="25" Width="200"
ItemsSource="{Binding MyThings}"
IsTextSearchEnabled="True"
IsEnabled="True"
StaysOpenOnEdit="True">
</local:FilteredComboBox>
</Grid>
My ViewModel:
public class MainWindowVM : INotifyPropertyChanged
{
private ObservableCollection<string> _myThings;
public ObservableCollection<string> MyThings { get { return _myThings;} set { _myThings = value; RaisePropertyChanged(); } }
public MainWindowVM()
{
MyThings = new ObservableCollection<string>();
MyThings.Add("Hallo");
MyThings.Add("Jello");
MyThings.Add("Rollo");
MyThings.Add("Hella");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If it doesnt meet your exact needs im sure you can edit it. Hope this helps.
Use the .Contains method.
This method will return true if the string contains the string you pass as a parameter.
Else it will return false.
if(agency.Title.Contains(combobox.Text))
{
//add this object to the List/Array that contains the object which will be shown in the combobox
}
I have 3 combo boxes
<Grid>
<ComboBox Name="cbo1" SelectionChanged="OnComboBoxChanged" />
<ComboBox Name="cbo2" SelectionChanged="OnComboBoxChanged"/>
<ComboBox Name="cbo3" SelectionChanged="OnComboBoxChanged" />
The list for combo boxes is { a,b,c,d}
so if "b" is selected in the first box then the drop down should not have b and it will need to updated with {a,c,d} if the second one is set to a then last one need to have {c,d}. if they go back and change any we need to update the list accordingly. I addded a event oncomboboxchanged but it is not updating the combo box , when i set the item source to the new list.
private List<string> comboList = new List<string>();
string[] defaultParam = { "A", "B", "C", "D" };
public MainWindow()
{
InitializeComponent();
foreach(string s in defaultParam)
{
LoadCombo(s);
}
}
public void LoadCombo(string name)
{
comboList.Add(name);
cbo1.ItemsSource = comboList;
cbo2.ItemsSource = comboList;
cbo3.ItemsSource = comboList;
}
private void OnComboBoxChanged(object sender,SelectionChangedEventArgs e)
{
var combo = sender as ComboBox;
string oldval = combo.Text;
string id = combo.Name;
string itemSel = (sender as ComboBox).SelectedItem.ToString();
comboList.Remove(itemSel);
//add old value only if it is not empty
if (!string.IsNullOrEmpty(oldval))
{
comboList.Add(oldval);
}
combo.ItemsSource = comboList;
ComboBox[] comboNameLst = {cbo1,cbo2,cbo3 };
foreach (ComboBox cbo in comboNameLst)
{
if (id != cbo.Name)
{
if (cbo.SelectedItem == null)
{
cbo.ItemsSource = comboList;
}
else if (cbo.SelectedItem != null)
{
string tempitemsel = cbo.SelectedItem.ToString();
comboList.Add(tempitemsel);
cbo.ItemsSource = comboList;
comboList.Remove(tempitemsel);
}
}
}
}
so cbo.ItemSource is not doing any thing , do I need to do any thing differently so I see the update.
You need to use binding in XAML, rather than set ItemsSource in your code behind. Also data bind SelectedItem:
<Grid>
<ComboBox ItemsSource="{Binding DefaultList}" SelectedItem="{Binding SelectedItem_Cob1}"/>
<ComboBox ItemsSource="{Binding FilteredListA}" SelectedItem="{Binding SelectedItem_Cob2}"/>
<ComboBox ItemsSource="{Binding FilteredListB}" SelectedItem="{Binding SelectedItem_Cob3}"/>
</Grid>
In your code behind, you need to implement INotifyPropertyChanged; define your relevant ItemsSources, and SlectedItems as properties; and set your Windows's DataContext to your code itself (you should use MVVM pattern but you could worry about that later) :
using System.ComponentModel;
public partial class MainWindow: INotifyPropertyChanged
{
string[] defaultParam = { "A", "B", "C", "D" };
private string _selecteditem_cob1;
private string _selecteditem_cob2;
private string _selecteditem_cob3;
public List<string> DefaultList
{
get { return defaultParam.ToList(); }
}
public string SelectedItem_Cob1
{
get { return _selecteditem_cob1; }
set
{
if (_selecteditem_cob1 != value)
{
_selecteditem_cob1 = value;
RaisePropertyChanged("SelectedItem_Cob1");
RaisePropertyChanged("FilteredListA");
RaisePropertyChanged("FilteredListB");
}
}
}
public string SelectedItem_Cob2
{
get { return _selecteditem_cob2; }
set
{
if (_selecteditem_cob2 != value)
{
_selecteditem_cob2 = value;
RaisePropertyChanged("SelectedItem_Cob2");
RaisePropertyChanged("FilteredListB");
}
}
}
public string SelectedItem_Cob3
{
get { return _selecteditem_cob3; }
set
{
if (_selecteditem_cob3 != value)
{
_selecteditem_cob3 = value;
RaisePropertyChanged("SelectedItem_Cob3");
}
}
}
public List<string> FilteredListA
{
get { return defaultParam.ToList().Where(a=>a!=SelectedItem_Cob1).ToList(); }
}
public List<string> FilteredListB
{
get { return FilteredListA.Where(a => a != SelectedItem_Cob2).ToList(); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext=this;
}
//Implementation for INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
protected void RaisePropertyChanged(String propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
Result:
Three ComboBoxes will all show A,B,C,D at the initial stage. And
then if user made selections cbo2 and cbo3 will only display
filtered result dynamically.
I realized this is not 100% what you want (thanks to #TheodosiusVonRichthofen), but I feel you can still use this, and be able to easily modify it to suit your needs.
Also, the list that contains the combo-box items should be an ObservableCollection instead of a List. By making it an ObservableCollection, the combo-box items will be updated when you add/remove/change items in the lists.
I have created an ObservableCollection and binded that to my Listview. Before my items are loaded into the ListView they are being sorted using Linq and then added to the ListView:
//Get's the Items and sets it
public ObservableCollection<ItemProperties> ItemCollection { get; private set; }
//Orders the Items alphabetically using the Title property
DataContext = ItemCollection.OrderBy(x => x.Title);
<!--ItemCollection has been binded to the ListView-->
<ListView ItemsSource="{Binding}"/>
The problem I'm having is that I can't remove a selected item from the collection. The problem occurs only if I add the DataContext = ItemCollection.OrderBy(x => x.Title);. If it's DataContext = ItemCollection then I can delete the selected item.
My delete method gets activated once the ContextMenu (MenuFlyout) is being opened and the 'Delete' item is clicked. My delete method is:
private void btn_Delete_Click(object sender, RoutedEventArgs e)
{
var edit_FlyOut = sender as MenuFlyoutItem;
var itemProperties = edit_FlyOut.DataContext as ItemProperties;
ItemCollection.Remove(itemProperties);
}
This is my ItemProperties class:
public class ItemProperties : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ItemProperties() { }
private string m_Title;
public string Title
{
get { return m_Title; }
set
{
m_Title = value;
OnPropertyChanged("Title");
}
}
private string m_Post;
public string Post
{
get { return m_Post; }
set
{
m_Post = value;
OnPropertyChanged("Post");
}
}
private string m_Modified;
public string Modified
{
get { return m_Modified; }
set
{
m_Modified = value;
OnPropertyChanged("Modified");
}
}
private string m_ID;
public string ID
{
get { return m_ID; }
set
{
m_ID = value;
OnPropertyChanged("ID");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
}
Edit
How I load my Items:
public async void GetList()
{
var AppStorage = ApplicationData.Current.LocalFolder;
var noteFolders = await AppStorage.GetFolderAsync(#"folder\files\");
var Folders = await noteFolders.GetFoldersAsync();
ItemCollection = new ObservableCollection<ItemProperties>();
foreach (var noteFolder in Folders)
{
ItemCollection.Add(new ItemProperties { Title = readTitle, Post = readBody, ID = noteFolder.Name, Modified = timeFormat });
}
//code which readers and adds text to the properties...
DataContext = ItemCollection.OrderBy(x => x.Title);
}
You can get selected item from ListView and delete it:
if (lvElement.SelectedIndex == -1) return;
ItemProperties selectedProperty = (ItemProperties)lvElement.SelectedItem;
// remove selectedProperty from original collection
My solution is:
ItemCollection = new ObservableCollection<ItemProperties>(ItemCollection.OrderBy(a => a.Title));
DataContext = ItemCollection;
It seemed like I had to reinitialize the ItemCollection to a new ObservableCollection and order it all in one go rather than loading the items then sorting. What this did was it had one list which was first adding the items then the other list which had to sort. To avoid this I had do to it all in one go. Above helped me. I got it from here.
I have 2 combo boxes, one that contains a list of 'Items' and another that contains a list of 'Subitems'.
The list of Subitems depends on the currently selected Item.
I've got most of this working (by binding the ItemSource of the Subitems to a PossibleSubitems property), however the problem is when I change the Item and the Subitem is no longer valid for the new item. In this case I just want to pick the first valid subitem, but instead I get a blank combo-box. Note that I think that the property in the class is set correctly, but the binding doesn't seem to reflect it correctly.
Here's some code to show you what I'm doing. In this case, I have:
'Item 1' which can have SubItem A or Subitem B and
'Item 2' which can have SubItem B or Subitem C
The problem comes when I switch to Item 2 when I have Subitem A selected.
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="134" Width="136">
<StackPanel Height="Auto" Width="Auto">
<ComboBox ItemsSource="{Binding PossibleItems, Mode=OneWay}" Text="{Binding CurrentItem}"/>
<ComboBox ItemsSource="{Binding PossibleSubitems, Mode=OneWay}" Text="{Binding CurrentSubitem}"/>
</StackPanel>
</Window>
Code Behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
// List of potential Items, used to populate the options for the Items combo box
public ObservableCollection<string> PossibleItems
{
get
{
ObservableCollection<string> retVal = new ObservableCollection<string>();
retVal.Add("Item 1");
retVal.Add("Item 2");
return retVal;
}
}
// List of potential Items, used to populate the options for the Subitems combo box
public ObservableCollection<string> PossibleSubitems
{
get
{
ObservableCollection<string> retVal = new ObservableCollection<string>();
if (CurrentItem == PossibleItems[0])
{
retVal.Add("Subitem A");
retVal.Add("Subitem B");
}
else
{
retVal.Add("Subitem B");
retVal.Add("Subitem C");
}
return retVal;
}
}
// Track the selected Item
private string _currentItem;
public string CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
// Changing the item changes the possible sub items
NotifyPropertyChanged("PossibleSubitems");
}
}
// Track the selected Subitem
private string _currentSubitem;
public string CurrentSubitem
{
get { return _currentSubitem; }
set
{
if (PossibleSubitems.Contains(value))
{
_currentSubitem = value;
}
else
{
_currentSubitem = PossibleSubitems[0];
// We're not using the valuie specified, so notify that we have in fact changed
NotifyPropertyChanged("CurrentSubitem");
}
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
CurrentItem = PossibleItems[0];
CurrentSubitem = PossibleSubitems[0];
}
public event PropertyChangedEventHandler PropertyChanged;
internal void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I rewrote my own sample - kept it code behind so as not to deviate from your sample too much. Also, I'm using .NET 4.5 so didn't have to provide property names in OnPropertyChanged calls - you will need to insert them if on .NET 4.0. This works in all scenarios.
In practice, I'd recommend locating this code in a view-model as per the MVVM pattern. Aside from the binding of the DataContext, It wouldn't look too different from this implemenation though.
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow: Window, INotifyPropertyChanged
{
private string _currentItem;
private string _currentSubitem;
private ObservableCollection<string> _possibleItems;
private ObservableCollection<string> _possibleSubitems;
public MainWindow()
{
InitializeComponent();
LoadPossibleItems();
CurrentItem = PossibleItems[0];
UpdatePossibleSubItems();
DataContext = this;
CurrentItem = PossibleItems[0];
CurrentSubitem = PossibleSubitems[0];
PropertyChanged += (s, o) =>
{
if (o.PropertyName != "CurrentItem") return;
UpdatePossibleSubItems();
ValidateCurrentSubItem();
};
}
private void ValidateCurrentSubItem()
{
if (!PossibleSubitems.Contains(CurrentSubitem))
{
CurrentSubitem = PossibleSubitems[0];
}
}
public ObservableCollection<string> PossibleItems
{
get { return _possibleItems; }
private set
{
if (Equals(value, _possibleItems)) return;
_possibleItems = value;
OnPropertyChanged();
}
}
public ObservableCollection<string> PossibleSubitems
{
get { return _possibleSubitems; }
private set
{
if (Equals(value, _possibleSubitems)) return;
_possibleSubitems = value;
OnPropertyChanged();
}
}
public string CurrentItem
{
get { return _currentItem; }
private set
{
if (value == _currentItem) return;
_currentItem = value;
OnPropertyChanged();
}
}
public string CurrentSubitem
{
get { return _currentSubitem; }
set
{
if (value == _currentSubitem) return;
_currentSubitem = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void LoadPossibleItems()
{
PossibleItems = new ObservableCollection<string>
{
"Item 1",
"Item 2"
};
}
private void UpdatePossibleSubItems()
{
if (CurrentItem == PossibleItems[0])
{
PossibleSubitems = new ObservableCollection<string>
{
"Subitem A",
"Subitem B"
};
}
else if (CurrentItem == PossibleItems[1])
{
PossibleSubitems = new ObservableCollection<string>
{
"Subitem B",
"Subitem C"
};
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You're notifying the wrong property. On your CurrentItem, you call the "PossibleSubitems".
private string _currentItem;
public string CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
// Changing the item changes the possible sub items
NotifyPropertyChanged("PossibleSubitems");
}
}
Fix that and try again :)
WARNING ... THIS IS A HACK ...
I changed this to make it work (just because I was curious), but this is by no mean the proper way, nor an elegant one:
// List of potential Items, used to populate the options for the Subitems combo box
public ObservableCollection<string> PossibleSubitems { get; set; }
// Track the selected Item
private string _currentItem;
public string CurrentItem
{
get { return _currentItem; }
set
{
_currentItem = value;
// Changing the item changes the possible sub items
if (value == "Item 1")
PossibleSubitems = new ObservableCollection<string>() {"A","B"} ;
else
PossibleSubitems = new ObservableCollection<string>() { "C", "D" };
RaisePropertyChanged("CurrentItem");
RaisePropertyChanged("PossibleSubitems");
}
}
So basically, when current item change, it'll create new collection of subitems ...
UGLY !!! I know ... You could reuse those collections, and do lots of other things ... but as I said, I was curious about if it can be done this way ... :)
If this breaks your keyboard, or your cat runs away, I TAKE NO RESPONSIBILITY WHATSOEVER.
I am developing windows 8 store app. I wants to show the previously selected items in GridView if navigate back and fro, the selected items should be shown selected.I have tried This tutorial
and did exactly as suggested. but its not working in my case. I have also tried with index as
int index = myGridView.SelectedIndex
so that to find index and directly provide
myGridView.SelectedIndex = index ;
but its again not useful because I am not getting changes into the index in
SelectionChanged(object sender, SelectionChangedEventArgs e){};
What works is
myGridView.SelectAll();
it selects all the elements. but I don't want this. Please help me? Thanks in advance
Please refer my code
<GridView x:Name="MyList" HorizontalAlignment="Left" VerticalAlignment="Top" Width="auto" Padding="0" Height="600" Margin="0" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple" SelectionChanged="names_SelectionChanged" ItemClick="mylist_ItemClick" SelectedItem="{Binding Path=selectedItem}">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Width="260" Height="80">
<TextBlock Text="{Binding Path=Name}" Foreground="White" d:LayoutOverrides="Width" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This is The class I am dealing with
public sealed partial class MyClass: MyApp.Common.LayoutAwarePage, INotifyPropertyChanged
{
SQLite.SQLiteAsyncConnection db;
public MyClass()
{
this.InitializeComponent();
Constants.sourceColl = new ObservableCollection<MyModel>();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
getData();
foreach (MyModel item in Constants.sourceColl)
MyList.SelectedItems.Add(item);
}
private async void getData()
{
List<MyModel> mod = new List<MyModel>();
var query = await db.Table<MyModel>().Where(ch => ch.Id_Manga == StoryNumber).ToListAsync();
foreach (var _name in query)
{
var myModel = new MyModel()
{
Name = _name.Name
};
mod.Add(myModel);
Constants.sourceColl.Add(myModel);
}
MyList.ItemsSource = mod;
}
private void names_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
GridView myGridView = sender as GridView;
if (myGridView == null) return;
Constants.sourceColl = (ObservableCollection<MyModel>)myGridView.SelectedItems;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private MyModel _selectedItem;
public MyModel selectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
NotifyPropertyChanged("selectedItem");
}
}
}
}
Here is my model
class MyModel
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public String Name { get; set; }
}
Hello rahul I have just solved the problem you are facing it is not the perfect way but it will work in your code. try to follow it.
first I made a singleton class which store your previous selected items (lstSubSelectedItems)..like this
public class checkClass
{
static ObservableCollection<Subject> _lstSubSelectedItems = new ObservableCollection<Subject>();
static checkClass chkclss;
public static checkClass GetInstance()
{
if (chkclss == null)
{
chkclss = new checkClass();
}
return chkclss;
}
public ObservableCollection<Subject> lstSubSelectedItems
{
get
{
return _lstSubSelectedItems;
}
set
{
_lstSubSelectedItems = value;
}
}
}
i have filled lstSubSelectedItems on pagenavigationfrom method like this.. here lstsub is selectedsubjects..
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
checkClass obj = checkClass.GetInstance();
obj.lstSubSelectedItems = lstsub;
}
Here is the workaround what I have done in my constructor...
Here I removed the non selected items using removeat function of gridview.selecteditems other function are not doing this this for for (I don't know why). subject class is just like your model class . and also setting of selecteditems is not working that why I choose this way... Hope this help.
public SelectSubject()
{
this.InitializeComponent(); // not required
objselectsubjectViewmodel = new SelectSubjectViewModel(); // not required
groupedItemsViewSource.Source = objselectsubjectViewmodel.Categories; // not required the way set the itemssource of grid.
this.DataContext = this;
checkClass obj = checkClass.GetInstance();
if (obj.lstSubSelectedItems.Count > 0)
{
// List<Subject> sjsfj = new List<Subject>();
// ICollection<Subject> fg = new ICollection<Subject>();
itemGridView.SelectAll();
// int i = 0;
List<int> lstIndex = new List<int>();
foreach (Subject item1 in itemGridView.SelectedItems)
{
foreach (var item3 in obj.lstSubSelectedItems)
{
if (item3.SubjectCategory == item1.SubjectCategory && item3.SubjectName == item1.SubjectName)
{
lstIndex.Add(itemGridView.SelectedItems.IndexOf(item1));
}
}
}
int l = itemGridView.SelectedItems.Count;
for (int b = l-1; b >= 0; b--)
{
if (!lstIndex.Contains(b))
{
itemGridView.SelectedItems.RemoveAt(b);
}
}
}
}
tell me if it works for you...
You can set selectedItems property of gridView for doing this first make observableCollection and the continuously update this collection on selectionchange Event of your gridView . and when you comeback to this page set the GridViewName.SelectedItems = aboveCollection;
private ObservableCollection<Subject> lstsub = new ObservableCollection<Subject>() ;
private void itemGridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
checkTemp = 1;
GridView tempObjGridView = new GridView();
tempObjGridView = sender as GridView;
lstsub = tempObjGridView.SelectedItems;
}
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
yourGridName.SelectedItems = lstsub ;
}