MVVMLight - how to get a reference to the ViewModel in the View? - silverlight

I'm building a Windows Phone 7 app, and I need a reference to my ViewModel in my view so I can set a property from my event handler. The only problem is that I'm not able to get that reference.
What I did;
I have a ViewModelLocator (deleted the irrelevant bits):
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<TunerViewModel>();
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This non-static member is needed for data binding purposes.")]
public TunerViewModel Tuner
{
get { return ServiceLocator.Current.GetInstance<TunerViewModel>(); }
}
And a view (XAML):
DataContext="{Binding Tuner, Source={StaticResource Locator}}">
And the code-behind of the view:
public partial class Tuner : PhoneApplicationPage
{
private readonly TunerViewModel _viewModel;
public Tuner()
{
_viewModel = DataContext as TunerViewModel;
InitializeComponent();
}
I found this link MVVM View reference to ViewModel where the DataContext is casted to a ViewModel, so I tried the same because it looks like a good solution. However, my _viewModel field is null after the cast. Why is this and how do I fix this? I couldn't find it on Google/Stackoverflow
Thanks in advance :)

Because you set the DataContext from XAML with a binding expression in the View's constructor the DataContext is not set yet. That's why you get null.
Try the cast the DataContext in or after the Loaded event:
public Tuner()
{
InitializeComponent();
Loaded += OnTunerLoaded;
}
private void OnTunerLoaded(object sender, RoutedEventArgs routedEventArgs)
{
_viewModel = DataContext as TunerViewModel;
}

Related

DataContext of usercontrol in WPF

I'm new to WPF and I'm trying to start a little project with a maximum of good practice. I'm using MVVM and dependency injection.
I have a concern which seems to be easy to understand but i can't find an answer (at this step, DataContext is not very clear for me).
The UserControlView of type UserControl contains just a button for testing.
This is the app class :
public App()
{
IServiceCollection services = new ServiceCollection();
services.AddSingleton<MainWindow>();
services.AddSingleton<UserControlViewModel>();
services.AddSingleton<UserControlView>();
_serviceProvider = services.BuildServiceProvider();
}
The user control is included in the Main windows like that :
<Grid>
<views:UserControlView/>
</Grid>
Now, in the OnStartup overrided method :
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = _serviceProvider.GetRequiredService<MainWindow>();
MainWindow.DataContext = _serviceProvider.GetRequiredService<PaymentMeansViewModel>();
MainWindow.Show();
}
Like that it works, my button is correctly binded to the command.
But what is strange for me is that I have to set the 'UserControlViewModel' as the DataContext of the Main Window.
Isn'it possible to bind it to the 'UserControlView', something like :
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = _serviceProvider.GetRequiredService<MainWindow>();
UserControlView testUC = _serviceProvider.GetRequiredService<UserControlView>();
testUC.DataContext = _serviceProvider.GetRequiredService<UserControlViewModel>();
MainWindow.Show();
}
Thanks for help.
Finally I did it.
I think (I hope I'm right) that I understood.
First of all, let's begin with the basic.
A view must have a viewmodel to bind the properties. A usercontrol is a kind of view "encapsulated" in a view. Therefore a usercontrol must have its own viewmodel and the view must have its own viewmodel.
The datacontext of the MainWindow is set in the app onstartup method :
MainWindow = new MainWindow()
{
DataContext = new MainWindowViewModel()
};
MainWindow must implement INotifyPropertyChanged. All view models must implement this interface. We can create a base class which will be derived in the view models :
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string? propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The DataContext of the usercontrol must be explicit in the xaml of the MainWindow:
<Grid>
<views:UserControlView DataContext="{Binding CurrentViewModel}"/>
</Grid>
"CurrentViewModel" is a DataContext, then it's a ViewModel, and as it is binded, it must be a property of the MainViewModel.
public class MainWindowViewModel : ViewModelBase
{
public ViewModelBase CurrentViewModel { get; }
public MainWindowViewModel()
{
CurrentViewModel=new UserControlViewModel();
}
}
Hope it can help.

Binding 'SelectedItems' dependency property to ViewModel property does not work

I have a multiselect Combobox usercontrol and a dependency property 'SelectedItems'.
I m trying to use the usercontrol and bind the 'SelectedItems' to another property called 'SelectedResultItems' in my ViewModel. But I dont get any values to SelectedResultItems. Please help
Here is what i tried.
My main xaml:
<DataTemplate x:Key="TypeATemplate">
<control:MultiSelectComboBox Width="315" ItemsSource="{Binding
ResultvalueList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedItems="{Binding
SelectedResultItems,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
My Combobox usercontrol code behind:
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems",
typeof(ObservableCollection<string>), typeof(MultiSelectComboBox), new
FrameworkPropertyMetadata(null,new
PropertyChangedCallback(MultiSelectComboBox.OnSelectedItemsChanged)));
public ObservableCollection<string> SelectedItems
{
get { return
(ObservableCollection<string>)GetValue(SelectedItemsProperty); }
set
{
SetValue(SelectedItemsProperty, value);
}
}
I am setting the 'SelectedItems' on click of the checkbox.
My mainviewmodel:
public ObservableCollection<string> SelectedResultItems
{
get => _selectedResultItems;
set
{
_selectedResultItems = value;
NotifyPropertyChanged(nameof(SelectedResultItems));
}
}
If this is the same as for ListView(never used MultiSelectCombobox), you cannot bind to SelectedItems because it is a read-only property.
What I did to solve that is add the event SelectionChanged to ListView(or MultiSelectCombobox for you).
Then event would be :
private void YourComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
contexte.ResultItems = YourComboBox.SelectedItems.Cast<YourItem>().ToList();
}
Maybe there is a different way to do it, but until now that's the easiest way I found.

Reference the View from ViewModel while using DataTemplate for the ViewModel

i'm using a DataTemplate to provide the association of View to ViewModel, for example:
<DataTemplate DataType="{x:Type viewModels:SomeViewModel}">
<views:SomeView />
</DataTemplate>
now i need to reference the View somehow in my ViewModel, so i could reference some control directly by its name.
Is there anyway to do that?
Note
i already tried to add a SomeView parameter to the SomeViewModel Ctor but than the SomeView Ctor is being invoked twice(one for the instance i sent to the SomeViewModel Ctor and the because of the framework i guess...)
thanks for your help
Maybe this is what you're looking for:
public partial class SomeView : UserControl
{
public SomeView()
{
InitializeComponent();
this.Loaded += View_Loaded;
}
void SomeView_Loaded(object sender, RoutedEventArgs e)
{
var someViewModel = (SomeViewModel)this.DataContext;
someViewModel.View = this;
}
}

How to pass initialization parameters to UserControl in WPF (using MVVM)

I have a UserControl called ActionsTreeView I built using MVVM practices where I have an IPluginsProvider interface that populates the data in my UserControl. I want to be able to provide an object implementating this IContentProvider interface as a parameter to initialize my UserControl's ViewModel.
Here is my approach so far, which isn't working. I am wondering if I'm going down the right path? I declare a DependencyProperty in my user control which is visible to my mainWindow where I want to instantiate this UserControl. This code just attempts to pass the PluginsProvider object to my UserControl which needs it to build its ViewModel.
My PluginProvider DependencyProperty setter in my UserControl never gets hit because my My PropertyChanged handler is always null in MainWindow.xaml.cs I think I have the code right, but not sure I'm going down the right road and what I'm missing to make this connection?
ActionsTreeView.xaml.cs
public partial class ActionsTreeView: UserControl
{
public static readonly DependencyProperty PluginProviderProperty = DependencyProperty.Register("PluginProvider", typeof(Models.IPluginsProvider), typeof(ActionsTreeView), new FrameworkPropertyMetadata(null, OnPluginProviderChanged));
private ViewModels.ActionsTreeViewModel vm;
public ActionsTreeView()
{
//Wire-up our ViewModel with the data provider and bind it to DataContext for our user control
//This is a Mock-up until I figure out a way to get the real provider here
Models.IPluginProvider pluginSource = new Models.MockPluginProvider();
vm = new ViewModels.ActionsTreeViewModel(pluginSource );
this.DataContext = vm;
InitializeComponent();
}
private static void OnPluginProviderChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ActionsTreeView)source).PluginProvider = (Models.IPluginsProvider)e.NewValue;
}
public Models.IPluginsProvider PluginProvider
{
get
{
return (Models.IPluginsProvider)GetValue(PluginProviderProperty);
}
set
{
SetValue(PluginProviderProperty, value);
vm.SetPluginSource(PluginProvider);
}
}...
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
}
private Models.IPluginsProvider _actionProvider;
public Models.IPluginsProvider ActionProvider
{
get { return _actionProvider; }
set
{
_actionProvider = value;
OnPropertyChanged("ActionProvider");
}
}
protected void OnPropertyChanged(string property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) //HANDLER IS ALWAYS NULL
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
Using my UserControl in MainWindow.xaml
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider}" />
</Grid>
I don't think you can pass a parameter in the ctor in xaml.
If you create control in code behind you can pass the parameter in the ctor(Param param)
Not sure if this fits in the MVVM model but I use it a lot in regular code behind
Use a frame in the XAML for a place to put the UserControl
Seems like you are missing the binding source
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" />
</Grid>
since your property ActionProvider is declared in MainWindow so during binding you are required to refer the same source unless you've set it as data context of the window
alternative to above you can also do the below if there is no other data context used in the MainWindow then you can use the original binding you have PluginProvider="{Binding ActionProvider}"
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
DataContext = this;
}
I've set the DataContext to this which will effectively resolve the value of ActionProvider in binding from the instance this
Extra
you may also choose to remove INotifyPropertyChanged from MainWindow as it is already DependencyObject and capable of property notification and declare a DependencyProperty for ActionProvider
eg
public Models.IPluginsProvider ActionProvider
{
get { return (Models.IPluginsProvider)GetValue(ActionProviderProperty); }
set { SetValue(ActionProviderProperty, value); }
}
// Using a DependencyProperty as the backing store for ActionProvider. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActionProviderProperty =
DependencyProperty.Register("ActionProvider", typeof(Models.IPluginsProvider), typeof(MainWindow), new PropertyMetadata(null));
so you don't need to worry for the notification change manually, you might be required to use this if the above solution does not work for you otherwise it is good to have.

