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.
Related
As the title says, I'm looking for a way to assign keyboard shortcuts from a user at runtime, using WPF MVVM pattern. I know that I can define keyboard shortcuts at start like this:
<Window.InputBindings>
<KeyBinding Command="{Binding MyCommand}" Key="A"/>
</Window.InputBindings>
I've also seen that there's a way to parse input bindings from a user. What I'm struggling with, however, is binding an inputbinding from my ViewModel to the MainWindow's InputBinding. I don't know how to achieve this. Here's the code in my MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
And here's some sample code from my ViewModel:
public partial class MainWindowViewModel : Window, INotifyPropertyChanged
{
public MainWindowViewModel()
{
KeyBinding kb = new KeyBinding { Key = Key.T, Command = MyCommand };
this.InputBindings.Add(kb);
}
}
I know that the this.InputBindings.Add(kb); part should probably be replaced with something else; adding the keybinding to the MainWindow's InputBinding instead. However, I don't know how to do this with the MVVM pattern. Therefore: how would I go about doing this?
You might define the input bindings in the view model, but you still need to add them to the view somehow.
You could for example use an attached behaviour that does this for you:
public class InputBindingsBehavior
{
public static readonly DependencyProperty InputBindingsProperty = DependencyProperty.RegisterAttached(
"InputBindings", typeof(IEnumerable<InputBinding>), typeof(InputBindingsBehavior), new PropertyMetadata(null, new PropertyChangedCallback(Callback)));
public static void SetInputBindings(UIElement element, IEnumerable<InputBinding> value)
{
element.SetValue(InputBindingsProperty, value);
}
public static IEnumerable<InputBinding> GetInputBindings(UIElement element)
{
return (IEnumerable<InputBinding>)element.GetValue(InputBindingsProperty);
}
private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = (UIElement)d;
uiElement.InputBindings.Clear();
IEnumerable<InputBinding> inputBindings = e.NewValue as IEnumerable<InputBinding>;
if (inputBindings != null)
{
foreach (InputBinding inputBinding in inputBindings)
uiElement.InputBindings.Add(inputBinding);
}
}
}
View Model:
public partial class MainWindowViewModel
{
public MainWindowViewModel()
{
KeyBinding kb = new KeyBinding { Key = Key.T, Command = MyCommand };
InputBindings.Add(kb);
}
public List<InputBinding> InputBindings { get; } = new List<InputBinding>();
public ICommand MyCommand => ...
}
View:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="Window18" Height="300" Width="300"
local:InputBindingsBehavior.InputBindings="{Binding InputBindings}">
<Grid>
</Grid>
</Window>
If these are to be persisted so they work next time the user runs the app then you could consider creating a resource dictionary as a string or uncompiled flat file.
This would allow you to work with xaml as strings. You could write that to disk and xamlreader.load into a resource dictionary then merge it into application resources.
https://social.technet.microsoft.com/wiki/contents/articles/28797.wpf-dynamic-xaml.aspx
This approach offers several benefits:
The styling is easily persisted.
You can try it out and see what's going on.
You can write a file to disk using a model method called from your viewmodel.
I understand that the standard way in WPF to expose a custom property in XAML is to define it as DependencyProperty in the View’s code-behind.
However, this only works for DependencyObjects, such as a UserControl. Yet, in clean Prism fashion, my code-behind (i.e., the class deriving from UserControl) is empty, and I deal with all the logic in my view model, which derives from BindableBase, which is not a child class of DependencyObject.
Consider the following XAML fragment:
<MyNamespace:MyCustomView MyProperty={Binding} />
The core of MyCustomViewModel is
private string myProperty;
public string MyProperty {
get { return myProperty; }
set { SetProperty(ref myProperty, value); }
I’m still relatively new to Prism. What do I do to expose a MyProperty, which is defined in my MyCustomViewModel so that I can set it in XAML with a tag similar to that above?
Update
Following #mm8’s answer and our discussion in the corresponding comments, I developed a minimal (non-)working example of what I have in mind. A summary first:
Data model is a list of objects.
Shell must display each of these objects by means of a custom user control for this object type.
A) The shell
A.1) XAML
The XAML is straightforward.
<Window x:Class="MyProject.Views.MainWindow"
Name="MainWindowName"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:MyNamespace="clr-namespace:MyProject.Views"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding StringCollection, ElementName=MainWindowName}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MyNamespace:MyUserControl MyTargetProperty="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
A.2) Code-behind
The code-behind contains a data model definition; in reality, I’d define this in the Models namespace, of course.
using System.Collections;
using System.Windows;
namespace MyProject.Views {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
StringCollection = new ArrayList();
StringCollection.Add("String 1");
StringCollection.Add("String 2");
StringCollection.Add("String 3");
}
private ArrayList stringCollection;
public ArrayList StringCollection {
get { return stringCollection; }
set { stringCollection = value; }
}
}
}
A.3) View model
The view model is the standard one provided with the Prism code templates.
using Prism.Mvvm;
namespace MyProject.ViewModels {
public class MainWindowViewModel : BindableBase {
private string _title = "Prism Unity Application";
public string Title {
get { return _title; }
set { SetProperty(ref _title, value); }
}
public MainWindowViewModel() {
}
}
}
B) The custom user control
This is where the fun starts. In the end, I’d like to have access to the MyTargetProperty in the MyUserControlViewModel, since I want to invoke sophisticated program logic on it that depends on other work with the data model, and is thus not to be placed in the code-behind.
B.1) XAML
Very naive; only contains a label.
<UserControl x:Class="MyProject.Views.MyUserControl"
Name="UserControlName"
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">
<Label Content="{Binding MyTargetProperty, ElementName=UserControlName}" Background="AliceBlue"/>
</UserControl>
B.2) Code-behind
This is where I declare the target property as DependencyProperty, as suggested in #mm8’s answer.
using System.Windows;
using System.Windows.Controls;
namespace MyProject.Views {
/// <summary>
/// Interaction logic for MyUserControl
/// </summary>
public partial class MyUserControl : UserControl {
public MyUserControl() {
InitializeComponent();
}
public static readonly DependencyProperty MyTargetPropertyProperty = DependencyProperty.Register("MyTargetProperty", typeof(string), typeof(MyUserControl));
public string MyTargetProperty {
get { return (string)GetValue(MyTargetPropertyProperty); }
set { SetValue(MyTargetPropertyProperty, value); }
}
}
}
B.3) View model
The view model defines the source property.
using Prism.Mvvm;
namespace MyProject.ViewModels {
public class MyUserControlViewModel : BindableBase {
public MyUserControlViewModel() {
}
private string mySourceProperty;
public string MySourceProperty {
get { return mySourceProperty; }
set { SetProperty(ref mySourceProperty, value); }
}
}
}
I can’t for the life of me figure out how to access the values I set in the MainWindow’s ItemTemplate within the MyUserControl’s view model.
Only target (view) properties must be dependency properties. So for you to be able to bind anything to such a property, it must be a dependency property like MyProperty in this case:
<MyNamespace:MyCustomView MyProperty="{Binding SourceProperty}" />
A source property in a view model may however be a plain CLR property:
public string SourceProperty { get; set; }
So your view models don't have to (and shouldn't!) inherit from DependencyObject but views should.
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.
I am trying to run a simple application based on catel 4 but I didn't even show the window. I don't have errors or warnings
Here is the code of the test application and the application itself.
<catel:DataWindow x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
Title="MainWindow" Height="350" Width="525">
<Grid>
<CheckBox Content="Check me to continue" IsChecked="{Binding UserAgreedToContinue, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" />
</Grid>
public partial class MainWindow : DataWindow
{
public MainWindow()
: base(DataWindowMode.Custom)
{
InitializeComponent();
}
}
public class MainWindowViewModel : ViewModelBase
{
public override string Title { get { return "Just acknowledge"; } }
public bool UserAgreedToContinue
{
get { return GetValue<bool>(UserAgreedToContinueProperty); }
set { SetValue(UserAgreedToContinueProperty, value); }
}
public static readonly PropertyData UserAgreedToContinueProperty = RegisterProperty("UserAgreedToContinue", typeof(bool));
}
What am I doing wrong? Why does not even start the window?
https://www.dropbox.com/s/qjf1khq10y606ql/WpfApplication1.zip?dl=0
Enable debug log listener:
public App()
{
#if DEBUG
LogManager.AddDebugListener();
#endif
}
It immediately shows you what's wrong:
The view model of the view 'WpfApplication1.MainWindow' could not be
resolved. Make sure to customize the IViewModelLocator or register the
view and view model manually
This is because you don't have the namespaces that are usually used in MVVM. You can of course customize everything but since you are a beginner, I recommend the following actions:
Move view model to WpfApplication1.ViewModels namespace
Move view to WpfApplication1.Views namespace (both xaml and code-behind)
Change startup uri in App.xaml to "/Views/MainWindow.xaml"
I also strongly recommend that you read the getting started guide.
I'm trying to navigate from one View Model to another without any side panel.
For example, I have a Main Window View, this is where I load my User Control.
I have tried to access the static instance from the MainViewModel to change the Views, but it's not working.
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type vm:FirstViewModel}">
<v:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondViewModel}">
<v:SecondView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentViewModel}"/>
MainViewModel.cs
class MainviewModel : ObservableObject
{
private ObservableObject _currentViewModel = new FirstViewModel();
public ObservableObject CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
RaisePropertyChangedEvent("CurrentViewModel");
}
}
private static MainViewModel _instance = new MainViewModel();
public static MainViewModel Instance { get { return _instance; } }
}
Here, I have my FirstView, it just contains a button and several other UI designs
FirstView.xaml
<Button Command="{Binding goToSecondView}" />
FirstViewModel.cs
class FirstViewModel : ObservableObject
{
public ICommand goToSecondView
{
get
{
return new DelegateCommand(() =>
{
MainViewModel.Instance.CurrentViewModel = new SecondViewModel();
});
}
}
}
And I have a SecondView, which is similar to FirstView, just that it navigates to FirstView
I have tried searching for a solution, but so far, I have only managed to find examples that shows buttons on a panel which then allow switching of the User Control from clicking those button.
What I am trying to achieve is to enable switching of User Control via the buttons on the User Control itself, without any side panel.
Any help would be very much appreciated and would definitely aid me in my future projects.
Thank You.
You're creating two different instances of MainViewModel. The first one is probably being created by a locator or something and it's the one that your view is binding to when the window is first created. It's then creating a second instance and assigning it to its own static Instance member:
private static MainViewModel _instance = new MainViewModel();
public static MainViewModel Instance { get { return _instance; } }
It's this instance that your ICommand handler is changing when you press the button, which isn't bound to anything. Generally speaking you should be using a dependency injection framework to ensuring it's a true singleton, but for now just do something like this:
public MainViewModel()
{
Instance = this;
}
public static MainViewModel Instance { get; private set; }
Also your code calls RaisePropertyChangedEvent("CurrentViewModel")...I'm pretty sure you meant that to be RaisePropertyChanged("CurrentViewModel").
i would go another way and expose an Event from your first and Second viewmodel like
public event EventHandler<ShowMeEventArgs> ShowMeEvent;
then your main just need to subscribe to this event and can show the viewmodel. and even more your first and second viewmodel dont need to know anything from mainviewmodel