I have a check list box from the wpf toolkit 2. I cannot get all of the selected items. I read that I am supposed to use SelectedItemsOverride to get all of my selected items but it does not seem to work. I put a break point in the setter for TestClassSelected but it is never fired. Any Ideas?
<xctk:CheckListBox Name="MyCheckList"
ItemsSource="{Binding TestClassCollection}"
DisplayMemberPath="DisplayName"
SelectedItemsOverride="{Binding TestClassSelected}" />
.
public IEnumerable<TestClass> TestClassCollection
{
get { return _testClassCollection; }
set
{
_testClassCollection = value;
OnPropertyChanged("TestClassCollection");
}
}
public List<TestClass> TestClassSelected
{
get { return _testClassSelected; }
set
{
_testClassSelected = value;
OnPropertyChanged("TestClassSelected");
}
}
.
public class TestClass
{
public String FirstName { get; set; }
public String LastName { get; set; }
public String DisplayName {
get { return string.Format("{0} {1}", FirstName, LastName); }
}
}
This is my test data I have been using.
TestClassCollection = new List<TestClass>
{
new TestClass {FirstName = "FIrstName", LastName = "LastName"},
new TestClass {FirstName = "Brad", LastName = "Holder"},
new TestClass {FirstName = "Sam", LastName = "Ryans"},
new TestClass {FirstName = "Ryan", LastName = "Thomas"},
new TestClass {FirstName = "Lee", LastName = "Rod"},
new TestClass {FirstName = "Amanda", LastName = "Gustaf"},
new TestClass {FirstName = "Chris", LastName = "Holems"},
new TestClass {FirstName = "Doug", LastName = "Schnitzel"},
new TestClass {FirstName = "Lisa", LastName = "Bull"},
new TestClass {FirstName = "Fred", LastName = "Simpson"},
new TestClass {FirstName = "Scott", LastName = "Rogers"}
};
try add Mode and UpdateSourceTrigger at binding
<xctk:CheckListBox Name="MyCheckList"
ItemsSource="{Binding TestClassCollection}"
DisplayMemberPath="DisplayName"
SelectedItemsOverride="{Binding TestClassSelected,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
update:
I'd checked example code from closed issue at Extended WPF Toolkit CodePlex site.
Try to change TestClassSelected property to ObservableCollection. (Still keep UpdateSourceTrigger described above in .xaml)
public ObservableCollection<TestClass> TestClassSelected
I faced the same problem and solved it by assigning the private field of the property
In your example
private ObservableCollection<TestClass> _testClassSelected=new ObservableCollection<TestClass>();
I combined previous answers/comments and what worked for me was to bind SelectedItemsOverride to an ObservableCollection<T>, include UpdateSourceTrigger=PropertyChanged, and attach a method to the ObservableCollection<T>'s event CollectionChanged.
The XAML:
<xctk:CheckListBox
ItemsSource="{Binding AllItems}"
DisplayMemberPath="Display"
ValueMemberPath="Value"
SelectedItemsOverride="{Binding Path=DataContext.SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</xctk:CheckListBox>
Code in ViewModel:
public IEnumerable<Item> AllItems { get; set; }
public ObservableCollection<Item> SelectedItems { get; set; }
public ViewModel()
{
SelectedItems = new ObservableCollection<Item>();
SelectedItems.CollectionChanged += SelectedItems_CollectionChanged;
}
private void SelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Handle collection changed event
}
Related
I'm upgrading a WPF app to UWP, and I'm stuck with upgrading the DataGrid control.
I'm trying to upgrade System.Windows.Controls.DataGrid (the old WPF control) -> Microsoft.Toolkit.Uwp.UI.Controls.DataGrid (the new MS recommended community control)
The old version works fine. It is populating the grid from a .NET DataTable.
XAML:
<DataGrid SelectionMode="Single" SelectionChanged="dataGridSongs_SelectionChanged" BorderThickness="1" BorderBrush="LightGray" Margin="0 0" IsReadOnly="True" GridLinesVisibility="None" Name="dataGridSongs" ItemsSource="{Binding}">
</DataGrid>
Code behind:
public MainWindow()
{
initializing = true;
InitializeComponent();
DataTable dt = dbHelper.GetAllSongs();
dataGridSongs.DataContext = dt.DefaultView;
dataGridSongs.HeadersVisibility = DataGridHeadersVisibility.None;
initializing = false;
}
The new UWP DataGrid gives me an error:
"BindingExpression path error: 'Item' property not found on 'System.Data.DataRowView'"
And the grid is populated with some strange data:
The new UWP app XAML:
<controls:DataGrid x:Name="dataGridSongs" ItemsSource="{Binding}">
</controls:DataGrid>
Code behind:
public MainPage()
{
initializing = true;
this.InitializeComponent();
DataTable dt = dbHelper.GetAllSongs();
dataGridSongs.DataContext = dt.DefaultView;
//dataGridSongs.ItemsSource = dt.DefaultView;
}
I assume I'm populating the grid with the wrong objects, however, I couldn't find any example for this simple scenario.
The UWP Community Toolkit DataGrid control doesn't support auto-generating columns for a DataView of a DataTable.
You may create the columns yourself though:
dataGridSongs.Columns.Clear();
dataGridSongs.AutoGenerateColumns = false;
for (int i = 0; i < dt.Columns.Count; i++)
{
dataGridSongs.Columns.Add(new DataGridTextColumn()
{
Header = dt.Columns[i].ColumnName,
Binding = new Binding { Path = new PropertyPath("[" + i.ToString() + "]") }
});
}
var sourceCollection = new ObservableCollection<object>();
foreach (DataRow row in dt.Rows)
sourceCollection.Add(row.ItemArray);
dataGridSongs.ItemsSource = sourceCollection;
You could refer to the following sample which shows how to bind a DataGrid to a data source:
public class Customer
{
public String FirstName { get; set; }
public String LastName { get; set; }
public String Address { get; set; }
public Boolean IsNew { get; set; }
public Customer(String firstName, String lastName,
String address, Boolean isNew)
{
this.FirstName = firstName;
this.LastName = lastName;
this.Address = address;
this.IsNew = isNew;
}
}
public class ViewModel
{
private List<Customer> m_customers;
public List<Customer> Customers { get { return m_customers; }
}
public ViewModel()
{
m_customers = new List<Customer>(new Customer[4] {
new Customer("A.", "Zero",
"12 North Third Street, Apartment 45",
false),
new Customer("B.", "One",
"34 West Fifth Street, Apartment 67",
false),
new Customer("C.", "Two",
"56 East Seventh Street, Apartment 89",
true),
new Customer("D.", "Three",
"78 South Ninth Street, Apartment 10",
true)
});
}
}
MainPage class
public sealed partial class MainPage : Page
{
public ViewModel MyViewModel;
public MainPage()
{
this.InitializeComponent();
MyViewModel = new ViewModel();
}
}
MainPage.xaml
<controls:DataGrid x:Name="dataGrid" AutoGenerateColumns="True"
ItemsSource="{x:Bind MyViewModel.Customers}">
</controls:DataGrid>
You could refer to the documetn for more information about data binding and customizing columns in DataGrid.
So I am currently trying to learn WPF with MVVM and I was following some tutorials online. Now that I have a simple Project, I tried to do it on my own but somehow the ListBox stays empty. It doesn't get bound to the ViewModel, I think. What am I missing here?
ViewModel:
public class PersonViewModel
{
ObservableCollection<Person> Personen { get; set; } = new ObservableCollection<Person>();
public PersonViewModel()
{
Personen.Add(new Person { Vorname = "My", Nachname = "Name", Email = "my#name.com" });
Personen.Add(new Person { Vorname = "Max", Nachname = "Mustermann", Email = "max#mustermann.de" });
Personen.Add(new Person { Vorname = "John", Nachname = "Doe", Email = "john#doe.com" });
Personen.Add(new Person { Vorname = "Jane", Nachname = "Doe", Email = "jane#doe.com" });
}
}
}
Also, I'm setting my DataContext like this:
public PersonenView()
{
InitializeComponent();
this.DataContext = new ViewModel.PersonViewModel();
}
You just need to give a public accessor to ObservableCollection Personen in your ViewModel.
You should also make the property readonly (or fire a property change notification from its setter):
public ObservableCollection<Person> Personen { get; }
= new ObservableCollection<Person>();
I have a view model that exposes an ListCollectionView and data grid that is bound to it. For some reason when swapping the ListCollectionView and creating a new one from the source collection the sorting is lost for the new items that are added to the source collection.
The source collection is an ObservableCollection.
The sorting is correct for the items that already exist in source collection on ListCollectionView creation.
I dont use GetDefaultView but instead create ListCollectionView myself whenever required.
When adding new items to the source collection no sorting is done and items appear at the end of the list.
I raise INotifyPropertyChanged when swapping ListCollectionView.
Anyone knows why i get such behavior ?
The problem is probably in however you create your listcollectionview. ( You should have posted that code. )
I put some experimental code together, which works as I expected. I just use a listbox but this will make no difference.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding AddPersonCommand}" Grid.Column="1"/>
<ListBox ItemsSource="{Binding People}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Viewmodel
public class MainWindowViewModel : BaseViewModel
{
private RelayCommand addPersonCommand;
public RelayCommand AddPersonCommand
{
get
{
return addPersonCommand
?? (addPersonCommand = new RelayCommand(
() =>
{
People.Add(new Person { FirstName = "Adam", LastName = "Barlow" });
}
));
}
}
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
public ListCollectionView LCV { get; set; }
public MainWindowViewModel()
{
LCV = (ListCollectionView)CollectionViewSource.GetDefaultView(People);
LCV.SortDescriptions.Add(
new SortDescription("LastName", ListSortDirection.Ascending));
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
}
}
When I click the button it adds Ken Barlow to the observablecollection and it appears top of the listbox.
I also tried binding to LCV:
And instantiating that passing people in via the ctor.
public class MainWindowViewModel : BaseViewModel
{
private RelayCommand addPersonCommand;
public RelayCommand AddPersonCommand
{
get
{
return addPersonCommand
?? (addPersonCommand = new RelayCommand(
() =>
{
Person person = new Person { FirstName = "Adam", LastName = "Barlow" };
People.Add(person);
}
));
}
}
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
public ListCollectionView LCV { get; set; }
public MainWindowViewModel()
{
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
LCV = new ListCollectionView(People);
LCV.SortDescriptions.Add(
new SortDescription("LastName", ListSortDirection.Ascending));
}
}
This also works.
Code
I have this UI
with this code
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
namespace WinFormsComboBoxDatabinding
{
public partial class Form1 : Form
{
public List<Person> PersonList { get; set; }
public Person SelectedPerson { get; set; }
public Form1()
{
InitializeComponent();
InitializePersonList();
InitializeDataBinding();
}
private void InitializePersonList()
{
PersonList = new List<Person>
{
new Person { FirstName = "Bob", LastName = "Builder" },
new Person { FirstName = "Mary", LastName = "Poppins" }
};
}
private void InitializeDataBinding()
{
SelectedPerson = PersonList[0];
var bindingSource = new BindingSource();
bindingSource.DataSource = PersonList;
comboBox.DisplayMember = "FirstName";
//comboBox.ValueMember = "LastName";
comboBox.DataSource = bindingSource;
textBoxFirstName.DataBindings.Add("Text", SelectedPerson, "FirstName");
textBoxLastName.DataBindings.Add("Text", SelectedPerson, "LastName");
}
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
SelectedPerson = comboBox.SelectedItem as Person;
Debug.WriteLine($"SelectedPerson: {SelectedPerson}");
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return $"{FirstName} {LastName}";
}
}
}
Questions
I have two questions about databinding:
When I select Mary in the ComboBox, the two TextBox controls don't get updated. Why is that? What did I do wrong?
When I change the text "Mary" in the ComboBox, the SelectedPerson object doesn't get updated with the new FirstName, say "Mary changed", from the ComboBox. How would I achieve that behaviour of changing the ComboBox FirstName to update the FirstName of the SelectedPerson? Or is that not possible with a ComboBox?
Other experiment
I've seen that one can set the two TextBox controls' Text property when comboBox_SelectedIndexChanged gets called, but that's not really databinding, is it. That would be manually doing all the updating logic.
Let me know if I need to add more details to the question.
You don't need the SelectedPerson variable. It looks like you just have the wrong DataSource wired up. Try it this way:
textBoxFirstName.DataBindings.Add("Text", bindingSource, "FirstName");
textBoxLastName.DataBindings.Add("Text", bindingSource, "LastName");
Try this
private void InitializeDataBinding()
{
SelectedPerson = PersonList[0];
var bindingSource = new BindingSource();
bindingSource.DataSource = PersonList;
comboBox.DisplayMember = "FirstName";
comboBox.DataSource = bindingSource;
textBoxFirstName.DataBindings.Add("Text", bindingSource, "FirstName");
textBoxLastName.DataBindings.Add("Text", bindingSource, "LastName");
}
private void comboBox_TextChanged(object sender, EventArgs e)
{
var selectedPerson = PersonList.FirstOrDefault(x => x.FirstName == comboBox.Text);
if (selectedPerson == null) return;
comboBox.SelectedItem = selectedPerson;
}
You just need to set the ComboBox.DataSource to the List<Person> object, represented by the PersonList property here.
Add a DataBinding to the controls that needs to be updated when the ComboBox selects a new element from its DataSource:
textBoxFirstName.DataBindings.Add("Text", PersonList, "FirstName");
The controls are updated automatically.
In the ComboBox SelectedIndexChanged handler, you can set the SelectedPerson property value to the current SelectedItem, casting it to the Person class.
public List<Person> PersonList { get; set; }
public Person SelectedPerson { get; set; }
private void InitializePersonList()
{
this.PersonList = new List<Person>
{
new Person { FirstName = "Bob", LastName = "Builder" },
new Person { FirstName = "Mary", LastName = "Poppins" }
};
}
private void InitializeDataBinding()
{
comboBox.DisplayMember = "FirstName";
comboBox.DataSource = this.PersonList;
textBoxFirstName.DataBindings.Add("Text", PersonList, "FirstName");
textBoxLastName.DataBindings.Add("Text", PersonList, "LastName");
}
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
this.SelectedPerson = (Person)(sender as ComboBox).SelectedItem;
}
I have a ListView that holds a list of names, each name comes from a binding to an object containing a "name" field, like this:
public class User {
public User() {}
public User(string name, int ID) {
this.name = name;
this.ID = ID;
}
public string name {set; get;}
public int ID {set; get;}
}
and the listView:
<ListView Margin="10" Name="lvName" DisplayMemberPath="name"/>
and in the code behind I set lvName.ItemSource to an ObservableCollection of User objects, in the collection I have Users that can have the same Name, but not the same ID.
I also have a dataGrid, with 2 columns defined(one for name and one for ID), I want to make it so that when the user selects a row from the listview(which shows names of Users), the datagrid would be populated with all the User objects(both the name and ID) that have the same name as was selected in the listview.
how can I do that?
Note: I managed to bind the name field to a textblock, by doing this:
Text="{Binding SelectedItem.name, ElementName=lvName}"
but I have no idea how to do it on a datagrid, let alone with the ID field as well as the name field.
This could be easily done using MVVM approach.
Assuming, that User class looks like this:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
you can define this view model:
public class ViewModel
{
public ViewModel()
{
Users = new ObservableCollection<User>
{
new User { Id = 1, Name = "John" },
new User { Id = 2, Name = "Mary" },
new User { Id = 3, Name = "Peter" },
new User { Id = 4, Name = "John" },
new User { Id = 5, Name = "John" },
new User { Id = 5, Name = "Peter" }
};
UsersView = new ListCollectionView(Users)
{
Filter = obj =>
{
var user = (User)obj;
return SelectedUser != null && user.Name == SelectedUser.Name && user.Id != selectedUser.Id;
}
};
}
public ObservableCollection<User> Users { get; private set; }
public ICollectionView UsersView { get; set; }
public User SelectedUser
{
get { return selectedUser; }
set
{
if (selectedUser != value)
{
selectedUser = value;
UsersView.Refresh();
}
}
}
private User selectedUser;
}
XAML:
<StackPanel>
<ListView DisplayMemberPath="Name" ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}"/>
<DataGrid ItemsSource="{Binding UsersView}"/>
</StackPanel>
Result: