WPF Navigation using MVVM - c#

I'm trying to follow the answer provided in this post, but I must be missing something trivial. I've defined my DataTemplates as App.xaml as follows:
<Application.Resources>
<DataTemplate DataType="{x:Type vm:BlowerViewModel}">
<v:BlowerView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<v:HomeView />
</DataTemplate>
</Application.Resources>
Then, in my MainWindow.xaml I've defined the following code:
<Window x:Class="App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:App.UI.ViewModel"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<ContentControl Content="{Binding CurrentView}" />
</Window>
The code for MainViewModel contains a property CurrentView and an ICommand so I can switch views. Defined as follows:
public class MainViewModel : BaseViewModel
{
private BaseViewModel _currentView;
public MainViewModel()
{
CurrentView = new HomeViewModel();
}
public BaseViewModel CurrentView
{
get { return _currentView; }
set
{
if (_currentView != value)
{
_currentView = value;
RaiseChangedEvent("CurrentView");
}
}
}
public ICommand SwitchView {
get {
return new CommandHandler(() => SwitchBlower());
}
}
protected void SwitchBlower()
{
CurrentView = new BlowerViewModel();
}
}
In my HomeView.xaml, I have defined a button that links to the MainViewModel to execute the SwitchView ICommand. This is shown below.
<UserControl x:Class="App.UI.View.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:App.UI.ViewModel"
Height="300" Width="300">
<Grid>
<TextBlock>This is the homeview</TextBlock>
<Button Command="{Binding DataContext.SwitchView, RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}}, Mode=OneWay}" Content="Test" />
</Grid>
</UserControl>
When I start the application it doesn't register the event, and clicking on the button does not fire the event to change the view. I've tried putting breakpoints in both the ICommand get and the function call itself. At first, I thought maybe I needed to define MainViewModel in my data templates, but doing so results in the following error (even though the project builds fine)
Can't put a Window in a style
Can anyone provide the missing piece I need to get this working?

The AncestorType should be MainWindow not MainViewModel. MainViewModel is not a class that is part of the visual tree.

Related

How to handle Master-Detail screen communication in WPF with MVVM architecture?

I'm trying to build my first app with WPF and in order to fully understand MVVM I'm not using any framework, the only helper I use is Microsoft.Toolkit.Mvvm
I have thi app with 2 pages, one is the master and the other one is the detail.
I did set up navigation as it's explained in WPF MVVM navigate views
Now I don't understand how I should tell to the detail screen which data it should display, since I'm not allowed to pass parameters to the viewmodel that I am instantiating in the datacontext.
My MainWindow.xaml
<Window x:Class="AlgsManagerDesktop.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:AlgsManagerDesktop"
xmlns:views="clr-namespace:AlgsManagerDesktop.Views"
xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:MasterViewModel}">
<views:MasterView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:DetailsViewModel}">
<views:DetailsView />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding ViewModel}" />
</Grid>
</Window>
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
private BaseViewModel viewModel;
public BaseViewModel ViewModel
{
get => viewModel;
set => SetProperty(ref viewModel, value);
}
public RelayCommand SwitchToDetailsCommand { get; }
public MainWindowViewModel()
{
ViewModel = new MasterViewModel();
SwitchToDetailsCommand = new RelayCommand(SwitchToDetails);
}
private void SwitchToDetails()
{
ViewModel = new DetailsViewModel();
}
}
MasterViewModel.cs
public class MasterViewModel : BaseViewModel
{
private ItemModel selectedItem;
public ItemModel SelectedItem
{
get => selectedItem;
set
{
SetProperty(ref selectedItem, value);
DeleteCommand.NotifyCanExecuteChanged();
}
}
public ObservableCollection<ItemModel> items { get; set; }
public RelayCommand DeleteCommand { get; }
public MasterViewModel()
{
DeleteCommand = new RelayCommand(RemoveItem, ItemIsSelected);
}
private void RemoveItems()
{
AlgSets.Remove(SelectedItem);
}
private bool ItemIsSelected()
{
return SelectedItem != null;
}
}
MasterView.xaml
<UserControl x:Class="AlgsManagerDesktop.Views.MasterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AlgsManagerDesktop.Views"
xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
xmlns:root="clr-namespace:AlgsManagerDesktop"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<viewModel:MasterViewModel/>
</UserControl.DataContext>
<!-- ListBox here that updates a SelectedItem property -->
<!-- this button handles navigation to details screen, I'd like to pass SelectedItem to the next screen -->
<Button Command="{Binding DataContext.SwitchToDetailsCommand,
RelativeSource={RelativeSource AncestorType={x:Type root:MainWindow}},
Mode=OneWay}">
Open Selected
</Button>
</UserControl>
DetailsView.xaml
<UserControl x:Class="AlgsManagerDesktop.Views.DetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AlgsManagerDesktop.Views"
xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<viewModel:DetailsViewModel/>
</UserControl.DataContext>
<-- Item details here, I'd like to take them from an Item property in the DetailsViewModel -->
</UserControl>
The DetailsView should inherit the DataContext from the ViewModel property of the MainWindowViewModel which it will if you remove the following XAML markup from it, i.e. you should not set the DataContext of the UserControl explicitly somewhere:
<UserControl.DataContext>
<viewModel:DetailsViewModel/>
</UserControl.DataContext>
It's then up to the MainWindowViewModel to initialize and set the state of the DetailsViewModel.
You created a SelectedItem property in MasterViewModel, presumably to bind to the SelectedItem property of your presumable ListBox that's missing from your XAML, but that is a dead-end view model. In fact I'd argue that you shouldn't split your view model in three (the actual view model, the master one and the details one) because they're all linked together -- they're one view split in a view and 2 sub-views, so logically you should have one view model.
It should be immediately obvious that your approach isn't going to work because when you create the master/details view models in your code you don't link them together at all, you just create throw-aways.
The alternative if you want to keep your 3 view models separate for whatever reason is to keep a property link to the main view model in both of them, and to move the SelectedItem property to the main view model, then bind to it in both sub-views.

