WPF view who leads to another using MVVM - c#

I am trying to set up a navigation between views using a MVVM pattern. My application contains a MainWindow and two views with a button each. When I click on the button in the View1 I want to set up the View2 on the MainWindow.
I have found several tutorials witch explain how to navigate from a view to another with a button on the main window (simulate a tabControl), it works but it is not what I want.
I'm looking for something like :
View1_View.xaml.cs :
public partial class View1_View : UserControl
{
private View1_ViewModel _viewModel = new View1_ViewModel();
public View1_View()
{
InitializeComponent();
}
private void Btn_SwitchToView2_Click(object sender, RoutedEventArgs e)
{
MainWindow.SwitchToView2();
}
}
MainWindow.xaml.cs :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new View1_View();
}
public void SwitchToView2()
{
this.DataContext = new View2_View();
}
}
My problem is if I do so, from the class View1_View I cannot access to the method SwitchToView2() if it is not static, and if it is static I lose the context of the MainWindow.
How should I proceed ?
Thanks.

I would recommend using a ContentControl to switch the part of your main view.
This could look like this (short form just to give you an idea; without INotifyPropertyChanged).
Create an empty interface of type ISwitchableViewModel.
Add a property to your main ViewModel
public property ISwitchableViewModel MyViewModel {get; set;}
Create two classes that implements the interface ISwitchableViewModel. Each for each view you want to show (View1 and View2 in your example) and call them ViewModel1 and ViewModel2.
When you press the button in your xaml set the MyViewModel to View1 or View2; whatever your logic is.
In your xaml add this at the place where you want to show the switchable content.
<ContentControl Content="{Binding MyViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModel:ViewModel1}">
<view:View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ViewModel2}">
<view:View2 />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
When you set the MyViewModel in your MainViewModelthe UI will show automatically the correct view for that viewmodel.

You can achieve this by creating the views and assigning them to a content control.
Lets assume you have this content control in your main view.
<Window x:Class="MVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MVVM"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="ChangeView" Click="SwitchToSecondView" Content="Set View"></Button>
<ContentControl x:Name="MainContent"></ContentControl>
</StackPanel>
</Window>
You can then set the content in the code behind file of your main view.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void SwitchToSecondView(object sender, outedEventArgs e)
{
var view = new SecondView();
var model = new SecondViewModel(this);
view.DataContext = model;
MainContent.Content = view;
}
public void SwitchToThirdView(object sender, outedEventArgs e)
{
var view = new ThirdView();
var model = new ThirdViewModel(this);
view.DataContext = model;
MainContent.Content = view;
}
}
Another solution would be to use an MVVM Framework light Caliburn.Micro, Prism etc, which essential do the same thing as the code snippet above, but hide the boilerplate code.
EDIT: I realized i didn't explicitly get to the second part of your question.
Usally one would need some kind of router which is able to control the navigation. For the sake of simplicity we use the main view as router. To access the main view, you need to inject it in each component.
This allows each of your submodels to access the main view.
This could be improved by using some kind of DI-Container or by a Mediator. A mediator would allow each component to send requests, which then are dispatched to the MainView, eliminating the direct dependency.

Related

How to pass a parameter to the viewmodel of the navigated page without mvvm light

