I am using MVVM Light in a (pretty simple) WPF project.
I have a list of versions, and for each of them there is a button "activate" and "archive". Only one version can be active.
When clicking on "activate", the software must archive the currently active version, and activate the selected one.
How would you modelize this ? I'm currently using a pretty ugly solution : the selected version re-instantiates the "active version" and archives it, so obviously the previously-active version isn't "refreshed".
The main window contains a list of versions, displayed in a ListBox (see this question).
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
this.InstalledVersions = InstalledVersionViewModel.GetInstalledVersions();
}
public ObservableCollection<InstalledVersionViewModel> InstalledVersions { get; set; }
}
The InstalledVersionViewModel is (simplified) like this :
public class InstalledVersionViewModel : ViewModelBase
{
public InstalledVersionViewModel()
{
this.HandleActivateVersionCommand = new RelayCommand<RoutedEventArgs>(e => { this.ActivateVersion(); });
this.HandleArchiveVersionCommand = new RelayCommand<RoutedEventArgs>(e => { this.ArchiveVersion(); });
}
public string FolderPath { get; set; }
public RelayCommand<RoutedEventArgs> HandleActivateVersionCommand { get; private set; }
public RelayCommand<RoutedEventArgs> HandleArchiveVersionCommand { get; private set; }
public string VersionNumber { get; set; }
public static InstalledVersionViewModel GetCurrentVersion()
{
return GetVersionInfos(baseInstallPath); // returns the currently-active version
}
public static ObservableCollection<InstalledVersionViewModel> GetInstalledVersions()
{
var list = new ObservableCollection<InstalledVersionViewModel>();
// snip : fill the list from detected versions
return list;
}
private void ActivateVersion()
{
// snip
GetCurrentVersion().Archive();
// snip
}
private void ArchiveVersion()
{
// snip
}
}
The problem is in the ActivateVersion() method : I'm getting a new version instance to archive it, so obviously the version instance in the list is never aware of this change. But I don't know how to change the behavior to archive the version in the list instead. I'm pretty sure there should be either some kind of messaging system, a wrapper or an overarching structure, but I can't quite put my finger on it.
Thanks !
To me, it should be handled in the MainViewModel. For instance, add a property IsActive to your InstalledVersionViewModel, and subscribe to the PropertyChanged event from your MainViewModel. When the event is raised, browse your InstalledVersions list to find the previously active item, and call the Archive method on it.
To subscribe to the event, simply browse your list after creating it:
public MainWindowViewModel()
{
this.InstalledVersions = InstalledVersionViewModel.GetInstalledVersions();
foreach (var version in this.InstalledVersions)
{
version.PropertyChanged += this.VersionPropertyChanged;
}
}
Then, in the event, check which property has been changed:
private void VersionPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsActive")
{
var changedVersion = (Version)sender;
// Checks that the version has been activated
if (changedVersion.IsActive)
{
// Finds the previously active version and archive it
foreach (var version in this.InstalledVersions)
{
if (version.IsActive && version != changedVersion)
{
version.Archive();
}
}
}
}
}
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 have a problem. I created a Database with a table based on the following class:
public class Device
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
In my App.xaml.cs I do the following:
static List<Device> knownDeviceList;
public App()
{
InitializeComponent();
MyHandler();
MainPage = new NavigationPage(new Page1());
}
public Task MyHandler()
{
return GetKnownDevices();
}
private async Task Get()
{
KnownDeviceList = deviceDatabaseController.GetDevice();
}
public static List<KnownDevice> KnownDeviceList
{
get
{
if (knownDeviceList == null)
{
knownDeviceList = new List<KnownDevice>();
}
return knownDeviceList;
}
set
{
knownDeviceList = value;
}
}
After that code automatically runs, I have a filled KnownDeviceList.
Now in Page1 I made a ListView with a ViewModel that shows the devices from KnownDeviceList using bindings. On that same page I made a button to add a new device, so it takes you to Page2 using a NavigationPage. But when I write the new device to the database, I use: Navigation.PopToRootAsync(); to go back, but now I want the ListView to refresh and KnownDeviceList to get the devices from the database again, so my new device is in the ListView.
How can I achieve this, because I have no idea what to do after I added it!?
I problem here is that you are changing the collection. But not notifying the UI about it.
List<> will not notify the collection change on its own. Where as ObservableCollection does notify collection changes, like Add, Remove, etc. Hence the name ObservableCollection.
Using ObservableCollection<KnownDevice> instead of List<KnownDevice> could solve this issue.
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.
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.
I've been looking all over the web for example implementations of an MVC setup in .NET. I have found many examples but they all seem to differ in certain aspects. I have a book on Design Patterns that described that MVC was originated in Smalltalk so I read over several people discussing its implementation in that language. What follows is an example project I wrote utilizing what I gathered was a proper implementation but I've been confused by a few details.
One problem I run into is the proper order of the construction of the objects. Here is the impl in my Program.cs
Model mdl = new Model();
Controller ctrl = new Controller(mdl);
Application.Run(new Form1(ctrl, mdl));
The View:
Immediately I have a couple of issues I'm unsure of. First, if the view is supposed to only read data from the Model for updating, but contains a reference to it, what is stopping me from making the calls the controller does to the model from the view? Should a programmer just ignore the fact that they are exposed to the models member functions? Another thought I had, was perhaps the event that informs the view the model is updated, would send some sort of state object for the view to update itself with.
public interface IView
{
double TopSpeed { get; }
double ZeroTo60 { get; }
int VehicleID { get; }
string VehicleName { get; }
}
/// <summary>
/// Assume the form has the following controls
/// A button with a click event OnSaveClicked
/// A combobox with a selected index changed event OnSelectedIndexChanged
/// A textbox that displays the vehicles top speed named mTextTopSpeed
/// A textbox that displays the vehicles zero to 60 time named mTextZeroTo60
/// </summary>
public partial class Form1 : Form, IView
{
private IController mController;
private IModel mModel;
public Form1(IController controller, IModel model)
{
InitializeComponent();
mController = controller;
mController.SetListener(this);
mModel = model;
mModel.ModelChanged += new ModelUpdated(mModel_ModelChanged);
}
void mModel_ModelChanged(object sender, EventArgs e)
{
mTextTopSpeed.Text = mModel.TopSpeed.ToString();
mTextZeroTo60.Text = mModel.ZeroTo60.ToString();
}
public double TopSpeed { get { return Double.Parse(mTextTopSpeed.Text); } }
public double ZeroTo60 { get { return Double.Parse(mTextZeroTo60.Text); } }
public int VehicleID { get { return (int)mComboVehicles.SelectedValue; } }
public string VehicleName { get { return mComboVehicles.SelectedText; } }
#region Form Events
private void OnFormLoad(object sender, EventArgs e)
{
mComboVehicles.ValueMember = "Key";
mComboVehicles.DisplayMember = "Value";
mComboVehicles.DataSource = new BindingSource(mModel.VehicleList, null);
}
private void OnSelectedIndexChanged(object sender, EventArgs e)
{
mController.OnSelectedVehicleChanged();
}
private void OnSaveClicked(object sender, EventArgs e)
{
mController.OnUpdateVehicle();
}
#endregion
}
The Controller:
My only real issue with the way I have implemented the controller is that it seems a bit odd to me that is possible to construct the controller without definitely having a view assigned to it. I could ignore the view entirely but that would mean I would pass parameters to the controller's functions for updating the model which seems to miss the point entirely.
public interface IController
{
void OnUpdateVehicle();
void OnSelectedVehicleChanged();
void SetListener(IView view);
}
class Controller : IController
{
private IModel mModel;
private IView mView = null;
public Controller(IModel model)
{
mModel = model;
}
public void OnUpdateVehicle()
{
if(mView == null)
return;
mModel.UpdateVehicle(mView.VehicleID, mView.TopSpeed, mView.ZeroTo60);
}
public void SetListener(IView view)
{
mView = view;
}
public void OnSelectedVehicleChanged()
{
if (mView == null)
return;
mModel.SelectVehicle(mView.VehicleID);
}
}
The Model:
In my form, I have a combobox that is a list of the vehicles given in my pseudo database. I feel as though my form should actually implement multiple Views / Models because of this. A view specific to listing the possible vehicles with a corresponding controller / model, and a view for displaying information about the selected vehicle with its own controller / model.
public delegate void ModelUpdated(object sender, EventArgs e);
public interface IModel
{
event ModelUpdated ModelChanged;
void UpdateVehicle(int id, double topSpeed, double zeroTo60);
void SelectVehicle(int id);
double TopSpeed { get; }
double ZeroTo60 { get; }
IDictionary<int, string> VehicleList { get; }
}
// class for the sake of a pseudo database object
class Vehicle
{
public int ID { get; set; }
public string Name { get; set; }
public double TopSpeed { get; set; }
public double ZeroTo60 { get; set; }
public Vehicle(int id, string name, double topSpeed, double zeroTo60)
{
ID = id;
Name = name;
TopSpeed = topSpeed;
ZeroTo60 = zeroTo60;
}
}
class Model : IModel
{
private List<Vehicle> mVehicles = new List<Vehicle>()
{
new Vehicle(1, "Civic", 120.0, 5.0),
new Vehicle(2, "Batmobile", 9000.0, 1.0),
new Vehicle(3, "Tricycle", 5.0, 0.0)
};
private Vehicle mCurrentVehicle;
public Model()
{
mCurrentVehicle = mVehicles[0];
}
public event ModelUpdated ModelChanged;
public void OnModelChanged()
{
if (ModelChanged != null)
{
ModelChanged(this, new EventArgs());
}
}
public double TopSpeed { get { return mCurrentVehicle.TopSpeed; } }
public double ZeroTo60 { get { return mCurrentVehicle.ZeroTo60; } }
public IDictionary<int, string> VehicleList
{
get
{
Dictionary<int, string> vDict = new Dictionary<int, string>();
foreach (Vehicle v in mVehicles)
{
vDict.Add(v.ID, v.Name);
}
return vDict as IDictionary<int, string>;
}
}
#region Pseudo Database Calls
public void SelectVehicle(int id)
{
foreach (Vehicle v in mVehicles)
{
if (v.ID == id)
{
mCurrentVehicle = v;
OnModelChanged(); // send notification to registered views
break;
}
}
}
public void UpdateVehicle(int id, double topSpeed, double zeroTo60)
{
foreach (Vehicle v in mVehicles)
{
if (v.ID == id)
{
mCurrentVehicle.TopSpeed = topSpeed;
mCurrentVehicle.ZeroTo60 = zeroTo60;
OnModelChanged(); // send notification to registered views
break;
}
}
}
#endregion
}
In Conclusion of this tl;dr, I guess what I'm looking for, is some guidance on whether or not what I'm doing here represents a true MVC implementation and maybe for someone to shed some light on the aforementioned concerns. Any advice would be greatly appreciated.
We'll it depends on what you want to do. You currently have an implementation of the Supervising Controller. If you wish to remove the model from the view (and any databinding), you can implement a Passive View pattern instead. See this article for more differences.
(source: microsoft.com)
And Martin Fowler is king (GUI Architectures).