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.
Related
I'm trying to hide an element based on a bool. I use a button in this example, but it doesn't work no matter what element type I use.
Here is the XAMl that contains the binding.
<Button
Command="local:CustomCommands.Toggle"
Content="Toggle"
Visibility="{Binding Show, Converter={StaticResource BoolToVis}}" />
Here is the view model I am binding to.
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool show = true;
public bool Show
{
get
{
return show;
}
set
{
value = show;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Show)));
}
}
}
}
I've debugged and see the property changing, but nothign updates on the view.
Any ideas?
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.
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 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"));
}
I am trying to bind a list box to a collection. The problem is that the collection can change, but the collection doesn't implement IObservableCollection. What is the best way to force the binding to refresh?
As Tormod suggested, the preferable methods would be changing the collection to an ObservableCollection, or implementing INotifyCollectionChanged in the collection would take care of refreshing the UI.
However, if those options aren't available, then you can 'force' a refresh by using INotifyPropertyChanged in whatever class contains the collection. We then will be treating the list just like a regular property, and using the setter to notify on changes. To do this it requires re-assigning the reference, which is why using something like an ObservableCollection is preferred, as well as raising the PropertyChanged event.
Here is a quick sample showing how this can be done with just a standard generic List:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
this.Names = new List<string>() { "Mike", "Robert" };
this.DataContext = this;
}
private IList<string> myNames;
public IList<string> Names
{
get
{
return this.myNames;
}
set
{
this.myNames = value;
this.NotifyPropertyChanged("Names");
}
}
private void OnAddName(object sender, RoutedEventArgs e)
{
Names.Add("Kevin");
Names = Names.ToList();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Xaml:
<Window x:Class="Sample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300">
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding Names}" />
<Button Content="Add Name"
Click="OnAddName" />
</StackPanel>
</Grid>
Without more information on how and where this collection is used, here are some pointers which may help you.
If the collection is not sealed, you could inherit it.
If the collection is sealed, you could create an adapter class which contains an instance of your collection and wraps all relevant methods.
In any case, your new class could implement IObservableCollection and be used for binding.
You can set a binding to update explicitly and then trigger an update through code by say having a refresh button for example.
As an example.
<StackPanel>
<ListBox
x:Name="lb"
ItemsSource="{Binding SomeList, UpdateSourceTrigger=Explicit}"
/>
<Button Content="Refresh" Click="Refresh_Click" />
</StackPanel>
private void Refresh_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = lb.GetBindingExpression(ListBox.ItemsSourceProperty);
be.UpdateSource();
}
You can also force a refresh in your ViewModel. This is sth I've seen Josh Smith do in his MVVM demo app:
ICollectionView coll = CollectionViewSource.GetDefaultView(myCollection);
if (coll!=null)
coll.Refresh();
myCollection can be any type of collection that you have bound to the View.
Bea Stollnitz has a bit more information about CollectionViewSource:
http://www.beacosta.com/blog/?m=200608