how can i get data from view - c#

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);
}
}

Related

How to notify View from ViewModel without breaking MVVM?

I recently started trying out MVVM pattern in school and was wondering what the best way (if any) is to notify View from the ViewModel, letting the view know to run a method without breaking MVVM? Basically letting the view know if something was successful, like a login attempt or trying to connect to a database?
An example could be a login page, where the mainwindow should change content to a new page only if the login was successful, if not, a messagebox should show up
Edit:
I'm using .NET
What I have tried so far:
View:
<Page
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:View.Pages" xmlns:ViewModels="clr-namespace:ViewModel.ViewModels;assembly=ViewModel" x:Class="View.Pages.Start_Page"
mc:Ignorable="d"
d:DesignHeight="720" d:DesignWidth="1280"
Title="Start_Page">
<Page.DataContext>
<ViewModels:Start_Page_ViewModel/>
</Page.DataContext>
Code behind it:
public Start_Page()
{
InitializeComponent();
Start_Page_ViewModel currentDataContext = DataContext as Start_Page_ViewModel;
currentDataContext.CurrentUserIDGotten += GoToMenu;
}
private void GoToMenu(int result)
{
if (result == -1)
{
MessageBox.Show("User credentials incorrect");
}
else if (result == -2)
{
MessageBox.Show("Connection failed");
}
else
{
Application.Current.MainWindow.Content = new Menu_Page();
}
}
ViewModel:
public class Start_Page_ViewModel
{
private string userName;
private string userPassword;
public string UserName { get => userName; set => userName = value; }
public string UserPassword { get => userPassword; set => userPassword = value; }
private RelayCommand logIn;
public RelayCommand LogIn => logIn;
public delegate void CurrentUserIDGottenEventHandler(int result);
public event CurrentUserIDGottenEventHandler CurrentUserIDGotten;
public Start_Page_ViewModel()
{
logIn = new RelayCommand(LogInToProgram, CanLogIn);
}
public void LogInToProgram(object o)
{
PasswordBox passwordBox = o as PasswordBox;
ViewModelController.Instance.CurrentUserID = Database_Controller.Instance.SignIn(userName, passwordBox.Password);
OnUserIDGotten(ViewModelController.Instance.CurrentUserID);
}
public bool CanLogIn(object o)
{
if (userName != null)
{
return true;
}
return false;
}
protected virtual void OnUserIDGotten(int result)
{
if (CurrentUserIDGotten != null)
{
CurrentUserIDGotten(result);
}
}
}
In pure way, without specified framework.
Create a event delegate (or listener interface), associate with view model
Register the event handler on view
Fire the event when view model is changed
Likes this
using System;
public class MainClass {
public static void Main (string[] args) {
ViewModel m = new ViewModel();
View v = new View();
v.Model = m;
m.MakeSomeChange();
}
}
public class View {
private IViewModel _model;
public IViewModel Model {
get {
return _model;
}
set {
if(_model != null) {
_model.OnChanged -= OnChanged;
}
if(value != null) {
value.OnChanged += OnChanged;
}
_model = value;
}
}
private void OnChanged(){
//update view
Console.WriteLine ("View Updated");
}
}
public delegate void ViewChangeDelegate();
public interface IViewModel {
event ViewChangeDelegate OnChanged;
}
public class ViewModel: IViewModel {
public event ViewChangeDelegate OnChanged;
public void MakeSomeChange() {
//make some change in the view Model
OnChanged.Invoke();
}
}
Generally, the ViewModel communicates with the View via databindings. The ViewModel might expose a property, like LoginSuccessful, that the the View would bind to. Then, when the property updates, the View would receive a PropertyChanged notification and change some aspect of its appearance. How it would do this varies; for example, a text property in XAML could be bound directly to an underlying ViewModel property:
<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}"/>
The ViewModel might look like:
public class UserViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public string Username {
get { return _username; }
set {
_username = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Username"));
}
}
private string _username;
public UserViewModel() { }
}
Whenever the Username property is changed on the UserViewModel class, the text box will update to display the new value.
However, this approach doesn't work for all situations. When working with boolean values, it's often useful to implement data triggers:
<TextBox Text="{Binding Source={StaticResource UserViewModel}, Path=Username}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource UserViewModel}, Path=IsTaken}" Value="True">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBox>
This code extends the previous example to color the background of the text box red if the IsTaken property is set to true on the ViewModel. A nice thing about data triggers is that they reset themselves; for example, if the value is set to false the background will revert to its original color.
If you want to go the other way, and notify the ViewModel of user input or a similarly important event, you can use commands. Commands can be bound to properties in XAML, and are implemented by the ViewModel. They are called when the user performs a certain action, such a clicking a button. Commands can be created by implementing the ICommand interface.

