This is probably duplicated question, but i could not find solution for my problem.
I'm working on WPF application using MVVM pattern.
There are four views which are binded to their ViewModels. All ViewModels have BaseViewModel as parent.
public abstract class ViewModelBase : INotifyPropertyChanged
{
private bool isbusy;
public bool IsBusy
{
get
{
return isbusy;
}
set
{
isbusy = value;
RaisePropertyChanged("IsBusy");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainView contains BusyIndicator:
<extWpfTk:BusyIndicator IsBusy="{Binding IsBusy}">
<ContentControl />
</extWpfTk:BusyIndicator>
If I set IsBusy = true in MainViewModel, BusyIndicator is shown.
If I try to set IsBusy = true from other ViewModels, BusyIndicator is not shown.
Just to notice, I can not use 3rd party libraries in my project like MVVMLight in order to use their Messenger to communicate between ViewModels.
MainView:
public class MainWindowViewModel : ViewModelBase
{
public ViewModel1 ViewModel1 { get; set; }
public ViewModel2 ViewModel2 { get; set; }
public ViewModel3 Model3 { get; set; }
public MainWindowViewModel()
{
ViewModel1 = new ViewModel1();
ViewModel2 = new ViewModel2();
ViewModel3 = new ViewModel3();
//IsBusy = true; - its working
}
}
ViewModel1:
public class ViewModel1 : ViewModelBase
{
RelayCommand _testCommand;
public ViewModel1()
{
}
public ICommand TestCommand
{
get
{
if (_testCommand == null)
{
_testCommand = new RelayCommand(
param => this.Test(),
param => this.CanTest
);
}
return _testCommand;
}
}
public void Test()
{
//IsBusy = true; - BusyIndicator is not shown
}
bool CanTest
{
get
{
return true;
}
}
}
public class MainWindowViewModel : ViewModelBase
{
public ViewModel1 ViewModel1 { get; set; }
public ViewModel2 ViewModel2 { get; set; }
public ViewModel3 Model3 { get; set; }
public MainWindowViewModel()
{
ViewModel1 = new ViewModel1();
ViewModel2 = new ViewModel2();
ViewModel3 = new ViewModel3();
ViewModel1.PropertyChanged += (s,e) =>
{
if(e.PropertyName == "IsBusy")
{
// set the MainWindowViewModel.IsBusy property here
// for example:
IsBusy = ViewModel1.IsBusy;
}
}
//IsBusy = true; - its working
}
}
Repeate subcsription to all your viewModels.
Don't forget to unsubscribe from the events, when you don't need it more, to avoid memory leacks.
Your problem was: you binded to the MainWindowViewModel's propetry, not to inner ViewModel's properties.
Related
I have been trying to figure out these past 2 days how to switch between 2 User Controls back and forward with buttons inside those User Controls.
I managed to make this happen but with the buttons outside those User Controls.
This is how my project files look
BaseCommand.cs
public class BaseCommand : ICommand
{
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
public ICommand LogInCommand { get; set; }
public ICommand SetupCommand { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public MainViewModel()
{
LogInCommand = new BaseCommand(OpenLogIn);
SetupCommand = new BaseCommand(OpenSetup);
}
private void OpenLogIn(object obj)
{
SelectedViewModel = new LogInViewModel();
}
private void OpenSetup(object obj)
{
SelectedViewModel = new SetupViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
MainWindow.xaml
<StackPanel>
<ContentControl Content="{Binding SelectedViewModel}"/>
<Button Content="Open LogIn" Height="24" Command="{Binding LogInCommand}"/>
<Button Content="Open Setup" Height="24" Command="{Binding SetupCommand}"/>
</StackPanel>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
LoginViewModel and SetupViewModel are empty classes and their corresponding views have a text block indicating what they are.
What I want is to have instead 2 buttons in my MainWindow.xaml I want 1 in my LogInView.xaml that opens SetupView.xaml and vice versa.
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type viewmodels:LogInViewModel}">
<views:LogInView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:SetupViewModel}">
<views:SetupView/>
</DataTemplate>
</Application.Resources>
Move "Open Setup" button to LogInView and "Open LogIn" button to SetupView.
In LogInViewModel create SetupCommand and pass the MainViewModel in the ctor. When the button "Open Setup" is clicked and SetupCommand is invoked then call new OpenSetup method on the MainViewModel.
public class LogInViewModel : INotifyPropertyChanged // etc.
{
private readonly MainViewModel mainViewModel;
public ICommand SetupCommand { get; set; }
public LogInViewModel(MainViewModel mainViewModel)
{
this.mainViewModel = mainViewModel;
SetupCommand = new BaseCommand(OpenSetup);
}
private void OpenSetup(object obj)
{
mainViewModel.OpenSetup();
}
....
}
Similar for SetupViewModel
public class SetupViewModel : INotifyPropertyChanged // etc.
{
private readonly MainViewModel mainViewModel;
public ICommand LogInCommand { get; set; }
public SetupViewModel(MainViewModel mainViewModel)
{
this.mainViewModel = mainViewModel;
LogInCommand = new BaseCommand(OpenLogIn);
}
private void OpenLogIn(object obj)
{
mainViewModel.OpenLogIn();
}
....
}
Finally, remove commands from MainViewModel, modify OpenLogIn and OpenSetup methods and pass reference to the MainViewModel when you create new instance of LogInViewModel or SetupViewModel.
public class MainViewModel : INotifyPropertyChanged
{
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public MainViewModel()
{
OpenLogIn();
}
public void OpenLogIn()
{
SelectedViewModel = new LogInViewModel(this);
}
public void OpenSetup()
{
SelectedViewModel = new SetupViewModel(this);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I want to read some data from database and do some process on them and then view them in the view.
I read a lot about MVVM and now I am confused.
Imaging I read a person entity from database with Name attribute.
please make a small code and show me how should I make my model and ViewModel.
I guess it we will be something like this :
public class PersonModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string Name;
public string name
{
get
{
return Name;
}
set
{
Name = value;
onpropertychanged("name");
}
}
public PersonModel( string s)
{
name = s;
}
public void onpropertychanged(string PName)
{
if (PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PName));
}
}
}
public class PersonViewModel
{
public ObservableCollection <PersonModel> list { get; set; }
public PersonViewModel()
{
list = new ObservableCollection<model>();
list.Add(new model("abc"));
list.Add(new model("def"));
}
public void change()
{
list[1].name = "changed";
}
}
public class ViewModelBase
{
public PersonViewModel vperson { get; set; }
public ViewModelBase()
{
vperson = new PersonViewModel();
vperson.change();
}
}
Edite : Where should database connections be?
Edite :
<Grid>
<TextBox Text="{Binding vperson.list[1].name}" />
</Grid>
</Window>
I edited your classes and is working
public class PersonModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged();
}
}
public PersonModel(string name)
{
_name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class PersonViewModel
{
public ObservableCollection<PersonModel> Items { get; set; }
public PersonViewModel()
{
Items = new ObservableCollection<PersonModel> { new PersonModel("abc"), new PersonModel("def") };
}
public void Change()
{
Items[1].Name = "changed";
}
}
public class ViewModelBase
{
public PersonViewModel PersonViewModel { get; set; }
public ViewModelBase()
{
PersonViewModel = new PersonViewModel();
PersonViewModel.Change();
}
}
//Use the dataContext in this way, will help you with the strong type
xmlns:viewModels="clr-namespace:WpfApp1.ViewModels"
<Window.DataContext>
<viewModels:ViewModelBase />
</Window.DataContext>
<Grid>
<TextBox Text="{Binding PersonViewModel.Items[1].Name}" />
</Grid>
I'm having a problem with using MVVM for a Xamarin project.
I can not refresh the user interface if one of my objects in my viewModel is updated (after a PUT request, for example).
Let me explain :
My model :
public class MyObject
{
public string Id { get; private set; }
public string Name { get; private set; }
}
My viewmodel :
public class MyViewModel : BaseViewModel
{
public MyObject MyObject { get; private set; }
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
}
BaseViewModel implements INotifyPropertyChanged
public class BaseViewModel : INotifyPropertyChanged
{
public string PageTitle { get; protected set; }
LayoutViewModel() {}
// MVVM ----------------------------------------------------------------------------------------
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
MyViewModel is defined as BindingContext for my page
My properties IdMvvm and NameMvvm are bind in Entry in my page in xaml
When I modify an Entry then the value is raised but if my MyModel object changes value, for example update (click on a button) then the value of the different Entry is not updated
Can you help me please? Because it seems that I missed something ...
If you need more explanation, tell me to know
Sorry if my english is not good
It is because when you change the model, your view is not aware about the change. Update your code so that you explicitly notify property changes when your model changes.
public class MyViewModel : BaseViewModel
{
private MyObject _myObject;
public MyObject MyObject
{
get { return _myObject; }
private set { _myObject = value; NotifyModelChange(); }
}
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
private void NotifyModelChange()
{
OnPropertyChanged(nameof(IdMvvm));
OnPropertyChanged(nameof(NameMvvm));
}
}
I am trying to bind the data displayed in a DataGrid to a dynamic list of object (WhisperModel) which is inside another object(WhisperReader). The DataGrid only displays the headers, but no values. How can I make the DataGrid dynamically update itself when the list "whispers" is changed?
Main Window XAML:
<DataGrid x:Name="whisperDataGrid" Margin="10,69,10,10" IsReadOnly="True" ItemsSource="{Binding}"/>
Main Window C#
public partial class MainWindow : Window
{
private WhisperReader wr;
public MainWindow()
{
InitializeComponent();
wr = new WhisperReader();
whisperDataGrid.DataContext = wr.whispers;
}
WhisperReader:
class WhisperReader
{
public ObservableCollection<WhisperModel> whispers { get; private set; }
public WhisperReader()
{
whispers = new ObservableCollection<WhisperModel>();
}
WhisperModel:
class WhisperModel
{
public DateTime sentTime { get; set; }
public string sender { get; set; }
public string message { get; set; }
}
I think your problem is that it doesn't know when to update itself because:
You have made the whispers list the data context.
The properties that you are binding to don't use INotifyPropertyChanged.
WhisperReader and WhisperModel are not public
All bindings must be public, must be properties, and must call the PropertyChanged method.
The PropertyChanged function triggers the binding updates.
Try this...
public partial class MainWindow : Window
{
private WhisperReader wr;
public MainWindow()
{
InitializeComponent();
wr = new WhisperReader();
whisperDataGrid.DataContext = wr;
}
public class WhisperReader : INotifyPropertyChanged
{
ObservableCollection<WhisperModel> _whispers;
public ObservableCollection<WhisperModel> whispers
{
get { return _whispers; }
private set
{
_whispers = value;
NotifyPropertyChanged();
}
}
public WhisperReader()
{
whispers = new ObservableCollection<WhisperModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WhisperModel : INotifyPropertyChanged
{
public DateTime sentTime { get; set; }
private string _sender;
public string sender
{
get { return _sender; }
set { _sender = value; NotifyPropertyChanged();
}
private string _message;
public string message
{
get { return _message; }
set { _message = value; NotifyPropertyChanged();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<DataGrid x:Name="whisperDataGrid" Margin="10,69,10,10" IsReadOnly="True" AutoGenerateColumns="True" ItemsSource="{Binding whispers}"/>
how populate ComboBox and DataGridView using MVP (Model-View-Presenter). Actually i have something like this:
The View base class:
public interface IView
{
event EventHandler Initialize;
event EventHandler Load;
}
The presenter base class:
public class Presenter<TView> where TView : class, IView
{
private TView view;
public TView View { get { return view; } private set { view = value; } }
public Presenter(TView view)
{
if (view == null)
throw new ArgumentNullException("view");
View = view;
View.Initialize += OnViewInitialize;
View.Load += OnViewLoad;
}
protected virtual void OnViewInitialize(object sender, EventArgs e) { }
protected virtual void OnViewLoad(object sender, EventArgs e) { }
}
The specific view:
public interface IAdministrarUsuariosView : IView
{
string NombreUsuarioABuscar {get; set;}
List<Perfil> ListaPerfiles {get; set;}
event EventHandler BuscarUsuarioPorNombre;
event EventHandler BuscarUsuarioPorPerfil;
}
I don't know how to populate the ComboBox and the Datagridview!
PD: Thanks to Josh for the code of the View and Presenter base classes (MVP Base Class)
Thanks!!
you need to create a property that you will use to set up the data source for the ComboBox and DropdownList.
just to give you an example(you need to improve this code but it shows a way on how you can do that)
in you view :
//this is just a template to simulate a datasource item
public class TestItem
{
public int Id { get; set; }
public string Description { get; set; }
}
public interface IAdministrarUsuariosView : IView
{
string NombreUsuarioABuscar { get; set; }
// List<Perfil> ListaPerfiles { get; set; }
event EventHandler BuscarUsuarioPorNombre;
event EventHandler BuscarUsuarioPorPerfil;
List<TestItem> SetComboBox { set; }
List<TestItem> SetGridView { set; }
}
then in the concrete view (the winform that imolements the IAdministrarUsuariosView
public class YourView:IAdministrarUsuariosView
{
public string NombreUsuarioABuscar
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public event EventHandler BuscarUsuarioPorNombre;
public event EventHandler BuscarUsuarioPorPerfil;
public List<TestItem> SetComboBox
{
set
{
ComboBox.DataSource = value;
//your need to specify value and text property
ComboBox.DataBind();
}
}
public List<TestItem> SetGridView
{
set
{
GridView.DataSource = value;
//your need to specify value and text property
GridView.DataBind();
}
}
}
then your presenter should look like the below:
public class YourPresenter:Presenter<IAdministrarUsuariosView>
{
public YourPresenter(IAdministrarUsuariosView view) : base(view)
{
}
protected override void OnViewLoad(object sender, EventArgs e)
{
List<TestItem> listResult = GetListItem();
this.View.SetComboBox = listResult;
this.View.SetGridView = listResult;
}
}