Simple WPF data binding

I want to separate my user interface from my code, so I (obviously) landed at bindings. As a test, I've written the following XAML:
<Window x:Class="BindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="Auto" Width="200">
<StackPanel>
<TextBox Text="{Binding Item}"/>
<Button Content="Add" Click="AddNew"/>
<ListBox Height="100" ItemsSource="{Binding Items}"/>
</StackPanel>
</Window>
The C# looks like this:
namespace BindingTest
{
public partial class MainWindow : Window
{
public string Item { get; set; }
public ObservableCollection<string> Items { get; set; }
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<string>();
}
private void AddNew(object sender, RoutedEventArgs e)
{
Items.Add(Item);
}
}
}
What I want to happen is that the text entered into the textbox is added to the listbox's itemssource. However, this doesn't happen...
Two things you need two do -
Set - DataContext = this; in your constructor.
You'd be better off if you would change your properties to dependency properties instead. You could do that easily with the "propdp" snippet in visual studio.
Data binding is performed against the current data context. However, you have not set the data context for your window. Often you will set the data context to a view model but in your case you simply want to use the window class for that.
You should add the following line to the constructor:
DataContext = this;
Change your code to this:
public partial class MainWindow : Window
{
public string Item { get; set; }
public ObservableCollection<string> Items { get; set; }
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<string>();
DataContext = this;
}
private void AddNew(object sender, RoutedEventArgs e)
{
Items.Add(Item);
}
}
}
You do need to set your DataContext - works for me.
Two things:
You should set the correct data context for your window. Otherwise the binding will not find your properties.
You should initialize your Items collection before the InitializeComponent() call as inside it the ListBox tries to evaluate the expression and get NULL as the binding souce. And since you are not implementing INotifyPropertyChanged and the property is not a DependencyProperty the ListBox will never reevaluate the binding thus it will never get the instance of your Items collection.
So, the code should be as follows:
public MainWindow()
{
Items = new ObservableCollection<string>();
DataContext = this;
InitializeComponent();
}
Try this
hope this will work. But this is not hte right approach. You need to set the DataContext to the Object whose properties u guna use for binding. you must follow MVVM Architecture.

Resources