I want to pass a parameter "Trip" to the viewmodel of my navigated page. So far, i have this:
the code of my page from which i will navigate from. Its a Flip of trips
public sealed partial class TripOverview : Page
{
public TripOverview()
{
this.InitializeComponent();
DataContext = new TripOverviewViewmodel();
}
public void Trip_Detail(object sender, RoutedEventArgs e)
{
Trip selectedTrip = (Trip)TripFlip.SelectedItem;
this.Frame.Navigate(typeof(TripDetail), selectedTrip);
}
}
this is my Trip detail page. I want to add the parameter that i get from the onNavigatedTo method to the viewmodel which i then link to the Datacontext.
public sealed partial class TripDetail : Page
{
public Trip selectedTrip { get; set; }
public TripDetailViewmodel vm = new TripDetailViewmodel();
public TripDetail()
{
this.InitializeComponent();
this.DataContext = vm;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Trip trip = (Trip)e.Parameter;
base.OnNavigatedTo(e);
}
}
So basicly i want the property "currentTrip in my viewmodel:
public class TripDetailViewmodel
{
public Trip CurrentTrip { get; set; }
public TripDetailViewmodel()
{
}
}
to be set without the use of mvvm light
Here is the XAML of my TripDetail page
<Page
x:Class="TravelChecker.TripDetail"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TravelChecker"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewmodel="using:TravelChecker.Viewmod"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<viewmodel:TripDetailViewmodel x:Name="tripsDetailVm" />
</Page.DataContext>
<Grid>
<TextBlock x:Name="title" Text="{x:Bind tripsDetailVm.CurrentTrip.Destination.LocationName}"
FontFamily="Segoe UI" FontSize="26.667"
Foreground="Black" Padding="15,20" RenderTransformOrigin="0.318,0.392" />
<TextBox Text="{Binding ElementName=title, Path=Text, Mode=TwoWay}" x:Name="titletxt">
</TextBox>
</Grid>
When you use {x:Bind} to implement the data binding, the default source is the page instead of the DataContext. {x:Bind} will look in the code-behind of your page for properties, fields, and methods. To expose your view model to {x:Bind}, you will typically want to add new fields or properties to the code behind for your page.
But if you use {Binding} to implement data binding, {Binding} will need the DataContext.
Therefore, you could select the tripsDetailVm which is created in XAML’s Page.DataContext tag or the vm which is created in code behind as the binding source. However, whichever binding source you select, you need to set the CurrentTrip property as trip.
When you use tripsDetailVm as DataContext, you need to add the code tripsDetailVm.CurrentTrip = trip; to OnNavigatedTo method. Then {x:Bind} could find tripsDetailVm.CurrentTrip.Destination.LocationName in page and {Binding} could find title in DataContext(that is tripsDetailVm). Though the vm is initialized later than the page built, but it does not matter.

Can't add children to Grid when binding to VM with Caliburn Micro

