How do you bind ObservableCollections to ItemsSource? - c#

DataContextDataContext context1 = new DataContextDataContext();
public MainWindow()
{
InitializeComponent();
DataContext = new ObservableCollection<MyObject>();
RadGridView1.Filtered+=new EventHandler<GridViewFilteredEventArgs>(RadGridView1_Filtered);
ObservableCollection<MyObject> _MyObject = new ObservableCollection<MyObject>();
foreach (var p in context1.Students)
{
_MyObject.Add(new MyObject { ID = p.StudentID, Name = p.StudentFN });
}
}
void RadGridView1_Filtered(object sender, GridViewFilteredEventArgs e)
{
RadGridView1.ItemsSource = ObservableCollection<MyObject>();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
public class MyObject
{
public int ID { get; set; }
public string Name { get; set; }
}
How do you bind my ObservableCollections to the ItemsSource?

You want to set the ItemSource to the instance of an ObservableCollection you created in the constructor:
RadGridView1.ItemsSource = _MyObject;

You can make the observable collection as a public property in your code-behind/presenter/viewmodel, like
public ObservableCollection<MyObject> MyObjectCollection {get;set;}
then you can populate that and the binding can be code code behind.
ItemsSource is a dependency property you can bind it in XAML or code behind, like suppose you want to bind to ListBox's(say named lstItems) ItemsSource, like (below code is considering that 'MyObjectCollection' is in codebehind
Binding bindingObject = new Binding("MyObjectCollection");
bindingObject.Source = this; //codebehind class instance which has MyObjectCollection
lstItems.SetBinding(ListBox.ItemsSource, bindingObject);
or in XAML,
<ListBox x:Name="lstItems" ItemsSource="{Binding Path=MyObjectCollection}"/>
for both the ways above you need to set the datacontext which is 'this' (for this specific solution).
But maybe you want to look into basic WPF databinding where you can understand Depedency properties, binding objects, binding modes, etc.
http://msdn.microsoft.com/en-us/library/aa480224.aspx
http://msdn.microsoft.com/en-us/library/ms750612.aspx
http://joshsmithonwpf.wordpress.com/2008/05/19/gradual-introduction-to-wpf-data-binding/

Related

Getting null in ViewModel from DependencyProperty after SetValue

I have this extended ListBox with a SelectedItems property to be able to get the selected items from when SelectionMode is set to Extended.
public class BindableMultiSelectListBox : ListBox
{
public static new readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(BindableMultiSelectListBox),
new PropertyMetadata(default(IList)));
public new IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => throw new Exception("This property is read-only. To bind to it you must use 'Mode=OneWayToSource'.");
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
SetValue(SelectedItemsProperty, base.SelectedItems);
}
}
I use this in a view like this:
<usercontrols:BindableMultiSelectListBox SelectedItems="{Binding SelectedDogs, Mode=OneWayToSource}" ItemsSource="{Binding Dogs}" SelectionMode="Extended"/>
and bind to an ObservableCollection in my viewModel:
private ObservableCollection<string> _selectedDogs;
public ObservableCollection<string> SelectedDogs
{
get
{
return _selectedDogs;
}
set
{
_selectedDogs = value;
OnPropertyChanged(nameof(SelectedDogs));
}
}
When I debug the OnSelectionChanged base.SelectedItems have the selected values as expected, but when the SelectedDogs setter is hit the value is null!
What have I done wrong here?
The target property is supposed to bind to the source property.
Initialize the source collection in the view model:
public ObservableCollection<string> SelectedDogs { get; } = new ObservableCollection<string>();
...and implement the synchronization in the control:
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (SelectedItems != null)
{
foreach (var removed in e.RemovedItems)
SelectedItems.Remove(removed);
foreach (var added in e.AddedItems)
SelectedItems.Add(added);
}
}
XAML:
... SelectedItems="{Binding SelectedDogs}"
If you want to set the source property, you should initialize the collection in the control.
Changing the SelectedDogs property in the viewModel from ObservableCollection<string> to just IList fixed the problem.
It seems to make little difference in practice, but what if I want to be able to specify ObservableCollection in my viewModel? Why doesn't ObservableCollection work? It does inherit from Collection which implements IList.

How to make a bound data member a default value in WPF ComboBox?

