Changing View Wpf MVVM - c#

I'm beginner in WPF and MVVM, but want to learn it by building some small project.
I've got a WPF app using the Model-View-ViewModel pattern, based on Rachel Lim example. In my app I have 2 views - EmployeesList and EmployeeDetails.
List of employees is storage in GidView.
The main problem I have is
How to change view when I double-click on a row,
How to get the value from the first column (employee_id) and pass it into EmployeeDetails view.
Base navigation is in xaml with DataTmplate and ItmCntrol:
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:EmployeesListViewModel}">
<local:EmployeesListView />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
also I've got ApplicationViewModel where is list of views
public class ApplicationViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public ApplicationViewModel()
{
// Add available pages
PageViewModels.Add(new HomeViewModel());
PageViewModels.Add(new EmployeesListViewModel());
PageViewModels.Add(new EmployeeDetailsViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
public List<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;
}
}
public IPageViewModel CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
}

How to change view when I double-click on a row
First, you need to add EventTrigger for MouseDoubleClick event:
<DataGrid Name="gridEmployees" ItemsSource="{Binding Employees}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<local:CustomCommandAction Command="{Binding DoubleClickCommand}" CommandParameter="{Binding ElementName=gridEmployees, Path=SelectedItems[0]}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
CustomCommandAction is a class, that inherits from TriggerAction and is used as a link between event and command in your View Model. Here is the code:
public sealed class CustomCommandAction : TriggerAction<DependencyObject>
{
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(CustomCommandAction), null);
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CustomCommandAction), null);
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
protected override void Invoke(object parameter)
{
if (this.AssociatedObject != null)
{
ICommand command = this.Command;
if (command != null)
{
if (this.CommandParameter != null)
{
if (command.CanExecute(this.CommandParameter))
{
command.Execute(this.CommandParameter);
}
}
else
{
if (command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
}
}
}
After that the easiest solution is to use ChangeViewModel method in yours command Execute method, e.g.:
...
_doubleClickCommand = new RelayCommand(OnDoubleClick);
...
private RelayCommand _doubleClickCommand = null;
private ApplicationViewModel _applicationViewModel;
private void OnDoubleClick(object obj)
{
EmployeeDetailsViewModel selectedModel = obj as EmployeeDetailsViewModel;
_applicationViewModel.ChangeViewModel(selectedModel);
}
public ICommand DoubleClickCommand
{
get
{
return _doubleClickCommand;
}
}
How to get the value from the first column (employee_id) and pass it into EmployeeDetails view
For your DataGrid you may use collection of EmployeeDetailsViewModel as ItemsSource. If you do so, selected item will be passed to your command Execute method as an instance of EmployeeDetailsViewModel, and you'll be able to get Id from there.

It looks like you're missing a needed element to show the selected view. If you look at the linked sample note the ItemsControl is contained within a Border which is in turn inside a DockPanel.
Below the DockPanel there is a ContentControl which is a key element needed to show the selected view.

Related

How to get property and call command from different ViewModel with mvvm pattern

I have a ViewModel with all the properties that i will need in every sub ViewModel.
It's the first time i try to split commands and viewmodel to multiple files. Last time everything was in the same ViewModel and it was a pain to work with it. Everything shows up as expected but i want to find a way to pass the same data in every viewmodel.
From my GetOrdersCommand, i want to get the HeaderViewModel.SelectedSource property. I didn't find any way to do it without getting a null return or loosing the property data...
I would like to call my GetOrdersCommand from HeaderView button too.
Any tips how i can achieve this ? Perhaps, my design is not good for what i'm trying to do ?
MainWindow.xaml
<views:HeaderView Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding HeaderViewModel}" LoadHeaderViewCommand="{Binding LoadHeaderViewCommand}"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding OrderViewModel}" GetOrdersCommand="{Binding GetOrdersCommand}"/>
</TabItem>
</TabControl>
HeaderView.xaml
<DockPanel>
<ComboBox DockPanel.Dock="Left" Width="120" Margin="4" VerticalContentAlignment="Center" ItemsSource="{Binding SourceList}" SelectedItem="{Binding SelectedSource}" DisplayMemberPath="SourceName"/>
<Button x:Name="btnTest" HorizontalAlignment="Left" DockPanel.Dock="Left" Margin="4" Content="Test"/>
</DockPanel>
HeaderView.xaml.cs
public partial class OrderView : UserControl
{
public ICommand GetOrdersCommand
{
get { return (ICommand)GetValue(GetOrdersCommandProperty); }
set { SetValue(GetOrdersCommandProperty, value); }
}
public static readonly DependencyProperty GetOrdersCommandProperty =
DependencyProperty.Register("GetOrdersCommand", typeof(ICommand), typeof(OrderView), new PropertyMetadata(null));
public OrderView()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (GetOrdersCommand != null)
{
GetOrdersCommand.Execute(this);
}
}
}
MainViewModel.cs
private OrderViewModel orderViewModel;
public OrderViewModel OrderViewModel { get; set; } // Getter, setter with OnPropertyChanged
private HeaderViewModel headerViewModel;
public HeaderViewModel HeaderViewModel { get; set; } // Getter, setter with OnPropertyChanged
public MainViewModel()
{
HeaderViewModel = new HeaderViewModel();
OrderViewModel = new OrderViewModel();
}
HeaderViewModel.cs
public ICommand LoadHeaderViewCommand { get; set; }
public HeaderViewModel()
{
LoadHeaderViewCommand = new LoadHeaderViewCommand(this);
}
GetOrdersCommand.cs
public class GetOrdersCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly OrderViewModel _orderViewModel;
public GetOrdersCommand(OrderViewModel orderViewModel)
{
_orderViewModel = orderViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
/* Build Order List according to HeaderViewModel.SelectedSource */
_orderViewModel.Orders = new ObservableCollection<Order>()
{
new Order { ID = 1, IsReleased = false, Name = "Test1"},
new Order { ID = 2, IsReleased = true, Name = "Test2"},
};
}
}
Thanks guys ! I moved my commands to their owning ViewModel as suggested.
I tried MVVVM Light Tools and found about Messenger Class.
I used it to send my SelectedSource (Combobox from HeaderView) from HeaderViewModel to OrderViewModel. Am i suppose to use Messenger class like that ? I don't know, but it did the trick!!!
I thought about moving GetOrdersCommand to OrderViewModel, binding my button command to OrderViewModel, binding SelectedSource as CommandParameter but i didn't know how i was suppose to RaiseCanExecuteChanged when HeaderViewModel.SelectedSource changed... Any advice?
MainWindow.xaml
<views:HeaderView DataContext="{Binding Source={StaticResource Locator}, Path=HeaderVM}" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding Source={StaticResource Locator}, Path=OrderVM}"/>
</TabItem>
</TabControl>
OrderViewModel.cs
private ObservableCollection<Order> _orders;
public ObservableCollection<Order> Orders
{
get { return _orders; }
set
{
if (_orders != value)
{
_orders = value;
RaisePropertyChanged(nameof(Orders));
}
}
}
public OrderViewModel()
{
Messenger.Default.Register<Source>(this, source => GetOrders(source));
}
private void GetOrders(Source source)
{
if (source.SourceName == "Production")
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 1, IsReleased = false, Name = "Production 1" }
};
}
else
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 2, IsReleased = true, Name = "Test 1" }
};
}
}
Part of HeaderViewModel.cs
private Source _SelectedSource;
public Source SelectedSource
{
get { return _SelectedSource; }
set
{
if (_SelectedSource != value)
{
_SelectedSource = value;
RaisePropertyChanged(nameof(SelectedSource));
GetOrdersCommand.RaiseCanExecuteChanged();
}
}
}
private RelayCommand _GetOrdersCommand;
public RelayCommand GetOrdersCommand
{
get
{
if (_GetOrdersCommand == null)
{
_GetOrdersCommand = new RelayCommand(GetOrders_Execute, GetOrders_CanExecute);
}
return _GetOrdersCommand;
}
}
private void GetOrders_Execute()
{
Messenger.Default.Send(SelectedSource);
}
private bool GetOrders_CanExecute()
{
return SelectedSource != null ? true : false;
}

