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.
Related
Is there a way to call methods from the view from the view model? Is this good practice to do so? If not, how would I hide elements in the view from the view model? I'm just a bit confused because I'm used to working with ASP.Net, with code behind, etc.
xaml.cs
btnsave.visibility = visibility.hidden;
btnclose.visibility = visibility.hidden;
For your specific example of hiding elements in the view, you probably want to set up some properties in the ViewModel that define the conditions under which those elements are visible. Then you bind the Visibility property (with a BooleanToVisibilityConverter, most likely) of those elements in the View to those properties in the ViewModel.
More generally, you want to keep the direct coupling between them minimal if you can, but sometimes "reality" gets in the way. I've had some cases where I've passed in the View to the constructor of the ViewModel. Other cases where it's been an interface that the View implements and that gets passed into the ViewModel. So there are options. But you should make sure you HAVE to go that route before doing it.
Example:
XAML:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="_B2VC" />
</Window.Resources>
<StackPanel>
<Button Content="Save" Visibility="{Binding IsSaveButtonVisible}" />
<Button Content="Close" Visibility="{Binding IsCloseButtonVisible}" />
</StackPanel>
</Window>
ViewModel:
public class ViewModel: INotifyPropertyChanged
{
#region INPC Stuff
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private bool _IsSaveButtonVisible;
public bool IsSaveButtonVisible
{
get { return _IsSaveButtonVisible; }
set
{
if (_IsSaveButtonVisible != value)
{
_IsSaveButtonVisible = value;
RaisePropertyChanged("IsSaveButtonVisible");
}
}
}
private bool _IsCloseButtonVisible;
public bool IsCloseButtonVisible
{
get { return _IsCloseButtonVisible; }
set
{
if (_IsCloseButtonVisible != value)
{
_IsCloseButtonVisible = value;
RaisePropertyChanged("IsCloseButtonVisible");
}
}
}
}
Then your ViewModel changes those properties in response to whatever it needs to (say for instance Save is only valid if they've changed something - once that something is changed, the property on the ViewModel gets updated and bam, that gets propogated to the View.
If you need further examples, i'd just suggest going and reading on MVVM. It takes a bit to grok, but its awesome once in use.
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");
}
}
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.
I need to bind so that the Content of a content control is set to the SelectedValue of either the TreeView or the ListBox. The SelectedValue that was most recently changed should provide the content for the ContentControl.
I was able to get this working using the following concept.
Bind the content control to a read only property "SelectedItem" (with private property _selectedItem).
Bind the ListBox.SelectedItem to a read/write property "SelectedItemLB".
In the SelectedItemLB setter, set the value of _selectedItem, and raise the PropertyChanged event for SelectedItem.
Create a handler for VreeView.SelectedItemChanged, which sets the value of _selectedItem and raises the PropertyChanged event for SelectedItem.
Here is my full code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.items = new List<object>();
this.items.Add(new Car("Green"));
this.items.Add(new Car("Blue"));
this.items.Add(new Car("Red"));
this._selectedItem = this.items[0];
this.treeView1.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(treeView1_SelectedItemChanged);
this.DataContext = this;
}
void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this._selectedItem = treeView1.SelectedItem;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
private List<object> items;
public List<object> Items
{
get { return items; }
set { items = value; }
}
public object SelectedItemLB
{
get { return _selectedItem; }
set
{
_selectedItem = value;
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
The XAML is pretty simple:
<StackPanel>
<ListBox Name="listBox1" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItemLB, Mode=TwoWay}" ></ListBox>
<TreeView Name="treeView1" ItemsSource="{Binding Path=Items}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}"></Setter>
</Style>
</TreeView.Resources>
</TreeView>
<ContentControl Content="{Binding Path=SelectedItem.Color}"></ContentControl>
</StackPanel>
I can't think of a way to do that directly. However there are several straightforward solutions.
A. Use events to set the Content
Simply attach a common handler to the SelectedValueChanged events of your ItemsControls. Whenever one of them changes its selection, the handler will set the Content to whatever was selected. I think this is most simple.
B. Use intermediary properties
Bind the SelectedValue of each ItemsControl to a property. In the property's setter, also set the Content equal to value. This allows you to use data binding instead of event handlers, but it still requires you to write code-behind and it doesn't buy you much. Of course, if you are already binding to properties for other purposes, there is almost no extra cost (only an assignment in each setter) so this method might be preferable.
I have simple issue setting a two-way databinding of a checkbox in Silverlight 3.0. It must be a no-brainer but probably I forgot my brain home today...
I defined a Model class to represent my .. 'data'. I implemented the INotifyPropertyChanged interface to enable the UI to see when the data changes.
public class Model : INotifyPropertyChanged
{
private bool _value;
public bool Value
{
get { return this._value; }
set
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
this._value = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Next I put a checkbox and a button on the .. 'form' :
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="check" IsChecked="{Binding Value, Mode=TwoWay}" Content="SomeLabel"/>
<Button Click="Button_Click" Content="Test" />
</StackPanel>
Then I supplied the data in the constructor :
public MainPage()
{
InitializeComponent();
this.DataContext = new Model() { Value = true };
}
The issue is that you have to click twice on the checkbox for it to check/uncheck unless I de-implement the INotifyPropertyChanged. If de-implement it however, then the UI doesn't notice if I change the underlying data.
If I remove the Mode=TwoWay bit from the IsChecked binding expression then also the UI won't notice the underlying data change even if the Model is implementing the interface.
How can I do to :
Have the checkbox bound to the data at startup
Have the checkbox IsChecked change to modify the underlying data
Have the checkbox detect the underlying data change and update itself?
You've got a sequencing error in your set property procedure, you need to assign to _value before notifying the change :-
set
{
this._value = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}