Binding a collection to a custom control property - c#

I am having no luck trying to bind a collection of data to my custom control its property. I already have implemented the mechanism for a string property of this control (with a small help from here) and expected the collection type to be as easy. However I cannot make it work again.
Here is my custom control view
<UserControl x:Class="BadaniaOperacyjne.Controls.Matrix"
mc:Ignorable="d" Name="CustomMatrix"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<!-- ... -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<!-- ... -->
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ElementName=CustomMatrix, Path=Title}"/>
<Grid Grid.Row="2" Grid.Column="1" Name="contentGrid">
<ListBox ItemsSource="{Binding ElementName=CustomMatrix, Path=ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</UserControl>
and its code-behind
#region ItemsList Property
public static readonly DependencyProperty ItemsListProperty =
DependencyProperty.Register("ItemsList", typeof(ObservableCollection<object>), typeof(Matrix), new PropertyMetadata(new ObservableCollection<object>(), new PropertyChangedCallback(ItemsListChanged)));
public ObservableCollection<object> ItemsList
{
get { return GetValue(ItemsListProperty) as ObservableCollection<object>; }
set { SetValue(ItemsListProperty, value); }
}
private void ItemsListChanged(object value)
{
System.Diagnostics.Debug.WriteLine("matrix: items list changed " + value);
if (ItemsList != null)
{
ItemsList.CollectionChanged += ItemsList_CollectionChanged;
System.Diagnostics.Debug.WriteLine("got " + string.Join(",", ItemsList.ToList()));
}
else
{
System.Diagnostics.Debug.WriteLine("got null");
}
}
void ItemsList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("matrix: current items list collection changed");
}
private static void ItemsListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// a breakpoint
((Matrix)d).ItemsListChanged(e.NewValue);
}
#endregion
// showing the Title property implementation just to state that
// it is done the same way as for ItemsList
#region Title Property
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(Matrix), new PropertyMetadata("", new PropertyChangedCallback(TitleChanged)));
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
private void TitleChanged(string title)
{
System.Diagnostics.Debug.WriteLine("matrix: title changed to: " + title);
}
private static void TitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Matrix)d).TitleChanged((string)e.NewValue);
}
#endregion
And here's how I am trying to bind to that control
<custom:Matrix x:Name="customMatrix" DockPanel.Dock="Top" Title="{Binding Title}" ItemsList="{Binding Items}"/>
and the code-behind for the main page is
//internal ObservableCollection<List<int>> ItemsList { get; set; }
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
Items = new ObservableCollection<int> { 1, 2, 3, 4, 5, 6};
}
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("problem manager: items list changed " + e.NewItems.Count);
}
public ObservableCollection<int> Items { get; set; }
protected string title;
public string Title
{
get { return title; }
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged("Title");
}
}
}
protected void NotifyPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public ViewModel VM { get; private set; }
// this is the window constructor
private ProblemManager()
{
VM = new ViewModel();
DataContext = VM;
InitializeComponent();
VM.Title = "title";
}
private int i = 0;
private void btnAddRow_Click(object sender, RoutedEventArgs e)
{
// when doing either of these two lines below,
// the control breakpoint is never hit
VM.Items.Add(++i);
VM.Items = new ObservableCollection<int> { 2, 3 };
// however, when directly assigning to the control's property,
// the event is raised and the breakpoint is hit and the UI is updated
customMatrix.ItemsList = new ObservableCollection<object> { 1, 2, 3 };
customMatrix.ItemsList.Add(66);
// and this of course makes the control's title update
VM.Title = (++i).ToString();
}
Both DependencyPropertys for the control's Title and ItemsList are, I believe, created the same way. Nonetheless, the binding is probably not working as the ItemsListChanged event is not raised by that binding.
So, the problem is that I cannot bind my window's ViewModel.Items collection via XAML to the control's ItemsList collection. Is creating a DependencyProperty for a collection within a control any different from DependencyProperty for a simple string property?

Problem is in your DependencyProperty registration. Co-variance is not applicable for generic lists i.e. you cannot do this -
ObservableCollection<object> objects = new ObservableCollection<int>();
You have declared type of DP as ObservableCollection<object> but binding it with list of type ObservableCollection<int>.
You should change either type of DP to ObservableCollection<int> OR change binding collection type to ObservableCollection<object>.
public ViewModel()
{
Items = new ObservableCollection<object> { 1, 2, 3, 4, 5, 6};
}
public ObservableCollection<object> Items { get; set; }