Binding WPF Grid to a model

I have a MVVM application which has a WPF Grid which contains other embedded WPF Grids and at the same time, each of them contain some fields (WPF TextBlocks).
Very simplified example - View:
<Grid>
<Grid>
// Row definitions
// Colum definitions
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{Binding Path=SomeField1}" />
<Grid>
<Grid>
// Row definitions
// Colum definitions
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding Path=SomeField2}" />
<Grid>
</Grid>
Each of these TextBlocks are bound to a string properties defined in view model.
View model (It implements INotifyPropertyChanged):
private string _someField1;
public string SomeField1
{
get return _someField1;
set
{
if (_someField1 == value) return;
_someField1 = value;
OnPropertyChanged("SomeField1");
}
}
private string _someField2;
public string SomeField2
{
get return _someField2;
set
{
if (_someField2 == value) return;
_someField2 = value;
OnPropertyChanged("SomeField2");
}
}
Then I have a model, I mean, a class with some public properties that is filled in by one process once data is obtained from a device. This class contains exactly the same properties as those defined in the view model.
Model:
public class MyModel
{
private string _someField1;
public string SomeField1
{
get return _someField1;
set
{
if (_someField1 == value) return;
_someField1 = value;
}
}
private string _someField2;
public string SomeField2
{
get return _someField2;
set
{
if (_someField2 == value) return;
_someField2 = value;
}
}
}
Later from view model I extract the data from this class (model), and I assign the values of those properties to the matching properties in view model. Finally, since view is bound to these properties, then view is correctly updated with values as below example.
View model method which extracts data received:
private void DataReceived(MyModel data)
{
this.SomeField1= data.SomeField1;
this.SomeField2= data.SomeField2;
}
The problem is that I have to define twice the properties, in view model and model. So I want to avoid this, I would like to bind Textblocks directly to properties in model and not defined the properties in view model to avoid redundant code. Or for example, is there any easy way to bind my model (MyModel) to the outer main grid and then textboxes bound to the properties in the view model (similar when bound itemsource in datagrid)?
I would suggest a generic view model:
public class BaseViewModel<TModel>
{
public TModel Model
{
get;
private set;
}
public BaseViewModel(TModel model)
{
this.Model = model;
}
}
Then you can bind to it:
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding Path=Model.SomeField1}" />
I was thinking if below it is ok and respects MVVM pattern. I have thought it after seeing solution proposed by c0d3b34n. I think it is simpler and no need to do interfaces and generic view model. I have checked and it works:
Declare a property in view model:
private MyModel _model;
public MyModel Model
{
get { return _model; }
set
{
_model = value;
OnPropertyChanged("Model");
}
}
Then in the view:
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding Path=Model.SomeField1}" />
... and the same for the rest of TextBlocks.
Finally:
private void DataReceived(MyModel data)
{
this.Model = data;
}
But as said by BionicCode in comments, this solution breaks MVVM pattern.

WPF TreeView CheckBox Binding - How to populate ViewModel with checked boxes