I have an ObservableCollection<string> named MyCollection containing "A", "B", "C", "D". I create a view like this:
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding MyCollection}"
SelectedIndex="{Binding 3}"/>
<Button Click="OnClickButton">Button</Button>
Then my codebehind looks like this:
public partial class MyClass {
private string _mySelection;
public string MySelection
{
get { return _mySelection; }
set
{
_mySelection = value;
}
}
public void OnClickButton(object sender, RoutedEventArgs e) {
MySelection = (MyComboBox.SelectedItem).ToString();
MessageBox.Show(MySelection);
}
This is fine. The ComboBox populates just as it should, and MySelection is set properly and appears in the message box. But currently, my ComboBox appears blank in the user interface until the user clicks on it and selects an option. I want option C to be the default value, and if the user doesn't select anything, then MySelection will be set to C.
But no matter how many different combinations of SelectedItem, SelectedValue, and SelectedIndex I try, I can't get it to work. The ComboBox always starts off empty.
How can I do this?
Set a default value of the _mySelection field, i.e. "C"
Or, more general, set the value of MySelection to the desired default value after construction.
Also make sure that the MySelection property fires a change notification.
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> MyCollection { get; }
= new ObservableCollection<string>();
private string mySelection;
public string MySelection
{
get { return mySelection; }
set
{
mySelection = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(MySelection)));
}
}
}
Initialize the DataContext of the view with an instance of MyViewModel:
public MainWindow()
{
InitializeComponent();
var vm = new MyViewModel();
vm.MyCollection.Add("A");
vm.MyCollection.Add("B");
vm.MyCollection.Add("C");
vm.MyCollection.Add("D");
vm.MySelection = "C";
DataContext = vm;
}
private void OnClickButton(object sender, RoutedEventArgs e)
{
MessageBox.Show(((MyViewModel)DataContext).MySelection);
}

Binding to WPF Combo Box

I have a model class that I wish to bind a combo box to. My plan was to have an object with two propertied. 1) an ObservableCollection that contains the items I want to populate the combo box with. 2) A string property that stores the value of the selected item. I cannot seem to get this to work and open to suggestions. I am Trying to follow MVVM as best as possible. The behavior I observe is an empty combo box.
The class looks like this.
public class WellListGroup : Notifier
{
private ObservableCollection<string> _headers;
public ObservableCollection<string> headers
{
get { return this._headers; }
set { this._headers = value; OnPropertyChanged("headers"); }
}
private string _selected;
public string selected
{
get { return this._selected;}
set { this._selected = value; OnPropertyChanged("selected");}
}
}
Notifier looks like:
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And my viewmodel makes a call to a data access layer that creates the following object i wish to bind to.
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
public WellListGroup wlg = new WellListGroup {headers = headers, selected = null};
}
Data Access Layer - getHeaders()
public ObservableCollection<string> getHeaders()
{
ObservableCollection<string> vals = new ObservableCollection<string>();
WVWellModel wvm = new WVWellModel();
var properties = getProperties(wvm);
foreach (var p in properties)
{
string name = p.Name;
vals.Add(name);
}
return vals;
}
Then the view:
<ComboBox DockPanel.Dock="Top" ItemsSource = "{Binding Path = wlg.headers}" SelectedItem="{Binding Path = wlg.selected}"></ComboBox>
View Code Behind (Where the Data Context is set)
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
MainViewModel mvm = new MainViewModel();
DataContext = mvm;
}
}
App.xaml.cs
public partial class App : Application
{
private void OnStartup(object sender, StartupEventArgs e)
{
Views.MainView view = new Views.MainView();
view.Show();
}
private void APP_DispatcherUnhandledException(object sender,DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show(e.Exception.Message);
e.Handled = true;
}
}
I have tried several iterations of this but cant for the life of me get this to work. I am presented with an empty combo box.
I am going to assume DataContext is set to MainViewModel on the view.
I think you well list group should call OnPropertyChanged
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = null};
public WellListGroup wlg
{
get { return _wlg; }
set { _wlg = value; OnPropertyChanged("wlg"); }
}
The combo box binding should look like this:
<ComboBox
ItemsSource = "{Binding wlg.headers}"
SelectedItem = "{Binding wlg.selected Mode=TwoWay}"
/>
If neither of those work I would make sure the MainViewModel is being instantiated and assigned to DataContext in the Page constructor or a page loaded event.
Here is a code project Tutorial that may help break down the binding process Step by Step WPF Data Binding with Comboboxes

TreeView with ObservableCollection Source, no update