After such a long time, I kind of needed this to work in both ways:
Be able to define a collection of any kind of items
Be informed when an item was added/removed and so on.
I am working on a bread crumb like control to be more precise.
Indeed, the ItemsSource DP is defined with an IList type, to allow the "genericity" aforementioned:
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IList), typeof(BreadCrumbUserControl),
new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
And here is how I use my BreadCrumb:
<views:BreadCrumbUserControl ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
where Items is an ObservableCollection of a custom type:
public ObservableCollection<BaseItem> Items { get; set; }
Indeed it works fine, my ItemsSource local property points to the right collection but I need to be informed in my UserControl when an item is added to the ObservableCollection.
So, I've made use of the PropertyChangedCallback delegate on my ItemsSource DP.
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BreadCrumbUserControl thisUserControl = (BreadCrumbUserControl)d;
(e.NewValue as INotifyCollectionChanged).CollectionChanged += thisUserControl.BreadCrumbUserControl_CollectionChanged;
}
This is the trick that worked for me, because my input reference is in fact the ObservableCollection defined in the upper layers.
Both goals are now achieved: work with collection of any custom types and in the same type, stay informed.
If a protocol is set, reflection can be used to change values on our custom type.
Here is just an example of iterating on my collection which has some unknown type in my UserControl's world:
foreach (var baseItem in ItemsSource)
{
baseItem.GetType().GetProperty("IsActive").SetValue(baseItem, false);
}

Related

SelectedValue binding of a nested DataGrid doesn't work while ItemsSource does

I have a DataGrid inside of a UserControl which in turn lies inside of another UserControl. This is due to other needs of the project and I can't change this nested architecture. I'm binding a list of Person class to this DataGrid. This is a dumbed-down version without using a VM, but in my real project I am using a VM.
My UserControl with the DataGrid:
<Grid>
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"
MouseDoubleClick="MyDg_MouseDoubleClick"
SelectedValue="{Binding SelectedValue, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Code Behind:
public partial class UCDataGrid : UserControl
{
public event RoutedEventHandler RoutedDataGridDoubleClick;
public UCDataGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
private void MyDg_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
RoutedDataGridDoubleClick?.Invoke(this, new RoutedEventArgs());
}
}
2nd UserControl that contains the above control:
<Grid>
<ContentControl Content="{Binding MyDataGrid, ElementName=ucDisplay}"/>
</Grid>
ucDisplay is simply the Name property value of this UserControl.
Code Behind:
Nothing fancy here.
public partial class UCDisplay : UserControl
{
public UCDisplay()
{
InitializeComponent();
}
public static readonly DependencyProperty MyDataGridProperty = DependencyProperty.Register("MyDataGrid", typeof(object), typeof(UCDisplay), new PropertyMetadata(null));
public object MyDataGrid
{
get { return GetValue(MyDataGridProperty); }
set { SetValue(MyDataGridProperty, value); }
}
}
Main Window
In my Main Window, I bind my People list as well as SelectedPerson instance, like so:
<Grid>
<local:UCDisplay>
<local:UCDisplay.MyDataGrid>
<local:UCDataGrid ItemsSource="{Binding People}"
SelectedValue="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged}"
RoutedDataGridDoubleClick="UCDataGrid_RoutedDataGridDoubleClick"/>
</local:UCDisplay.MyDataGrid>
</local:UCDisplay>
</Grid>
Code Behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private List<Person> people;
public List<Person> People
{
get => people;
set => SetField(ref people, value);
}
private Person selectedPerson;
public Person SelectedPerson
{
get => selectedPerson;
set => SetField(ref selectedPerson, value);
}
public MainWindow()
{
InitializeComponent();
People = GetPeople();
DataContext = this;
}
private void UCDataGrid_RoutedDataGridDoubleClick(object sender, RoutedEventArgs e)
{
}
private List<Person> GetPeople()
{
return new List<Person>
{
new Person() { Name = "A" },
new Person() { Name = "B" },
new Person() { Name = "C" }
};
}
public class Person
{
public string Name { get; set; }
}
}
Again, in reality I'm using a VM, this is only to keep things simple.
Now when I run this I can display my list content just fine. But when I double-click an item in my DataGrid, in the corresponding in my Main Window code behind, the SelectedPerson remains null, although its binding is identical to the People list. I confirm this by using a break point in the main code behind:
But if I debug and see the value in the code behind of my innermost UserControl, you see that the SelectedValue there has the correct selected items value.
So what am I doing wrong here? Why can't I seem to bind the SelectedValue although I do it exactly the same as my ItemsSource binding, but the latter works?
SelectedValue is supposed to be used in conjunction with SelectedValuePath. You should use SelectedItem instead.
Besides that, you are missing a TwoWay Binding. Either explicitly declare the SelectedItem Binding TwoWay
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding SelectedItem,
RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}"/>
or register the property to bind TwoWay by default:
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem), typeof(object), typeof(UCDataGrid),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Also note that setting UpdateSourceTrigger=PropertyChanged is pointless in all your Bindings.

