How do I extract SelectedItem from a TreeView when using data binding? - c#

I have followed the advice of this article in binding a treeview control to an xml document:
http://www.codeproject.com/Articles/317766/Displaying-XML-in-a-WPF-TreeView?msg=4546407#xx4546407xx
However, now I can't figure out how to gain access to the selected item.
Here is my XAML:
<Window.Resources>
<XmlDataProvider x:Key="xmldata" Source="cats.xml" XPath="/CategoryArray" />
<HierarchicalDataTemplate DataType="Category" ItemsSource="{Binding XPath=./*}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,0,0,0" Text="{Binding XPath=#Name}" Tag="{Binding XPath=#ID}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="CategoryArray" ItemsSource="{Binding XPath=./*}">
<TextBlock Margin="0" Text="eBay Categories" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid DataContext="{StaticResource xmldata}">
<TreeView Name="treeView1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="False" VirtualizingStackPanel.VirtualizationMode="Standard" SelectedItemChanged="treeView1_SelectedItemChanged" />
</Grid>
This doesn't seem to work the way I thought it would:
private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
try
{
TreeViewItem selectedItem = treeView1.SelectedValue as TreeViewItem;
categoryName = selectedItem.Name;
categoryID = selectedItem.Tag.ToString();
categoryChosen = true;
}
catch { }
}
Since I'm using this hierarchical data template and a textblock, I'm not sure what to do. Any ideas? Thanks in advance.

You should probably get rid of the dirty try catch em all. You can use the e.NewValue and check if its one of your specified types. Since you are directly telling WPF whats your data model you can use it like this:
private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var category = e.NewValue as Category;
if(category != null)
{
//FIXME: do things if its Category
}
else
{
var categoryArray = e.NewValue as CategoryArray;
if(categoryArray != null)
{
//FIXME: do things if its CategoryArray
}
}
}

While using XAML you can use binding to retrieve the property of the framework elements. However in this particular case the SelectedItem property of TreeView is read only so you may not bind it directly, but you can make use of attached properties to achieve the same.
I tried to make a sample for your case
declare an attached property in the VM class with change notification to execute your logic
public static object GetSelectedTreeItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedTreeItemProperty);
}
public static void SetSelectedTreeItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedTreeItemProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedTreeItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTreeItemProperty =
DependencyProperty.RegisterAttached("SelectedTreeItem", typeof(object), typeof(YourVMClass), new PropertyMetadata(null, OnSelectedItemChange));
public static void OnSelectedItemChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//do your stuff here eg.
YourVMClass vm = d as YourVMClass;
dynamic selectedItem = e.NewValue;
vm.categoryName = selectedItem.Name;
vm.categoryID = selectedItem.Tag.ToString();
vm.categoryChosen = true;
}
your XAML binding goes as follows
<TreeView vm:YourVMClass.SelectedTreeItem="{Binding SelectedItem,Mode=OneWay,RelativeSource={RelativeSource Self}}">
So this will set the value of the Selected Item of TreeView to SelectedTreeItem property of VM class and will trigger the change event for you to perform further actions.
Also I can see that currently you are using XML data as your data context, so probably you may need adjust the above code according to your usage, concept remains same. You may perhaps declare the attached property in the code behind file for the respective XAML if you do not intent to change your data context. But I would recommend you to do some re-factoring to implement a VM class for your XAML and expose the XML data as a property in your new VM.

Related

Curious behavior with ComboBox in UWP - SelectedItem & ItemsSource

hope you're all fine.
I'm encountering 2issues with a ComboBox in a UWP application.
If the property ItemsSource is bound to a collection that implements INotifyPropertyCollectionChanged, the list is never loaded completly. I only have the 2, 3 or 4 first items... depending on the time. No problem when the same collection is bound to a DataGrid so I think my collection is built correctly. As a workaround (code-behind), I first load my collection (in a Task) and set the ItemsSource property when the task is completed. This solution works but I'd like to do less things code-behind.
The binding on the property SelectedItem seems to work with ReferenceEquals only, the type of item in my collection implements Equals based on IDs and it has been tested separately and successfylly in a console app. As a workaround (code-behind), once my list is loaded, I change the property bound to SelectedItem like this:
Users.TaskFill.ContinueWith(t => BaseItemCollection.UserInterfaceAction.Invoke(() =>
{
if (Item?.Manager != null) Item.Manager = t.Result.FirstOrDefault(i => i.Equals(Manager));
ComboBoxManager.SetBinding(ComboBox.ItemsSourceProperty, this, "Users", BindingMode.TwoWay);
ComboBoxManager.SetBinding(Selector.SelectedItemProperty, "Manager", BindingMode.TwoWay);
}));
Users is my collection (filled asynchronously) used as source for the ComboBox
SetBinding is a custom extension method I've created myself to set bindings code-behind from a single-line (as follow):
public static class ExtensionMethods
{
#region DependencyObject
public static void SetBinding(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, object source, string propertyName, BindingMode mode)
{
var binding = new Binding()
{
Source = source,
Path = new PropertyPath(propertyName),
Mode = mode
};
BindingOperations.SetBinding(dependencyObject, dependencyProperty, binding);
}
public static void SetBinding(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, string propertyName, BindingMode mode)
{
var binding = new Binding()
{
Path = new PropertyPath(propertyName),
Mode = mode
};
BindingOperations.SetBinding(dependencyObject, dependencyProperty, binding);
}
#endregion
}
How can I get this working from XAML withtout needing these workarounds? I has been able to get a similar configuration working with WPF for years but am really struggling with UWP...
Thank you in advance for your help.
If the property ItemsSource is bound to a collection that implements INotifyPropertyCollectionChanged, the list is never loaded completly.
If your collection is not string, you need specify DisplayMemberPath, please check the following code. And please check the collection has value. For my testing collection that implements INotifyPropertyCollectionChanged works for ComboBox.
<ComboBox
x:Name="cmbCountry"
Grid.Row="4"
Width="292"
Height="32"
Margin="28,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DisplayMemberPath="FirstName"
ItemsSource="{Binding MyItems}"
PlaceholderText="Select Country ..."
/>
Curious behavior with ComboBox
The default ItemsPanelTemplate of ComboBox is CuriousPanel that could implement scroll loop within touch device. If you don't want to use it, you could replace it with StackPanel
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
The binding on the property SelectedItem seems to work with ReferenceEquals only,
The SelectedItem is not ComboBox display field, it is an complete User object. You could get the select user in the SelectedItem binding property set method. the following is complete code that you could refer.
public sealed partial class TestPage : Page, INotifyPropertyChanged
{
private User _selecteduser;
public TestPage()
{
this.InitializeComponent();
_myItems = new ObservableCollection<User>
{
new User{UserId=1,FirstName="Fay",LastName="Wang",City="Delhi",State="DEL",Country="INDIA"},
new User{UserId=2,FirstName="Mark",LastName="Liu",City="New York", State="NY", Country="USA"},
new User{UserId=3,FirstName="Rich",LastName="Cai",City="Philadelphia", State="PHL", Country="USA"},
new User{UserId=4,FirstName="Eveia",LastName="Dong",City="Noida", State="UP", Country="CANADA"}}
};
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)//string propertyName
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, args);
}
}
public ObservableCollection<User> Users
{
get
{ return _myItems; }
set
{
_myItems = value;
OnPropertyChanged("Users");
}
}
private ObservableCollection<User> _myItems;
public User SelectedUser
{
get
{
return _selecteduser;
}
set
{
_selecteduser = value;
OnPropertyChanged("SelectedUser");
}
}
}
Xaml
<ComboBox
x:Name="cmbCountry"
Grid.Row="4"
Width="292"
Height="32"
Margin="28,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
DisplayMemberPath="FirstName"
ItemsSource="{Binding Users}"
PlaceholderText="Select User..."
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
/>

Attached property attached only on the first user control instance

Following Josh Smith example on mvvm workspaces (customers view), I have a mainwindow and a mainwindowviewmodel which contains an ObservableCollection of "ChatTabViewModel":
internal class FriendsListViewModel : ObservableObject
{
#region bound properties
private ICollectionView viewfriends;
private ObservableCollection<ChatTabViewModel> _chatTab;
...
#endregion
}
I have an area dedicated to this collection in the xaml like that :
<ContentControl Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Content="{Binding Path=ChatTabs}" ContentTemplate="{StaticResource ChatTabsTemplate}" />
And in my resources dictionary:
<DataTemplate DataType="{x:Type vm:ChatTabViewModel}">
<View:ChatTabView />
</DataTemplate>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel>
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16"
/>
<ContentPresenter
Content="{Binding Path=Caption, Mode=OneWay}"
VerticalAlignment="Center">
</ContentPresenter>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="ChatTabsTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"/>
</DataTemplate>
On user event I add a new ChattabViewModel in my collection and the view related to it appears in the main window.
But when I tried to add an attached property on a scrollbar in the ChattabView, this property will attach only on the first ChattabViewModel instance, the other tabs won't be bound to the attached property. Here's the ChattabView XAML:
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="0">
<ItemsControl 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 the attached property:
namespace GtalkOntre.View
{
/// <summary>
/// Util class to scroll down when a new message is added.
/// </summary>
/// <remarks>attached property called ScrollOnNewItem that when set to true hooks into the INotifyCollectionChanged events of the itemscontrol items source and upon detecting a new item, scrolls the scrollbar to it.</remarks>
public class ItemsControlBehavior
{
static 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 DependencyProperty ScrollOnNewItemProperty =
DependencyProperty .RegisterAttached(
"ScrollOnNewItem",
typeof(bool),
typeof(ItemsControlBehavior),
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 += MyControl_Loaded;
mycontrol.Unloaded += 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;
}
}
}
}
So why is the attached property only bound once, on the first "chattabview" occurence of the chattabviewmodel collection? and therefore, working only on the first chattabviewmodel.
When I close them all, the attached property will unbind itself on the last instance of chattabviewmodel, and when I add a new first chattabviewmodel, the property will bind correctly. So it triggers only on the first instance and last instance of the "chattabviewmodel" collection of mainwindowviewmodel.
After a week of searching, I'm a little desperate now...
So far my hypothesis is : the problem might be related to the way I set the view to my viewmodel in dictionary resources. The view might be shared and the first scrollbar only might react. I tried to add an x:Shared = false attribute on the DataTemplate tag but it didn't change anything.
Are you sure there are different instances of your ChatTabView being created?
I believe WPF's TabControl re-uses the existing template if it's the same instead of creating a new one, and simply replaces the DataContext behind it.
So it would only create one copy of your ChatTabView and switching tabs is replacing the DataContext behind the ChatTabView to a different item in the collection.
You haven't shown us ChatTabsTemplate, so I can only assume it contains a TabControl. If so, that explains the behavior you're seeing. The TabControl lazily loads its child tab items, so only the current view will be initialized, and hence have the attached property applied to it. When you switch tabs, however, you should see the same attached property firing. Is that not the case?
As for your hunch, it's not quite right. The DataTemplate is being shared, but the DataTemplate is used to create distinct instances of its contents, which are not being shared.

Updating the SelectedItem in an ItemsControl (ComboBox) in a UserControl

Before any item in a ComboBox is selected, its SelectedItemis null and the ComboBox itself is visually blank. Once something is selected, there doesn't seem to be any way for the user to select "the absence of a selection" (though it can be done by setting SelectedItem to null in code).
My ComboBoxes are bound to ObservableCollections of my objects. I don't want to add a "special" first null-like object to the front of every ObservableCollection. So I'm taking this opportunity to learn a bit about writing a UserControl.
The problem is SelectedItem doesn't work the way it normally does. That is, the ComboBox is nicely bound to a backing ObservableCollection, but picking something from the ComboBox doesn't update the SelectedItem it's supposed to be bound to.
I feel like I need to be passing along some information from the ComboBox in the UserControl to...somewhere. Am I on the right track? What should I be googling for?
C#:
public partial class ClearableComboBox : UserControl
{
public ClearableComboBox()
{
InitializeComponent();
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)base.GetValue(ItemsSourceProperty); }
set { base.SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource",
typeof(IEnumerable),
typeof(ClearableComboBox));
public object SelectedItem
{
get { return (object)base.GetValue(SelectedItemProperty); }
set { base.SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem",
typeof(object),
typeof(ClearableComboBox));
public string DisplayMemberPath
{
get { return (string)base.GetValue(DisplayMemberPathProperty); }
set { base.SetValue(DisplayMemberPathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath",
typeof(string),
typeof(ClearableComboBox));
private void Button_Click(object sender, RoutedEventArgs e)
{
comboBox.SelectedItem = null;
}
}
XAML:
<UserControl x:Class="MyProj.ClearableComboBox"
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"
x:Name="root">
<DockPanel>
<Button DockPanel.Dock="Left" Click="Button_Click" ToolTip="Clear">
<Image Source="pack://application:,,,/img/icons/silk/cross.png" Stretch="None" />
</Button>
<ComboBox
Name="comboBox"
ItemsSource="{Binding ElementName=root, Path=ItemsSource}"
SelectedItem="{Binding ElementName=root, Path=SelectedItem}"
DisplayMemberPath="{Binding ElementName=root, Path=DisplayMemberPath}" />
</DockPanel>
</UserControl>
Usage:
<wpfControl:ClearableComboBox ItemsSource="{Binding Path=Things}"
DisplayMemberPath="SomeProperty"
SelectedItem="{Binding Path=SelectedThing}" />
// Picking a Thing doesn't update SelectedThing :(
Since combobox derives from Selector class which in turn derives from ItemsControl. So, by deriving from UserControl you are devoiding your combobox with properties of Selector class which might internally handle the Selection thing for you. so, i would suggest instead of deriving it from UserControl, you should derive it from Combobox like this -
public partial class ClearableComboBox : ComboBox
So, that ways you won't have to override the ItemsSource, DisplayMemberPath etc. in your class since it s already present in the ComboBox class. You can always extend your class further to provide addidtional features which is in your case setting the SelectedItem to null on some button click. Hope this is what you want..
EDIT (Custom Control)
Creating a Custom Control is your answer here, to get started if you are not aware of it, look at this for start - http://www.wpftutorial.net/HowToCreateACustomControl.html
When you create a Custom Control say CustomControl1, replace the template for CustomControl1 in your Generic.xaml file with this one -
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<Button Name="btn" DockPanel.Dock="Left" ToolTip="Clear" Width="20">
<Image Source="pack://application:,,,/img/icons/silk/cross.png" Stretch="None" />
</Button>
<ComboBox Name="comboBox"
ItemsSource="{TemplateBinding ItemsSource}"
SelectedItem="{TemplateBinding SelectedItem}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}" />
</DockPanel>
</Border>
</ControlTemplate>
By default your CustomControl1 class will be derived from Control. Replace it to derive from class ComboBox so that you don't have declare DP's yet over again like this and copy paste this code there -
public class CustomControl1 : ComboBox
{
private Button clearButton;
private ComboBox comboBox;
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
clearButton = GetTemplateChild("btn") as Button;
comboBox = GetTemplateChild("comboBox") as ComboBox;
clearButton.Click += new RoutedEventHandler(clearButton_Click);
}
private void clearButton_Click(object sender, RoutedEventArgs e)
{
comboBox.SelectedItem = null;
}
}
Now, your CustomControl1 class is ready for use in your other xaml files like this -
<local:CustomControl1 ItemsSource="{Binding YourSource}"
SelectedItem="{Binding YourSelectedItem}"
Height="50" Width="200"/>
I chose to handle the key press event on the combo box and handle the escape key press to clear out the combo box's SelectedItem.
I think there's a better way, develop a wrapper/Adorner for ComboBox, that adds a button next to the ComboBox and wipe the selection on click.

Problem with Silverlight binding

I have the following XAML:
...
<ListBox Name ="RoomsListBox" Height="100"
HorizontalAlignment="Left" Margin="12,41,0,0"
VerticalAlignment="Top" Width="120"></ListBox>
...
And the following C#-code:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
RoomsListBox.ItemsSource = new[] { new { Name = "First1" },
new { Name = "First2" } };
RoomsListBox.DisplayMemberPath = "Name";
}
The problem is that my ListBox have items but they are empty. Why I don't see "First1" and "First2" instead?
The issue here isn't with the bindings nor the ItemTemplate nor the change notification. It's the Anonymous Type you're using that's causing it. try using a class or struct for your items
public class Item
{
public string Name { get; set; }
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
RoomsListBox.ItemsSource = new[] {
new Item { Name = "First1" },
new Item { Name = "First2" }};
RoomsListBox.DisplayMemberPath = "Name";
}
your xaml stays the same, or you can define a DataTemplate for the ListBox items if you want. Note that you can't set both the ItemTemplate and DisplayMemberPath at the same time (one has to be null). Also, make sure that the class representing your items has to be public.
Hope this helps :)
Just a thought..
Have you tried settings the DisplayMemberPath property in XAML? There might be an issue with the order of calls.
You have to set DisplayMemberPath property on your ListBox to Name.
Moving forward you might want to consider creating a DataTemplate for your items to have more control:
<ListBox x:Name ="RoomsListBox" Height="100"
HorizontalAlignment="Left" Margin="12,41,0,0"
VerticalAlignment="Top" Width="120">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
See this tutorial for more info: http://weblogs.asp.net/scottgu/pages/silverlight-tutorial-part-5-using-the-listbox-and-databinding-to-display-list-data.aspx
I would prefer that you define your binding in your xaml and for example in your Code-Behind you define a property for the items of your listbox.
Example: (xaml)
<ListBox Name ="RoomsListBox"
ItemsSource="{Binding MyItems}"
Height="100"
HorizontalAlignment="Left"
Margin="12,41,0,0"
VerticalAlignment="Top"
Width="120" />
Example: (C# in your code-behind)
//...
private ObservableCollection<string> _myItems;
public ObservableCollection<String> MyItems
{
get
{
return _myItems ?? (_myItems = new ObservableCollection<string> { "FirstItem", "SecondItem"});
}
set
{
_myItems = value;
}
}
Like ChrisF said you could use the INotifiyPropertyChanged Interface, there you would raise the PropertyChanged event in the setter of your property.
See --> http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx

Silverlight - Can't get ListBox to bind to my ObservableCollection

I'm trying to make a ListBox that updates to the contents of an ObservableCollection whenever anything in that collection changes, so this is the code I've written for that:
xaml:
<ListBox x:Name="UserListTest" Height="300" Width="200" ItemsSource="listOfUsers">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#:
public ObservableCollection<User> listOfUsers
{
get { return (ObservableCollection<User>)GetValue(listOfUsersProperty); }
set { SetValue(listOfUsersProperty, value); }
}
public static readonly DependencyProperty listOfUsersProperty =
DependencyProperty.Register("listOfUsers", typeof(ObservableCollection<User>), typeof(MainPage), null);
And I set up a call to a WCF Service that populates listOfUsers:
void repoService_FindAllUsersCompleted(object sender, FindAllUsersCompletedEventArgs e)
{
this.listOfUsers = new ObservableCollection<User>();
foreach (User u in e.Result)
{
listOfUsers.Add(u);
}
//Making sure it got populated
foreach (User u in listOfUsers)
{
MessageBox.Show(u.LastName);
}
}
The ListBox never populates with anything. I assume my problem may be with the xaml since the ObservableCollection actually has all of my Users in it.
You're missing the {Binding} part from your ItemsSource there.
<ListBox x:Name="UserListTest" Height="300" Width="200" ItemsSource="{Binding listOfUsers}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Also, you may not need to have a DependencyProperty for your list, you may be able to achieve what you need with a property on a class that implements INotifyPropertyChanged. This may be a better option unless you need a DependencyProperty (and the overhead that goes along with it) for some other reason.
e.g.
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<User> _listOfUsers;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> ListOfUsers
{
get { return _listOfUsers; }
set
{
if (_listOfUsers == value) return;
_listOfUsers = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("ListOfUsers"));
}
}
}

Categories

Resources