I'm pretty new to WPF and MVVM concept and i'm trying to solve probably stupid issue. I have MainViewModel where I control what should be seen on the screen according to pressed buttons in the menu.
class MainViewModel : ObservableObject
{
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand SettingsViewCommand { get; set; }
public HomeViewModel HomeVM { get; set; }
public SettingsViewModel SettingsVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVM = new HomeViewModel();
SettingsVM = new SettingsViewModel();
CurrentView = HomeVM;
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVM;
});
SettingsViewCommand = new RelayCommand(o =>
{
CurrentView = SettingsVM;
});
}
One of my Views is SettingsView. In this View I have a button, which should check if the connection string is allright. And because I am going to use kinda lot SQL commands my thought was, to put all code regarding SQL into one folder. So basically the project is MVVM(file)>View(file),ViewModel(file).... and SQL(file)>....
Sadly when I add to the SettingsView sql, the app falls because of 'Object reference not set to an instance of an object'
In the SettingsView is:
public partial class SettingsView : UserControl
{
private readonly HSQLTestConnection _SQLTestConnection;
public SettingsView(HSQLTestConnection SQLTestConnection)
{
InitializeComponent();
_SQLTestConnection = SQLTestConnection;
And button is like this:
_SQLTestConnection.TryConnectionString(ConnectionString);
Model is empty:
class SettingsViewModel
{
public SettingsViewModel()
{
}
}
Interface for SQL is:
public interface HSQLTestConnection
{
void TryConnectionString(string ConnectionString);
}
And SQL function:
public class SQLTestConnection : HSQLTestConnection
{
public void TryConnectionString(string ConnectionString)
{
//do something
}
}
App does not show any error and was working and changing Views pretty fine, I think the issue is with public SettingsView(HSQLTestConnection SQLTestConnection) But I could not find how to solve this.
Since I am going to have multiple classes for SQL, I wanted to solve it this way. It used to work in different app I wrote, but I was not using RellayCommand, which I wanted to try.
Thank you for your help in advance.
I see that SettingsView class extends the UserControl class. This is problematic because
you say you want to follow the MVVM design pattern but your user control code (SettingsView) has a reference to your database connection (HSQLTestConnection) in the constructor. You should not mix your view code with your database code.
your SettingsView is initialized by the framework so where is it supposed to get the value for the HSQLTestConnection parameter in the constructor?
In any case, your view should not have any database logic in it, at least not if you want to follow MVVM.
What you could do is to initiate your database connection in the view model of the settings view SettingsViewModel. Not great, but somewhat better.
Since you mentioned there will be "kinda lot SQL commands", I would suggest you separate this code into a dedicated service, and then use this service in your view models where applicable.
public interface IMyDatabaseService
{
bool TestConnection();
IEnumerable<SomeDataObject> GetData();
...
}
You would then pass this interface where needed:
public class SettingsViewModel : INotifyPropertyChanged
{
private readonly IMyDatabaseService databaseService;
private bool connectionSuccessful;
public RelayCommand TestConnectionCommand { get; set; }
public bool ConnectionSuccessful
{
get => connectionSuccessful;
set
{
connectionSuccessfull = value;
var propertyName = nameof(ConnectionSuccessful);
PropertyChanged?.Invoke(this, new (propertyName));
}
}
public SettingsViewModel(IMyDatabaseService databaseService)
{
this.databaseService = databaseService;
TestConnectionCommand = new RelayCommand(_ =>
ConnectionSuccessful = databaseService.TestConnection()
);
...
}
}
public class MainViewModel
{
...
public MainViewModel()
{
...
IMyDatabaseService databaseService = new MyDatabaseService();
SettingsVM = new SettingsViewModel(databaseService);
...
}
...
}
Notice the TestConnectionCommand as well as the ConnectionSuccesful boolean.
The command will call your database service and this is what you will bind your button to.
Similarly, the boolean can be bound to in your view to show the connection state. Since this value can change whenever you run the command, you have to invoke PropertyChanged to let the framework know the value has changed. In this case, the invocation is done in the setter.
Assuming your data context is set correctly, in your SettingsView view you would now have:
...
<Button Command="{Binding TestConnectionCommand}"> Test </Button>
<TextBlock Text="Connection failed!">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ConnectionSuccessfull}" Value="True">
<Setter Property="Text" Value="Connection successful!" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
...
I used a text block with a data trigger to show the connection state, but you can use the boolean and the data trigger to do whatever you wish. Maybe show a modal dialog or something.
Better still would be to use some sort of dependency injection to inject your database service and other dependencies as well as making the database code async so that your UI won't freeze, but that will be for the future you to figure out.
Related
I've been trying to do the following thing in WPF:
A window with a login-page and a home-page.
Upon successfully authenticating a user, the window should display the home-page.
It should work by using the native WPF dependency injection framework.
But also...
There might be a page 3, 4, 5 and each of these pages should be able to call one another.
And maybe each of these pages could have pages inside them that can also call each other.
So the solution should be able to work with nested pages and navigations if possible.
What I have:
So, after looking for solutions in the stack forum I ended up with this composition approach.
Starting by the App.xaml, all services and viewmodels are initialized and the main window receives its viewmodel by injection:
private void ConfigureServices(ServiceCollection services)
{
services.AddSingleton<MainWindow>();
//ViewModels
services.AddSingleton<MainViewModel>();
services.AddSingleton<AuthViewModel>();
services.AddSingleton<HomeViewModel>();
}
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.DataContext = serviceProvider.GetService<MainViewModel>();
mainWindow.Show();
}
Then, the mainViewModel receives by injection every other viewmodel and stores them in a property.
public class MainViewModel
{
public IPageViewModel SelectedPage {get; set; } //PropertyChanged() removed for brevity.
public ObservableCollection<IPageViewModel> Pages {get; set;}
public MainViewModel(AuthViewModel authViewModel, HomeViewModel homeViewModel)
{
this.Pages = new ObservableCollection<IPageViewModel>() { authViewModel, homeViewModel};
this.SelectedPage = this.Pages.First();
}
}
All page viewmodels inherit from this interface so they can be retrieved from the collection by name and then added as the SelectedPage when needed.
public interface IPageViewModel : INotifyPropertyChanged
{
public string PageTitle { get; set; }
}
The window has a content control with a property content bound to the SelectedPage so it's updated.
<Window>
<ContentControl Content="{Binding SelectedPage}" />
</Window>
And it knows which view to use for each viewmodel by these data templates.
<Application.Resources>
<DataTemplate DataType="{x:Type vm:AuthViewModel}">
<views:AuthView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<views:HomeView />
</DataTemplate>
</Application.Resources>
But then... I noticed that this won't work, I can only call changes on the SelectedPage from within the mainViewModel.
public class AuthViewModel : BaseViewModel
{
public AuthViewModel() { }
public void AttemptLogin() {
// how
SelectedPage = Pages[1];
}
}
Issues
I could perhaps inject the mainviewmodel in all child models, but that would not look good and in fact from the start a lot of things are kind of a mess.
For example, I have to:
Add a service viewmodel for every viewmodel I create to the app.xaml.
Add each one of them as a parameter of the mainwindow viewmodel which looks ugly.
I'm probably doing this very wrong, I need help.
There are many possible solutions. A simple one is to introduce an event.
I also recommend to move and restrict the responsibility to select view models to the MainViewModel. Other page models should not be aware of the flow like who selects who. Otherwise this would add a too tight coupling, which is avoidable at this point.
public class MainViewModel
{
public IPageViewModel SelectedPage { get; set; }
private Dictionary<string, IPageViewModel> Pages { get; }
public MainViewModel(AuthViewModel authViewModel, HomeViewModel homeViewModel)
{
authViewModel.AuthenticationPassed += OnAuthenticationSuccessfull;
this.Pages = new Dictionary<string, IPageViewModel>()
{
{ nameof(AuthViewModel), authViewModel },
{ nameof(HomeViewModel), homeViewModel }
};
this.SelectedPage = this.Pages[nameof(AuthViewModel)];
}
public OnAuthenticationSuccessfull(object sender, EventArgs e)
{
(sender as AuthViewModel).AuthenticationPassed -= OnAuthenticationSuccessfull;
this.SelectedPage = this.Pages[nameof(HomeViewModel)];
}
}
class AuthViewModel
{
public event EventHandler AuthenticationPassed { get; }
...
}
I have a UserControl view which require data to be pulled from the database to set the ObservableCollection in order to render a DataGrid.
I want to set the ObservableCollection when the UserControl is loaded not when the view model is constructed. Also, I prefer to asynchronously set the ObservableCollection so the UserContror opens immediacy then then the data-grid is set when the data becomes available.
In my view model I create a public method called SetVendors() which fetches the data from the database and sets ObservableCollection just before the view is notified. However, I am using the view class directly to set the SetVendors() method which sound like a bad practice for some reason to me.
Questions
Is it possible to set the data on view-load directly from the view-model? If so, how? Also, is it possible to set the data asynchronously so the view loads quick?
Here is my ViewModel code
public class VendorsListViewModel : ViewModel
{
protected IUnitOfWork UnitOfWork { get; set; }
public int CurrentPage { get; set; }
public int ModelsPerPage { get; set; }
public IPagedList PageMeta { get; set; }
private ObservableCollection<Vendor> _Vendors { get; set; }
public ObservableCollection<Vendor> Vendors
{
get
{
return _Vendors;
}
set
{
_Vendors = value;
NotifyPropertyChanged();
}
}
public VendorsListViewModel(IUnitOfWork unitOfWork)
: base()
{
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 20;
}
public VendorsListViewModel()
: this(new UnitOfWork())
{
}
public void SetVendors()
{
var vendors = UnitOfWork.Vendors.Get(CurrentPage, ModelsPerPage);
Vendors = new ObservableCollection<Vendor>(vendors);
PageMeta = vendors.GetMetaData();
}
}
This is how currently set the data from the code behind the view which sound like a bad practice to me.
public partial class Index : UserControl
{
public Index()
{
InitializeComponent();
}
// This event is called from the view when the form is loaded using `Loaded="ViewLoaded"` XAML code
private void ViewLoaded(object sender, EventArgs e)
{
(DataContext as VendorsListViewModel).SetVendors();
}
}
Loading the data from the view´s code behind is indeed a bad practice as you are breaking the MVVM separation of concerns.
A better way to do that would be using a command, or, more precisely an event to command, check this article for a nice explanation.
To achieve this you can use the System.Windows.Interactivity library that comes with Blend for Visual Studio SDK for .NET, if you don't have it just run the Visual Studio installer and select it.
Then add a reference to the library by going to Add reference -> Assemblies -> Extensions.
Once you have this, things are simple really, you need to just:
First add the namespace to your View:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then, bind the event to a command in your view model like this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Now in your ViewModel we have a little problem, we need to provide a command to be called when the event is triggered, a command is an implementation of the ICommand interface, all MVVM frameworks out there will provide you with ready to roll ICommand implementations so you can either use one of these frameworks or you can implement ICommand yourself if you just need it for this particular example, wait! you don't need to implement it, you can just get it from this question ,-)
For my example, however, I will use MVVM Light toolkit which I recommend you to use as it will relief you from most of the MVVM annoyances that you will need to deal with in any real-world application.
You can get MVVM Light from NuGet:
Install-Package MvvmLight -Version 5.4.1
So now you can use MvvmLight implementation of ICommand which is called RelayCommand, you would do so in your ViewModel as follows:
[...]
public ICommand LoadedCommand { get; private set; }
public VendorsListViewModel(IUnitOfWork unitOfWork) : base() {
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 25;
LoadedCommand = new RelayCommand(this.Loaded);
}
private void Loaded()
{
SetVendors();
}
As per your second question: absolutely, you can load the data asynchronously if you want to. This is a whole different question though, take a look at this article for more info: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/
I'm trying to develop an easy MVVM project that it has two windows:
The first window is a text editor, where I bind some properties such as FontSize or BackgroundColor:
<TextBlock FontSize="{Binding EditorFontSize}"></TextBlock>
its DataContext is MainWindowViewModel:
public class MainWindowViewModel : BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
The second window is the option window, where I have an slider for changing the font size:
<Slider Maximum="30" Minimum="10" Value="{Binding EditorFontSize }" ></Slider>
its DataContext is OptionViewModel:
public class OptionViewModel: BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
My problem is that I have to get the value of the slider in the option window and then I have to modify the FontSize property of my TextBlock with this value. But I don't know how to send the font size from OptionViewModel to MainViewModel.
I think that I should use:
A shared model
A model in MainWindowViewModel and a ref of this model in OptionViewModel
Other systems like notifications, messages ...
I hope that you can help me. It's my first MVVM project and English isn't my main language :S
Thanks
Another option is to store such "shared" variables in a SessionContext-class of some kind:
public interface ISessionContext: INotifyPropertyChanged
{
int EditorFontSize { get;set; }
}
Then, inject this into your viewmodels (you are using Dependency Injection, right?) and register to the PropertyChanged event:
public class MainWindowViewModel
{
public MainWindowViewModel(ISessionContext sessionContext)
{
sessionContext.PropertyChanged += OnSessionContextPropertyChanged;
}
private void OnSessionContextPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "EditorFontSize")
{
this.EditorFontSize = sessionContext.EditorFontSize;
}
}
}
There are many ways to communicate between view models and a lot of points what the point is the best. You can see how it is done:
using MVVMLight
in Prism
by Caliburn
In my view, the best approach is using EventAggregator pattern of Prism framework. The Prism simplifies MVVM pattern. However, if you have not used Prism, you can use Rachel Lim's tutorial - simplified version of EventAggregator pattern by Rachel Lim.. I highly recommend you Rachel Lim's approach.
If you use Rachel Lim's tutorial, then you should create a common class:
public static class EventSystem
{...Here Publish and Subscribe methods to event...}
And publish an event into your OptionViewModel:
eventAggregator.GetEvent<ChangeStockEvent>().Publish(
new TickerSymbolSelectedMessage{ StockSymbol = “STOCK0” });
then you subscribe in constructor of another your MainViewModel to an event:
eventAggregator.GetEvent<ChangeStockEvent>().Subscribe(ShowNews);
public void ShowNews(TickerSymbolSelectedMessage msg)
{
// Handle Event
}
The Rachel Lim's simplified approach is the best approach that I've ever seen. However, if you want to create a big application, then you should read this article by Magnus Montin and at CSharpcorner with an example.
Update: For versions of Prism later than 5 CompositePresentationEvent is depreciated and completely removed in version 6, so you will need to change it to PubSubEvent everything else can stay the same.
I have done a big MVVM application with WPF. I have a lot of windows and I had the same problem. My solution maybe isn't very elegant, but it works perfectly.
First solution: I have done one unique ViewModel, splitting it in various file using a partial class.
All these files start with:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : DevExpress.Mvvm.ViewModelBase
{
...
}
}
I'm using DevExpress, but, looking your code you have to try:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : BindableBase
{
...
}
}
Second solution: Anyway, I have also a couple of different ViewModel to manage some of these windows. In this case, if I have some variables to read from one ViewModel to another, I set these variables as static.
Example:
public static event EventHandler ListCOMChanged;
private static List<string> p_ListCOM;
public static List<string> ListCOM
{
get { return p_ListCOM; }
set
{
p_ListCOM = value;
if (ListCOMChanged != null)
ListCOMChanged(null, EventArgs.Empty);
}
}
Maybe the second solution is simplier and still ok for your need.
I hope this is clear. Ask me more details, if you want.
I'm not a MVVM pro myself, but what I've worked around with problems like this is,
having a main class that has all other view models as properties, and setting this class as data context of all the windows, I don't know if its good or bad but for your case it seems enough.
For a more sophisticated solution see this
For the simpler one,
You can do something like this,
public class MainViewModel : BindableBase
{
FirstViewModel firstViewModel;
public FirstViewModel FirstViewModel
{
get
{
return firstViewModel;
}
set
{
firstViewModel = value;
}
}
public SecondViewModel SecondViewModel
{
get
{
return secondViewModel;
}
set
{
secondViewModel = value;
}
}
SecondViewModel secondViewModel;
public MainViewModel()
{
firstViewModel = new FirstViewModel();
secondViewModel = new SecondViewModel();
}
}
now you have to make another constructor for your OptionWindow passing a view model.
public SecondWindow(BindableBase viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
this is to make sure that both windows work on the same instance of a view model.
Now, just wherever you're opening the second window use these two lines
var window = new SecondWindow((ViewModelBase)this.DataContext);
window.Show();
now you're passing the First Window's view model to the Second window, so that they work on the same instance of the MainViewModel.
Everything is done, just you've to address to binding as
<TextBlock FontSize="{Binding FirstViewModel.EditorFontSize}"></TextBlock>
<TextBlock FontSize="{Binding SecondViewModel.EditorFontSize}"></TextBlock>
and no need to say that the data context of First window is MainViewModel
In MVVM, models are the shared data store. I would persist the font size in the OptionsModel, which implements INotifyPropertyChanged. Any viewmodel interested in font size subscribes to PropertyChanged.
class OptionsModel : BindableBase
{
public int FontSize {get; set;} // Assuming that BindableBase makes this setter invokes NotifyPropertyChanged
}
In the ViewModels that need to be updated when FontSize changes:
internal void Initialize(OptionsModel model)
{
this.model = model;
model.PropertyChanged += ModelPropertyChanged;
// Initialize properties with data from the model
}
private void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(OptionsModel.FontSize))
{
// Update properties with data from the model
}
}
I'm new to WPF and I've come up with a solution to this and I'm curious of more knowledgeable people's thoughts about what's right and wrong with it.
I have an Exams tab and a Templates tab. In my simple proof of concept, I want each tab to "own" an Exam object, and to be able to access the other tab's Exam.
I define the ViewModel for each tab as static because if it's a normal instance property, I don't know how one tab would get the actual instance of the other tab. It feels wrong to me, though it's working.
namespace Gui.Tabs.ExamsTab {
public class GuiExam: INotifyPropertyChanged {
private string _name = "Default exam name";
public string Name {
get => _name;
set {
_name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName="") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class ExamsHome : Page {
public ExamsHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly ExamsTabViewModel ViewModel = new ExamsTabViewModel();
}
public class ExamsTabViewModel {
public GuiExam ExamsTabExam { get; set; } = new GuiExam() { Name = "Exam from Exams Tab" };
public GuiExam FromTemplatesTab { get => TemplatesHome.ViewModel.TemplatesTabExam; }
}
}
namespace Gui.Tabs.TemplatesTab {
public partial class TemplatesHome : Page {
public TemplatesHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly TemplatesTabViewModel ViewModel = new TemplatesTabViewModel();
}
public class TemplatesTabViewModel {
public GuiExam TemplatesTabExam { get; set; } = new GuiExam() { Name = "Exam from Templates Tab" };
public GuiExam FromExamTab { get => ExamsHome.ViewModel.ExamsTabExam; }
}
}
And then everything is accessible in the xaml:
TemplatesHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="From Exams Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromExamTab.Name}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="Local Content:"/>
<TextBox Text="{Binding TemplatesTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
ExamsHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="Local Content:"/>
<TextBox Text="{Binding ExamsTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="From Templates Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromTemplatesTab.Name}"/>
</StackPanel>
I am really struggling to understand binding. I know there are loads of other threads with much the same title as this one, but they're all trying to do something more complex than I am, and all the answers assume a whole pile of stuff that I just don't get :(
I'm trying to display a dynamically updated message log. I've defined a Message class:
public class Message
{
public DateTime Timestamp { get; private set; }
public string Value { get; private set; }
public int Severity { get; private set; }
public Message(string value, int severity)
{
Timestamp = DateTime.Now;
Value = value;
Severity = severity;
}
}
I've defined a MessageLog class as simply:
public class MessageLog: ObservableCollection<Message>
{
public MessageLog(): base()
{ }
}
In my MainWindow constructor I have a Log property:
public MessageLog Log { get; private set; }
In the MainWindow constructor I initialise Log:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
// and so on
}
In the XAML for the main window I have:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding MessageLog}" IsEnabled="False"/>
Now if I add Message instances to the MessageLog I expected to see them appear in the ListBox. They don't. What have I missed?
Thanks in advance (and if you can point me somewhere that explains bindings clearly -- especially the view that XAML has of the code and where it can look for things -- then many more thanks on top. At the moment I'm using Matthew McDonald's "Pro WPF 4.5 in C#" and I'm just not getting it.)
Change your constructor:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
}
to this:
public MainWindow()
{
InitializeComponent();
Log = new Model.MessageLog(); // <- This line before setting the DataContext
DataContext = this;
}
Explanation:
Setting properties after having set the DataContext requires your class to implement INotifyPropertyChanged and raise change notifications after properties are set.
Since you're setting the DataContext before setting the property, the value of this.Log is null at the time of DataBinding, and WPF is never notified that it ever changed.
That being said, you don't usually put Data inside UI Elements (such as Window). The accepted and recommended approach to WPF is MVVM, where you usually create a ViewModel and set that as the Window's DataContext:
public class MyViewModel
{
public MessageLog Log {get;set;}
public MyViewModel()
{
Log = new MessageLog();
}
}
Window Constructor:
public MainWindow
{
DataContext = new MyViewModel();
}
Your collection property name is Log which is what you should be binding to in ItemsSource property; and if you have not done a typo in your question then you are binding wrongly to MessageLog, and change Binding as below:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding Log}" IsEnabled="False"/>
For more information and learning on Data Binding in WPF (4.5), see MSDN Data Binding Overview
The datacontext of the view must be the viewmodel.
I'm playing around with MVVM, getting to know the stuff involved in the pattern. The first application I'm writing is a very small application which basically displays 2 settings from the App.Config.
My goal is to be able to write to this app.config when the button is clicked.
My problem lies in the fact I don't know exactly how to wire up a command to delegate this work to, or if this is even the way to go.
My App.config is very straight forward:
<configuration>
<appSettings>
<add key="duration" value="100" />
<add key="operators" value="10" />
</appSettings>
</configuration>
The model looks like:
get
{
// try to parse the setting from the configuration file
// if it fails return the default setting 0
int durationSetting = 0;
Int32.TryParse(ConfigurationManager.AppSettings["duration"], out durationSetting);
return durationSetting;
}
set
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings.Remove("duration");
config.AppSettings.Settings.Add("duration", Convert.ToString(value));
ConfigurationManager.RefreshSection("appSettings");
config.Save();
}
}
So, the model is responsible for the actual data access, this is what we want, right?
Furthermore I have a ViewModel (ViewModelBase implements INotifyPropertyChanged):
public class SettingsViewModel : ViewModelBase
{
private Settings Settings { get; set; }
private SaveCommand saveCommand = new SaveCommand();
public ICommand SaveCommand
{
get
{
return saveCommand;
}
}
public SettingsViewModel(Settings settings)
{
if (settings == null)
throw new ArgumentNullException("Settings", "Settings cannot be null");
Settings = settings;
}
public int Duration
{
get { return Settings.Duration; }
set
{
if (Settings.Duration != value)
{
Settings.Duration = value;
RaisePropertyChanged("Duration");
}
}
}
The view is a xaml usercontrol, instantiated like this:
public partial class MainWindow : Window
{
public SettingsViewModel SettingsViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Settings settings = new Settings();
SettingsViewModel = new SettingsViewModel(settings);
}
}
Lastly there's a SaveCommand which implements ICommand, which is basically empty at this point. I've hooked up the command to a button in the view.
But basically, now what? What's the best way to handle the saving of the values?
Is the example I'm working on too contrived?
I would recommend using the very useful MVVM Light toolkit.
Basically, you will expose a ICommand public property that returns an instance of a RelayCommand:
private RelayCommand myCommand;
public ICommand MyCommand
{
get
{
return this.myCommand;
}
}
...
// in constructor of the view model:
this.myCommand = new RelayCommand(this.MyCommandImplementation);
...
private void MyCommandImplementation()
{
// Save your parameters here from your model
}
Something strange in your code is that you actually already save your settings in the setter of the public property named Duration.
What you could do instead (to prevent from saving each time the property is modified) is to simply use a private variable in your ViewModel:
private int duration;
public int Duration
{
get { return this.duration; }
set
{
if (this.duration != value)
{
this.duration = value;
RaisePropertyChanged("Duration");
}
}
}
So when you modify a UI field binded to your Duration property, you only update the private duration field. Thus you'll only save it to the app.config file in the MyCommandImplementation method.
private void MyCommandImplementation()
{
this.Settings.Duration = this.Duration;
}
Also note that your Settings management is a bit complicated (you remove then add again a settings, why?).
Last thing: in your view, you're currently using the view itself as datacontext. You have to specify your ViewModel instead:
this.SettingsViewModel = new SettingsViewModel(settings);
this.DataContext = this.SettingsViewModel;
Also, I don't think it's the role of your view to instantiate your model. I would instantiate Settings from the ViewModel instead.
All you need to do is to implement ICommand interface in a class and set SaveCommand property to an instance of this class. You can either you some third party command classes, like DelegateCommand of prism library or RelayCommand.
Refer to the following web page for a sample implementation of ICommand;
http://weblogs.asp.net/fredriknormen/archive/2010/01/03/silverlight-4-make-commanding-use-lesser-code.aspx
then the actions to be employed shall be registered in the command;
this.SaveCommand= new SaveCommand((o) =>
{
//change the model
});