UI not updating even when CollectionChanged or PropertyChanged events are fired - silverlight

Background, from MSDN:
ObservableCollections CollectionChanged event will only be raised
when properties of ObservableCollection are changed (Addition,
deletion of an element) and not when the properties of existing elements are changed.
Bummer, because I need the UI to update when a specific property of an existing element changes. I tried firing both CollectionChanged events and PropertyChanged Events but neither worked.
My situation:
In my application, I have a listbox bound to an observablecollection where the visibility of the items depends on the "Favorite" property of each item using a BoolToVisibilityConverter. XAML:
<ListBox x:Name="FavoritesListBox"
Margin="0,0,-12,0"
ItemsSource="{Binding FeedItemOCollection}"
SelectionChanged="FavoritesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="FavoritesStackPanel"
Margin="0,0,0,17" Visibility="{Binding Favorite, Converter={StaticResource BooltoVisibilityConverter}}">
<TextBlock Text="{Binding Title}"
TextWrapping="Wrap"
Margin="12,0,0,0"
Style="{StaticResource PhoneTextLargeStyle}" />
<TextBlock Text="{Binding PublishDate,Converter={StaticResource DateTimeToDateConverter}}"
TextWrapping="Wrap"
Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSmallStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note: This object is initialized in App.Xaml.cs and so is global for the whole application. This may be the unusual thing that is causing things not to work for me.
Once the initial binding occurs, changes to the value of an element's Favorite property does not cause the item to show up or disappear from the Favorites Listbox as is desired for the reason noted at the beginning of the post. This is expected.
To work around this I've tried firing both CollectionChanged events and PropertyChanged Events when the Favorite property is changed to get the UI to update, but neither worked and I'm confused why not. I have succeed in working around my issue, by adding and removing the element from the ObservableCollection, but clearly this is a hack. Code:
public void MarkFavorite(FeedItem feedItem)
{
try
{
feedItem.Favorite = true;
//CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); <-- why doesn't this work?
//PropertyChanged(this, new PropertyChangedEventArgs("Count")); <-- why doesn't this work?
this.Remove(feedItem); <-- this works, but is a hack
this.Add(feedItem); <-- this works, but is a hack
SaveToIso();
}
catch (Exception exception)
{
//TODO: Log this.
}
}
Why doesn't firing the events work?
Many thanks ahead of time.

Your FeedItem class must implement INotifyPropertyChanged interface, and your Favorite property must look like:
private bool _Favorite;
private bool _Favorite;
public bool Favorite
{
get { return _Favorite; }
set
{
_Favorite = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Favorite"));
}
}
Or you can extract a method
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and your property will look like this:
private bool _Favorite;
public bool Favorite
{
get { return _Favorite; }
set
{
_Favorite = value;
OnPropertyChanged("Favorite");
}
}

Related

WPF Button binding to ICommand not firing

I have a Button bound to an ICommand interface but it isn't being fired when I run the application.
The button should be disabled when the app runs, putting a breakpoint in the ICommand or CanUpdate but it isn't being hit.
The ICommand seems to have been implemented correctly as far I can see - have substituted value in CanUpdate for simplicity...
Scratching my head to workout what is missing?....
XAML
<StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1">
<RadioButton Width="64" IsChecked="{Binding Passed}" GroupName="T1">Yes</RadioButton>
<RadioButton Width="64" IsChecked="{Binding Passed, Converter={StaticResource InverseBoolRadioConverter}}" GroupName="T1" >No</RadioButton>
</StackPanel >
Button Command="{Binding UpdateHasPassed}" Content="Update"></Button>
Code-Behind:-
private RelayCommand hasPassed;
public bool Passed
{
get
{
return passed;
}
set
{
if (passed !=value )
{
passed = value;
OnPropertyChanged();
}
}
}
public ICommand HasPassed
{
get
{
if (hasPassed == null)
{
haspassed = new RelayCommand( param => CanUpdate());
}
return haspassed;
}
}
private bool CanUpdate()
{
return (1 != 2)
}
You're on the right track looking into INotifyPropertyChanged. I would also recommend reading up on WPF data bindings, the ICommand interface (and specifically creating a RelayCommand, more on that later), and MVVM design.
The benefit of WPF data bindings and ICommand is that you can control when the button gets enabled or disabled, based on your conditional criteria (i.e. name has changed from its original value). With the tips mentioned here, you should be able to do what you want in short time. Just google each of the topics and you'll get what you need.