I want to add (any) elements to a grid from my view model in my Caliburn.Micro application (it's really a Revit add-in so not quite an application, but should work the same).
I'm having a hard time wrapping my head around certain aspects of the MVVM model and binding data to elements in the view...
My ViewModel
public class MyViewModel : Screen
{
private Grid _myGrid;
public Grid MyGrid
{
get { return _myGrid; }
set
{
_myGrid = value;
NotifyOfPropertyChange(() => MyGrid);
}
}
public MyViewModel()
{
MyGrid = new Grid();
var label = new Label { Content = "Hello!" };
MyGrid.Children.Add(label); // I know this isn't MVVM but how can I do basically this?
}
}
My View
<Window x:Class="MyProject.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cal="http://www.caliburnproject.org"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:MyProject.Views"
mc:Ignorable="d">
<Grid x:Name="MyGrid">
<!-- Here goes stuff from view model -->
</Grid>
</Window>
I know this is all wrong... I need help getting elements into my grid element from the view model.
The general approach you are taking here is wrong as you are creating View elements in your ViewModel. With MVVM, you still want to design your UI (View) in XAML. In your case you would still create the Grid and the Label in your XAML in most cases. Think of what you are doing with this approach.
You have a data model that stores the data properties and notifies of changes. Think of this as the structural side of the data.
public class MyModel : INotifyPropertyChanged
{
private string labelValue;
public string LabelValue
{
get { return labelValue; }
set
{
labelValue = value;
NotifyOfPropertyChange(() => LabelValue);
}
}
//Property changed handler logic below.
//I assume you have something based on your code.
}
Now we create a ViewModel that does stuff with the data. This is what the view is bound too.
public class MyViewModel
{
//Add a property for the model you created
public MyModel NewModel {get;set;}
//Load the NewModel values when the view model is created.
public MyViewModel()
{
NewModel = new MyModel(){LabelValue="Hello World"};
}
}
Ok so we have a data Model, and we have a ViewModel to actually use the Model and fill it with values. Now lets create the UI (View) and bind to the ViewModel.
In your Window you will create a label and simply bind your desired ViewModel property. In this case we will your your models LabelValue property.
<Grid>
<Label Content="{Binding NewModel.LabelValue}"/>
</Grid>
Now we simply set the datacontext in the UI codebehind
public MyViewModel myViewModel = new MyViewModel(); //Create the ViewModel
public MyWindow()
{
InitializeComponent();
//Set the DataContext to the ViewModel
DataContext = myViewModel;
}
When you fire up the program the label content should be whatever you set it as in your ViewModel MyModel.LabelValue property. This is only a simple explanation of very large concept, but it should give you an idea of how it works. There are also many techniques and styles of MVVM people use, but this concept of is MVVM in its most basic form. Hopefully this gives you an idea and starts you on the right path.
The goal is to run your logic in you ViewModel so that the Views and Models can be built with minimal code. If you properly implement INotifyPropertyChanged all values on your View automatically update with no additional code or UI thread locks. Though the concept seems bulky at first, it saves a lot of time and effort in the long run.

Apply Ninject to work with "child viewmodels" in my C# WPF application

I'm having some problem with doing Ninject - dependency injection - on my WPF application.
I've managed to do the dependency injection correctly for my MainView.xaml and MainViewModel.cs, I did this by settings the datacontext in my App.xaml.cs. With this I set my MainWindow datatext to the viewmodel retrieved by the kernel from ninject.
Here is my App.xaml.cs, which I set as Startup instead of StartupUri in app.xaml
private void Application_Startup(object sender, StartupEventArgs e)
{
var kernel = new StandardKernel(new CompositionModule());
MainWindow mainWindow = new MainWindow();
mainWindow.DataContext = kernel.Get<MainViewModel>();
mainWindow.Show();
}
So far it works perfect for MainViewModel, but my problem is that my MainViewModel have multiple child viewmodels.
Here's an example of my child viewmodel is setup my MainView.xaml looks like - From under you can see that my MainView.xaml split the main window into two parts. One part of the main window show the view1 and viewmodel1, and the other part show view2 and viewmodel2. Both are in the same window, I did this just to make it easier to show what these part do. My problem is that now I'm binding Content = Viewmodel1, but when I try to put parameter value in the constructor, my application doesn't execute the constructor code. It just skips it. My child viewmodels is of partial class, which concist of a viewmodel1.cs and viewmodel.commands.cs. The viewmodel.commands part only contain commands for GUI. The ContentTemplate is datatemplate in my xaml resource - resourcedictionary files loaded in app.xaml.
<telerik:RadSplitContainer Orientation="Vertical" telerik:DockingPanel.InitialSize="750,200">
<telerik:RadPaneGroup IsContentPreserved="False" telerik:ProportionalStackPanel.RelativeSize="200,300" >
<telerik:RadPane Header="{Binding ViewModel1.Title, Mode=TwoWay}"
CanUserClose="False" CanUserPin="False"
CanDockInDocumentHost="True">
<ContentControl ContentTemplate="{StaticResource View1Template}"
Content="{Binding Viewmodel1}" />
</telerik:RadPane>
</telerik:RadPaneGroup>
<telerik:RadPaneGroup IsContentPreserved="False" telerik:ProportionalStackPanel.RelativeSize="100,120">
<telerik:RadPane Header="ViewModel2.Tile"
CanUserClose="False" CanUserPin="False"
CanDockInDocumentHost="True">
<ContentControl ContentTemplate="{StaticResource View2Template}"
Content="{Binding Viewmodel2}" />
</telerik:RadPane>
</telerik:RadPaneGroup>
</telerik:RadSplitContainer>
These two viewmodels under gets binded to Content in MainView.xaml.
public ViewModel1 ViewModel1 { get; set; } = new ViewModel1();
public ViewModel2 ViewModel2 { get; set; } = new ViewModel2();
How can I make dependency injection also work for my child viewmodels? It seems like the reason it doesn't work is because the constructor never gets executed whenever I put an interface as constructor parameter.
Thanks for any help I can get. It works perfectly for MainViewModel, but not for my resource files and other viewmodels.
I posted this code on your previous question. When you set
public ViewModel1 ViewModel1 { get; set; } = new ViewModel1();
Then that will ignore ninject. You're just newing up a class.
As I read your question the sample code I posted seems to do what you are having a problem with.
I tell it when I ask for an IVMOne then it should give me a new instance of VMOne.
You do not seem to have the equivalent code.
private void Application_Startup(object sender, StartupEventArgs e)
{
var nj = new StandardKernel();
nj.Bind<IVMOne>().To<VMOne>().InTransientScope(); // <<<<<<<<<<<<<<<
MainWindow mw = new MainWindow();
mw.DataContext = nj.Get<MainWindowViewModel>();
mw.Show();
}
See where I bind IVMOne?
This is equivalent to your Viewmodel1 and 2.
You don't have that in your code.
When I get MainWindowViewModel that has IVMOne as a ctor parameter.
class MainWindowViewModel
{
public IVMOne VMOne { get; set; }
public MainWindowViewModel(IVMOne vmOne)
{
VMOne = vmOne;
}
}
Because I told it what to give me, it passes an instance of VMOne.
I see the text appear in my textblock when I spin this up.
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding VMOne.Hello}"/>
</Grid>
If I put a breakpoint in on the mainwindowviewmodel ctor I have an instance of vmone there.
Could you try binding one of your viewmodels prior to getting mainwindowviewmodel?
When you Get mainwindowviewmodel it will use the ctor with the most parameters.
Put a break point in that.
Do you get an instance of your viewmodel there?
If not then put a different sln together.
Not what you have now. Because that will be complicated and probably have external dependencies.
Put in that the absolute minimum markup and code that demonstrates your issue without any external dependencies.
This is so someone can easily and quickly reproduce your issue.