I'm slightly confused about how to set up a CheckBox with a binding that ensures that my ViewModel is populated with all the checked fields. I have provided some of the code and a description at the bottom.
My Xaml file let's call it TreeView.xaml:
<TreeView x:Name="availableColumnsTreeView"
ItemsSource="{Binding Path=TreeFieldData, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate x:Uid="HierarchicalDataTemplate_1" ItemsSource="{Binding Path=Children, Mode=OneWay, Converter={StaticResource SortingConverter}, ConverterParameter='DisplayName.Text'}">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsSelected, Mode=TwoWay}">
<TextBlock x:Uid="TextBlock_1" Text="{Binding DisplayName.Text, Mode=OneWay}" />
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The "code behind" TreeView.xaml.cs
public partial class MultipleColumnsSelectorView : UserControl
{
public MultipleColumnsSelectorView()
{
InitializeComponent();
}
private MultipleColumnsSelectorVM Model
{
get { return DataContext as MultipleColumnsSelectorVM; }
}
}
The ViewModel (tried to include only the relevant stuff) MultipleColumnsSelectorVM:
public partial class MultipleColumnsSelectorVM : ViewModel, IMultipleColumnsSelectorVM
{
public ReadOnlyCollection<TreeFieldData> TreeFieldData
{
get { return GetValue(Properties.TreeFieldData); }
set { SetValue(Properties.TreeFieldData, value); }
}
public List<TreeFieldData> SelectedFields
{
get { return GetValue(Properties.SelectedFields); }
set { SetValue(Properties.SelectedFields, value); }
}
private void AddFields()
{
//Logic which loops over SelectedFields and when done calls a delegate which passes
//the result to another class. This works, implementation hidden
}
The model TreeFieldData:
public class TreeFieldData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<TreeFieldData> Children { get; private set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
The Problem:
The behaviour that I want is when the user checks a checkbox, it should set the IsSelected property of TreeField (it does that right now) but then I want to go back to the ViewModel and make sure that this specific TreeField is added to SelectedFields. I don't really understand what the PropertyChangedEvent.Invoke does and who will receive that event? How can I make sure that SelectedFields gets populated so when AddFields() is invoked it has all the TreeField data instances which were checked?
You could iterate through the TreeFieldData objects in the TreeFieldData collection and hook up an event handler to their PropertyChanged event and then add/remove the selected/unselected items from the SelectedFields collection, e.g.:
public MultipleColumnsSelectorVM()
{
Initialize();
//do this after you have populated the TreeFieldData collection
foreach (TreeFieldData data in TreeFieldData)
{
data.PropertyChanged += OnPropertyChanged;
}
}
private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
TreeFieldData data = sender as TreeFieldData;
if (data.IsSelected && !SelectedFields.Contains(data))
SelectedFields.Add(data);
else if (!data.IsSelected && SelectedFields.Contains(data))
SelectedFields.Remove(data);
}
}
The subscriber of the PropertyChanged event is the view, so that if you change IsSelected programmatically the view knows it needs to update.
To insert the selected TreeField into your list you would add this code to your setter.
Also, you could define the following function which makes the notification much easier if you have many properties:
private void NotifyPropertyChange([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The CallerMemberName attribute instructs the compiler to automatically insert the name of the property calling the method. The ? after PropertyChanged is a shorthand to your comparison to not null.
The setter of IsSelected can then be changed to
set
{
_isSelected = value;
if (value) { viewModel.SelectedFields.Add(this); }
else { viewModel.SelectedFields.Remove(this); }
NotifyPropertyChange();
}
Of course you would need to provide the TreeFieldData with the ViewModel instance, e.g. in the constructor.
I don't know if SelectedFields is bounded/shown in your view. If yes and you want the changes made to the list to be shown, you should change List to ObservableCollection.

Prism 5.0 wrap Model Attributes in ViewModel

Hello I am relatively new with Prism but here is my question:
I am currently using Prism 5 with WPF. I have created a model, viewmodel and view. The View gets updated when an attribute from model changes. My problem is: when I want to process the Attribute from the model(for example another attribute name), the view does not get updated. Heres my Code. I'll be very thnakful if you could help me.
When RunTinting in the model Changes, the view does not get updated.
Model
public class MyModel :BindableBase
{
private Boolean _RunTinting;
public Boolean RunTinting
{
get { return _RunTinting; }
set { SetProperty(ref _RunTinting, value); }
}
Viewmodel
public class MainWindowViewModel : BindableBase
{
private MyModel model;
public MyModel Model
{
get { return this.model; }
set { SetProperty(ref this.model, value); }
}
public MainWindowViewModel()
{
this.Model=new MyModel();
}
public Boolean RunTinting2
{
get { return this.model.RunTinting; }
set { SetProperty(ref this._RunTinting, value); }
}
}
XAML
<Label x:Name="label1_Copy11" Content="{Binding RunTinting2}" HorizontalAlignment="Left" Margin="366,320,0,0" VerticalAlignment="Top" Height="25" Width="85" >
If you set the model property dynamically the view should bind to this property instead of the view model property:
<Label x:Name="label1_Copy11" Content="{Binding Model.RunTinting}" HorizontalAlignment="Left" Margin="366,320,0,0" VerticalAlignment="Top" Height="25" Width="85" >
Because the view model won't raise any change notifications when a property of the model is set to a new value.
The other option is to set the model property through the wrapper property of the view model, i.e. instead of setting the model property directly you set the view model property. Make sure that you then set the model's property in the setter of the wrapper property and that you raise a PropertyChanged event for the view model property that the view binds to:
public class MainWindowViewModel : BindableBase
{
...
public Boolean RunTinting2
{
get { return this.model.RunTinting; }
set { this.model.RunTinting = value; OnPropertyChanged("RunTinting2"); }
}
}
<Label x:Name="label1_Copy11" Content="{Binding RunTinting2}" HorizontalAlignment="Left" Margin="366,320,0,0" VerticalAlignment="Top" Height="25" Width="85" >
this is how I solved this problem, thank you Rachel and Will.
ViewModel
public void InitModel()
{
Model.PropertyChanged += Model_PropertyChanged;
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "RunTinting")
{
OnPropertyChanged("RunTinting2");
}
}
#mm8, I tried your solution, but it didnt work... but thank you anyways for the time taken.
I do not know why my answer has negative ratings. It works and respect the mvvm model!

Changing View Wpf MVVM

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.

Categories