MVVM Light pass parameters to child view model - wpf

I am new to MVVM and WPF.
I am using MVVM Light to make an application which contains a DataGrid within a window, which has a view model (MainViewModel) and another window for adding and editing records in the DataGrid, that also has its own view model (EditViewModel).
What I am worried about is the approach I am using to open the Add/Edit window from the MainViewModel. In the MainViewModel I have a property SelectedItem, which is bound to the SelectedItem property of the DataGrid and an IsEdit boolean property that indicates if the Add/Edit window should be launched in Add or Edit mode.
When the Add/Edit window gets opened in edit mode, in the constructor of its view model I have the following line:
MainViewModel mainViewModel = ServiceLocator.Current.GetInstance<MainViewModel>();
That obviously retrieves the current instance of the MainViewModel, which works perfectly fine, but I am not really sure it is the best way to do this.
Also if I have more than one instances of the Main window, that use the same MainViewModel instance and I open an instance of the Add/Edit window from both of them, the Add/Edit windows are going to get data from the same instance of the MainViewModel which may be a problem.
If I try to create a new instance of MainViewModel for each MainWindow I open, then I don't know how to pass the instance of the currently used MainViewModel to the EditViewModel.
I hope I made clear what I need to do. Tell me if I have missed something and I will add it:)
Thanks in advance