Trying to: Avoid static instance as datacontext

Im having problem sharing a windows viewmodel to the windows hosted frame.
Therefore I made a static viewmodel for the mainwindow, so any class can edit it´s properties:
class GUICollection
{
public static MainWindowViewModel MainWindowViewModel = new MainWindowViewModel();
}
This is then set into the MainWindows datacontext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = GUICollection.MainWindowViewModel;
}
}
This is the windows xaml:
<Window x:Class="MVVMFrameQuestiontoStackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Frame NavigationUIVisibility="Hidden" Source="{Binding MainWindow.FrameURI}"/>
</Grid>
It´s view model:
class MainWindowViewModel
{
private string startUpURI;
private object startUpDataContext;
private MainWindowModel mainWindow;
public MainWindowViewModel()
{
startUpURI = "pack://application:,,,/MVVMFrameQuestiontoStackOverflow;component/Page1.xaml";
mainWindow = new MainWindowModel(startUpURI);
}
/// <summary>
/// Gets the MainWindow instance
/// </summary>
public MainWindowModel MainWindow
{
get
{
return mainWindow;
}
}
}
So from here I can choose the frame Source, which means I can choose which view to show. However Im wondering if I could avoid the static initiliazing and still being able to access the mainwindows FrameURI property (Here is my current logic):
public Page1()
{
InitializeComponent();
DataContext = new MainMenuViewModel();
//Statement below causes an exception, but the whole issue is about accesing this without using a static instance.
GUICollection.MainWindowViewModel.MainWindow.FrameURI = "Change MainWindows FrameURI property!";
}
Is the same behaviour able to produce without using a static class? If so an example would be warmly appreciated.
Thanks on advance!
I think in fact you problem is that you haven't understood MVVM and use a mix between MVC and MVVM : You create the view (mainWindows) in the ViewModel wich is forbidden.
Your App must load the main view.
In the constructor of the main view you should create the view-model as a private field.
When you will create new windows (and you should only do that from view, never from viewmodel) you will give the viewmodel datacontext object as a parameter for the new view, wich will give it to it's own viewmodel via parameters.
If you have Model object(s) wich is(are) shared throught all the application, create it in the App launch method, and pass it throught views via their constructors as a parameter.

