WPF - UI Not Updating When Item Added to Bound Collection - wpf

I have a Generic List of objects that I've bound to a custom control. Everything seems to work OK in the code-behind, but any changes I make to the collection don't seem to reflect in the UI (even though they're working find in all the code behind).
Here's the XAML for my UI:
<controls:ControllableListView x:Name="lvSummaryCaseEvents" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" Label="Case Events:" ItemsSource="{Binding CaseEvents}" AddButtonClicked="ControllableListView_AddButtonClicked">
And the code behind where I add the item into the collection:
_caseScreen.CaseEvents.Add(caseEvent);
//after the line above executes, lvSummaryCaseEvents (in the debugger) shows the correct number of items and the items' values are all correct. No change to the UI whatsoever
The ItemSource property in my user control:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IList), typeof(ControllableListView));
public System.Collections.IList ItemsSource
{
get
{
return this.GetValue(ItemsSourceProperty) as System.Collections.IList;
}
set
{
this.SetValue(ItemsSourceProperty, value);
}
}
And finally, the XAML for a standard ListView that lives in my user control which is bound to the ItemsSource property listed above:
<ListView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Name="lvListView" ItemsSource="{Binding ItemsSource}" View="{Binding View}" SelectionChanged="lvListView_SelectionChanged" />
Just to reiterate, everything works fine when I display the collection the first time around, but when I add an item to the collection, the UI does not reflect the changes made.
Thanks in advance,
Sonny

Change your collection to ObservableCollection<T>

Related

Bind user control dependency properties in MVVM style Windows Phone app

I'm having some issues with binding some custom controls in a Windows Phone app right now. Usually this is never an issue but apparently my mind can't comprehend this today.
So I'm doing an MVVM style setup which is good. I have my page with a view and also a viewmodel. Now on a WebClient callback I assign the dataContext of my view to the list of models in my ViewModel, nice and simple thus far...now in my view I created a ListBox with a custom control in the datatemplate which is basically a cell in the list. I once again set my user controls dataContext to binding, and binding all the models values to the regular UI elements works no problem.
Here's a sample:
<Grid Grid.Column="0">
<Image Source="{Binding SmallPath}" VerticalAlignment="Top"/>
</Grid>
<Grid Grid.Column="1">
<StackPanel Margin="12,0,0,0">
<TextBlock x:Name="MemberId_TextBlock" Text="{Binding MemberId}" FontSize="28"
Margin="0,-8,0,0"
Foreground="{StaticResource PhoneForegroundBrush}"/>
<StackPanel Orientation="Horizontal" Margin="0,-11,0,0">
<TextBlock Text="{Binding DaysReported}" FontSize="42"
Margin="0,0,0,0"
Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="days" FontSize="24"
Margin="3,19,0,0"
Foreground="{StaticResource PhoneSubtleBrush}"/>
</StackPanel>
</StackPanel>
</Grid>
That's in my user control, and here's the the view where the usercontrol is housed:
<Grid x:Name="LayoutRoot" Background="Transparent">
<ListBox Name="TopSpotter_ListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!--<TextBlock Text="{Binding MemberId}"/>-->
<controls:TopSpotterItemControl DataContext="{Binding}"/>
<Grid Height="18"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Now this is good enough but what I want to do in my view is set data from my model like Booleans that determine whether or not I should show certain Grids etc. So if I try to set a dependency property explicitly in my control it fires and will run logic in the Getter/Setters for instance. HOWEVER if I try to set these custom objects from a binding source it won't actually set.
Here's what works:
<controls:TopSpotterItemControl ChampVisibility="True">
This way will trigger the ChampVisibility property and then in the code behind of the user control I can set visibilities.
Here's what fails but I want to work:
<controls:TopSpotterItemControl ChampVisibility="{Binding IsChamp">
In addition I can still set the DataContext to {Binding} and the result will be unchanged.
In this scenario IsChamp is part of my model that I would like to bind to this user control which I guess comes from the dataContext being set on the view from the viewModel. I'm not sure what I can do to get this so the bindings work etc. without having to set custom properties.
Finally, here's my user control:
public partial class TopSpotterItemControl : UserControl
{
public string MemberId
{
get
{
return this.MemberId_TextBlock.Text;
}
set
{
this.MemberId_TextBlock.Text = value;
}
}
public bool ChampVisibility {
set
{
if (value)
{
this.Champ_Grid.Visibility = System.Windows.Visibility.Visible;
}
}
}
public static readonly DependencyProperty MemberNameProperty =
DependencyProperty.Register("MemberId", typeof(string), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public TopSpotterItemControl()
{
InitializeComponent();
}
}
Bit long winded and I hope I made things on the issue clear. My one major hang up so far, and I'd like to abstract as much control as I can to the user control via dependency properties explicitly set in xaml, rather than setting up binding in its xaml that depend on the knowledge of a model. Thanks!
Your DependencyProperty is badly formed. (I also don't see Champ_Grid defined in your class or XAML, but I assume that is an ommission)
Setting ChampVisibility = true in code works because it is unrelated to the DependencyProperty.
You can tell easily because the default value for your DP is invalid. It will compile, but the instance constructor will through an exception if it is ever invoked.
new PropertyMetadata(null)
bool = null = exception
If you call GetValue(TopSpotterItemControl.ChampVisibilityProperty) from somewhere you can confirm all of the above.
You should make changes to instance fields in the property changed handler and declare the property like the following, it will work:
Note that the property has to change (not just be set) for the event to be raised.
public bool ChampVisibility
{
get { return (bool)GetValue(ChampVisibilityProperty); }
set { SetValue(ChampVisibilityProperty, value); }
}
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility ", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(true, (s, e) =>
{
TopSpotterItemControl instance = s as TopSpotterItemControl;
instance.Champ_Grid.Visibility = instance.ChampVisibility ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
}));
Incidentally, your MemberId DependencyProperty is also completely wrong and cannot work.
Note:
The Binding on your TextBox works, because it is binding to the DataContext (your model), so it probably shows the right value.
The Dependency property in your UserControl will never be set though.
Use the propdp code-snippet in Visual Studio so you dont have to concern yourself with the complexities of Dependency Property declaration.
Also check this out for more info about Dependency Properties

