listbox is not updated by dependency property observablecollection - c#

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);
}
}

Related

C# UWP - Update UI to display object values that have been read in by the user

I have been tasked with creating my first UWP App in C#.
The basic idea is to read in an XML file and create objects based on the data read in, then display the properties stored in the object to users in the IU.
Lets say a Person object that has a name, age, and height. I want to display the Person fields after I have read in the data but I can't get anything to show up in the UI after creating the Person object.
I have created a Person class that holds the name, age, height. I have another class that extends ObservableCollection<> and a ItemTemplate that looks for the observable class but currently nothing is showing up on the UI.
Has anyone been through a similar process or know of the correct documentation to read?
Thanks.
First of all in UWP you can choose between two types of binding:
{x:Bind }, is slightly faster at compile time, binds to your Framework Element code-behind class, but it is not as flexible as the other type of binding.
The default mode for this type of binding is OneTime, therefore you will only have your data actually propagated onto your UI, when you construct your object.
{Binding }, in this type of binding where you can only reference variables which exists inside the DataContext of a parent element. The default mode is OneWay.
With that in mind, first of all dealing with a ViewModel which is just a bunch of properties, is different from actually dealing with a Collection, since I don't think the Collection can actually detect alterations on the items itself, but rather on its structure.
Therefore during the Add/Remove process of items in your Collection, you have to actually subscribe/unsubscribe those items to the PropertyChanged EventHandler.
Nevertheless with the following code, i think you should be able to start visualizing updates onto your UI:
VIEWMODEL
public class PersonsObservable<T> : ObservableCollection<Person> where T : INotifyPropertyChanged
{
private PersonsObservable<Person> _personslist;
public PersonsObservable<Person> personslist
{
get { return _personslist; }
set
{
_personslist = value;
_personslist.CollectionChanged += OnObservableCollectionChanged;
}
}
public void OnObservableCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(e.NewItems != null)
{
foreach (object item in e.NewItems)
((INotifyPropertyChanged)item).PropertyChanged += OnItemPropertyChanged;
}
if(e.OldItems != null)
{
foreach (object item in e.OldItems)
((INotifyPropertyChanged)item).PropertyChanged -= OnItemPropertyChanged;
}
}
public void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((Person)sender));
OnCollectionChanged(args);
}
}
public class Person : INotifyPropertyChanged
{
public Person()
{
_name = "Walter White";
_age = 40;
_height = 180;
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _name;
public string name
{
get
{
return _name;
}
set
{
_name = value;
this.OnPropertyChanged();
}
}
private int _age;
public int age
{
get
{
return _age;
}
set
{
_age = value;
this.OnPropertyChanged();
}
}
private int _height;
public int height
{
get
{
return _height;
}
set
{
_height = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Add Items
PersonsList.Add(new Person());
}
}
XAML
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Vertical">
<TextBlock Text="DataBinding" Foreground="DarkBlue" FontSize="18" FontWeight="Bold"/>
<ItemsControl ItemsSource="{Binding Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: "/>
<TextBlock Text="{Binding name, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Age: "/>
<TextBlock Text="{Binding age, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Height: "/>
<TextBlock Text="{Binding height, Mode=TwoWay}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Add Items" Click="Button_Click" Background="Blue" VerticalAlignment="Bottom"/>
</StackPanel>
</Grid>
*Test adding items *
private void Button_Click(object sender, RoutedEventArgs e)
{
// Add Items
PersonsList.Add(new Person());
}
Expose your property and set it to the DataContext of your page (with x:Bind you wouldn't need to do this, but instead you would have to perform a cast for your code to actually compile).
public MainPage()
{
InitializeComponent();
PersonsList = new PersonsObservable<Person>();
this.DataContext = PersonsList;
PersonsList.Add(new Person());
PersonsList.Add(new Person());
}
PersonsObservable<Person> PersonsList { get; set; }
I haven't tested for the situation where one of the items is altered, but you can easily do that, by adding another button (and click event) and actually test if changing one of the items's properties update in your UI.
Anything else, feel free to ask, will be glad to help!

Binding ObservableCollection<Item> To TextBox (UWP)

I want to achieve one-way binding from an ObservableCollection of "struct-like" items to a TextBox that has a TextChanged event. The idea is that as the Comments field of Item accumulates in the TextBox, the TextBox scroll down automatically so that the last line is always in view. The collection is bound to a ListView but I want it bound read-only to the TextBox. I would prefer not to add another method in ResultViewModel but do it in XAML. How would I go about in doing this? TIA
// ViewModel
public class Item
{
public string First { get; set; }
public string Last { get; set; }
public string Comments { get; set; }
}
public class ResultViewModel
{
private ObservableCollection<Item> items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items { get { return items; } }
// member functions
}
public ResultViewModel ViewModel { get; set; }
// What I have
<ListView x:Name="myListView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.Items}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<StackPanel>
<TextBox Text="{x:Bind First}"/>
<TextBlock Text="{x:Bind Last}"/>
<TextBlock Text="{x:Bind Comments}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// What I want
<TextBox Text="{x:Bind Comments}"/>
I'm afraid you can't do it with XAML alone.
You can create a behavior which will listen to events and add lines to the textbox when the collection is modified.
Dirty example, you will need to include Microsoft.Xaml.Behaviors.Uwp.Managed package:
public class CommentsBehavior : Behavior
{
public ObservableCollection<string> Comments ... // you will need to make it a dependency property
protected virtual void OnAttached()
{
Comments.CollectionChanged += OnCollectionChanged;
}
protected virtual void OnDetaching()
{
Comments.CollectionChanged -= OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(string newItem in e.NewItems)
{
((TextBox)AssociatedObject).Text = ((TextBox)AssociatedObject).Text + '\n' + newItem;
}
}
}
}
And for scrolling - UWP C# Scroll to the bottom of TextBox
But why do you want to use textbox for this? Using list makes more sense.

How to set SeletedItems = null in a Listbox if i select a other Listbox

basically i'm trying to do THIS
but you can see it is not MVVM so i'm looking for a way to set SeletedItems = null or clear() depending on what's doable
because in my View i will got N ListBoxes and if he pressed a Button after selecting some Items i will change some properties of the SeletedItems but only for the last active Listbox
so i decided to use on SelectedItems Property for all the Listboxes but it doesn't work based on 2 problems i can't bind to to SelectedItems and based on this i can't test how to remove the selection from the other Listboxes
EDIT:
to give you an simple example:
XAML
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ListBox Width="432" Height="67"
HorizontalAlignment="Left" VerticalAlignment="Top"
SelectionMode="Extended"
<!-- SeletedItems="{Binding SelectedListItems}" ??? -->
ItemsSource="{Binding Collection1}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding MyText}"
Background="{Binding MyBackground}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Width="432" Height="67"
HorizontalAlignment="Left" VerticalAlignment="Top"
SelectionMode="Extended"
<!-- SeletedItems="{Binding SelectedListItems}" ??? -->
ItemsSource="{Binding Collection2}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding MyText}"
Background="{Binding MyBackground}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="unselect" Width="80" Height="150"
HorizontalAlignment="Right" VerticalAlignment="Top"
Command="{Binding MyCommand}"/>
</StackPanel>
</Window>
Code
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace Test
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM : INotifyPropertyChanged
{
private ObservableCollection<DetailVM> _SelectedListItems = new ObservableCollection<DetailVM>();
public ObservableCollection<DetailVM> SelectedListItems
{
get { return _SelectedListItems; }
set
{
_SelectedListItems = value;
OnPropertyChanged("SelectedListItems");
}
}
public List<DetailVM> Collection1 { get; set; }
public List<DetailVM> Collection2 { get; set; }
private RelayCommand _myCommand;
public ICommand MyCommand
{
get { return _myCommand?? (_myCommand= new RelayCommand(param => OnMyCommand())); }
}
public void OnMyCommand()
{
foreach DetailVM item in SelectedListItems
{
item.MyBackground ="Red";
}
}
public VM()
{
Collection1 = new List<DetailVM>();
Collection2 = new List<DetailVM>();
for (int i = 0; i < 10; i++)
{
Collection1.Add(new DetailVM { MyText = "C1ITEM " + i });
Collection2.Add(new DetailVM { MyText = "C2ITEM " + i });
}
}
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class DetailVM
{
public string MyText { get; set; }
public string MyBackground { get; set; }
}
}
The code above should change the color of the Textbox background to Red
if the user selected some Items in a Listbox and he should only be able to seleted Items in one Listbox at the same time
so how to do this? (bear in mind this is a simple example but i need this for N Listboxes which will be generated over a template)
First of all, I would recommend you to extend ListView so that it includes a bindable SelectedValues property (you cannot use the name SelectedItems since it's already a non-bindable property of ListView). Here's an example of how this can be achieved.
public class MultiSelectListView : ListView
{
// Using a DependencyProperty as backing store
public static readonly DependencyProperty SelectedValuesProperty =
DependencyProperty.Register("SelectedValues", typeof(IList), typeof(MultiSelectListView), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
public IList SelectedValues
{
get { return (IList)GetValue(SelectedValuesProperty); }
set { SetValue(SelectedValuesProperty, value); }
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// if selected items list implements INotifyCollectionChanged, we subscribe to its CollectionChanged event
var element = (MultiSelectListView)d;
if (e.OldValue != null && e.OldValue is INotifyCollectionChanged)
{
var list = e.OldValue as INotifyCollectionChanged;
list.CollectionChanged -= element.OnCollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged)
{
var list = e.NewValue as INotifyCollectionChanged;
list.CollectionChanged += element.OnCollectionChanged;
}
}
// when selection changes in the view, elements are added or removed from the underlying list
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (SelectedValues != null)
{
foreach (var item in e.AddedItems)
{
if (!SelectedValues.Contains(item))
SelectedValues.Add(item);
}
foreach (var item in e.RemovedItems)
{
if (SelectedValues.Contains(item))
SelectedValues.Remove(item);
}
}
base.OnSelectionChanged(e);
}
// when underlying list changes, we set the control's selected items to the contents of the list
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (SelectedValues != null)
{
SetSelectedItems(SelectedValues);
}
}
}
Once you've done this you can control the behavior of a list's selected items through the viewmodel. Clearing the viewmodel list clears the selected items in the control.
Next you can subscribe to the collection changed event of your selected items lists (in the view model) and in the handler check whether you need to clear any of your lists.

Binding a collection to a custom control property

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);
}

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.

Categories