MVVM Switching Between Views

I'm new to WPF so bear with me. I have a WinForms Application that I am trying to redo in WPF. In my current WinForms Application, I stick all my Controls into one Form, and hide/show them based on what buttons are hit, as well as making use of a second form.
My goal: Create different views to switch between smoothly based on what button is hit, instead of hiding Controls or making separate Forms and then hiding those.
I currently have a MainWindow view (My initial launch window), where with a button, I switch to my CreateAccount view. What I am having issues with is, how can I make my button in my CreateAccount go "back" to my MainWindow?
My end goal is to be able to switch between 4 views based off Button clicks.
Here is my MainWindow.xaml
<Window x:Class="MusicPlayer.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:MusicPlayer"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">
<Views:CreateAccountView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>
<PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
My MainWindow.xaml.cs
using System;
using System.Windows;
using MusicPlayer.ViewModels;
namespace MusicPlayer {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Current.Shutdown();
} //end of onClosed
private void CreateAccountView_Clicked(object sender, RoutedEventArgs e) {
DataContext = new CreateAccountViewModel();
} //end of CreateAccountView_Clicked
}
}
And here is my CreateAccount.xaml
<UserControl x:Class="MusicPlayer.Views.CreateAccountView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
</UserControl.Resources>
<Grid Background="White">
<Button Content="Button" HorizontalAlignment="Left" Margin="276,279,0,0" VerticalAlignment="Top" Height="60" Width="59" Click="Button_Click"/>
</Grid>
</UserControl>
And my CreateAccountView.xaml.cs
using System.Windows;
using System.Windows.Controls;
using MusicPlayer.ViewModels;
namespace MusicPlayer.Views {
public partial class CreateAccountView : UserControl {
//public static readonly DependencyProperty TestMeDependency = DependencyProperty.Register("MyProperty", typeof(string), typeof(CreateAccountView));
public CreateAccountView() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
}
}
It seems to me that your current attempt is on the right track. The main issue with the code you posted is that the CreateAccountView.Button_Click() handler doesn't have access to the DataContext property it should be setting:
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
This DataContext property belongs to the CreateAccountView user control. However, this is not the controlling context for what's being displayed. So changing the value of that DataContext property doesn't have any useful effect. (Indeed, a user control should not set its own DataContext property at all, because doing so discards whatever context the client code using that user control had set.)
There's not enough context to know exactly what the best way for you to do this would be. I don't think it would be possible to provide enough context here on Stack Overflow. The overall architecture will depend on too many little details about your program. But, one way to approach this which I think is a good one would be this:
Create a "main" view model that governs the overall behavior of the app
Create individual view models that relate to different states of the UI
Have the main view model configure the individual view models to switch the current view model as appropriate, given the user input (e.g. clicking buttons)
Translating that into code, looks something like this…
First, the view models:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
Of course, these view models contain only the implementation details needed to handle the UI switching. In your program, each would also include the stuff specific to each view state that you need.
In my little sample, the "home" view contains a couple of buttons, used to select the individual sub-views available:
<UserControl x:Class="WpfApp1.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
The sub views just contain the button required to go back to the home view:
<UserControl x:Class="WpfApp1.Sub1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
Finally, the main window sets the main view model, and declares templates to use for each of the specific sub views:
<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:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
Importantly, you'll see that none of the view objects include any code-behind. It's not necessary when you approach the problem this way, at least not for the purpose of controlling basic behaviors in the code. (You may still wind up with code-behind for view objects, but this will usually only be for the purpose of implementing specific user-interface behaviors unique to that view object, not for dealing with the view model state.)
Using this approach, you let WPF do as much of the heavy-lifting as possible. It also decouples all of the view model objects from each other. There's a clear hierarchy: only the top-level "main" view model even knows about the other view models. This allows the sub-view models ("home", "sub1", and "sub2") to be reused as necessary in other scenarios without any modification or special-case handling within them.
Here are the helper classes I used above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}