ComboBox, is it possible to have ItemsSource and SelectedValue bound to different sources?

I have two different objects that are pointing at each other. The first object represents a division in a company. That object has two collection: Employees, which is all the employees working in the division and Project, which is all the special projects that are in progress within that division. So the first object looks like this:
public class Division : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
ObservableCollection<Employee> _employees;
ObservableCollection<Project> _projects;
public Division()
{
Employees = new ObservableCollection<Employee>();
Projects = new ObservableCollection<Project>();
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
if (_employees != value)
{
_employees = value;
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
}
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
if (_projects != value)
{
_projects = value;
PropertyChanged(this, new PropertyChangedEventArgs("Projects"));
}
}
}
public void AddNewProject()
{
this.Projects.Add(new Project(this));
}
}
Notice that when adding a new project to the division, I pass a reference to the division into that project, which looks like this:
public class Project : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
string _projectName;
DateTime _deadline = DateTime.Now;
Division _division;
ObservableCollection<Employee> _members;
public Project()
{
Members = new ObservableCollection<Employee>();
}
public Project(Division div)
{
Members = new ObservableCollection<Employee>();
Division = div;
}
public string ProjectName
{
get { return _projectName; }
set
{
if (_projectName != value)
{
_projectName = value;
PropertyChanged(this, new PropertyChangedEventArgs("ProjectName"));
}
}
}
public DateTime Deadline
{
get { return _deadline; }
set
{
if (_deadline != value)
{
_deadline = value;
PropertyChanged(this, new PropertyChangedEventArgs("Deadline"));
}
}
}
public Division Division
{
get { return _division; }
set
{
if (_division != value)
{
if (_division != null)
{
_division.Employees.CollectionChanged -= members_CollectionChanged;
}
_division = value;
if (_division != null)
{
_division.Employees.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Division"));
}
}
}
public ObservableCollection<Employee> Members
{
get { return _members; }
set
{
if (_members != value)
{
if (_members != null)
{
_members.CollectionChanged -= members_CollectionChanged;
}
_members = value;
if (_members != null)
{
_members.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Members"));
}
}
}
public ObservableCollection<Employee> AvailableEmployees
{
get
{
if (Division != null){
IEnumerable<Employee> availables =
from s in Division.Employees
where !Members.Contains(s)
select s;
return new ObservableCollection<Employee>(availables);
}
return new ObservableCollection<Employee>();
}
}
void members_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableEmployees"));
}
}
The reason I'm doing it like this is, that the project could have any type of team working on it, but only from within the division. So, when building a dashboard for the division, the manager could select any of the employees to that project but without putting in an employee that is already assigned to it. So, the AvailableEmployees property in the project object always keeps track of who is not already assigned to that project.
The problem I'm having is how to translate this into a UI. The experiment I've done so far looks like this:
<UserControl x:Class="Test.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:local="clr-namespace:Test.Views"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<ListBox ItemsSource="{Binding Div.Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Transparent"
BorderThickness="0, 0, 0, 2"
BorderBrush="Black"
Margin="0, 0, 0, 5"
Padding="0, 0, 0, 5">
<StackPanel>
<TextBox Text="{Binding ProjectName}"/>
<ListBox ItemsSource="{Binding Members}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
Text="{Binding FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add Employee to Project"
Command="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AddEmployeeToProject}"
CommandParameter="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add New Project"
Command="{Binding AddNewProject}" />
</StackPanel>
The view model associated with this view is as follows:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private Division _div;
public TestViewModel(Division div)
{
Div = div;
AddNewProject = new DelegateCommand(OnAddNewProject);
AddEmployeeToProject = new DelegateCommand<Project>(OnAddEmployeeToProject);
}
public DelegateCommand AddNewProject { get; set; }
public DelegateCommand<Project> AddEmployeeToProject { get; set; }
public Division Div
{
get { return _div; }
set
{
if (_div != value)
{
_div = value;
PropertyChanged(this, new PropertyChangedEventArgs("Div"));
}
}
}
private void OnAddNewProject()
{
Div.AddNewProject();
}
private void OnAddEmployeeToProject(Project proj)
{
var availables = proj.AvailableEmployees;
if (availables.Count > 0)
{
proj.Members.Add(availables[0]);
}
}
}
However, I cannot get the combobox for each employee in each project to work. It seems like the selected item/value is bound to the itemssource, and each time the combobox turns out blank. I've tried to do this also with SelectedValue and SelectedItem properties for the combobox, but none worked.
How do I get these two separated. Is there anything else I'm missing here?
OK. After so many experiments the best solution I came up with was to create my own user control that is composed of both a button and a combobox that imitate the behavior I was expecting of the combobox on it own.
First, I had a really stupid mistake in the model where both lists of members Project and Division contain the same instances of Employee, which makes the AvailableEmployees property buggy. What I really needed to do is to create a list of copies of employees in the Project instead of just references.
In any case, I created a new user control and called it DynamicSourceComboBox. The XAML of this control looks like this:
<Grid>
<Button x:Name="selected"
Content="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=SelectedValue}"
Click="selected_Click"/>
<ComboBox x:Name="selections"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=ItemsSource}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=DisplayMemberPath}"
Visibility="Collapsed"
SelectionChanged="selections_SelectionChanged"
MouseLeave="selections_MouseLeave"/>
</Grid>
I have here a few bindings from the button and the combobox to properties in my user control. These are actually dependency properties. The code-behind of my user control looks like this:
public partial class DynamicSourceComboBox : UserControl
{
public DynamicSourceComboBox()
{
InitializeComponent();
}
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(DynamicSourceComboBox), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
ComboBox.ItemsSourceProperty.AddOwner(typeof(DynamicSourceComboBox));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty =
ComboBox.DisplayMemberPathProperty.AddOwner(typeof(DynamicSourceComboBox));
private void selected_Click(object sender, RoutedEventArgs e)
{
selected.Visibility = Visibility.Hidden;
selections.Visibility = Visibility.Visible;
selections.IsDropDownOpen = true;
}
private void selections_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
if (e.AddedItems.Count == 1)
{
var item = e.AddedItems[0];
Type itemType = item.GetType();
var itemTypeProps = itemType.GetProperties();
var realValue = (from prop in itemTypeProps
where prop.Name == DisplayMemberPath
select prop.GetValue(selections.SelectedValue)).First();
SelectedValue = realValue;
}
}
private void selections_MouseLeave(object sender, MouseEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
}
}
These dependency properties imitate the properties with similar names in ComboBox but they are hooked up to the internal combobox and the button in a way that makes them behave together as a single complex combobox.
The Click event in the button hides it and present the combobox to make the effect of just a box that is opening. Then I have a SelectionChanged event in the combobox firing to update all the needed information and a MouseLeave event just in case the user doesn't make any real selection change.
When I need to use the new user control, I set it up like this:
<local:DynamicSourceComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType=ListBox}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
SelectedValue="{Binding FirstName, Mode=TwoWay}"/>
Of course, for all of it to work, I have to make a lot of hookups with PropertyChanged events in the models, so the Projects instance will know to raise a PropertyChanged event for AvailableEmployees any time a change is made, but this is not really the concern of this user control itself.
This is a pretty clunky solution, with a lot of extra code that is a bit hard to follow, but it's really the best (actually only) solution I could have come up with to the problem I had.

