I have seen two different ways to define the Commands
public DelegateCommand OpenCommand {get;set;}
public ViewModelConstructor()
{
OpenCommand = new DelegateCommand(OpenMethod);
}
Wheras other people decide to set it directly, as it is uncommon for a Command to change.
public DelegateCommand => new DelegateCommand(OpenMethod);
this seems a like a cleaner way to do it, but maybe it is expected to define them in the constructor so it is clear for other people.
What are the conventions on where to define objects such as Commands that won't change during Runtime?
As #vasily.sib mentions, the second method will create a new instance on each call.
I find the second method actually causes problems with the way RaiseCanExecuteChanged affects Views because of this. Consider the following ViewModel:
public class ViewModel
{
private bool CanEx { get; set; }
public DelegateCommand XCommand => new DelegateCommand(X, Can);
public DelegateCommand YCommand { get; set; }
public DelegateCommand SwitchCommand { get; set; }
public ViewModel()
{
CanEx = true;
YCommand = new DelegateCommand(Y, Can);
SwitchCommand = new DelegateCommand(Switch);
}
private void X(object obj) => System.Diagnostics.Debug.WriteLine("X");
private void Y(object obj) => System.Diagnostics.Debug.WriteLine("Y");
private bool Can(object obj) => CanEx;
private void Switch(object obj)
{
CanEx = !CanEx;
XCommand.RaiseCanExecuteChanged();
YCommand.RaiseCanExecuteChanged();
}
}
When I create a View from this ViewModel, with Buttons for each command, the Button for XCommand will not show as disabled when I fire SwitchCommand.
This seems to have something to do with the fact that at the time when RaiseCanExecuteChanged() is called, it is null.
Related
I created WPF on the MVVM principle, but I can't make it possible to open another one from the current page.
I followed this example
We need something like this:
Example
It is also worth considering that a page instance should be created. (That is, so that you can open yourself from Page 2, but with a different name)
My failed attempt:
Page 2 ViewModel
public Page2ViewModel()
{
ButtonCommand = new RelayCommand(o => LoadOtherView());
}
public string Title { get; set; } = "Page2";
public string Text { get; set; } = "Page two";
public ICommand ButtonCommand { get; set; }
private void MainButtonClick(object sender)
{
//MainViewModel main = new MainViewModel();
//main.SelectedPageViewModel = main.PageViewModels[0];
//main.SelectedPageViewModel.Title = "да";
//main.SelectedPageViewModel.Text = "Первая страница";
}
private void LoadOtherView()
{
// Instead of interacting with a whole ViewModel, we just use the interface
//_pageDisplay.ChangePageCommand.Execute(new ContactViewModel());
_pageDisplay.ChangeViewModel(_listPageViewModels[0]);
}
Interfaces
namespace WpfMVVMCore.Interfaces
{
public interface IPageDisplay
{
public IPageViewModel GetCurrentPage();
public IList<IPageViewModel> ListPageViewModels();
public void ChangeViewModel(IPageViewModel newPage);
}
}
MainViewModel
public MainViewModel(IPageDisplay pageDisplay, IList<IPageViewModel> ListPageViewModels)
{
_pageDisplay = pageDisplay;
_pageViewModels = ListPageViewModels;
}
public IPageViewModel GetCurrentPage()
{
return _selectedPageViewModel;
}
public void ChangeViewModel(IPageViewModel newPage)
{
this.SelectedPageViewModel = newPage;
}
public IList<IPageViewModel> ListPageViewModels()
{
return _pageViewModels;
}
** If the information provided by me is not enough for you, you can download this project (with my unsuccessful attempt 🤭 ):download**
P.S. Please do not criticize me if it is not difficult for you. I'm new to MVVM. Better help :)
I'm doing a intro c# MVVM project (Caliburn.Micro) and I'm injecting a "job" object into each viewmodel so that the job information is accessible everywhere.
I was hoping that changes to the injected object, which might be affected by a method in one viewmodel, would be reflected in all viewmodels, and this appears to be the case, however how would the bound properties associated with that injected object be updated?
For eg. below: The instance of JobClass is passed around and is changed by the MenuBarViewModel. How would one trigger the NotifyOnPropertyChange() in the property in the MainPanelViewModel so a bound xaml control in the associated view would pick up on the changes to the underlying member.
Or is this not how is should work. Is this what the event aggregator is supposed to manage? I was hoping that passing around the job would simplify things, or is that the global variable issue... Any tips would be welcome!
Hanuman....
// MainWindowViewModel
public class MainWindowViewModel
{
private JobClass _jobClass;
private readonly IEventAggregator _eventAggregator;
public MenuBarViewModel MenuBarViewModel { get; set; }
public MainPanelViewModel MainPanelViewModel { get; set; }
public MainWindowViewModel()
{
_eventAggregator = new EventAggregator();
_jobClass= new JobClass ();
this.MenuBarViewModel = new MenuBarViewModel(_eventAggregator, _jobClass);
this.MainPanelViewModel = new MainPanelViewModel(_eventAggregator, _jobClass);
}
}
// MenuBarViewModel
public class MenuBarViewModel: PropertyChangedBase
{
IEventAggregator _events;
JobClass _jobClass;
public MenuBarViewModel(IEventAggregator eventAggregator, JobClass jobClass)
{
_events = eventAggregator;
_jobClass = JobClass;
}
public SomeMethod()
{
_jobClass.MethodToAddSomedata();
}
// MainPanelViewModel
public class MainPanelViewModel : PropertyChangedBase
{
IEventAggregator _events;
JobClass _jobClass;
public JobClass JobCase
{
get { return _jobClass; }
set
{
_jobClass= value;
NotifyOfPropertyChange(() => JobCase);
}
}
}
My model is mainly made from the 2 classes below (I actually got another class which inherits from the abstract class but it doesnt matter I think):
public abstract class FeedForEvents: BaseObservableObject
{
public abstract void ReadFeed();
public List<Event> Events { get; set; }
public void AddEvent(Event aEvent)
{
Events.Add(aEvent);
OnPropertyChanged("Events");
}
}
public class Event : BaseObservableObject
{
public string MyProp
{
get
{
return _myProp;
}
set
{
_myprop= value;
OnPropertyChanged();
}
}
}
My form contains:
private BindingList<FeedForEvents> ListFeedsForEvents = new BindingList<FeedForEvents>();
private BindingList<Event> ListEvents
=> new BindingList<Event>(ListFeedsForEvents.SelectMany(m =>m.Events).ToList());
private BindingSource pagesBindingSource = new BindingSource();
public void RefreshGrid()
{
pagesBindingSource.DataSource = ListEvents;
this.grdEvents.DataSource = pagesBindingSource;
this.grdEvents.AutoGenerateColumns = true;
}
But even if my 2 objects correctly raised the PropertyChanged notficiation, the interface never show the objects updated (unless I manually refresh them by pressing a button to manually call RefreshGrid() ). Why?
I am trying to pass a value to a view model from another view model before navigating to the page attached to that view model.
I was previously passing it to the view, then passing it to the view model. This seems like a clumsy way of doing things.
I am not using any kind of framework so that is not an option.
At the moment the property is set as static and this works but im not sure if this is good practice.
The code:
View model 1:
This command opens the new page:
public void OpenRouteDetails()
{
RouteStopPopOverViewModel.RouteName = "TestRoute";
App.Page.Navigation.PushAsync(new RouteStopPopOverView());
}
View model 2: (RouteStopPopOverViewModel)
public static string RouteName { get; set; }
This does work but I would prefer not to use static as a way to achieve this.
Is there some way to set the RouteName property without using static or passing it through view-> view model.
I have seen some answers about this but they don't seem to answer to question clearly.
Share a controller class between view models.
The same instance has to be supplied to the constructor in both view models.
So you can set values, and listen for events in both view models.
The controller class becomes the intermediary.
public class SharedController : IControlSomething
{
private string _sharedValue;
public string SharedValue
{
get => _sharedValue;
set
{
if (_sharedValue == value)
return;
_sharedValue = value;
OnSharedValueUpdated();
}
}
public event EventHandler SharedValueUpdated;
protected virtual void OnSharedValueUpdated()
{
SharedValueUpdated?.Invoke(this, EventArgs.Empty);
}
}
public class ViewModel1
{
private readonly IControlSomething _controller;
public ViewModel1(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
public class ViewModel2
{
private readonly IControlSomething _controller;
public ViewModel2(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
here the sample you can achieve your requirement easily with navigation
public class ViewModelFrom : BaseViewModel
{
async Task ExecuteCommand()
{
string routeName="value to trasfer";
Navigation.PushAsync(new View(routeName));
}
}
public partial class View : ContentPage
{
public View(string routeName)
{
InitializeComponent();
BindingContext = new ViewModelTo(routeName);
}
}
public class ViewModelTo : BaseViewModel
{
public string RouteName { get; set; }
public ViewModelTo(string routeName)
{
RouteName=routeName;
}
}
If there is a hierarchy you could express that in a parent to both of them.
public class Route
{
private string Name;
}
public class RouteSelectedArgs : EventArgs
{
public Route Selected { get; set; }
}
public interface IRouteSelection
{
event EventHandler<RouteSelectedArgs> RouteSelected;
}
public interface IRouteDetails { }
public class RouteWizard
{
public UserControl view { get; set; }
private IRouteSelection _selection;
private IRouteDetails _details;
public RouteWizard(IRouteSelection selection, IRouteDetails details)
{
_selection = selection;
_details = details;
_selection.RouteSelected += Selection_RouteSelected;
view = MakeView(_selection);
}
private void Selection_RouteSelected(object sender, RouteSelectedArgs e)
{
_selection.RouteSelected -= Selection_RouteSelected;
view = MakeView(_details, e.Selected);
}
private UserControl MakeView(params object[] args)
{
////magic
throw new NotImplementedException();
}
}
As you are using the MVVM pattern, you can use one of the many MVVM Frameworks to achieve this.
I use FreshMvvm and it allow me to pass parameters between view models like this
await CoreMethods.PushPageModel<SecondPageModel>(myParameter, false);
Then in SecondPageModel I can see access the parameters in the Init method
private MyParamType _myParameter;
public override void Init(object initData)
{
base.Init(initData);
var param = initData as MyParamType;
if (param != null)
{
_myParameter = param;
}
}
You can find more details about FreshMvvm here although most MVVM frameworks have similar functionality.
In my XAML code I've got a combo box that is bound to a static property as shown below.
<ComboBox x:Name="DifferentKinds"
ItemsSource="{x:Static local:MainWindow.DifferentKinds}"/>
And the code for the property and its source.
public static Kind[] DifferentKinds
=> (Kind[])Enum.GetValues(typeof(Kind));
public enum Kind { WeeHee, BuuHuu }
I just learned that there'll be more kinds in the future. They won't be created particularly often but it's uncertain how many they might become with time. So, instead of adding new elements to the enum, I'll read in these from the DB.
For the simplicity of the example, let's say we read in those values every time the property is accessed. The solution becomes a private fields that is read in from the DB before the execution of InitializeComponent() starts. Then, I serve those values as a static property still, like so.
public MainWindow()
{
PopulateDifferentKinds();
InitializeComponent();
}
private static IEnumerable<Kind> _allDifferentKinds;
public static IEnumerable<Kind> AllDifferentKinds
=> _allDifferentKinds.Where(element => element.Active);
public class Kind
{
public String Name { get; set; }
public bool Active { get; set; }
public override string ToString() { return Name; }
}
Is this approach creating a huge problem that I miss to see?
Is there a better way to bind the items in the bombo box to the values from DB?
The main problem I see here is that calling the PopulateDifferentKinds method in the view's constructor will create a performance problem. While this method is running and the database is being queried, your UI is being blocked.
This could be improved using a class that loads your data on a background thread and uses a PropertyChanged event to signal that the data has been loaded:
public class Kind
{
public string Name { get; set; }
public bool Active { get; set; }
public int Value { get; set; }
}
public class AppEnumValues : INotifyPropertyChanged
{
private static readonly Lazy<AppEnumValues> current
= new Lazy<AppEnumValues>(() => new AppEnumValues(), LazyThreadSafetyMode.ExecutionAndPublication);
public static AppEnumValues Current
{
get { return current.Value; }
}
public Kind[] AllDifferentKinds { get; private set; }
public bool IsLoaded { get; private set; }
private AppEnumValues()
{
Task.Run(() => this.LoadEnumValuesFromDb())
.ContinueWith(t => this.OnAllPropertiesChanged());
}
protected virtual void OnAllPropertiesChanged()
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(string.Empty));
}
}
private void LoadEnumValuesFromDb()
{
// This simulates some latency
Thread.Sleep(2000);
// Call your data service here and load the values
var kinds = new[]
{
new Kind {Active = true, Name = "WeeHee", Value = 1},
new Kind {Active = true, Name = "BuuHuu", Value = 2}
};
this.AllDifferentKinds = kinds;
this.IsLoaded = true;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
You could extend this with properties for each extensible "enum" you need in your application. Implementing the Singleton pattern, this would load its data in background the first time it is used. You could bind your ComboBoxes like this:
<ComboBox ItemsSource="{Binding Source={x:Static wpfApplication2:AppEnumValues.Current},Path=AllDifferentKinds}"
IsEnabled="{Binding Source={x:Static wpfApplication2:AppEnumValues.Current},Path=IsLoaded}"
DisplayMemberPath="Name" />
While the data is being loaded, the ComboBox would be disabled.
I would recommend looking into MVVM and Dependency Injection. This will enhance your WPF application architecture and make things like that easy: You wouldn't provide a static property or singleton, which has bad testability and extensibility, but you could use constructor injection to give the AppEnumValues provider into your View Model and then bind your view to it.