I have a question on calling a method of view model from view (xaml.cs). Any suggestions would be appreciated.
The method works in view model since if I call the method in view model constructor, I will be able to see the data in DataGrid. But when I call the method in view (xaml.cs), the DataGrid is empty.
In the view part, I have the code snippet below: (xaml.cs part)
private ShellViewModel viewModel = new ShellViewModel();
public ShellView()
{
this.DataContext = viewModel;
InitializeComponent();
}
......
private void Button_Click(object sender, RoutedEventArgs e)
{
viewModel.Init();
}// ##cannot call this method successfully
In View you can have event handlers but what ever those handlers do, they should only affect the view. So, I'd suggest you initialize your ViewModel e.g. in its own constructor.
But if you insist viewModel.Init() to be called after button is clicked, then bind your Button to a ICommand exposed from your ViewModel as property.
For example:
<Window.DataContext>
<viewModel:ShellViewModel />
</Window.DataContext>
<Button Command="{Binding InitCommand}">Init</Button>
public ShellViewModel()
{
InitCommand = new RelayCommand(Init);
}
public ICommand InitCommand { get; private set; }
private void Init()
{
throw NotImplementedException();
}
Related
I'm using mvvm for my application and I'm use binding to bind the items and change automaticly when there are some changes in my view but in some cases I want to add some logic and then send it to the view model. for example:
private string _children;
public string Children
{
get
{
return _children;
}
set
{
_children= value;
OnPropertyChanged(nameof(Children));
}
}
And my view:
<TextBox Name="Child"></TextBox>
<Button Content="Add" Click="Add_Children"/>
<ListView Name="ChildrenList">
</ListView>
private string children
private void Add_Children(object sender, RoutedEventArgs e)
{
string name = Child.Text;
ChildrenList.Items.Add(name);
children += name;
}
I want to send the children field to my view model, But I can't because I don't any access to the view model in xaml.cs file.
This example is not my exact problem. In this case I could just use a list instead of a string, But
I just wanted to ask a general question.
It's not the best way to achieve this in MVVM, but you can access your ViewModel like this:
In your xaml.cs file:
Declare variable
YourViewModel vm;
Add this declaration to constructor
this.Loaded += new RoutedEventHandler(OnLoaded);
Create this method
public void OnLoaded(object sender, RoutedEventArgs e)
{
vm = this.DataContext as TableDrawingsViewModel;
}
And then tou can access tour ViewModel instance by using this.vm
This's my first question here, so hi everybody.
I'm working on the mobile app in Xamarin.Forms with Prism. I've created ListView where shown data from the database.
When the user clicks in the selected row app should navigate to a new view and pass the selected item from ListView.
<ListView x:Name="DefectsBase"
RowHeight="65"
ItemsSource="{Binding Defects}"
ItemSelected="ShowDetailsEvent"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding Refresh}"
IsRefreshing="{Binding IsRefreshing}">
Code backend:
async void ShowDetailsEvent(object sender, EventArgs e)
{
var myListView = (ListView)sender;
var myItem = myListView.SelectedItem;
var p = new NavigationParameters();
p.Add("selectedDefect", myItem);
await _navigationService.NavigateAsync("DefectDetailsView", p);
}
Unfortunately, the app doesn't respond to pressing the selected row in ListView.
As I can see you are already using Prism and you have a List page with Items and you want to navigate to some details page based on the selected/taped/chosen item which the user taps in the ListView.
The idea is to move as much code and logic as we can to the view model and keep our code-behind. This is pretty easy to solve using Prism and EventToCommand behaviour.
In the example and answer below, I will show you how to solve this with few lines of code, with a nice code approach.
First of all, I recommend you use EventToCommand behaviour, you can include it with prism xmlns, like this: xmlns:prism="http://prismlibrary.com", later on, you can use it with ListView.
Remove ItemSelected event from your ListView and move the markup about it to the <ListView.Behaviors> part. Here is my code sample for the ListView which binds to some ObserverableCollection of the Car models:
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<prism:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding SelectedCarCommand}"
EventArgsParameterPath="Item" />
</ListView.Behaviors>
The main part here is <ListView.Behaviors>, where you can see that I am binding to the SelectedCarCommand which will be invoked when the user taps on some of the items from the list. I am using the ItemTapped event for this and passing the current "taped" item from the list as a parameter.
In order to follow this XAML part in my view model of this page, I have declared the DelegateCommand and method which will be called when the command is invoked. The view model part looks like this:
This is my CarListPageViewModel, take a look at DelegateCommand and SelectedCar method.
public class CarListPageViewModel
{
private readonly INavigationService _navigationService;
public ObservableCollection<Car> Cars { get; set; }
public DelegateCommand<Car> SelectedCarCommand { get; private set; }
public CarListPageViewModel(INavigationService navigationService, IDataProvider dataProvider)
{
_navigationService = navigationService;
// Insert test data into collection of Cars
Cars = new ObservableCollection<Car>(dataProvider.GetData());
SelectedCarCommand = new DelegateCommand<Car>(SelectedCar);
}
private async void SelectedCar(Car selectedCar)
{
NavigationParameters navigationParameters = new NavigationParameters
{
{ "selectedCar", selectedCar }
};
await _navigationService.NavigateAsync(nameof(CarDetailsPage), navigationParameters);
}
}
As you can see we have DelegateCommand defined with the type of parameter which will be passed, in my case, this is the Car class, the same class as our items in the ListView.
In the constructor, I did my initialization and defined the method which will be called, that method has a parameter of the type Car.
When the user taps on one of the items in the ListView, SelectedCar (method) will be called and we can pass the data to the next view using NavigationParameters and NavigationService.
In order to retrieve the passed data we can use INavigationAware in the details view model and with the OnNavigatedTo method, access the data which is being passed.
This is my CarDetailsPageViewModel, take a look at OnNavigatedTo method.
public class CarDetailsPageViewModel : BindableBase, INavigationAware
{
private string carTitle;
public string CarTitle
{
get { return carTitle; }
set { SetProperty(ref carTitle, value); }
}
private string photoUrl;
public string PhotoUrl
{
get { return photoUrl; }
set { SetProperty(ref photoUrl, value); }
}
public CarDetailsPageViewModel() { }
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("selectedCar"))
{
Car car = parameters.GetValue<Car>("selectedCar");
if (car != null)
{
CarTitle = $"{car.Make} {car.Model}";
PhotoUrl = car.PhotoUrl;
}
}
}
public void OnNavigatedFrom(INavigationParameters parameters) { }
}
From this answer and example, you can see:
How to, use EventToCommand behaviour with ListView
Define and use DelegateCommand with passing parameter
How to navigate to another view and pass navigation parameter and
... finally how to access the passed data.
Code and this sample you can find on my GitHub profile here.
Hope this answer was helpful for you!
Wishing you lots of luck with coding! 👋
I'm using Prism with Unity IOC-Container in a WPF-Project. For all my other Views I'm using only one ViewModel per View. Because this View should be a Mask for both Input and Output of Data, I'd like to use two ViewModels.
For the current navigation to the View i use this Code:
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());`
The Code Behind of my View:
public partial class Events : UserControl
{
public Events(EventsViewModel viewModel)
{
InitializeComponent();
}
}
One of the ViewModels:
public class EventsViewModel : BindableBase
{
public EventsViewModel()
{
// Some Code
}
// Some other Code
}
I heard about ViewModel Discovery, where you give the Constructor of the View an Interface instead of an actual ViewModel. But i could only find exacly this much information.
// Example of such a Method
public Events(IViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel
}
public Interface IViewModel
{
}
My question is now: How do I navigate to the View and tell it wich ViewModel it should get as DataContext? I'm relatively new to programming and the MVVM-Pattern and english is not my native language so maybe I missed some Information. I would be glad if someone had an answer for this. Thanks in advance.
Edit: Workaround
I came up with a workaround wich works for me. I used the method SetDefaultViewTypeToViewModelTypeResolver() from the ViewModelLocationProvider and customized it.
// Bootstrapper.cs
protected override void InitializeShell()
{
var window = (MainWindow)this.Shell;
Application.Current.MainWindow = window;
// Calling the method
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ResolveViewModel);
var regionManager = Container.Resolve<IRegionManager>();
window._regionManager = regionManager;
globalRegionManager = regionManager;
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(StartScreen));
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(Stock));
window.Show();
}
// Property for handing over the desired ViewModel
public static Type DynamicViewModel { private get; set; }
private Type ResolveViewModel (Type viewType)
{
string _viewModel = null;
var name = viewType.FullName.Replace(".Views.", ".ViewModels.");
if (DynamicViewModel != null)
_viewModel = DynamicViewModel.ToString();
else
_viewModel = $"{name}ViewModel";
var fullName = IntrospectionExtensions.GetTypeInfo(viewType).Assembly.FullName;
var typeString = string.Format(CultureInfo.InvariantCulture, $"{_viewModel}, {fullName}");
DynamicViewModel = null;
return Type.GetType(typeString);
}
Then when I want to navigate, I hand over the ViewModel beforehand.
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
Bootstrapper.DynamicViewModel = typeof(EventsViewModel);
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());
}
A little tricky but it seems to work without any Exceptions.
If there is a cleaner way I'm alway happy to here it. :)
Here are some techniques for getting the view model for a view (view first).
View discovery in views code behind constructor
public EventsView(EventsViewModel view_model)
{
InitializeComponent();
DataContext = view_model;
}
Explicitly newing up the view model in code behind constructor
public EventsView()
{
InitializeComponent();
DataContext = new EventsViewModel();
}
View model locator in the XAML for the view
<UserControl x:Class="EventsModule.Views.EventsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBlock Text="{Binding EventName}"></TextBlock>
</Grid>
</UserControl>
As long as you register your view with the region manager using any one of the techniques below, any one of the above will work. Then navigate like you are doing in your question.
RegionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(EventsView));
UnityContainer.RegisterType(typeof(object), typeof(EventsView), typeof(EventsView).FullName);
UnityContainer.RegisterTypeForNavigation<EventsView>(typeof(EventsView).FullName);
The first will activate the view in the region and is usually seen in the module Initialize method. Those last 2 are for registering a view for later navigation. The last one requires the Prism.Unity namespace.
I don't believe that a view can have more the one view model since the view object only has one DataContext property on it. You may have to extend one view model to include everything you need. Someone may come along and prove me wrong on this. I have seen where a view model is shared with more than one view, but never a view having more than one view model.
I see example on this link, how to switch between two views. Easiest sollution and perfect for my application - I will also have only two views.
So we have one parent View (MainWindow) and two children Views. Parent View have dedicadet two buttons to swtich between this two Views ("First View" and "Second View") which are located in "DockPanel".
My question is how to use any button in "First View" to switch to the second View and in "Second View" button to come back to the "First View". What I want is get rid of DockPanel and use buttons from View.
Please for advices, how to do that. If any question please ask. THANKS!
You can use an event from each child viewmodel to signal the parent to change views. So in the code below ButtonOnViewModel1Command is pressed on View1 (which is bound to ViewModel1) which raises the SwitchViewModel2Request event. The MainViewModel subscribes to this event and switches the CurrentViewModel collection to ViewModel2. You can do this same thing on ViewModel2 to switch back to ViewModel1.
public class MainViewModel
{
private ViewModel1 _viewModel1 = new ViewModel1();
private ViewModel2 _viewModel2 = new ViewModel2();
public MainViewModel()
{
//event from ViewModel1
_viewModel1.SwitchViewModel2Request += NavigateToView2;
}
//switch View to ViewModel2
private void NavigateToView2(object sender, EventArgs e)
{
CurrentViewModel = _viewModel2;
}
}
public class ViewModel1
{
public ViewModel1()
{
ButtonOnViewModel1Command = new RelayCommand(Button1Method);
}
//some button on child view 1
public RelayCommand ButtonOnViewModel1Command { get; set; }
private void Button1Method(object obj)
{
OnSwitchViewModel2Request();
}
//event that MainViewModel will subscribe to
public event EventHandler SwitchViewModel2Request = delegate { };
private void OnSwitchViewModel2Request()
{
SwitchViewModel2Request(this, EventArgs.Empty);
}
}
Since you're using MVVM light you should look to using the messenger system ( Good tutorial here ). A simple way would be on first view to send a NotificationMessage as follows:
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("GotoSecondView"));
Then in your main window you would register to receive it as follows:
Messenger.Default.Register<NotificationMessage>(this, NotificationReceived);
Followed by a function to handle them:
private void NotificationReceived(NotificationMessage message)
{
string notice = message.Notification;
switch (notice)
{
case "GotoSecondView":
ExecuteSecondViewCommand
break;
}
}
Repeat the same idea for the other view and add it to your switch. Then you can trigger from anywhere and mainview will handle the change without directly having to link your viewmodels.
I have seen many examples over the net but I am not able to figure out what i am doing wrong here.
I need to close a view from a view model. Below is the code i have tried:
ViewModel:
public class ViewModel
{
public event EventHandler RequestClose;
public ViewModel()
{
}
}
//Calling the event from view model to close the view from a method in View Model
This event is called on a button click
private void Download()
{
//Download Logic
if(RequestClose != null)
RequestClose(this,EventArgs.Empty);
}
View:
ViewModel vm = new ViewModel();
vm.RequestClose += delegate(object sender, EventArgs args) {this.Close();}
this.DataContext = vm;
You fire the RequestClose event in the ViewModel constructor which is too early to be catched by the event registration.
The best MVVM solution is to use an attached behavior, as outlined in the top rated answer to this question How should the ViewModel close the form?
I faced a similar problem earlier, and did the following: In the viewmodel, create a command that you can bind to (I personally use MvvmLight and its RelayCommand)
public class ViewModel
{
public RelayCommand<object> CloseWindowCommand {get; private set;}
public ViewModel()
{
CloseWindowCommand = new RelayCommand<object>(CloseTheWindow);
}
private void CloseWindow(object obj)
{
var window = obj as Window;
if(window != null)
window.Close();
}
}
In my view, I have button that triggers this command, e.g.
Button Content="Close" Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=NameOvViewModel}"
I realize now, that this may require the use of MvvmLight, but I hope it offers some guidance on a possible solution to your question.