Navigation from one view to another in WPF MVVM

I wrote code which should navigate between user controls in WPF application using MVVM, but I realised that this code doesn't work.
From window LoginView I want to change the view to VotingCardView.
Actually, after clicking on the button in the LoginView, the method DisplayVCV gets executed, but the view is not going to change. What am I doing wrong?
MainView.xaml:
<Window x:Class="ElectionCalculator.View.MainView"
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:ElectionCalculator"
xmlns:v="clr-namespace:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
Title="Election calculator" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<ContentControl Content="{Binding ViewModel}" />
</Window>
LoginView.xaml:
<UserControl x:Class="ElectionCalculator.View.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Command="{Binding DataContext.DisplayVC, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=OneWay}" Margin="161,147,47,124" />
</Grid>
</UserControl>
MainViewModel.cs
class MainViewModel : BaseViewModel
{
public BaseViewModel ViewModel { get; set; }
public MainViewModel()
{
ViewModel = new LoginViewModel();
}
public ICommand DisplayVC { get { return new RelayCommand(DisplayVCV); } }
public void DisplayVCV()
{
ViewModel = new VotingCardViewModel();
MessageBox.Show("DisplayVCCommandExecuted");
}
}
Your ViewModel property implementation doesn't raise a PropertyChanged event when the value changes. This is usually done via an INotifyPropertyChanged implementation. Because of that, your view doesn't get notified that something has changed.
In your case, this means that you need a backing field for your ViewModel property and implement your ViewModel property similar to this:
private BaseViewModel _viewModel;
public BaseViewModel ViewModel
{
get { return _viewModel; }
set
{
if(_viewModel != value)
{
_viewModel = value;
OnPropertyChanged("ViewModel");
}
}
}
Since you are already deriving from BaseViewModel I assume that the method OnPropertyChanged (or some method with a similar name) is implemented there. It is also quite common that you don't have to specify the property name ("ViewModel") as an argument, since lots of implementations use the [CallerMemberName] attribute for this purpose.

WPF binding issues while using MVVM

