Updating items inside ListBox - c#

I have a ListBox bound to an observable collection of DiceViewModel. Whenever I click a button to add a new item, the ListBox displays the new item like I expect. Everything so far is working well.
<ListBox
ItemsSource="{Binding Path=AllDice}"
DisplayMemberPath="Value"/>
However, I have another button to roll all existing dice. The items already listed in the box don't get updated, and I'm not sure how to enforce this while keeping to the MVVM design pattern.
Also, my DiceViewModel already implements INotifyPropertyChanged.
Any suggestions?

After some more digging around, here's what I've found. The ObservableCollection doesn't automatically register itself with my DiceViewModel's INotifyPropertyChanged event. So any property changes don't get handled.
However, there is a way to do it in the xaml file:
I added this namespace definition to my Window element.
xmlns:vm="clr-namespace:Rolling.ViewModel"
Then I modified my ListBox to use a DataTemplate with a specified DataType:
<ListBox ItemsSource="{Binding Path=AllDice}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type vm:DiceViewModel}">
<TextBlock Text="{Binding Path=Value}"/>
</DataTemplate>
</ListBox.Resources>
</ListBox>
With the specified DataType, the ObservableCollection could register itself with my collection items, receive their events, and then fire it's own CollectionChanged event.
I hope this helps some other people with this poorly documented feature.

You need to implement the INotifyCollectionChanged interface on the collection that the items are bound to and then have it fire the CollectionChanged event to indicate that the collection changed.
This will cause a refresh of the entire list.

In the case of ObservableCollection INotifyPropertyChanged will only notify on changes to the structure of the collection, generally this is the addition and removal of items. The collection has no knowledge of changes to the properties of an individual item within the collection. Instead that individual item itself is responsible for sending notification of its properties changing.
The reasoning behind this goes back to class responsibility and separation of concerns. Since the DiceViewModel likely has data related rolling a die and the value of its last roll then it would follow that it would send notification when its own properties change.

Related

Mvvm Unchecking a Checked Box in an underlying Listbox object

I have a listview that's item source is databound to an observable collection of a custom datatype. Each object in the list has the property DisplayName (string) and IsChecked (bool), among others. The data template essentially is :
<CheckBox VerticalAlignment="Center"
Content="{Binding DisplayName}"
IsChecked="{Binding IsChecked}"/>
When I check the boxes, I am able to determine, easily, the underlying items that now have been checked. However in the viewmodel, if i wish to uncheck an item, it does not seem to work going the other way.
I have NotifyProperty change implemented in the setter for the observable collection, but do i need to do something special for the bool?
In the Viewmodel, the following code is where I attempt to uncheck the checked checkboxes (as kind of a refresh after a command has run):
foreach (var bill in AllBills)
{
bill.IsChecked = false;
}
I have NotifyProperty change implemented in the setter for the observable collection, but do i need to do something special for the bool?
Yes. When you give your collection property a new collection, you raise PropertyChanged for that property. What does that have to do with property values changing on an item in the collection? Nothing at all. When you set IsChecked on one of those items, is there any imaginable way for that code to hit the setter for your collection property? Nope.
Your items in the collection must themselves implement INotifyPropertyChanged, and the setter for IsChecked must raise PropertyChanged when the value of IsChecked changes. If you did that for the collection property, I expect you already know how. But shoot me a comment if you run into any snags.
One of the tough parts of the WPF learning curve is developing a reliable intuitive sense of the borderline between things that just happen by secret framework rainbow pony magic, and things you have to do yourself. One spot on that borderline is where you're standing right now.
If you implement INotifyPropertyChanged in your viewmodel, and are firing the event in IsChecked property, then just change your checkbox to:
<CheckBox VerticalAlignment="Center"
Content="{Binding DisplayName}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"/>

Is a custom usercontrol created each time the datacontext changes?

This is more of a general question...
I have a user control that i've written (UserControl,not Custom Control). i'm using this control in the a DataGridColumn to provide lookup functionality..much like this:
<DataGridTemplate ColumnHeader="Company">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<lookupCtl:LookUpCTL SelectedCompany="{Binding Company, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
CompanyChangedCommand="{Binding DataContext.CompanyChangedCmd, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
What i'm noticing is that whenever the dataconext changes for the parent/containing control, the constructor of LookUpCTL is called. Is this expected behavior? Anyway to prevent this? as i don't see why this necessary...the single instance of the control should be able to refresh itself from the datacontext i think.
Edit: googling is not providing any clear answers...but from what i've read, it may be the Datagrid that's the issue. since my control is used in a datagrid, each time the grid's itemsource changes, does it destroy and recreate the controls? i can see sense in it doing so..but not sure if that is the reason for what i'm seeing. Assuming it is...is there a way to have the datagrid reuse instances of the usercontrol rather than create new ones when the datagrid's itemsource changes?
It does appear that the reason usercontrols used as DataTemplate columns for a datagrid are destroyed and recreated when the itemsource for the datagrid is changed. in my case the solution was to use a normal grid given that i had always 4 items in my list that was used as the itemsource for the grid. This is not ideal and not a solution for N-item lists..but in my case it vastly improved performance since my usercontrol performed some intensive datbase lookups on initialization to cache data.

