I'm relatively new to MVVM and I'm wondering about the best way to structure my application. Here is a sample of my models:
public class ModelSource : ModelBase
{
#region Fields
private int _isLoading;
private BackgroundWorker worker = new BackgroundWorker();
private ObservableCollection<PCDatabase> _databases;
#endregion //Fields
#region Properties
public ObservableCollection<PCDatabase>Databases
{
get
{
if (_databases == null)
{
_databases = new ObservableCollection<PCDatabase>();
}
return _databases;
}
set
{
_databases = value;
this.OnPropertyChanged("Databases");
}
}
public int IsLoading
{
get
{
return _isLoading;
}
set
{
_isLoading = value;
OnPropertyChanged("IsLoading");
}
}
#endregion
#region Methods
/// <summary>
/// Gets all Databases from the Server
/// </summary>
public void getDatabasesAsync(ConfigDatabaseConnection _currentConfig)
{
//execute SQL Query...
}
(ModelBase implements INotifyPropertyChanged).
Here is my corresponding ViewModel:
namespace DbRestore.ViewModel
{
public class ViewModelSource : ViewModelBase
{
private ObservableCollection<PCDatabase> _databases;
private ModelSource _modelSource;
private ICommand _populateDatabaseCommand;
public ConfigDatabaseConnection _currentConfig;
public ViewModelSource()
{
this.ModelSource = new ModelSource();
}
#region Commands
/// <summary>
/// Command that opens a Database Connection Dialog
/// </summary>
public ICommand OpenDataBaseConnectionCommand
{
get
{
if (_populateDatabaseCommand == null)
{
_populateDatabaseCommand = new RelayCommand(
param => this.PopulateDatabases()
);
}
return _populateDatabaseCommand;
}
}
public ObservableCollection<PCDatabase> Databases
{
get
{
return _databases;
}
set
{
_databases = value;
OnPropertyChanged("Databases");
}
}
#endregion //Commands
public void PopulateDatabases()
{
ModelSource.getDatabasesAsync(_currentConfig);
}
Calling ModelSource.getDatabasesAsync(_currentConfig) gets my SQL Data in my model. Due to some of my SQL queries being quite complex, I've implemented a Background Worker that runs these queries asynchronously.
How do I get the data into my ViewModel, which is bound to my View? Or is my design approach as a whole faulty?
Things I've considered and tried:
Binding directly to the model: Works, but I've been told that this is a
bad practice, and the application logic should reside in the Model.
Moving the SQL queries into the ViewModel: Also works, but then my Model
class seems to be redundant - it would be nothing but a custom datatype.
Run the queries synchronously and directly assign the Observable
Collection in my model to the Observable Collection in my ViewModel. Also
works, but then I'm running into problems with my BackgroundWorker,
because the ViewModel won't know when the Query is actually finished.
Move all your database logic into a service (aka repository) class.
It is OK to bind directly to the model properties instead of creating a dozen ViewModel proxy classes for each Model, as soon as you don't need any special view-related logic around a particular model. So exposing a collection of PCDatabase is OK.
Since you're using BackgroundWorker, I assume you use .NET Framework 3.5 and don't have TPL.
public interface IPCDatabaseRepository
{
void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler);
}
public class PCDatabaseRepository : IPCDatabaseRepository
{
public void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler)
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
args.Result = // Execute SQL query...
};
worker.RunWorkerCompleted += (sender, args) =>
{
resultHandler(args.Result as IList<PCDatabase>);
worker.Dispose();
};
worker.RunWorkerAsync();
}
}
public class ViewModelSource : ViewModelBase
{
private readonly IPCDatabaseRepository _databaseRepository;
private ObservableCollection<PCDatabase> _databases;
private bool _isBusy;
public ViewModelSource(IPCDatabaseRepository databaseRepository /*Dependency injection goes here*/)
{
_databaseRepository = databaseRepository;
LoadDatabasesCommand = new RelayCommand(LoadDatabases, () => !IsBusy);
}
public ICommand LoadDatabasesCommand { get; private set; }
public ObservableCollection<PCDatabase> Databases
{
get { return _databases; }
set { _databases = value; OnPropertyChanged("Databases"); }
}
public bool IsBusy
{
get { return _isBusy; }
set { _isBusy = value; OnPropertyChanged("IsBusy"); CommandManager.InvalidateRequerySuggested(); }
}
public void LoadDatabases()
{
IsBusy = true;
_databaseRepository.GetPCDatabasesAsync(results =>
{
Databases = new ObservableCollection(results);
IsBusy = false;
});
}
Have you seen these articles?
Async Programming : Patterns for Asynchronous MVVM Applications: Data Binding
https://msdn.microsoft.com/en-us/magazine/dn605875.aspx
Async Programming : Patterns for Asynchronous MVVM Applications: Commands
https://msdn.microsoft.com/en-us/magazine/dn630647.aspx
These should cover a good strategy especially when working with async/await.
Related
I would like to create viewmodel properties in runtime.
I'm not so familiar with MVVM in UWP. Rather windows forms. In the past I created custom class object with reflection and I had possibility to add properties in runtime.
In current project I prepared solution with mvvm ligt and UWP app. Works fine with data exchange on viewmodel level. Now I try to find how to create properties of viewmodel in runtime ie. from descriptions in xml file.
namespace hmi_panel.ViewModels
{
public class HomeViewModel : ViewModelBase
{
#region Fields
readonly IPlcService _plcService;
#endregion
#region Constructors
public HomeViewModel(IPlcService dummyPlcService)
{
_plcService = dummyPlcService;
_plcService.Connect("127.0.0.1", 0, 1);
//zdarzenie cyklicznego odswiezania zeminnych
OnPlcServiceValuesRefreshed(null, null);
_plcService.ValuesRefreshed += OnPlcServiceValuesRefreshed;
}
#endregion
#region Properties
public string AppVersion
{
get { return $"{Package.Current.Id.Version.Major}.
{Package.Current.Id.Version.Minor}.{Package.Current.Id.Version.Build}.
{Package.Current.Id.Version.Revision}"; }
}
public string AppCopyright
{
get { return "plc service: " + _plcService.ConnectionState.ToString(); }
}
private bool _pumpState;
public bool pumpState
{
get { return _pumpState; }
set {
_pumpState=value;
RaisePropertyChanged(() => pumpState);
}
}
#endregion
#region Methods
private RelayCommand _ConnectCommand;
public RelayCommand ConnectCommand
{
get
{
return _ConnectCommand ?? (_ConnectCommand = new RelayCommand(() =>
{
pumpState = true;
}, () => true));
}
}
private void OnPlcServiceValuesRefreshed(object sender, EventArgs e)
{
pumpState = _plcService.PumpState;
}
#endregion
}
}
Property pumpState value is readed and writed with _plService. I can change value and I can read after external change.
I would like to start only with bidirectional binding in xaml and create needed property ie. pumpState when viewmodel instance is created ie. in construtor.
I'm making user changeable settings for my media player and I'm struggling to find an elegant solution to the problem.
One of my settings for example - pauses the video at it's last frame, if not checked it will either continue through the playlist or if it's only 1 file, reset it and pause it at the start.
This is how I've implemented it:
private void OnMediaEndedCommand()
{
if (GeneralSettings.PauseOnLastFrame)
{
MediaPlayer.SetMediaState(MediaPlayerStates.Pause);
return;
}
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
This is contained inside the ViewModel of the main window, where the media element is and GeneralSettings.PauseOnLastFrame is a boolean property.
This command is binded as follows:
<MediaElement ....>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="MediaEnded">
<ia:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>
</MediaElement>
It works but it's awful, how should I go about implementing such setting system in an elegant way? Some settings might not be boolean, they might have multiple options, some might be applied only on startup and others, as the one illustrated above, event based.
Based on the information and sample code you provided, I would suggest
Approach - 1
A tightly couple ViewModel with System.Configuration.ApplicationSettingsBase and you can mention all you properties in ViewModel and map single of them with a separate application setting property. You can use your settings directly in biding afterwards e.g. : {x:Static Settings.Default.Whatevs}. Othe "Save" button click event or main window close event, you can save all you settings e.g. : Settings.Default.Save();
Approach - 2
A better approach, I would suggest / prefer (if I am developing this app) is to develop a wrapper class (e.g.: SettingProvider) that implement an inheritance (e.g: ISettingProvider) which uncovers all you settings as separate properties and also have a save method which saves all setting values. You can use this wrapper class into your ViewModel to handle all the commands and setting values in better way.
The benefit of this approach is the if you decide to change you setting to database , you need not to make change to you ViewModel as all job is done in SettingProvider class.
I am not sure but based on viewing your code, I assume that you used Approach-1. Please put you comments and any feedback to this answer. I would like to know what you think and may be you have got more simple and interesting way of achieving this.
UPDATE-1
Example
Enum for showing you demo
public enum MediaStatus
{
Playing = 0,
Stopped = 1,
Paused = 2
}
Interface
public interface ISettingProvider
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Wrapper Class
public class SettingProvider : ISettingProvider
{
private double volumne;
public double Volumne // read-write instance property
{
get
{
return volumne;
}
set
{
volumne = value;
Settings.Default.Volumne = volumne;
}
}
private string lastMediaUrl;
public string LastMediaUrl // read-write instance property
{
get
{
return lastMediaUrl;
}
set
{
lastMediaUrl = value;
Settings.Default.LastMediaUrl = lastMediaUrl;
}
}
private MediaStatus playingMediaStatus;
public MediaStatus PlayingMediaStatus // read-write instance property
{
get
{
return playingMediaStatus;
}
set
{
playingMediaStatus = value;
Settings.Default.PlayingMediaStatus = (int)playingMediaStatus;
}
}
public void SaveSettings()
{
Settings.Default.Save();
}
//Constructor
public SettingProvider()
{
this.Volumne = Settings.Default.Volumne;
this.LastMediaUrl = Settings.Default.LastMediaUrl;
this.PlayingMediaStatus = (MediaStatus)Settings.Default.PlayingMediaStatus;
}
}
ViewModelBase Class
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
CommandHandler Class
public class CommandHandler : ICommand
{
public event EventHandler CanExecuteChanged { add { } remove { } }
private Action<object> action;
private bool canExecute;
public CommandHandler(Action<object> action, bool canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute;
}
public void Execute(object parameter)
{
action(parameter);
}
}
ViewModel
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
public double Volumne
{
get
{
return objSettingProvider.Volumne;
}
set
{
objSettingProvider.Volumne = value;
OnPropertyChanged("Volumne");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand saveSettingButtonCommand;
public ICommand SaveSettingButtonCommand
{
get
{
return saveSettingButtonCommand ?? (saveSettingButtonCommand = new CommandHandler(param => saveSettings(param), true));
}
}
private void saveSettings()
{
objSettingProvider.SaveSettings();
}
}
UPDATE-2
public interface ISettingProvider
{
bool PauseOnLastFrame;
bool IsAutoPlay;
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
public class SettingProvider : ISettingProvider
{
private bool pauseOnLastFrame;
public bool PauseOnLastFrame // read-write instance property
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
Settings.Default.PauseOnLastFrame = volumne;
}
}
private bool isAutoPlay;
public bool IsAutoPlay // read-write instance property
{
get
{
return isAutoPlay;
}
set
{
isAutoPlay = value;
Settings.Default.IsAutoPlay = volumne;
}
}
}
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
MediaStatus PlayingMediaStatus
{
get
{
return objSettingProvider.PlayingMediaStatus;
}
set
{
if(value == MediaStatus.Paused)
MediaPlayer.Pause();
if(value == MediaStatus.Playing)
MediaPlayer.Play();
if(value == MediaStatus.Stopped)
MediaPlayer.Stop();
objSettingProvider.PlayingMediaStatus = (int)value;
OnPropertyChanged("PlayingMediaStatus");
}
}
private string currentMediaFile;
public string CurrentMediaFile
{
get
{
return currentMediaFile;
}
set
{
currentMediaFile = value;
MediaPlayer.Stop();
MediaPlayer.Current = currentMediaFile;
if(objSettingProvider.IsAutoPlay)
MediaPlayer.Play();
OnPropertyChanged("CurrentMediaFile");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand onMediaEndedCommand;
public ICommand OnMediaEndedCommand
{
get
{
return onMediaEndedCommand ?? (onMediaEndedCommand = new CommandHandler(param => onMediaEnded(param), true));
}
}
private void onMediaEnded()
{
if(objSettingProvider.PauseOnLastFrame)
{
PlayingMediaStatus = MediaStatus.Paused;
}
else if(PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
PlayingMediaStatus = MediaStatus.Stopped;
}
else
{
CurrentMediaFile = PlayListViewModel.FilesCollection.MoveNext();
}
}
}
NOTE: This is the detailed example I put here and also avoid some syntax error or naming error if I missed somewhere. Please correct it.
I am not aware which media player settings you are using. I took some sample properties. This is just an example of structure you can implement for you application. You may need to alter more code to implement this structure.
An elegant way to implement this IMHO would be to use a dependency injection container, this will provide great flexibility while allowing you to completely separate concerns (i.e. the settings implementation from your view models and custom controls).
There are many DI frameworks out there, for my example I will use simple injector because it is free (open source), simple and fast but you can apply the same principle to other frameworks (Unity, Ninject, etc..).
Start by creating an interface for your settings service, for example:
public interface ISettingsService
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Then add your implementation for the service, the beauty of using DI is that you can change the implementation at anytime or completely replace it and your application will continue to work as usual.
Let's say you want to use application settings, here is your service:
public class SettingsServiceFromApplication : ISettingsService
{
public double Volume
{
get
{
return Properties.Settings.Volume;
}
}
[...]
}
Or let's say you want to use a database to store your settings:
public class SettingsServiceFromDb : ISettingsService
{
public double Volume
{
get
{
return MyDb.Volumen;
}
}
[...]
}
Then you can use a DI container to specify which implementation to use:
Start by installing the library using NuGet:
Install-Package SimpleInjector -Version 4.0.12
You need a way to share your container throughout the application, I usually just go with a static class that I initialize when starting the app:
using Container = SimpleInjector.Container;
namespace YourNamespace
{
public class Bootstrapper
{
internal static Container Container;
public static void Setup()
{
//Create container and register services
Container = new Container();
//Let's specify that we want to use SettingsServiceFromApplication
Container.Register<ISettingsService, SettingsServiceFromApplication>();
//You can use your bootstrapper class to initialize other stuff
}
}
You need to call Setup when starting the App, the best place is in the App constructor:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper.Setup();
}
}
So now you have an app wide depedency injection container that you can use to request "services" (specific implementations of an interface).
To get the settings implementation in your view models you could simply call the container as follows:
// This will return an instance of SettingsServiceFromApplication
ISettingsService settingsService = Bootstrapper.Container.GetInstance<ISettingsService>();
double volumen = settingsService.Volume;
To make it easier to work with, I usually create a base view model that will allow to get services more easyly, for example:
public abstract BaseViewModel
{
private ISettingsService _settings;
protected ISettingsService GeneralSettings
{
get
{
if (_settings == null)
_settings = Bootstrapper.Container.GetInstance<ISettingsService>();
return _settings;
}
}
}
Every view model inheriting from this class will have access to the settings:
public class YourViewModel : BaseViewModel
{
private void OnMediaEndedCommand()
{
if (GeneralSettings.PauseOnLastFrame)
{
MediaPlayer.SetMediaState(MediaPlayerStates.Pause);
return;
}
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
}
As you can see the code is the same as your code! But now the settings are coming from your container. Where is the elegance? Well, let's say that one year from now someone decides that you will store your settings in a database, what do you need to change in your code?
Container.Register<ISettingsService, SettingsServiceFromDb>();
A single line. Everything else should work as usual.
As well as view models, you could use this mechanism in your own controls:
public class MyMediaElement : UserControl //Or MediaElement and instead of commands you can override real events in the control code behind, this does not break the MVVM pattern at all, just make sure you use depedency properties if you need to exchange data with your view models
{
private void OnMediaEndedCommand()
{
//Get your settings from your container, do whatever you want to do depending on the settings
[...]
}
}
Then just use your control in your Views / ViewModels:
<local:MyMediaElement />
Yep, that's all you need because you handle everything in your User / Custom control, your view models doesn't need to care about how you handle settings in the control.
There are many options you can use to register containers, I recommend you take a look at the docs:
https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html
I think maybe you are looking for an interface approach?
public interface IMediaEndedHandler
{
bool AlternateHandling(MediaPlayer player);
}
public class NullMediaEndedHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
return false;
}
}
public class PauseOnLastFrameHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
player.SetMediaState(MediaPlayerStates.Pause);
return true;
}
}
public class GeneralSettings
{
private bool pauseOnLastFrame;
private bool PauseOnLastFrame
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
MediaEndedHandler = value
? new PauseOnLastFrameHandler()
: new NullMediaEndedHandler();
}
}
public IMediaEndedHandler MediaEndedHandler = new NullMediaEndedHandler();
}
Then:
private void OnMediaEndedCommand()
{
if (GeneralSettings.MediaEndedHandler.AlternateHandling(MediaPlayer))
return;
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
This way, if your setting is, for example. an enum instead of a bool, you can specify a different implementation of the interface for each possible value.
I have a question about how and where to load a large amount of data with ViewModel in WPF .NET 4.0 (so no async/await :/ ).
Here is my ViewModel:
public class PersonsViewModel : ViewModelBase
{
private readonly IRepository<Person> _personRepository;
private IEnumerable<Person> _persons;
public IEnumerable<Person> Persons
{
get { return _persons; }
private set { _persons = value; OnPropertyChanged("Persons"); }
}
public PersonsViewModel(IRepository<Person> personRepository)
{
if (personRepository == null)
throw new ArgumentNullException("personRepository");
_personRepository = personRepository;
}
}
This ViewModel is used in a Window and I need to load all the persons when the Window opens. I thought of many solutions but I can't figure which is the best (or maybe there's a better way to do this). I have two contraints:
- all the data must be loaded in another thread because it can take seconds to load (huge amount of data in the database) and I don't want to freeze the UI.
- the ViewModel must be testable.
--=[ First solution: Lazy loading ]=--
public class PersonsViewModel : ViewModelBase
{
private IEnumerable<Person> _persons;
public IEnumerable<Person> Persons
{
get
{
if (_persons == null)
_persons = _personRepository.GetAll();
return _persons;
}
}
}
I don't like this solution because the data is loaded in the main thread.
--=[ Second solution: Loaded event ]=--
public class PersonsViewModel : ViewModelBase
{
// ...
private Boolean _isDataLoaded;
public Boolean IsDataLoaded
{
get { return _isDataLoaded; }
private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
}
public void LoadDataAsync()
{
if(this.IsDataLoaded)
return;
var bwLoadData = new BackgroundWorker();
bwLoadData.DoWork +=
(sender, e) => e.Result = _personRepository.GetAll();
bwLoadData.RunWorkerCompleted +=
(sender, e) =>
{
this.Persons = (IEnumerable<Person>)e.Result;
this.IsDataLoaded = true;
};
bwLoadData.RunWorkerAsync();
}
}
public class PersonWindow : Window
{
private readonly PersonsViewModel _personsViewModel;
public PersonWindow(IRepository<Person> personRepository)
{
_personsViewModel = new PersonsViewModel(personRepository);
this.Loaded += PersonWindow_Loaded;
}
private void PersonWindow_Loaded(Object sender, RoutedEventArgs e)
{
this.Loaded -= PersonWindow_Loaded;
_personsViewModel.LoadDataAsync();
}
}
I don't really like this solution because it forces the user of the ViewModel to call the LoadDataAsync method.
--=[ Third solution: load data in the ViewModel constructor ]=--
public class PersonsViewModel : ViewModelBase
{
// ...
public PersonsViewModel(IRepository<Person> personRepository)
{
if (personRepository == null)
throw new ArgumentNullException("personRepository");
_personRepository = personRepository;
this.LoadDataAsync();
}
private Boolean _isDataLoaded;
public Boolean IsDataLoaded
{
get { return _isDataLoaded; }
private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
}
public void LoadDataAsync()
{
if(this.IsDataLoaded)
return;
var bwLoadData = new BackgroundWorker();
bwLoadData.DoWork +=
(sender, e) => e.Result = _personRepository.GetAll();
bwLoadData.RunWorkerCompleted +=
(sender, e) =>
{
this.Persons = (IEnumerable<Person>)e.Result;
this.IsDataLoaded = true;
};
bwLoadData.RunWorkerAsync();
}
}
In this solution, the user of the ViewModel don't need to call an extra method to load data, but it violates the Single Responsability Principle as Mark Seeman says in his book "Dependency Injection" : "Keep the constructor free of any logic. The SRP implies that members should do only one thing, and now that we use the constructor to inject DEPENDENCIES, we should prefer to keep it free of other concerns".
Any ideas to resolve this problem in a proper way?
Difficult to give an accurate answer without knowing how you tie your ViewModels to your Views.
One practice is to have a "navigation aware" ViewModel (a ViewModel which implements a certain interface like INavigationAware and have your navigation service call this method when it instantiates the ViewModel/View and tie them together. It's the way how Prism's FrameNavigationService works.
i.e.
public interface INavigationAware
{
Task NavigatedTo(object param);
Task NavigatedFrom(...)
}
public class PersonWindow : ViewModelBase, INavigationAware
{
}
and implement your initialization code within NavigatedTo which would be called from the navigation service, if the ViewModel implements INavigationAware.
Prism for Windows Store Apps References:
INavigationAware
FrameNavigationService => Look at the NavigateToCurrentViewModel Method
I have designed MMVM pattern in C#. My GUI has different button. Each button is for particular command. These commands are derived from CommandsBase Class. Each command runs on seperate thread by calling CommandExecute. There are several commands like CommandRunMode1, CommandRunMode2, commandDiagnosys etc. Now new requirement has been arised to abort command. I am trying to write CommandAbort Class. And the problem is how to abort already executing command when ABORT button is pressed on GUI (i.e. stop other thread in the halfway from CommandAbort class thread).
enter code here
#region COMMAND_Base
public abstract class CommandsBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected delegate void NoArgsDelegate();
protected delegate void OneArgDelegate(string arg1);
protected delegate void TwoArgDelegate(string arg1, bool arg2);
protected ViewModelBase ParentViewModel;
private StatusIndicator _isEnableState;
string _uiText;
bool _uiEnable;
RelayCommand _command;
protected Dispatcher _dispatcher;
private readonly int _responseDelay = 2000; // milliseconds
#region PROPERTIES
public CommandText CommandText { get; set; } // Ui Text
public CommandStatusIndicator CommandStatus { get; set; } // background color
public StatusIndicator IsEnableState
{
get { return _isEnableState; }
set
{
_isEnableState = value;
OnChanged("Status");
}
}
public string UiText
{
get { return _uiText; }
set
{
_uiText = value;
OnChanged("UiText");
}
}
public bool UiEnabled
{
get
{
return _uiEnable;
}
set
{
_uiEnable = value;
OnChanged("UiEnabled");
}
}
public ICommand Command
{
get
{
if (_command == null)
{
_command = new RelayCommand(param => this.CommandExecute(), param => this.CommandCanExecute);
}
return _command;
}
}
public int NumberOfAttempts;
public int ResponseDelay
{
get
{
return _responseDelay;
}
}
#endregion
protected CommandsBase()
{
}
protected void UpdateUi(string text)
{
UiText = text;
}
protected void UpdateUi(bool enabled)
{
UiEnabled = enabled;
}
protected void UpdateUi(string text, bool enabled)
{
UiText = text;
UiEnabled = enabled;
}
#region COMMAND_EXECUTION
public virtual void CommandExecute()
{
NoArgsDelegate commandExecution = new NoArgsDelegate(CommandExecuteInAThread);
commandExecution.BeginInvoke(null, null);
}
protected abstract void CommandExecuteInAThread();
public abstract bool CommandCanExecute { get; }
#endregion
public virtual void OnChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
region COMMAND_RunMode1
public class CommandRunMode1 : CommandsBase
{
public CommandRunMode1(string uiText, bool isEnabled, ViewModelBase parentViewModel)
{
UiEnabled = isEnabled;
ParentViewModel = parentViewModel;
_dispatcher = Dispatcher.CurrentDispatcher;
UiText = uiText;
IsEnableState = new StatusIndicator(null, null);
}
#region COMMAND_EXECUTION
public override bool CommandCanExecute
{
get
{
return true;
}
}
/// <summary>
/// This is a method run asynchronously, so that executing a command might not stop the UI events
/// </summary>
protected override void CommandExecuteInAThread()
{
// Transmit command to Mode1
ApplicationLayer.TransmitString("START1");
while (Display.CurrentScreent != DisplayController.CurrentScreen.ST1SCREEN);
ApplicationLayer.TransmitString("MODE1ENTER");
while (Display.CurrentScreent != DisplayController.CurrentScreen.MD1SCREEN);
ApplicationLayer.TransmitString("PROCESSCREEN");
while (Display.CurrentScreent != DisplayController.CurrentScreen.PROCESSCREEN);
}
#endregion
}
endregion
region COMMAND_RunMode2
public class CommandRunMode2 : CommandsBase
{
public CommandRunMode2(string uiText, bool isEnabled, ViewModelBase parentViewModel)
{
UiEnabled = isEnabled;
ParentViewModel = parentViewModel;
_dispatcher = Dispatcher.CurrentDispatcher;
UiText = uiText;
IsEnableState = new StatusIndicator(null, null);
}
#region COMMAND_EXECUTION
public override bool CommandCanExecute
{
get
{
return true;
}
}
/// <summary>
/// This is a method run asynchronously, so that executing a command might not stop the UI events
/// </summary>
protected override void CommandExecuteInAThread()
{
// Transmit command to Mode2
ApplicationLayer.TransmitString("START2");
while (Display.CurrentScreent != DisplayController.CurrentScreen.ST2SCREEN);
ApplicationLayer.TransmitString("MODE2ENTER");
while (Display.CurrentScreent != DisplayController.CurrentScreen.MD2SCREEN);
ApplicationLayer.TransmitString("PROCESSCREEN");
while (Display.CurrentScreent != DisplayController.CurrentScreen.PROCESSCREEN);
}
#endregion
}
endregion
region COMMAND_Abort
public class CommandAbort : CommandsBase
{
public CommandAbort (string uiText, bool isEnabled, ViewModelBase parentViewModel)
{
UiEnabled = isEnabled;
ParentViewModel = parentViewModel;
_dispatcher = Dispatcher.CurrentDispatcher;
UiText = uiText;
IsEnableState = new StatusIndicator(null, null);
}
#region COMMAND_EXECUTION
public override bool CommandCanExecute
{
get
{
return true;
}
}
/// <summary>
/// This is a method run asynchronously, so that executing a command might not stop the UI events
/// </summary>
protected override void CommandExecuteInAThread()
{
// Transmit command to Abort currently running command
ApplicationLayer.TransmitString("ABRT");
}
#endregion
}
endregion
Cancellation of a thread should always be cooperative.
What do i mean by that?
The code running in your thread should periodically check to see if it should continue. This may be a boolean somewhere. If cancellation is required, simply cleanup the resources you are using and return.
volatile bool IsCancelled = false;
void DoWork()
{
while(!IsCancelled)
{
//do work
}
}
When is this flag set?
Perhaps you have a Cancel button. Pushing this would trigger an event handler. This event handler sets the flag. The next time your threaded code checks the flag, it will cancel the operation. This is why it is called cooperative cancellation. 2 threads work together to make it happen.
Im afraid you have an architectural challenge to overcome
In MVVM, commands are the pipeline in which user interaction is communicated to the view model. The View model should react to this command by 'doing to the work' by calling the appropriate methods on other classes ( your model/foundation layer/business objects...use whichever words you prefer). You will need an implementation of DelegateCommand or RelayCommand.
User clicks Button
Button executes command
Command invokes method on view model
View model calls your domain classes to do the work
Why structure it this way? Now you can have your cancellation flag in the view model. This is one of the things a view model is for - storing state!
If you are able to, I recommend using .Net's Task Parallel Library. It supports cooperative cancellation for free!
To sum up. I strongly recommend you move the serial code out of the command classes. It is possible to make it work with your design but this is not good practice :(
I've been using WPF for a while but I'm new to Commands, but would like to start using them properly for once. Following a code example, I've established a separate static Commands class to hold all of my commands, and it looks like this.
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Should be set to true if an item is selected in the datagrid.
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
My problem is that although the command is going to be bound to a Button control in MainWindow.xaml, the OpenDocument_CanExecute method needs to look at a DataGrid in MainWindow.xaml to see if an item is selected.
How can I wire things up such that the method can see the DataGrid?
SOLUTION
Inspired by Ken's reply (thanks again!), I put the following in place, which works perfectly.
MainWindow.xaml.cs
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += delegate
{
DataContext = ViewModel.Current;
Commands.BindCommands(this);
};
}
}
ViewModel.cs
public class ViewModel
{
private static ViewModel _current;
public static ViewModel Current
{
get { return _current ?? (_current = new ViewModel()); }
set { _current = value; }
}
public object SelectedItem { get; set; }
}
Commands.cs
public static class Commands
{
public static RoutedUICommand OpenDocument { get; set; }
static Commands()
{
OpenDocument = new RoutedUICommand("Open Document", "OpenDocument", typeof(Commands));
}
public static void BindCommands(Window window)
{
window.CommandBindings.Add(new CommandBinding(OpenDocument, OpenDocument_Executed, OpenDocument_CanExecute));
}
private static void OpenDocument_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ViewModel.Current.SelectedItem != null;
}
private static void OpenDocument_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
ICommand implementations work best in the MVVM pattern:
class ViewModel : INotifyPropertyChanged {
class OpenDocumentCommand : ICommand {
public bool CanExecute(object parameter) {
return ViewModel.ItemIsSelected;
}
public OpenDocumentCommand(ViewModel viewModel) {
viewModel.PropertyChanged += (s, e) => {
if ("ItemIsSelected" == e.PropertyName) {
RaiseCanExecuteChanged();
}
};
}
}
private bool _ItemIsSelected;
public bool ItemIsSelected {
get { return _ItemIsSelected; }
set {
if (value == _ItemIsSelected) return;
_ItemIsSelected = value;
RaisePropertyChanged("ItemIsSelected");
}
}
public ICommand OpenDocument {
get { return new OpenDocumentCommand(this); }
}
}
Obviously, I left out a whole bunch of stuff. But this pattern has worked well for me in the past.
why even implement a command if you are tightly coupling it to UI implementation? Just respond to datagrid.SelectionChanged and code in what supposed to happen.
Otherwise, put it in the ViewModel. Have the ViewModel monitor it's state and evaluate when CanExe is true.
Edit
On the other hand, you can pass a parameter to your command, as well as Exe() & CanExe() methods
//where T is the type you want to operate on
public static RoutedUICommand<T> OpenDocument { get; set; }
If you are doing an MVVM solution, this would be the perfect time to implement a publish / subscribe aggregator that allows controls to "talk" to each other. The gist behind it is that the datagrid would publish an event, 'Open Document'. Subsequent controls could subscribe to the event and react to the call to 'Open Document'. The publish / subscribe pattern prevents tightly coupling the datagrid and the control. Do some searches for event aggregators and I think you'll be on your way.