Set up a binding between two properties in code behind

In my view, I have a ListBox with some templated items that contain buttons.
<ListBox x:Name="MyListBox" ItemTemplate="{DynamicResource DataTemplate1}"
ItemsSource="{Binding MyItems}">
</ListBox>
And the template for generated items:
<DataTemplate x:Key="DataTemplate1">
<StackPanel Orientation="Horizontal">
<Button Width="50" Click="Button_Click" />
</StackPanel>
</DataTemplate>
When user clicks a button on one of those ListBox items, I want to send the index of that ListBox item to my ViewModel.
So figured to use Binding as it seems to be the way in MVVM. But I'm struggling to set up a binding in code between two properties.
My View code is as follows:
public partial class ItemView : UserControl
{
ViewModel.ItemViewModel VM;
public ItemView()
{
InitializeComponent();
VM = new ViewModel.ItemViewModel();
this.DataContext = VM;
}
private int clickedItemIndex;
public int ClickedItemIndex { get => clickedItemIndex; set => clickedItemIndex = value; }
private void Button_Click(object sender, RoutedEventArgs e)
{
var ClickedItem = (sender as FrameworkElement).DataContext;
ClickedItemIndex = MyListBox.Items.IndexOf(ClickedItem);
}
}
I get the index and set it to ClickedItemIndex property,
I also have property in my ViewModel:
public int SomeInt { get; set; }
Now how do I set up a binding between these two properties?
I'm quite new to MVVM and still learning it. So, maybe this not the correct approach. But I need to have a way for each individual listbox item to be able to call upon an effect in more global viewmodel. For example, if I wanted to have a "Remove" button on each of the listbox items, I would somehow need to send the index to the viewmodel and call the removeItem method with index as the parameter. Or is there a better way to do similar things?
I have a sample app created just for this scenario. I know it seems a lot of code at first glance. Copy this code in your project, that will help debug and get a hang of it(MVVM, databinding, commands and so on).
usercontrol.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
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:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Model}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Name}"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.UpdateCommand}"
CommandParameter="{Binding}"
Content="Update"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.RemoveCommand}"
CommandParameter="{Binding}"
Content="Remove"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Models}">
</ListBox>
</Grid>
usercontrol.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
View model
public class ViewModel : INotifyPropertyChanged
{
private Models _Models;
public Models Models
{
get { return _Models; }
set { _Models = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Models)));
}
}
public ViewModel()
{
Models = new Models();
UpdateCommand = new Command(o => true, UpdateItem);
RemoveCommand = new Command(o => true, RemoveItem);
}
void RemoveItem(object item)
{
Model m = (item as Model);
Models.Remove(m);
}
void UpdateItem(object item)
{
Model m = (item as Model);
m.Name = m.Name + " updated";
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ICommand UpdateCommand { get; private set; }
public ICommand RemoveCommand { get; private set; }
}
Icommand implementation
public class Command : ICommand
{
private readonly Func<object, bool> _canExe;
private readonly Action<object> _exe;
public Command(Func<object,bool> canExecute,Action<object> execute)
{
_canExe = canExecute;
_exe = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExe(parameter);
}
public void Execute(object parameter)
{
_exe(parameter);
}
}
Model and a collection of models
public class Models : ObservableCollection<Model>
{
public Models()
{
Add(new Model ());
Add(new Model ());
Add(new Model ());
Add(new Model ());
}
}
public class Model : INotifyPropertyChanged
{
static int count = 0;
public Model()
{
Name = "Model "+ ++count;
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
You don't need to use a Button in order to select the item. When you click/tap on the item it will get automatically selected.
Then simply bind ListBox.SelectedIndex to your view model property SomeInt and it will update on every selection.
Data binding overview in WPF
You can also get the item itself by binding ListBox.SelectedItem to your view model.
You can handle new values by invoking a handler from the property's set method:
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
private int currentItemIndex;
public int CurrentItemIndex
{
get => this.currentItemIndex;
set
{
this.currentItemIndex = value;
OnPropertyChanged();
// Handle property changes
OnCurrentItemIndexChanged();
}
}
private MyItem currentItem;
public MyItem CurrentItem
{
get => this.currentItem;
set
{
this.currentItem = value;
OnPropertyChanged();
}
}
protected virtual void OnCurrentItemIndexChanged()
{
// Handle the new this.CurrentItemIndex value
}
// Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ItemView .xaml
<UserControl>
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<ListBox ItemsSource="{Binding MyItems}"
SelectedIndex="{Binding CurrentItemIndex}"
SelectedItem="{Binding CurrentItem}" />
</UserControl>

wpf - One ViewModel that interacts with multiple instances of a Model

I have a WorkspaceViewModel that handles addition and deletion of tab items dynamically through an ObservableCollection. Each time a tab is connected to a PayslipModel, all bindings work fine but one problem I am having is that;
I have a save button in the UserControl who's DataContext is set to WorkspaceViewModel and I would like to save whatever info is being displayed in the selected tab. Now, each time a tab is added, a new instance of PayslipModel is created, which is exactly what I want because I don't want bindings to be shared for all tabs. However, I am unable to save what is being displayed since PayslipModel has multiple instances, therefore nothing is returned (temporarily using MessageBox to test if info is being retrieved) when I hit save.
I created a diagram to better explain my situation:
Is it possible to access the current instance when a tab is selected or cycle through all instances and do something like batch saving?
This is a working example which shows one of the possiblities:
View
<TabControl DataContext="{Binding}" ItemsSource="{Binding Models}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" >
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Top" Content="Click Me" Command="{Binding DataContext.PCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}}"
CommandParameter="{Binding Desc}"/>
<TextBlock Text="{Binding Desc}" >
</TextBlock>
</DockPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Model View
public class ModelView
{
public ModelView()
{
_models = new ObservableCollection<Model>();
_pCommand = new Command(DoParameterisedCommand);
}
ObservableCollection<Model> _models;
public ObservableCollection<Model> Models { get { return _models; } }
private void DoParameterisedCommand(object parameter)
{
MessageBox.Show("Parameterised Command; Parameter is '" +
parameter.ToString() + "'.");
}
Command _pCommand;
public Command PCommand
{
get { return _pCommand; }
}
}
Model
public class Model : INotifyPropertyChanged
{
string _desc;
public string Desc { get { return _desc; } set { _desc = value; RaisePropertyChanged("Desc"); } }
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Command
public class Command : ICommand
{
public Command(Action<object> parameterizedAction, bool canExecute = true)
{
_parameterizedAction = parameterizedAction;
_canExecute = canExecute;
}
Action<object> _parameterizedAction = null;
bool _canExecute = false;
public bool CanExecute
{
get { return _canExecute; }
set
{
if (_canExecute != value)
{
_canExecute = value;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute(object parameter)
{
return _canExecute;
}
void ICommand.Execute(object parameter)
{
this.DoExecute(parameter);
}
public virtual void DoExecute(object param)
{ if (_parameterizedAction != null)
_parameterizedAction(param);
else
throw new Exception();
}
}
Use this to initialize:
public MainWindow()
{
InitializeComponent();
ModelView mv = new ModelView();
mv.Models.Add(new Model() { Name = "a", Desc = "aaa" });
mv.Models.Add(new Model() { Name = "b" , Desc = "bbb"});
mv.Models.Add(new Model() { Name = "c", Desc = "cccc" });
this.DataContext = mv;
}

Navigate between UserControls with Event Aggegator

I have a MainWindow where I navigate between UserControls by clicking on a menu and it works fine.
I am using this following pattern:
https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/
In one of those usercontrol there is a button. By clicking on this button I want to navigate to another usercontrol.
How do I do that?
MainView
<UserControl.Resources>
<DataTemplate DataType="{x:Type cvm:FirstViewModel}">
<cv:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:SecondViewModel}">
<cv:SecondView/>
</DataTemplate>
<cvm:MainViewModel x:Key="main"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource main}}">
<Border Grid.Row="0">
<Menu Height="58">
<ItemsControl ItemsSource="{Binding PageViewModels}" Width="289" Height="58">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay, Source={StaticResource main}}" CommandParameter="{Binding}" TextDecorations="{x:Null}">
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Menu>
</Border>
<Border Grid.Row="1" >
<ContentControl Content="{Binding CurrentUserControl}"/>
</Border>
</Grid>
MainViewModel
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
PageViewModels.Add(new FirstViewModel());
PageViewModels.Add(new SecondViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
}
#region Fields
private List<IUserContentViewModel> _pageViewModels;
public List<IUserContentViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IUserContentViewModel>();
return _pageViewModels;
}
}
private IUserContentViewModel _currentUserControl;
public IUserContentViewModel CurrentUserControl
{
get { return _currentUserControl; }
set
{
if (value != _currentUserControl)
{
_currentUserControl = value;
OnPropertyChanged("CurrentUserControl");
}
}
}
#region Methods
private void ChangeViewModel(IUserContentViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentUserControl = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
private ICommand _changePageCommand;
#endregion
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IUserContentViewModel)p),
p => p is IUserContentViewModel);
}
return _changePageCommand;
}
}
}
SecondView
<Grid Background="Blue">
<Button /> <!-- Going to ThirdView?????????-->
</Grid>
You have to call the ChangePageCommand from your button:
<Button DataContext="{Binding Source={StaticResource main}}"
Command="{Binding ChangePageCommand"}
CommandParameter="{Binding PageViewModels[2]}">
I am assuming that you have your FirstViewModel stored at PageViewModels[0], and your SecondViewModel at PageViewModels[1].
You also have to create a ThirdView link to your ThirdViewModel, as your other two Views/ViewModels:
<UserControl.Resources>
...
<DataTemplate DataType="{x:Type cvm:ThirdViewModel}">
<cv:ThirdView/>
</DataTemplate>
</UserControl.Resources>
And just as an advice, you can set your UserControl DataContext at the start of your code, instead of using it in any UIElement(Button and Grid in your case), like this:
<UserControl.DataContext>
<cvm:MainViewModel />
</UserControl.DataContext>
EDIT>>>>
Forgot to say that you also have to add your ThirdViewModel to your PageViewModels collection:
PageViewModels.Add(new ThirdViewModel());
I finally have my solution.
I use Event Aggregator with Prism 6.
First I create a Singleton.
internal sealed class ApplicationService
{
private ApplicationService() { }
private static readonly ApplicationService _instance = new ApplicationService();
internal static ApplicationService Instance { get { return _instance; } }
private IEventAggregator _eventAggregator;
internal IEventAggregator EventAggregator
{
get
{
if (_eventAggregator == null)
_eventAggregator = new EventAggregator();
return _eventAggregator;
}
}
}
Then public class GoToThird : PubSubEvent<TEvent> { }
In MainViewModel I subscribe to the event and add my ThirdViewModel().
public class MainViewModel : ViewModelBase
{
protected readonly IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
PageViewModels.Add(new FirstViewModel());
PageViewModels.Add(new SecondViewModel(ApplicationService.Instance.EventAggregator)));
PageViewModels.Add(new ThirdViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
this._eventAggregator = eventAggregator;
}
private void GoToThird()
{
CurrentUserControl = PageViewModels[2];
}
}
At the end I publish the event in SecondViewModel()
public class SecondViewModel
{
protected readonly IEventAggregator _eventAggregator;
public SecondViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator = eventAggregator;
}
private void Go()
{
_eventAggregator.GetEvent<GoToThird>().Publish();
}
private ICommand goToThirdCommand;
public ICommand GoToThirdCommand
{
get
{
return goToThirdCommand ?? (goToThirdCommand = new RelayCommand(p => this.Go(), p => this.CanGo()));
}
}
private bool CanGo()
{
return true;
}
}
Big Thanks to Rachel and Kirenenko

