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;
}
}
Related
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.
I have a WPF application which implements navigation using MVVM, filling a different DataTemplate for each View within the same Window e.g. :
<Window.Resources>
<DataTemplate DataType="{x:Type foo:FooViewModel}">
<foo:FooView/>
</DataTemplate>
<DataTemplate DataType="{x:Type bar:BarViewModel}">
<bar:BarView/>
</DataTemplate>
<Window.Resources>
(Switching between Views/UserControls using MVVM is Rachel Lim's article which has inspired the aforementioned approach)
What happens now is that FooView gets FooViewModel automatically injected as a dependency by DI (which in my case is Microsoft.Extensions.DependencyInjection on .Net Core3 Preview) e.g. :
public partial class FooView : UserControl
{
public FooView(FooViewModel fooViewModel)
{
this.InitializeComponent();
this.DataContext = fooViewModel;
}
}
At this point obviously the DataTemplate complains because the FooView does not define a parameter-less ctor (as per reference Type '{0}' is not usable as an object element)
Is there any way to bypass this issue and let FooView to use FooViewModel as DataContext?
DataTemplate
<DataTemplate DataType="{x:Type foo:FooViewModel}">
<foo:FooView/>
</DataTemplate>
will assign an instance of FooViewModel to FooView.DataContext.
this.DataContext = fooViewModel; line in FooView control is useless, because DataContext will be overwritten in such scenario. I would say, it is perfectly fine not to pass view model via contrustor. It can be accessed from DataContext:
public partial class FooView : UserControl
{
public FooView()
{
this.InitializeComponent();
}
private FooViewModel Vm { get { return this.DataContext as FooViewModel; } }
}
In the navigation pattern you are using, View is a receiver of ViewModel. Current ViewModel is set by AppViewModel:
public class AppViewModel
{
// simplified properties
public ViewModelBase CurrentViewModel {get; set;}
public ICommand ViewFooCommand {get;}
public ICommand ViewBarCommand {get;}
}
You are trying to make View an originator/producer of ViewModel, which conflicts with pattern.
So i have this ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Porsons
{
get { return _persons; }
set
{
_persons = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And then create this ViewModel class and populate its Person list:
ViewModel viewModel;
ObservableCollection<Person> persons
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
viewModel.Porsons= persons;
}
And then my ListView:
<ListView ItemSource={Binding Persons}/>
So instead of binding this Persons list into my ViewModel class and then do this ItemSource can i do it in pure XAML or this is the right way ?
Instead of creating a ViewModel property on your view it is recommended to use it's DataContext (this link also shows how to set it using XAML). Also don't populate the view model in the view since most of the time the data resides in the model and the view should not know anything about any models (when following MVVM).
Please read the link above and visit the links you meet. Also read this article about MVVM. This gives you some basic knowledge to make it easier to understand how to use the WPF framework.
There are many variations of view model creation in XAML.
For example alternatively you can create it in the App.Xaml to make it globally accessible via the StaticResource markup extension and assign it to the individual controls's DataContext via a Style or use an ObjectDataProvider.
This example uses XAML Property Element declaration to create a ViewModel instance directly in the target view. This instance is locally accessible only.
ViewModel.cs:
namespace Example
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Persons = new ObservableCollection<Person>();
}
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons
{
get => _persons;
set
{
_persons = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View.xaml:
<Window x:Class="Example.MainWindow"
...
xmlns:local="clr-namespace:Example">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<ListView ItemSource={Binding Persons}/>
</Grid>
</Window>
Yes, you can. But no, you most certainly do not want to.
To answer your question, let's say your Person class looks like this:
public class Person
{
public string Name { get; set; }
}
You can easily declare a list in XAML and bind it to a ListView (say) like this:
<ListView DisplayMemberPath="Name">
<ListView.ItemsSource>
<x:Array Type="{x:Type vm:Person}">
<vm:Person Name="Tom" />
<vm:Person Name="Dick" />
<vm:Person Name="Harry" />
</x:Array>
</ListView.ItemsSource>
</ListView>
The result of which is this:
Just because you can do this, though, doesn't mean you should. The whole point of MVVM is to separate your view layer from your view model layer. You should be able to run your entire application from a test build without creating a single view object at all. In asking this question what you are apparently trying to do is declare a data structure in your view layer, which is totally the wrong place to put it. Your view layer should be as "dumb" as possible, with only the weakest possible bindings to your view model layer where the actual logic is going on.
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;
}
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.