Set ListBox SelectedItems based on another collection

I have a ListBox that is bound to MyCollection of type IEnumerable<string>.
<ListBox x:Name="ListBox" ItemsSource="{Binding MyCollection, Mode=OneWay}" SelectionMode="Multiple"/>
I have another List<string> SubCollection that contains a subset of MyCollection
Whenever SubCollection changes, I'd like the SelectedItems to be highlighted as per the SubCollection
Is there any Binding, Behaviour or any other way to accomplish this?
Edit:
Let's say I have a ListBox bound to MyCollection { "Orange", "Mango", "Stawberry", "Pineapple" }
Let's assume that I press a button to load data from a database and the result is "Orange", "Mango" which is then placed into to SubCollection. ListBox should now have "Orange", "Mango" as its SelectedItems.
I have a suggestion for you.
You can bind to the "IsSelected" property of the ListBoxItem.
To do that you have to use a collection of objects(let's say MyListBoxItem) instead of collection of strings.
public class MyListBoxItem
{
public string Description { get; set; }
public bool IsSelected { get; set; }
}
Use this "IsSelected" property of the MyListBoxItem class to bind the "IsSelected" property of the ListBoxItem.
Ex: In you view model,
this.MyCollection = new ObservableCollection<MyListBoxItem>();
MyListBoxItem item1 = new MyListBoxItem()
item1.Description = "Mango";
item1.IsSelected = true;
MyCollection .add(item1);
MyListBoxItem item2 = new MyListBoxItem()
item2 .Description = "Orange";
item2 .IsSelected = false;
MyCollection .add(item2 );
XAML (Inside the ListBox)
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
You could create a List AttachedProperty for the ListBox that adds items to the ListBox.SelectedItems when the list is changed.
The AttachedProperty solution keeps WPF clean and your MVVM pattern, Also it makes this feature reusable thoughout all your projects :)
Here is an example:
AttachedProperty:
public static class ListBoxExtensions
{
// Using a DependencyProperty as the backing store for SearchValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemListProperty =
DependencyProperty.RegisterAttached("SelectedItemList", typeof(IList), typeof(ListBoxExtensions),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSelectedItemListChanged)));
public static IList GetSelectedItemList(DependencyObject obj)
{
return (IList)obj.GetValue(SelectedItemListProperty);
}
public static void SetSelectedItemList(DependencyObject obj, IList value)
{
obj.SetValue(SelectedItemListProperty, value);
}
private static void OnSelectedItemListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listbox = d as ListBox;
if (listbox != null)
{
listbox.SelectedItems.Clear();
var selectedItems = e.NewValue as IList;
if (selectedItems != null)
{
foreach (var item in selectedItems)
{
listbox.SelectedItems.Add(item);
}
}
}
}
}
Xaml Usage:
<ListBox ItemsSource="{Binding Items}" SelectionMode="Multiple"
local:ListBoxExtensions.SelectedItemList="{Binding SelectedItems}" />
Demo:
Working example if you want to test:
Xaml:
<Window x:Class="WpfApplication17.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication17"
Title="MainWindow" Height="227" Width="170" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<ListBox ItemsSource="{Binding Items}" SelectionMode="Multiple"
local:ListBoxExtensions.SelectedItemList="{Binding SelectedItems}" Margin="0,0,0,37" >
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="{x:Static SystemColors.HighlightColor}" />
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
<Button Content="Populate SelectedItemList" Click="Button_Click" Height="32" Margin="2,0,1,2" VerticalAlignment="Bottom"/>
</Grid>
</Window>
Code:
namespace WpfApplication17
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private List<string> _selectedItems = new List<string>();
private ObservableCollection<string> _items = new ObservableCollection<string>
{ "Orange", "Mango", "Stawberry", "Pineapple", "Apple", "Grape", "Banana" };
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<string> Items
{
get { return _items; }
set { _items = value; }
}
public List<string> SelectedItems
{
get { return _selectedItems; }
set { _selectedItems = value; OnPropertyChanged("SelectedItems"); }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SelectedItems = new List<string> { "Orange", "Pineapple", "Apple" };
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(e));
}
}
public static class ListBoxExtensions
{
// Using a DependencyProperty as the backing store for SearchValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemListProperty =
DependencyProperty.RegisterAttached("SelectedItemList", typeof(IList), typeof(ListBoxExtensions),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSelectedItemListChanged)));
public static IList GetSelectedItemList(DependencyObject obj)
{
return (IList)obj.GetValue(SelectedItemListProperty);
}
public static void SetSelectedItemList(DependencyObject obj, IList value)
{
obj.SetValue(SelectedItemListProperty, value);
}
private static void OnSelectedItemListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listbox = d as ListBox;
if (listbox != null)
{
listbox.SelectedItems.Clear();
var selectedItems = e.NewValue as IList;
if (selectedItems != null)
{
foreach (var item in selectedItems)
{
listbox.SelectedItems.Add(item);
}
}
}
}
}
}
Result:
The standard data binding doesn’t work, because the SelectedItems property is read-only.
One simple approach, is to manually iterate the MyCollection and Set the IsSelected Property of each item, based on the items in SubCollection.
For this, the items in MyCollection list should contain objects inherited from ListBoxItem which would expose the IsSelected Property.
Here is a sample WPF app to demostrate it :
The button click would update the selected items based on items in SubCollection list. For now I have hardcode the values of SubCollection list.
Based on you implementation you could update your SubCollection, and hook the code inside button click event to any other events appropriately.(like make SubCollection list as ObservableCollection and hook to ObservableCollection.CollectionChange)
MainWindow.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="250" Width="255">
<Grid>
<StackPanel Orientation="Vertical">
<ListBox x:Name="ListBox" ItemsSource="{Binding MyCollection}" SelectionMode="Extended" Margin="10,10"/>
<Button Content="UpdateSelection" Click="Button_Click" Margin="10,10"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_myCollection = new ObservableCollection<MyListBoxItem>() { new MyListBoxItem("Orange"), new MyListBoxItem("Mango"), new MyListBoxItem("Stawberry"), new MyListBoxItem("Pineapple") };
//Items to be selected on this.MyCollection ListBox
this.SubCollection = new List<string>() { "Pineapple", "Mango" };
this.DataContext = this;
}
private ObservableCollection<MyListBoxItem> _myCollection;
public ObservableCollection<MyListBoxItem> MyCollection
{
get { return _myCollection; }
set
{
this._myCollection = value;
}
}
public IList<string> SubCollection { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
// Clear all the selected items in the ListBox
this.MyCollection.ToList().ForEach(item => item.IsSelected = false);
// SELECT only the items in MySubCollection into this.MyCollection ListBox
this.MyCollection.Where(item => this.SubCollection.Contains(item.Content.ToString())).ToList().ForEach(item => item.IsSelected= true);
}
}
public class MyListBoxItem : ListBoxItem
{
public MyListBoxItem(string displayName)
{
this.Content = displayName;
}
}
}
Check this blogpost: how-to-databind-to-selecteditems. There is a demo project with code attached.
Use "SelectedValue" property of the ListBox.
(Because you are using collection of string to bind the List Box.)
Define a string type property in your view model (let's say MySelectedValue).
Then Bind it to the "SelectedValue" property of the both List Boxes. (Remeber to set the Mode = TwoWay in your Main ListBox)
Let's say a method will execute when you select an item in Sub collection List Box. (You have to trigger the SelectionChanged event of the Sub Collection ListBox)
That's it.
So. You have to do is:
Use same property to bind the "SelectedValue" property of the BOTH
List Boxes.
Let's say if you wanna bind a collection of objects to the List Box.
Then use SelectedItem property to achieve this task.
in case you know the index number you want to select, you can use
int[] index = {1, 2, 5};
for( int i in index)
{
listBox.SetSelected( i, true );
}

Dependency Property in User Control works only on the first instance

I have several custom user controls in a window. They appear dynamically, like workspaces.
I need to add a dependency property on an itemscontrol to trigger a scrolldown when an item is being added to the bound observable collection to my itemscontrol, like so:
(usercontrol)
<ScrollViewer VerticalScrollBarVisibility="Auto" >
<ItemsControl Grid.Row="0" ItemsSource="{Binding Messages}" View:ItemsControlBehavior.ScrollOnNewItem="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox IsReadOnly="True" TextWrapping="Wrap" Text="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
And the code of my dependency property :
public class ItemsControlBehavior
{
static readonly Dictionary<ItemsControl, Capture> Associations =
new Dictionary<ItemsControl, Capture>();
public static bool GetScrollOnNewItem(DependencyObject obj)
{
return (bool)obj.GetValue(ScrollOnNewItemProperty);
}
public static void SetScrollOnNewItem(DependencyObject obj, bool value)
{
obj.SetValue(ScrollOnNewItemProperty, value);
}
public static readonly DependencyProperty ScrollOnNewItemProperty =
DependencyProperty.RegisterAttached(
"ScrollOnNewItem",
typeof(bool),
typeof(ItemsControl),
new UIPropertyMetadata(false, OnScrollOnNewItemChanged));
public static void OnScrollOnNewItemChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var mycontrol = d as ItemsControl;
if (mycontrol == null) return;
bool newValue = (bool)e.NewValue;
if (newValue)
{
mycontrol.Loaded += new RoutedEventHandler(MyControl_Loaded);
mycontrol.Unloaded += new RoutedEventHandler(MyControl_Unloaded);
}
else
{
mycontrol.Loaded -= MyControl_Loaded;
mycontrol.Unloaded -= MyControl_Unloaded;
if (Associations.ContainsKey(mycontrol))
Associations[mycontrol].Dispose();
}
}
static void MyControl_Unloaded(object sender, RoutedEventArgs e)
{
var mycontrol = (ItemsControl)sender;
Associations[mycontrol].Dispose();
mycontrol.Unloaded -= MyControl_Unloaded;
}
static void MyControl_Loaded(object sender, RoutedEventArgs e)
{
var mycontrol = (ItemsControl)sender;
var incc = mycontrol.Items as INotifyCollectionChanged;
if (incc == null) return;
mycontrol.Loaded -= MyControl_Loaded;
Associations[mycontrol] = new Capture(mycontrol);
}
class Capture : IDisposable
{
public ItemsControl mycontrol{ get; set; }
public INotifyCollectionChanged incc { get; set; }
public Capture(ItemsControl mycontrol)
{
this.mycontrol = mycontrol;
incc = mycontrol.ItemsSource as INotifyCollectionChanged;
incc.CollectionChanged +=incc_CollectionChanged;
}
void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
ScrollViewer sv = mycontrol.Parent as ScrollViewer;
sv.ScrollToBottom();
}
}
public void Dispose()
{
incc.CollectionChanged -= incc_CollectionChanged;
}
}
}
During the first instantiation of my user control, it works like a charm.
But when another user control of the same type is dynamically instantiated, the DependencyProperty is never attached anymore to my scrollviewer. Only the first instance will work correctly.
I know that dependency properties are static, but does that mean they can't work at the same time on several user control of the same type added to the window?
Update 02/03 : Here's how I set the viewmodel to the view (not programmatically) :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:testDp.ViewModel"
xmlns:View="clr-namespace:testDp.View">
<DataTemplate DataType="{x:Type vm:ChatTabViewModel}">
<View:ChatTabView />
</DataTemplate>
</ResourceDictionary>
even with x:shared = false in the datatemplate tag, it won't work.
But if I set the datacontext in a classic way like usercontrol.datacontext = new viewmodel(), it definitely work. But it's recommended to have a "shared" view, so how do we make dependency properties work with this "xaml" way of setting datacontext ?
Sorry, I couldn't reproduce your problem.
I started Visual C# 2010 Express, created a new 'WPF Application', added your XAML to a UserControl that I imaginatively titled UserControl1, and added your ItemsControlBehavior class. I then modified the MainWindow that VC# created for me as follows:
MainWindow.xaml (contents of <Window> element only):
<StackPanel Orientation="Vertical">
<Button Content="Add user control" Click="ButtonAddUserControl_Click" />
<Button Content="Add message" Click="ButtonAddMessage_Click" />
<StackPanel Orientation="Horizontal" x:Name="sp" Height="300" />
</StackPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ObservableCollection<string> Messages { get; private set; }
public MainWindow()
{
InitializeComponent();
Messages = new ObservableCollection<string>() { "1", "2", "3", "4" };
DataContext = this;
}
private void ButtonAddUserControl_Click(object sender, RoutedEventArgs e)
{
sp.Children.Add(new UserControl1());
}
private void ButtonAddMessage_Click(object sender, RoutedEventArgs e)
{
Messages.Add((Messages.Count + 1).ToString());
}
}
I made no modifications to the XAML in your UserControl, nor to your ItemsControlBehavior class.
I found that no matter how many user controls were added, their ScrollViewers all scrolled down to the bottom when I clicked the 'Add message' button.
If you're only seeing the scroll-to-the-bottom behaviour on one of your user controls, then there must be something that you're not telling us.

