I'm using Caliburn Micro framework in a WPF application and I need to bind a collection to ItemsSource of a DatGrid.
Please consider below code:
Class
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public ObservableCollection<Subject> Subjects;
}
public class Subject
{
public string Title{ get; set; }
}
View Model
public class PersonViewModel : Screen
{
private Person _person;
public Person Person
{
get { return _person; }
set
{
_person = value;
NotifyOfPropertyChange(() => Person);
NotifyOfPropertyChange(() => CanSave);
}
}
....
}
View
<UserControl x:Class="CalCompose.ViewModels.PersonView" ...ommited... >
<Grid Margin="0">
<TextBox x:Name="Person_Id" HorizontalAlignment="Left" Height="23" Margin="10,52,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="Person_Name" HorizontalAlignment="Left" Height="23" Margin="10,90,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<DataGrid ItemsSource="{Binding Person_Subjects}" Margin="10,177,0,0"></DataGrid>
</Grid>
</UserControl>
Problem 1:
When I run the application the TextBoxes are getting right values, but the data grid haven't populated.
Here i'm using deep property binding technique using convention "ClassName_PropertyName".
Problem 2
When I change the value of 'Name' property the
NotifyOfPropertyChange(() => Person) is never called. I would like to call the guard method when the text changes in the Name field.
Can anyone suggest me a simple solution to overcome this issues?
Thanks in advance.
Implement PropertyChangedBase on the Person class, then for Name we can write
private string name;
public string Name
{
get { return name; }
set
{
if (name == value)
return;
name = value;
NotifyOfPropertyChange(() => Name);
}
}
For the binding to the DataGrid, dont use the "deep binding", merely use
<DataGrid ItemsSource="{Binding Person.Subjects}" ...
I hope this helps.
Related
So, I have a UserControl which contains 2 comboboxes, which I want to be filled from a dictionary. So ComboBoxA gets filled with dictionary keys, and ComboBoxB get filled with the dictionary[ComboBoxA selected item]. How can I achieve that using MVVM? Category is basically int, and Parameter is a string.
My code so far:
Model
public class CategoryUserControlModel
{
public Dictionary<Category, List<Parameter>> parametersOfCategories { get; set;}
public Category chosenCategory { get; set; }
public Parameter chosenParameter { get; set; }
}
ViewModel
public class CategoryUserControlViewModel
{
public CategoryUserControlViewModel(CategoryUserControlModel controlModel)
{
Model = controlModel;
}
public CategoryUserControlModel Model { get; set; }
public Category ChosenCategory
{
get => Model.chosenCategory;
set
{
Model.chosenCategory = value;
}
}
public Parameter ChosenParameter
{
get => Model.chosenParameter;
set => Model.chosenParameter = value;
}
}
XAML
<Grid>
<ComboBox x:Name="Categories" HorizontalAlignment="Left" Margin="0,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.parametersOfCategories.Keys}"/>
<TextBlock x:Name="Text" HorizontalAlignment="Left" Height="15" Margin="0,-2,0,0" TextWrapping="Wrap" Text="Категория" VerticalAlignment="Top" Width="60"/>
<ComboBox x:Name="Parameter" HorizontalAlignment="Left" Margin="125,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.parametersOfCategories.Values}/>
<TextBlock x:Name="ParameterText" HorizontalAlignment="Left" Height="15" Margin="125,-2,0,0" TextWrapping="Wrap" Text="Параметр" VerticalAlignment="Top" Width="60"/>
</Grid>
</UserControl>
You are using inappropriate names for your model and viewmodel, they should never be related to a view, and your model should not have members with names that give the impression that the model need user interaction. Consider an improved version similar to this one:
public class CategoryWithParameterModel
{
public Dictionary<Category, List<Parameter>> ParametersOfCategories { get; set;}
public Category Category { get; set; }
public Parameter Parameter { get; set; }
}
Your viewmodel must implement INotifyPropertyChanged interface in order to inform the UI that it need to refresh bindings, this is not necessary for model since you are wrapping it in viewmodel. That is said, your viewmodel definition would become something like:
public class CategoryWithParameterViewModel : INotifyPropertyChanged
{ ... }
Next, since you want to bind to a list from the dictionary then your viewmodel have to expose a property which point to that list, let's call it AvailableParameters, so it should be defined like this:
public List<Parameter> AvailableParameters
{
get
{
if (Model.ParametersOfCategories.ContainsKey(ChosenCategory))
return Model.ParametersOfCategories[ChosenCategory];
else
return null;
}
}
This is the property that need to be bound to ItemsSource of second combobox named "Parameter" :)
However, the property ChosenCategory is not bound at all so you need to bind it to selected item of first combobox to be able to detect user choice which allow the viemodel to find the list of parameters, same thing applies to ChosenParameter, so here is the updated xaml code:
<Grid>
<ComboBox x:Name="Categories" HorizontalAlignment="Left" Margin="0,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Model.ParametersOfCategories.Keys}" SelectedItem="{Binding ChosenCategory}"/>
<TextBlock x:Name="Text" HorizontalAlignment="Left" Height="15" Margin="0,-2,0,0" TextWrapping="Wrap" Text="Категория" VerticalAlignment="Top" Width="60"/>
<ComboBox x:Name="Parameter" HorizontalAlignment="Left" Margin="125,14,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding AvailableParameters}" SelectedItem="{Binding ChosenParameter}"/>
<TextBlock x:Name="ParameterText" HorizontalAlignment="Left" Height="15" Margin="125,-2,0,0" TextWrapping="Wrap" Text="Параметр" VerticalAlignment="Top" Width="60"/>
</Grid>
Lastly, you have to notify UI when ChosenCategory has changed so for this you will need to raise PropertyChanged event for AvailableParameters. Implementing that will make the viewmodel become something like this:
public class CategoryWithParameterViewModel : INotifyPropertyChanged
{
public CategoryWithParameterViewModel(CategoryWithParameterModel model)
{
Model = model;
}
// This should be read-only
public CategoryWithParameterModel Model { get; /*set;*/ }
public Category ChosenCategory
{
get => Model.Category;
set
{
Model.Category = value;
OnPropertyChanged(nameof(AvailableParameters));
}
}
public Parameter ChosenParameter
{
get => Model.Parameter;
set => Model.Parameter = value;
}
public List<Parameter> AvailableParameters
{
get
{
if (Model.ParametersOfCategories.ContainsKey(ChosenCategory))
return Model.ParametersOfCategories[ChosenCategory];
else
return null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm using the MVVMLight Toolkit in my WPF project. All my ViewModels derive from the toolkit's ViewModelBase class, which implements the INotifyPropertyChanged for you and does all the notify work.
My current setup is extremely simple. I have a Person class with a single Name property.
public class Person
{
public string Name { get; set; }
}
My window has a TextBlock and a Button, and to the TextBlock I bind the Name property of the Person class object that I have. DataContext is set using a ViewModelLocator class.
<Window x:Class="BindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
Height="300" Width="300"
Title="MVVM Light Application"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Contact.Name}"/>
<Button Grid.Row="1" Content="Click" Command="{Binding ClickCommand}"/>
</Grid>
</Window>
In my ViewModel, I set the Name to Tom in the constructor, and change it when the button is clicked. I expect Tom to show up in the TextBlock when window is loaded (which it does), and to be changed to Jane when the button is clicked (which it doesn't).
public class MainViewModel : ViewModelBase
{
private Person _contact = new Person();
public Person Contact
{
get { return _contact; }
set { Set(ref _contact, value); }
}
public RelayCommand ClickCommand { get; private set; }
public MainViewModel(IDataService dataService)
{
Contact = new Person() { Name = "Tom" };
ClickCommand = new RelayCommand(Click);
}
public void Click()
{
Contact.Name = "Jane";
}
}
What am I missing?
Setting Contact.Name does not trigger the INotifyPropertyChanged.NotifyChanged event as the Contact setter is not executed. You could fix this using by one of the following techniques:
Implement INotifyPropertyChanged also in your model class
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, nameof(Name));
}
}
public event PropertyChangedHandler PropertyChanged;
}
Or wrap the PersonClass in a PersonViewModel
public class PersonViewModel : ViewModelBase
{
private readonly Person _person;
public PersonViewModel(Person person)
{
_person = person;
}
public string Name
{
get => _person.Name;
set
{
var name = _person.Name;
if (Set(ref name, value))
_person.Name = name;
}
}
}
and in MainViewModel:
private PersonViewModel _contactViewModel
public PersonViewModel Contact
{
get { return _contactViewModel ?? (_contactViewModel = new PersonViewModel(_contact)); }
}
Or create a separate ContactName property in the MainViewModel
... and using ContactName instead of Contact.Name in the binding and the Click event handler.
public string ContactName
{
get { return _contact.Name; }
set
{
var name = _contact.Name;
if (Set(ref name, value))
_contact.Name = name;
}
}
<Stackpanel>
<TextBox x:Name="txtid" Width="90" Text={Binding Name} Height="25"/>
<TextBox x:Name="txtname" Width="90" Text={Binding Age} Height="25" Margin="0 10 0 10"/>
<Button Command={Binding AddCommand} Content="Add"/>
<ListView ItemsSource={Binding StudentList}/>
</Stackpanel>
ViewModel
public class StudentViewModel : INotifyPropertyChanged
{
public StudentViewModel()
{
_studentList = new ObservableCollection<StudentDetails>();
LoadCommand();
}
private ObservableCollection<StudentDetails> _studentList;
public ObservableCollection<StudentDetails> StudentList
{
get { return _studentList; }
set
{
_studentList = value;
OnPropertyChanged("StudentList");
}
}
public StudentDetails SelectedItems { get; set; }
private string _name;
private int _age;
public string Name
{
get { return _name;}
set { _name = value; OnPropertyChanged("Name")}
}
public string Age
{
get { return _age;}
set { _age = value; OnPropertyChanged("Age")}
}
public ICommand AddCommand { get; set; }
public void LoadCommand()
{
AddCommand = new CustomCommand(Add, CanAdd);
}
private bool CanAdd(object obj)
{
return true;
}
private void Add(object obj)
{
StudentList.Add(new StudentDetails { Name = Name, Age = Age });
}}
Model
public class StudentDetails : INotifyPropertyChanged
{
private string _name;
private int _age;
public string Name
{
get { return _name;}
set { _name = value; OnPropertyChanged("Name")}
}
public string Age
{
get { return _age;}
set { _age = value; OnPropertyChanged("Age")}
}}
I have two textbox and a listview like above. how to do two way binding using MVVM?? which means the entered textbox value should add to listview and if i select the values in the listview then the selected value should bind to the same textbox so that i can update the values. how to do it??
I have tried to run your code. It has lots of errors. Anyways from your question I think you want to have a listview and when user selects a particular list item, the corresponding age and name is displayed in text boxes and if user want to update the data, you want to add it to the list. First create a list view with data template that binds to the class file properties.
Now also create a StudentDetails object and bind the SelectedItem of the list view to it.
When user selects a list item, you get SelectionChanged event. During this time update the property of 2 text boxes to display the selected list items data in them.
Now in the add button event handler, update the list data for corresponding selected item. Make sure you bind the listitemssource to an ObservableCollection
I would have different view models for an individual student and for the student list. Name and Age properties don't actually belong to the list.
I used MVVM Light syntax for the example:
StudentViewModel
public class StudentViewModel : ViewModelBase
{
private string _name;
private int _age;
public string Name
{
get { return _name; }
set { Set<string>(ref _name, value); }
}
public int Age
{
get { return _age; }
set { Set<int>(ref _age, value); }
}
}
StudentView.xaml
<UserControl x:Class="MasterDetailExample.Views.StudentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MasterDetailExample.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MasterDetailExample.ViewModel"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock Text="Name: "/>
<TextBox Text="{Binding Name}" Width="150"/>
<TextBlock Text="Age: "/>
<TextBox Text="{Binding Age}" Width="20"/>
</WrapPanel>
</UserControl>
Now StudentsViewModel represents the student list:
public class StudentsViewModel : ViewModelBase
{
private ObservableCollection<StudentViewModel> _studentList;
private StudentViewModel _selectedStudent;
public StudentsViewModel()
{
StudentList = new ObservableCollection<StudentViewModel>();
StudentList.Add(new StudentViewModel { Name = "Joe", Age = 21 });
StudentList.Add(new StudentViewModel { Name = "Jane", Age = 19 });
}
public ObservableCollection<StudentViewModel> StudentList
{
get { return _studentList; }
private set { _studentList = value; }
}
public StudentViewModel SelectedStudent
{
get { return _selectedStudent; }
set { Set<StudentViewModel>(ref _selectedStudent, value); }
}
}
** List view, StudentsView**
<UserControl x:Class="MasterDetailExample.Views.StudentsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:MasterDetailExample.Views"
xmlns:vm="clr-namespace:MasterDetailExample.ViewModel"
d:DesignHeight="300"
d:DesignWidth="500"
mc:Ignorable="d">
<UserControl.Resources>
<vm:StudentsViewModel x:Key="StudentsVm" />
</UserControl.Resources>
<DockPanel DataContext="{StaticResource StudentsVm}">
<ListView DockPanel.Dock="Left" Width="100" ItemsSource="{Binding StudentList}" SelectedItem="{Binding SelectedStudent}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Separator />
<views:StudentView DockPanel.Dock="Right" DataContext="{Binding SelectedStudent}"/>
</DockPanel>
</UserControl>
Directly setting the DataContext kind of smells,
<views:StudentView DockPanel.Dock="Right" DataContext="{Binding SelectedStudent}"/>
In more complicated example, you would either create a DependencyProperty for the SelectedStudent, or implement some messaging logic to communicate between different view models.
I've got a dilemma here. So I have mutiple expanders stacked on top of one another. Inside each expander is a ListBox, data bound, where each listitem displays a name of an object.
I've bound the search to filter the list items based on their name. However since I have two observable objects, the filtered items and unfiltered, the UI doesn't appear to get populated until someone searches. Whats the best way to fix this. I find it redundant to add items to both lists each time a new Person gets created. Using an mvvm approach.
The two collections are called People and PeopleFiltered. When i create people I add them to the list called People. When the search is applied it populates the PeopleFiltered list, which is the list the UI is bound to. How can I maintain this list be init to mimic People.
At the end of the day the PeopleFiltered collection should mimic People unless a search is being applied.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="400" Width="200">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Search:"/>
<TextBox Grid.Column="1" Background="Gold" Text="{Binding SearchString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
<StackPanel Grid.Row="1">
<Expander Header="People" IsExpanded="{Binding IsExpanded, Mode=OneWay}">
<ListView ItemsSource="{Binding PeopleFiltered}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Ellipse Width="8" Height="8" Fill="Green" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Name}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</StackPanel>
</Grid>
</Window>
MainWindowViewModel.cs
using System.Collections.ObjectModel;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Collections.Generic;
namespace WpfApplication1
{
public class MainWindowViewModel : NotifyBase
{
// search text
private string searchString;
public string SearchString
{
get { return searchString; }
set
{
this.searchString = value;
NotifyPropertyChanged("SearchString");
ApplySearchFilter();
}
}
private void ApplySearchFilter()
{
if (string.IsNullOrWhiteSpace(SearchString))
{
IsExpanded = false;
PeopleFiltered.Clear();
foreach (DisplayItem displayItem in People)
{
PeopleFiltered.Add(displayItem);
}
}
else
{
// open expanders and apply search
IsExpanded = true;
PeopleFiltered.Clear();
foreach (DisplayItem displayItem in People)
{
if (displayItem.Name.ToLowerInvariant().Contains(SearchString.ToLowerInvariant()))
{
PeopleFiltered.Add(displayItem);
}
}
}
}
// used to to open and close expanders
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
// data collections for each expander
private ObservableCollection<DisplayItem> people;
public ObservableCollection<DisplayItem> People
{
get { return people ?? (people = new ObservableCollection<DisplayItem>()); }
set
{
people = value;
NotifyPropertyChanged("People");
}
}
private ObservableCollection<DisplayItem> peopleFiltered;
public ObservableCollection<DisplayItem> PeopleFiltered
{
get { return peopleFiltered ?? (peopleFiltered = new ObservableCollection<DisplayItem>()); }
set
{
peopleFiltered = value;
NotifyPropertyChanged("PeopleFiltered");
}
}
// init
public MainWindowViewModel()
{
// People
People.Add(new DisplayItem() { Name="John" });
People.Add(new DisplayItem() { Name="Veta"});
People.Add(new DisplayItem() { Name="Sammy"});
People.Add(new DisplayItem() { Name = "Sarah" });
People.Add(new DisplayItem() { Name = "Leslie" });
People.Add(new DisplayItem() { Name = "Mike" });
People.Add(new DisplayItem() { Name = "Sherry" });
People.Add(new DisplayItem() { Name = "Brittany" });
People.Add(new DisplayItem() { Name = "Kevin" });
}
}
// class used to display all items
public class DisplayItem
{
public string Name { get; set; }
}
//observable object class
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here's a nice approach.
Create a Query property in your View Model, this will be bound to your filter TextBox.
private string _Query;
public string Query
{
get { return _Query; }
set
{
_Query = value;
Filter();
//Notify property changed.
}
}
One thing to note here is the Filter() method. This will be called every time the property changes, I'll get back to this later. Firstly, make sure your TextBox binding is TwoWay, it'll look like this:
<TextBox Text="{Binding Query}" ... />
In your View Model, you will need a collection for each ListBox.
private List<object> _Collection1; //The original collection
private List<object> _FilteredCollection1; //The filtered collection
public List<object> FilteredCollection1
{
get { return _FilteredCollection1; }
set
{
_FilteredCollection1 = value;
//Notify property changed.
}
}
//Some more collections
...
It's important to note here that there is a variable for the original unfiltered collection. This is important because we want to filter this list into a new collection, otherwise we'll just keep filtering over and over and eventually have nothing in the collection.
You'll need to bind the ItemsSource property in your ListBox to the collection(s).
<ListBox ItemsSource="{Binding FilteredCollection1}" ... />
Now, your Filter method can simply filter the _Collection1 variable into the FilteredCollection1 property.
private void Filter()
{
//Perform the filter here for all collections.
FilteredCollection1 = _Collection1.Where(x => x.Something == Query);
//Do the same for all other collections...
}
Note: The above Linq is just an example, I expect yours will be slightly more complicated than that, but you get the idea.
So. Whenever Query gets updated, the Filter collection will fire, update the FilteredCollection properties which will in turn call property changed and update the view.
Alternative Approach
Here's another way.
Instead of using a Filter method, you can instead put your filter code inside the get block in the FilteredCollection properties, like this:
public List<object> FilteredCollection1
{
get
{
return _Collection1.Where(...);
}
}
Then, in your Query property, simply call INotifyPropertyChanged for the collection:
private string _Query;
public string Query
{
get { return _Query; }
set
{
_Query = value;
//Notify property changed.
OnPropertyChanged("FilteredCollection1");
}
}
This will force the view to refresh the FilteredCollection1 property.
I am getting started with MVVM (using Caliburn.Micro) and have come across an issue which I'm not sure if I'm doing this correctly. I have a model MediaCacherConfig which represents a textfile that stores the data in json format. The model contains 2 lists of strings and one string by itself.
What I am struggling with is how to correctly set up the viewmodel and in particular the AddNewFolder() method. I'm not sure if I am raising the correct event and whether the viewmodel's representation is correct. I can see how to bind to a simple property, but binding to a collection seems a bit more of a head spinner as I am creating a whole new collection everytime an item (string) is added.
Furthermore, when I load an entirely new model I have to run the NotifyPropertyChanged() method on all the properties which doesn't make sense to me.
Any guidance is much appreciated.
public class MediaCacherConfig : IConfig
{
public string DatabaseFileName { get; set; }
public ICollection<string> FoldersToScan { get; set; }
public ICollection<string> ExtensionsToIgnore { get; set; }
}
I have a viewmodel MediaCacherConfigViewModel:
public class MediaCacherConfigViewModel : PropertyChangedBase
{
private MediaCacherConfig Model { get; set; }
public string DatabaseFileName
{
get { return Model.DatabaseFileName; }
set
{
Model.DatabaseFileName = value;
NotifyOfPropertyChange(() => DatabaseFileName);
}
}
public BindableCollection<string> FoldersToScan
{
get
{
return new BindableCollection<string>(Model.FoldersToScan);
}
set
{
Model.FoldersToScan = value;
NotifyOfPropertyChange(() => FoldersToScan);
}
}
public BindableCollection<string> ExtensionsToIgnore
{
get
{
return new BindableCollection<string>(Model.ExtensionsToIgnore);
}
set
{
Model.ExtensionsToIgnore = value;
NotifyOfPropertyChange(() => ExtensionsToIgnore);
}
}
/* Constructor */
public MediaCacherConfigViewModel()
{
LoadSampleConfig();
}
/* Methods */
public void LoadSampleConfig()
{
MediaCacherConfig c = new MediaCacherConfig();
string sampleDatabaseFileName = "testing.config";
List<string> sampleFoldersToScan = new List<string>();
sampleFoldersToScan.Add("A");
sampleFoldersToScan.Add("B");
sampleFoldersToScan.Add("C");
List<string> sampleExtensionsToIgnore = new List<string>();
sampleExtensionsToIgnore.Add("txt");
sampleExtensionsToIgnore.Add("mov");
sampleExtensionsToIgnore.Add("db");
sampleExtensionsToIgnore.Add("dat");
c.DatabaseFileName = sampleDatabaseFileName;
c.FoldersToScan = sampleFoldersToScan;
c.ExtensionsToIgnore = sampleExtensionsToIgnore;
Model = c;
NotifyOfPropertyChange(() => DatabaseFileName);
NotifyOfPropertyChange(() => FoldersToScan);
NotifyOfPropertyChange(() => ExtensionsToIgnore);
}
public void AddNewFolder()
{
Model.FoldersToScan.Add("new one added");
NotifyOfPropertyChange(() => FoldersToScan);
}
public void SaveConfig()
{
ConfigTools.Configure(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Cacher", "Config"));
ConfigTools.SaveConfig(Model,"sampleconfig.txt");
}
public void LoadConfig()
{
ConfigTools.Configure(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Cacher", "Config"));
MediaCacherConfig m = ConfigTools.LoadConfig<MediaCacherConfig>("sampleconfig.txt") as MediaCacherConfig;
Model = m;
NotifyOfPropertyChange(() => DatabaseFileName);
NotifyOfPropertyChange(() => FoldersToScan);
NotifyOfPropertyChange(() => ExtensionsToIgnore);
}
}
And here is my view:
<UserControl x:Class="MediaCacher.Views.MediaCacherConfigView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="413" Width="300">
<Grid MinWidth="300" MinHeight="300" Background="LightBlue" Margin="0,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="409*"/>
<RowDefinition Height="4*"/>
</Grid.RowDefinitions>
<TextBox x:Name="DatabaseFileName" TextWrapping="Wrap" Margin="10,64,10,0" HorizontalAlignment="Center" Width="280" Height="42" VerticalAlignment="Top"/>
<ListBox x:Name="FoldersToScan" HorizontalAlignment="Left" Height="145" Margin="10,111,0,0" VerticalAlignment="Top" Width="280"/>
<ListBox x:Name="ExtensionsToIgnore" HorizontalAlignment="Left" Height="145" Margin="10,261,0,0" VerticalAlignment="Top" Width="280"/>
<Button x:Name="AddNewFolder" Content="Add" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="87" Height="49"/>
<Button x:Name="LoadConfig" Content="Load" HorizontalAlignment="Left" Margin="102,10,0,0" VerticalAlignment="Top" Width="96" Height="49"/>
<Button x:Name="SaveConfig" Content="Save" HorizontalAlignment="Left" Margin="203,10,0,0" VerticalAlignment="Top" Width="87" Height="49"/>
</Grid>
First, here you are returning a brand new collection every time, so obviously nothing gets persisted.
public BindableCollection<string> FoldersToScan
{
get
{
return new BindableCollection<string>(Model.FoldersToScan);
}
set
{
Model.FoldersToScan = value;
NotifyOfPropertyChange(() => FoldersToScan);
}
}
Secondly, your AddFolder method should belong in your ViewModel. When you Add a string to your already existing collection the fact that it is a BindingCollection should fire off an event to your View automatically that a new Item was added.
This is how I would do it. This is obviously an example for demonstration purposes, please add everything else you need. Youd ideall want to pass EventArgs and note I am not implementing INotifyPorpertyChanged because I don't have time to write it all out. Also I am using ObservableCollection but you can use your BindableCollection.
The point of this example is to show you how to manage your ViewModel - > Model communcation. Technically speaking your View -> ViewModel should talk through a CommandPattern.
public class YourViewModel
{
private readonly YourModel model;
private ObservableCollection<string> foldersToScan = new ObservableCollection<string>();
public ObservableCollection<string> FoldersToScan
{
get { return this.foldersToScan; }
}
public YourViewModel(YourModel model)
{
this.model = model;
this.model.OnItemAdded += item => this.foldersToScan.Add(item);
}
public void AddFolder(string addFolder) //gets called from view
{
this.model.AddFolder(addFolder); //could be ICommand using Command Pattern
}
}
public class YourModel
{
private readonly List<string> foldersToScan;
public IEnumerable<string> FoldersToScan
{
get { return this.foldersToScan; }
}
public event Action<string> OnItemAdded;
public YourModel()
{
this.foldersToScan = new List<string>();
}
public void AddFolder(string folder)
{
this.foldersToScan.Add(folder);
this.RaiseItemAdded(folder);
}
void RaiseItemAdded(string folder)
{
Action<string> handler = OnItemAdded;
if (handler != null) handler(folder);
}
}