How to make one ObservableCollection dependent on other

I have ObservableCollection that contains some elements.In some Views to this collection binded some elements. But one element is special, and this element must not displaying in other Views. Because I want have other collection dependent on first collection. Of course, I can add elements to first and two other collections in my ViewModel, but first collection changed in many places. So, from CollectionChanged event I can't modify second collection. How can I make one ObservableCollection dependent on other?
I hope I understood your problem correctly :)
I don't think you need a second/third/... ObservableCollection. Extend your view model with a SpecialVisibility property (the name is up to you) of type System.Windows.Visibility. Then, bind this property to the Visibility property of your data template.
<ItemsControl.ItemTemplate>
<DataTemplate>
<YourControl Visibility="{Binding Path=SpecialVisibility}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
These 'special' elements (i.e. the elements which should not be dispayed) just need the SpecialVisibility set to System.Windows.Visibility.Collapsed.
Of course, this is not a solution to your problem of synchronizing many ObservableCollections. However, I think this might solve your problem. If using the CollectionChanged event is not an option for you, you might want to restructure your code.
You can create a new class that inherits ObservableCollection<T> that has a reference to the original collection. This new class subscribes to the CollectionChanged event of the original and reacts to it by changing itself.

RibbonComboBox Text Not Retaining Edits

I have the following xaml in my ui:
<ribbon:RibbonGallery SelectedValue="{Binding Text}"
SelectedValuePath="Content"
SelectedItem="{Binding SelectedRemark, Mode=TwoWay}"
MaxColumnCount="1">
<ribbon:RibbonGalleryCategory ItemsSource="{Binding Remarks}" DisplayMemberPath="Text"/>
</ribbon:RibbonGallery>
Both SelectedRemark and Remarks are properties on my view model; and Remarks is an ObservableCollection<Remark>.
It will display properly, and I can edit the text in the combobox. However, as soon as the combobox loses focus, it reverts back to whatever the the original text was.
I'm new to WPF, and cannot figure out what am I doing wrong.
Beware of a bug that causes the selectionchanged event to fire after the mouse moves. See this bug report: https://connect.microsoft.com/VisualStudio/feedback/details/666352/
Those bindings are all related to selection. I'm not certain how the Ribbon operates but it doesn't appear that what you are trying to do will give you the results you are after.
In addition the ObservableCollection<Remark> is only relative from an Add/Remove stance when making use of binding. It will not propagate changes to the items within the collection. If you were wanting that functionality you will need to implement INotifyPropertyChanged on the Remark object, then raise a property changed notification as needed.

In Silverlight, how to bind ListBox item selection to a Navigate event?

I am writing a windows-phone 7 application. I've got a page with a list of TextBlock(s) contained in a ListBox. The behavior I want is that upon clicking one of those TextBlock(s) the page is redirected to a different one, passing the Text of that TextBlock as an argument.
This is the xaml code: (here I am binding to a collection of strings, and the event MouseLeftButtonDown is attached to each TextBlock).
<ListBox x:Name="List1" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock MouseLeftButtonDown="List1_MouseLeftButtonDown" Text="{Binding}"
FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But this has been unsuccessful for me. I have tried attaching MouseLeftButtonDown event to either the individual TextBox(es) or to the ListBox. And I have had exceptions raised as soon as I use NavigationService.Navigate(uri). Which event should be attached? Should the event be attached to the individual items or to the list as a whole?
I have found a way to work around this problem by populating ListBox with HyperlinkButton(s). However, I would like to understand why the TextBox approach did not work.
This is my first attempt with Silverlight, so I might be missing something basic here.
There are a few ways to do this but I'll walk you through one of the the simplest (but not the purest from an architectural perspective).
Basically you want to find out when the selection of the ListBox changes. The ListBox raises a SelectionChanged event which can be listened to in the code behind.
<ListBox x:Name="List1" ItemsSource="{Binding}" SelectionChanged="SelectionChangedHandler" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then have a handler something like:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
}
One thing you'll need to be aware of is that ListBoxes support multiple selection. For this reason, the event arguments give you back a list of the selected items. For simplicity, all I've done is taken the first value from this list and used that as the navigation value. Notice how I've also set the SlectionMode property of the ListBox to Single which will ensure the user can only select one item.
If I were doing this for real I'd look into creating an TriggerAction tat can be hooked up to an event trigger through xaml which will remove the for code behinds. Take a look at this link if you're interesetd.
In addition to Chris' and James' replies, I'd add that you will also need to clear the listbox selection in the event handler, otherwise the user won't be able to tap the same item twice on the listbox (because the item will already be selected).
Using James' approach, I would change the SelectionChangedHandler() implementation as follows:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
// Avoid entering an infinite loop
if (e.AddedItems.Count == 0)
{
return;
}
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
// Clear the listbox selection
((ListBox)sender).SelectedItem = null;
}
What I would recommend is binding the SelectedItem property of the ListBox to a property in your ViewModel. Then, on the ListBox's SelectedItemChanged event, navigate to to the appropriate URL passing the data key on the QueryString, or upgrade to something like MVVM Light and put the actual SelectedItem object on the message bus for the child window to pick up. I have a sample of this second method on my Skydrive that you can check out.
HTH!
Chris

Categories

Resources