I create a simple Treeview that I bound to an ObservableCollection.
ObservableCollection<IMarketDataViewModel> MarketDataItems;
public interface IMarketDataViewModel
{
string Title { get; }
ObservableCollection<IMarketDataViewModel> Items { get; set; }
}
public MarketDataUserControl(IMarketDataViewer viewModel)
{
InitializeComponent();
DataContext = viewModel;
marketDataTreeView.ItemsSource = viewModel.MarketDataItems;
}
When I update data in my ViewModel, I only see the first level in my Treeview. The only way I found to resolve the problem is to create an event in my ViewModel and when the data is updated instead calling PropertyChange on MarketDataItems, I trigger the event and the View reset marketDataTreeView.ItemsSource like this :
private void ViewModelOnOnUpdateItems()
{
marketDataTreeView.ItemsSource = null;
marketDataTreeView.ItemsSource = viewModel.MarketDataItems;
}
And this work perfectly --> All levels are displayed.
Someone know why the PropertyChange doesn't work and why I have to reset the ItemsSource ?
I think you should implement a binding to the ItemSource and this is done by a property:
// Create property
public ObservableCollection<IMarketDataViewModel> MarketDataItems { get; private set; }
...
// Create Binding
Binding bindingObject = new Binding("MarketDataItems");
bindingObject.Source = this; //codebehind class instance which has MarketDataItems
marketDataTreeView.SetBinding(TreeView.ItemsSource, bindingObject);
Or the binding in XAML:
<TreeView x:Name="marketDataTreeView" ItemsSource="{Binding Path=MarketDataItems}"/>
Finally the issue is that I didn't call OnPropertyChanged("Items")
public class MarketDataViewModelBase : IMarketDataViewModel, INotifyPropertyChanged
{
.....
private ObservableCollection<IMarketDataViewModel> items;
public ObservableCollection<IMarketDataViewModel> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged("Items"); //Add this line fix my issue
}
}
}

Filtering a xaml list box

Hi I have a list box with some customer names, I want to filter the list based on the text entered in the TextBox. After researching a bit I heard that we can use CollectionViewSourse and ICollectionView but did not get a stage where I could get it working.
Could you please suggest on how to achieve this.
Your help is much appreciated.
XAML
<TextBox x:Name="txtSearch"/>
<ListBox x:Name="lbCustomers">
XAML.cs
List<string> customerList;
public MainPage()
{
this.InitializeComponent();
customerList = new List<string>();
customerList.Add("Andrew");
customerList.Add("bravo");
customerList.Add("Carol");
customerList.Add("Dela");
customerList.Add("Eva");
customerList.Add("family");
customerList.Add("George");
customerList.Add("Health");
customerList.Add("Illa");
customerList.Add("Jack");
customerList.Add("Andrew");
lbCustomers.ItemsSource = customerList;
CollectionViewSource collectionViewSource = new CollectionViewSource();
collectionViewSource.Source = customerList;
ICollectionView collectionView = collectionViewSource.View;
}
Edit:
I can not access 'CollectionViewSource.GetDefaultView' and 'view.Filter'. I get an error : 'collectionviewsource does not contain a definition for getdefaultview'
When I looked into the definition i did not find the 'GetDefaultView' and 'Filter dependency properties'
public sealed class CollectionViewSource : DependencyObject, ICollectionViewSource
{
public CollectionViewSource();
public static DependencyProperty IsSourceGroupedProperty { get; }
public static DependencyProperty ItemsPathProperty { get; }
public static DependencyProperty SourceProperty { get; }
public static DependencyProperty ViewProperty { get; }
public System.Boolean IsSourceGrouped { get; set; }
public PropertyPath ItemsPath { get; set; }
public System.Object Source { get; set; }
public ICollectionView View { get; }
}
Try getting the default collection view for your collection. Every time your txtSearch changes you have to change the filter.
ICollectionView view = CollectionViewSource.GetDefaultView(customerList);
view.Filter = obj =>
{
string item = obj as string;
return (item.ToLower().Contains(YourFilter));
};
I would advice you to read about data binding and how it use it to bind listboxes and textboxes and manage your collections in your viewmodels.
But to fix your problem as it is.
Define your IcollectionView at a global level just like customerList and in your main change your code to
CollectionViewSource collectionViewSource = new CollectionViewSource();
collectionViewSource.Source = customerList;
collectionView = collectionViewSource.View;
collectionView.Filter = collectionFilter;
lbCustomers.ItemsSource = collectionView;
and add these 2 additional methods
private bool collectionFilter(object obj)
{
if (string.IsNullOrWhiteSpace(txtSearch.Text))
return true;
string name = obj.ToString();
return name.Contains(txtSearch.Text);
}
private void TxtSearch_OnTextChanged(object sender, TextChangedEventArgs e)
{
collectionView.Refresh();
}
change textbox to
<TextBox x:Name="txtSearch" TextChanged="TxtSearch_OnTextChanged"/>
These should be self explanatory changes but if you need any help i am happy to explain
The filter method is where you define your logic of filtering the listbox items on display

Categories

Resources