listbox is not updated by dependency property observablecollection

The feature I'm working on is autocomplete for keyword search. As soon as the user inputs something into search bar, the view model calls the autocomplete api with keyword parameter to get the autocomplete suggestions back and put them into a observablecollection container. This observablecollection is a dependency property, it's bound with the list box to show the autocomplete suggestions. My problem is the dependency property is populated correctly but the list box doesn't display anything. Following are some code pieces:
data binding in xaml.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
searchBar.Focus();
_searchViewModel = new SearchViewModel();
DataContext = _searchViewModel;
}
invoke a method in view model to call the autocomplete api:
private void searchBar_TextChanged(object sender, TextChangedEventArgs e)
{
_searchViewModel.getTypeaheadListFromServer(searchBar.Text);
}
dependency property in view model, it's populated successfully:
public ObservableCollection<TypeaheadElement> TypeaheadList
{
get { return (ObservableCollection<TypeaheadElement>)GetValue(TypeaheadListProperty); }
set { SetValue(TypeaheadListProperty, value); }
}
// Using a DependencyProperty as the backing store for TypeaheadList. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TypeaheadListProperty =
DependencyProperty.Register("TypeaheadList", typeof(ObservableCollection<TypeaheadElement>), typeof(SearchViewModel), new PropertyMetadata(null));
data binding in xaml:
<ListBox Name="typeahead" Grid.Row="1" ItemsSource="{Binding TypeaheadList}" Height="518" Margin="0,0,0,-518" SelectionChanged="typeahead_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Text="{Binding TypeaheadElementStr}" FontSize="{StaticResource ListItemFontSize}" FontFamily="Segoe WP" Margin="10,0,0,0" VerticalAlignment="Top">
<TextBlock.Foreground>
<SolidColorBrush Color="{StaticResource ListItemFontColor}"/>
</TextBlock.Foreground>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thank you very much for your help!
try this
<ListBox Name="typeahead" Grid.Row="1" ItemsSource="{Binding TypeaheadList, UpdateSourceTrigger=PropertyChanged}" Height="518" Margin="0,0,0,-518" SelectionChanged="typeahead_SelectionChanged">
I don't understand why you try to implement DependencyProperty is this situations. TypeaheadList is a source of Binding, not a target, right? So it can be a simple property on your ViewModel.
Have you tried using the AutoCompleteBox from the toolkit? If the list of possibilities are not large, you can pre-populate the ItemsSource of the AutoCompleteBox. If you cannot pre-populate it, you could issue an async request to the server to get all of the possibilities when the app starts.
Here are some blogs about using the AutoCompleteBox:
http://www.jeff.wilcox.name/2011/03/acb-in-pivot/
If that is not possible, then you can do something like the following:
Xaml:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<toolkit:AutoCompleteBox ItemsSource="{Binding People}" Populating="AutoCompleteBox_Populating" />
</Grid>
Code:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
People = new ObservableCollection<string> {"Shawn", "steve", "Bob", "randy", "mike"};
DataContext = this;
InitializeComponent();
}
public ObservableCollection<string> People { get; set; }
private void AutoCompleteBox_Populating(object sender, PopulatingEventArgs e)
{
// Have we already populated with this text?
if(People.Any(person => person.ToLower().StartsWith(e.Parameter.ToLower()))) return;
Completer c = new Completer();
c.Completed += new EventHandler<EventArgs>(c_Completed);
c.Complete(e.Parameter);
}
void c_Completed(object sender, EventArgs e)
{
Completer c = sender as Completer;
foreach (var name in c.Names)
{
People.Add(name);
}
}
}
internal class Completer
{
public event EventHandler<EventArgs> Completed;
public IEnumerable<string> Names { get; set; }
public void Complete(string parameter)
{
if (parameter.StartsWith("d"))
{
Names = new List<string>() { "Dick", "Dave" };
}
else if (parameter.StartsWith("j"))
{
Names = new List<string>() { "Jane", "Joe" };
}
OnCompleted();
}
protected virtual void OnCompleted()
{
var handler = Completed;
if (handler != null) handler(this, EventArgs.Empty);
}
}

Categories