Why isn't my user control with a combobox binding correctly?

I've got a really simple UserControl I'm trying to create that contains a list of US states. I am trying to expose the selected state via a "SelectedState" property. However, I'm having trouble trying to get this binding to fire once it's hooked up in another UserControl / form.
The XAML for the user control looks like this:
<UserControl x:Class="Sample.Desktop.UserControls.StateDropdown"
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:local="clr-namespace:Sample.Desktop.UserControls"
mc:Ignorable="d"
Width="170" Height="28"
d:DesignHeight="28" d:DesignWidth="170">
<ComboBox x:Name="cboState"
ItemsSource="{Binding StateList, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedValue="{Binding SelectedState, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Abbreviation}"></Label>
<Label> - </Label>
<Label Content="{Binding Name}"></Label>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In the code-behind, I have this code:
public static readonly DependencyProperty SelectedStateProperty = DependencyProperty.Register("SelectedState",
typeof(USState),
typeof(StateDropdown),
new UIPropertyMetadata(null,
new PropertyChangedCallback(OnSelectedStateChanged),
new CoerceValueCallback(OnCoerceSelectedState)));
private static object OnCoerceSelectedState(DependencyObject o, object value)
{
StateDropdown stateDropdown = o as StateDropdown;
if (stateDropdown != null)
return stateDropdown.OnCoerceSelectedState((USState)value);
else
return value;
}
private static void OnSelectedStateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
StateDropdown stateDropdown = o as StateDropdown;
if (stateDropdown != null)
stateDropdown.OnSelectedStateChanged((USState)e.OldValue, (USState)e.NewValue);
}
protected virtual USState OnCoerceSelectedState(USState value)
{
// TODO: Keep the proposed value within the desired range.
return value;
}
protected virtual void OnSelectedStateChanged(USState oldValue, USState newValue)
{
// TODO: Add your property changed side-effects. Descendants can override as well.
}
public USState SelectedState
{
// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
get
{
return (USState)GetValue(SelectedStateProperty);
}
set
{
SetValue(SelectedStateProperty, value);
}
}
I wasn't able to get the SelectedValue bound property of SelectedState to fire, so I ended up hooking up the SelectionChanged event.
private void cboState_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems?.Count > 0)
{
SelectedState = (USState)e.AddedItems[0];
}
}
In my other user control, I have this in the XAML:
<uc:StateDropdown Margin="10,0,0,0" SelectedState="{Binding SelectedState}" ></uc:StateDropdown>
And the ViewModel (I'm using Caliburn Micro), I have this property:
protected USState _selectedState;
public USState SelectedState
{
get { return _selectedState; }
set
{
_selectedState = value;
NotifyOfPropertyChange(() => SelectedState);
}
}
The combo is populated as expected. However, SelectedState is never fired/updated when I change the selection.
I had also previously tried using SelectedItem instead of SelectedValue, with the same results.
I'm sure I'm missing something obvious, but I'm having trouble seeing where I went wrong.
EDIT: Here's what fixed the binding.
I removed the SelectionChanged event. Then I modified my "hosting page" usercontrol to set TwoWay binding:
<uc:StateDropdown Margin="10,0,0,0" SelectedState="{Binding SelectedState, Mode=TwoWay}" ></uc:StateDropdown>
As soon as I added that, SelectedState started being updated when I changed the ComboBox value.
The only things I see, is this line :
SelectedValue="{Binding SelectedState, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
You don't need it, because of the SelectionChanged event. And it can cause the problem.
Also I would bind the SelectedState of the UserControl using a TwoWay binding.
Hope that will help you.

Viewmodel object does not receive database source changes

New to WPF, MVVM, data binding, and Entity Framework, so apologies in advance.
I'm attempting to bind WPF controls to my database-generated model objects through a viewmodel. I'm able to change database values by typing in the textbox, but any direct changes to the database rows do not seem to fire off the PropertyChanged event. At least they are not reflected in my viewmodel objects. I've implemented iNotifyPropertyChanged on my Entity Framework generated classes thus:
public partial class GenGround : INotifyPropertyChanged
{
public double ClMax
{
get { return (double)this.clMax; }
set
{
this.clMax = (float)value;
MainWindow.db.SaveChanges();
OnPropertyChanged("ClMax");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
I have a listbox:
<DataTemplate x:Key="missionLegTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<ListBox x:Name="missionList" SelectionChanged="missionList_SelectionChanged" Grid.ColumnSpan="2" ItemsSource="{Binding Mode=OneWay}" ItemTemplate="{DynamicResource missionLegTemplate}" />
private void MainWindow1_Loaded(object sender, RoutedEventArgs e)
{
this.missionList.ItemsSource = _CurrentMissionProfile.MissionLegs;
}
"MissionLegs" is an ObservableCollection of MissionLeg objects, attached to the database. The selected item of this listbox should tell the textboxes what properties to get and set. Textbox:
<TextBox x:Name="velocityBox" Text="{Binding Mode=TwoWay,Path=SelectedItem.Velocity, ElementName=missionList}" IsEnabled="False" />
As I said, this seems to write to the database, but when I make changes to the corresponding row in SSMS, nothing seems to happen. Ideas?

Treeview Checkboxes not updating after bound property changed (SL4)

My problem is simple. I have a treeview bound to an ObservableCollection of objects, and those objects all have their own ObservableCollections. Based on user selection of other criteria on my page I want to dynamically set which checkboxes are checked. Unfortunately my checkboxes fail to update their IsChecked status after I have changed the corresponding bool Property bound to IsChecked. The checkboxes will be in the correct state the first time any node is expanded, but afterwards they stop updating. I suspect this means the objects are not created/evaluated until they are actually due to be shown for the first time.
The structure of data is Silverlight -> ViewModel -> ObservableCollection of StoreGroups LocalStoreGroups -> StoreGroup has ObservableCollection of Store Stores
Through debugging I have noticed that there are no handlers attached to this.PropertyChanged, and am wondering if this is the problem?
Treeview control :
<controls:TreeView ItemsSource="{Binding LocalStoreGroups}" ItemTemplate="{StaticResource TreeviewStoreGroupTemplate}" />
In my project I use a treeview with the following HeirarchalDataTemplates :
<UserControl.Resources>
<sdk:HierarchicalDataTemplate x:Key="TreeviewStoreTemplate">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Content="{Binding DTO.Name}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="TreeviewStoreGroupTemplate" ItemsSource="{Binding Stores}" ItemTemplate="{StaticResource TreeviewStoreTemplate}">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Content="{Binding DTO.Name}" />
</sdk:HierarchicalDataTemplate>
</UserControl.Resources>
The code for the IsSelected Property (both the StoreGroup object and the Store object have this property :
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler temp = this.PropertyChanged;
if (null != temp)
temp(this, e);
}
Code to change IsSelected
foreach (Store s in LocalStoreGroups.SelectMany(sg => sg.Stores))
{
s.IsSelected = false;
}
foreach (StoreLink link in links)
{
Store targetStore = (from s in LocalStoreGroups.SelectMany(sg => sg.Stores) where s.DTO.ID == link.DTO.StoreID select s).FirstOrDefault();
targetStore.IsSelected = true;
}
It looks like you are updating the property in response to a load event. It is likely then that you are not on the UI thread when you update the property. Unless the change occurs on the UI thread it will not update the display.
For bound properties and properties that are collections (and not children in observable collections) it is only the OnPropertyChanged that needs to be on the UI thread. The properties can change earlier, but the UI will not change bindings until OnPropertyChanged is called.
All our ViewModels derive from a ViewModelBase we created that implements a helper SendPropertyChanged like below (so we never have to worry about cross-threading).
All our notify properties call that instead of calling OnPropertyChanged directly.
It also exposes a generally useful OnUiThread method so you can execute arbitrary code on the UI thread:
protected delegate void OnUiThreadDelegate();
public event PropertyChangedEventHandler PropertyChanged;
public void SendPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.OnUiThread(() => this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
}
protected void OnUiThread(OnUiThreadDelegate onUiThreadDelegate)
{
if (Deployment.Current.Dispatcher.CheckAccess())
{
onUiThreadDelegate();
}
else
{
Deployment.Current.Dispatcher.BeginInvoke(onUiThreadDelegate);
}
}
Anyways, the give-away here should have been that nobody was subscribed to the PropertyChanged event. Turns out that although I implemented the PropertyChanged event I forgot to actually give the class the INotifyPropertyChanged interface.

Inner property of ItemsControl not updating when bound on ItemsSource

I have an ItemsControl like the following
<ItemsControl ItemsSource="{Binding MyClass.Links}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid d:DesignWidth="450" d:DesignHeight="245" Height="Auto" Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="145"/>
<ColumnDefinition Width="Auto" MinWidth="179"/>
</Grid.ColumnDefinitions>
<HyperlinkButton Content="{Binding ViewName}" IsEnabled="{Binding ViewEnabled, Mode=OneWay}" cmd:Click.Command="{Binding DataSource.ViewCommand, Source={StaticResource DataContextProxy}}" cmd:Click.CommandParameter="{Binding}" Margin="4"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
I have an ObservableCollection of the following class that the itemssource is getting bound to
public class LinkClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string ViewName { get; set; }
private bool _viewEnabled;
public bool ViewEnabled {
get { return this._viewEnabled; }
set
{
if (value != this._viewEnabled)
{
this._viewEnabled = value;
if (this.PropertyChanged != null)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
this.PropertyChanged(this, new PropertyChangedEventArgs("ViewEnabled"))
);
}
}
}
}
}
When the command is hit in the view model, the bound link's ViewEnabled is getting set to false (disable link for view I'm looking at). The problem is, the link isn't actually getting disabled (IsEnabled set to false).
So the end question is, why isn't this working? I'm new to MVVM and silverlight, so I'm hoping it's something simple.
UPDATE
I'm setting the ViewEnabled property to true for all but the clicked button's bound LinkClass, which I'm setting to false. It is firing the PropertyChanged event for each (that changes), but not updating the UI. I ran an empty converter with the binding and it isn't getting hit either when the link is clicked, so the PropertyChanged isn't bubbling properly (or as I suspect it should anyway).
Here's the code setting the ViewEnabled properties of my collection of LinkClass:
public ICommand ViewCommand
{
get {
return new DelegateCommand<object>(param =>
{
this.ViewSelected((LinkClass)param);
});
}
}
public void ViewSelected(LinkClass link)
{
foreach (var containerLink in _myClass.Links)
{
if (containerLink == link)
containerLink.ViewEnabled = false;
else
containerLink.ViewEnabled = true;
}
...other code here
}
Well it might actually be getting disabled but if your ViewCommand isn't paying attention to that property then you're stuck. Especially since it looks like that command is an attached property.
Googling got me this post that you might want to look at.
But personally I would look at your CanExecute of your ViewCommand and make sure that it is only running if ViewEnabled == true
When I was using MVVM, in the setter of my properties I had a method named NotifyPropertyChanged() and would call it passing the string value for the property's name. I'm not sure what Deployment.Current.Dispatcher.BeginInvoke(...) does, but this method always worked for me.
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler.IsNotNull())
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
So in my property I would do something like...
public Nullable<int> UpdatedBy
{
get { return _updatedBy; }
set
{
if (_updatedBy.IsEqualTo(value))
return;
_updatedBy = value;
NotifyPropertyChanged("UpdatedBy");
}
}
Also, just grasping at straws, but try putting {Binding Path=ViewEnabled, ...}
Hope this helps.
Taking Jose's advice, I looked at the canExecute method of the ViewCommand (DelegateCommand) I was using, but implementing the method didn't solve the problem, as it only was run once, and not when changed. I found an example recommending to use the PropertyChanged event handler of the INotifyPropertyChanged class to call the RaiseCanExecuteChanged() method of the DelegateCommand. I did this for all of the LinkClass instances, as shown here for 1 before setting it to _myClass.Links:
var link = new LinkClass()
{
...
ViewEnabled = true
};
link.PropertyChanged += new PropertyChangedEventHandler(link_PropertyChanged);
return link;
I did this, but to no avail. I then found this blog post:DelegateCommand in Prism loses eventhandler and CanExecute never gets called I then switched from Prism to a RelayCommand class and it worked! Hopefully this helps someone else out.
UPDATE
The actual issue was in using Prism's cmd:Click.Command and cmd:Click.CommandParameter in xaml. Switching from that to Command and CommandParameter properties in xaml, as I did after switching to the RelayCommand, is what actually got it working.

Resources