Hi if I havent misunderstood your problem incorrect you can do it this way:
Since i need IsRequired dependency Property in both MainView and EditView i created a class that extends Window class
public class ExtendedWindow:Window
{
public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.Register("IsRequired", typeof(bool), typeof(ExtendedWindow));
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
}
MainView and ViewModel
public partial class MainWindow:ExtendedWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EditView editView = new EditView();
**((EditViewModel)editView.DataContext).IsRequired = this.IsRequired;**
editView.Show();
}
}
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
IsRequired = true;
}
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
EditView and ViewModel
public partial class EditView:ExtendedWindow
{
public EditView()
{
InitializeComponent();
DataContext = new EditViewModel();
}
}
public class EditViewModel : INotifyPropertyChanged
{
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
This is just kind of dummy but can give you idea how you can do it. I have tried it in dummy and its working fine.

Related

SelectedItem not shown in ComboBox

I am using MVVM in a WPF application with C# and got a problem binding a ComboBox correctly.
This is my ComboBox line in the XAML:
<ComboBox ItemsSource="{Binding Repository.Models}" SelectedValue="{Binding Repository.SelectedModel}" DisplayMemberPath="Name"></ComboBox>
This is the interesting part of my Repository:
class Repository : INotifyPropertyChanged
{
//init MVVM pattern
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<Model> _models;
public ObservableCollection<Model> Models
{
get
{
return _models;
}
set
{
_models = value;
NotifyPropertyChanged("Models");
}
}
private Model _selectedModel;
public Model SelectedModel
{
get
{
return _selectedModel;
}
set
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
This is the interesting part of my Model class:
abstract class Model : INotifyPropertyChanged
{
//init MVVM pattern
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
So when I select/change different items of the combobox a DataGrid that is binded to Repository.SelectedModel.Parameters does update just as i want it to.
Because of that I know, that the binding does work!
When I restart the application and debug into my Repository, I see that there is a SelectedModel (deserialised on startup) but the ComboBox stays blank. The DataGrid though does show the right data.
So the binding itself does work, but the binding to the ComboBoxLabel somehow fails.
I tried a lot of things like switching between SelectedItem and SelectedValue, between Binding and Binding Path, between IsSynchronizedWithCurrentItem true and false, but nothing worked so far.
Do you see my mistake?
Thanks in advance!
EDIT
Here is the interesting part of my MainWindowViewModel:
class MainWindowViewModel : INotifyPropertyChanged
{
//init MVVM pattern
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private Repository _repository;
public Repository Repository
{
get
{
return _repository;
}
set
{
_repository = value;
NotifyPropertyChanged("Repository");
}
}
And here is my App.xaml.cs where I init my DataContext:
//init point of app
public partial class App : Application
{
private MainWindowViewModel mainWindowViewModel;
//gets fired as the app starts
protected override void OnStartup(StartupEventArgs e)
{
//create the ViewModel
mainWindowViewModel = new MainWindowViewModel();
//create the mainWindow
var mainWindow = new MainWindow();
mainWindow.DataContext = mainWindowViewModel;
//show the mainWindow
mainWindow.Show();
}
When I restart the application and debug into my Repository, I see that there is a SelectedModel (deserialised on startup) but the ComboBox stays blank. The DataGrid though does show the right data.
Looks like the deserialization is the problem.
You have a selected item which was deserialized. That means that a new Model instance was created which has a Name of whatever, and Properties that are whatever. And you have a list of Model instances in an ObservableCollection<Model> which are displayed in a ComboBox.
And you assure us that at least sometimes, you have ComboBox.SelectedItem bound to SelectedModel, though for some reason the code in your question binds ComboBox.SelectedValue instead. That's not going to work. Here's how ComboBox.SelectedValue would be used:
<ComboBox
ItemsSource="{Binding Repository.Models}"
SelectedValuePath="Name"
SelectedValue="{Binding SelectedModelName}"
/>
...and you would have to have a String SelectedModelName { get; set; } property on your viewmodel. The Name property of the selected Model would be assigned to that by the ComboBox when the selection changed. But you don't have SelectedModelName, and you don't want it, so forget about SelectedValue.
Back to SelectedItem. The ComboBox gets the value of SelectedModel from the binding, and tries to find that exact object in its list of Items. Since that exact object is not in that list, it selects nothing. There is probably an item in Repository.Models that has the same name and has identical Properties, but it is not the same actual instance of the Model class. ComboBox doesn't look for an identical twin of the value in SelectedItem; it looks for the same object.
SelectedModel.Properties works in the DataGrid because the DataGrid doesn't know or care what's in Models. You give it a collection, it's good.
So: If you want to deserialize a SelectedModel and have it mean anything, what you need to do is go ahead and deserialize, but then find the equivalent item in Repository.Models (same Name, same Properties), and assign that actual object instance to SelectedModel.
You may be tempted to overload Model.Equals(). Don't. I've done that to solve the same problem. The resulting behavior is not expected in C# and will bite you, hard, and when you least expect it, because you are invisibly altering behavior that happens in framework code. I've spent days tracking down bugs I created that way, and I'll never do it to myself again.
Try SelectedItem instead of SelectedValue in ComboBox.

ICommand with MVVM in WPF

I am new to MVVM and WPF, trying to use ICommand in WPF and MVVM. Below is the code.
Can someone please help to know why the below code is not working, means nothing happens on button click.
Appreciate your help.
View
<Grid>
<Button Height="40" Width="200" Name="button1" Command="{Binding Path=Click}">Click Me</Button>
</Grid>
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
MainWindowViewModel vm = new MainWindowViewModel();
mainWindow.DataContext = vm;
}
}
MainWindowViewModel.cs
namespace TestWPFApplication.ViewModel
{
public class MainWindowViewModel
{
private ICommand _click;
public ICommand Click
{
get
{
if (_click == null)
{
_click = new CommandTest();
}
return _click;
}
set
{
_click = value;
}
}
private class CommandTest : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Hi! Test");
}
}
}
}
It looks like your OnStartup method is instantiating a MainWindow and never showing it. You probably have the StartupUri set in XAML which is creating a different MainWindow with the data context not set.
You could remove the StartupUri and call mainWindow.Show(). Alternatively, you could get rid of the OnStartup method and set up the data context in the main window's constructor.
You don't need to initialize this Window in OnStartup.
In MainWindow constructor after Initialize create instance of ViewModel and it should work.

Bind observerbaleCollection to wpf combo box

I'm having Class call Apps. It has Observable collection called AvailableTypes. I want to bind this AvailableTypes observable collection to the wpf ComboBox. When form is loaded these AppId should loaded into comboBox.. Would you give me a solution to this one?
class Apps: INotifyPropertyChanged{
ServiceReference1.AssetManagerServiceClient client;
ObservableCollection<string> availableType;
public ObservableCollection<string> AvailableTypes
{
get
{
if (availableType == null)
{
availableType = new ObservableCollection<string>();
}
client = new ServiceReference1.AssetManagerServiceClient();
List<string> AssestList = client.GetAppIds().ToList<string>();
foreach (string appid in AssestList)
{
availableType.Add(appid);
}
return availableType;
}
set
{
availableType = value;
NotifyPropertyChanged("AvailableTypes");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In your xaml code, this is a simple example of how you can bind to your combobox.
<ComboBox ItemsSource={Binding Path=AvailableTypes} />
You will also need to load your viewmodel into the DataContext of your window too.
var window = new MainWindow
{
DataContext = new Apps()
};
window.Show();
If you want open the window on App startup, you can do this instead
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var window = new MainWindow
{
DataContext = new Apps()
};
window.Show();
}
}
Don't overload property getters/setters. Make it simplier.
I recommend to use auto properties and NotifyPropertyWeaver or use PostSharp post build compile-time injected instuctions to support INotifyPropertyChanged interface.
This makes your view model more readable and easy to manage/understand.
In your form 'Loaded' event or 'NavigatedTo' in SL you can start loading your data from anywhere you want and set corresponding properties after loading completed (in callbacks/events handlers, don't forget about using UI dispatcher while updating binded properties)

Silverlight 3 user control data Binding in xaml

I’m trying to achieve something that is conceptually quite simple but can’t seem to get it working.
I have a class called c1 it has 2 dependency properties in it an integer I and a string S. It implements INotifiyPropertyChanged.
public class c1: INotifyPropertyChanged
{
private int i;
public int I { get { return i; } set { i = value; if(PropertyChanged != null) PropertyChanged(this,new PropertyChangedEventArgs("I")); } }
private string s;
public string S { get { return s; } set { s = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("S")); } }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
This class is referenced by a Silverlight user control SUC that also implements INotifiyPropertyChanged as a dependency property C, with a PropertyChangedCallback etc. As seen below.
public partial class SUC : UserControl, INotifyPropertyChanged
{
public c1 C
{
get { return (c1)GetValue(CProperty); }
set { SetValue(CProperty, value); }
}
public static readonly DependencyProperty CProperty =
DependencyProperty.Register("C", typeof(c1), typeof(SUC), new PropertyMetadata(new c1(), new PropertyChangedCallback(c1Changed)));
private static void c1Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
SUC s = obj as SUC;
if (s != null)
s.CChanged((c1)e.NewValue);
}
public void CChanged(c1 c)
{
C = c;
if(PropertyChanged!=null)
PropertyChanged(this,new PropertyChangedEventArgs("C"));
}
public SUC()
{
InitializeComponent();
this.DataContext = this;
}
private void bclick(object sender, RoutedEventArgs e)
{
C.S = C.S + " Clicked";
MessageBox.Show(C.I.ToString() + " - " + C.S);
}
public event PropertyChangedEventHandler PropertyChanged;
}
In my main page which also implements INotifiyPropertyChanged I have an instance of c1 and of SUC.
public partial class MainPage : UserControl, INotifyPropertyChanged
{
public c1 MC
{
get { return (c1)GetValue(MCProperty); }
set { SetValue(MCProperty, value); }
}
public static readonly DependencyProperty MCProperty =
DependencyProperty.Register("MC", typeof(c1), typeof(MainPage), new PropertyMetadata(new c1()));
private static void MCChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainPage mp = d as MainPage;
if (mp != null)
mp.MCChanged();
}
public void MCChanged()
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MC"));
}
public MainPage()
{
InitializeComponent();
MC.S = "ssss";
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
}
I want to set the C property of the SUC user control via XAML. Like so
local:SUC x:Name="suc" C="{Binding MC, Mode=TwoWay}"
This works well in the c# code behind but not in XAML. The reason I need it in XAML is because I want to bind a collection of c1’s to SUC’s in a DataTemplate.
Any working examples with downloadable code would be most appreciated.
It's a simple little bug in the constructor of the SUC class:
public SUC()
{
InitializeComponent();
this.DataContext = this; //this line shouldn't be here, delete and it will work
}
That means the DataContext of SUC control is itself instead of the MainPage class which is what it needs to be in order to bind to MainPage.MC (the SUC class doesn't have an MC property).
Also, and I realise most of these were you probably just trying to get it to work, but MC does not need to be a DP, you don't need the 'C=c;' line in the SUC, and I wouldn't use the MainPage control class as a datacontext class as well, create another class to bind the DataContext to.
The problem seems to be that you set the DataContext of the UserControl after you load the XAML. Either set it before the XAML is loaded (i.e. before InitializeComponent), or even better, set it in the XAML as such:
<local:MainPage ... DataContext="{Binding RelativeSource={RelativeSource Self}}">
....
</local:MainPage>
The RelativeSource binding specifies that the DataContext of your MainPage should be itself, which seems to be what you want. This then eliminates the assignment of DataContext in code-behind, which is always a good thing in WPF/Silverlight.
Hope that helps.
The DataContext of the UserControl's Controls can be different from the UserControl itself or the UserControl's Parent "Form" (or Parent Page, UserControl). You have to set the Binding in the Code Behind. See this post for more information: Silverlight UserControl Custom Property Binding
Also, You may want to create a Silverlight Control instead of a Silverlight UserControl

Broken binding with Prism, Silverlight and ViewFirst approach

The problem we are having is that we cannot get binding to work in our
prism silverlight application when using the view-model first
approach. The view first approach work fine. We have gone over the
official documentation and various web sites, but have still not
resolved the issue. Below is the code for both the view-model first,
and the view first approach. Are we missing something? Read about it on my blog http://silvercasts.blogspot.com
View-Model first approach:
Bootstrapper:
internal void RegisterLoginRegionAndView()
{
IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion,
() => Container.Resolve<IViewModel>().View);
}
ViewModel:
public ViewModel(IView view)
{
View = view;
View.SetModel(this);
User = new User();
User.Username = "TestUser";
}
ViewModel Interface:
public interface IViewModel
{
IView View { get; set; }
}
View Interface:
public interface IView
{
void SetModel(IViewModel model);
}
View Xaml:
<TextBox x:Name="Username" TextWrapping="Wrap" Text="{Binding User.Username}" />
View Code Behind:
public void SetModel(IViewModel viewModel)
{
this.DataContext = viewModel;
}
View first approach
Bootstrapper:
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion, typeof(IView));
ViewModel:
public ViewModel()
{
User = new User();
User.Username = "TestUser";
}
View Code Behind:
public View(IViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
Your implementation of SetModel on your view needs to be as follows:
public void MyUserControl : UserControl, IView
{
//...
public void SetModel(IViewModel vm)
{
this.DataContext = vm;
}
}
If that's not there, it needs to be (you haven't posted your implementation of SetModel, but this would be the source of the issue in this case).
If this is not the issue, it's likely because your ViewModel does not implement INotifyPropertyChanged. I usually use a base ViewModel that does this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then all of my ViewModels derive from that:
public class MyViewModel : ViewModelBase
{
private User _user;
public User User
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("User");
}
}
}
Note: in your case the "User" object should probably also be a ViewModel and also raise OnPropertyChanged for the Username property.
Hope this helps.
The obvious difference to me is that you set the DataContext in the "view first" approach, but not in the "view model first" approach. I'm not sure if Prism sets the DataContext for you (I'd guess that you're assuming that it does) but try setting the DataContext manually to see if this is the problem. In your ViewModel constructor you call View.SetModel(this) - does that call set the DataContext?
The problem was that I was using the SetModel method before the data object was instanced. Moving it like this:
public ViewModel(IView view)
{
View = view;
User = new User();
User.Username = "TestUser";
View.SetModel(this);
}
solved the problem.

Resources