how can i get data from view

i use MVVM to built my project, now i have some troubles,when i click a button, i want get data from view to viewmodel, what should i do?
thanks
Bob
Bind that data to the view model and execute a command when the user clicks the button. The command and data are housed in the view model, so it has everything it needs.
public class YourViewModel : ViewModel
{
private readonly ICommand doSomethingCommand;
private string data;
public YourViewModel()
{
this.doSomethingCommand = new DelegateCommand(this.DoSomethingWithData);
}
public ICommand DoSomethingCommand
{
get { return this.doSomethingCommand; }
}
public string Data
{
get { return this.data; }
set
{
if (this.data != value)
{
this.data = value;
this.OnPropertyChanged(() => this.Data);
}
}
}
private void DoSomethingWithData(object state)
{
// do something with data here
}
}
XAML:
<TextBox Text="{Binding Data}"/>
<Button Command="{Binding DoSomethingWithData}"/>
For information on the various dependencies in the above example such as ViewModel and DelegateCommand, see my series of posts on MVVM.
EDIT after receiving more info: For tracking item selection, simply introduce a view model to represent the item:
public class CustomerViewModel : ViewModel
{
private bool isSelected;
public bool IsSelected
{
get { return this.isSelected; }
set
{
if (this.isSelected != value)
{
this.isSelected = value;
this.OnPropertyChanged(() => this.IsSelected);
}
}
}
}
Your "main" view model would expose a collection of these items (generally an ObservableCollection<T>):
public ICollection<CustomerViewModel> Customers
{
get { return this.customers; }
}
Your view would then bind as:
<ListBox ItemsSource="{Binding Customers}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Notice how each ListBoxItem will have its IsSelected property bound to the CustomerViewModel.IsSelected property. Thus, your main view model can just check this property to determine which customers are selected:
var selectedCustomers = this.Customers.Where(x => x.IsSelected);
The solution suggested by Kent is in my opinion by far the best/only one to follow MVVM.
If however you don't want to replicate/reflect listbox selections to the view model or you want a quick and - according to MVVM - dirty solution, you can use the command parameter to send data from the view to the view model.
For that you have to bind the CommandParameter property of the button to the property which contains the data you want to send to the view model. For simplicity I just used a TextBox.
<StackPanel Orientation="Vertical">
<TextBox x:Name="Data"/>
<Button Content="DoSomething"
Command="{Binding Path=DoSomethingCommand}"
CommandParameter="{Binding ElementName=Data, Path=Text}"/>
</StackPanel>
The ViewModel of the sample looks like the following.
public class ViewModel
{
private ICommand doSomethingCommand = new MyCommand();
public ICommand DoSomethingCommand
{
get
{
return doSomethingCommand;
}
}
}
With this, you will get the specified content as the parameter in the Execute method of ICommand.
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
string dataFromView = (string)parameter;
// ...
MessageBox.Show(dataFromView);
}
}

Categories