I am working with C# using the MVVM pattern. I have two WPF windows, each with a view model. Basically I need to pass a property of the main view model to the 'child' view model. At the minute, I do this by setting a private variable equal to the new view model in the main view model's constructor, and in doing so passing the property in the constructor of the child view model.
However, there is a dependency property linked to the property as it used as a binding for the selected item in a combobox. Therefore, it is likely to change after the child view model is initialized, but by passing the property in the constructor, the change is not made in my child view model.
So, is there anyway for me to pass the property into the constructor and have it change in the child view model when it does in the main view model? Or would I have to create a property in the child view model which is updated everytime the property in the main view model is set?
Hope that makes sense.
Edit Inside my main view model, I declare the following:
public readonly DependencyProperty CurrentDatabaseManagedProperty = DependencyProperty.Register("CurrentDatabaseManaged", typeof(DatabaseInfo), typeof(MainViewModel));
public DatabaseInfo CurrentDatabaseManaged {
get { return (DatabaseInfo)GetValue(CurrentDatabaseManagedProperty); }
set { SetValue(CurrentDatabaseManagedProperty, value); }
}
public DatabaseInfo CurrentDatabaseManagedSelection {
get { return CurrentDatabaseManaged; }
set {
if (CurrentDatabaseManaged != null &&
(String.Equals(value.Name, CurrentDatabaseManaged.Name, StringComparison.OrdinalIgnoreCase))) return;
CurrentDatabaseManaged = (value.IsUsable ? value : dbm.ReadDatabase(value.FileName));
}
}
Where CurrentDatabaseManagedSelection is the SelectedItem of the combobox. In the constructor of the main view model, I have the following:
_DatabaseVM = new ChildViewModel(CurrentDatabaseManaged);
And the constructor of ChildViewModel looks like this:
public ChildViewModel( DatabaseInfo SelectedDatabase)
{
if (SelectedDatabase != null)
_SelectedDatabase = SelectedDatabase;
}
}
Basically I would like _SelectedDatabase to be updated whenever CurrentDatabaseManagedSelection is.
You have to change the value later, after bindings are set up in the UI.
Use the Dispatcher.BeginInvoke method to put off updating the property until later on.
public MyClass(object someValue)
{
Dispatcher.BeginInvoke(
(Action)(() => Property = someValue), // the cast may not be required
DispatcherPriority.ApplicationIdle); // runs after everything is loaded
}
It looks like you want to bind to the CurrentDatabaseManagedSelection property. The simplest way to simulate this is to add this to the setter of that property:
_DatabaseVM._SelectedDatabase = value;
To do it with actual bindings, you'd need to
make ChildViewModel._SelectedDatabase a dependency property,
make MainViewModel implement INotifyPropertyChanged, and
call the PropertyChanged event in the setter of CurrentDatabaseManagedSelection.
make ChildViewModel extend DependencyObject
instead of just setting the property, set a binding, e.g.
BindingOperations.SetBinding(this, _SelectedDatabaseProperty,
new Binding("CurrentDatabaseManagedSelection") { Source = mainViewModel });
Related
So the question is pretty simple, I think:
I have a View and its associated ViewModel. I have a value generated in the ViewModel and want to pass it to the View's Code Behind, following the MVVM pattern. I know it's entirely possible, because that's basically what's happening when using INotifyPropertyChanged and Data Binding, but I want to pass the value into a variable in the Code Behind, not a XAML component.
As an example, consider something like this:
ViewModel
public string VM_String { get; set; }
View's Code Behind
ViewModel MyViewModel = new ViewModel;
public string View_String { get; set; }
I simply want to pass the value in VM_String onto View_String, while following MVVM principles, so something like:
View_String = MyViewModel.VM_String;
is not what I want. I'd want those values decoupled from each other.
How would one go about doing this? Something to do with delegates / events maybe?
Thanks
You need to create a BindableProperty in your view's code-behind and use xaml to bind this property to the property in your view model. Then you can use the propertyChanged event of BindableProperty to apply your logic whenever the binding value changes.
If your view is a ContentPage, it's not possible to declare a BindableProperty in the code-behind and bind it in the xaml file of the same view. However, using an attached BindableProperty will do the job.
In the code-behind of your view (NewPage.xaml.cs):
public static readonly BindableProperty VMStringProperty
= BindableProperty.CreateAttached("VMString",
typeof(string),
typeof(NewPage),
default(string),
propertyChanged: VMStringChanged);
public static string GetVMString(BindableObject target)
{
return (string)target.GetValue(VMStringProperty);
}
private static void VMStringChanged(BindableObject target, object oldValue, object newValue)
{
var yourView = target as NewPage;
var vmString = newValue as string; // or GetVMString(target)
yourView.ApplyLogic(newValue); // ApplyLogic is an instance method in your NewPage class
}
In the xaml file of your view (NewPage.xaml):
<ContentPage
xmlns:local="clr-namespace:YourNamespace"
local:NewPage.VMString="{Binding VM_String}"
...
Using attached bindable properties this way is not a common thing to do. Instead, depending on your situation, you may consider creating a custom control having the (non-attached) VmString bindable property in it. Then you can add this control like a normal control to your NewPage and bind its VmString property to the property in your page's ViewModel.
The only way the code behind can interact with a View Model is through binding the View Model to the view.
So ViewModel MyViewModel = new ViewModel; this set up the view to know about the View Model.
VariableInCodeBehind = MyViewModel.VariableInViewModel;
This is the way you would assign a variable in the code behind with a variable from your View Model.
Issue
I want to be able to check a property from another View Model to see if it has values in it, if it does do something and vise versa.
Code
So in View Model A (OnDemandMainViewModel is the class name) I have a property which holds all the items inside of a Timeline:
public ObservableCollection<ITimeLineDataItem> Timeline2Items
{
get { return _timeline2Items; }
set
{
_timeline2Items = value;
OnPropertyChanged("Timeline2Items");
}
}
private ObservableCollection<ITimeLineDataItem> _timeline2Items;
Then in View Model B (WizardViewModel is the class name) I want to be able run an if statement to check if that property has any items:
if (//CHECK FOR ITEMS)
{
}
How would I be able to check to see if the property has any items or not?
You can do something like this (assuming OnDemandMain is your view1 and OnDemandMainViewModel your viewmodel1:
OnDemandMain win=Application.Current.Windows.OfType<OnDemandMain>().FirstOrDefault();
OnDemandMainViewModel vm=(OnDemandMainViewModel)win.DataContext;
vm.Timeline2Items.Count();
I have a ListView and a GridView that lists users in an application by names. Whenever the user selects an user to edit, I add a new tab to a TabControl, and bind all editable properties to the WPF controls.
However, when the user is editing in the Edit Tab, the information in the List (specifically, the name field) is also being updated.
Currently I'm making a copy of the object to be edited and leaving the original so it doesn't update the ListView, but isn't there a better/easier way to do this?
I've tried setting the Binding Mode=OneWay, didn't work, and also UpdateSourceTrigger=Explicit in the GridView but also didn't work.
Is there any easier way to do this?
Edit: The way I implemented my INotifyPropertyChanged class is part of the issue, since I have this:
public partial class MyTabControl : UserControl
{
public MyTabControl()
{
InitializeComponent();
//Here, DataContext is a List<Users>
//Users being my Model from the Database
//Some of it's properties are bound to a GridView
//User doesn't implement INPC
}
public void OpenTab(object sender, RoutedEventArgs e)
{
User original = (sender as Button).DataContext as User;
// - This will create a new ViewModel below with the User I'm sending
MyTabControl.AddTab(original);
}
}
And my ViewModel of Users is:
public class UserViewModel : INotifyPropertyChanged
{
public User Original { get; private set; }
public string Name { get { return Original.Name; } set { Original.Name = value; OnPropertyChanged("Name"); } }
public UserViewModel(User original)
{
Original = original ?? new User();
}
// - INPC implementation
}
Since my ViewModel is the one reporting the property changes, I didn't expect my original User to report it as well to the GridView.
The Mode=OneWay causes the information flow to go from the bound data entity to the target UI property only, any change to the UI property will not be bound back.
The reason why the UI content is changing is because the underlying property is read/write (i.e. has a getter and a setter) and is notifying any value change (due to the implementation of the INPC interface).
Presuming that it is a list of User objects you've bound to the GridView, you have two simple options to fix this. Which one is best depends on how much scope for change you have:
change the current Name property on the User object, remove the setter for it. Replace the setter with a method to set the property (i.e. SetUserName(string name)) which then sets the private member variable. Or pass the name as an argument to the constructor of the User entity.
create a new property with only a getter which returns the name and set your binding to that; i.e. public string UserName { get { return Name; }}. As there is only a getter there will be no notification of this property, so if the name does change it won't be propagated via this new property.
I have started learning MVVM with some basic applications and I just encountered below issue with binding.
I have 2 textboxes in my View say- Student_name and Student_year. I have a Student class implemented in my viewmodel with its properties. But, the actual Student class is in my Model layer.
<TextBox x:Name="StuName"
Text="{Binding Path=MyStudent.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<TextBox x:Name="StuYear"
Text="{Binding Path=MyStudent.Year, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
ViewModel:
private Student _myStudent = new Student();
public Student MyStudent
{
get { return _myStudent ; }
set
{
if (value != _myStudent )
{
_myStudent = value;
OnPropertyChanged("MyStudent");
}
}
}
Model (Student Class):
public string Name
{
get { return _name; }
set
{
if (_name!= value)
{
_name= value;
OnPropertyChanged("Name");
}
}
}
I can see everything working fine on binding the values from VM to View. But, the other way is behaving little tricky here..
Wheneven I change Name/Year in the textbox, the control has to land on Viewmodel's Set property? Rather, it straight away goes to Model's Set property.
For Instance, When I modify txtbox 'StuName', SET method of Student class is invoked. But not SET method of Viewmodel(MyStudent object).
I am not sure why this behaves in such a way. Is it because I have directly bounded Student.Name to the textbox? What are the alternatives to handle this SET operation in Viewmodel class..
Thanks in advance.
PS: I have implemented INotifyPropertyChanged interface properly and rest other bindings(of primitive data type) are working fine with other controls.
As Philip Stuyck correctly pointed out in his answer, the ViewModel only has a setter for the Student instance, which never changes. So the setter on the ViewModel is never invoked. The binding goes to the name property of that instance.
A different approach would be to wrap the name property in you ViewModel explicitly. This allows for a clearer separation of concerns between Model and ViewModel. I.e. right now your Model implements INotifyPropertyChanged which IMO belongs into the ViewModel, because in general it is only used for triggering View updates. Your ViewModel would look like this:
class StudentViewModel
{
private Student _myStudent = new Student();
public string Name
{
get { return _myStudent.Name ; }
set
{
if (value != _myStudent.Name )
{
_myStudent.Name = value;
OnPropertyChanged("Name");
}
}
}
}
Your Model on the other hand becomes simpler, because it doesn't have to implement INotifyPropertyChanged anymore.
That is normal behavior because your binding is to MyStudent.Name.
So the Mystudent setter is never called because the instance never changes.
The setter of the name is called because in fact that is where your binding is going to.
I have a strange behavior going on. I'm using MVVM pattern, i have a binding to an Observable collection named AlarmCollection to a grid control in a View named AlarmView. When i create multiple instances of a AlarmModelView class, and add items to AlarmCollection, all the instances display the changes.
Any changes to the ObservableColelction AlarmCollection, affects all the bound ItemSources of the grid controls.
I have tried to lock the dispatcher thread, from a similar post here, to no avail.
Is there anyway to keep the changes to this Observable collection, within each instance of the ViewModel? So that each modification does not affect any other collection in the UI thread.
Any help is appreciated.
[edit below]
It is strange scenario, I need to zoom/drill into what is rendered by creating the new instances of the Child MV, which in turn adds tabs to the Parent MV. The Child Views are all bound to the same Collection names, and all are being updated by a WCF Async call. I need X number multiple instances, based on the how deep the zoom level goes, so i need 1 ModelView object.
How would i achieve this using CollectionChanged event or creating the ModelView's own CollectionView?
private MainViewModel _parentViewModel;
public MainViewModel ParentViewModel
{
get { return _parentViewModel; }
set
{
if (ParentViewModel == value) { return; }
SetPropertyValue(ref _parentCircuitViewModel, value, "ParentViewModel");
}
}
private ObservableCollection<DetailEntity> _alarmCollection;
public ObservableCollection<DetailEntity> AlarmCollection
{
get
{
if (_alarmCollection == null)
_alarmCollection = new ObservableCollection<DetailEntity>();
return _alarmCollection;
}
private set { _alarmCollection = value; }
}
ServiceNode _selectedNode;
public ServiceNode SelectedNode
{
get { return _selectedNode; }
set
{
SetPropertyValue(ref _selectedNode, value, "SelectedNode");
// render selected child node service path
RenderSubPath(_selectedNode);
// reset storage value
_selectedCircuitNode = null;
}
}
// Constructor
public RenderViewModel(string servicePath CircuitMainViewModel parentViewModel)
{
ServicePath = servicePath,
ParentCircuitViewModel = parentViewModel;
// event to handler for completed async calls
Client.GetAlarmsByNodeListCompleted += new EventHandler<GetAlarmsByNodeListCompletedEventArgs>(Client_GetAlarmsByNodeListCompleted);
}
void RenderSubPath(ServiceNode childNode)
{
if (childNode == null)
return;
// create a new child instance and add to parent VM tab
_parentViewModel.AddServiceRenderTab(new ViewModel.Workspaces.RenderViewModel(childNode.elementPath _parentViewModel);
}
// wcf async webservice call to add alarm to ObservableCollection
// ** This is updating all Collections in all Views.
void Client_GetAlarmsByNodeListCompleted(object sender, AlarmServiceReference.GetAlarmsByNodeListCompletedEventArgs e)
{
try
{
if (e.Result == null)
return;
// add to parent Netcool alarm collection
foreach (DetailEntity alarm in nodeAlarms)
{
_alarmCollection.Add(alarm);
}
}
}
From your description, it sounds as though all your views are bound to the same underlying collection. For any collection you bind to, WPF will actually bind to a collection view (ICollectionView) wrapped around that collection. If you don't explicitly create your own collection view, it will use a default one. Any binding to the same collection will result in the same collection view being used.
It's hard to say without seeing your code, but it's likely you want to either use a separate instance of the underlying view model (and, hence, the collection) or you want to explicitly create separate collection views and bind to them instead.