WPF ComboBox initially displays first item at wrong size

I have a ComboBox on a WPF window that is giving me some heartache, in that when first displayed, the first selection is rendered improperly. The ComboBox does not display text only; it displays an object of a type that descends from UserControl. Here's the XAML for the ComboBox itself:
<ComboBox Grid.Column="0"
Height="69"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
ItemsSource="{Binding Path=ViewChoices,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Mode=OneWay}"
Margin="10"
Name="ViewPicker"
SelectionChanged="ViewPicker_SelectionChanged"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center" />
As you can see, the ComboBox's ItemsSource is bound to a property of the UserControl that owns it called ViewChoices. The ViewChoices object is an ObservableCollection.
The contents of the ComboBox is set in code in the code behind, as the exact contents in the final code will be read from an XML file; the values are hard coded right now. Essentially, a CameraViewChoice object is created for each XML row read and added to the ViewChoices collection. This all happens in the UserControl's default constructor, after called InitializeComponent(). In the UserControl's Loaded event handler, I have code which sets the ComboBox's SelectedIndex property to 0.
The CameraViewChoice object is descended from UserControl. Here's the Xaml for this control:
<UserControl x:Class="CarSystem.CustomControls.CameraViewChoice"
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"
xmlns:cs="clr-namespace:CarSystem.CustomControls"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="50">
<Border BorderBrush="Black" BorderThickness="1">
<Grid>
<Image Opacity="0.35" Source="..." Stretch="Uniform" />
<Label Content="{Binding Path=Text}"
FontSize="18"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</UserControl>
Here's the code-behind for the CameraViewChoice object:
public partial class CameraViewChoice : UserControl {
public static readonly DependencyProperty AttachedCameraProperty = DependencyProperty.Register( "AttachedCamera", typeof( Camera ), typeof( CameraViewChoice ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender ) );
public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof( string ), typeof( CameraViewChoice ), new FrameworkPropertyMetadata( string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsRender ) );
public Camera AttachedCamera {
get { return (Camera) GetValue( AttachedCameraProperty ); }
set { SetValue( AttachedCameraProperty, value ); }
}
public string Text {
get { return (string) GetValue( TextProperty ); }
set { SetValue( TextProperty, value ); }
}
public CameraViewChoice() {
InitializeComponent();
}
This is all working fine but there's one problem. When the program starts running and the ComboBox displayed for the first time, the selected item is displayed all wrong. The label is blank and the CameraViewChoice control is displayed too large, so much so that the bottom of it is cut off. What I'm seeing is a blank CameraViewChoice object displayed without scaling to the ComboBox.
If I choose an item in the list, all of the choices in the list display properly and are sized properly & look fine after that, including the selected one. The problem is only when the window is first displayed.
Does anyone have any ideas about what's going on?
Tony
Edit:
I did some research on Google & MSDN Magazine and I found an article by Josh Smith about Data Templates. From there, I made the following changes to the XAML for my ComboBox:
<ComboBox Grid.Column="0"
Height="69"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
ItemsSource="{Binding Path=ViewChoices,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Mode=OneWay}"
Margin="10"
Name="ViewPicker"
SelectionChanged="ViewPicker_SelectionChanged"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<cs:CameraViewChoice Margin="10" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This is better as the items in the ComboBox do not change size, but the initial display is still too large. That is, it's getting cut off at the bottom. Further, the size of the selected item is consistently too large. So when you select one item in the list, it's displayed in the combobox partially clipped.
I want the choice displayed in the ComboBox with all of it fitting in side of it. Any suggestions for changes to the CombobBox's ItemTemplate?
In the Loaded event, provided you have at least 2 items, set the SelectedIndex to 1. After this, no matter how many items you have, call InvalidateMeasure and UpdateLayout on the ComboBox, then set the SelectedIndex to 0.
Here's what I think is happening.
You are using a standard ComboBox and dynamically adding UIElements to it. When the ComboBox is first displayed, there are no items, so it uses a default template. After you start adding UIElements to it, the renderer then performs it measuring and arranging. In essence, it's only learning what it should look like after the UIElements are created and inserted (but it still needed to know what to look like before that happened).
My suggestion would be to move from this development pattern to a more common methodology. Instead of creating UIElements on the fly, just create an ObservableCollection of CameraChoices (or whatever name would be appropriate). Typically this would be contained in a ViewModel.
Then instead of creating a UserControl and inserting it into the ItemsSource of the ComboBox, you'd be better served to create an ItemsTemplate (where you can use the UserControl) for the ComboBox. Alternatively, you can use a DataTemplate of the same type as the object in the ObservableCollection.
This will provide a more robust mechanism for displaying the list of items and provide you with a way to get to the raw data instead of having to deal with a UIElement when the SelectionChanged event is signaled.