Time for my first question :)
I have the following:
public class BuilderViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private double _contentScale = 1.0;
public double ContentScale
{
get { return _contentScale; }
set
{
_contentScale = value;
NotifyPropertyChanged("ContentScale");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region Commands
bool CanZoomIn() { return true; }
void ZoomInExecute()
{
ContentScale += 1.0;
}
public ICommand ZoomIn { get { return new RelayCommand(ZoomInExecute, CanZoomIn); } }
#endregion
}
And the corresponding view:
<UserControl x:Class="PS_IDE.FormBuilder.View.Builder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PS_IDE.FormBuilder.ViewModel">
<UserControl.DataContext>
<local:BuilderViewModel />
</UserControl.DataContext>
<TextBox Text="{Binding ContentScale}" Width="100" />
</UserControl>
I'm trying to have the ZoomIn command in BuilderViewModel update the text box value in it's view. The command is being fired from another user control, UIBuilder, which includes Builder. If I debug and fire the command from UIBuilder, I can see it updating ContentScale properly.
However, my text box value does not get updated (it only says "1", which is the initial value of ContentScale).
I know I'm missing something and hope someone can point me in the right direction.
EDIT: Added the control that is firing the command
<UserControl x:Class="PS_IDE.FormBuilder.UIBuilder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PS_IDE.FormBuilder"
xmlns:ViewModel="clr-namespace:PS_IDE.FormBuilder.ViewModel"
xmlns:View="clr-namespace:PS_IDE.FormBuilder.View" mc:Ignorable="d">
<UserControl.DataContext>
<ViewModel:BuilderViewModel />
</UserControl.DataContext>
<DockPanel LastChildFill="True">
....
<ToolBarTray DockPanel.Dock="Bottom" HorizontalAlignment="Right">
<ToolBar>
<Button Height="24" Width="24" ToolTip="Zoom In" Command="{Binding ZoomIn}">
<Image Source="Images/ZoomIn.png" Height="16"/>
</Button>
....
</ToolBar>
</ToolBarTray>
<View:Builder x:Name="builder" />
</DockPanel>
</UserControl>
With the setting in both view:
<UserControl.DataContext>
<local:BuilderViewModel />
</UserControl.DataContext>
you are basically creating two viewmodels, one for each view. So when your Command updates the property it does it on one of the viewmodel but your textbox is bound to a different viewmodel.
To resolve it remove the DataContext setting from the Builder.xaml
Additionally you need to pass your DataContext to your Builder control (with this both view will share the same viewmodel).
So modify your UIBuilder.xaml:
<View:Builder x:Name="builder" DataContext="{Binding}" />
Use Mode TwoWay in your binding
Text ="{Binding ElementName=BuilderViewModel,
Path=ContentScale,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Nota : use observable collection in order to send notify

Changing View on Buttonclick

I am learning WPF and MVVM at the moment (or at least I am trying to...).
I created a little sample-app, that shows a Window with 2 buttons, each of it should show a new View on Click. So I created 3 UserControls (DecisonMaker with the 2 Buttons, and one Usercontrol for each "clicktarget").
So I bound the CotentControl of the MainWindow to a property called "CurrentView" in my MainWindowViewModel
Code of MainWindow.xaml:
<Window x:Class="WpfTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTestApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentView, Mode=OneWay}" />
</Grid>
</Window>
Code of MainWindowViewModel:
class MainWindowViewModel
{
private UserControl _currentView = new DecisionMaker();
public UserControl CurrentView
{
get { return _currentView; }
set { _currentView = value; }
}
public ICommand MausCommand
{
get { return new RelayCommand(LoadMouseView); }
}
public ICommand TouchCommand
{
get { return new RelayCommand(LoadTouchView); }
}
private void LoadMouseView()
{
CurrentView = new UserControlMouse();
}
private void LoadTouchView()
{
CurrentView = new UserControlTouch();
}
}
The initial UserControl (DecisionMaker) shows up as supposed. Also the method LoadMouseView is called. But the View doesn't change. What am I missing?
UPDATE: Thanks so much! I missed the INotifyPropertyChanged-interface. All of your answers were just great and very accurate and helpful! I don't know which one to accept - I think it's the most fair way to accept the "first" answer?
I accepted blindmeis answer, as it solved the problem and helped me understand MVVM better. But every answer was really great thanks to all of you!
if you wanna do mvvm - then you should have no references to your view/usercontrols in your viewmodel. you have to implement INotifyPropertyChanged! ps: if you need System.Windows namespace in your Viewmodel - then something is wrong.
in your case what you need:
1 mainviewmodel
1 viewmodel for UserControlMouse
1 viewmodel for UserControlTouch
1 view/usercontrol for UserControlMouse
1 view/usercontrol for UserControlTouch
your mainviewmodel should have at least 2commands to switch your view and 1 property for CurrentView. in your command you simply set your CurrentView to the right viewmodel instance. at least you need two datatemplates for each viewmodel which define the right view.
public object CurrentView
{
get { return _currentView; }
set {
_currentView = value; this.RaiseNotifyPropertyChanged("CurrentView");}
}
xaml
<Window x:Class="WpfTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTestApplication"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyMouseViewModel}">
<local:MyMouseUserControlView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyTouchViewModel}">
<local:MyTouchUserControlView/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<!-- here your buttons with command binding, i'm too lazy to write this. -->
<!-- you content control -->
<ContentControl Content="{Binding CurrentView, Mode=OneWay}" />
</Grid>
</Window>
I would do something like this to select the input style that you want, to MainWindow I've added a property that lets me select the mode of input.
public enum UserInterfaceModes
{
Mouse,
Touch,
}
public UserInterfaceModes UserInterfaceMode
{
get { return (UserInterfaceModes)GetValue(UserInterfaceModeProperty); }
set { SetValue(UserInterfaceModeProperty, value); }
}
public static readonly DependencyProperty UserInterfaceModeProperty = DependencyProperty.Register("UserInterfaceMode", typeof(UserInterfaceModes), typeof(MainWindow), new UIPropertyMetadata(UserInterfaceModes.Mouse));
then for the xaml view part you can select the correct template with a trigger.
<Style TargetType="{x:Type local:MainWindow}">
<Style.Triggers>
<DataTrigger Binding="{Binding UserInterfaceMode}" Value="Mouse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MainWindow}">
<Grid Background="Red"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding UserInterfaceMode}" Value="Touch">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MainWindow}">
<Grid Background="Blue"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The viewmodel need to implement INotifyPropertyChanged. Otherwise the view won't be notified when a property changes in the viewmodel.
class MainWindowViewModel : INotifyPropertyChanged
{
private UserControl _currentView = new DecisionMaker();
public UserControl CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You need to implement INotifyPropertyChanged on MainWindowViewModel, such that the view is informed when the CurrentView property is changed.
It sounds like the behaviour you want is pretty much what you get with a [TabControl][1] - why not use this built in control and just bind the DataContext of both tabs to the same view model.
This also has the advantage that your view model wouldn't know about the view classes (I am assuming that UserControlMouse etc are user controls).
Note: this will not be applicable if you need the view model to be aware of whether it is in touch or mouse mode.

Categories

Resources