Creating a single controller for multiple WPF Pages

I'm very new to WPF and a beginner in C#.NET. I'm currently making an application where there will be many pages and the trigger to change the page is hand gesture using Kinect SDK (the trigger method is not relevant for this question). Normally when a WPF file is created, there will be a similarly named .cs file attached to it, which acts somewhat like a controller. However, I need multiple WPF files/pages to be controlled only by a single controller .cs file. How do I achieve that? Thanks for viewing my question and your answer will be very appreciated :)
You probably want to write a class that contains your 'controller' code and reference it from your WPF UserControls / Pages.
In a new file:
public class MyController
{
public void DoThings(object parameter)
{
// stuff you want to do
}
}
and then inside your UserControl code-behind class:
public partial class MyWpfControl : UserControl
{
private MyController controller;
public MyWpfControl
{
this.controller = new MyController();
}
}
and finally, tie your events back to the controller's method:
private void OnGesture(object sender, EventArgs e)
{
// call the method on the controller, and pass whatever parameters you need...
this.controller.DoThings(e);
}
The code behind is really part of the view and isn't really analogous to a controller and generally there shouldn't be much code in them. Typically you would want most of your logic between your "View Model" which serves as an abstraction of the view and "Model" which serves as an abstraction of the business logic that your UI is interacting with.
In this light what I think you really want is a View Model(VM) that controls multiple views. This is a fairly typical scenario and the preferred method (IMO anyway) is to have a hierarchical view model that has a top level the application model and a number of sub VMs that represent different components within your UI, though you can bind everything to your top level VM if you really want to.
To do this we would first define our view model like so
public interface IGestureSink
{
void DoGesture();
}
public class MyControlVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private ApplicationVM parent;
public MyControlVM(ApplicationVM parent)
{
this.Name = "my user control";
this.parent = parent;
parent.PropertyChanged += (s, o) => PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
public String Name { get; set; }
public bool Visible { get { return parent.ControlVisible; } }
public void DoGesture()
{
parent.DoGesture();
}
}
public class ApplicationVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ApplicationVM()
{
this.ControlVM = new MyControlVM(this);
this.ControlVisible = false;
}
public MyControlVM ControlVM { get; private set; }
public bool ControlVisible {get; set;}
public void DoGesture()
{
this.ControlVisible = !this.ControlVisible;
PropertyChanged(this, new PropertyChangedEventArgs("ControlVisible"));
}
}
and then all we need to do is to build a user control
<UserControl x:Class="WpfApplication2.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="LightBlue">
<Label Content="{Binding Name}"/>
</Grid>
</UserControl>
and page
<Window xmlns:my="clr-namespace:WpfApplication2" x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding ControlVM}" Visibility="{Binding Visible,Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="222,262,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
That use it. The only thing that we need in our code behind is a constructor that sets up the page VM and wiring from our button to the view model.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ApplicationVM();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
((IGestureSink)(this.DataContext)).DoGesture();
}
}
If you wanted to use a monolithic view model instead you would use this Instead of binding the DataContext to ControlVM:
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding DataContext}" Visibility="{Binding ControlVisible,Converter={StaticResource BooleanToVisibilityConverter}}"/>

Categories