Silverlight 3 data-binding child property doesn't update

I have a Silverlight control that has my root ViewModel object as it's data source. The ViewModel exposes a list of Cards as well as a SelectedCard property which is bound to a drop-down list at the top of the view. I then have a form of sorts at the bottom that displays the properties of the SelectedCard. My XAML appears as (reduced for simplicity):
<StackPanel Orientation="Vertical">
<ComboBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<TextBlock Text="{Binding Path=SelectedCard.Name}"
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=SelectedCard.PendingTransactions}"
/>
</StackPanel>
I would expect the TextBlock and ListBox to update whenever I select a new item in the ComboBox, but this is not the case. I'm sure it has to do with the fact that the TextBlock and ListBox are actually bound to properties of the SelectedCard so it is listening for property change notifications for the properties on that object. But, I would have thought that data-binding would be smart enough to recognize that the parent object in the binding expression had changed and update the entire binding.
It bears noting that the PendingTransactions property (bound to the ListBox) is lazy-loaded. So, the first time I select an item in the ComboBox, I do make the async call and load the list and the UI updates to display the information corresponding to the selected item. However, when I reselect an item, the UI doesn't change!
For example, if my original list contains three cards, I select the first card by default. Data-binding does attempt to access the PendingTransactions property on that Card object and updates the ListBox correctly. If I select the second card in the list, the same thing happens and I get the list of PendingTransactions for that card displayed. But, if I select the first card again, nothing changes in my UI! Setting a breakpoint, I am able to confirm that the SelectedCard property is being updated correctly.
How can I make this work???
If you are using Silverlight 3 you will need to use INotifyPropertyChanged.
Example:
public class CardViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Card> Cards { get; set; }
private Card _selectedCard;
public SelectedCard
{
get
{
return _selectedCard;
}
set
{
if (value != _selectedCard)
{
_selectedCard = value;
NotifyPropertyChanged("SelectedCard");
}
}
}
public CardViewModel()
{
Cards = new ObservableCollection<Card>();
//Populate Cards collection with objects
}
public void NotifyPropertyChanged(string item)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(item));
}
}
}
All you would need to do is set this class to your views DataContext and everything should be happy.
A pattern I've been using recently is to bind the data context of a container of detail info to the selected item of the list box. The XAML in your case becomes:
<StackPanel Orientation="Vertical">
<ComboBox x:Name="_lbxCards" <-- new
DisplayMemberPath="Name"
ItemsSource="{Binding Path=Cards}"
SelectedItem="{Binding Path=SelectedCard, Mode=TwoWay}"
/>
<StackPanel DataContext={Binding ElementName=_lbxCards,Path=SelectedItem}> <-- new
<TextBlock Text="{Binding Path=Name}" <-- updated
/>
<ListBox DisplayMemberPath="Name"
ItemsSource="{Binding Path=PendingTransactions}" <-- updated
/>
</StackPanel> <-- new
</StackPanel>
Turns out the problem isn't in the UI at all. The PendingTransactions class lazy-loads its values using a async WCF call to the server. The async pattern uses events to notify the caller that the operation is complete so the data can be parsed into the class. Because each Card has its own instance of the PendingTransactions class and we used a ServiceFactory to manage our WCF proxies, each instance was wiring up their event handler to the same event (we are using a singleton approach for performance reasons - for the time being). So, each instance received the event each time any of the instances triggered the async operation.
This means that the data-binding was working correctly. The PendingTransactions collections were overwriting themselves each time a new Card was viewed. So, it appeared that selecting a previous card did nothing when, in fact, it was selecting the correct object for binding, it was the data that was screwed up and make it look like nothing was changing.
Thanks for the advice and guidance nonetheless!

MVVM ListBox controlling a Content Control

I've been going round in circles with this for a couple of days, and I'm hoping a WPF guru can see where I'm going wrong.
I'm setting CurrentViewModel in code. The Selected item of my ListBox and the Content of my ContentControl bind correctly. But when changing the selected item in the Listbox via the UI the CurrentViewModel is being set but the Content Control is not being updated.
I'm using a data template to map my Views and View Models.
<DataTemplate DataType="{x:Type ViewModel:MyViewModel}">
<View:MyView />
</DataTemplate>
I have a ListBox which is bound to an observable collection of ViewModels. The Selected Item is bound to the current view model.
<ListBox ItemsSource="{Binding MyViewModelCollection}" DisplayMemberPath="DisplayName" SelectedItem="{Binding CurrentViewModel, Mode=TwoWay}"/>
I also have a content control that is also bound to the CurrentView Model
<ContentControl Content="{Binding CurrentViewModel, Mode=TwoWay}"/>
This is the property that they are both bound to
public MyViewModel CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel== value) return;
_currentViewModel= value;
OnPropertyChanged("CurrentViewModel");
}
}
I've edited the names for clarity and removed formatting information.
Any help greatly appreciated.
Cheers,
Daniel
EDIT: Came across the link How can I debug WPF bindings?. I set a break point on the Content binding and it does indeed only get called once when the binding is first set.
You should not be setting TwoWay as the mode on your ContentControl:
<ContentControl Content="{Binding CurrentViewModel, Mode=OneWay}"/>
This is because you intend your ContentControl to read the value, but never write it.
As an aside, you can also bind the ContentControl to the currently selected item in the collection, rather than to that property by doing this:
<ListBox ItemsSource="{Binding MyViewModelCollection}"
DisplayMemberPath="DisplayName"
IsSynchronizedWithCurrentItem="True"/>
<ContentControl Content="{Binding MyViewModelCollection/}"/>
The "slash" (/) at the end of the collection indicates the current item selected in the collection and setting that current item property is as simple as setting the IsSynchronizedWithCurrentItem equal to true.
A lot of times I find with this combination, I really don't need the extra property on my view model.
Anyway, I hope this helps.

How to bind an observable collection to Multiple user controls at runtime?

I am stucked at the part where I have to bind a collection to a dynamic usercontrol. Scenario is something like this.
I have a dynamic control, having a expander , datagrid, combobox and textbox, where combox and textbox are inside datagrid. There are already two collections with them. One is binded with combobox and another is binded with datagrid. When the item is changes in combox its respective value is set to its respective textbox, and so on. and this pair of value is then set to the collection binded with datagrid. A user can add multiple items.
Now the main problem is that all these things are happening inside a user control which is added dynamically, that is on button click event. A user can add desired numbers of user controls to the form.
problem is coming in this situtaion. Say I have added 3 controls. Now in 1st one if i add a code to the collection then it gets reflected in the next two controls too, as they are binded with same collection.
So, I want to know is there anyway to regenrate/rename the same collection so that the above condition should not arise.
It's hard to answer your question without seeing the bigger picture, however I have a feeling you are going about this the wrong way. It appears that you are adding instances of your user control directly from code. Instead of doing that, you should create some kind of ItemsControl in your XAML, and in its ItemTemplate have your user control. Bind that ItemsControl to a collection in your view model, and only manipulate that collection.
You should not be referring to visual controls in your view model or code behind. Whenever you find yourself referencing visual elements directly from code, it should raise a warning flag in your mind "Hey! There's a better way than that!"...
Example:
The view model:
public class ViewModel
{
public ObservableCollection<MyDataObject> MyDataObjects { get; set; }
public ViewModel()
{
MyDataObjects = new ObservableCollection<MyDataObject>
{
new MyDataObject { Name="Name1", Value="Value1" },
new MyDataObject { Name="Name2", Value="Value2" }
};
}
}
public class MyDataObject
{
public string Name { get; set; }
public string Value { get; set; }
}
The window XAML fragment containing the list box and the data template:
<Window.Resources>
...
<DataTemplate x:Key="MyDataTemplate">
<local:MyUserControl/>
</DataTemplate>
</Window.Resources>
...
<ListBox ItemsSource="{Binding MyDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}"
HorizontalContentAlignment="Stretch"/>
The user control:
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}" HorizontalAlignment="Right"/>
</UniformGrid>

Resources